Merge branch 'abrensch:master' into rework-voicehint

This commit is contained in:
afischerdev 2023-12-10 14:19:49 +01:00 committed by GitHub
commit 4c310bf4b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1600 additions and 1650 deletions

View file

@ -0,0 +1,43 @@
package btools.router;
import java.io.BufferedWriter;
import java.io.StringWriter;
public class FormatCsv extends Formatter {
public FormatCsv(RoutingContext rc) {
super(rc);
}
@Override
public String format(OsmTrack t) {
try {
StringWriter sw = new StringWriter();
BufferedWriter bw = new BufferedWriter(sw);
writeMessages(bw, t);
return sw.toString();
} catch (Exception ex) {
return "Error: " + ex.getMessage();
}
}
public void writeMessages(BufferedWriter bw, OsmTrack t) throws Exception {
dumpLine(bw, MESSAGES_HEADER);
for (String m : t.aggregateMessages()) {
dumpLine(bw, m);
}
if (bw != null)
bw.close();
}
private void dumpLine(BufferedWriter bw, String s) throws Exception {
if (bw == null) {
System.out.println(s);
} else {
bw.write(s);
bw.write("\n");
}
}
}

View file

@ -0,0 +1,532 @@
package btools.router;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.Map;
import btools.mapaccess.MatchedWaypoint;
import btools.util.StringUtils;
public class FormatGpx extends Formatter {
public FormatGpx(RoutingContext rc) {
super(rc);
}
@Override
public String format(OsmTrack t) {
try {
StringWriter sw = new StringWriter(8192);
BufferedWriter bw = new BufferedWriter(sw);
formatAsGpx(bw, t);
bw.close();
return sw.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String formatAsGpx(BufferedWriter sb, OsmTrack t) throws IOException {
int turnInstructionMode = t.voiceHints != null ? t.voiceHints.turnInstructionMode : 0;
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
if (turnInstructionMode != 9) {
for (int i = t.messageList.size() - 1; i >= 0; i--) {
String message = t.messageList.get(i);
if (i < t.messageList.size() - 1)
message = "(alt-index " + i + ": " + message + " )";
if (message != null)
sb.append("<!-- ").append(message).append(" -->\n");
}
}
if (turnInstructionMode == 4) { // comment style
sb.append("<!-- $transport-mode$").append(t.voiceHints.getTransportMode()).append("$ -->\n");
sb.append("<!-- cmd idx lon lat d2next geometry -->\n");
sb.append("<!-- $turn-instruction-start$\n");
for (VoiceHint hint : t.voiceHints.list) {
sb.append(String.format(" $turn$%6s;%6d;%10s;%10s;%6d;%s$\n", hint.getCommandString(), hint.indexInTrack,
formatILon(hint.ilon), formatILat(hint.ilat), (int) (hint.distanceToNext), hint.formatGeometry()));
}
sb.append(" $turn-instruction-end$ -->\n");
}
sb.append("<gpx \n");
sb.append(" xmlns=\"http://www.topografix.com/GPX/1/1\" \n");
sb.append(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \n");
if (turnInstructionMode == 9) { // BRouter style
sb.append(" xmlns:brouter=\"Not yet documented\" \n");
}
if (turnInstructionMode == 7) { // old locus style
sb.append(" xmlns:locus=\"http://www.locusmap.eu\" \n");
}
sb.append(" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\" \n");
if (turnInstructionMode == 3) {
sb.append(" creator=\"OsmAndRouter\" version=\"1.1\">\n");
} else {
sb.append(" creator=\"BRouter-" + t.version + "\" version=\"1.1\">\n");
}
if (turnInstructionMode == 9) {
sb.append(" <metadata>\n");
sb.append(" <name>").append(t.name).append("</name>\n");
sb.append(" <extensions>\n");
sb.append(" <brouter:info>").append(t.messageList.get(0)).append("</brouter:info>\n");
if (t.params != null && t.params.size() > 0) {
sb.append(" <brouter:params><![CDATA[");
int i = 0;
for (Map.Entry<String, String> e : t.params.entrySet()) {
if (i++ != 0) sb.append("&");
sb.append(e.getKey()).append("=").append(e.getValue());
}
sb.append("]]></brouter:params>\n");
}
sb.append(" </extensions>\n");
sb.append(" </metadata>\n");
}
if (turnInstructionMode == 3 || turnInstructionMode == 8) { // osmand style, cruiser
float lastRteTime = 0;
sb.append(" <rte>\n");
float rteTime = t.getVoiceHintTime(0);
StringBuffer first = new StringBuffer();
// define start point
{
first.append(" <rtept lat=\"").append(formatILat(t.nodes.get(0).getILat())).append("\" lon=\"")
.append(formatILon(t.nodes.get(0).getILon())).append("\">\n")
.append(" <desc>start</desc>\n <extensions>\n");
if (rteTime != lastRteTime) { // add timing only if available
double ti = rteTime - lastRteTime;
first.append(" <time>").append("" + (int) (ti + 0.5)).append("</time>\n");
lastRteTime = rteTime;
}
first.append(" <offset>0</offset>\n </extensions>\n </rtept>\n");
}
if (turnInstructionMode == 8) {
if (t.matchedWaypoints.get(0).direct && t.voiceHints.list.get(0).indexInTrack == 0) {
// has a voice hint do nothing, voice hint will do
} else {
sb.append(first.toString());
}
} else {
sb.append(first.toString());
}
for (int i = 0; i < t.voiceHints.list.size(); i++) {
VoiceHint hint = t.voiceHints.list.get(i);
sb.append(" <rtept lat=\"").append(formatILat(hint.ilat)).append("\" lon=\"")
.append(formatILon(hint.ilon)).append("\">\n")
.append(" <desc>")
.append(turnInstructionMode == 3 ? hint.getMessageString() : hint.getCruiserMessageString())
.append("</desc>\n <extensions>\n");
rteTime = t.getVoiceHintTime(i + 1);
if (rteTime != lastRteTime) { // add timing only if available
double ti = rteTime - lastRteTime;
sb.append(" <time>").append("" + (int) (ti + 0.5)).append("</time>\n");
lastRteTime = rteTime;
}
sb.append(" <turn>")
.append(turnInstructionMode == 3 ? hint.getCommandString() : hint.getCruiserCommandString())
.append("</turn>\n <turn-angle>").append("" + (int) hint.angle)
.append("</turn-angle>\n <offset>").append("" + hint.indexInTrack).append("</offset>\n </extensions>\n </rtept>\n");
}
sb.append(" <rtept lat=\"").append(formatILat(t.nodes.get(t.nodes.size() - 1).getILat())).append("\" lon=\"")
.append(formatILon(t.nodes.get(t.nodes.size() - 1).getILon())).append("\">\n")
.append(" <desc>destination</desc>\n <extensions>\n");
sb.append(" <time>0</time>\n");
sb.append(" <offset>").append("" + (t.nodes.size() - 1)).append("</offset>\n </extensions>\n </rtept>\n");
sb.append("</rte>\n");
}
if (turnInstructionMode == 7) { // old locus style
float lastRteTime = t.getVoiceHintTime(0);
for (int i = 0; i < t.voiceHints.list.size(); i++) {
VoiceHint hint = t.voiceHints.list.get(i);
sb.append(" <wpt lon=\"").append(formatILon(hint.ilon)).append("\" lat=\"")
.append(formatILat(hint.ilat)).append("\">")
.append(hint.selev == Short.MIN_VALUE ? "" : "<ele>" + (hint.selev / 4.) + "</ele>")
.append("<name>").append(hint.getMessageString()).append("</name>")
.append("<extensions><locus:rteDistance>").append("" + hint.distanceToNext).append("</locus:rteDistance>");
float rteTime = t.getVoiceHintTime(i + 1);
if (rteTime != lastRteTime) { // add timing only if available
double ti = rteTime - lastRteTime;
double speed = hint.distanceToNext / ti;
sb.append("<locus:rteTime>").append("" + ti).append("</locus:rteTime>")
.append("<locus:rteSpeed>").append("" + speed).append("</locus:rteSpeed>");
lastRteTime = rteTime;
}
sb.append("<locus:rtePointAction>").append("" + hint.getLocusAction()).append("</locus:rtePointAction></extensions>")
.append("</wpt>\n");
}
}
if (turnInstructionMode == 5) { // gpsies style
for (VoiceHint hint : t.voiceHints.list) {
sb.append(" <wpt lon=\"").append(formatILon(hint.ilon)).append("\" lat=\"")
.append(formatILat(hint.ilat)).append("\">")
.append("<name>").append(hint.getMessageString()).append("</name>")
.append("<sym>").append(hint.getSymbolString().toLowerCase()).append("</sym>")
.append("<type>").append(hint.getSymbolString()).append("</type>")
.append("</wpt>\n");
}
}
if (turnInstructionMode == 6) { // orux style
for (VoiceHint hint : t.voiceHints.list) {
sb.append(" <wpt lat=\"").append(formatILat(hint.ilat)).append("\" lon=\"")
.append(formatILon(hint.ilon)).append("\">")
.append(hint.selev == Short.MIN_VALUE ? "" : "<ele>" + (hint.selev / 4.) + "</ele>")
.append("<extensions>\n" +
" <om:oruxmapsextensions xmlns:om=\"http://www.oruxmaps.com/oruxmapsextensions/1/0\">\n" +
" <om:ext type=\"ICON\" subtype=\"0\">").append("" + hint.getOruxAction())
.append("</om:ext>\n" +
" </om:oruxmapsextensions>\n" +
" </extensions>\n" +
" </wpt>\n");
}
}
for (int i = 0; i <= t.pois.size() - 1; i++) {
OsmNodeNamed poi = t.pois.get(i);
sb.append(" <wpt lon=\"").append(formatILon(poi.ilon)).append("\" lat=\"")
.append(formatILat(poi.ilat)).append("\">\n")
.append(" <name>").append(StringUtils.escapeXml10(poi.name)).append("</name>\n")
.append(" </wpt>\n");
}
if (t.exportWaypoints) {
for (int i = 0; i <= t.matchedWaypoints.size() - 1; i++) {
MatchedWaypoint wt = t.matchedWaypoints.get(i);
sb.append(" <wpt lon=\"").append(formatILon(wt.waypoint.ilon)).append("\" lat=\"")
.append(formatILat(wt.waypoint.ilat)).append("\">\n")
.append(" <name>").append(StringUtils.escapeXml10(wt.name)).append("</name>\n");
if (i == 0) {
sb.append(" <type>from</type>\n");
} else if (i == t.matchedWaypoints.size() - 1) {
sb.append(" <type>to</type>\n");
} else {
sb.append(" <type>via</type>\n");
}
sb.append(" </wpt>\n");
}
}
sb.append(" <trk>\n");
if (turnInstructionMode == 9
|| turnInstructionMode == 2
|| turnInstructionMode == 8
|| turnInstructionMode == 4) { // Locus, comment, cruise, brouter style
sb.append(" <src>").append(t.name).append("</src>\n");
sb.append(" <type>").append(t.voiceHints.getTransportMode()).append("</type>\n");
} else {
sb.append(" <name>").append(t.name).append("</name>\n");
}
if (turnInstructionMode == 7) {
sb.append(" <extensions>\n");
sb.append(" <locus:rteComputeType>").append("" + t.voiceHints.getLocusRouteType()).append("</locus:rteComputeType>\n");
sb.append(" <locus:rteSimpleRoundabouts>1</locus:rteSimpleRoundabouts>\n");
sb.append(" </extensions>\n");
}
// all points
sb.append(" <trkseg>\n");
String lastway = "";
boolean bNextDirect = false;
OsmPathElement nn = null;
String aSpeed;
for (int idx = 0; idx < t.nodes.size(); idx++) {
OsmPathElement n = t.nodes.get(idx);
String sele = n.getSElev() == Short.MIN_VALUE ? "" : "<ele>" + n.getElev() + "</ele>";
VoiceHint hint = t.getVoiceHint(idx);
MatchedWaypoint mwpt = t.getMatchedWaypoint(idx);
if (t.showTime) {
sele += "<time>" + getFormattedTime3(n.getTime()) + "</time>";
}
if (turnInstructionMode == 8) {
if (mwpt != null &&
!mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) {
sele += "<name>" + mwpt.name + "</name>";
}
}
boolean bNeedHeader = false;
if (turnInstructionMode == 9) { // trkpt/sym style
if (hint != null) {
if (mwpt != null &&
!mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) {
sele += "<name>" + mwpt.name + "</name>";
}
sele += "<desc>" + hint.getCruiserMessageString() + "</desc>";
sele += "<sym>" + hint.getCommandString(hint.cmd) + "</sym>";
if (mwpt != null) {
sele += "<type>Via</type>";
}
sele += "<extensions>";
if (t.showspeed) {
double speed = 0;
if (nn != null) {
int dist = n.calcDistance(nn);
float dt = n.getTime() - nn.getTime();
if (dt != 0.f) {
speed = ((3.6f * dist) / dt + 0.5);
}
}
sele += "<brouter:speed>" + (((int) (speed * 10)) / 10.f) + "</brouter:speed>";
}
sele += "<brouter:voicehint>" + hint.getCommandString() + ";" + (int) (hint.distanceToNext) + "," + hint.formatGeometry() + "</brouter:voicehint>";
if (n.message != null && n.message.wayKeyValues != null && !n.message.wayKeyValues.equals(lastway)) {
sele += "<brouter:way>" + n.message.wayKeyValues + "</brouter:way>";
lastway = n.message.wayKeyValues;
}
if (n.message != null && n.message.nodeKeyValues != null) {
sele += "<brouter:node>" + n.message.nodeKeyValues + "</brouter:node>";
}
sele += "</extensions>";
}
if (idx == 0 && hint == null) {
if (mwpt != null && mwpt.direct) {
sele += "<desc>beeline</desc>";
} else {
sele += "<desc>start</desc>";
}
sele += "<type>Via</type>";
} else if (idx == t.nodes.size() - 1 && hint == null) {
sele += "<desc>end</desc>";
sele += "<type>Via</type>";
} else {
if (mwpt != null && hint == null) {
if (mwpt.direct) {
// bNextDirect = true;
sele += "<desc>beeline</desc>";
} else {
sele += "<desc>" + mwpt.name + "</desc>";
}
sele += "<type>Via</type>";
bNextDirect = false;
}
}
if (hint == null) {
bNeedHeader = (t.showspeed || (n.message != null && n.message.wayKeyValues != null && !n.message.wayKeyValues.equals(lastway))) ||
(n.message != null && n.message.nodeKeyValues != null);
if (bNeedHeader) {
sele += "<extensions>";
if (t.showspeed) {
double speed = 0;
if (nn != null) {
int dist = n.calcDistance(nn);
float dt = n.getTime() - nn.getTime();
if (dt != 0.f) {
speed = ((3.6f * dist) / dt + 0.5);
}
}
sele += "<brouter:speed>" + (((int) (speed * 10)) / 10.f) + "</brouter:speed>";
}
if (n.message != null && n.message.wayKeyValues != null && !n.message.wayKeyValues.equals(lastway)) {
sele += "<brouter:way>" + n.message.wayKeyValues + "</brouter:way>";
lastway = n.message.wayKeyValues;
}
if (n.message != null && n.message.nodeKeyValues != null) {
sele += "<brouter:node>" + n.message.nodeKeyValues + "</brouter:node>";
}
sele += "</extensions>";
}
}
}
if (turnInstructionMode == 2) { // locus style new
if (hint != null) {
if (mwpt != null) {
if (!mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) {
sele += "<name>" + mwpt.name + "</name>";
}
if (mwpt.direct && bNextDirect) {
sele += "<src>" + hint.getLocusSymbolString() + "</src><sym>pass_place</sym><type>Shaping</type>";
// bNextDirect = false;
} else if (mwpt.direct) {
if (idx == 0)
sele += "<sym>pass_place</sym><type>Via</type>";
else
sele += "<sym>pass_place</sym><type>Shaping</type>";
bNextDirect = true;
} else if (bNextDirect) {
sele += "<src>beeline</src><sym>" + hint.getLocusSymbolString() + "</sym><type>Shaping</type>";
bNextDirect = false;
} else {
sele += "<sym>" + hint.getLocusSymbolString() + "</sym><type>Via</type>";
}
} else {
sele += "<sym>" + hint.getLocusSymbolString() + "</sym>";
}
} else {
if (idx == 0 && hint == null) {
int pos = sele.indexOf("<sym");
if (pos != -1) {
sele = sele.substring(0, pos);
}
if (mwpt != null && !mwpt.name.startsWith("from"))
sele += "<name>" + mwpt.name + "</name>";
if (mwpt != null && mwpt.direct) {
bNextDirect = true;
}
sele += "<sym>pass_place</sym>";
sele += "<type>Via</type>";
} else if (idx == t.nodes.size() - 1 && hint == null) {
int pos = sele.indexOf("<sym");
if (pos != -1) {
sele = sele.substring(0, pos);
}
if (mwpt != null && mwpt.name != null && !mwpt.name.startsWith("to"))
sele += "<name>" + mwpt.name + "</name>";
if (bNextDirect) {
sele += "<src>beeline</src>";
}
sele += "<sym>pass_place</sym>";
sele += "<type>Via</type>";
} else {
if (mwpt != null) {
if (!mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) {
sele += "<name>" + mwpt.name + "</name>";
}
if (mwpt.direct && bNextDirect) {
sele += "<src>beeline</src><sym>pass_place</sym><type>Shaping</type>";
} else if (mwpt.direct) {
if (idx == 0)
sele += "<sym>pass_place</sym><type>Via</type>";
else
sele += "<sym>pass_place</sym><type>Shaping</type>";
bNextDirect = true;
} else if (bNextDirect) {
sele += "<src>beeline</src><sym>pass_place</sym><type>Shaping</type>";
bNextDirect = false;
} else if (mwpt.name.startsWith("via") ||
mwpt.name.startsWith("from") ||
mwpt.name.startsWith("to")) {
if (bNextDirect) {
sele += "<src>beeline</src><sym>pass_place</sym><type>Shaping</type>";
} else {
sele += "<sym>pass_place</sym><type>Via</type>";
}
bNextDirect = false;
} else {
sele += "<name>" + mwpt.name + "</name>";
sele += "<sym>pass_place</sym><type>Via</type>";
}
}
}
}
}
sb.append(" <trkpt lon=\"").append(formatILon(n.getILon())).append("\" lat=\"")
.append(formatILat(n.getILat())).append("\">").append(sele).append("</trkpt>\n");
nn = n;
}
sb.append(" </trkseg>\n");
sb.append(" </trk>\n");
sb.append("</gpx>\n");
return sb.toString();
}
public String formatAsWaypoint(OsmNodeNamed n) {
try {
StringWriter sw = new StringWriter(8192);
BufferedWriter bw = new BufferedWriter(sw);
formatGpxHeader(bw);
formatWaypointGpx(bw, n);
formatGpxFooter(bw);
bw.close();
sw.close();
return sw.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void formatGpxHeader(BufferedWriter sb) throws IOException {
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
sb.append("<gpx \n");
sb.append(" xmlns=\"http://www.topografix.com/GPX/1/1\" \n");
sb.append(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \n");
sb.append(" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\" \n");
sb.append(" creator=\"BRouter-" + OsmTrack.version + "\" version=\"1.1\">\n");
}
public void formatGpxFooter(BufferedWriter sb) throws IOException {
sb.append("</gpx>\n");
}
public void formatWaypointGpx(BufferedWriter sb, OsmNodeNamed n) throws IOException {
sb.append(" <wpt lon=\"").append(formatILon(n.ilon)).append("\" lat=\"")
.append(formatILat(n.ilat)).append("\">");
if (n.getSElev() != Short.MIN_VALUE) {
sb.append("<ele>").append("" + n.getElev()).append("</ele>");
}
if (n.name != null) {
sb.append("<name>").append(StringUtils.escapeXml10(n.name)).append("</name>");
}
if (n.nodeDescription != null && rc != null) {
sb.append("<desc>").append(rc.expctxWay.getKeyValueDescription(false, n.nodeDescription)).append("</desc>");
}
sb.append("</wpt>\n");
}
public static String getWaypoint(int ilon, int ilat, String name, String desc) {
return "<wpt lon=\"" + formatILon(ilon) + "\" lat=\"" + formatILat(ilat) + "\"><name>" + name + "</name>" + (desc != null ? "<desc>" + desc + "</desc>" : "") + "</wpt>";
}
public OsmTrack read(String filename) throws Exception {
File f = new File(filename);
if (!f.exists()) {
return null;
}
OsmTrack track = new OsmTrack();
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f)));
for (; ; ) {
String line = br.readLine();
if (line == null)
break;
int idx0 = line.indexOf("<trkpt ");
if (idx0 >= 0) {
idx0 = line.indexOf(" lon=\"");
idx0 += 6;
int idx1 = line.indexOf('"', idx0);
int ilon = (int) ((Double.parseDouble(line.substring(idx0, idx1)) + 180.) * 1000000. + 0.5);
int idx2 = line.indexOf(" lat=\"");
if (idx2 < 0)
continue;
idx2 += 6;
int idx3 = line.indexOf('"', idx2);
int ilat = (int) ((Double.parseDouble(line.substring(idx2, idx3)) + 90.) * 1000000. + 0.5);
track.nodes.add(OsmPathElement.create(ilon, ilat, (short) 0, null, false));
}
}
br.close();
return track;
}
}

View file

@ -0,0 +1,246 @@
package btools.router;
import java.io.BufferedWriter;
import java.io.StringWriter;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.List;
import java.util.Locale;
import btools.mapaccess.MatchedWaypoint;
import btools.util.StringUtils;
public class FormatJson extends Formatter {
public FormatJson(RoutingContext rc) {
super(rc);
}
@Override
public String format(OsmTrack t) {
int turnInstructionMode = t.voiceHints != null ? t.voiceHints.turnInstructionMode : 0;
StringBuilder sb = new StringBuilder(8192);
sb.append("{\n");
sb.append(" \"type\": \"FeatureCollection\",\n");
sb.append(" \"features\": [\n");
sb.append(" {\n");
sb.append(" \"type\": \"Feature\",\n");
sb.append(" \"properties\": {\n");
sb.append(" \"creator\": \"BRouter-" + t.version + "\",\n");
sb.append(" \"name\": \"").append(t.name).append("\",\n");
sb.append(" \"track-length\": \"").append(t.distance).append("\",\n");
sb.append(" \"filtered ascend\": \"").append(t.ascend).append("\",\n");
sb.append(" \"plain-ascend\": \"").append(t.plainAscend).append("\",\n");
sb.append(" \"total-time\": \"").append(t.getTotalSeconds()).append("\",\n");
sb.append(" \"total-energy\": \"").append(t.energy).append("\",\n");
sb.append(" \"cost\": \"").append(t.cost).append("\",\n");
if (t.voiceHints != null && !t.voiceHints.list.isEmpty()) {
sb.append(" \"voicehints\": [\n");
for (VoiceHint hint : t.voiceHints.list) {
sb.append(" [");
sb.append(hint.indexInTrack);
sb.append(',').append(hint.getJsonCommandIndex());
sb.append(',').append(hint.getExitNumber());
sb.append(',').append(hint.distanceToNext);
sb.append(',').append((int) hint.angle);
// not always include geometry because longer and only needed for comment style
if (turnInstructionMode == 4) { // comment style
sb.append(",\"").append(hint.formatGeometry()).append("\"");
}
sb.append("],\n");
}
sb.deleteCharAt(sb.lastIndexOf(","));
sb.append(" ],\n");
}
if (t.showSpeedProfile) { // set in profile
List<String> sp = t.aggregateSpeedProfile();
if (sp.size() > 0) {
sb.append(" \"speedprofile\": [\n");
for (int i = sp.size() - 1; i >= 0; i--) {
sb.append(" [").append(sp.get(i)).append(i > 0 ? "],\n" : "]\n");
}
sb.append(" ],\n");
}
}
// ... traditional message list
{
sb.append(" \"messages\": [\n");
sb.append(" [\"").append(MESSAGES_HEADER.replaceAll("\t", "\", \"")).append("\"],\n");
for (String m : t.aggregateMessages()) {
sb.append(" [\"").append(m.replaceAll("\t", "\", \"")).append("\"],\n");
}
sb.deleteCharAt(sb.lastIndexOf(","));
sb.append(" ],\n");
}
if (t.getTotalSeconds() > 0) {
sb.append(" \"times\": [");
DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getInstance(Locale.ENGLISH);
decimalFormat.applyPattern("0.###");
for (OsmPathElement n : t.nodes) {
sb.append(decimalFormat.format(n.getTime())).append(",");
}
sb.deleteCharAt(sb.lastIndexOf(","));
sb.append("]\n");
} else {
sb.deleteCharAt(sb.lastIndexOf(","));
}
sb.append(" },\n");
if (t.iternity != null) {
sb.append(" \"iternity\": [\n");
for (String s : t.iternity) {
sb.append(" \"").append(s).append("\",\n");
}
sb.deleteCharAt(sb.lastIndexOf(","));
sb.append(" ],\n");
}
sb.append(" \"geometry\": {\n");
sb.append(" \"type\": \"LineString\",\n");
sb.append(" \"coordinates\": [\n");
OsmPathElement nn = null;
for (OsmPathElement n : t.nodes) {
String sele = n.getSElev() == Short.MIN_VALUE ? "" : ", " + n.getElev();
if (t.showspeed) { // hack: show speed instead of elevation
double speed = 0;
if (nn != null) {
int dist = n.calcDistance(nn);
float dt = n.getTime() - nn.getTime();
if (dt != 0.f) {
speed = ((3.6f * dist) / dt + 0.5);
}
}
sele = ", " + (((int) (speed * 10)) / 10.f);
}
sb.append(" [").append(formatILon(n.getILon())).append(", ").append(formatILat(n.getILat()))
.append(sele).append("],\n");
nn = n;
}
sb.deleteCharAt(sb.lastIndexOf(","));
sb.append(" ]\n");
sb.append(" }\n");
if (t.exportWaypoints || !t.pois.isEmpty()) {
sb.append(" },\n");
for (int i = 0; i <= t.pois.size() - 1; i++) {
OsmNodeNamed poi = t.pois.get(i);
addFeature(sb, "poi", poi.name, poi.ilat, poi.ilon);
if (i < t.matchedWaypoints.size() - 1) {
sb.append(",");
}
sb.append(" \n");
}
if (t.exportWaypoints) {
for (int i = 0; i <= t.matchedWaypoints.size() - 1; i++) {
String type;
if (i == 0) {
type = "from";
} else if (i == t.matchedWaypoints.size() - 1) {
type = "to";
} else {
type = "via";
}
MatchedWaypoint wp = t.matchedWaypoints.get(i);
addFeature(sb, type, wp.name, wp.waypoint.ilat, wp.waypoint.ilon);
if (i < t.matchedWaypoints.size() - 1) {
sb.append(",");
}
sb.append(" \n");
}
}
} else {
sb.append(" }\n");
}
sb.append(" ]\n");
sb.append("}\n");
return sb.toString();
}
private void addFeature(StringBuilder sb, String type, String name, int ilat, int ilon) {
sb.append(" {\n");
sb.append(" \"type\": \"Feature\",\n");
sb.append(" \"properties\": {\n");
sb.append(" \"name\": \"" + StringUtils.escapeJson(name) + "\",\n");
sb.append(" \"type\": \"" + type + "\"\n");
sb.append(" },\n");
sb.append(" \"geometry\": {\n");
sb.append(" \"type\": \"Point\",\n");
sb.append(" \"coordinates\": [\n");
sb.append(" " + formatILon(ilon) + ",\n");
sb.append(" " + formatILat(ilat) + "\n");
sb.append(" ]\n");
sb.append(" }\n");
sb.append(" }");
}
public String formatAsWaypoint(OsmNodeNamed n) {
try {
StringWriter sw = new StringWriter(8192);
BufferedWriter bw = new BufferedWriter(sw);
addJsonHeader(bw);
addJsonFeature(bw, "info", "wpinfo", n.ilon, n.ilat, n.getElev(), (n.nodeDescription != null ? rc.expctxWay.getKeyValueDescription(false, n.nodeDescription) : null));
addJsonFooter(bw);
bw.close();
sw.close();
return sw.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void addJsonFeature(BufferedWriter sb, String type, String name, int ilon, int ilat, double elev, String desc) {
try {
sb.append(" {\n");
sb.append(" \"type\": \"Feature\",\n");
sb.append(" \"properties\": {\n");
sb.append(" \"creator\": \"BRouter-" + OsmTrack.version + "\",\n");
sb.append(" \"name\": \"" + StringUtils.escapeJson(name) + "\",\n");
sb.append(" \"type\": \"" + type + "\"");
if (desc != null) {
sb.append(",\n \"message\": \"" + desc + "\"\n");
} else {
sb.append("\n");
}
sb.append(" },\n");
sb.append(" \"geometry\": {\n");
sb.append(" \"type\": \"Point\",\n");
sb.append(" \"coordinates\": [\n");
sb.append(" " + formatILon(ilon) + ",\n");
sb.append(" " + formatILat(ilat) + ",\n");
sb.append(" " + elev + "\n");
sb.append(" ]\n");
sb.append(" }\n");
sb.append(" }\n");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void addJsonHeader(BufferedWriter sb) {
try {
sb.append("{\n");
sb.append(" \"type\": \"FeatureCollection\",\n");
sb.append(" \"features\": [\n");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void addJsonFooter(BufferedWriter sb) {
try {
sb.append(" ]\n");
sb.append("}\n");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,91 @@
package btools.router;
import java.util.List;
import btools.mapaccess.MatchedWaypoint;
import btools.util.StringUtils;
public class FormatKml extends Formatter {
public FormatKml(RoutingContext rc) {
super(rc);
}
@Override
public String format(OsmTrack t) {
StringBuilder sb = new StringBuilder(8192);
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
sb.append("<kml xmlns=\"http://earth.google.com/kml/2.0\">\n");
sb.append(" <Document>\n");
sb.append(" <name>KML Samples</name>\n");
sb.append(" <open>1</open>\n");
sb.append(" <distance>3.497064</distance>\n");
sb.append(" <traveltime>872</traveltime>\n");
sb.append(" <description>To enable simple instructions add: 'instructions=1' as parameter to the URL</description>\n");
sb.append(" <Folder>\n");
sb.append(" <name>Paths</name>\n");
sb.append(" <visibility>0</visibility>\n");
sb.append(" <description>Examples of paths.</description>\n");
sb.append(" <Placemark>\n");
sb.append(" <name>Tessellated</name>\n");
sb.append(" <visibility>0</visibility>\n");
sb.append(" <description><![CDATA[If the <tessellate> tag has a value of 1, the line will contour to the underlying terrain]]></description>\n");
sb.append(" <LineString>\n");
sb.append(" <tessellate>1</tessellate>\n");
sb.append(" <coordinates>");
for (OsmPathElement n : t.nodes) {
sb.append(formatILon(n.getILon())).append(",").append(formatILat(n.getILat())).append("\n");
}
sb.append(" </coordinates>\n");
sb.append(" </LineString>\n");
sb.append(" </Placemark>\n");
sb.append(" </Folder>\n");
if (t.exportWaypoints || !t.pois.isEmpty()) {
if (!t.pois.isEmpty()) {
sb.append(" <Folder>\n");
sb.append(" <name>poi</name>\n");
for (int i = 0; i < t.pois.size(); i++) {
OsmNodeNamed poi = t.pois.get(i);
createPlaceMark(sb, poi.name, poi.ilat, poi.ilon);
}
sb.append(" </Folder>\n");
}
if (t.exportWaypoints) {
int size = t.matchedWaypoints.size();
createFolder(sb, "start", t.matchedWaypoints.subList(0, 1));
if (t.matchedWaypoints.size() > 2) {
createFolder(sb, "via", t.matchedWaypoints.subList(1, size - 1));
}
createFolder(sb, "end", t.matchedWaypoints.subList(size - 1, size));
}
}
sb.append(" </Document>\n");
sb.append("</kml>\n");
return sb.toString();
}
private void createFolder(StringBuilder sb, String type, List<MatchedWaypoint> waypoints) {
sb.append(" <Folder>\n");
sb.append(" <name>" + type + "</name>\n");
for (int i = 0; i < waypoints.size(); i++) {
MatchedWaypoint wp = waypoints.get(i);
createPlaceMark(sb, wp.name, wp.waypoint.ilat, wp.waypoint.ilon);
}
sb.append(" </Folder>\n");
}
private void createPlaceMark(StringBuilder sb, String name, int ilat, int ilon) {
sb.append(" <Placemark>\n");
sb.append(" <name>" + StringUtils.escapeXml10(name) + "</name>\n");
sb.append(" <Point>\n");
sb.append(" <coordinates>" + formatILon(ilon) + "," + formatILat(ilat) + "</coordinates>\n");
sb.append(" </Point>\n");
sb.append(" </Placemark>\n");
}
}

View file

@ -0,0 +1,110 @@
package btools.router;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public abstract class Formatter {
static final String MESSAGES_HEADER = "Longitude\tLatitude\tElevation\tDistance\tCostPerKm\tElevCost\tTurnCost\tNodeCost\tInitialCost\tWayTags\tNodeTags\tTime\tEnergy";
RoutingContext rc;
Formatter() {
}
Formatter(RoutingContext rc) {
this.rc = rc;
}
/**
* writes the track in gpx-format to a file
*
* @param filename the filename to write to
* @param t the track to write
*/
public void write(String filename, OsmTrack t) throws Exception {
BufferedWriter bw = new BufferedWriter(new FileWriter(filename));
bw.write(format(t));
bw.close();
}
public OsmTrack read(String filename) throws Exception {
return null;
}
/**
* writes the track in a selected output format to a string
*
* @param t the track to format
* @return the formatted string
*/
public abstract String format(OsmTrack t);
static String formatILon(int ilon) {
return formatPos(ilon - 180000000);
}
static String formatILat(int ilat) {
return formatPos(ilat - 90000000);
}
private static String formatPos(int p) {
boolean negative = p < 0;
if (negative)
p = -p;
char[] ac = new char[12];
int i = 11;
while (p != 0 || i > 3) {
ac[i--] = (char) ('0' + (p % 10));
p /= 10;
if (i == 5)
ac[i--] = '.';
}
if (negative)
ac[i--] = '-';
return new String(ac, i + 1, 11 - i);
}
public static String getFormattedTime2(int s) {
int seconds = (int) (s + 0.5);
int hours = seconds / 3600;
int minutes = (seconds - hours * 3600) / 60;
seconds = seconds - hours * 3600 - minutes * 60;
String time = "";
if (hours != 0)
time = "" + hours + "h ";
if (minutes != 0)
time = time + minutes + "m ";
if (seconds != 0)
time = time + seconds + "s";
return time;
}
static public String getFormattedEnergy(int energy) {
return format1(energy / 3600000.) + "kwh";
}
static private String format1(double n) {
String s = "" + (long) (n * 10 + 0.5);
int len = s.length();
return s.substring(0, len - 1) + "." + s.charAt(len - 1);
}
static final String dateformat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
static public String getFormattedTime3(float time) {
SimpleDateFormat TIMESTAMP_FORMAT = new SimpleDateFormat(dateformat, Locale.US);
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
// yyyy-mm-ddThh:mm:ss.SSSZ
Date d = new Date((long) (time * 1000f));
return TIMESTAMP_FORMAT.format(d);
}
}

View file

@ -7,33 +7,20 @@ package btools.router;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import btools.mapaccess.MatchedWaypoint;
import btools.mapaccess.OsmPos;
import btools.util.CompactLongMap;
import btools.util.FrozenLongMap;
import btools.util.StringUtils;
public final class OsmTrack {
final public static String version = "1.7.3";
@ -66,7 +53,7 @@ public final class OsmTrack {
private CompactLongMap<OsmPathElementHolder> detourMap;
private VoiceHintList voiceHints;
public VoiceHintList voiceHints;
public String message = null;
public List<String> messageList = null;
@ -178,7 +165,7 @@ public final class OsmTrack {
nodesMap = new FrozenLongMap<>(nodesMap);
}
private List<String> aggregateMessages() {
public List<String> aggregateMessages() {
ArrayList<String> res = new ArrayList<>();
MessageData current = null;
for (OsmPathElement n : nodes) {
@ -200,7 +187,7 @@ public final class OsmTrack {
return res;
}
private List<String> aggregateSpeedProfile() {
public List<String> aggregateSpeedProfile() {
ArrayList<String> res = new ArrayList<>();
int vmax = -1;
int vmaxe = -1;
@ -395,752 +382,9 @@ public final class OsmTrack {
public int plainAscend;
public int cost;
public int energy;
/**
* writes the track in gpx-format to a file
*
* @param filename the filename to write to
*/
public void writeGpx(String filename) throws Exception {
BufferedWriter bw = new BufferedWriter(new FileWriter(filename));
formatAsGpx(bw);
bw.close();
}
public String formatAsGpx() {
try {
StringWriter sw = new StringWriter(8192);
BufferedWriter bw = new BufferedWriter(sw);
formatAsGpx(bw);
bw.close();
return sw.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String formatAsGpx(BufferedWriter sb) throws IOException {
int turnInstructionMode = voiceHints != null ? voiceHints.turnInstructionMode : 0;
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
if (turnInstructionMode != 9) {
for (int i = messageList.size() - 1; i >= 0; i--) {
String message = messageList.get(i);
if (i < messageList.size() - 1)
message = "(alt-index " + i + ": " + message + " )";
if (message != null)
sb.append("<!-- ").append(message).append(" -->\n");
}
}
if (turnInstructionMode == 4) { // comment style
sb.append("<!-- $transport-mode$").append(voiceHints.getTransportMode()).append("$ -->\n");
sb.append("<!-- cmd idx lon lat d2next geometry -->\n");
sb.append("<!-- $turn-instruction-start$\n");
for (VoiceHint hint : voiceHints.list) {
sb.append(String.format(" $turn$%6s;%6d;%10s;%10s;%6d;%s$\n", hint.getCommandString(), hint.indexInTrack,
formatILon(hint.ilon), formatILat(hint.ilat), (int) (hint.distanceToNext), hint.formatGeometry()));
}
sb.append(" $turn-instruction-end$ -->\n");
}
sb.append("<gpx \n");
sb.append(" xmlns=\"http://www.topografix.com/GPX/1/1\" \n");
sb.append(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \n");
if (turnInstructionMode == 9) { // BRouter style
sb.append(" xmlns:brouter=\"Not yet documented\" \n");
}
if (turnInstructionMode == 7) { // old locus style
sb.append(" xmlns:locus=\"http://www.locusmap.eu\" \n");
}
sb.append(" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\" \n");
if (turnInstructionMode == 3) {
sb.append(" creator=\"OsmAndRouter\" version=\"1.1\">\n");
} else {
sb.append(" creator=\"BRouter-" + version + "\" version=\"1.1\">\n");
}
if (turnInstructionMode == 9) {
sb.append(" <metadata>\n");
sb.append(" <name>").append(name).append("</name>\n");
sb.append(" <extensions>\n");
sb.append(" <brouter:info>").append(messageList.get(0)).append("</brouter:info>\n");
if (params != null && params.size() > 0) {
sb.append(" <brouter:params><![CDATA[");
int i = 0;
for (Map.Entry<String, String> e : params.entrySet()) {
if (i++ != 0) sb.append("&");
sb.append(e.getKey()).append("=").append(e.getValue());
}
sb.append("]]></brouter:params>\n");
}
sb.append(" </extensions>\n");
sb.append(" </metadata>\n");
}
if (turnInstructionMode == 3 || turnInstructionMode == 8) { // osmand style, cruiser
float lastRteTime = 0;
sb.append(" <rte>\n");
float rteTime = getVoiceHintTime(0);
StringBuffer first = new StringBuffer();
// define start point
{
first.append(" <rtept lat=\"").append(formatILat(nodes.get(0).getILat())).append("\" lon=\"")
.append(formatILon(nodes.get(0).getILon())).append("\">\n")
.append(" <desc>start</desc>\n <extensions>\n");
if (rteTime != lastRteTime) { // add timing only if available
double t = rteTime - lastRteTime;
first.append(" <time>").append("" + (int) (t + 0.5)).append("</time>\n");
lastRteTime = rteTime;
}
first.append(" <offset>0</offset>\n </extensions>\n </rtept>\n");
}
if (turnInstructionMode == 8) {
if (matchedWaypoints.get(0).direct && voiceHints.list.get(0).indexInTrack == 0) {
// has a voice hint do nothing, voice hint will do
} else {
sb.append(first.toString());
}
} else {
sb.append(first.toString());
}
for (int i = 0; i < voiceHints.list.size(); i++) {
VoiceHint hint = voiceHints.list.get(i);
sb.append(" <rtept lat=\"").append(formatILat(hint.ilat)).append("\" lon=\"")
.append(formatILon(hint.ilon)).append("\">\n")
.append(" <desc>")
.append(turnInstructionMode == 3 ? hint.getMessageString() : hint.getCruiserMessageString())
.append("</desc>\n <extensions>\n");
rteTime = getVoiceHintTime(i + 1);
if (rteTime != lastRteTime) { // add timing only if available
double t = rteTime - lastRteTime;
sb.append(" <time>").append("" + (int) (t + 0.5)).append("</time>\n");
lastRteTime = rteTime;
}
sb.append(" <turn>")
.append(turnInstructionMode == 3 ? hint.getCommandString() : hint.getCruiserCommandString())
.append("</turn>\n <turn-angle>").append("" + (int) hint.angle)
.append("</turn-angle>\n <offset>").append("" + hint.indexInTrack).append("</offset>\n </extensions>\n </rtept>\n");
}
sb.append(" <rtept lat=\"").append(formatILat(nodes.get(nodes.size() - 1).getILat())).append("\" lon=\"")
.append(formatILon(nodes.get(nodes.size() - 1).getILon())).append("\">\n")
.append(" <desc>destination</desc>\n <extensions>\n");
sb.append(" <time>0</time>\n");
sb.append(" <offset>").append("" + (nodes.size() - 1)).append("</offset>\n </extensions>\n </rtept>\n");
sb.append("</rte>\n");
}
if (turnInstructionMode == 7) { // old locus style
float lastRteTime = getVoiceHintTime(0);
for (int i = 0; i < voiceHints.list.size(); i++) {
VoiceHint hint = voiceHints.list.get(i);
sb.append(" <wpt lon=\"").append(formatILon(hint.ilon)).append("\" lat=\"")
.append(formatILat(hint.ilat)).append("\">")
.append(hint.selev == Short.MIN_VALUE ? "" : "<ele>" + (hint.selev / 4.) + "</ele>")
.append("<name>").append(hint.getMessageString()).append("</name>")
.append("<extensions><locus:rteDistance>").append("" + hint.distanceToNext).append("</locus:rteDistance>");
float rteTime = getVoiceHintTime(i + 1);
if (rteTime != lastRteTime) { // add timing only if available
double t = rteTime - lastRteTime;
double speed = hint.distanceToNext / t;
sb.append("<locus:rteTime>").append("" + t).append("</locus:rteTime>")
.append("<locus:rteSpeed>").append("" + speed).append("</locus:rteSpeed>");
lastRteTime = rteTime;
}
sb.append("<locus:rtePointAction>").append("" + hint.getLocusAction()).append("</locus:rtePointAction></extensions>")
.append("</wpt>\n");
}
}
if (turnInstructionMode == 5) { // gpsies style
for (VoiceHint hint : voiceHints.list) {
sb.append(" <wpt lon=\"").append(formatILon(hint.ilon)).append("\" lat=\"")
.append(formatILat(hint.ilat)).append("\">")
.append("<name>").append(hint.getMessageString()).append("</name>")
.append("<sym>").append(hint.getSymbolString().toLowerCase()).append("</sym>")
.append("<type>").append(hint.getSymbolString()).append("</type>")
.append("</wpt>\n");
}
}
if (turnInstructionMode == 6) { // orux style
for (VoiceHint hint : voiceHints.list) {
sb.append(" <wpt lat=\"").append(formatILat(hint.ilat)).append("\" lon=\"")
.append(formatILon(hint.ilon)).append("\">")
.append(hint.selev == Short.MIN_VALUE ? "" : "<ele>" + (hint.selev / 4.) + "</ele>")
.append("<extensions>\n" +
" <om:oruxmapsextensions xmlns:om=\"http://www.oruxmaps.com/oruxmapsextensions/1/0\">\n" +
" <om:ext type=\"ICON\" subtype=\"0\">").append("" + hint.getOruxAction())
.append("</om:ext>\n" +
" </om:oruxmapsextensions>\n" +
" </extensions>\n" +
" </wpt>\n");
}
}
for (int i = 0; i <= pois.size() - 1; i++) {
OsmNodeNamed poi = pois.get(i);
sb.append(" <wpt lon=\"").append(formatILon(poi.ilon)).append("\" lat=\"")
.append(formatILat(poi.ilat)).append("\">\n")
.append(" <name>").append(StringUtils.escapeXml10(poi.name)).append("</name>\n")
.append(" </wpt>\n");
}
if (exportWaypoints) {
for (int i = 0; i <= matchedWaypoints.size() - 1; i++) {
MatchedWaypoint wt = matchedWaypoints.get(i);
sb.append(" <wpt lon=\"").append(formatILon(wt.waypoint.ilon)).append("\" lat=\"")
.append(formatILat(wt.waypoint.ilat)).append("\">\n")
.append(" <name>").append(StringUtils.escapeXml10(wt.name)).append("</name>\n");
if (i == 0) {
sb.append(" <type>from</type>\n");
} else if (i == matchedWaypoints.size() - 1) {
sb.append(" <type>to</type>\n");
} else {
sb.append(" <type>via</type>\n");
}
sb.append(" </wpt>\n");
}
}
sb.append(" <trk>\n");
if (turnInstructionMode == 9
|| turnInstructionMode == 2
|| turnInstructionMode == 8
|| turnInstructionMode == 4) { // Locus, comment, cruise, brouter style
sb.append(" <src>").append(name).append("</src>\n");
sb.append(" <type>").append(voiceHints.getTransportMode()).append("</type>\n");
} else {
sb.append(" <name>").append(name).append("</name>\n");
}
if (turnInstructionMode == 7) {
sb.append(" <extensions>\n");
sb.append(" <locus:rteComputeType>").append("" + voiceHints.getLocusRouteType()).append("</locus:rteComputeType>\n");
sb.append(" <locus:rteSimpleRoundabouts>1</locus:rteSimpleRoundabouts>\n");
sb.append(" </extensions>\n");
}
// all points
sb.append(" <trkseg>\n");
String lastway = "";
boolean bNextDirect = false;
OsmPathElement nn = null;
String aSpeed;
for (int idx = 0; idx < nodes.size(); idx++) {
OsmPathElement n = nodes.get(idx);
String sele = n.getSElev() == Short.MIN_VALUE ? "" : "<ele>" + n.getElev() + "</ele>";
VoiceHint hint = getVoiceHint(idx);
MatchedWaypoint mwpt = getMatchedWaypoint(idx);
if (showTime) {
sele += "<time>" + getFormattedTime3(n.getTime()) + "</time>";
}
if (turnInstructionMode == 8) {
if (mwpt != null &&
!mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) {
sele += "<name>" + mwpt.name + "</name>";
}
}
boolean bNeedHeader = false;
if (turnInstructionMode == 9) { // trkpt/sym style
if (hint != null) {
if (mwpt != null &&
!mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) {
sele += "<name>" + mwpt.name + "</name>";
}
sele += "<desc>" + hint.getCruiserMessageString() + "</desc>";
sele += "<sym>" + hint.getCommandString(hint.cmd) + "</sym>";
if (mwpt != null) {
sele += "<type>Via</type>";
}
sele += "<extensions>";
if (showspeed) {
double speed = 0;
if (nn != null) {
int dist = n.calcDistance(nn);
float dt = n.getTime() - nn.getTime();
if (dt != 0.f) {
speed = ((3.6f * dist) / dt + 0.5);
}
}
sele += "<brouter:speed>" + (((int) (speed * 10)) / 10.f) + "</brouter:speed>";
}
sele += "<brouter:voicehint>" + hint.getCommandString() + ";" + (int) (hint.distanceToNext) + "," + hint.formatGeometry() + "</brouter:voicehint>";
if (n.message != null && n.message.wayKeyValues != null && !n.message.wayKeyValues.equals(lastway)) {
sele += "<brouter:way>" + n.message.wayKeyValues + "</brouter:way>";
lastway = n.message.wayKeyValues;
}
if (n.message != null && n.message.nodeKeyValues != null) {
sele += "<brouter:node>" + n.message.nodeKeyValues + "</brouter:node>";
}
sele += "</extensions>";
}
if (idx == 0 && hint == null) {
if (mwpt != null && mwpt.direct) {
sele += "<desc>beeline</desc>";
} else {
sele += "<desc>start</desc>";
}
sele += "<type>Via</type>";
} else if (idx == nodes.size() - 1 && hint == null) {
sele += "<desc>end</desc>";
sele += "<type>Via</type>";
} else {
if (mwpt != null && hint == null) {
if (mwpt.direct) {
// bNextDirect = true;
sele += "<desc>beeline</desc>";
} else {
sele += "<desc>" + mwpt.name + "</desc>";
}
sele += "<type>Via</type>";
bNextDirect = false;
}
}
if (hint == null) {
bNeedHeader = (showspeed || (n.message != null && n.message.wayKeyValues != null && !n.message.wayKeyValues.equals(lastway))) ||
(n.message != null && n.message.nodeKeyValues != null);
if (bNeedHeader) {
sele += "<extensions>";
if (showspeed) {
double speed = 0;
if (nn != null) {
int dist = n.calcDistance(nn);
float dt = n.getTime() - nn.getTime();
if (dt != 0.f) {
speed = ((3.6f * dist) / dt + 0.5);
}
}
sele += "<brouter:speed>" + (((int) (speed * 10)) / 10.f) + "</brouter:speed>";
}
if (n.message != null && n.message.wayKeyValues != null && !n.message.wayKeyValues.equals(lastway)) {
sele += "<brouter:way>" + n.message.wayKeyValues + "</brouter:way>";
lastway = n.message.wayKeyValues;
}
if (n.message != null && n.message.nodeKeyValues != null) {
sele += "<brouter:node>" + n.message.nodeKeyValues + "</brouter:node>";
}
sele += "</extensions>";
}
}
}
if (turnInstructionMode == 2) { // locus style new
if (hint != null) {
if (mwpt != null) {
if (!mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) {
sele += "<name>" + mwpt.name + "</name>";
}
if (mwpt.direct && bNextDirect) {
sele += "<src>" + hint.getLocusSymbolString() + "</src><sym>pass_place</sym><type>Shaping</type>";
// bNextDirect = false;
} else if (mwpt.direct) {
if (idx == 0)
sele += "<sym>pass_place</sym><type>Via</type>";
else
sele += "<sym>pass_place</sym><type>Shaping</type>";
bNextDirect = true;
} else if (bNextDirect) {
sele += "<src>beeline</src><sym>" + hint.getLocusSymbolString() + "</sym><type>Shaping</type>";
bNextDirect = false;
} else {
sele += "<sym>" + hint.getLocusSymbolString() + "</sym><type>Via</type>";
}
} else {
sele += "<sym>" + hint.getLocusSymbolString() + "</sym>";
}
} else {
if (idx == 0 && hint == null) {
int pos = sele.indexOf("<sym");
if (pos != -1) {
sele = sele.substring(0, pos);
}
if (mwpt != null && !mwpt.name.startsWith("from"))
sele += "<name>" + mwpt.name + "</name>";
if (mwpt != null && mwpt.direct) {
bNextDirect = true;
}
sele += "<sym>pass_place</sym>";
sele += "<type>Via</type>";
} else if (idx == nodes.size() - 1 && hint == null) {
int pos = sele.indexOf("<sym");
if (pos != -1) {
sele = sele.substring(0, pos);
}
if (mwpt != null && mwpt.name != null && !mwpt.name.startsWith("to"))
sele += "<name>" + mwpt.name + "</name>";
if (bNextDirect) {
sele += "<src>beeline</src>";
}
sele += "<sym>pass_place</sym>";
sele += "<type>Via</type>";
} else {
if (mwpt != null) {
if (!mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) {
sele += "<name>" + mwpt.name + "</name>";
}
if (mwpt.direct && bNextDirect) {
sele += "<src>beeline</src><sym>pass_place</sym><type>Shaping</type>";
} else if (mwpt.direct) {
if (idx == 0)
sele += "<sym>pass_place</sym><type>Via</type>";
else
sele += "<sym>pass_place</sym><type>Shaping</type>";
bNextDirect = true;
} else if (bNextDirect) {
sele += "<src>beeline</src><sym>pass_place</sym><type>Shaping</type>";
bNextDirect = false;
} else if (mwpt.name.startsWith("via") ||
mwpt.name.startsWith("from") ||
mwpt.name.startsWith("to")) {
if (bNextDirect) {
sele += "<src>beeline</src><sym>pass_place</sym><type>Shaping</type>";
} else {
sele += "<sym>pass_place</sym><type>Via</type>";
}
bNextDirect = false;
} else {
sele += "<name>" + mwpt.name + "</name>";
sele += "<sym>pass_place</sym><type>Via</type>";
}
}
}
}
}
sb.append(" <trkpt lon=\"").append(formatILon(n.getILon())).append("\" lat=\"")
.append(formatILat(n.getILat())).append("\">").append(sele).append("</trkpt>\n");
nn = n;
}
sb.append(" </trkseg>\n");
sb.append(" </trk>\n");
sb.append("</gpx>\n");
return sb.toString();
}
static public String formatAsGpxWaypoint(OsmNodeNamed n) {
try {
StringWriter sw = new StringWriter(8192);
BufferedWriter bw = new BufferedWriter(sw);
formatGpxHeader(bw);
formatWaypointGpx(bw, n);
formatGpxFooter(bw);
bw.close();
sw.close();
return sw.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static public void formatGpxHeader(BufferedWriter sb) throws IOException {
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
sb.append("<gpx \n");
sb.append(" xmlns=\"http://www.topografix.com/GPX/1/1\" \n");
sb.append(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \n");
sb.append(" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\" \n");
sb.append(" creator=\"BRouter-" + version + "\" version=\"1.1\">\n");
}
static public void formatGpxFooter(BufferedWriter sb) throws IOException {
sb.append("</gpx>\n");
}
static public void formatWaypointGpx(BufferedWriter sb, OsmNodeNamed n) throws IOException {
sb.append(" <wpt lon=\"").append(formatILon(n.ilon)).append("\" lat=\"")
.append(formatILat(n.ilat)).append("\">");
if (n.getSElev() != Short.MIN_VALUE) {
sb.append("<ele>").append("" + n.getElev()).append("</ele>");
}
if (n.name != null) {
sb.append("<name>").append(StringUtils.escapeXml10(n.name)).append("</name>");
}
if (n.nodeDescription != null) {
sb.append("<desc>").append("hat desc").append("</desc>");
}
sb.append("</wpt>\n");
}
public void writeKml(String filename) throws Exception {
BufferedWriter bw = new BufferedWriter(new FileWriter(filename));
bw.write(formatAsKml());
bw.close();
}
public String formatAsKml() {
StringBuilder sb = new StringBuilder(8192);
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
sb.append("<kml xmlns=\"http://earth.google.com/kml/2.0\">\n");
sb.append(" <Document>\n");
sb.append(" <name>KML Samples</name>\n");
sb.append(" <open>1</open>\n");
sb.append(" <distance>3.497064</distance>\n");
sb.append(" <traveltime>872</traveltime>\n");
sb.append(" <description>To enable simple instructions add: 'instructions=1' as parameter to the URL</description>\n");
sb.append(" <Folder>\n");
sb.append(" <name>Paths</name>\n");
sb.append(" <visibility>0</visibility>\n");
sb.append(" <description>Examples of paths.</description>\n");
sb.append(" <Placemark>\n");
sb.append(" <name>Tessellated</name>\n");
sb.append(" <visibility>0</visibility>\n");
sb.append(" <description><![CDATA[If the <tessellate> tag has a value of 1, the line will contour to the underlying terrain]]></description>\n");
sb.append(" <LineString>\n");
sb.append(" <tessellate>1</tessellate>\n");
sb.append(" <coordinates>");
for (OsmPathElement n : nodes) {
sb.append(formatILon(n.getILon())).append(",").append(formatILat(n.getILat())).append("\n");
}
sb.append(" </coordinates>\n");
sb.append(" </LineString>\n");
sb.append(" </Placemark>\n");
sb.append(" </Folder>\n");
if (exportWaypoints || !pois.isEmpty()) {
if (!pois.isEmpty()) {
sb.append(" <Folder>\n");
sb.append(" <name>poi</name>\n");
for (int i = 0; i < pois.size(); i++) {
OsmNodeNamed poi = pois.get(i);
createPlaceMark(sb, poi.name, poi.ilat, poi.ilon);
}
sb.append(" </Folder>\n");
}
if (exportWaypoints) {
int size = matchedWaypoints.size();
createFolder(sb, "start", matchedWaypoints.subList(0, 1));
if (matchedWaypoints.size() > 2) {
createFolder(sb, "via", matchedWaypoints.subList(1, size - 1));
}
createFolder(sb, "end", matchedWaypoints.subList(size - 1, size));
}
}
sb.append(" </Document>\n");
sb.append("</kml>\n");
return sb.toString();
}
private void createFolder(StringBuilder sb, String type, List<MatchedWaypoint> waypoints) {
sb.append(" <Folder>\n");
sb.append(" <name>" + type + "</name>\n");
for (int i = 0; i < waypoints.size(); i++) {
MatchedWaypoint wp = waypoints.get(i);
createPlaceMark(sb, wp.name, wp.waypoint.ilat, wp.waypoint.ilon);
}
sb.append(" </Folder>\n");
}
private void createPlaceMark(StringBuilder sb, String name, int ilat, int ilon) {
sb.append(" <Placemark>\n");
sb.append(" <name>" + StringUtils.escapeXml10(name) + "</name>\n");
sb.append(" <Point>\n");
sb.append(" <coordinates>" + formatILon(ilon) + "," + formatILat(ilat) + "</coordinates>\n");
sb.append(" </Point>\n");
sb.append(" </Placemark>\n");
}
public List<String> iternity;
public void writeJson(String filename) throws Exception {
BufferedWriter bw = new BufferedWriter(new FileWriter(filename));
bw.write(formatAsGeoJson());
bw.close();
}
public String formatAsGeoJson() {
int turnInstructionMode = voiceHints != null ? voiceHints.turnInstructionMode : 0;
StringBuilder sb = new StringBuilder(8192);
sb.append("{\n");
sb.append(" \"type\": \"FeatureCollection\",\n");
sb.append(" \"features\": [\n");
sb.append(" {\n");
sb.append(" \"type\": \"Feature\",\n");
sb.append(" \"properties\": {\n");
sb.append(" \"creator\": \"BRouter-" + version + "\",\n");
sb.append(" \"name\": \"").append(name).append("\",\n");
sb.append(" \"track-length\": \"").append(distance).append("\",\n");
sb.append(" \"filtered ascend\": \"").append(ascend).append("\",\n");
sb.append(" \"plain-ascend\": \"").append(plainAscend).append("\",\n");
sb.append(" \"total-time\": \"").append(getTotalSeconds()).append("\",\n");
sb.append(" \"total-energy\": \"").append(energy).append("\",\n");
sb.append(" \"cost\": \"").append(cost).append("\",\n");
if (voiceHints != null && !voiceHints.list.isEmpty()) {
sb.append(" \"voicehints\": [\n");
for (VoiceHint hint : voiceHints.list) {
sb.append(" [");
sb.append(hint.indexInTrack);
sb.append(',').append(hint.getJsonCommandIndex());
sb.append(',').append(hint.getExitNumber());
sb.append(',').append(hint.distanceToNext);
sb.append(',').append((int) hint.angle);
// not always include geometry because longer and only needed for comment style
if (turnInstructionMode == 4) { // comment style
sb.append(",\"").append(hint.formatGeometry()).append("\"");
}
sb.append("],\n");
}
sb.deleteCharAt(sb.lastIndexOf(","));
sb.append(" ],\n");
}
if (showSpeedProfile) { // set in profile
List<String> sp = aggregateSpeedProfile();
if (sp.size() > 0) {
sb.append(" \"speedprofile\": [\n");
for (int i = sp.size() - 1; i >= 0; i--) {
sb.append(" [").append(sp.get(i)).append(i > 0 ? "],\n" : "]\n");
}
sb.append(" ],\n");
}
}
// ... traditional message list
{
sb.append(" \"messages\": [\n");
sb.append(" [\"").append(MESSAGES_HEADER.replaceAll("\t", "\", \"")).append("\"],\n");
for (String m : aggregateMessages()) {
sb.append(" [\"").append(m.replaceAll("\t", "\", \"")).append("\"],\n");
}
sb.deleteCharAt(sb.lastIndexOf(","));
sb.append(" ],\n");
}
if (getTotalSeconds() > 0) {
sb.append(" \"times\": [");
DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getInstance(Locale.ENGLISH);
decimalFormat.applyPattern("0.###");
for (OsmPathElement n : nodes) {
sb.append(decimalFormat.format(n.getTime())).append(",");
}
sb.deleteCharAt(sb.lastIndexOf(","));
sb.append("]\n");
} else {
sb.deleteCharAt(sb.lastIndexOf(","));
}
sb.append(" },\n");
if (iternity != null) {
sb.append(" \"iternity\": [\n");
for (String s : iternity) {
sb.append(" \"").append(s).append("\",\n");
}
sb.deleteCharAt(sb.lastIndexOf(","));
sb.append(" ],\n");
}
sb.append(" \"geometry\": {\n");
sb.append(" \"type\": \"LineString\",\n");
sb.append(" \"coordinates\": [\n");
OsmPathElement nn = null;
for (OsmPathElement n : nodes) {
String sele = n.getSElev() == Short.MIN_VALUE ? "" : ", " + n.getElev();
if (showspeed) { // hack: show speed instead of elevation
double speed = 0;
if (nn != null) {
int dist = n.calcDistance(nn);
float dt = n.getTime() - nn.getTime();
if (dt != 0.f) {
speed = ((3.6f * dist) / dt + 0.5);
}
}
sele = ", " + (((int) (speed * 10)) / 10.f);
}
sb.append(" [").append(formatILon(n.getILon())).append(", ").append(formatILat(n.getILat()))
.append(sele).append("],\n");
nn = n;
}
sb.deleteCharAt(sb.lastIndexOf(","));
sb.append(" ]\n");
sb.append(" }\n");
if (exportWaypoints || !pois.isEmpty()) {
sb.append(" },\n");
for (int i = 0; i <= pois.size() - 1; i++) {
OsmNodeNamed poi = pois.get(i);
addFeature(sb, "poi", poi.name, poi.ilat, poi.ilon);
if (i < matchedWaypoints.size() - 1) {
sb.append(",");
}
sb.append(" \n");
}
if (exportWaypoints) {
for (int i = 0; i <= matchedWaypoints.size() - 1; i++) {
String type;
if (i == 0) {
type = "from";
} else if (i == matchedWaypoints.size() - 1) {
type = "to";
} else {
type = "via";
}
MatchedWaypoint wp = matchedWaypoints.get(i);
addFeature(sb, type, wp.name, wp.waypoint.ilat, wp.waypoint.ilon);
if (i < matchedWaypoints.size() - 1) {
sb.append(",");
}
sb.append(" \n");
}
}
} else {
sb.append(" }\n");
}
sb.append(" ]\n");
sb.append("}\n");
return sb.toString();
}
private void addFeature(StringBuilder sb, String type, String name, int ilat, int ilon) {
sb.append(" {\n");
sb.append(" \"type\": \"Feature\",\n");
sb.append(" \"properties\": {\n");
sb.append(" \"name\": \"" + StringUtils.escapeJson(name) + "\",\n");
sb.append(" \"type\": \"" + type + "\"\n");
sb.append(" },\n");
sb.append(" \"geometry\": {\n");
sb.append(" \"type\": \"Point\",\n");
sb.append(" \"coordinates\": [\n");
sb.append(" " + formatILon(ilon) + ",\n");
sb.append(" " + formatILat(ilat) + "\n");
sb.append(" ]\n");
sb.append(" }\n");
sb.append(" }");
}
private VoiceHint getVoiceHint(int i) {
public VoiceHint getVoiceHint(int i) {
if (voiceHints == null) return null;
for (VoiceHint hint : voiceHints.list) {
if (hint.indexInTrack == i) {
@ -1150,7 +394,7 @@ public final class OsmTrack {
return null;
}
private MatchedWaypoint getMatchedWaypoint(int idx) {
public MatchedWaypoint getMatchedWaypoint(int idx) {
if (matchedWaypoints == null) return null;
for (MatchedWaypoint wp : matchedWaypoints) {
if (idx == wp.indexInTrack) {
@ -1168,128 +412,11 @@ public final class OsmTrack {
return vnode0 < vnode1 ? vnode0 : vnode1;
}
private int getTotalSeconds() {
public int getTotalSeconds() {
float s = nodes.size() < 2 ? 0 : nodes.get(nodes.size() - 1).getTime() - nodes.get(0).getTime();
return (int) (s + 0.5);
}
public String getFormattedTime() {
return format1(getTotalSeconds() / 60.) + "m";
}
public String getFormattedTime2() {
int seconds = (int) (getTotalSeconds() + 0.5);
int hours = seconds / 3600;
int minutes = (seconds - hours * 3600) / 60;
seconds = seconds - hours * 3600 - minutes * 60;
String time = "";
if (hours != 0)
time = "" + hours + "h ";
if (minutes != 0)
time = time + minutes + "m ";
if (seconds != 0)
time = time + seconds + "s";
return time;
}
SimpleDateFormat TIMESTAMP_FORMAT;
public String getFormattedTime3(float time) {
if (TIMESTAMP_FORMAT == null) {
TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
// yyyy-mm-ddThh:mm:ss.SSSZ
Date d = new Date((long) (time * 1000f));
return TIMESTAMP_FORMAT.format(d);
}
public String getFormattedEnergy() {
return format1(energy / 3600000.) + "kwh";
}
private static String formatILon(int ilon) {
return formatPos(ilon - 180000000);
}
private static String formatILat(int ilat) {
return formatPos(ilat - 90000000);
}
private static String formatPos(int p) {
boolean negative = p < 0;
if (negative)
p = -p;
char[] ac = new char[12];
int i = 11;
while (p != 0 || i > 3) {
ac[i--] = (char) ('0' + (p % 10));
p /= 10;
if (i == 5)
ac[i--] = '.';
}
if (negative)
ac[i--] = '-';
return new String(ac, i + 1, 11 - i);
}
private String format1(double n) {
String s = "" + (long) (n * 10 + 0.5);
int len = s.length();
return s.substring(0, len - 1) + "." + s.charAt(len - 1);
}
public void dumpMessages(String filename, RoutingContext rc) throws Exception {
BufferedWriter bw = filename == null ? null : new BufferedWriter(new FileWriter(filename));
writeMessages(bw, rc);
}
public void writeMessages(BufferedWriter bw, RoutingContext rc) throws Exception {
dumpLine(bw, MESSAGES_HEADER);
for (String m : aggregateMessages()) {
dumpLine(bw, m);
}
if (bw != null)
bw.close();
}
private void dumpLine(BufferedWriter bw, String s) throws Exception {
if (bw == null) {
System.out.println(s);
} else {
bw.write(s);
bw.write("\n");
}
}
public void readGpx(String filename) throws Exception {
File f = new File(filename);
if (!f.exists())
return;
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f)));
for (; ; ) {
String line = br.readLine();
if (line == null)
break;
int idx0 = line.indexOf("<trkpt lon=\"");
if (idx0 >= 0) {
idx0 += 12;
int idx1 = line.indexOf('"', idx0);
int ilon = (int) ((Double.parseDouble(line.substring(idx0, idx1)) + 180.) * 1000000. + 0.5);
int idx2 = line.indexOf(" lat=\"");
if (idx2 < 0)
continue;
idx2 += 6;
int idx3 = line.indexOf('"', idx2);
int ilat = (int) ((Double.parseDouble(line.substring(idx2, idx3)) + 90.) * 1000000. + 0.5);
nodes.add(OsmPathElement.create(ilon, ilat, (short) 0, null, false));
}
}
br.close();
}
public boolean equalsTrack(OsmTrack t) {
if (nodes.size() != t.nodes.size())
return false;
@ -1398,7 +525,7 @@ public final class OsmTrack {
return 2;
}
private float getVoiceHintTime(int i) {
public float getVoiceHintTime(int i) {
if (voiceHints.list.isEmpty()) {
return 0f;
}

View file

@ -192,34 +192,70 @@ public class RoutingEngine extends Thread {
track.message = "track-length = " + track.distance + " filtered ascend = " + track.ascend
+ " plain-ascend = " + track.plainAscend + " cost=" + track.cost;
if (track.energy != 0) {
track.message += " energy=" + track.getFormattedEnergy() + " time=" + track.getFormattedTime2();
track.message += " energy=" + Formatter.getFormattedEnergy(track.energy) + " time=" + Formatter.getFormattedTime2(track.getTotalSeconds());
}
track.name = "brouter_" + routingContext.getProfileName() + "_" + i;
messageList.add(track.message);
track.messageList = messageList;
if (outfileBase != null) {
String filename = outfileBase + i + ".gpx";
OsmTrack oldTrack = new OsmTrack();
oldTrack.readGpx(filename);
if (track.equalsTrack(oldTrack)) {
String filename = outfileBase + i + "." + routingContext.outputFormat;
OsmTrack oldTrack = null;
switch (routingContext.outputFormat) {
case "gpx":
oldTrack = new FormatGpx(routingContext).read(filename);
break;
case "geojson": // read only gpx at the moment
case "json":
// oldTrack = new FormatJson(routingContext).read(filename);
break;
case "kml":
// oldTrack = new FormatJson(routingContext).read(filename);
break;
default:
break;
}
if (oldTrack != null && track.equalsTrack(oldTrack)) {
continue;
}
oldTrack = null;
track.exportWaypoints = routingContext.exportWaypoints;
// doesn't work at the moment
// use routingContext.outputFormat
track.writeGpx(filename);
filename = outfileBase + i + "." + routingContext.outputFormat;
switch (routingContext.outputFormat) {
case "gpx":
outputMessage = new FormatGpx(routingContext).format(track);
break;
case "geojson":
case "json":
outputMessage = new FormatJson(routingContext).format(track);
break;
case "kml":
outputMessage = new FormatKml(routingContext).format(track);
break;
case "csv":
default:
outputMessage = null;
break;
}
if (outputMessage != null) {
File out = new File(filename);
FileWriter fw = new FileWriter(filename);
fw.write(outputMessage);
fw.close();
outputMessage = null;
}
foundTrack = track;
alternativeIndex = i;
outfile = filename;
} else {
if (i == routingContext.getAlternativeIdx(0, 3)) {
if ("CSV".equals(System.getProperty("reportFormat"))) {
track.dumpMessages(null, routingContext);
String filename = outfileBase + i + ".csv";
new FormatCsv(routingContext).write(filename, track);
} else {
if (!quite) {
System.out.println(track.formatAsGpx());
System.out.println(new FormatGpx(routingContext).format(track));
}
}
foundTrack = track;
@ -229,7 +265,7 @@ public class RoutingEngine extends Thread {
}
if (logfileBase != null) {
String logfilename = logfileBase + i + ".csv";
track.dumpMessages(logfilename, routingContext);
new FormatCsv(routingContext).write(logfilename, track);
}
break;
}
@ -308,15 +344,31 @@ public class RoutingEngine extends Thread {
OsmNodeNamed n = new OsmNodeNamed(listOne.get(0).crosspoint);
n.selev = startNode != null ? startNode.getSElev() : Short.MIN_VALUE;
// doesn't work at the moment
// use routingContext.outputFormat
outputMessage = OsmTrack.formatAsGpxWaypoint(n);
switch (routingContext.outputFormat) {
case "gpx":
outputMessage = new FormatGpx(routingContext).formatAsWaypoint(n);
break;
case "geojson":
case "json":
outputMessage = new FormatJson(routingContext).formatAsWaypoint(n);
break;
case "kml":
case "csv":
default:
outputMessage = null;
break;
}
if (outfileBase != null) {
String filename = outfileBase + ".gpx";
String filename = outfileBase + "." + routingContext.outputFormat;
File out = new File(filename);
FileWriter fw = new FileWriter(filename);
fw.write(outputMessage);
fw.close();
outputMessage = null;
} else {
if (!quite && outputMessage != null) {
System.out.println(outputMessage);
}
}
long endTime = System.currentTimeMillis();
logInfo("execution time = " + (endTime - startTime) / 1000. + " seconds");
@ -951,7 +1003,7 @@ public class RoutingEngine extends Thread {
if (track == null) {
for (int cfi = 0; cfi < airDistanceCostFactors.length; cfi++) {
if (cfi > 0) lastAirDistanceCostFactor = airDistanceCostFactors[cfi-1];
if (cfi > 0) lastAirDistanceCostFactor = airDistanceCostFactors[cfi - 1];
airDistanceCostFactor = airDistanceCostFactors[cfi];
if (airDistanceCostFactor < 0.) {
@ -1447,7 +1499,7 @@ public class RoutingEngine extends Thread {
boolean inRadius = boundary == null || boundary.isInBoundary(nextNode, bestPath.cost);
if (inRadius && (isFinalLink || bestPath.cost + bestPath.airdistance <= (lastAirDistanceCostFactor != 0. ? maxTotalCost*lastAirDistanceCostFactor : maxTotalCost) + addDiff)) {
if (inRadius && (isFinalLink || bestPath.cost + bestPath.airdistance <= (lastAirDistanceCostFactor != 0. ? maxTotalCost * lastAirDistanceCostFactor : maxTotalCost) + addDiff)) {
// add only if this may beat an existing path for that link
OsmLinkHolder dominator = link.getFirstLinkHolder(currentNode);
while (!trafficSim && dominator != null) {
@ -1628,7 +1680,7 @@ public class RoutingEngine extends Thread {
}
public String getTime() {
return foundTrack.getFormattedTime2();
return Formatter.getFormattedTime2(foundTrack.getTotalSeconds());
}
public OsmTrack getFoundTrack() {

View file

@ -14,8 +14,9 @@ public class RoutingParamCollector {
/**
* get a list of points and optional extra info for the points
* @param lonLats - linked list separated by ';' or '|'
* @return - a list
*
* @param lonLats linked list separated by ';' or '|'
* @return a list
*/
public List<OsmNodeNamed> getWayPointList(String lonLats) {
if (lonLats == null) throw new IllegalArgumentException("lonlats parameter not set");
@ -49,9 +50,10 @@ public class RoutingParamCollector {
/**
* get a list of points (old style, positions only)
* @param lons - array with longitudes
* @param lats - array with latitudes
* @return - a list
*
* @param lons array with longitudes
* @param lats array with latitudes
* @return a list
*/
public List<OsmNodeNamed> readPositions(double[] lons, double[] lats) {
List<OsmNodeNamed> wplist = new ArrayList<>();
@ -93,8 +95,9 @@ public class RoutingParamCollector {
/**
* read a url like parameter list linked with '&'
* @param url - parameter list
* @return - a hashmap of the parameter
*
* @param url parameter list
* @return a hashmap of the parameter
* @throws UnsupportedEncodingException
*/
public Map<String, String> getUrlParams(String url) throws UnsupportedEncodingException {
@ -117,9 +120,10 @@ public class RoutingParamCollector {
/**
* fill a parameter map into the routing context
* @param rctx - the context
* @param wplist - the list of way points needed for 'straight' parameter
* @param params - the list of parameters
*
* @param rctx the context
* @param wplist the list of way points needed for 'straight' parameter
* @param params the list of parameters
*/
public void setParams(RoutingContext rctx, List<OsmNodeNamed> wplist, Map<String, String> params) {
if (params != null) {
@ -129,11 +133,15 @@ public class RoutingParamCollector {
if (params.containsKey("profile")) {
rctx.localFunction = params.get("profile");
}
if (params.containsKey("nogoLats")) {
if (params.containsKey("nogoLats") && params.get("nogoLats").length() > 0) {
List<OsmNodeNamed> nogoList = readNogos(params.get("nogoLons"), params.get("nogoLats"), params.get("nogoRadi"));
if (nogoList != null) {
RoutingContext.prepareNogoPoints(nogoList);
if (rctx.nogopoints == null) {
rctx.nogopoints = nogoList;
} else {
rctx.nogopoints.addAll(nogoList);
}
}
params.remove("nogoLats");
params.remove("nogoLons");
@ -143,7 +151,11 @@ public class RoutingParamCollector {
List<OsmNodeNamed> nogoList = readNogoList(params.get("nogos"));
if (nogoList != null) {
RoutingContext.prepareNogoPoints(nogoList);
if (rctx.nogopoints == null) {
rctx.nogopoints = nogoList;
} else {
rctx.nogopoints.addAll(nogoList);
}
}
params.remove("nogos");
}
@ -196,6 +208,12 @@ public class RoutingParamCollector {
rctx.turnInstructionMode = Integer.parseInt(value);
} else if (key.equals("timode")) {
rctx.turnInstructionMode = Integer.parseInt(value);
} else if (key.equals("turnInstructionFormat")) {
if ("osmand".equalsIgnoreCase(value)) {
rctx.turnInstructionMode = 3;
} else if ("locus".equalsIgnoreCase(value)) {
rctx.turnInstructionMode = 2;
}
} else if (key.equals("exportWaypoints")) {
rctx.exportWaypoints = (Integer.parseInt(value) == 1);
} else if (key.equals("format")) {
@ -213,8 +231,9 @@ public class RoutingParamCollector {
/**
* fill profile parameter list
* @param rctx - the routing context
* @param params - the list of parameters
*
* @param rctx the routing context
* @param params the list of parameters
*/
public void setProfileParams(RoutingContext rctx, Map<String, String> params) {
if (params != null) {

View file

@ -20,8 +20,6 @@ android {
minSdkVersion 14
targetSdkVersion 33
resConfigs "en"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View file

@ -171,7 +171,7 @@ public class BInstallerActivity extends AppCompatActivity {
}
mButtonDownload.setText(getString(R.string.action_download, getSegmentsPlural(selectedTilesDownload.size())));
mButtonDownload.setEnabled(true);
mSummaryInfo.setText(getString(R.string.summary_segments, Formatter.formatFileSize(this, tileSize), Formatter.formatFileSize(this, getAvailableSpace(mBaseDir.getAbsolutePath()))));
mSummaryInfo.setText(String.format(getString(R.string.summary_segments), Formatter.formatFileSize(this, tileSize), Formatter.formatFileSize(this, getAvailableSpace(mBaseDir.getAbsolutePath()))));
} else if (selectedTilesUpdate.size() > 0) {
mButtonDownload.setText(getString(R.string.action_update, getSegmentsPlural(selectedTilesUpdate.size())));
mButtonDownload.setEnabled(true);

View file

@ -19,6 +19,7 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.GZIPOutputStream;
@ -39,10 +40,6 @@ public class BRouterService extends Service {
BRouterWorker worker = new BRouterWorker();
for (String key : params.keySet()) {
// Log.d("BS", "income " + key + " = " + params.get(key));
}
int engineMode = 0;
if (params.containsKey("engineMode")) {
engineMode = params.getInt("engineMode", 0);
@ -103,7 +100,7 @@ public class BRouterService extends Service {
boolean canCompress = "true".equals(params.getString("acceptCompressedResult"));
try {
String gpxMessage = worker.getTrackFromParams(params);
if (canCompress && gpxMessage.startsWith("<")) {
if (canCompress) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write("z64".getBytes(Charset.forName("UTF-8"))); // marker prefix
@ -277,8 +274,13 @@ public class BRouterService extends Service {
private void logBundle(Bundle params) {
if (AppLogger.isLogging()) {
for (String k : params.keySet()) {
Object val = "remoteProfile".equals(k) ? "<..cut..>" : params.getString(k);
String desc = "key=" + k + (val == null ? "" : " class=" + val.getClass() + " val=" + val.toString());
Object val = "remoteProfile".equals(k) ? "<..cut..>" : params.get(k);
String desc = "key=" + k + (val == null ? "" : " class=" + val.getClass() + " val=");
if (val instanceof double[]) {
desc += Arrays.toString(params.getDoubleArray(k));
} else {
desc += val.toString();
}
AppLogger.log(desc);
}
}

View file

@ -6,16 +6,20 @@ import android.os.Bundle;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Map;
import btools.router.FormatGpx;
import btools.router.FormatJson;
import btools.router.FormatKml;
import btools.router.OsmNodeNamed;
import btools.router.OsmNogoPolygon;
import btools.router.OsmTrack;
import btools.router.RoutingContext;
import btools.router.RoutingEngine;
import btools.router.RoutingParamCollector;
public class BRouterWorker {
private static final int OUTPUT_FORMAT_GPX = 0;
@ -39,6 +43,72 @@ public class BRouterWorker {
engineMode = params.getInt("engineMode", 0);
}
RoutingContext rc = new RoutingContext();
rc.rawTrackPath = rawTrackPath;
rc.localFunction = profilePath;
RoutingParamCollector routingParamCollector = new RoutingParamCollector();
// parameter pre control
if (params.containsKey("lonlats")) {
waypoints = routingParamCollector.getWayPointList(params.getString("lonlats"));
params.remove("lonlats");
}
if (params.containsKey("lats")) {
double[] lats = params.getDoubleArray("lats");
double[] lons = params.getDoubleArray("lons");
waypoints = routingParamCollector.readPositions(lons, lats);
params.remove("lons");
params.remove("lats");
}
if (waypoints == null) {
throw new IllegalArgumentException("no points!");
}
if (engineMode == 0) {
if (waypoints.size() < 2) {
throw new IllegalArgumentException("we need two lat/lon points at least!");
}
} else {
if (waypoints.size() < 1) {
throw new IllegalArgumentException("we need two lat/lon points at least!");
}
}
if (nogoList != null && nogoList.size() > 0) {
// forward already read nogos from filesystem
if (rc.nogopoints == null) {
rc.nogopoints = nogoList;
} else {
rc.nogopoints.addAll(nogoList);
}
}
Map<String, String> theParams = new HashMap<>();
for (String key : params.keySet()) {
Object value = params.get(key);
if (value instanceof double[]) {
String s = Arrays.toString(params.getDoubleArray(key));
s = s.replace("[", "").replace("]", "");
theParams.put(key, s);
} else {
theParams.put(key, value.toString());
}
}
routingParamCollector.setParams(rc, waypoints, theParams);
if (params.containsKey("extraParams")) {
Map<String, String> profileparams = null;
try {
profileparams = routingParamCollector.getUrlParams(params.getString("extraParams"));
routingParamCollector.setProfileParams(rc, profileparams);
} catch (UnsupportedEncodingException e) {
// ignore
}
}
String pathToFileResult = params.getString("pathToFileResult");
if (pathToFileResult != null) {
@ -52,100 +122,9 @@ public class BRouterWorker {
long maxRunningTime = 60000;
String sMaxRunningTime = params.getString("maxRunningTime");
if (sMaxRunningTime != null) {
maxRunningTime = Integer.parseInt(sMaxRunningTime) * 1000;
maxRunningTime = Integer.parseInt(sMaxRunningTime) * 1000L;
}
RoutingContext rc = new RoutingContext();
rc.rawTrackPath = rawTrackPath;
rc.localFunction = profilePath;
String tiFormat = params.getString("turnInstructionFormat");
if (tiFormat != null) {
if ("osmand".equalsIgnoreCase(tiFormat)) {
rc.turnInstructionMode = 3;
} else if ("locus".equalsIgnoreCase(tiFormat)) {
rc.turnInstructionMode = 2;
}
}
if (params.containsKey("timode")) {
rc.turnInstructionMode = params.getInt("timode");
}
if (params.containsKey("direction")) {
rc.startDirection = params.getInt("direction");
}
if (params.containsKey("heading")) {
rc.startDirection = params.getInt("heading");
rc.forceUseStartDirection = true;
}
if (params.containsKey("alternativeidx")) {
rc.alternativeIdx = params.getInt("alternativeidx");
}
readNogos(params); // add interface provided nogos
if (nogoList != null) {
RoutingContext.prepareNogoPoints(nogoList);
if (rc.nogopoints == null) {
rc.nogopoints = nogoList;
} else {
rc.nogopoints.addAll(nogoList);
}
}
if (rc.nogopoints == null) {
rc.nogopoints = nogoPolygonsList;
} else if (nogoPolygonsList != null) {
rc.nogopoints.addAll(nogoPolygonsList);
}
List<OsmNodeNamed> poisList = readPoisList(params);
rc.poipoints = poisList;
if (params.containsKey("lats")) {
waypoints = readPositions(params);
}
if (params.containsKey("lonlats")) {
waypoints = readLonlats(params, engineMode);
}
if (waypoints == null) return "no pts ";
if (params.containsKey("straight")) {
try {
String straight = params.getString("straight");
String[] sa = straight.split(",");
for (int i = 0; i < sa.length; i++) {
int v = Integer.parseInt(sa[i]);
if (waypoints.size() > v) waypoints.get(v).direct = true;
}
} catch (NumberFormatException e) {
}
}
String extraParams = null;
if (params.containsKey("extraParams")) { // add user params
extraParams = params.getString("extraParams");
}
if (extraParams != null && this.profileParams != null) {
// don't overwrite incoming values
extraParams = this.profileParams + "&" + extraParams;
} else if (this.profileParams != null) {
extraParams = this.profileParams;
}
if (params.containsKey("extraParams")) { // add user params
if (rc.keyValues == null) rc.keyValues = new HashMap<>();
StringTokenizer tk = new StringTokenizer(extraParams, "?&");
while (tk.hasMoreTokens()) {
String t = tk.nextToken();
StringTokenizer tk2 = new StringTokenizer(t, "=");
if (tk2.hasMoreTokens()) {
String key = tk2.nextToken();
if (tk2.hasMoreTokens()) {
String value = tk2.nextToken();
rc.keyValues.put(key, value);
}
}
}
}
try {
writeTimeoutData(rc);
@ -170,50 +149,46 @@ public class BRouterWorker {
return cr.getErrorMessage();
}
String format = params.getString("trackFormat");
int writeFromat = OUTPUT_FORMAT_GPX;
if (format != null) {
if ("kml".equals(format)) writeFromat = OUTPUT_FORMAT_KML;
if ("json".equals(format)) writeFromat = OUTPUT_FORMAT_JSON;
if (rc.outputFormat != null) {
if ("kml".equals(rc.outputFormat)) writeFromat = OUTPUT_FORMAT_KML;
if ("json".equals(rc.outputFormat)) writeFromat = OUTPUT_FORMAT_JSON;
}
OsmTrack track = cr.getFoundTrack();
OsmTrack track = null;
track = cr.getFoundTrack();
if (track != null) {
if (params.containsKey("exportWaypoints")) {
track.exportWaypoints = (params.getInt("exportWaypoints", 0) == 1);
}
track.exportWaypoints = rc.exportWaypoints;
if (pathToFileResult == null) {
switch (writeFromat) {
case OUTPUT_FORMAT_GPX:
return track.formatAsGpx();
case OUTPUT_FORMAT_KML:
return track.formatAsKml();
return new FormatKml(rc).format(track);
case OUTPUT_FORMAT_JSON:
return track.formatAsGeoJson();
return new FormatJson(rc).format(track);
case OUTPUT_FORMAT_GPX:
default:
return track.formatAsGpx();
return new FormatGpx(rc).format(track);
}
}
}
try {
switch (writeFromat) {
case OUTPUT_FORMAT_GPX:
track.writeGpx(pathToFileResult);
break;
case OUTPUT_FORMAT_KML:
track.writeKml(pathToFileResult);
new FormatKml(rc).write(pathToFileResult, track);
break;
case OUTPUT_FORMAT_JSON:
track.writeJson(pathToFileResult);
new FormatJson(rc).write(pathToFileResult, track);
break;
case OUTPUT_FORMAT_GPX:
default:
track.writeGpx(pathToFileResult);
new FormatGpx(rc).write(pathToFileResult, track);
break;
}
} catch (Exception e) {
return "error writing file: " + e;
}
}
} else { // get other infos
if (cr.getErrorMessage() != null) {
return cr.getErrorMessage();
@ -223,204 +198,6 @@ public class BRouterWorker {
return null;
}
private List<OsmNodeNamed> readPositions(Bundle params) {
List<OsmNodeNamed> wplist = new ArrayList<>();
double[] lats = params.getDoubleArray("lats");
double[] lons = params.getDoubleArray("lons");
if (lats == null || lats.length < 2 || lons == null || lons.length < 2) {
throw new IllegalArgumentException("we need two lat/lon points at least!");
}
for (int i = 0; i < lats.length && i < lons.length; i++) {
OsmNodeNamed n = new OsmNodeNamed();
n.name = "via" + i;
n.ilon = (int) ((lons[i] + 180.) * 1000000. + 0.5);
n.ilat = (int) ((lats[i] + 90.) * 1000000. + 0.5);
wplist.add(n);
}
if (wplist.get(0).name.startsWith("via")) wplist.get(0).name = "from";
if (wplist.get(wplist.size() - 1).name.startsWith("via"))
wplist.get(wplist.size() - 1).name = "to";
return wplist;
}
private List<OsmNodeNamed> readLonlats(Bundle params, int mode) {
List<OsmNodeNamed> wplist = new ArrayList<>();
String lonLats = params.getString("lonlats");
if (lonLats == null) throw new IllegalArgumentException("lonlats parameter not set");
String[] coords;
if (mode == 0) {
coords = lonLats.split("\\|");
if (coords.length < 2)
throw new IllegalArgumentException("we need two lat/lon points at least!");
} else {
coords = new String[1];
coords[0] = lonLats;
}
for (int i = 0; i < coords.length; i++) {
String[] lonLat = coords[i].split(",");
if (lonLat.length < 2)
throw new IllegalArgumentException("we need a lat and lon point at least!");
wplist.add(readPosition(lonLat[0], lonLat[1], "via" + i));
if (lonLat.length > 2) {
if (lonLat[2].equals("d")) {
wplist.get(wplist.size() - 1).direct = true;
} else {
wplist.get(wplist.size() - 1).name = lonLat[2];
}
}
}
if (wplist.get(0).name.startsWith("via")) wplist.get(0).name = "from";
if (wplist.get(wplist.size() - 1).name.startsWith("via"))
wplist.get(wplist.size() - 1).name = "to";
return wplist;
}
private static OsmNodeNamed readPosition(String vlon, String vlat, String name) {
if (vlon == null) throw new IllegalArgumentException("lon " + name + " not found in input");
if (vlat == null) throw new IllegalArgumentException("lat " + name + " not found in input");
return readPosition(Double.parseDouble(vlon), Double.parseDouble(vlat), name);
}
private static OsmNodeNamed readPosition(double lon, double lat, String name) {
OsmNodeNamed n = new OsmNodeNamed();
n.name = name;
n.ilon = (int) ((lon + 180.) * 1000000. + 0.5);
n.ilat = (int) ((lat + 90.) * 1000000. + 0.5);
return n;
}
private void readNogos(Bundle params) {
if (params.containsKey("nogoLats")) {
double[] lats = params.getDoubleArray("nogoLats");
double[] lons = params.getDoubleArray("nogoLons");
double[] radi = params.getDoubleArray("nogoRadi");
if (lats == null || lons == null || radi == null) return;
for (int i = 0; i < lats.length && i < lons.length && i < radi.length; i++) {
OsmNodeNamed n = new OsmNodeNamed();
n.name = "nogo" + (int) radi[i];
n.ilon = (int) ((lons[i] + 180.) * 1000000. + 0.5);
n.ilat = (int) ((lats[i] + 90.) * 1000000. + 0.5);
n.isNogo = true;
n.nogoWeight = Double.NaN;
AppLogger.log("added interface provided nogo: " + n);
nogoList.add(n);
}
}
if (params.containsKey("nogos")) {
nogoList = readNogoList(params);
}
if (params.containsKey("polylines") ||
params.containsKey("polygons")) {
nogoPolygonsList = readNogoPolygons(params);
}
}
private List<OsmNodeNamed> readNogoList(Bundle params) {
// lon,lat,radius|...
String nogos = params.getString("nogos");
if (nogos == null) return null;
String[] lonLatRadList = nogos.split("\\|");
List<OsmNodeNamed> nogoList = new ArrayList<>();
for (int i = 0; i < lonLatRadList.length; i++) {
String[] lonLatRad = lonLatRadList[i].split(",");
String nogoWeight = "NaN";
if (lonLatRad.length > 3) {
nogoWeight = lonLatRad[3];
}
nogoList.add(readNogo(lonLatRad[0], lonLatRad[1], lonLatRad[2], nogoWeight));
}
return nogoList;
}
private static OsmNodeNamed readNogo(String lon, String lat, String radius, String nogoWeight) {
double weight = "undefined".equals(nogoWeight) ? Double.NaN : Double.parseDouble(nogoWeight);
return readNogo(Double.parseDouble(lon), Double.parseDouble(lat), Integer.parseInt(radius), weight);
}
private static OsmNodeNamed readNogo(double lon, double lat, int radius, double nogoWeight) {
OsmNodeNamed n = new OsmNodeNamed();
n.name = "nogo" + radius;
n.ilon = (int) ((lon + 180.) * 1000000. + 0.5);
n.ilat = (int) ((lat + 90.) * 1000000. + 0.5);
n.isNogo = true;
n.nogoWeight = nogoWeight;
return n;
}
private List<OsmNodeNamed> readNogoPolygons(Bundle params) {
List<OsmNodeNamed> result = new ArrayList<>();
parseNogoPolygons(params.getString("polylines"), result, false);
parseNogoPolygons(params.getString("polygons"), result, true);
return result.size() > 0 ? result : null;
}
private static void parseNogoPolygons(String polygons, List<OsmNodeNamed> result, boolean closed) {
if (polygons != null) {
String[] polygonList = polygons.split("\\|");
for (int i = 0; i < polygonList.length; i++) {
String[] lonLatList = polygonList[i].split(",");
if (lonLatList.length > 1) {
OsmNogoPolygon polygon = new OsmNogoPolygon(closed);
polygon.name = "nogo" + i;
int j;
for (j = 0; j < 2 * (lonLatList.length / 2) - 1; ) {
String slon = lonLatList[j++];
String slat = lonLatList[j++];
int lon = (int) ((Double.parseDouble(slon) + 180.) * 1000000. + 0.5);
int lat = (int) ((Double.parseDouble(slat) + 90.) * 1000000. + 0.5);
polygon.addVertex(lon, lat);
}
String nogoWeight = "NaN";
if (j < lonLatList.length) {
nogoWeight = lonLatList[j];
}
polygon.nogoWeight = Double.parseDouble(nogoWeight);
if (polygon.points.size() > 0) {
polygon.calcBoundingCircle();
result.add(polygon);
}
}
}
}
}
private List<OsmNodeNamed> readPoisList(Bundle params) {
// lon,lat,name|...
String pois = params.getString("pois");
if (pois == null) return null;
String[] lonLatNameList = pois.split("\\|");
List<OsmNodeNamed> poisList = new ArrayList<>();
for (int i = 0; i < lonLatNameList.length; i++) {
String[] lonLatName = lonLatNameList[i].split(",");
OsmNodeNamed n = new OsmNodeNamed();
n.ilon = (int) ((Double.parseDouble(lonLatName[0]) + 180.) * 1000000. + 0.5);
n.ilat = (int) ((Double.parseDouble(lonLatName[1]) + 90.) * 1000000. + 0.5);
n.name = lonLatName[2];
poisList.add(n);
}
return poisList;
}
private void writeTimeoutData(RoutingContext rc) throws Exception {
String timeoutFile = baseDir + "/brouter/modes/timeoutdata.txt";
@ -431,7 +208,7 @@ public class BRouterWorker {
bw.write(rc.rawTrackPath);
bw.write("\n");
writeWPList(bw, waypoints);
writeWPList(bw, nogoList);
writeWPList(bw, rc.nogopoints);
bw.close();
}

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<plurals name="numberOfSegments" tools:ignore="MissingQuantity">
<item quantity="one">%d قطعة</item>
<item quantity="other">%d قطع</item>
</plurals>
<string name="cancel_download">إلغاء التنزيل</string>
<string name="import_profile">استيراد ملف تعريف</string>
<string name="action_download">تنزيل %s</string>
<string name="action_delete">حذف %s</string>
<string name="action_update">تحديث %s</string>
<string name="action_select">حدد القطع</string>
<string name="action_cancel">إيقاف التنزيل</string>
<string name="summary_segments">الحجم=%1$s\nالحجم المتوفر=%2$s</string>
<string name="notification_title">تحميل القطع</string>
</resources>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<plurals name="numberOfSegments" tools:ignore="MissingQuantity">
<item quantity="one">%d segment</item>
<item quantity="other">%d segments</item>
</plurals>
<string name="cancel_download">Cancel·lar descàrrega</string>
<string name="import_profile">Importar perfil</string>
<string name="action_download">Descàrrega %s</string>
<string name="action_delete">Eliminar %s</string>
<string name="action_update">Actualitzar %s</string>
<string name="action_select">Seleccionar segments</string>
<string name="action_cancel">Aturar Descàrrega</string>
<string name="summary_segments">Espai=%1$s\nLliure=%2$s</string>
<string name="notification_title">Descarregar segments</string>
</resources>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<plurals name="numberOfSegments" tools:ignore="MissingQuantity">
<item quantity="one">%d Segment</item>
<item quantity="other">%d Segmente</item>
</plurals>
<string name="cancel_download">Download abbrechen</string>
<string name="import_profile">Profil importieren</string>
<string name="action_download">%s downloaden</string>
<string name="action_delete">%s löschen</string>
<string name="action_update">%s aktualisieren</string>
<string name="action_select">Segmente auswählen</string>
<string name="action_cancel">Download stoppen</string>
<string name="summary_segments">Größe=%1$s\nFrei=%2$s</string>
<string name="notification_title">Segmente herunterladen</string>
</resources>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<plurals name="numberOfSegments" tools:ignore="MissingQuantity">
<item quantity="one">%d τμήμα</item>
<item quantity="other">%d τμήματα</item>
</plurals>
<string name="cancel_download">Ακύρωση λήψης</string>
<string name="import_profile">Εισαγωγή προφίλ</string>
<string name="action_download">Λήψη %s</string>
<string name="action_delete">Διαγραφή %s</string>
<string name="action_update">Ενημέρωση %s</string>
<string name="action_select">Επιλογή τμημάτων</string>
<string name="action_cancel">Διακοπή λήψης</string>
<string name="summary_segments">Μέγεθος=%1$s\nΕλεύθερο=%2$s</string>
<string name="notification_title">Λήψη τμημάτων</string>
</resources>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<plurals name="numberOfSegments" tools:ignore="MissingQuantity">
<item quantity="one">%d segmento</item>
<item quantity="other">%d segmentos</item>
</plurals>
<string name="cancel_download">Cancelar descarga</string>
<string name="import_profile">Importar perfil</string>
<string name="action_download">Descargar %s</string>
<string name="action_delete">Eliminar %s</string>
<string name="action_update">Actualizar %s</string>
<string name="action_select">Seleccionar segmentos</string>
<string name="action_cancel">Detener descarga</string>
<string name="summary_segments">Tamaño=%1$s\nGratis=%2$s</string>
<string name="notification_title">Descargar segmentos</string>
</resources>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<plurals name="numberOfSegments" tools:ignore="MissingQuantity">
<item quantity="one">%d segment</item>
<item quantity="other">%d segments</item>
</plurals>
<string name="cancel_download">Annuler le téléchargement</string>
<string name="import_profile">Importer le profil</string>
<string name="action_download">Télécharger %s</string>
<string name="action_delete">Supprimer %s</string>
<string name="action_update">Mettre à jour %s</string>
<string name="action_select">Sélectionner les segments</string>
<string name="action_cancel">Arrêter le téléchargement</string>
<string name="summary_segments">Taille=%1$s\nGratuit=%2$s</string>
<string name="notification_title">Télécharger les segments</string>
</resources>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<plurals name="numberOfSegments" tools:ignore="MissingQuantity">
<item quantity="one">%d segmento</item>
<item quantity="other">%d segmenti</item>
</plurals>
<string name="cancel_download">Annulla download</string>
<string name="import_profile">Importa profilo</string>
<string name="action_download">Scarica %s</string>
<string name="action_delete">Elimina %s</string>
<string name="action_update">Aggiorna %s</string>
<string name="action_select">Seleziona segmenti</string>
<string name="action_cancel">Interrompi download</string>
<string name="summary_segments">Taglia=%1$s\nGratis=%2$s</string>
<string name="notification_title">Scarica segmenti</string>
</resources>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<plurals name="numberOfSegments" tools:ignore="MissingQuantity">
<item quantity="one">%d segment</item>
<item quantity="other">%d segmenten</item>
</plurals>
<string name="cancel_download">Download annuleren</string>
<string name="import_profile">Profiel importeren</string>
<string name="action_download">Downloaden %s</string>
<string name="action_delete">Verwijderen %s</string>
<string name="action_update">Bijwerken %s</string>
<string name="action_select">Segmenten selecteren</string>
<string name="action_cancel">Download stoppen</string>
<string name="summary_segments">Grootte=%1$s\nGratis=%2$s</string>
<string name="notification_title">Segmenten downloaden</string>
</resources>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<plurals name="numberOfSegments" tools:ignore="MissingQuantity">
<item quantity="one">%d segment</item>
<item quantity="other">%d segmenty/ów</item>
</plurals>
<string name="cancel_download">Anuluj pobieranie</string>
<string name="import_profile">Importuj profil</string>
<string name="action_download">Pobierz %s</string>
<string name="action_delete">Usuń %s</string>
<string name="action_update">Zaktualizuj %s</string>
<string name="action_select">Wybierz segmenty</string>
<string name="action_cancel">Zatrzymaj pobieranie</string>
<string name="summary_segments">Rozmiar=%1$s\nDostępne=%2$s</string>
<string name="notification_title">Pobieranie segmentów</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">BRouter</string>
<string name="profile_filename_example">filename.brf</string>
<string name="notification_channel_id">brouter_download</string>
<string name="channel_name">Downloads</string>
</resources>

View file

@ -1,37 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<plurals name="numberOfSegments">
<resources xmlns:tools="http://schemas.android.com/tools">
<plurals name="numberOfSegments" tools:ignore="MissingQuantity">
<item quantity="one">%d segment</item>
<item quantity="other">%d segments</item>
</plurals>
<string name="app_name">BRouter</string>
<string name="cancel_download">Cancel Download</string>
<string name="import_profile">Import Profile</string>
<string name="profile_filename_example">filename.brf</string>
<string name="download_info_start">Starting download…</string>
<string name="download_info_cancel">Cancelling…</string>
<string name="cancel_download">Cancel download</string>
<string name="import_profile">Import profile</string>
<string name="action_download">Download %s</string>
<string name="action_delete">Delete %s</string>
<string name="action_update">Update %s</string>
<string name="action_select">Select segments</string>
<string name="action_cancel">Stop Download</string>
<string name="summary_segments">Size=%s\nFree=%s</string>
<string name="notification_channel_id">brouter_download</string>
<string name="notification_title">Download Segments</string>
<string name="channel_name">Downloads</string>
<string name="action_cancel">Stop download</string>
<string name="summary_segments">Size=%1$s\nFree=%2$s</string>
<string name="notification_title">Download segments</string>
</resources>

View file

@ -1,11 +1,7 @@
package btools.server;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -14,11 +10,10 @@ import btools.router.OsmTrack;
import btools.router.RoutingContext;
import btools.router.RoutingEngine;
import btools.router.RoutingParamCollector;
import btools.router.SearchBoundary;
public class BRouter {
public static void main(String[] args) throws Exception {
if (args.length == 3) { // cgi-input-mode
if (args.length == 3 || args.length == 4) { // cgi-input-mode
try {
System.setProperty("segmentBaseDir", args[0]);
System.setProperty("profileBaseDir", args[1]);
@ -28,19 +23,27 @@ public class BRouter {
int lonIdx = queryString.indexOf("lonlats=");
int sepIdx = queryString.indexOf("&", lonIdx);
String lonlats = queryString.substring(lonIdx+8, sepIdx);
String lonlats = queryString.substring(lonIdx + 8, sepIdx);
RoutingContext rc = new RoutingContext();
RoutingParamCollector routingParamCollector = new RoutingParamCollector();
List<OsmNodeNamed> wplist = routingParamCollector.getWayPointList(lonlats);
Map<String, String> params = routingParamCollector.getUrlParams(queryString);
int engineMode = 0;
if (params.containsKey("engineMode")) {
engineMode = Integer.parseInt(params.get("engineMode"));
}
routingParamCollector.setParams(rc, wplist, params);
String exportName = null;
if (args.length == 4) {
exportName = args[3];
} else {
// cgi-header
System.out.println("Content-type: text/plain");
System.out.println();
}
long maxRunningTime = 60000; // the cgi gets a 1 Minute timeout
String sMaxRunningTime = System.getProperty("maxRunningTime");
@ -48,9 +51,7 @@ public class BRouter {
maxRunningTime = Integer.parseInt(sMaxRunningTime) * 1000;
}
RoutingEngine re = new RoutingEngine(null, null, new File(args[0]), wplist, rc);
RoutingEngine re = new RoutingEngine(exportName, null, new File(args[0]), wplist, rc, engineMode);
re.doRun(maxRunningTime);
if (re.getErrorMessage() != null) {
System.out.println(re.getErrorMessage());
@ -65,34 +66,10 @@ public class BRouter {
System.out.println("Find routes in an OSM map");
System.out.println("usage: java -jar brouter.jar <segmentdir> <profiledir> <engineMode> <profile> <lonlats-list> [parameter-list] [profile-parameter-list] ");
System.out.println(" or: java -cp %CLASSPATH% btools.server.BRouter <segmentdir>> <profiledir> <engineMode> <profile> <lonlats-list> [parameter-list] [profile-parameter-list]");
System.out.println(" or: java -jar brouter.jar <segmentdir> <profiledir> <parameter-list> ");
System.out.println(" or: java -jar brouter.jar <segmentdir> <profiledir> <parameter-list> [output-filename]");
System.exit(0);
}
RoutingEngine re = null;
if ("seed".equals(args[3])) {
List<OsmNodeNamed> wplist = new ArrayList<>();
wplist.add(readPosition(args, 1, "from"));
int searchRadius = Integer.parseInt(args[4]); // if = 0 search a 5x5 square
String filename = SearchBoundary.getFileName(wplist.get(0));
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("traffic/" + filename)));
for (int direction = 0; direction < 8; direction++) {
RoutingContext rc = readRoutingContext(args);
SearchBoundary boundary = new SearchBoundary(wplist.get(0), searchRadius, direction / 2);
rc.trafficOutputStream = dos;
rc.inverseDirection = (direction & 1) != 0;
re = new RoutingEngine("mytrack", "mylog", new File(args[0]), wplist, rc);
re.boundary = boundary;
re.airDistanceCostFactor = rc.trafficDirectionFactor;
rc.countTraffic = true;
re.doSearch();
if (re.getErrorMessage() != null) {
break;
}
}
dos.close();
} else {
int engineMode = 0;
try {
engineMode = Integer.parseInt(args[2]);
@ -124,7 +101,8 @@ public class BRouter {
routingParamCollector.setProfileParams(rc, params);
}
try {
if (engineMode==RoutingEngine.BROUTER_ENGINEMODE_GETELEV) {
RoutingEngine re = null;
if (engineMode == RoutingEngine.BROUTER_ENGINEMODE_GETELEV) {
re = new RoutingEngine("testinfo", null, new File(args[0]), wplist, rc, engineMode);
} else {
re = new RoutingEngine("testtrack", null, new File(args[0]), wplist, rc, engineMode);
@ -133,28 +111,8 @@ public class BRouter {
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
private static OsmNodeNamed readPosition(String[] args, int idx, String name) {
OsmNodeNamed n = new OsmNodeNamed();
n.name = name;
n.ilon = (int) ((Double.parseDouble(args[idx]) + 180.) * 1000000. + 0.5);
n.ilat = (int) ((Double.parseDouble(args[idx + 1]) + 90.) * 1000000. + 0.5);
return n;
}
private static RoutingContext readRoutingContext(String[] args) {
RoutingContext c = new RoutingContext();
if (args.length > 5) {
c.localFunction = args[5];
if (args.length > 6) {
c.setAlternativeIdx(Integer.parseInt(args[6]));
}
}
c.memoryclass = (int) (Runtime.getRuntime().maxMemory() / 1024 / 1024);
// c.startDirection= Integer.valueOf( 150 );
return c;
}
}

View file

@ -29,6 +29,7 @@ import btools.router.OsmTrack;
import btools.router.ProfileCache;
import btools.router.RoutingContext;
import btools.router.RoutingEngine;
import btools.router.RoutingParamCollector;
import btools.server.request.ProfileUploadHandler;
import btools.server.request.RequestHandler;
import btools.server.request.ServerHandler;
@ -146,7 +147,9 @@ public class RouteServer extends Thread implements Comparable<RouteServer> {
}
String url = getline.split(" ")[1];
Map<String, String> params = getUrlParams(url);
RoutingParamCollector routingParamCollector = new RoutingParamCollector();
Map<String, String> params = routingParamCollector.getUrlParams(url);
long maxRunningTime = getMaxRunningTime();
@ -186,33 +189,17 @@ public class RouteServer extends Thread implements Comparable<RouteServer> {
return;
}
RoutingContext rc = handler.readRoutingContext();
List<OsmNodeNamed> wplist = handler.readWayPointList();
List<OsmNodeNamed> wplist = routingParamCollector.getWayPointList(params.get("lonlats"));
if (wplist.size() < 10) {
SuspectManager.nearRecentWps.add(wplist);
}
int engineMode = 0;
for (Map.Entry<String, String> e : params.entrySet()) {
if ("engineMode".equals(e.getKey())) {
engineMode = Integer.parseInt(e.getValue());
} else if ("timode".equals(e.getKey())) {
rc.turnInstructionMode = Integer.parseInt(e.getValue());
} else if ("heading".equals(e.getKey())) {
rc.startDirection = Integer.parseInt(e.getValue());
rc.forceUseStartDirection = true;
} else if (e.getKey().startsWith("profile:")) {
if (rc.keyValues == null) {
rc.keyValues = new HashMap<>();
}
rc.keyValues.put(e.getKey().substring(8), e.getValue());
} else if (e.getKey().equals("straight")) {
String[] sa = e.getValue().split(",");
for (int i = 0; i < sa.length; i++) {
int v = Integer.parseInt(sa[i]);
if (wplist.size() > v) wplist.get(v).direct = true;
}
}
if (params.containsKey("engineMode")) {
engineMode = Integer.parseInt(params.get("engineMode"));
}
routingParamCollector.setParams(rc, wplist, params);
cr = new RoutingEngine(null, null, serviceContext.segmentDir, wplist, rc, engineMode);
cr.quite = true;
cr.doRun(maxRunningTime);
@ -224,8 +211,13 @@ public class RouteServer extends Thread implements Comparable<RouteServer> {
} else {
OsmTrack track = cr.getFoundTrack();
if (engineMode == 2) {
// no zip for this engineMode
encodings = null;
}
String headers = encodings == null || encodings.indexOf("gzip") < 0 ? null : "Content-Encoding: gzip\n";
writeHttpHeader(bw, handler.getMimeType(), handler.getFileName(), headers, HTTP_STATUS_OK);
if (engineMode == 0) {
if (track != null) {
if (headers != null) { // compressed
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@ -238,6 +230,12 @@ public class RouteServer extends Thread implements Comparable<RouteServer> {
bw.write(handler.formatTrack(track));
}
}
} else if (engineMode == 2) {
String s = cr.getFoundInfo();
if (s != null) {
bw.write(s);
}
}
}
bw.flush();
} catch (Throwable e) {

View file

@ -1,9 +1,7 @@
package btools.server.request;
import java.util.List;
import java.util.Map;
import btools.router.OsmNodeNamed;
import btools.router.OsmTrack;
import btools.router.RoutingContext;
import btools.server.ServiceContext;
@ -19,8 +17,6 @@ public abstract class RequestHandler {
public abstract RoutingContext readRoutingContext();
public abstract List<OsmNodeNamed> readWayPointList();
public abstract String formatTrack(OsmTrack track);
public abstract String getMimeType();

View file

@ -1,14 +1,12 @@
package btools.server.request;
import java.io.BufferedWriter;
import java.io.File;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import btools.router.OsmNodeNamed;
import btools.router.OsmNogoPolygon;
import btools.router.FormatCsv;
import btools.router.FormatGpx;
import btools.router.FormatJson;
import btools.router.FormatKml;
import btools.router.OsmTrack;
import btools.router.RoutingContext;
import btools.server.ServiceContext;
@ -62,59 +60,9 @@ public class ServerHandler extends RequestHandler {
}
rc.localFunction = profile;
rc.setAlternativeIdx(Integer.parseInt(params.get("alternativeidx")));
List<OsmNodeNamed> poisList = readPoisList();
rc.poipoints = poisList;
List<OsmNodeNamed> nogoList = readNogoList();
List<OsmNodeNamed> nogoPolygonsList = readNogoPolygons();
if (nogoList != null) {
RoutingContext.prepareNogoPoints(nogoList);
rc.nogopoints = nogoList;
}
if (rc.nogopoints == null) {
rc.nogopoints = nogoPolygonsList;
} else if (nogoPolygonsList != null) {
rc.nogopoints.addAll(nogoPolygonsList);
}
return rc;
}
@Override
public List<OsmNodeNamed> readWayPointList() {
// lon,lat|...
String lonLats = params.get("lonlats");
if (lonLats == null) throw new IllegalArgumentException("lonlats parameter not set");
String[] coords = lonLats.split("\\|");
if (coords.length < 2)
throw new IllegalArgumentException("we need two lat/lon points at least!");
List<OsmNodeNamed> wplist = new ArrayList<>();
for (int i = 0; i < coords.length; i++) {
String[] lonLat = coords[i].split(",");
if (lonLat.length < 2)
throw new IllegalArgumentException("we need two lat/lon points at least!");
wplist.add(readPosition(lonLat[0], lonLat[1], "via" + i));
if (lonLat.length > 2) {
if (lonLat[2].equals("d")) {
wplist.get(wplist.size()-1).direct = true;
} else {
wplist.get(wplist.size()-1).name = lonLat[2];
}
}
}
if (wplist.get(0).name.startsWith("via")) wplist.get(0).name = "from";
if (wplist.get(wplist.size() - 1).name.startsWith("via")) wplist.get(wplist.size() - 1).name = "to";
return wplist;
}
@Override
public String formatTrack(OsmTrack track) {
String result;
@ -130,23 +78,17 @@ public class ServerHandler extends RequestHandler {
}
if (format == null || "gpx".equals(format)) {
result = track.formatAsGpx();
result = new FormatGpx(rc).format(track);
} else if ("kml".equals(format)) {
result = track.formatAsKml();
result = new FormatKml(rc).format(track);
} else if ("geojson".equals(format)) {
result = track.formatAsGeoJson();
result = new FormatJson(rc).format(track);
} else if ("csv".equals(format)) {
try {
StringWriter sw = new StringWriter();
BufferedWriter bw = new BufferedWriter(sw);
track.writeMessages(bw, rc);
return sw.toString();
} catch (Exception ex) {
return "Error: " + ex.getMessage();
}
result = new FormatCsv(rc).format(track);
} else {
System.out.println("unknown track format '" + format + "', using default");
result = track.formatAsGpx();
//result = track.formatAsGpx();
result = new FormatGpx(rc).format(track);
}
return result;
@ -191,115 +133,4 @@ public class ServerHandler extends RequestHandler {
return params.get("trackname") == null ? null : params.get("trackname").replaceAll("[^a-zA-Z0-9 \\._\\-]+", "");
}
private static OsmNodeNamed readPosition(String vlon, String vlat, String name) {
if (vlon == null) throw new IllegalArgumentException("lon " + name + " not found in input");
if (vlat == null) throw new IllegalArgumentException("lat " + name + " not found in input");
return readPosition(Double.parseDouble(vlon), Double.parseDouble(vlat), name);
}
private static OsmNodeNamed readPosition(double lon, double lat, String name) {
OsmNodeNamed n = new OsmNodeNamed();
n.name = name;
n.ilon = (int) ((lon + 180.) * 1000000. + 0.5);
n.ilat = (int) ((lat + 90.) * 1000000. + 0.5);
return n;
}
private List<OsmNodeNamed> readPoisList() {
// lon,lat,name|...
String pois = params.get("pois");
if (pois == null) return null;
String[] lonLatNameList = pois.split("\\|");
List<OsmNodeNamed> poisList = new ArrayList<>();
for (int i = 0; i < lonLatNameList.length; i++) {
String[] lonLatName = lonLatNameList[i].split(",");
if (lonLatName.length != 3)
continue;
OsmNodeNamed n = new OsmNodeNamed();
n.ilon = (int) ((Double.parseDouble(lonLatName[0]) + 180.) * 1000000. + 0.5);
n.ilat = (int) ((Double.parseDouble(lonLatName[1]) + 90.) * 1000000. + 0.5);
n.name = lonLatName[2];
poisList.add(n);
}
return poisList;
}
private List<OsmNodeNamed> readNogoList() {
// lon,lat,radius|...
String nogos = params.get("nogos");
if (nogos == null) return null;
String[] lonLatRadList = nogos.split("\\|");
List<OsmNodeNamed> nogoList = new ArrayList<>();
for (int i = 0; i < lonLatRadList.length; i++) {
String[] lonLatRad = lonLatRadList[i].split(",");
String nogoWeight = "NaN";
if (lonLatRad.length > 3) {
nogoWeight = lonLatRad[3];
}
nogoList.add(readNogo(lonLatRad[0], lonLatRad[1], lonLatRad[2], nogoWeight));
}
return nogoList;
}
private static OsmNodeNamed readNogo(String lon, String lat, String radius, String nogoWeight) {
double weight = "undefined".equals(nogoWeight) ? Double.NaN : Double.parseDouble(nogoWeight);
return readNogo(Double.parseDouble(lon), Double.parseDouble(lat), Integer.parseInt(radius), weight);
}
private static OsmNodeNamed readNogo(double lon, double lat, int radius, double nogoWeight) {
OsmNodeNamed n = new OsmNodeNamed();
n.name = "nogo" + radius;
n.ilon = (int) ((lon + 180.) * 1000000. + 0.5);
n.ilat = (int) ((lat + 90.) * 1000000. + 0.5);
n.isNogo = true;
n.nogoWeight = nogoWeight;
return n;
}
private List<OsmNodeNamed> readNogoPolygons() {
List<OsmNodeNamed> result = new ArrayList<>();
parseNogoPolygons(params.get("polylines"), result, false);
parseNogoPolygons(params.get("polygons"), result, true);
return result.size() > 0 ? result : null;
}
private static void parseNogoPolygons(String polygons, List<OsmNodeNamed> result, boolean closed) {
if (polygons != null) {
String[] polygonList = polygons.split("\\|");
for (int i = 0; i < polygonList.length; i++) {
String[] lonLatList = polygonList[i].split(",");
if (lonLatList.length > 1) {
OsmNogoPolygon polygon = new OsmNogoPolygon(closed);
int j;
for (j = 0; j < 2 * (lonLatList.length / 2) - 1; ) {
String slon = lonLatList[j++];
String slat = lonLatList[j++];
int lon = (int) ((Double.parseDouble(slon) + 180.) * 1000000. + 0.5);
int lat = (int) ((Double.parseDouble(slat) + 90.) * 1000000. + 0.5);
polygon.addVertex(lon, lat);
}
String nogoWeight = "NaN";
if (j < lonLatList.length) {
nogoWeight = lonLatList[j];
}
polygon.nogoWeight = Double.parseDouble(nogoWeight);
if (polygon.points.size() > 0) {
polygon.calcBoundingCircle();
result.add(polygon);
}
}
}
}
}
}

View file

@ -27,3 +27,5 @@ end
# Performance-booster for watching directories on Windows
gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin]
gem "webrick", "~> 1.8"

View file

@ -249,6 +249,7 @@ GEM
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
webrick (1.8.1)
PLATFORMS
x86_64-linux
@ -258,6 +259,7 @@ DEPENDENCIES
tzinfo (~> 1.2)
tzinfo-data
wdm (~> 0.1.1)
webrick (~> 1.8)
BUNDLED WITH
2.4.13
2.4.20

15
docs/_README.md Normal file
View file

@ -0,0 +1,15 @@
# BRouter Docs
This documentation can be used to generate a static website using [jekyll](https://jekyllrb.com/).
## Dependencies
jekyll uses ruby and therefore installs dependencies using gem. It is recommended to use bundler to manage those dependencies.
`bundle install` installs all dependencies. To update dependencies use `bundle update`.
## Preview
jekyll provides a built-in webserver which can be used for fast feedback during editing.
`bundle exec jekyll serve`

View file

@ -1,49 +1,67 @@
Environmental considerations (penalties for traffic, noise, town, no river, no forest) are possible due to the creation of pseudo tags during processing OSM data by spatial SQL queries in https://github.com/abrensch/brouter/blob/master/misc/scripts/mapcreation/brouter.sql. During this processing, roads are extended by a 32 m buffer creating 64 m wide lines. Then it is calculated what percentage of such line is at a specific distance to a noise source or within a forest, for example. The percentage is converted to a factor and the factor is assigned to a class. Ways that pass through different environments and are represented by a single OSM way can be problematic because the class is always based on the average environment along an entire OSM way. For traffic, calculations are on another level of complexity.
---
parent: Developers
---
# Environmental considerations
Environmental considerations (penalties for traffic, noise, town, no river, no forest) are possible due to the creation of pseudo tags during processing OSM data by spatial SQL queries in [brouter.sql](https://github.com/abrensch/brouter/blob/master/misc/scripts/mapcreation/brouter.sql). During this processing, roads are extended by a 32 m buffer creating 64 m wide lines. Then it is calculated what percentage of such line is at a specific distance to a noise source or within a forest, for example. The percentage is converted to a factor and the factor is assigned to a class. Ways that pass through different environments and are represented by a single OSM way can be problematic because the class is always based on the average environment along an entire OSM way. For traffic, calculations are on another level of complexity.
### noise_class
### consider_noise, noise_penalty
For proximity of noisy roads (secondary and higher). The noise factor represents the proportion of a road's buffer area that lies within the 64-meter buffer of noisy roads. This proportion is reduced:
- for motorways and trunk roads with max speed < 105 by 1.5
- for primary roads 2 times
- 3 times if maxspeed is 75 - 105 for primary and secondary
- other secondary roads 5 times
Noise class is roughly proportional to the noise factor:
`noise_class` is roughly proportional to the noise factor:
noise_factor = noise class
- < 0.1 = '1'
- < 0.25 = '2'
- < 0.4 = '3'
- < 0.55 = '4'
- < 0.8 = '5'
- ELSE = '6'
| `noise_factor` | `noise_class` |
| -------------- | ------------- |
| < 0.1 | 1 |
| < 0.25 | 2 |
| < 0.4 | 3 |
| < 0.55 | 4 |
| < 0.8 | 5 |
| ELSE | 6 |
To be classified as noise class 6, a way must be less than 13 m on average from the middle of the carriageway of a motorway with a maximum speed exceeding 105. For a class 5, the distance must be up to 35 meters. (1 - noise factor) * 64 m for a given class determines the distance
To be classified as noise class 6, a way must be less than 13 m on average from the middle of the carriageway of a motorway with a maximum speed exceeding 105. For a class 5, the distance must be up to 35 meters. (1 - noise_factor) \* 64 m for a given class determines the distance
**Max noise class:**
| Max speed | Motorway, trunk |Primary|Secondary |
|--- |:---: |:---: |:---: |
| >105 |6 |4 | 3 |
| 105 |5 |4 |3 |
| 75 |5 |3 |2 |
| highway | maxspeed | max `noise_class` |
| -------------- | -------- | ----------------- |
| motorway,trunk | > 105 | 6 |
| motorway,trunk | 105 | 5 |
| motorway,trunk | 75 | 5 |
| primary | > 105 | 4 |
| primary | 105 | 4 |
| primary | 75 | 3 |
| secondary | > 105 | 3 |
| secondary | 105 | 3 |
| secondary | 75 | 2 |
### river_class
### consider_river, no_river_penalty
OSM data recognized as river:
- waterway: river, canal
- natural: water (except wastewater)
Waterways have 32 m wide buffers. Water areas have 77 m wide buffers.
river_see = river class
- < 0.17 = '1'
- < 0.35 = '2'
- < 0.57 = '3'
- < 0.80 = '4'
- < 0.95 = '5'
- ELSE = '6'
| `river_see` | `river_class` |
| ----------- | ------------- |
| < 0.1 | 1 |
| < 0.3 | 2 |
| < 0.5 | 3 |
| < 0.8 | 4 |
| < 0.9 | 5 |
| ELSE | 6 |
### forest_class
### consider_forest, no_forest_penalty
OSM data recognized as forest:
- landuse: forest, allotments, flowerbed, orchard, vineyard, recreation_ground, village_green
- leisure: garden, park, nature_reserve
@ -51,31 +69,34 @@ No forest buffers are used.
Imagine you trace the way with a pencil drawing lines 62 meters wide. Then estimated_forest_class=6 corresponds to the case that at least 98% of the line is in the woodland. This number is called a green factor.
green_factor = forest class
- < 0.1 = NULL
- < 0.2 = '1'
- < 0.4 = '2'
- < 0.6 = '3'
- < 0.8 = '4'
- < 0.98 = '5'
- ELSE = '6'
| `green_factor` | `forest_class` |
| -------------- | -------------- |
| < 0.1 | NULL |
| < 0.2 | 1 |
| < 0.4 | 2 |
| < 0.6 | 3 |
| < 0.8 | 4 |
| < 0.98 | 5 |
| ELSE | 6 |
### town_class
### consider_town, town_penalty
Town_class is determined by population data from OSM.
Class
- 1 = 50-80 k people
- 2 = 80-150 k people
- 3 = 150 - 400 k people
- 4 = 400 - 1,000 k people
- 5 = 1 - 2 million people
- 6 = > 2 million people
| population | `town_class` |
| ------------------ | ------------ |
| < 80 k people | 1 |
| < 150 k people | 2 |
| < 400 k people | 3 |
| < 1 million people | 4 |
| < 2 million people | 5 |
| > 2 million people | 6 |
### traffic_class
### consider_traffic, traffic_penalty
(modified copy from the sql file).
OSM data used to estimate the traffic:
- population of towns (+ distance from position to the towns)
- size of industrial areas (landuse=industrial) and distance to them. Not considered: solar & wind farms.
- airports international
@ -83,5 +104,5 @@ OSM data used to estimate the traffic:
- density of highways (tertiary and higher) calculated on a grid (100 km^2). Traffic decreases when more such roads are available. Exceptions: near junctions between motorways and other roads the traffic increases on these roads.
- mountain-ranges calculated as density of peaks > 400 m traffic is generally on highways in such regions higher as only generated by the local population or industrial areas
- calculate traffic from the population (for each segment of type primary secondary tertiary)
- SUM of (population of each town < 100 km) / ( town-radius + 2500 + dist(segment-position to the town) ** 2 )
- SUM of (population of each town < 100 km) / ( town-radius + 2500 + dist(segment-position to the town) \*\* 2 )
- town-radius is calculated as sqrt(population)