From 68b6edc58002c03b719c821ed9623a008a42ed4a Mon Sep 17 00:00:00 2001 From: afischerdev Date: Wed, 2 Apr 2025 18:58:31 +0200 Subject: [PATCH 01/37] added switch to avoid u-turns --- brouter-core/src/main/java/btools/router/RoutingContext.java | 3 +++ brouter-core/src/main/java/btools/router/RoutingEngine.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/brouter-core/src/main/java/btools/router/RoutingContext.java b/brouter-core/src/main/java/btools/router/RoutingContext.java index f5b2bf3..c1d63c5 100644 --- a/brouter-core/src/main/java/btools/router/RoutingContext.java +++ b/brouter-core/src/main/java/btools/router/RoutingContext.java @@ -77,6 +77,7 @@ public final class RoutingContext { public double waypointCatchingRange; public boolean correctMisplacedViaPoints; public double correctMisplacedViaPointsDistance; + public boolean continueStraight; public boolean useDynamicDistance; public boolean buildBeelineOnRange; @@ -126,6 +127,8 @@ public final class RoutingContext { correctMisplacedViaPoints = 0.f != expctxGlobal.getVariableValue("correctMisplacedViaPoints", 1.f); correctMisplacedViaPointsDistance = expctxGlobal.getVariableValue("correctMisplacedViaPointsDistance", 0.f); // 0 == don't use distance + continueStraight = 0.f != expctxGlobal.getVariableValue("continueStraight", 0.f); + // process tags not used in the profile (to have them in the data-tab) processUnusedTags = 0.f != expctxGlobal.getVariableValue("processUnusedTags", 0.f); diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index 698bc7b..8c2e9de 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -995,7 +995,7 @@ public class RoutingEngine extends Thread { } else { seg = searchTrack(matchedWaypoints.get(i), matchedWaypoints.get(i + 1), i == matchedWaypoints.size() - 2 ? nearbyTrack : null, refTracks[i]); wptIndex = i; - if (engineMode == BROUTER_ENGINEMODE_ROUNDTRIP) { + if (routingContext.continueStraight) { if (i < matchedWaypoints.size() - 2) { OsmNode lastPoint = seg.containsNode(matchedWaypoints.get(i+1).node1) ? matchedWaypoints.get(i+1).node1 : matchedWaypoints.get(i+1).node2; OsmNodeNamed nogo = new OsmNodeNamed(lastPoint); From acbda6c3e7ccc473b7c3fcc493a360feba3dee10 Mon Sep 17 00:00:00 2001 From: Nitue Date: Sat, 5 Apr 2025 00:37:23 +0300 Subject: [PATCH 02/37] RFC7230 compliant HTTP header delimiter Changes HTTP header endings from LF to CRLF. RFC7230 states that headers are separated by CRLF. Brouter currently uses only LF. Most recipients probably accept LF only, but some are strict about this. --- .../main/java/btools/server/RouteServer.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/brouter-server/src/main/java/btools/server/RouteServer.java b/brouter-server/src/main/java/btools/server/RouteServer.java index 4b2be7a..2def82b 100644 --- a/brouter-server/src/main/java/btools/server/RouteServer.java +++ b/brouter-server/src/main/java/btools/server/RouteServer.java @@ -160,8 +160,8 @@ public class RouteServer extends Thread implements Comparable { } else if (url.startsWith(PROFILE_UPLOAD_URL)) { if (getline.startsWith("OPTIONS")) { // handle CORS preflight request (Safari) - String corsHeaders = "Access-Control-Allow-Methods: GET, POST\n" - + "Access-Control-Allow-Headers: Content-Type\n"; + String corsHeaders = "Access-Control-Allow-Methods: GET, POST\r\n" + + "Access-Control-Allow-Headers: Content-Type\r\n"; writeHttpHeader(bw, "text/plain", null, corsHeaders, HTTP_STATUS_OK); bw.flush(); return; @@ -220,7 +220,7 @@ public class RouteServer extends Thread implements Comparable { // no zip for this engineMode encodings = null; } - String headers = encodings == null || encodings.indexOf("gzip") < 0 ? null : "Content-Encoding: gzip\n"; + String headers = encodings == null || encodings.indexOf("gzip") < 0 ? null : "Content-Encoding: gzip\r\n"; writeHttpHeader(bw, handler.getMimeType(), handler.getFileName(), headers, HTTP_STATUS_OK); if (engineMode == RoutingEngine.BROUTER_ENGINEMODE_ROUTING || engineMode == RoutingEngine.BROUTER_ENGINEMODE_ROUNDTRIP) { @@ -407,17 +407,17 @@ public class RouteServer extends Thread implements Comparable { private static void writeHttpHeader(BufferedWriter bw, String mimeType, String fileName, String headers, String status) throws IOException { // http-header - bw.write(String.format("HTTP/1.1 %s\n", status)); - bw.write("Connection: close\n"); - bw.write("Content-Type: " + mimeType + "; charset=utf-8\n"); + bw.write(String.format("HTTP/1.1 %s\r\n", status)); + bw.write("Connection: close\r\n"); + bw.write("Content-Type: " + mimeType + "; charset=utf-8\r\n"); if (fileName != null) { - bw.write("Content-Disposition: attachment; filename=\"" + fileName + "\"\n"); + bw.write("Content-Disposition: attachment; filename=\"" + fileName + "\"\r\n"); } - bw.write("Access-Control-Allow-Origin: *\n"); + bw.write("Access-Control-Allow-Origin: *\r\n"); if (headers != null) { bw.write(headers); } - bw.write("\n"); + bw.write("\r\n"); } private static void cleanupThreadQueue(Queue threadQueue) { From 02733ffbec5bd693c677753b0bf08f4b9976b3ec Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 5 Apr 2025 12:29:55 +0200 Subject: [PATCH 03/37] reactivated end distance control --- .../src/main/java/btools/router/RoutingEngine.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index 8c2e9de..e8c80b5 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -1137,7 +1137,12 @@ public class RoutingEngine extends Thread { indexfore++; if (routingContext.correctMisplacedViaPointsDistance > 0 && - wayDistance > routingContext.correctMisplacedViaPointsDistance) break; + wayDistance > routingContext.correctMisplacedViaPointsDistance) { + removeVoiceHintList.clear(); + removeBackList.clear(); + removeForeList.clear(); + return false; + } } From 2682981da9655bbda48a12f5f7643d167130e6de Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 5 Apr 2025 18:08:46 +0200 Subject: [PATCH 04/37] added export for corrected via points --- .../src/main/java/btools/router/OsmTrack.java | 2 ++ .../main/java/btools/router/RoutingContext.java | 1 + .../src/main/java/btools/router/RoutingEngine.java | 14 ++++++++++++++ .../java/btools/router/RoutingParamCollector.java | 2 ++ .../main/java/btools/routingapp/BRouterWorker.java | 1 + .../java/btools/server/request/ServerHandler.java | 4 ++++ 6 files changed, 24 insertions(+) diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index ccc232a..b56a7df 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -61,7 +61,9 @@ public final class OsmTrack { public String name = "unset"; protected List matchedWaypoints; + protected List correctedWaypoints; public boolean exportWaypoints = false; + public boolean exportCorrectedWaypoints = false; public void addNode(OsmPathElement node) { nodes.add(0, node); diff --git a/brouter-core/src/main/java/btools/router/RoutingContext.java b/brouter-core/src/main/java/btools/router/RoutingContext.java index f5b2bf3..03b6b4a 100644 --- a/brouter-core/src/main/java/btools/router/RoutingContext.java +++ b/brouter-core/src/main/java/btools/router/RoutingContext.java @@ -221,6 +221,7 @@ public final class RoutingContext { public String outputFormat = "gpx"; public boolean exportWaypoints = false; + public boolean exportCorrectedWaypoints = false; public OsmPrePath firstPrePath; diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index 698bc7b..351aa61 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -41,6 +41,7 @@ public class RoutingEngine extends Thread { private boolean finished = false; protected List waypoints = null; + protected List correctedWaypoints = null; List extraWaypoints = null; protected List matchedWaypoints; private int linksProcessed = 0; @@ -262,6 +263,7 @@ public class RoutingEngine extends Thread { } oldTrack = null; track.exportWaypoints = routingContext.exportWaypoints; + track.exportCorrectedWaypoints = routingContext.exportCorrectedWaypoints; filename = outfileBase + i + "." + routingContext.outputFormat; switch (routingContext.outputFormat) { case "gpx": @@ -975,6 +977,10 @@ public class RoutingEngine extends Thread { hasDirectRouting = true; } } + for (MatchedWaypoint mwp : matchedWaypoints) { + //System.out.println(FormatGpx.getWaypoint(mwp.waypoint.ilon, mwp.waypoint.ilat, mwp.name, null)); + //System.out.println(FormatGpx.getWaypoint(mwp.crosspoint.ilon, mwp.crosspoint.ilat, mwp.name+"_cp", null)); + } routingContext.hasDirectRouting = hasDirectRouting; @@ -1030,6 +1036,7 @@ public class RoutingEngine extends Thread { matchedWaypoints.get(matchedWaypoints.size() - 1).indexInTrack = totaltrack.nodes.size() - 1; totaltrack.matchedWaypoints = matchedWaypoints; + totaltrack.correctedWaypoints = correctedWaypoints; totaltrack.processVoiceHints(routingContext); totaltrack.prepareSpeedProfile(routingContext); @@ -1183,6 +1190,13 @@ public class RoutingEngine extends Thread { setNewVoiceHint(t, last, lastJunctions, newJunction, newTarget); + if (correctedWaypoints == null) correctedWaypoints = new ArrayList<>(); + OsmNodeNamed n = new OsmNodeNamed(); + n.ilon = newJunction.getILon(); + n.ilat = newJunction.getILat(); + n.name = startWp.name + "_corr"; + correctedWaypoints.add(n); + return true; } return false; diff --git a/brouter-core/src/main/java/btools/router/RoutingParamCollector.java b/brouter-core/src/main/java/btools/router/RoutingParamCollector.java index f239315..1bcc6ee 100644 --- a/brouter-core/src/main/java/btools/router/RoutingParamCollector.java +++ b/brouter-core/src/main/java/btools/router/RoutingParamCollector.java @@ -227,6 +227,8 @@ public class RoutingParamCollector { } } else if (key.equals("exportWaypoints")) { rctx.exportWaypoints = (Integer.parseInt(value) == 1); + } else if (key.equals("exportCorrectedWaypoints")) { + rctx.exportCorrectedWaypoints = (Integer.parseInt(value) == 1); } else if (key.equals("format")) { rctx.outputFormat = ((String) value).toLowerCase(); } else if (key.equals("trackFormat")) { diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java index 33ee802..437d136 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java @@ -161,6 +161,7 @@ public class BRouterWorker { track = cr.getFoundTrack(); if (track != null) { track.exportWaypoints = rc.exportWaypoints; + track.exportCorrectedWaypoints = rc.exportCorrectedWaypoints; if (pathToFileResult == null) { switch (writeFromat) { case OUTPUT_FORMAT_KML: diff --git a/brouter-server/src/main/java/btools/server/request/ServerHandler.java b/brouter-server/src/main/java/btools/server/request/ServerHandler.java index 37ba145..fcb536c 100644 --- a/brouter-server/src/main/java/btools/server/request/ServerHandler.java +++ b/brouter-server/src/main/java/btools/server/request/ServerHandler.java @@ -78,6 +78,10 @@ public class ServerHandler extends RequestHandler { if (exportWaypointsStr != null && Integer.parseInt(exportWaypointsStr) != 0) { track.exportWaypoints = true; } + exportWaypointsStr = params.get("exportCorrectedWaypoints"); + if (exportWaypointsStr != null && Integer.parseInt(exportWaypointsStr) != 0) { + track.exportCorrectedWaypoints = true; + } if (format == null || "gpx".equals(format)) { result = new FormatGpx(rc).format(track); From 7e117d96759b8aad5f290740efb177872d162b66 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 5 Apr 2025 18:09:15 +0200 Subject: [PATCH 05/37] added export formats --- .../main/java/btools/router/FormatGpx.java | 41 ++++++++++++++----- .../main/java/btools/router/FormatJson.java | 16 +++++++- .../main/java/btools/router/FormatKml.java | 16 +++++++- 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/FormatGpx.java b/brouter-core/src/main/java/btools/router/FormatGpx.java index c652a0a..6f969d1 100644 --- a/brouter-core/src/main/java/btools/router/FormatGpx.java +++ b/brouter-core/src/main/java/btools/router/FormatGpx.java @@ -197,25 +197,28 @@ public class FormatGpx extends Formatter { for (int i = 0; i <= t.pois.size() - 1; i++) { OsmNodeNamed poi = t.pois.get(i); - formatWaypointGpx(sb, poi); + formatWaypointGpx(sb, poi, "poi"); } if (t.exportWaypoints) { for (int i = 0; i <= t.matchedWaypoints.size() - 1; i++) { MatchedWaypoint wt = t.matchedWaypoints.get(i); - sb.append(" \n") - .append(" ").append(StringUtils.escapeXml10(wt.name)).append("\n"); if (i == 0) { - sb.append(" from\n"); + formatWaypointGpx(sb, wt, "from"); } else if (i == t.matchedWaypoints.size() - 1) { - sb.append(" to\n"); + formatWaypointGpx(sb, wt, "to"); } else { - sb.append(" via\n"); + formatWaypointGpx(sb, wt, "via"); } - sb.append(" \n"); } } + if (t.exportCorrectedWaypoints && t.correctedWaypoints != null) { + for (int i = 0; i <= t.correctedWaypoints.size() - 1; i++) { + OsmNodeNamed n = t.correctedWaypoints.get(i); + formatWaypointGpx(sb, n, "via_corr"); + } + } + sb.append(" \n"); if (turnInstructionMode == 9 || turnInstructionMode == 2 @@ -454,7 +457,7 @@ public class FormatGpx extends Formatter { StringWriter sw = new StringWriter(8192); BufferedWriter bw = new BufferedWriter(sw); formatGpxHeader(bw); - formatWaypointGpx(bw, n); + formatWaypointGpx(bw, n, null); formatGpxFooter(bw); bw.close(); sw.close(); @@ -477,7 +480,7 @@ public class FormatGpx extends Formatter { sb.append("\n"); } - public void formatWaypointGpx(BufferedWriter sb, OsmNodeNamed n) throws IOException { + public void formatWaypointGpx(BufferedWriter sb, OsmNodeNamed n, String type) throws IOException { sb.append(" "); if (n.getSElev() != Short.MIN_VALUE) { @@ -489,6 +492,24 @@ public class FormatGpx extends Formatter { if (n.nodeDescription != null && rc != null) { sb.append("").append(rc.expctxWay.getKeyValueDescription(false, n.nodeDescription)).append(""); } + if (type != null) { + sb.append("").append(type).append(""); + } + sb.append("\n"); + } + + public void formatWaypointGpx(BufferedWriter sb, MatchedWaypoint wp, String type) throws IOException { + sb.append(" "); + if (wp.waypoint.getSElev() != Short.MIN_VALUE) { + sb.append("").append("" + wp.waypoint.getElev()).append(""); + } + if (wp.name != null) { + sb.append("").append(StringUtils.escapeXml10(wp.name)).append(""); + } + if (type != null) { + sb.append("").append(type).append(""); + } sb.append("\n"); } diff --git a/brouter-core/src/main/java/btools/router/FormatJson.java b/brouter-core/src/main/java/btools/router/FormatJson.java index 53f0b4a..c380fa4 100644 --- a/brouter-core/src/main/java/btools/router/FormatJson.java +++ b/brouter-core/src/main/java/btools/router/FormatJson.java @@ -126,7 +126,7 @@ public class FormatJson extends Formatter { sb.append(" ]\n"); sb.append(" }\n"); - if (t.exportWaypoints || !t.pois.isEmpty()) { + if (t.exportWaypoints || t.exportCorrectedWaypoints || !t.pois.isEmpty()) { sb.append(" },\n"); for (int i = 0; i <= t.pois.size() - 1; i++) { OsmNodeNamed poi = t.pois.get(i); @@ -137,6 +137,7 @@ public class FormatJson extends Formatter { sb.append(" \n"); } if (t.exportWaypoints) { + if (!t.pois.isEmpty()) sb.append(" ,\n"); for (int i = 0; i <= t.matchedWaypoints.size() - 1; i++) { String type; if (i == 0) { @@ -155,6 +156,19 @@ public class FormatJson extends Formatter { sb.append(" \n"); } } + if (t.exportCorrectedWaypoints) { + if (t.exportWaypoints) sb.append(" ,\n"); + for (int i = 0; i <= t.correctedWaypoints.size() - 1; i++) { + String type = "via_corr"; + + OsmNodeNamed wp = t.correctedWaypoints.get(i); + addFeature(sb, type, wp.name, wp.ilat, wp.ilon, wp.getSElev()); + if (i < t.correctedWaypoints.size() - 1) { + sb.append(","); + } + sb.append(" \n"); + } + } } else { sb.append(" }\n"); } diff --git a/brouter-core/src/main/java/btools/router/FormatKml.java b/brouter-core/src/main/java/btools/router/FormatKml.java index 5798c5c..79d1e08 100644 --- a/brouter-core/src/main/java/btools/router/FormatKml.java +++ b/brouter-core/src/main/java/btools/router/FormatKml.java @@ -43,7 +43,7 @@ public class FormatKml extends Formatter { sb.append(" \n"); sb.append(" \n"); sb.append(" \n"); - if (t.exportWaypoints || !t.pois.isEmpty()) { + if (t.exportWaypoints || t.exportCorrectedWaypoints || !t.pois.isEmpty()) { if (!t.pois.isEmpty()) { sb.append(" \n"); sb.append(" poi\n"); @@ -62,6 +62,10 @@ public class FormatKml extends Formatter { } createFolder(sb, "end", t.matchedWaypoints.subList(size - 1, size)); } + if (t.exportCorrectedWaypoints) { + int size = t.correctedWaypoints.size(); + createViaFolder(sb, "via_cor", t.correctedWaypoints.subList(0, size)); + } } sb.append(" \n"); sb.append("\n"); @@ -79,6 +83,16 @@ public class FormatKml extends Formatter { sb.append(" \n"); } + private void createViaFolder(StringBuilder sb, String type, List waypoints) { + sb.append(" \n"); + sb.append(" " + type + "\n"); + for (int i = 0; i < waypoints.size(); i++) { + OsmNodeNamed wp = waypoints.get(i); + createPlaceMark(sb, wp.name, wp.ilat, wp.ilon); + } + sb.append(" \n"); + } + private void createPlaceMark(StringBuilder sb, String name, int ilat, int ilon) { sb.append(" \n"); sb.append(" " + StringUtils.escapeXml10(name) + "\n"); From bffcae48ea36dc82424a08c82dc9759a943f3c39 Mon Sep 17 00:00:00 2001 From: Emux Date: Thu, 17 Apr 2025 14:10:42 +0300 Subject: [PATCH 06/37] Update Android app dependencies --- brouter-routing-app/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index d6a9bfc..558b95b 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -101,8 +101,8 @@ repositories { dependencies { implementation 'androidx.appcompat:appcompat:1.7.0' - implementation "androidx.constraintlayout:constraintlayout:2.1.4" - implementation 'androidx.work:work-runtime:2.9.0' + implementation "androidx.constraintlayout:constraintlayout:2.2.1" + implementation 'androidx.work:work-runtime:2.10.0' implementation 'com.google.android.material:material:1.12.0' implementation project(':brouter-mapaccess') @@ -115,7 +115,7 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' - androidTestImplementation 'androidx.work:work-testing:2.9.0' + androidTestImplementation 'androidx.work:work-testing:2.10.0' } gradle.projectsEvaluated { From c68c451151eff39ef4e72587a1b3efc9d16072b0 Mon Sep 17 00:00:00 2001 From: Emux Date: Thu, 17 Apr 2025 13:53:49 +0300 Subject: [PATCH 07/37] Update Gradle --- build.gradle | 6 +++--- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43504 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 +++++-- gradlew.bat | 2 ++ 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 946559c..21aaac8 100644 --- a/build.gradle +++ b/build.gradle @@ -6,13 +6,13 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.7.3' + classpath 'com.android.tools.build:gradle:8.9.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } -task clean(type: Delete) { - delete rootProject.buildDir +tasks.register('clean', Delete) { + delete rootProject.layout.buildDirectory } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..2c3521197d7c4586c843d1d3e9090525f1898cde 100644 GIT binary patch delta 8703 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 Ojj7^59?Sdh$^Qpp*~F>< delta 8662 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 09523c0..e2847c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4..f5feea6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 7101f8e..9b42019 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## From c23fa53ec372b336987478e43e5bc4491c062b98 Mon Sep 17 00:00:00 2001 From: Emux Date: Tue, 22 Apr 2025 09:55:45 +0300 Subject: [PATCH 08/37] Update Android Gradle plugin --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 21aaac8..c7a0bbf 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.9.1' + classpath 'com.android.tools.build:gradle:8.9.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From dbaab103770243b196794e621bc7ef3e0ac74109 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 9 May 2025 16:46:28 +0200 Subject: [PATCH 09/37] remove mismatched pts array --- .../src/main/java/btools/router/FormatGpx.java | 12 ++++++++---- .../src/main/java/btools/router/FormatJson.java | 16 ++++++++++------ .../src/main/java/btools/router/FormatKml.java | 15 +++++++++++++-- .../src/main/java/btools/router/OsmTrack.java | 1 - .../main/java/btools/router/RoutingEngine.java | 10 ++-------- .../java/btools/mapaccess/MatchedWaypoint.java | 1 + 6 files changed, 34 insertions(+), 21 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/FormatGpx.java b/brouter-core/src/main/java/btools/router/FormatGpx.java index 6f969d1..1d0b88c 100644 --- a/brouter-core/src/main/java/btools/router/FormatGpx.java +++ b/brouter-core/src/main/java/btools/router/FormatGpx.java @@ -212,10 +212,14 @@ public class FormatGpx extends Formatter { } } } - if (t.exportCorrectedWaypoints && t.correctedWaypoints != null) { - for (int i = 0; i <= t.correctedWaypoints.size() - 1; i++) { - OsmNodeNamed n = t.correctedWaypoints.get(i); - formatWaypointGpx(sb, n, "via_corr"); + if (t.exportCorrectedWaypoints) { + for (int i = 0; i <= t.matchedWaypoints.size() - 1; i++) { + MatchedWaypoint wt = t.matchedWaypoints.get(i); + if (wt.correctedpoint != null) { + OsmNodeNamed n = new OsmNodeNamed(wt.correctedpoint); + n.name = wt.name + "_corr"; + formatWaypointGpx(sb, n, "via_corr"); + } } } diff --git a/brouter-core/src/main/java/btools/router/FormatJson.java b/brouter-core/src/main/java/btools/router/FormatJson.java index c380fa4..d5ba17b 100644 --- a/brouter-core/src/main/java/btools/router/FormatJson.java +++ b/brouter-core/src/main/java/btools/router/FormatJson.java @@ -158,15 +158,19 @@ public class FormatJson extends Formatter { } if (t.exportCorrectedWaypoints) { if (t.exportWaypoints) sb.append(" ,\n"); - for (int i = 0; i <= t.correctedWaypoints.size() - 1; i++) { + boolean hasCorrPoints = false; + for (int i = 0; i <= t.matchedWaypoints.size() - 1; i++) { String type = "via_corr"; - OsmNodeNamed wp = t.correctedWaypoints.get(i); - addFeature(sb, type, wp.name, wp.ilat, wp.ilon, wp.getSElev()); - if (i < t.correctedWaypoints.size() - 1) { - sb.append(","); + MatchedWaypoint wp = t.matchedWaypoints.get(i); + if (wp.correctedpoint != null) { + if (hasCorrPoints) { + sb.append(","); + } + addFeature(sb, type, wp.name + "_corr", wp.correctedpoint.ilat, wp.correctedpoint.ilon, wp.correctedpoint.getSElev()); + sb.append(" \n"); + hasCorrPoints = true; } - sb.append(" \n"); } } } else { diff --git a/brouter-core/src/main/java/btools/router/FormatKml.java b/brouter-core/src/main/java/btools/router/FormatKml.java index 79d1e08..931689a 100644 --- a/brouter-core/src/main/java/btools/router/FormatKml.java +++ b/brouter-core/src/main/java/btools/router/FormatKml.java @@ -1,5 +1,6 @@ package btools.router; +import java.util.ArrayList; import java.util.List; import btools.mapaccess.MatchedWaypoint; @@ -63,8 +64,17 @@ public class FormatKml extends Formatter { createFolder(sb, "end", t.matchedWaypoints.subList(size - 1, size)); } if (t.exportCorrectedWaypoints) { - int size = t.correctedWaypoints.size(); - createViaFolder(sb, "via_cor", t.correctedWaypoints.subList(0, size)); + List list = new ArrayList<>(); + for (int i = 0; i < t.matchedWaypoints.size(); i++) { + MatchedWaypoint wp = t.matchedWaypoints.get(i); + if (wp.correctedpoint != null) { + OsmNodeNamed n = new OsmNodeNamed(wp.correctedpoint); + n.name = wp.name + "_corr"; + list.add(n); + } + } + int size = list.size(); + createViaFolder(sb, "via_corr", list.subList(0, size)); } } sb.append(" \n"); @@ -84,6 +94,7 @@ public class FormatKml extends Formatter { } private void createViaFolder(StringBuilder sb, String type, List waypoints) { + if (waypoints.isEmpty()) return; sb.append(" \n"); sb.append(" " + type + "\n"); for (int i = 0; i < waypoints.size(); i++) { diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index b56a7df..eeb3ce0 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -61,7 +61,6 @@ public final class OsmTrack { public String name = "unset"; protected List matchedWaypoints; - protected List correctedWaypoints; public boolean exportWaypoints = false; public boolean exportCorrectedWaypoints = false; diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index b86f4a0..cc86b00 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -41,7 +41,6 @@ public class RoutingEngine extends Thread { private boolean finished = false; protected List waypoints = null; - protected List correctedWaypoints = null; List extraWaypoints = null; protected List matchedWaypoints; private int linksProcessed = 0; @@ -1036,7 +1035,6 @@ public class RoutingEngine extends Thread { matchedWaypoints.get(matchedWaypoints.size() - 1).indexInTrack = totaltrack.nodes.size() - 1; totaltrack.matchedWaypoints = matchedWaypoints; - totaltrack.correctedWaypoints = correctedWaypoints; totaltrack.processVoiceHints(routingContext); totaltrack.prepareSpeedProfile(routingContext); @@ -1195,12 +1193,8 @@ public class RoutingEngine extends Thread { setNewVoiceHint(t, last, lastJunctions, newJunction, newTarget); - if (correctedWaypoints == null) correctedWaypoints = new ArrayList<>(); - OsmNodeNamed n = new OsmNodeNamed(); - n.ilon = newJunction.getILon(); - n.ilat = newJunction.getILat(); - n.name = startWp.name + "_corr"; - correctedWaypoints.add(n); + // fill to correctedpoint + startWp.correctedpoint = new OsmNode(newJunction.getILon(), newJunction.getILat()); return true; } diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/MatchedWaypoint.java b/brouter-mapaccess/src/main/java/btools/mapaccess/MatchedWaypoint.java index c9556d5..4c2a415 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/MatchedWaypoint.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/MatchedWaypoint.java @@ -16,6 +16,7 @@ public final class MatchedWaypoint { public OsmNode node2; public OsmNode crosspoint; public OsmNode waypoint; + public OsmNode correctedpoint; public String name; // waypoint name used in error messages public double radius; // distance in meter between waypoint and crosspoint public boolean direct; // from this point go direct to next = beeline routing From 3ca25eab04a36bc22aa2585bc6f33411e377cb59 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sun, 11 May 2025 17:00:24 +0200 Subject: [PATCH 10/37] added mismatched pts on roundabout --- .../src/main/java/btools/router/OsmTrack.java | 2 + .../java/btools/router/RoutingEngine.java | 380 ++++++++++++++---- 2 files changed, 308 insertions(+), 74 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index eeb3ce0..cece041 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -339,6 +339,7 @@ public final class OsmTrack { } float t0 = ourSize > 0 ? nodes.get(ourSize - 1).getTime() : 0; float e0 = ourSize > 0 ? nodes.get(ourSize - 1).getEnergy() : 0; + int c0 = ourSize > 0 ? nodes.get(ourSize - 1).cost : 0; for (i = 0; i < t.nodes.size(); i++) { OsmPathElement e = t.nodes.get(i); if (i == 0 && ourSize > 0 && nodes.get(ourSize - 1).getSElev() == Short.MIN_VALUE) @@ -346,6 +347,7 @@ public final class OsmTrack { if (i > 0 || ourSize == 0) { e.setTime(e.getTime() + t0); e.setEnergy(e.getEnergy() + e0); + e.cost = e.cost + c0; if (e.message != null){ if (!(e.message.lon == e.getILon() && e.message.lat == e.getILat())) { e.message.lon = e.getILon(); diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index cc86b00..42ae5a1 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -23,6 +23,7 @@ import btools.mapaccess.OsmLinkHolder; import btools.mapaccess.OsmNode; import btools.mapaccess.OsmNodePairSet; import btools.mapaccess.OsmPos; +import btools.util.CheapAngleMeter; import btools.util.CheapRuler; import btools.util.CompactLongMap; import btools.util.SortedHeap; @@ -1020,7 +1021,7 @@ public class RoutingEngine extends Thread { boolean changed = false; if (routingContext.correctMisplacedViaPoints && !matchedWaypoints.get(i).direct && !routingContext.allowSamewayback) { - changed = snappPathConnection(totaltrack, seg, routingContext.inverseRouting ? matchedWaypoints.get(i + 1) : matchedWaypoints.get(i)); + changed = snapPathConnection(totaltrack, seg, routingContext.inverseRouting ? matchedWaypoints.get(i + 1) : matchedWaypoints.get(i)); } if (wptIndex > 0) matchedWaypoints.get(wptIndex).indexInTrack = totaltrack.nodes.size() - 1; @@ -1043,12 +1044,198 @@ public class RoutingEngine extends Thread { if (routingContext.poipoints != null) totaltrack.pois = routingContext.poipoints; - totaltrack.matchedWaypoints = matchedWaypoints; + return totaltrack; } + OsmTrack getExtraSegment(OsmPathElement start, OsmPathElement end) { + + List wptlist = new ArrayList<>(); + MatchedWaypoint wpt1 = new MatchedWaypoint(); + wpt1.waypoint = new OsmNode(start.getILon(), start.getILat()); + wpt1.name = "wptx1"; + wpt1.crosspoint = new OsmNode(start.getILon(), start.getILat()); + wpt1.node1 = new OsmNode(start.getILon(), start.getILat()); + wpt1.node2 = new OsmNode(end.getILon(), end.getILat()); + wptlist.add(wpt1); + MatchedWaypoint wpt2 = new MatchedWaypoint(); + wpt2.waypoint = new OsmNode(end.getILon(), end.getILat()); + wpt2.name = "wptx2"; + wpt2.crosspoint = new OsmNode(end.getILon(), end.getILat()); + wpt2.node2 = new OsmNode(start.getILon(), start.getILat()); + wpt2.node1 = new OsmNode(end.getILon(), end.getILat()); + wptlist.add(wpt2); + + MatchedWaypoint mwp1 = wptlist.get(0); + MatchedWaypoint mwp2 = wptlist.get(1); + + OsmTrack mid = null; + + boolean corr = routingContext.correctMisplacedViaPoints; + routingContext.correctMisplacedViaPoints = false; + + guideTrack = new OsmTrack(); + guideTrack.addNode(start); + guideTrack.addNode(end); + + mid = findTrack("getinfo", mwp1, mwp2, null, null, false); + + guideTrack = null; + routingContext.correctMisplacedViaPoints = corr; + + return mid; + } + + private int snapRoundaboutConnection(OsmTrack tt, OsmTrack t, int indexStart, int indexEnd, int indexMeeting, MatchedWaypoint startWp) { + + int indexMeetingBack = (indexMeeting == -1 ? tt.nodes.size() - 1 : indexMeeting); + int indexMeetingFore = 0; + int indexStartBack = indexStart; + int indexStartFore = 0; + + OsmPathElement ptStart = tt.nodes.get(indexStartBack); + OsmPathElement ptMeeting = tt.nodes.get(indexMeetingBack); + OsmPathElement ptEnd = t.nodes.get(indexEnd); + + boolean bMeetingIsOnRoundabout = ptMeeting.message.isRoundabout(); + boolean bMeetsRoundaboutStart = false; + + int i; + for (i = 0; i < indexEnd; i++) { + OsmPathElement n = t.nodes.get(i); + if (n.positionEquals(ptStart)) { + indexStartFore = i; + bMeetsRoundaboutStart = true; + } + if (n.positionEquals(ptMeeting)) { + indexMeetingFore = i; + } + + } + + if (!bMeetsRoundaboutStart && bMeetingIsOnRoundabout) { + indexEnd = indexMeetingFore; + } + if (bMeetsRoundaboutStart && bMeetingIsOnRoundabout) { + indexEnd = indexStartFore; + } + + List removeList = new ArrayList<>(); + if (!bMeetsRoundaboutStart) { + indexStartBack = indexMeetingBack; + while (!tt.nodes.get(indexStartBack).message.isRoundabout()) { + indexStartBack--; + if (indexStartBack == 2) break; + } + } + + for (i = indexStartBack + 1; i < tt.nodes.size(); i++) { + OsmPathElement n = tt.nodes.get(i); + OsmTrack.OsmPathElementHolder detours = tt.getFromDetourMap(n.getIdFromPos()); + OsmTrack.OsmPathElementHolder h = detours; + while (h != null) { + h = h.nextHolder; + } + removeList.add(n); + } + + OsmPathElement ttend = null; + OsmPathElement ttend_next = null; + if (!bMeetingIsOnRoundabout && !bMeetsRoundaboutStart) { + ttend = tt.nodes.get(indexStartBack); + ttend_next = tt.nodes.get(indexStartBack + 1); + OsmTrack.OsmPathElementHolder ttend_detours = tt.getFromDetourMap(ttend.getIdFromPos()); + + tt.registerDetourForId(ttend.getIdFromPos(), null); + } + + for (OsmPathElement e : removeList) { + tt.nodes.remove(e); + } + removeList.clear(); + + + for (i = 0; i < indexEnd; i++) { + OsmPathElement n = t.nodes.get(i); + if (n.positionEquals(bMeetsRoundaboutStart ? ptStart : ptEnd)) break; + if (!bMeetingIsOnRoundabout && !bMeetsRoundaboutStart && n.message.isRoundabout()) break; + + OsmTrack.OsmPathElementHolder detours = t.getFromDetourMap(n.getIdFromPos()); + OsmTrack.OsmPathElementHolder h = detours; + while (h != null) { + h = h.nextHolder; + } + removeList.add(n); + } + + // time hold + float atime = 0; + float aenergy = 0; + int acost = 0; + if (i > 1) { + atime = t.nodes.get(i).getTime(); + aenergy = t.nodes.get(i).getEnergy(); + acost = t.nodes.get(i).cost; + } + + for (OsmPathElement e : removeList) { + t.nodes.remove(e); + } + removeList.clear(); + + if (atime > 0f) { + for (OsmPathElement e : t.nodes) { + e.setTime(e.getTime() - atime); + e.setEnergy(e.getEnergy() - aenergy); + e.cost = e.cost - acost; + } + } + + if (!bMeetingIsOnRoundabout && !bMeetsRoundaboutStart) { + + OsmPathElement tstart = t.nodes.get(0); + OsmPathElement tstart_next = null; + OsmTrack.OsmPathElementHolder tstart_detours = t.getFromDetourMap(tstart.getIdFromPos()); + OsmTrack.OsmPathElementHolder ttend_detours = tt.getFromDetourMap(ttend.getIdFromPos()); + + OsmTrack mid = getExtraSegment(ttend, ttend_detours.node); + OsmPathElement tt_end = tt.nodes.get(tt.nodes.size() - 1); + + int last_cost = tt_end.cost; + int tmp_cost = 0; + + if (mid != null) { + boolean start = false; + for (OsmPathElement e : mid.nodes) { + if (start) { + if (e.positionEquals(ttend_detours.node)) { + tmp_cost = e.cost; + break; + } + e.cost = last_cost + e.cost; + tt.nodes.add(e); + } + if (e.positionEquals(tt_end)) start = true; + } + + } + + ttend_detours.node.cost = last_cost + tmp_cost; + tt.nodes.add(ttend_detours.node); + t.nodes.add(0, ttend_detours.node); + + } + + tt.cost = tt.nodes.get(tt.nodes.size()-1).cost; + t.cost = t.nodes.get(t.nodes.size()-1).cost; + + startWp.correctedpoint = new OsmNode(ptStart.getILon(), ptStart.getILat()); + + return (t.nodes.size()); + } + // check for way back on way point - private boolean snappPathConnection(OsmTrack tt, OsmTrack t, MatchedWaypoint startWp) { + private boolean snapPathConnection(OsmTrack tt, OsmTrack t, MatchedWaypoint startWp) { if (!startWp.name.startsWith("via") && !startWp.name.startsWith("rt")) return false; @@ -1080,29 +1267,88 @@ public class RoutingEngine extends Thread { OsmPathElement newTarget = null; OsmPathElement tmpback = null; OsmPathElement tmpfore = null; + OsmPathElement tmpStart = null; int indexback = ourSize - 1; int indexfore = 0; int stop = (indexback - MAX_STEPS_CHECK > 1 ? indexback - MAX_STEPS_CHECK : 1); double wayDistance = 0; double nextDist = 0; + boolean bCheckRoundAbout = false; + boolean bBackRoundAbout = false; + boolean bForeRoundAbout = false; + int indexBackFound = 0; + int indexForeFound = 0; + int differentLanePoints = 0; + int indexMeeting = -1; + while (indexback >= 1 && indexback >= stop && indexfore < t.nodes.size()) { + tmpback = tt.nodes.get(indexback); + tmpfore = t.nodes.get(indexfore); + if (!bBackRoundAbout && tmpback.message != null && tmpback.message.isRoundabout()) { + bBackRoundAbout = true; + indexBackFound = indexfore; + } + if (!bForeRoundAbout && + tmpfore.message != null && tmpfore.message.isRoundabout() || + (tmpback.positionEquals(tmpfore) && tmpback.message.isRoundabout())) { + bForeRoundAbout = true; + indexForeFound = indexfore; + } + if (indexfore == 0) { + tmpStart = t.nodes.get(0); + } else { + double dirback = CheapAngleMeter.getDirection(tmpStart.getILon(), tmpStart.getILat(), tmpback.getILon(), tmpback.getILat()); + double dirfore = CheapAngleMeter.getDirection(tmpStart.getILon(), tmpStart.getILat(), tmpfore.getILon(), tmpfore.getILat()); + double dirdiff = CheapAngleMeter.getDifferenceFromDirection(dirback, dirfore); + // walking wrong direction + if (dirdiff > 60 && !bBackRoundAbout && !bForeRoundAbout) break; + } + // seems no roundabout, only on one end + if (bBackRoundAbout != bForeRoundAbout && indexfore - Math.abs(indexForeFound - indexBackFound) > 8) break; + if (!tmpback.positionEquals(tmpfore)) differentLanePoints++; + if (tmpback.positionEquals(tmpfore)) indexMeeting = indexback; + bCheckRoundAbout = bBackRoundAbout && bForeRoundAbout; + if (bCheckRoundAbout) break; + indexback--; + indexfore++; + } + //System.out.println("snap round result " + indexback + ": " + bBackRoundAbout + " - " + indexfore + "; " + bForeRoundAbout + " pts " + differentLanePoints); + if (bCheckRoundAbout) { + + tmpback = tt.nodes.get(--indexback); + while (tmpback.message != null && tmpback.message.isRoundabout()) { + tmpback = tt.nodes.get(--indexback); + } + + int ifore = ++indexfore; + OsmPathElement testfore = t.nodes.get(ifore); + while (ifore < t.nodes.size() && testfore.message != null && testfore.message.isRoundabout()) { + testfore = t.nodes.get(ifore); + ifore++; + } + + snapRoundaboutConnection(tt, t, indexback, --ifore, indexMeeting, startWp); + + // remove filled arrays + removeVoiceHintList.clear(); + removeBackList.clear(); + removeForeList.clear(); + return true; + } + indexback = ourSize - 1; + indexfore = 0; while (indexback >= 1 && indexback >= stop && indexfore < t.nodes.size()) { int junctions = 0; tmpback = tt.nodes.get(indexback); tmpfore = t.nodes.get(indexfore); if (tmpback.message != null && tmpback.message.isRoundabout()) { - removeBackList.clear(); - removeForeList.clear(); - removeVoiceHintList.clear(); - return false; + bCheckRoundAbout = true; } if (tmpfore.message != null && tmpfore.message.isRoundabout()) { - removeBackList.clear(); - removeForeList.clear(); - removeVoiceHintList.clear(); - return false; + bCheckRoundAbout = true; } - int dist = tmpback.calcDistance(tmpfore); - if (1 == 1) { + { + + int dist = tmpback.calcDistance(tmpfore); OsmTrack.OsmPathElementHolder detours = tt.getFromDetourMap(tmpback.getIdFromPos()); OsmTrack.OsmPathElementHolder h = detours; while (h != null) { @@ -1110,53 +1356,56 @@ public class RoutingEngine extends Thread { lastJunctions.put(h.node.getIdFromPos(), h); h = h.nextHolder; } - } - if (dist == 1 && indexfore > 0) { - if (indexfore == 1) { - removeBackList.add(tt.nodes.get(tt.nodes.size() - 1)); // last and first should be equal, so drop only on second also equal - removeForeList.add(t.nodes.get(0)); - removeBackList.add(tmpback); - removeForeList.add(tmpfore); - removeVoiceHintList.add(tt.nodes.size() - 1); - removeVoiceHintList.add(indexback); - } else { - removeBackList.add(tmpback); - removeForeList.add(tmpfore); - removeVoiceHintList.add(indexback); - } - nextDist = t.nodes.get(indexfore - 1).calcDistance(tmpfore); - wayDistance += nextDist; - } - if (dist > 1 || indexback == 1) { - if (removeBackList.size() != 0) { - // recover last - should be the cross point - removeBackList.remove(removeBackList.get(removeBackList.size() - 1)); - removeForeList.remove(removeForeList.get(removeForeList.size() - 1)); - break; - } else { + if (dist == 1 && indexfore > 0) { + if (indexfore == 1) { + removeBackList.add(tt.nodes.get(tt.nodes.size() - 1)); // last and first should be equal, so drop only on second also equal + removeForeList.add(t.nodes.get(0)); + removeBackList.add(tmpback); + removeForeList.add(tmpfore); + removeVoiceHintList.add(tt.nodes.size() - 1); + removeVoiceHintList.add(indexback); + } else { + removeBackList.add(tmpback); + removeForeList.add(tmpfore); + removeVoiceHintList.add(indexback); + } + nextDist = t.nodes.get(indexfore - 1).calcDistance(tmpfore); + wayDistance += nextDist; + + } + if (dist > 1 || indexback == 1) { + if (removeBackList.size() != 0) { + // recover last - should be the cross point + removeBackList.remove(removeBackList.get(removeBackList.size() - 1)); + removeForeList.remove(removeForeList.get(removeForeList.size() - 1)); + break; + } else { + return false; + } + } + indexback--; + indexfore++; + + if (routingContext.correctMisplacedViaPointsDistance > 0 && + wayDistance > routingContext.correctMisplacedViaPointsDistance) { + removeVoiceHintList.clear(); + removeBackList.clear(); + removeForeList.clear(); return false; } } - indexback--; - indexfore++; - - if (routingContext.correctMisplacedViaPointsDistance > 0 && - wayDistance > routingContext.correctMisplacedViaPointsDistance) { - removeVoiceHintList.clear(); - removeBackList.clear(); - removeForeList.clear(); - return false; - } } // time hold float atime = 0; float aenergy = 0; + int acost = 0; if (removeForeList.size() > 1) { - atime = t.nodes.get(removeForeList.size() - 2).getTime(); - aenergy = t.nodes.get(removeForeList.size() - 2).getEnergy(); + atime = t.nodes.get(indexfore -1).getTime(); + aenergy = t.nodes.get(indexfore -1).getEnergy(); + acost = t.nodes.get(indexfore -1).cost; } for (OsmPathElement e : removeBackList) { @@ -1176,6 +1425,7 @@ public class RoutingEngine extends Thread { for (OsmPathElement e : t.nodes) { e.setTime(e.getTime() - atime); e.setEnergy(e.getEnergy() - aenergy); + e.cost = e.cost - acost; } } @@ -1191,7 +1441,8 @@ public class RoutingEngine extends Thread { newJunction = t.nodes.get(0); newTarget = t.nodes.get(1); - setNewVoiceHint(t, last, lastJunctions, newJunction, newTarget); + tt.cost = tt.nodes.get(tt.nodes.size()-1).cost; + t.cost = t.nodes.get(t.nodes.size()-1).cost; // fill to correctedpoint startWp.correctedpoint = new OsmNode(newJunction.getILon(), newJunction.getILat()); @@ -1201,28 +1452,6 @@ public class RoutingEngine extends Thread { return false; } - private void setNewVoiceHint(OsmTrack t, OsmPathElement last, CompactLongMap lastJunctiona, OsmPathElement newJunction, OsmPathElement newTarget) { - - if (last == null || newJunction == null || newTarget == null) - return; - int lon0, - lat0, - lon1, - lat1, - lon2, - lat2; - lon0 = last.getILon(); - lat0 = last.getILat(); - lon1 = newJunction.getILon(); - lat1 = newJunction.getILat(); - lon2 = newTarget.getILon(); - lat2 = newTarget.getILat(); - // get a new angle - double angle = routingContext.anglemeter.calcAngle(lon0, lat0, lon1, lat1, lon2, lat2); - - newTarget.message.turnangle = (float) angle; - } - private void recalcTrack(OsmTrack t) { int totaldist = 0; int totaltime = 0; @@ -1566,7 +1795,7 @@ public class RoutingEngine extends Thread { OsmPath p = getStartPath(n1, n2, new OsmNodeNamed(mwp.crosspoint), endPos, sameSegmentSearch); // special case: start+end on same segment - if (p.cost >= 0 && sameSegmentSearch && endPos != null && endPos.radius < 1.5) { + if (p != null && p.cost >= 0 && sameSegmentSearch && endPos != null && endPos.radius < 1.5) { p.treedepth = 0; // hack: mark for the final-check } return p; @@ -1605,7 +1834,7 @@ public class RoutingEngine extends Thread { if (bestLink != null) { bestLink.addLinkHolder(bestPath, n1); } - bestPath.treedepth = 1; + if (bestPath != null) bestPath.treedepth = 1; return bestPath; } finally { @@ -1703,6 +1932,9 @@ public class RoutingEngine extends Thread { logInfo("firstMatchCost from initial match=" + firstMatchCost); } + if (startPath1 == null) return null; + if (startPath2 == null) return null; + synchronized (openSet) { openSet.clear(); addToOpenset(startPath1); From 4a414b0d1a07f478e33569eb82845045f1a5a131 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sun, 11 May 2025 17:00:46 +0200 Subject: [PATCH 11/37] added test roundabout --- .../java/btools/server/RouteServerTest.java | 65 ++++++++++++------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/brouter-server/src/test/java/btools/server/RouteServerTest.java b/brouter-server/src/test/java/btools/server/RouteServerTest.java index d4c971e..ad6776c 100644 --- a/brouter-server/src/test/java/btools/server/RouteServerTest.java +++ b/brouter-server/src/test/java/btools/server/RouteServerTest.java @@ -14,6 +14,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.ConnectException; import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -28,7 +30,7 @@ public class RouteServerTest { public static TemporaryFolder profileDir = new TemporaryFolder(); @BeforeClass - public static void setupServer() throws IOException, InterruptedException { + public static void setupServer() throws IOException, InterruptedException, URISyntaxException { File workingDir = new File(".").getCanonicalFile(); File segmentDir = new File(workingDir, "../brouter-map-creator/build/resources/test/tmp/segments"); File profileSourceDir = new File(workingDir, "../misc/profiles2"); @@ -41,7 +43,7 @@ public class RouteServerTest { try { RouteServer.main(new String[]{segmentDir.getAbsolutePath(), profileDir.getRoot().getAbsolutePath(), customProfileDir, port, "1"}); } catch (Exception e) { - e.printStackTrace(); + e.printStackTrace(System.out); } }; @@ -49,7 +51,7 @@ public class RouteServerTest { thread.start(); // Busy-wait for server startup - URL requestUrl = new URL(baseUrl); + URL requestUrl = new URI(baseUrl).toURL(); HttpURLConnection httpConnection = (HttpURLConnection) requestUrl.openConnection(); for (int i = 20; i >= 0; i--) { try { @@ -66,8 +68,8 @@ public class RouteServerTest { } @Test - public void defaultRouteTrekking() throws IOException { - URL requestUrl = new URL(baseUrl + "brouter?lonlats=8.723037,50.000491|8.712737,50.002899&nogos=&profile=trekking&alternativeidx=0&format=geojson"); + public void defaultRouteTrekking() throws IOException, URISyntaxException { + URL requestUrl = new URI(baseUrl + "brouter?lonlats=8.723037,50.000491%7C8.712737,50.002899&nogos=&profile=trekking&alternativeidx=0&format=geojson").toURL(); HttpURLConnection httpConnection = (HttpURLConnection) requestUrl.openConnection(); httpConnection.connect(); @@ -82,8 +84,8 @@ public class RouteServerTest { } @Test - public void overrideParameter() throws IOException { - URL requestUrl = new URL(baseUrl + "brouter?lonlats=8.723037,50.000491|8.712737,50.002899&nogos=&profile=trekking&alternativeidx=0&format=geojson&profile:avoid_unsafe=1"); + public void overrideParameter() throws IOException, URISyntaxException { + URL requestUrl = new URI(baseUrl + "brouter?lonlats=8.723037,50.000491%7C8.712737,50.002899&nogos=&profile=trekking&alternativeidx=0&format=geojson&profile:avoid_unsafe=1").toURL(); HttpURLConnection httpConnection = (HttpURLConnection) requestUrl.openConnection(); httpConnection.connect(); @@ -95,8 +97,8 @@ public class RouteServerTest { } @Test - public void voiceHints() throws IOException { - URL requestUrl = new URL(baseUrl + "brouter?lonlats=8.705796,50.003124|8.705859,50.0039599&nogos=&profile=trekking&alternativeidx=0&format=geojson&timode=2"); + public void voiceHints() throws IOException, URISyntaxException { + URL requestUrl = new URI(baseUrl + "brouter?lonlats=8.705796,50.003124%7C8.705859,50.0039599&nogos=&profile=trekking&alternativeidx=0&format=geojson&timode=2").toURL(); HttpURLConnection httpConnection = (HttpURLConnection) requestUrl.openConnection(); httpConnection.connect(); @@ -108,8 +110,8 @@ public class RouteServerTest { } @Test - public void directRoutingFirst() throws IOException { - URL requestUrl = new URL(baseUrl + "brouter?lonlats=8.718354,50.001514|8.718917,50.001361|8.716986,50.000105|8.718306,50.00145&nogos=&profile=trekking&alternativeidx=0&format=geojson&straight=0&timode=3"); + public void directRoutingFirst() throws IOException, URISyntaxException { + URL requestUrl = new URI(baseUrl + "brouter?lonlats=8.718354,50.001514%7C8.718917,50.001361%7C8.716986,50.000105%7C8.718306,50.00145&nogos=&profile=trekking&alternativeidx=0&format=geojson&straight=0&timode=3").toURL(); HttpURLConnection httpConnection = (HttpURLConnection) requestUrl.openConnection(); httpConnection.connect(); @@ -121,8 +123,8 @@ public class RouteServerTest { } @Test - public void directRoutingLast() throws IOException { - URL requestUrl = new URL(baseUrl + "brouter?lonlats=8.718306,50.00145|8.717464,50.000405|8.718917,50.001361|8.718354,50.001514&nogos=&profile=trekking&alternativeidx=0&format=geojson&straight=2&timode=3"); + public void directRoutingLast() throws IOException, URISyntaxException { + URL requestUrl = new URI(baseUrl + "brouter?lonlats=8.718306,50.00145%7C8.717464,50.000405%7C8.718917,50.001361%7C8.718354,50.001514&nogos=&profile=trekking&alternativeidx=0&format=geojson&straight=2&timode=3").toURL(); HttpURLConnection httpConnection = (HttpURLConnection) requestUrl.openConnection(); httpConnection.connect(); @@ -134,8 +136,8 @@ public class RouteServerTest { } @Test - public void directRoutingMiddle() throws IOException { - URL requestUrl = new URL(baseUrl + "brouter?lonlats=8.718539,50.006581|8.718198,50.006065,d|8.71785,50.006034|8.7169,50.004456&nogos=&profile=trekking&alternativeidx=0&format=geojson&timode=3"); + public void directRoutingMiddle() throws IOException, URISyntaxException { + URL requestUrl = new URI(baseUrl + "brouter?lonlats=8.718539,50.006581%7C8.718198,50.006065,d%7C8.71785,50.006034%7C8.7169,50.004456&nogos=&profile=trekking&alternativeidx=0&format=geojson&timode=3").toURL(); HttpURLConnection httpConnection = (HttpURLConnection) requestUrl.openConnection(); httpConnection.connect(); @@ -147,8 +149,8 @@ public class RouteServerTest { } @Test - public void misplacedPoints() throws IOException { - URL requestUrl = new URL(baseUrl + "brouter?lonlats=8.708678,49.999188|8.71145,49.999761|8.715801,50.00065&nogos=&profile=trekking&alternativeidx=0&format=geojson&correctMisplacedViaPoints=1&timode=3"); + public void misplacedPoints() throws IOException, URISyntaxException { + URL requestUrl = new URI(baseUrl + "brouter?lonlats=8.708678,49.999188%7C8.71145,49.999761%7C8.715801,50.00065&nogos=&profile=trekking&alternativeidx=0&format=geojson&correctMisplacedViaPoints=1&timode=3").toURL(); HttpURLConnection httpConnection = (HttpURLConnection) requestUrl.openConnection(); httpConnection.connect(); @@ -160,8 +162,21 @@ public class RouteServerTest { } @Test - public void uploadValidProfile() throws IOException { - URL requestUrl = new URL(baseUrl + "brouter/profile"); + public void misplacedPointsRoundabout() throws IOException, URISyntaxException { + URL requestUrl = new URI(baseUrl + "brouter?lonlats=8.699487,50.001257%7C8.701569,50.000092%7C8.704873,49.998898&nogos=&profile=trekking&alternativeidx=0&format=geojson&profile:correctMisplacedViaPoints=1&timode=3").toURL(); + HttpURLConnection httpConnection = (HttpURLConnection) requestUrl.openConnection(); + httpConnection.connect(); + + Assert.assertEquals(HttpURLConnection.HTTP_OK, httpConnection.getResponseCode()); + + InputStream inputStream = httpConnection.getInputStream(); + JSONObject geoJson = new JSONObject(new String(inputStream.readAllBytes(), StandardCharsets.UTF_8)); + Assert.assertEquals("482", geoJson.query("/features/0/properties/track-length")); + } + + @Test + public void uploadValidProfile() throws IOException, URISyntaxException { + URL requestUrl = new URI(baseUrl + "brouter/profile").toURL(); HttpURLConnection httpConnection = (HttpURLConnection) requestUrl.openConnection(); httpConnection.setRequestMethod("POST"); @@ -192,8 +207,8 @@ public class RouteServerTest { } @Test - public void uploadInvalidProfile() throws IOException { - URL requestUrl = new URL(baseUrl + "brouter/profile"); + public void uploadInvalidProfile() throws IOException, URISyntaxException { + URL requestUrl = new URI(baseUrl + "brouter/profile").toURL(); HttpURLConnection httpConnection = (HttpURLConnection) requestUrl.openConnection(); httpConnection.setRequestMethod("POST"); @@ -214,8 +229,8 @@ public class RouteServerTest { } @Test - public void robots() throws IOException { - URL requestUrl = new URL(baseUrl + "robots.txt"); + public void robots() throws IOException, URISyntaxException { + URL requestUrl = new URI(baseUrl + "robots.txt").toURL(); HttpURLConnection httpConnection = (HttpURLConnection) requestUrl.openConnection(); httpConnection.connect(); @@ -226,8 +241,8 @@ public class RouteServerTest { } @Test - public void invalidUrl() throws IOException { - URL requestUrl = new URL(baseUrl + "invalid"); + public void invalidUrl() throws IOException, URISyntaxException { + URL requestUrl = new URI(baseUrl + "invalid").toURL(); HttpURLConnection httpConnection = (HttpURLConnection) requestUrl.openConnection(); httpConnection.connect(); From b7151e779ef0966b7ef8ff56d37ad4bba71b5211 Mon Sep 17 00:00:00 2001 From: Emux Date: Fri, 16 May 2025 09:50:35 +0300 Subject: [PATCH 12/37] Update Android Gradle plugin --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c7a0bbf..90fe6d7 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.9.2' + classpath 'com.android.tools.build:gradle:8.9.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 466627f18b2214a92c1a21865449fc8689f7c8d7 Mon Sep 17 00:00:00 2001 From: Emux Date: Fri, 16 May 2025 09:52:13 +0300 Subject: [PATCH 13/37] Update Android app dependencies --- brouter-routing-app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index 558b95b..e19e45c 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -102,7 +102,7 @@ repositories { dependencies { implementation 'androidx.appcompat:appcompat:1.7.0' implementation "androidx.constraintlayout:constraintlayout:2.2.1" - implementation 'androidx.work:work-runtime:2.10.0' + implementation 'androidx.work:work-runtime:2.10.1' implementation 'com.google.android.material:material:1.12.0' implementation project(':brouter-mapaccess') @@ -115,7 +115,7 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' - androidTestImplementation 'androidx.work:work-testing:2.10.0' + androidTestImplementation 'androidx.work:work-testing:2.10.1' } gradle.projectsEvaluated { From 4c824531a234add0143d2f0780bd49d1da3b9dbb Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 27 May 2025 11:45:23 +0200 Subject: [PATCH 14/37] db scripts pushed to last state --- misc/scripts/mapcreation/brouter.sql | 1937 ++++++++++++++-------- misc/scripts/mapcreation/brouter_cfg.lua | 8 +- 2 files changed, 1217 insertions(+), 728 deletions(-) diff --git a/misc/scripts/mapcreation/brouter.sql b/misc/scripts/mapcreation/brouter.sql index 359f093..4e8a7a9 100644 --- a/misc/scripts/mapcreation/brouter.sql +++ b/misc/scripts/mapcreation/brouter.sql @@ -2,6 +2,845 @@ -- formatted by https://sqlformat.darold.net/ SET client_encoding TO UTF8; +-- prepare the lines table with a new index and a new column +SELECT + now(); + +ANALYZE; + +SELECT + now(); + +SELECT + osm_id, + highway, + maxspeed, + way, + waterway, + li.natural, + width, + oneway, + st_length (way) / st_length (ST_Transform (way, 4326)::geography) AS merca_coef INTO TABLE lines_bis +FROM + lines li; + +SELECT + now(); + +DROP TABLE lines; + +ALTER TABLE lines_bis RENAME TO lines; + +CREATE INDEX lines_osm_id_idx ON lines (osm_id) WITH (fillfactor = '100'); + +CREATE INDEX lines_way_idx ON public.lines USING gist (way) WITH (fillfactor = '100'); + +ANALYZE lines; + +-- generation of pseudo-tags +-- 1: noise +-- create a table with the segments producing noise (motorway, primary and secondary) and a noise_factor for each. +-- the noise_factor depends on the highway type, maxspeed and oneway +-- oneway is basically given on motorway segments, in some cases also on primary... +-- then 2 segments exist on the same route, so noise_factor per segment is lower!!!! +SELECT + now(); + +SELECT + osm_id::bigint, + highway, + maxspeed, + CASE WHEN maxspeed IS NULL + OR (NOT (maxspeed ~ '^\d+(\.\d+)?$')) + OR maxspeed::numeric > '105' THEN + -- maxspeed not defined OR not numeric / usable OR > 105 km/h + CASE WHEN highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link') THEN + 0.6 + WHEN highway IN ('primary', 'primary_link') THEN + CASE WHEN oneway IS NULL + OR oneway NOT IN ('yes', 'true', '1') THEN + 0.66 + ELSE + 0.45 + END + WHEN highway IN ('secondary') THEN + 0.33 + ELSE + 0 + END + WHEN maxspeed::numeric > '75' THEN + -- 75 < maxspeed <= 105 + CASE WHEN highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link') THEN + 0.55 + WHEN highway IN ('primary', 'primary_link') THEN + CASE WHEN oneway IS NULL + OR oneway NOT IN ('yes', 'true', '1') THEN + 0.66 + ELSE + 0.45 + END + WHEN highway IN ('secondary') THEN + 0.33 + ELSE + 0 + END + ELSE + -- maxspeed <= 75 + CASE WHEN highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link') THEN + 0.4 + WHEN highway IN ('primary', 'primary_link') THEN + CASE WHEN oneway IS NULL + OR oneway NOT IN ('yes', 'true', '1') THEN + 0.4 + ELSE + 0.3 + END + WHEN highway IN ('secondary') THEN + 0.2 + ELSE + 0 + END + END AS noise_factor, + way AS way, + ST_Buffer (way, 75 * merca_coef) AS way75, + merca_coef INTO TABLE noise_emittnew +FROM + lines li +WHERE + highway IS NOT NULL + AND highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link', 'secondary'); + +SELECT + now(); + +CREATE INDEX noise_emittnew_osm_id_idx ON noise_emittnew (osm_id) WITH (fillfactor = '100'); + +-- modify noise_factor by very small segments +SELECT + now(); + +UPDATE + noise_emittnew +SET + noise_factor = noise_factor * (st_length (way) / merca_coef) / 20 +WHERE (st_length (way) / merca_coef) < 20; + +SELECT + now(); + +ANALYZE noise_emittnew; + +SELECT + now(); + +-- create a tuple (highway + noise source) of the highways having noise (for perf tuning) +SELECT + dd.osm_id::bigint AS lines_osm_id, + dd.highway, + dd.merca_coef, + dd.way, + q.osm_id AS noise_osm_id INTO TABLE tuples_with_noise +FROM + lines dd + INNER JOIN noise_emittnew AS q ON ST_Intersects (dd.way, q.way75) +WHERE + dd.highway IS NOT NULL + AND dd.highway NOT IN ('proposed', 'construction') + AND (st_length (dd.way) / dd.merca_coef) < 40000; + +--group by dd.osm_id, dd.highway, dd.merca_coef, dd.way; +SELECT + now(); + +CREATE INDEX tuples_with_noise_osm_id_idx ON tuples_with_noise (lines_osm_id) WITH (fillfactor = '100'); + +SELECT + now(); + +ANALYZE tuples_with_noise; + +SELECT + lines_osm_id AS osm_id, + way, + merca_coef INTO TABLE lines_with_noise +FROM + tuples_with_noise +GROUP BY + lines_osm_id, + merca_coef, + way; + +SELECT + now(); + +CREATE INDEX lineswithnoise_osm_id_idx ON lines_with_noise (osm_id) WITH (fillfactor = '100'); + +ANALYZE lines_with_noise; + +-- calculate noise using "lines_with_noise" +-- split each segment with noise into 20 meter sections and calculate the noise per section +-- the average giving the noise for the segment +SELECT + now(); + +WITH lines_split AS ( + SELECT + osm_id, + merca_coef, + ST_LineSubstring (d.way, startfrac, LEAST (endfrac, 1)) AS way, + len / merca_coef AS lgt_seg_real + FROM ( + SELECT + osm_id, + merca_coef, + way, + st_length (way) len, + (20 * merca_coef) sublen + FROM + lines_with_noise) AS d + CROSS JOIN LATERAL ( + SELECT + i, + (sublen * i) / len AS startfrac, + (sublen * (i + 1)) / len AS endfrac + FROM + generate_series(0, floor(len / sublen)::integer) AS t (i) + -- skip last i if line length is exact multiple of sublen + WHERE (sublen * i) / len <> 1.0) AS d2 +) +SELECT + m.osm_id::bigint losmid, + m.lgt_seg_real, + st_distance (m.way, t.way) / m.merca_coef AS dist, + -- the line below delivers the same result as above !!!!!!! but need much more time (* 7 !) + -- st_distance(st_transform(m.way, 4326)::geography, st_transform(t.way, 4326)::geography) as distgeog, + t.noise_factor INTO TABLE noise_tmp2newz +FROM + lines_split AS m + INNER JOIN tuples_with_noise AS q ON m.osm_id = q.lines_osm_id + INNER JOIN noise_emittnew t ON t.osm_id = q.noise_osm_id +WHERE + st_distance (m.way, t.way) / m.merca_coef < 75; + +SELECT + now(); + +ANALYZE noise_tmp2newz; + +-- group +SELECT + now(); + +-- calculate an indicator per section (1 / d*d here) and reduce the results by taking the average on the osm_segment +SELECT + losmid, + m.lgt_seg_real, + sum(noise_factor / ((dist + 15) / 15)) / ((m.lgt_seg_real / 20)::integer + 1) AS sum_noise_factor INTO noise_tmp2new +FROM + noise_tmp2newz m +GROUP BY + m.losmid, + m.lgt_seg_real; + +SELECT + now(); + +DROP TABLE noise_tmp2newz; + +DROP TABLE tuples_with_noise; + +DROP TABLE noise_emittnew; + +ANALYZE noise_tmp2new; + +SELECT + now(); + +-- add noise from Airports... +-- polygons of the international airports +SELECT + name, + st_buffer (way, (700 * st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 3857)) / st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 4326)::geography))) AS way INTO TABLE poly_airportnew +FROM + polygons +WHERE + aeroway = 'aerodrome' + AND aerodrome = 'international'; + +SELECT + now(); + +ANALYZE poly_airportnew; + +SELECT + m.osm_id::bigint losmid, + -- st_area(st_intersection(m.way, q.way)) / (st_area(m.way) * 1.5) + -- 1 / 1.5 + (700 - (st_distance (m.way, q.way) / m.merca_coef)) / (700 * 1.5) AS dist_factor INTO TABLE noise_airportnew +FROM + lines AS m + INNER JOIN poly_airportnew AS q ON ST_intersects (m.way, q.way) +WHERE + m.highway IS NOT NULL +ORDER BY + dist_factor DESC; + +SELECT + now(); + +ANALYZE noise_airportnew; + +SELECT + losmid, + sum(noise_factor) AS sum_noise_factor INTO TABLE noise_tmp3new +FROM (( + SELECT + losmid, + sum_noise_factor AS noise_factor + FROM + noise_tmp2new AS nois1) + UNION ( + SELECT + losmid, + dist_factor AS noise_factor + FROM + noise_airportnew AS nois2)) AS nois_sum +GROUP BY + losmid; + +SELECT + now(); + +ANALYZE noise_tmp3new; + +-- create the noise classes +SELECT + now(); + +SELECT + losmid, + CASE WHEN y.sum_noise_factor < 0.06 THEN + '1' + WHEN y.sum_noise_factor < 0.13 THEN + '2' + WHEN y.sum_noise_factor < 0.26 THEN + '3' + WHEN y.sum_noise_factor < 0.45 THEN + '4' + WHEN y.sum_noise_factor < 0.85 THEN + '5' + ELSE + '6' + END AS noise_class INTO TABLE noise_tags +FROM + noise_tmp3new y +WHERE + y.sum_noise_factor > 0.01 +ORDER BY + noise_class; + +SELECT + now(); + +ANALYZE noise_tags; + +SELECT + count(*) +FROM + noise_tags; + +SELECT + noise_class, + count(*) +FROM + noise_tags +GROUP BY + noise_class +ORDER BY + noise_class; + +------------------------------------------------------------------------- +-- 2: create tags for river +-- create a table with the segments and polygons with "river" (or water!) +SELECT + now(); + +WITH river_from_polygons AS ( + SELECT + osm_id::bigint, + way, + ST_Buffer (way, 110 * st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 3857)) / st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 4326)::geography)) AS way2 + FROM + polygons q + WHERE + q.natural IN ('water', 'bay', 'beach', 'wetland') + AND (q.water IS NULL + OR q.water NOT IN ('wastewater')) + AND st_area (ST_Transform (q.way, 4326)::geography) BETWEEN 1000 AND 5000000000 +), +river_from_lines AS ( + SELECT + osm_id::bigint, + way, + ST_Buffer (way, 80 * merca_coef) AS way2 + FROM + lines q + WHERE + q.waterway IN ('river', 'canal', 'fairway') + OR q.natural IN ('coastline') + ORDER BY + way +), +river_coastline AS ( + SELECT + osm_id::bigint, + way, + ST_Buffer (ST_ExteriorRing (way), 100 * st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 3857)) / st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 4326)::geography)) AS way2 + FROM + polygons p + WHERE + st_area (ST_Transform (p.way, 4326)::geography) > 1000 + AND (p.natural IN ('coastline') + AND st_length (way) < 100000) + ORDER BY + way +) +SELECT + * INTO river_proxy +FROM ( + SELECT + * + FROM + river_from_polygons part1 + UNION + SELECT + * + FROM + river_from_lines part2 + UNION + SELECT + * + FROM + river_coastline part3) AS sumriver; + +SELECT + now(); + +CREATE INDEX river_proxy_osm_id_idx ON river_proxy (osm_id) WITH (fillfactor = '100'); + +ANALYZE river_proxy; + +SELECT + now(); + +-- create a tuple (highway + noise source) of the highways in river proxymity (for perf tuning) +SELECT + dd.osm_id::bigint AS lines_osm_id, + dd.merca_coef, + dd.way, + q.osm_id AS river_osm_id INTO TABLE tuples_with_river +FROM + lines dd + INNER JOIN river_proxy AS q ON ST_Intersects (dd.way, q.way2) +WHERE + dd.highway IS NOT NULL + AND dd.highway NOT IN ('proposed', 'construction') + AND (st_length (dd.way) / dd.merca_coef) < 40000; + +SELECT + now(); + +CREATE INDEX tuples_with_river_osm_id_idx ON tuples_with_river (lines_osm_id) WITH (fillfactor = '100'); + +SELECT + now(); + +ANALYZE tuples_with_river; + +SELECT + now(); + +-- create a table of highways with river... +SELECT + lines_osm_id AS osm_id, + way, + merca_coef INTO TABLE lines_with_river +FROM + tuples_with_river +GROUP BY + lines_osm_id, + merca_coef, + way; + +SELECT + now(); + +CREATE INDEX lineswithriver_osm_id_idx ON lines_with_river (osm_id) WITH (fillfactor = '100'); + +ANALYZE lines_with_river; + +SELECT + now(); + +-- calculate river factor using "lines_with_river" +-- split each segment with river into 20 meter sections and calculate the river per section +-- the average giving the river_factor for the segment +WITH lines_split AS ( + SELECT + osm_id, + merca_coef, + ST_LineSubstring (d.way, startfrac, LEAST (endfrac, 1)) AS way, + len / merca_coef AS lgt_seg_real + FROM ( + SELECT + osm_id, + merca_coef, + way, + st_length (way) len, + (20 * merca_coef) sublen + FROM + lines_with_river) AS d + CROSS JOIN LATERAL ( + SELECT + i, + (sublen * i) / len AS startfrac, + (sublen * (i + 1)) / len AS endfrac + FROM + generate_series(0, floor(len / sublen)::integer) AS t (i) + -- skip last i if line length is exact multiple of sublen + WHERE (sublen * i) / len <> 1.0) AS d2 +) +SELECT + m.osm_id::bigint losmid, + m.lgt_seg_real, + st_distance (m.way, t.way) / m.merca_coef AS dist INTO TABLE river_tmp2newz +FROM + lines_split AS m + INNER JOIN tuples_with_river AS q ON m.osm_id = q.lines_osm_id + INNER JOIN river_proxy t ON t.osm_id = q.river_osm_id +WHERE + st_distance (m.way, t.way) / m.merca_coef < 165; + +SELECT + now(); + +ANALYZE river_tmp2newz; + +SELECT + now(); + +SELECT + losmid, + m.lgt_seg_real, + sum(1 / ((dist + 50) / 50)) / ((m.lgt_seg_real / 20)::integer + 1) AS sum_river_factor INTO river_tmp2new +FROM + river_tmp2newz m +GROUP BY + m.losmid, + m.lgt_seg_real; + +SELECT + now(); + +DROP TABLE river_tmp2newz; + +DROP TABLE tuples_with_river; + +DROP TABLE river_proxy; + +ANALYZE river_tmp2new; + +SELECT + now(); + +SELECT + losmid, + CASE WHEN y.sum_river_factor < 0.22 THEN + '1' + WHEN y.sum_river_factor < 0.35 THEN + '2' + WHEN y.sum_river_factor < 0.5 THEN + '3' + WHEN y.sum_river_factor < 0.75 THEN + '4' + WHEN y.sum_river_factor < 0.98 THEN + '5' + ELSE + '6' + END AS river_class INTO TABLE river_tags +FROM + river_tmp2new y +WHERE + y.sum_river_factor > 0.03; + +SELECT + now(); + +SELECT + count(*) +FROM + river_tags; + +SELECT + river_class, + count(*) +FROM + river_tags +GROUP BY + river_class +ORDER BY + river_class; + +SELECT + now(); + +------------------------------------------------------------- +-- create pseudo-tags for forest +-- +-- create first a table of the polygons with forest +SELECT + now(); + +SELECT + osm_id::bigint, + leisure, + landuse, + p.natural, + p.water, + way, + ST_Buffer (way, 32.15 * st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 3857)) / st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 4326)::geography)) AS way32 INTO TABLE osm_poly_forest +FROM + polygons p +WHERE + st_area (ST_Transform (p.way, 4326)::geography) > 1500 + AND ((p.landuse IN ('forest', 'allotments', 'flowerbed', 'orchard', 'vineyard', 'recreation_ground', 'village_green')) + OR p.leisure IN ('garden', 'park', 'nature_reserve')) + AND st_area (ST_Transform (p.way, 4326)::geography) BETWEEN 5000 AND 5000000000 +ORDER BY + way; + +SELECT + now(); + +CREATE INDEX osm_poly_forest_osm_id_idx ON osm_poly_forest (osm_id) WITH (fillfactor = '100'); + +SELECT + now(); + +ANALYZE osm_poly_forest; + +-- create a table of the lines within forests (green_factor is nomally 1, but 5 is better to calculate class 6 ) +SELECT + now(); + +SELECT + m.osm_id::bigint, + m.highway, + 6 AS green_factor INTO TABLE lines_within_forest +FROM + lines AS m + INNER JOIN osm_poly_forest q ON ST_Within (m.way, q.way) +WHERE + m.highway IS NOT NULL +GROUP BY + m.osm_id, + m.highway, + m.way; + +CREATE INDEX lines_within_forest_osm_id_idx ON lines_within_forest (osm_id) WITH (fillfactor = '100'); + +SELECT + now(); + +ANALYZE lines_within_forest; + +-- create a tuple table (lines+polygons) of the lines near but not within forests +SELECT + m.osm_id::bigint AS lines_osm_id, + m.highway, + m.merca_coef, + m.way AS lines_way, + q.osm_id AS forest_osm_id INTO TABLE tuples_limit_forest +FROM + lines AS m + INNER JOIN osm_poly_forest AS q ON ST_Intersects (m.way, q.way32) +WHERE + m.highway IS NOT NULL + AND m.highway NOT IN ('proposed', 'construction') + AND (st_length (m.way) / m.merca_coef) < 40000 + AND m.osm_id::bigint NOT IN ( + SELECT + osm_id + FROM + lines_within_forest); + +SELECT + now(); + +CREATE INDEX tuples_lines_osm_id_idx ON tuples_limit_forest (lines_osm_id) WITH (fillfactor = '100'); + +SELECT + now(); + +ANALYZE tuples_limit_forest; + +SELECT + now(); + +-- create a table with only the lines near but not within forests +SELECT + m.lines_osm_id osm_id, + m.highway, + m.merca_coef, + m.lines_way AS way INTO TABLE lines_limit_forest +FROM + tuples_limit_forest AS m +GROUP BY + m.lines_osm_id, + m.highway, + m.merca_coef, + m.lines_way; + +SELECT + now(); + +CREATE INDEX lines_limit_forest_osm_id_idx ON lines_limit_forest (osm_id) WITH (fillfactor = '100'); + +SELECT + now(); + +ANALYZE lines_limit_forest; + +-- calculate the forest factor (or green_factor) for the lines at forest limit.. +-- spilt the line into 20 meter sections +-- calculate the distance section-forest +SELECT + now(); + +WITH lines_split AS ( + SELECT + osm_id, + highway, + merca_coef, + ST_LineSubstring (d.way, startfrac, LEAST (endfrac, 1)) AS way, + len / merca_coef AS lgt_seg_real + FROM ( + SELECT + osm_id, + highway, + merca_coef, + way, + st_length (way) len, + (20 * merca_coef) sublen + FROM + lines_limit_forest) AS d + CROSS JOIN LATERAL ( + SELECT + i, + (sublen * i) / len AS startfrac, + (sublen * (i + 1)) / len AS endfrac + FROM + generate_series(0, floor(len / sublen)::integer) AS t (i) + -- skip last i if line length is exact multiple of sublen + WHERE (sublen * i) / len <> 1.0) AS d2 +) +SELECT + m.osm_id, + lgt_seg_real, + st_distance (m.way, t.way) / m.merca_coef AS dist INTO TABLE forest_tmp2newz +FROM + lines_split AS m + INNER JOIN tuples_limit_forest AS q ON m.osm_id = q.lines_osm_id + INNER JOIN osm_poly_forest t ON t.osm_id = q.forest_osm_id +WHERE + st_distance (m.way, t.way) / m.merca_coef < 65; + +SELECT + now(); + +ANALYZE forest_tmp2newz; + +SELECT + now(); + +SELECT + m.osm_id, + m.lgt_seg_real, + sum(1 / ((dist + 25) / 25)) / ((m.lgt_seg_real / 20)::integer + 1) AS green_factor INTO forest_tmp2new +FROM + forest_tmp2newz m +GROUP BY + m.osm_id, + m.lgt_seg_real; + +SELECT + now(); + +ANALYZE forest_tmp2new; + +DROP TABLE forest_tmp2newz; + +DROP TABLE osm_poly_forest; + +DROP TABLE tuples_limit_forest; + +SELECT + now(); + +-- merge lines_within_forest with lines_limit_forest +WITH forest_tmp3new AS ( + SELECT + * + FROM ( + SELECT + osm_id, + green_factor + FROM + forest_tmp2new AS part1 + UNION + SELECT + osm_id, + green_factor + FROM + lines_within_forest) AS part2 +) +SELECT + y.osm_id losmid, + CASE WHEN y.green_factor < 0.32 THEN + '1' + WHEN y.green_factor < 0.5 THEN + '2' + WHEN y.green_factor < 0.7 THEN + '3' + WHEN y.green_factor < 0.92 THEN + '4' + WHEN y.green_factor < 5 THEN + '5' + ELSE + '6' + END AS forest_class INTO TABLE forest_tags +FROM + forest_tmp3new y +WHERE + y.green_factor > 0.1; + +ANALYZE forest_tags; + +SELECT + count(*) +FROM + forest_tags; + +SELECT + forest_class, + count(*) +FROM + forest_tags +GROUP BY + forest_class +ORDER BY + forest_class; + SELECT now(); @@ -21,133 +860,10 @@ $$ LANGUAGE plpgsql SECURITY INVOKER; --- create new tables for tuning --- -SELECT - osm_id::bigint, - highway, - waterway, - li.natural, - width, - maxspeed, - CASE WHEN maxspeed IS NULL THEN - 0 - --when not isnumeric(maxspeed) then 0 - WHEN NOT (maxspeed ~ '^\d+(\.\d+)?$') THEN - 0 - WHEN maxspeed::numeric > '105' THEN - 1 - WHEN maxspeed::numeric > '75' THEN - 2 - ELSE - 3 - END AS maxspeed_class - -- "buffer radius" was initially created with 50 meters at a latitude of 50 degrees... ==> ST_Buffer(way,50) - -- but, using geometry "projection", to get same results by a calculation of the planet (latitude between -80, +85) this value should be adapted to the latitude of the highways... -, - -- - ST_Buffer (way, 32.15 * st_length (ST_Transform (way, 3857)) / st_length (ST_Transform (way, 4326)::geography)) AS way INTO TABLE osm_line_buf_50 -FROM - lines li -WHERE - highway IS NOT NULL - OR waterway IN ('river', 'canal', 'fairway') - OR (li.natural = 'coastline' - AND st_length (way) < 100000); - SELECT now(); --- modify "way" by large waterways !!" (example Rhein ==> width = 400 ...) enlarge a bit the "50 meter" buffer -UPDATE - osm_line_buf_50 -SET - way = st_buffer (way, (width::numeric / 10)) -WHERE - waterway = 'river' - AND width IS NOT NULL - AND (width ~ '^[0-9\.]+$') - AND width::numeric > 15 - AND width::numeric < 2500; - -SELECT - osm_id::bigint, - leisure, - landuse, - p.natural, - p.water, - ST_Buffer (way, 32.15 * st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 3857)) / st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 4326)::geography)) AS way INTO TABLE osm_poly_buf_50 -FROM - polygons p -WHERE - -- do not consider small surfaces - st_area (p.way) > 1000 - AND (p.natural IN ('water', 'bay', 'beach', 'wetland') - OR p.landuse IN ('forest', 'allotments', 'flowerbed', 'orchard', 'vineyard', 'recreation_ground', 'village_green') - OR p.leisure IN ('garden', 'park', 'nature_reserve')); - --- by forest no buffer ! -SELECT - osm_id::bigint, - leisure, - landuse, - p.natural, - p.water, - way INTO TABLE osm_poly_no_buf -FROM - polygons p -WHERE - -- do not consider small surfaces - st_area (p.way) > 1000 - AND (p.natural IN ('water', 'bay', 'beach', 'costline', 'wetland') - OR p.landuse IN ('forest', 'allotments', 'flowerbed', 'orchard', 'vineyard', 'recreation_ground', 'village_green') - OR p.leisure IN ('garden', 'park', 'nature_reserve')); - -SELECT - osm_id::bigint, - leisure, - landuse, - p.natural, - p.water, - ST_Buffer (way, 45 * st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 3857)) / st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 4326)::geography)) AS way INTO TABLE osm_poly_buf_120 -FROM - osm_poly_buf_50 p; - --- for coastline special case -SELECT - osm_id::bigint, - leisure, - landuse, - p.natural, - p.water, - ST_Buffer (ST_ExteriorRing (way), 64 * st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 3857)) / st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 4326)::geography)) AS way INTO TABLE osm_poly_coastline_buf_100 -FROM - polygons p -WHERE - -- do not consider small surfaces - st_area (p.way) > 1000 - AND (p.natural IN ('coastline') - AND st_length (way) < 100000); - ---CREATE INDEX osm_poly_coastline_ind ON public.osm_poly_coastline_buf_100 USING gist (way) WITH (fillfactor='100'); -SELECT - * INTO TABLE osm_line_water -FROM - osm_line_buf_50 q -WHERE - q.waterway IN ('river', 'canal', 'fairway') - OR q.natural IN ('coastline'); - -CREATE INDEX osm_line_water_ind ON public.osm_line_water USING gist (way) WITH (fillfactor = '100'); - -SELECT - now(); - --- create indexes -CREATE INDEX osm_line_buf_50_idx ON public.osm_line_buf_50 USING gist (way) WITH (fillfactor = '100'); - -ANALYZE; - +-- create tables for traffic SELECT osm_id, highway, @@ -166,6 +882,168 @@ CREATE INDEX primsecter15k_idx1 ON public.primsecter15k USING gist (way) WITH (f CREATE INDEX primsecter15k_idx0 ON public.primsecter15k USING gist (way0) WITH (fillfactor = '100'); +ANALYZE primsecter15k; + +SELECT + now(); + +-- consistency check on population, first evaluate and documents it +WITH cities_x AS ( + SELECT + a.name, + a.name_en, + place, + osm_id, + replace(a.population, '.', '')::bigint population, + a.way + FROM + cities a + WHERE + a.population IS NOT NULL + AND isnumeric (a.population) + AND a.place IN ('village', 'town', 'city', 'municipality')) +SELECT + name, + name_en, + place, + population, + osm_id +FROM + cities_x a +WHERE (place = 'village' + AND a.population > 90000) + OR (place = 'town' + AND a.population > 1500000) + OR (place = 'city' + AND a.population > 40000000) +ORDER BY + place, + Population DESC; + +WITH cities_relx AS ( + SELECT + a.name, + a.name_en, + place, + osm_id, + replace(a.population, '.', '')::bigint population, + a.way + FROM + cities_rel a + WHERE + a.population IS NOT NULL + AND isnumeric (a.population) + AND a.place IN ('village', 'town', 'city', 'municipality')) +SELECT + name, + name_en, + place, + population, + osm_id +FROM + cities_relx a +WHERE (place = 'village' + AND a.population > 90000) + OR (place = 'town' + AND a.population > 1500000) + OR (place = 'city' + AND a.population > 40000000) + OR (place IS NULL + AND a.population > 40000000) +ORDER BY + place, + Population DESC; + +-- now store the inconstencies +WITH cities_x AS ( + SELECT + a.name, + a.name_en, + place, + osm_id, + replace(a.population, '.', '')::bigint population, + a.way + FROM + cities a + WHERE + a.population IS NOT NULL + AND isnumeric (a.population) + AND a.place IN ('village', 'town', 'city', 'municipality')) +SELECT + name, + name_en, + place, + population, + osm_id INTO TABLE cities_incon +FROM + cities_x a +WHERE (place = 'village' + AND a.population > 90000) + OR (place = 'town' + AND a.population > 1500000) + OR (place = 'city' + AND a.population > 40000000) +ORDER BY + place, + Population DESC; + +WITH cities_relx AS ( + SELECT + a.name, + a.name_en, + place, + osm_id, + replace(a.population, '.', '')::bigint population, + a.way + FROM + cities_rel a + WHERE + a.population IS NOT NULL + AND isnumeric (a.population) + AND a.place IN ('village', 'town', 'city', 'municipality')) +SELECT + name, + name_en, + place, + population, + osm_id INTO TABLE cities_rel_incon +FROM + cities_relx a +WHERE (place = 'village' + AND a.population > 90000) + OR (place = 'town' + AND a.population > 1500000) + OR (place = 'city' + AND a.population > 40000000) + OR (place IS NULL + AND a.population > 40000000) +ORDER BY + place, + Population DESC; + +-- and eliminate the inconsistencies +UPDATE + cities +SET + population = 0 +WHERE + osm_id IN ( + SELECT + osm_id + FROM + cities_incon); + +UPDATE + cities_rel +SET + population = 0 +WHERE + osm_id IN ( + SELECT + osm_id + FROM + cities_rel_incon); + SELECT now(); @@ -173,18 +1051,25 @@ SELECT -- clean the cities table (when population is null or population is not numeric or unusable) SELECT a.name, + a.name_en, replace(a.population, '.', '')::bigint population, - a.way INTO cities_ok + a.way INTO TABLE cities_ok FROM cities a WHERE a.population IS NOT NULL AND isnumeric (a.population) - AND a.place IN ('town', 'city', 'municipality'); + AND a.place IN ('village', 'town', 'city', 'municipality'); + +ANALYZE cities_ok; + +SELECT + now(); -- clean the cities_rel table (when population is not numeric or unusable) SELECT a.name AS name, + a.name_en AS name_en, a.place AS place, a.admin_level, CASE WHEN a.population IS NOT NULL @@ -193,20 +1078,26 @@ SELECT ELSE NULL END AS population, - a.way INTO cities_rel_ok + a.way INTO TABLE cities_rel_ok FROM cities_rel a WHERE - boundary = 'administrative'; + boundary IN ('administrative', 'ceremonial'); CREATE INDEX cities_ok_idx ON public.cities_ok USING gist (way) WITH (fillfactor = '100'); CREATE INDEX cities_rel_ok_idx ON public.cities_rel_ok USING gist (way) WITH (fillfactor = '100'); --- select town + population + way starting with cities_ok ... (to catch special cases as ex. "Berlin" which is tagged with "admin_level=4") +SELECT + now(); + +ANALYZE cities_rel_ok; + +-- select town + population + way starting with cities_ok .... (to catch specials cases as ex. "Berlin" which is tagged with "admin_level=4") -- SELECT a.name AS name, + a.name_en AS name_en, st_x (a.way), st_y (a.way), a.population, @@ -295,7 +1186,7 @@ LIMIT 1) AND a.name = b.name AND st_intersects (a.way, b.way)) LIMIT 1) --- Berlin admin_level=4! +-- Berlin admin_level=4! , but Beijing/Shangai administrative-regions have the same name and area>20000 km*2 !!! WHEN ( SELECT way @@ -324,7 +1215,7 @@ LIMIT 1) cities_rel_ok b WHERE (st_area (b.way) / 1000000 < 10000 AND b.admin_level IS NULL - AND b.place IN ('city', 'town') + AND b.place IN ('town', 'city', 'village', 'municipality') AND a.name = b.name AND st_intersects (a.way, b.way)) LIMIT 1) IS NOT NULL THEN @@ -335,7 +1226,7 @@ LIMIT 1) cities_rel_ok b WHERE (st_area (b.way) / 1000000 < 10000 AND b.admin_level IS NULL - AND b.place IN ('city', 'town') + AND b.place IN ('town', 'city', 'village', 'municipality') AND a.name = b.name AND st_intersects (a.way, b.way)) LIMIT 1) @@ -347,7 +1238,7 @@ LIMIT 1) cities_rel_ok b WHERE (st_area (b.way) / 1000000 < 10000 AND b.admin_level = '2' - AND b.place IN ('city', 'town') + AND b.place IN ('town', 'city', 'village', 'municipality') AND a.name = b.name AND st_intersects (a.way, b.way)) LIMIT 1) IS NOT NULL THEN @@ -358,7 +1249,7 @@ LIMIT 1) cities_rel_ok b WHERE (st_area (b.way) / 1000000 < 10000 AND b.admin_level = '2' - AND b.place IN ('city', 'town') + AND b.place IN ('town', 'city', 'village', 'municipality') AND a.name = b.name AND st_intersects (a.way, b.way)) LIMIT 1) @@ -376,9 +1267,17 @@ FROM ORDER BY name; --- select town + population + way starting with cities_rel_ok ... +CREATE INDEX cities_intermed3_idx ON public. cities_intermed3 USING gist (way) WITH (fillfactor = '100'); + +SELECT + now(); + +ANALYZE cities_intermed3; + +-- select town + population + way starting with cities_rel_ok .... SELECT a.name AS name, + a.name_en AS name_en, st_area (a.way) st_area, CASE WHEN a.population IS NOT NULL THEN a.population @@ -398,43 +1297,52 @@ FROM cities_rel_ok a WHERE a.admin_level = '8' + AND (a.place IS NULL + OR a.place IN ('town', 'city', 'village', 'municipality')) ORDER BY a.name; --- merge SELECT - name, - max(population) AS population, - way, - max(way0) AS way0, - st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 3857)) / st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 4326)::geography) AS merca_coef INTO cities_intermed5 -FROM (( - SELECT - name, - population, - way, - way0 - FROM - cities_intermed3) - UNION ( - SELECT - name, - population, - way, - way0 - FROM - cities_intermed4)) a -WHERE - population IS NOT NULL - -- and population > 20000 -GROUP BY - name, - way -ORDER BY - population; + now(); +-- merge +WITH intermed5 AS ( + SELECT + name, + max(name_en) AS name_en, + max(population) AS population, + way, + max(way0) AS way0, + st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 3857)) / st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 4326)::geography) AS merca_coef + FROM (( + SELECT + name, + name_en, + population, + way, + way0 + FROM + cities_intermed3) + UNION ( + SELECT + name, + name_en, + population, + way, + way0 + FROM + cities_intermed4)) a + WHERE + population IS NOT NULL + GROUP BY + name, + way + ORDER BY + population +) SELECT name, + name_en, population, way, CASE WHEN way0 IS NULL THEN @@ -443,565 +1351,16 @@ SELECT way0::geometry END AS way0, merca_coef INTO TABLE cities_all -FROM - cities_intermed5; - -SELECT - now(); - --- create tags for noise --- create raw data for noise coming from cars --- when several highways-segments are producing noise, aggregate the noises using the "ST_Union" of the segments! --- (better as using "sum" or "max" that do not deliver good factors) -SELECT - * INTO TABLE osm_line_noise -FROM - osm_line_buf_50 q -WHERE - q.highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link', 'secondary'); - -CREATE INDEX osm_line_noise_ind ON public.osm_line_noise USING gist (way) WITH (fillfactor = '100'); - -SELECT - now(); - -SELECT - m.osm_id losmid, - m.highway lhighway, - q.highway AS qhighway, - q.maxspeed_class, - st_area (st_intersection (m.way, ST_Union (q.way))) / st_area (m.way) AS noise_factor INTO TABLE noise_part0 -FROM - osm_line_buf_50 AS m - INNER JOIN osm_line_noise AS q ON ST_Intersects (m.way, q.way) -WHERE - m.highway IS NOT NULL - AND q.highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link') - AND q.maxspeed_class < 1.1 -GROUP BY - losmid, - lhighway, - m.way, - q.highway, - q.maxspeed_class -ORDER BY - noise_factor DESC; - -SELECT - now(); - -SELECT - m.osm_id losmid, - m.highway lhighway, - q.highway AS qhighway, - q.maxspeed_class, - st_area (st_intersection (m.way, ST_Union (q.way))) / (1.5 * st_area (m.way)) AS noise_factor INTO TABLE noise_part1 -FROM - osm_line_buf_50 AS m - INNER JOIN osm_line_noise AS q ON ST_Intersects (m.way, q.way) -WHERE - m.highway IS NOT NULL - AND q.highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link') - AND q.maxspeed_class >= 1.1 -GROUP BY - losmid, - lhighway, - m.way, - q.highway, - q.maxspeed_class -ORDER BY - noise_factor DESC; - -SELECT - now(); - -SELECT - m.osm_id losmid, - m.highway lhighway, - q.highway AS qhighway, - q.maxspeed_class, - st_area (st_intersection (m.way, ST_Union (q.way))) / (2 * st_area (m.way)) AS noise_factor INTO TABLE noise_part2 -FROM - osm_line_buf_50 AS m - INNER JOIN osm_line_noise AS q ON ST_Intersects (m.way, q.way) -WHERE - m.highway IS NOT NULL - AND q.highway IN ('primary', 'primary_link') - AND q.maxspeed_class < 2.1 -GROUP BY - losmid, - lhighway, - m.way, - q.highway, - q.maxspeed_class -ORDER BY - noise_factor DESC; - -SELECT - now(); - -SELECT - m.osm_id losmid, - m.highway lhighway, - q.highway AS qhighway, - q.maxspeed_class, - st_area (st_intersection (m.way, ST_Union (q.way))) / (3 * st_area (m.way)) AS noise_factor INTO TABLE noise_part3 -FROM - osm_line_buf_50 AS m - INNER JOIN osm_line_noise AS q ON ST_Intersects (m.way, q.way) -WHERE - m.highway IS NOT NULL - AND q.highway IN ('primary', 'primary_link') - AND q.maxspeed_class >= 2.1 -GROUP BY - losmid, - lhighway, - m.way, - q.highway, - q.maxspeed_class -ORDER BY - noise_factor DESC; - -SELECT - now(); - -SELECT - m.osm_id losmid, - m.highway lhighway, - q.highway AS qhighway, - q.maxspeed_class, - st_area (st_intersection (m.way, ST_Union (q.way))) / (3 * st_area (m.way)) AS noise_factor INTO TABLE noise_part4 -FROM - osm_line_buf_50 AS m - INNER JOIN osm_line_noise AS q ON ST_Intersects (m.way, q.way) -WHERE - m.highway IS NOT NULL - AND q.highway IN ('secondary') - AND q.maxspeed_class < 2.1 -GROUP BY - losmid, - lhighway, - m.way, - q.highway, - q.maxspeed_class -ORDER BY - noise_factor DESC; - -SELECT - now(); - -SELECT - m.osm_id losmid, - m.highway lhighway, - q.highway AS qhighway, - q.maxspeed_class, - st_area (st_intersection (m.way, ST_Union (q.way))) / (5 * st_area (m.way)) AS noise_factor INTO TABLE noise_part5 -FROM - osm_line_buf_50 AS m - INNER JOIN osm_line_noise AS q ON ST_Intersects (m.way, q.way) -WHERE - m.highway IS NOT NULL - AND q.highway IN ('secondary') - AND q.maxspeed_class >= 2.1 -GROUP BY - losmid, - lhighway, - m.way, - q.highway, - q.maxspeed_class -ORDER BY - noise_factor DESC; - -SELECT - now(); - --- MERGE -SELECT - losmid, - sum(noise_factor) AS sum_noise_factor INTO TABLE noise_tmp2 FROM ( SELECT - losmid, - noise_factor + * FROM - noise_part0 - UNION - SELECT - losmid, - noise_factor - FROM - noise_part1 - UNION - SELECT - losmid, - noise_factor - FROM - noise_part2 - UNION - SELECT - losmid, - noise_factor - FROM - noise_part3 - UNION - SELECT - losmid, - noise_factor - FROM - noise_part4 - UNION - SELECT - losmid, - noise_factor - FROM - noise_part5) AS abcd -GROUP BY - losmid -ORDER BY - sum_noise_factor DESC; + intermed5 a) b; SELECT now(); --- noise coming from airports -SELECT - name, - st_buffer (way, (643 * st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 3857)) / st_length (ST_Transform (st_makeline (st_startpoint (way), st_centroid (way)), 4326)::geography))) AS way INTO TABLE poly_airport -FROM - polygons -WHERE - aeroway = 'aerodrome' - AND aerodrome = 'international'; - -SELECT - m.osm_id losmid, - st_area (st_intersection (m.way, q.way)) / (st_area (m.way) * 1.5) AS dist_factor INTO TABLE noise_airport -FROM - osm_line_buf_50 AS m - INNER JOIN poly_airport AS q ON ST_intersects (m.way, q.way) -WHERE - m.highway IS NOT NULL - --GROUP BY losmid, m.way -ORDER BY - dist_factor DESC; - --- add car & airport noises -SELECT - losmid, - sum(noise_factor) AS sum_noise_factor INTO TABLE noise_tmp3 -FROM (( - SELECT - losmid, - sum_noise_factor AS noise_factor - FROM - noise_tmp2 AS nois1) - UNION ( - SELECT - losmid, - dist_factor AS noise_factor - FROM - noise_airport AS nois2)) AS nois_sum -GROUP BY - losmid; - --- create the noise classes -SELECT - losmid, - CASE WHEN y.sum_noise_factor < 0.1 THEN - '1' - WHEN y.sum_noise_factor < 0.25 THEN - '2' - WHEN y.sum_noise_factor < 0.4 THEN - '3' - WHEN y.sum_noise_factor < 0.55 THEN - '4' - WHEN y.sum_noise_factor < 0.8 THEN - '5' - ELSE - '6' - END AS noise_class INTO TABLE noise_tags -FROM - noise_tmp3 y -WHERE - y.sum_noise_factor > 0.01; - -SELECT - count(*) -FROM - noise_tags; - -SELECT - noise_class, - count(*) -FROM - noise_tags -GROUP BY - noise_class -ORDER BY - noise_class; - -DROP TABLE noise_tmp2; - -SELECT - now(); - --- create tags for river -SELECT - xid, - sum(water_river_see) AS river_see INTO TABLE river_tmp -FROM ( - SELECT - m.osm_id AS xid, - st_area (st_intersection (m.way, ST_Union (q.way))) / st_area (m.way) AS water_river_see - FROM - osm_line_buf_50 AS m - INNER JOIN osm_poly_buf_120 AS q ON ST_Intersects (m.way, q.way) - WHERE - m.highway IS NOT NULL - -- and st_area(q.way) > 90746 !!! filter on very small surfaces was set above !!!!!!!!! - AND q.natural IN ('water', 'bay', 'beach', 'wetland') - AND (q.water IS NULL - OR q.water NOT IN ('wastewater')) - AND (st_area (ST_Transform (q.way, 4326)::geography) / 1000000) < 5000 - GROUP BY - m.osm_id, - m.way - UNION - SELECT - m.osm_id AS xid, - st_area (st_intersection (m.way, ST_Union (q.way))) / st_area (m.way) AS water_river_see - FROM - osm_line_buf_50 AS m - INNER JOIN osm_poly_coastline_buf_100 AS q ON ST_Intersects (m.way, q.way) - WHERE - m.highway IS NOT NULL - -- and st_area(q.way) > 90746 !!! filter on very small surfaces was set above !!!!!!!!! - GROUP BY - m.osm_id, - m.way - UNION - SELECT - m.osm_id AS xid, - st_area (st_intersection (m.way, ST_Union (q.way))) / st_area (m.way) AS water_river_see - FROM - osm_line_buf_50 AS m - INNER JOIN osm_line_water AS q ON ST_Intersects (m.way, q.way) - WHERE - m.highway IS NOT NULL - AND (st_area (ST_Transform (q.way, 4326)::geography) / 1000000) < 5000 - GROUP BY - m.osm_id, - m.way) AS abcd -GROUP BY - xid -ORDER BY - river_see DESC; - -SELECT - y.xid losmid, - CASE WHEN y.river_see < 0.17 THEN - '1' - WHEN y.river_see < 0.35 THEN - '2' - WHEN y.river_see < 0.57 THEN - '3' - WHEN y.river_see < 0.80 THEN - '4' - WHEN y.river_see < 0.95 THEN - '5' - ELSE - '6' - END AS river_class INTO TABLE river_tags -FROM - river_tmp y -WHERE - y.river_see > 0.05; - -SELECT - count(*) -FROM - river_tags; - -SELECT - river_class, - count(*) -FROM - river_tags -GROUP BY - river_class -ORDER BY - river_class; - -SELECT - now(); - --- create tags for forest -SELECT - m.osm_id, - m.highway, - st_area (st_intersection (m.way, ST_Union (q.way))) / st_area (m.way) AS green_factor INTO TABLE forest_tmp -FROM - osm_line_buf_50 AS m - INNER JOIN osm_poly_no_buf AS q ON ST_Intersects (m.way, q.way) -WHERE - m.highway IS NOT NULL - AND ((q.landuse IN ('forest', 'allotments', 'flowerbed', 'orchard', 'vineyard', 'recreation_ground', 'village_green')) - OR q.leisure IN ('garden', 'park', 'nature_reserve')) - AND (st_area (ST_Transform (q.way, 4326)::geography) / 1000000) < 5000 -GROUP BY - m.osm_id, - m.highway, - m.way -ORDER BY - green_factor DESC; - --- -SELECT - y.osm_id losmid, - CASE WHEN y.green_factor < 0.1 THEN - NULL - WHEN y.green_factor < 0.2 THEN - '1' - WHEN y.green_factor < 0.4 THEN - '2' - WHEN y.green_factor < 0.6 THEN - '3' - WHEN y.green_factor < 0.8 THEN - '4' - WHEN y.green_factor < 0.98 THEN - '5' - ELSE - '6' - END AS forest_class INTO TABLE forest_tags -FROM - forest_tmp y -WHERE - y.green_factor > 0.1; - -SELECT - count(*) -FROM - forest_tags; - -SELECT - forest_class, - count(*) -FROM - forest_tags -GROUP BY - forest_class -ORDER BY - forest_class; - -SELECT - now(); - --- create "town" tags --- get the highways within the town -SELECT - m.osm_id losmid, - m.highway lhighway, - CASE WHEN q.population::decimal > '2000000' THEN - 1 - WHEN q.population::decimal > '1000000' THEN - 0.8 - WHEN q.population::decimal > '400000' THEN - 0.6 - WHEN q.population::decimal > '150000' THEN - 0.4 - WHEN q.population::decimal > '80000' THEN - 0.2 - ELSE - 0.1 - END AS town_factor INTO TABLE town_tmp -FROM - osm_line_buf_50 AS m - --INNER JOIN cities_all AS q ON ST_Intersects(m.way, q.way) - INNER JOIN cities_all AS q ON ST_Within (m.way, q.way) -WHERE - m.highway IS NOT NULL - AND q.population > '50000' -ORDER BY - town_factor DESC; - --- -SELECT - losmid, - CASE WHEN y.town_factor = 0.1 THEN - '1' - WHEN y.town_factor = 0.2 THEN - '2' - WHEN y.town_factor = 0.4 THEN - '3' - WHEN y.town_factor = 0.6 THEN - '4' - WHEN y.town_factor = 0.8 THEN - '5' - ELSE - '6' - END AS town_class INTO TABLE town_tags -FROM ( - SELECT - losmid, - max(town_factor) AS town_factor - FROM - town_tmp y - GROUP BY - losmid) y; - -SELECT - count(*) -FROM - town_tags; - -SELECT - town_class, - count(*) -FROM - town_tags -GROUP BY - town_class -ORDER BY - town_class; - --- --- subtract the ways from town with a green tag (because administrative surface are sometimes too large) --- -DELETE FROM town_tags -WHERE losmid IN ( - SELECT - losmid - FROM - forest_tags - WHERE - forest_class NOT IN ('1')); - -DELETE FROM town_tags -WHERE losmid IN ( - SELECT - losmid - FROM - river_tags - WHERE - river_class NOT IN ('1')); - -SELECT - count(*) -FROM - town_tags; - -SELECT - town_class, - count(*) -FROM - town_tags -GROUP BY - town_class -ORDER BY - town_class; - -SELECT - now(); +ANALYZE cities_all; ------------------------------------------- -- create tags for TRAFFIC @@ -1035,7 +1394,6 @@ FROM INNER JOIN cities_all AS q ON ST_DWithin (m.way0, q.way0, ((3215 * q.merca_coef) + ((64300 * q.merca_coef) * q.population / (q.population + 10000)))) WHERE m.highway IS NOT NULL - --and m.highway in ('primary','primary_link','secondary', 'secondary_link', 'tertiary') AND q.population > 200 GROUP BY m.osm_id, @@ -1047,8 +1405,10 @@ ORDER BY SELECT now(); +ANALYZE traffic_tmp; + -- prepare some special tables --- the intersections motorway_link with primary/secondary/tertiary deliver the motorway accesses... +-- the intersections motorway_link with primary/secondary/tertiary deliver the motorway acccesses.... SELECT * INTO TABLE lines_link FROM @@ -1086,6 +1446,8 @@ CREATE INDEX motorway_access_idx3 ON public.motorway_access USING gist (way3) WI SELECT now(); +ANALYZE motorway_access; + -- find out all the primary/secondary/tertiary within 1000 m and 2000 m from a motorway access SELECT now(); @@ -1109,7 +1471,7 @@ SELECT SELECT m.osm_id losmid, - sum(st_length (q.way) / (6430 * merca_coef)) motorway_factor INTO TABLE motorway_access_2000 + sum(st_length (q.way) / (6430 * q.merca_coef)) motorway_factor INTO TABLE motorway_access_2000 FROM lines AS m INNER JOIN motorway_access AS q ON ST_Intersects (m.way, q.way3) @@ -1124,6 +1486,8 @@ ORDER BY SELECT now(); +ANALYZE motorway_access_2000; + -- -- special regions: mountain_range with "peaks" ==> few highways ==> higher traffic !!! -- calculate the "peak_density" @@ -1139,6 +1503,7 @@ FROM INNER JOIN peak AS q ON ST_Intersects (m.way2, q.way) WHERE (q.ele ~ '^[0-9\.]+$') AND q.ele::decimal > 400 + -- where (q.ele not ~ '^\d+(\.\d+)?$') and q.ele :: decimal > 400 GROUP BY m.osm_id, m.way @@ -1164,17 +1529,21 @@ FROM WHERE (landuse IN ('industrial', 'retail')) OR (aeroway = 'aerodrome' AND aerodrome = 'international') - --where landuse in ('industrial', 'retail') - --where landuse in ('industrial') AND (plant_method IS NULL OR plant_method NOT IN ('photovoltaic')) AND (plant_source IS NULL OR plant_source NOT IN ('solar', 'wind')); +SELECT + now(); + +ANALYZE poly_industri; + SELECT name, way, ST_Centroid (way) way0, + ST_Buffer (way, 12860 * merca_coef) AS way2, st_area (way) * power(50 / (32.15 * merca_coef), 2) areaReal, merca_coef INTO industri FROM @@ -1197,7 +1566,7 @@ SELECT END AS industrial_factor INTO industri_tmp FROM primsecter15k AS m - INNER JOIN industri AS q ON ST_dwithin (m.way0, q.way0, (12860 * q.merca_coef)) + INNER JOIN industri AS q ON ST_intersects (m.way0, q.way2) GROUP BY m.osm_id, m.highway, @@ -1363,7 +1732,7 @@ GROUP BY SELECT now(); --- Do not apply the positive effect of "motorway density" in proximity of motorway accesses!!!! +-- Do not apply the positiv effect of "motorway density" in proximity of motorway accesses!!!! UPDATE except_all SET @@ -1375,11 +1744,11 @@ WHERE FROM motorway_access_2000); --- quite direct at motorway accesses set a negative effect !!!! +-- quite direct at motorway accesses set a negativ effect !!!! UPDATE except_all SET - motorway_factor = - 15 + motorway_factor = -15 WHERE losmid IN ( SELECT @@ -1427,6 +1796,120 @@ GROUP BY ORDER BY traffic_class; +-- create town tags................. +-- create "town" tags +-- get the highways within the town +SELECT + m.osm_id losmid, + m.highway lhighway, + CASE WHEN q.population::decimal > '2000000' THEN + 1 + WHEN q.population::decimal > '1000000' THEN + 0.8 + WHEN q.population::decimal > '400000' THEN + 0.6 + WHEN q.population::decimal > '150000' THEN + 0.4 + WHEN q.population::decimal > '80000' THEN + 0.2 + ELSE + 0.1 + END AS town_factor INTO TABLE town_tmp +FROM + lines AS m + INNER JOIN cities_all AS q ON ST_Within (m.way, q.way) +WHERE + m.highway IS NOT NULL + AND q.population > '50000' +ORDER BY + town_factor DESC; + +SELECT + now(); + +ANALYSE town_tmp; + +-- +SELECT + losmid::bigint, + CASE WHEN y.town_factor = 0.1 THEN + '1' + WHEN y.town_factor = 0.2 THEN + '2' + WHEN y.town_factor = 0.4 THEN + '3' + WHEN y.town_factor = 0.6 THEN + '4' + WHEN y.town_factor = 0.8 THEN + '5' + ELSE + '6' + END AS town_class INTO TABLE town_tags +FROM ( + SELECT + losmid, + max(town_factor) AS town_factor + FROM + town_tmp y + GROUP BY + losmid) y; + +ANALYSE town_tags; + +SELECT + count(*) +FROM + town_tags; + +SELECT + town_class, + count(*) +FROM + town_tags +GROUP BY + town_class +ORDER BY + town_class; + +-- +-- substract the ways from town with a green tag (because administrative surface are some times too large) +-- +DELETE FROM town_tags +WHERE losmid IN ( + SELECT + losmid + FROM + forest_tags + WHERE + forest_class NOT IN ('1')); + +DELETE FROM town_tags +WHERE losmid IN ( + SELECT + losmid + FROM + river_tags + WHERE + river_class NOT IN ('1')); + +SELECT + count(*) +FROM + town_tags; + +SELECT + town_class, + count(*) +FROM + town_tags +GROUP BY + town_class +ORDER BY + town_class; + +SELECT + now(); + -- -- put all tags together in 1 table (1 "direct" access per way in mapcreator) -- @@ -1452,7 +1935,7 @@ ORDER BY CREATE INDEX all_tags_ind ON all_tags (losmid, noise_class, river_class, forest_class, town_class, traffic_class) WITH (fillfactor = '100'); -ANALYSE; +ANALYSE all_tags; SELECT now(); diff --git a/misc/scripts/mapcreation/brouter_cfg.lua b/misc/scripts/mapcreation/brouter_cfg.lua index 07604f6..6998a69 100644 --- a/misc/scripts/mapcreation/brouter_cfg.lua +++ b/misc/scripts/mapcreation/brouter_cfg.lua @@ -18,6 +18,7 @@ tables.lines = osm2pgsql.define_way_table('lines', { { column = 'waterway', type = 'text' }, { column = 'natural', type = 'text' }, { column = 'width', type = 'text' }, + { column = 'oneway', type = 'text' }, { column = 'way', type = 'linestring', projection = srid, not_null = true }, }) @@ -43,6 +44,7 @@ tables.polygons = osm2pgsql.define_area_table('polygons', { tables.cities = osm2pgsql.define_node_table('cities', { { column = 'name', type = 'text' }, + { column = 'name_en', type = 'text' }, { column = 'place', type = 'text' }, { column = 'admin_level', type = 'text' }, { column = 'osm_id', type = 'text' }, @@ -56,6 +58,7 @@ tables.cities_rel = osm2pgsql.define_relation_table('cities_rel', { { column = 'admin_level', type = 'text' }, { column = 'boundary', type = 'text' }, { column = 'name', type = 'text' }, + { column = 'name_en', type = 'text' }, { column = 'place', type = 'text' }, { column = 'osm_id', type = 'text' }, { column = 'population', type = 'text' }, @@ -114,10 +117,11 @@ end function osm2pgsql.process_node(object) - if (object.tags.place == 'city' or object.tags.place == 'town' or object.tags.place == 'municipality') and has_area_tags(object.tags) then +if (object.tags.place == 'city' or object.tags.place == 'town' or object.tags.place == 'village' or object.tags.place == 'municipality') and has_area_tags(object.tags) then tables.cities:insert({ osm_id = object.id, name = object.tags.name, + name_en = object.tags['name:en'], place = object.tags.place, admin_level = object.tags.admin_level, population = object.tags.population, @@ -166,6 +170,7 @@ function osm2pgsql.process_way(object) natural = object.tags.natural, width = object.tags.width, maxspeed = object.tags.maxspeed, + oneway = object.tags.oneway, way = object:as_linestring() }) end @@ -202,6 +207,7 @@ function osm2pgsql.process_relation(object) boundary = object.tags.boundary, admin_level = object.tags.admin_level, name = object.tags.name, + name_en = object.tags['name:en'], place = object.tags.place, population = object.tags.population, osm_id = object.id, From f340def65625c69bbaf5c4249398ac72bdfb918e Mon Sep 17 00:00:00 2001 From: Emux Date: Thu, 5 Jun 2025 11:16:20 +0300 Subject: [PATCH 15/37] Update Android Gradle plugin --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 90fe6d7..6fd211e 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.9.3' + classpath 'com.android.tools.build:gradle:8.10.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From f3bd0032b7f06433dfe64315c2218c15ffb34f58 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 7 Jun 2025 15:52:27 +0200 Subject: [PATCH 16/37] wpt distance check reworked --- .../src/main/java/btools/mapaccess/WaypointMatcherImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/WaypointMatcherImpl.java b/brouter-mapaccess/src/main/java/btools/mapaccess/WaypointMatcherImpl.java index 6096c79..b2769cd 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/WaypointMatcherImpl.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/WaypointMatcherImpl.java @@ -116,7 +116,7 @@ public final class WaypointMatcherImpl implements WaypointMatcher { double r22 = x2 * x2 + y2 * y2; double radius = Math.abs(r12 < r22 ? y1 * dx - x1 * dy : y2 * dx - x2 * dy) / d; - if (radius < mwp.radius) { + if (radius <= mwp.radius) { double s1 = x1 * dx + y1 * dy; double s2 = x2 * dx + y2 * dy; From 96f0b0b54f4f0621956ff09f3e265b2361fc7026 Mon Sep 17 00:00:00 2001 From: Emux Date: Wed, 11 Jun 2025 13:46:47 +0300 Subject: [PATCH 17/37] Android 16 --- brouter-routing-app/build.gradle | 4 ++-- brouter-util/src/main/java/btools/util/StackSampler.java | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index e19e45c..179d4f6 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -8,7 +8,7 @@ plugins { } android { - compileSdk 35 + compileSdk 36 base { archivesName = "BRouterApp." + project.version @@ -100,7 +100,7 @@ repositories { } dependencies { - implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'androidx.appcompat:appcompat:1.7.1' implementation "androidx.constraintlayout:constraintlayout:2.2.1" implementation 'androidx.work:work-runtime:2.10.1' implementation 'com.google.android.material:material:1.12.0' diff --git a/brouter-util/src/main/java/btools/util/StackSampler.java b/brouter-util/src/main/java/btools/util/StackSampler.java index 753fec4..169a291 100644 --- a/brouter-util/src/main/java/btools/util/StackSampler.java +++ b/brouter-util/src/main/java/btools/util/StackSampler.java @@ -1,5 +1,7 @@ package btools.util; +import android.os.Build; + import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; @@ -12,7 +14,7 @@ import java.util.Map; import java.util.Random; public class StackSampler extends Thread { - private DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS", new Locale("en", "US")); + private DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS", new Locale.Builder().setLanguage("en").setRegion("US").build()); private BufferedWriter bw; private Random rand = new Random(); @@ -47,6 +49,7 @@ public class StackSampler extends Thread { } } + @SuppressWarnings("deprecation") public void dumpThreads() { try { int wait1 = rand.nextInt(interval); @@ -65,7 +68,8 @@ public class StackSampler extends Thread { continue; } - sb.append(" (ID=").append(t.getId()).append(" \"").append(t.getName()).append("\" ").append(t.getState()).append("\n"); + final long threadId = Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA ? t.threadId() : t.getId(); + sb.append(" (ID=").append(threadId).append(" \"").append(t.getName()).append("\" ").append(t.getState()).append("\n"); for (StackTraceElement line : stack) { sb.append(" ").append(line.toString()).append("\n"); } From 8d563e81ae1f9bd31073244828220ec0579d485d Mon Sep 17 00:00:00 2001 From: Emux Date: Wed, 11 Jun 2025 14:08:16 +0300 Subject: [PATCH 18/37] Android 16 revert Thread.getId improvement --- brouter-util/src/main/java/btools/util/StackSampler.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/brouter-util/src/main/java/btools/util/StackSampler.java b/brouter-util/src/main/java/btools/util/StackSampler.java index 169a291..53c5db6 100644 --- a/brouter-util/src/main/java/btools/util/StackSampler.java +++ b/brouter-util/src/main/java/btools/util/StackSampler.java @@ -1,7 +1,5 @@ package btools.util; -import android.os.Build; - import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; @@ -49,7 +47,6 @@ public class StackSampler extends Thread { } } - @SuppressWarnings("deprecation") public void dumpThreads() { try { int wait1 = rand.nextInt(interval); @@ -68,8 +65,7 @@ public class StackSampler extends Thread { continue; } - final long threadId = Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA ? t.threadId() : t.getId(); - sb.append(" (ID=").append(threadId).append(" \"").append(t.getName()).append("\" ").append(t.getState()).append("\n"); + sb.append(" (ID=").append(t.getId()).append(" \"").append(t.getName()).append("\" ").append(t.getState()).append("\n"); for (StackTraceElement line : stack) { sb.append(" ").append(line.toString()).append("\n"); } From 83f78e833d265c2de32a8b27ddf48e9021c63d27 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 17 Jun 2025 13:02:10 +0200 Subject: [PATCH 19/37] shorter error messages --- .../main/java/btools/expressions/BExpressionContext.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java index 630af03..e94677e 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -782,7 +782,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { public void parseFile(File file, String readOnlyContext, Map keyValues) { if (!file.exists()) { - throw new IllegalArgumentException("profile " + file + " does not exist"); + throw new IllegalArgumentException("profile " + file.getName() + " does not exist"); } try { if (readOnlyContext != null) { @@ -813,12 +813,12 @@ public abstract class BExpressionContext implements IByteArrayUnifier { variableData[i] = readOnlyData[i]; } } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("ParseException " + file + " at line " + linenr + ": " + e.getMessage()); + throw new IllegalArgumentException("ParseException " + file.getName() + " at line " + linenr + ": " + e.getMessage()); } catch (Exception e) { throw new RuntimeException(e); } if (expressionList.size() == 0) { - throw new IllegalArgumentException(file.getAbsolutePath() + throw new IllegalArgumentException(file.getName() + " does not contain expressions for context " + context + " (old version?)"); } } From 558e8e59552ea7b3f263d8d46de6f089f30366e3 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 17 Jun 2025 13:02:41 +0200 Subject: [PATCH 20/37] added new angles --- .../src/main/java/btools/router/VoiceHint.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/VoiceHint.java b/brouter-core/src/main/java/btools/router/VoiceHint.java index 691453c..09d38ca 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHint.java +++ b/brouter-core/src/main/java/btools/router/VoiceHint.java @@ -580,7 +580,11 @@ public class VoiceHint { } else if (lowerBadWayAngle >= -100.f && higherBadWayAngle < 45.f) { cmd = KL; } else { - cmd = C; + if (lowerBadWayAngle > -35.f && higherBadWayAngle > 55.f) { + cmd = KR; + } else { + cmd = C; + } } } else if (cmdAngle < 5.f) { if (lowerBadWayAngle > -30.f) { @@ -597,7 +601,11 @@ public class VoiceHint { } else if (lowerBadWayAngle > -45.f && higherBadWayAngle <= 100.f) { cmd = KR; } else { - cmd = C; + if (lowerBadWayAngle < -55.f && higherBadWayAngle < 35.f) { + cmd = KL; + } else { + cmd = C; + } } } else if (cmdAngle < 45.f) { cmd = TSLR; From 156d5f9f9836aca8a6d258caed6328c74d471aa8 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 17 Jun 2025 13:08:30 +0200 Subject: [PATCH 21/37] made conditions equal --- .../src/main/java/btools/router/VoiceHintProcessor.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java index 5df8a67..912b5cf 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java +++ b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java @@ -314,7 +314,13 @@ public final class VoiceHintProcessor { input.cmd == VoiceHint.KR || input.cmd == VoiceHint.KL) && !input.goodWay.isLinktType()) { - if (input.goodWay.getPrio() < input.maxBadPrio && (inputLastSaved != null && inputLastSaved.distanceToNext > catchingRange)) { + if ( + ((Math.abs(input.lowerBadWayAngle) < 35.f || + input.higherBadWayAngle < 35.f) + || input.goodWay.getPrio() < input.maxBadPrio) + && (inputLastSaved != null && inputLastSaved.distanceToNext > minRange) + && (input.distanceToNext > minRange) + ) { results.add(input); } else { if (inputLast != null) { // when drop add distance to last From cfe3ff536ac44a47796d7bad9022c99cd3dec27d Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 17 Jun 2025 13:09:17 +0200 Subject: [PATCH 22/37] added consumed angle for roundabout --- brouter-core/src/main/java/btools/router/VoiceHintProcessor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java index 912b5cf..83a2ad4 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java +++ b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java @@ -116,6 +116,7 @@ public final class VoiceHintProcessor { input.angle = roundAboutTurnAngle; input.goodWay.turnangle = roundAboutTurnAngle; input.distanceToNext = distance; + input.turnAngleConsumed = true; //input.roundaboutExit = startTurn < 0 ? roundaboutExit : -roundaboutExit; input.roundaboutExit = roundAboutTurnAngle < 0 ? roundaboutExit : -roundaboutExit; float tmpangle = 0; From 5e68bfdf690d8bc815ca5d7c0ae2f6a3ddc57225 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 19 Jun 2025 10:09:58 +0200 Subject: [PATCH 23/37] use profile parameter --- .../main/java/btools/routingapp/BRouterWorker.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java index 437d136..3c87247 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java @@ -100,11 +100,19 @@ public class BRouterWorker { } routingParamCollector.setParams(rc, waypoints, theParams); + Map profileParamsCollection = null; + try { + if (profileParams != null) { + profileParamsCollection = routingParamCollector.getUrlParams(profileParams); + routingParamCollector.setProfileParams(rc, profileParamsCollection); + } + } catch (UnsupportedEncodingException e) { + // ignore + } if (params.containsKey("extraParams")) { - Map profileparams = null; try { - profileparams = routingParamCollector.getUrlParams(params.getString("extraParams")); - routingParamCollector.setProfileParams(rc, profileparams); + profileParamsCollection = routingParamCollector.getUrlParams(params.getString("extraParams")); + routingParamCollector.setProfileParams(rc, profileParamsCollection); } catch (UnsupportedEncodingException e) { // ignore } From 714584e39c5fd6264ef2fab4d407132385e131dc Mon Sep 17 00:00:00 2001 From: Emux Date: Thu, 19 Jun 2025 15:43:02 +0300 Subject: [PATCH 24/37] Update dependencies --- brouter-routing-app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index 179d4f6..0527879 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -102,7 +102,7 @@ repositories { dependencies { implementation 'androidx.appcompat:appcompat:1.7.1' implementation "androidx.constraintlayout:constraintlayout:2.2.1" - implementation 'androidx.work:work-runtime:2.10.1' + implementation 'androidx.work:work-runtime:2.10.2' implementation 'com.google.android.material:material:1.12.0' implementation project(':brouter-mapaccess') @@ -115,7 +115,7 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' - androidTestImplementation 'androidx.work:work-testing:2.10.1' + androidTestImplementation 'androidx.work:work-testing:2.10.2' } gradle.projectsEvaluated { From ac7ddddb62fa2fbe365baac3bc09fa6dac7cfe94 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 30 Jun 2025 12:36:30 +0200 Subject: [PATCH 25/37] added params in timeoutdata --- .../main/java/btools/routingapp/BRouterView.java | 14 ++++++++++++++ .../main/java/btools/routingapp/BRouterWorker.java | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java index 485d03f..092aac3 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -30,6 +30,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.zip.ZipEntry; @@ -44,6 +45,8 @@ import btools.router.OsmTrack; import btools.router.RoutingContext; import btools.router.RoutingEngine; import btools.router.RoutingHelper; +import btools.router.RoutingParamCollector; + import btools.util.CheapRuler; public class BRouterView extends View { @@ -437,6 +440,7 @@ public class BRouterView extends View { public void startProcessing(String profile) { rawTrackPath = null; + String params = null; if (profile.startsWith(" 2) { + try { + Map profileParamsCollection = null; + RoutingParamCollector routingParamCollector = new RoutingParamCollector(); + profileParamsCollection = routingParamCollector.getUrlParams(params); + routingParamCollector.setProfileParams(rc, profileParamsCollection); + } catch (Exception e) {} + } + int plain_distance = 0; int maxlon = Integer.MIN_VALUE; int minlon = Integer.MAX_VALUE; diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java index 3c87247..093b9f6 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java @@ -221,6 +221,14 @@ public class BRouterWorker { bw.write("\n"); writeWPList(bw, waypoints); writeWPList(bw, rc.nogopoints); + if (rc.keyValues != null) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry e : rc.keyValues.entrySet()) { + sb.append(sb.length()>0 ? "&" : "").append(e.getKey()).append("=").append(e.getValue()); + } + bw.write(sb.toString()); + bw.write("\n"); + } bw.close(); } From efcfc59d1b12e61e5012f26f69f23e572da633d7 Mon Sep 17 00:00:00 2001 From: Emux Date: Wed, 25 Jun 2025 14:51:38 +0300 Subject: [PATCH 26/37] Fix ListPreference in RoutingParameterDialog --- .../btools/routingapp/RoutingParameterDialog.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java b/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java index d69a037..3557b55 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java @@ -377,8 +377,8 @@ public class RoutingParameterDialog extends AppCompatActivity { for (String tmp : sa) { // Add the name and address to the ListPreference enties and entyValues //L.v("AFTrack", "device: "+device.getName() + " -- " + device.getAddress()); - entryValues[i] = "" + i; entries[i] = tmp.trim(); + entryValues[i] = entries[i].split("=")[0].trim(); if (entryValues[i].equals(s)) ii = i; i++; } @@ -394,11 +394,14 @@ public class RoutingParameterDialog extends AppCompatActivity { listPref.setSummary(p.description); listPref.setOnPreferenceChangeListener((Preference preference, Object newValue) -> { p.value = (String) newValue; - int iii = Integer.decode(p.value); - listPref.setTitle(p.name + ": " + entries[iii]); - + for (int iii = 0; iii < entryValues.length; iii++) { + String entryValue = entryValues[iii]; + if (entryValue.equals(p.value)) { + listPref.setTitle(p.name + ": " + entries[iii]); + break; + } + } return true; - }); gpsPrefCat.addPreference(listPref); From 30bc66e7fd800a77dae5f98e0c31eba4ea18bbae Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 30 Jun 2025 17:13:13 +0200 Subject: [PATCH 27/37] switched traffic from hike to fastbike --- misc/profiles2/fastbike.brf | 43 +++++------------------------- misc/profiles2/hiking-mountain.brf | 32 +--------------------- 2 files changed, 8 insertions(+), 67 deletions(-) diff --git a/misc/profiles2/fastbike.brf b/misc/profiles2/fastbike.brf index 97006bb..4507cc3 100644 --- a/misc/profiles2/fastbike.brf +++ b/misc/profiles2/fastbike.brf @@ -23,7 +23,7 @@ assign allow_steps = true # %allow_steps% | Set to false to disallow assign allow_ferries = true # %allow_ferries% | set to false to disallow ferries | boolean assign allow_motorways = false # %allow_motorways% | Set to true to allow motorways (useful in Asia / Oceania for example) | boolean -assign consider_traffic = false # %consider_traffic% | Activate to avoid traffic | boolean +assign consider_traffic = 1 # %consider_traffic% | how do you plan to drive the tour? | [1=as cyclist alone in the week, 0.5=as cyclist alone at weekend, 0.3 =with a group of cyclists, 0.1=with a group of cyclists at week-end, 0.0=do not consider traffic] assign consider_noise = false # %consider_noise% | Activate to prefer a low-noise route | boolean assign consider_river = false # %consider_river% | Activate to prefer a route along rivers, lakes, etc. | boolean assign consider_forest = false # %consider_forest% | Activate to prefer a route in forest or parks | boolean @@ -154,43 +154,14 @@ assign onewaypenalty = assign hascycleway = not and ( or cycleway= cycleway=no|none ) and ( or cycleway:left= cycleway:left=no ) ( or cycleway:right= cycleway:right=no ) -assign trafficpenalty0 = - if consider_traffic then - ( - if highway=primary|primary_link then - ( - if estimated_traffic_class=4 then 0.2 - else if estimated_traffic_class=5 then 0.4 - else if estimated_traffic_class=6|7 then 0.6 - else 0 - ) - else if highway=secondary|secondary_link then - ( - if estimated_traffic_class=3 then 0.2 - else if estimated_traffic_class=4 then 0.4 - else if estimated_traffic_class=5 then 0.6 - else if estimated_traffic_class=6|7 then 1 - else 0 - ) - else if highway=tertiary|tertiary_link then - ( - if estimated_traffic_class=2 then 0.1 - else if estimated_traffic_class=3 then 0.3 - else if estimated_traffic_class=4 then 0.5 - else if estimated_traffic_class=5|6|7 then 1 - else 0 - ) - else 0 - ) - else 0 assign trafficpenalty = - if consider_traffic then - ( - if hascycleway then min 0.3 trafficpenalty0 - else trafficpenalty0 - ) - else 0 + if estimated_traffic_class=|1|2 then 0 + else if estimated_traffic_class=3 then multiply 0.3 consider_traffic + else if estimated_traffic_class=4 then multiply 0.6 consider_traffic + else if estimated_traffic_class=5 then multiply 0.9 consider_traffic + else if estimated_traffic_class=6|7 then multiply 1.5 consider_traffic + else 0 assign isresidentialorliving = or highway=residential|living_street living_street=yes diff --git a/misc/profiles2/hiking-mountain.brf b/misc/profiles2/hiking-mountain.brf index 6d3d920..2b36b81 100644 --- a/misc/profiles2/hiking-mountain.brf +++ b/misc/profiles2/hiking-mountain.brf @@ -15,7 +15,6 @@ assign consider_noise = false # %consider_noise% | Activate to prefe assign consider_river = false # %consider_river% | Activate to prefer a route along rivers, lakes, etc. | boolean assign consider_forest = false # %consider_forest% | Activate to prefer a route in forest or green areas| boolean assign consider_town = false # %consider_town% | Activate to bypass cities / big towns as far as possible | boolean -assign consider_traffic = 1 # %consider_traffic% | how do you plan to drive the tour? | [1=as cyclist alone in the week, 0.5=as cyclist alone at weekend, 0.3 =with a group of cyclists, 0.1=with a group of cyclists at week-end] assign shortest_way 0 # 0 as default, duplicate shortest standard profile, SAC access limit ignored for now @@ -359,36 +358,7 @@ assign town_penalty switch estimated_town_class=5 1.4 switch estimated_town_class=6 1.6 99 0 -assign trafficpenalty = -# if any_cycleway then 0 -# else -if highway=primary|primary_link then - ( - if estimated_traffic_class=1|2 then 0 - else if estimated_traffic_class=3 then multiply 0.4 consider_traffic - else if estimated_traffic_class=4 then multiply 0.8 consider_traffic - else if estimated_traffic_class=5 then multiply 1 consider_traffic - else if estimated_traffic_class=6|7 then multiply 2 consider_traffic - else multiply 0.6 consider_traffic - ) - else if highway=secondary|secondary_link then - ( - if estimated_traffic_class=1|2 then multiply 0.1 consider_traffic - else if estimated_traffic_class=3 then multiply 0.3 consider_traffic - else if estimated_traffic_class=4 then multiply 0.7 consider_traffic - else if estimated_traffic_class=5 then multiply 1 consider_traffic - else if estimated_traffic_class=6|7 then multiply 1.5 consider_traffic - else multiply 0.2 consider_traffic - ) - else if highway=tertiary|tertiary_link then - ( - if estimated_traffic_class=1|2 then multiply 0.1 consider_traffic - else if estimated_traffic_class=3 then multiply 0.2 consider_traffic - else if estimated_traffic_class=4 then multiply 0.5 consider_traffic - else multiply if estimated_traffic_class=5|6|7 then multiply 1 consider_traffic - else 0.1 consider_traffic - ) - else 0 +assign trafficpenalty = 0 assign costfactor add town_penalty From 00d74d930e4ab6f4147f37e8412001fa162a117b Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 7 Jul 2025 12:32:53 +0200 Subject: [PATCH 28/37] protect null pointer --- .../src/main/java/btools/routingapp/BRouterView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java index 092aac3..dc1c0a1 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -499,7 +499,7 @@ public class BRouterView extends View { rc.localFunction = profilePath; rc.turnInstructionMode = cor.getTurnInstructionMode(); - if (params != null || params.length() > 2) { + if (params != null && params.length() > 2) { try { Map profileParamsCollection = null; RoutingParamCollector routingParamCollector = new RoutingParamCollector(); From 1cc1039ec0b2f9a18b19a404542443b021151bca Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 8 Jul 2025 10:48:54 +0200 Subject: [PATCH 29/37] updated for Android 16 --- brouter-routing-app/build.gradle | 2 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index 0527879..f2590b8 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -24,7 +24,7 @@ android { resValue('string', 'app_version', defaultConfig.versionName) minSdkVersion 21 - targetSdkVersion 34 + targetSdkVersion 36 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/build.gradle b/build.gradle index 6fd211e..9fd6710 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.10.1' + classpath 'com.android.tools.build:gradle:8.11.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e2847c8..d4081da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From c3e6b8a1ac02f8649ec52ed54fb1dd959bbb1d1e Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 8 Jul 2025 13:56:34 +0200 Subject: [PATCH 30/37] updated for gradle 8.13 --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 43504 -> 43705 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 5 ++--- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 9fd6710..6fd211e 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.11.0' + classpath 'com.android.tools.build:gradle:8.10.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 2c3521197d7c4586c843d1d3e9090525f1898cde..9bbc975c742b298b441bfb90dbc124400a3751b9 100644 GIT binary patch delta 34884 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 delta 34740 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�{ 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

2jq7_TfZ- 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d4081da..37f853b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6..faf9300 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. From f93fc0b0e5125223f47f0f88208b6e11353fc82c Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 8 Jul 2025 14:26:06 +0200 Subject: [PATCH 31/37] added new Android version --- docs/revisions.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/revisions.md b/docs/revisions.md index 895d4d5..1bcec5e 100644 --- a/docs/revisions.md +++ b/docs/revisions.md @@ -10,6 +10,8 @@ Android - use parameter changed in the BRouter app - reuse parameter for repeat:profile function - use unordered values for profile listbox (e.g. fastbike profile) +- Android 16 + Library From f019af4938562d3eac54de9d6c2dc89042439d18 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 10 Jul 2025 11:02:26 +0200 Subject: [PATCH 32/37] preparing for version 1.7.8 --- brouter-core/src/main/java/btools/router/OsmTrack.java | 4 ++-- brouter-routing-app/build.gradle | 2 +- buildSrc/src/main/groovy/brouter.version-conventions.gradle | 2 +- docs/revisions.md | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index cece041..848493b 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -23,8 +23,8 @@ import btools.util.CompactLongMap; import btools.util.FrozenLongMap; public final class OsmTrack { - final public static String version = "1.7.7"; - final public static String versionDate = "23072024"; + final public static String version = "1.7.8"; + final public static String versionDate = "12072025"; // csv-header-line private static final String MESSAGES_HEADER = "Longitude\tLatitude\tElevation\tDistance\tCostPerKm\tElevCost\tTurnCost\tNodeCost\tInitialCost\tWayTags\tNodeTags\tTime\tEnergy"; diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index f2590b8..beec651 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -18,7 +18,7 @@ android { namespace 'btools.routingapp' applicationId "btools.routingapp" - versionCode 54 + versionCode 55 versionName project.version resValue('string', 'app_version', defaultConfig.versionName) diff --git a/buildSrc/src/main/groovy/brouter.version-conventions.gradle b/buildSrc/src/main/groovy/brouter.version-conventions.gradle index b99c641..e0ce18b 100644 --- a/buildSrc/src/main/groovy/brouter.version-conventions.gradle +++ b/buildSrc/src/main/groovy/brouter.version-conventions.gradle @@ -4,4 +4,4 @@ // app: build.gradle (versionCode only) // OsmTrack (version and versionDate) // docs revisions.md (version and versionDate) -version '1.7.7' +version '1.7.8' diff --git a/docs/revisions.md b/docs/revisions.md index 1bcec5e..c9a492b 100644 --- a/docs/revisions.md +++ b/docs/revisions.md @@ -2,7 +2,7 @@ (ZIP-Archives including APK, readme + profiles) -### next version +### [brouter-1.7.8.zip](../brouter_bin/brouter-1.7.8.zip) (current revision, 12.07.2025) Android @@ -28,7 +28,7 @@ Library [Solved issues](https://github.com/abrensch/brouter/issues?q=is%3Aissue+milestone%3A%22Version+1.7.8%22+is%3Aclosed) -### [brouter-1.7.7.zip](../brouter_bin/brouter-1.7.7.zip) (current revision, 23.07.2024) +### [brouter-1.7.7.zip](../brouter_bin/brouter-1.7.7.zip) (23.07.2024) - new Android API 34 From 4c0cef83da79ba9f9d5089b1c0f27e6290d82435 Mon Sep 17 00:00:00 2001 From: Ben Varick Date: Fri, 19 Sep 2025 09:05:16 -0700 Subject: [PATCH 33/37] changed process unused to true in profiles --- misc/profiles2/.gitignore | 2 +- misc/profiles2/safety.brf | 403 ++++++++++++++++++++++++++++++++++++ misc/profiles2/shortest.brf | 3 + misc/profiles2/trekking.brf | 2 +- 4 files changed, 408 insertions(+), 2 deletions(-) create mode 100644 misc/profiles2/safety.brf diff --git a/misc/profiles2/.gitignore b/misc/profiles2/.gitignore index 8e365df..751a83b 100644 --- a/misc/profiles2/.gitignore +++ b/misc/profiles2/.gitignore @@ -2,7 +2,7 @@ /car-fast.brf /fastbike-asia-pacific.brf /fastbike-lowtraffic.brf -/safety.brf +#/safety.brf /trekking-ignore-cr.brf /trekking-noferries.brf /trekking-nosteps.brf diff --git a/misc/profiles2/safety.brf b/misc/profiles2/safety.brf new file mode 100644 index 0000000..8a6345d --- /dev/null +++ b/misc/profiles2/safety.brf @@ -0,0 +1,403 @@ +# *** The trekking profile is for slow travel +# *** and avoiding car traffic, but still with +# *** a focus on approaching your destination +# *** efficiently. + +---context:global # following code refers to global config + +# Bike profile +assign validForBikes = true + +# Use the following switches to change behaviour +assign allow_steps = true # %allow_steps% | Set false to disallow steps | boolean +assign allow_ferries = true # %allow_ferries% | Set false to disallow ferries | boolean +assign ignore_cycleroutes = false # %ignore_cycleroutes% | Set true for better elevation results | boolean +assign stick_to_cycleroutes = false # %stick_to_cycleroutes% | Set true to just follow cycleroutes | boolean +assign avoid_unsafe = true # %avoid_unsafe% | Set true to avoid standard highways | boolean + +assign add_beeline = false # %add_beeline% | Enable beeline on distant start/end points | boolean + +assign consider_noise = false # %consider_noise% | Activate to prefer a low-noise route | boolean +assign consider_river = false # %consider_river% | Activate to prefer a route along rivers, lakes, etc. | boolean +assign consider_forest = false # %consider_forest% | Activate to prefer a route in forest or parks | boolean +assign consider_town = false # %consider_town% | Activate to bypass cities / big towns as far as possible | boolean +assign consider_traffic = false # %consider_traffic% | Activate to consider traffic estimates | boolean + + + +# Change elevation parameters +assign consider_elevation = true # %consider_elevation% | Set true to favor a route with few elevation meters | boolean + +assign downhillcost = 60 # %downhillcost% | Cost for going downhill | number +assign downhillcutoff = 1.5 # %downhillcutoff% | Gradients below this value in percents are not counted. | number +assign uphillcost = 0 # %uphillcost% | Cost for going uphill | number +assign uphillcutoff = 1.5 # %uphillcutoff% | Gradients below this value in percents are not counted. | number + +assign downhillcost = if consider_elevation then downhillcost else 0 +assign uphillcost = if consider_elevation then uphillcost else 0 + +# Kinematic model parameters (travel time computation) +assign totalMass = 90 # %totalMass% | Mass (in kg) of the bike + biker, for travel time computation | number +assign maxSpeed = 45 # %maxSpeed% | Absolute maximum speed (in km/h), for travel time computation | number +assign S_C_x = 0.225 # %S_C_x% | Drag coefficient times the reference area (in m^2), for travel time computation | number +assign C_r = 0.01 # %C_r% | Rolling resistance coefficient (dimensionless), for travel time computation | number +assign bikerPower = 100 # %bikerPower% | Average power (in W) provided by the biker, for travel time computation | number + +# Turn instructions settings +assign turnInstructionMode = 1 # %turnInstructionMode% | Mode for the generated turn instructions | [0=none, 1=auto-choose, 2=locus-style, 3=osmand-style, 4=comment-style, 5=gpsies-style, 6=orux-style, 7=locus-old-style] +assign turnInstructionCatchingRange = 40 # %turnInstructionCatchingRange% | Within this distance (in m) several turning instructions are combined into one and the turning angles are better approximated to the general direction | number +assign turnInstructionRoundabouts = true # %turnInstructionRoundabouts% | Set "false" to avoid generating special turning instructions for roundabouts | boolean +assign considerTurnRestrictions = true # %considerTurnRestrictions% | Set true to take turn restrictions into account | boolean + +assign processUnusedTags = true # %processUnusedTags% | Set true to output unused tags in data tab | boolean + +---context:way # following code refers to way-tags + +# classifier constants +assign classifier_none = 1 +assign classifier_ferry = 2 + +# +# pre-calculate some logical expressions +# + +assign any_cycleroute = + if route_bicycle_icn=yes then true + else if route_bicycle_ncn=yes then true + else if route_bicycle_rcn=yes then true + else if route_bicycle_lcn=yes then true + else false + +assign nodeaccessgranted = + if any_cycleroute then true + else lcn=yes + +assign is_ldcr = + if ignore_cycleroutes then false + else any_cycleroute + +assign isbike = or bicycle_road=yes or bicycle=yes or or bicycle=permissive bicycle=designated lcn=yes +assign ispaved = surface=paved|asphalt|concrete|paving_stones|sett +assign isunpaved = not or surface= or ispaved surface=fine_gravel|cobblestone +assign probablyGood = or ispaved and ( or isbike highway=footway ) not isunpaved + + +# +# this is the cost (in Meter) for a 90-degree turn +# The actual cost is calculated as turncost*cos(angle) +# (Suppressing turncost while following longdistance-cycleways +# makes them a little bit more magnetic) +# +assign turncost = if is_ldcr then 0 + else if junction=roundabout then 0 + else 90 + + +# +# for any change in initialclassifier, initialcost is added once +# +assign initialclassifier = + if route=ferry then classifier_ferry + else classifier_none + + +# +# calculate the initial cost +# this is added to the total cost each time the costfactor +# changed +# +assign initialcost = + if ( equal initialclassifier classifier_ferry ) then 10000 + else 0 + +# +# implicit access here just from the motorroad tag +# (implicit access rules from highway tag handled elsewhere) +# +assign defaultaccess = + if access= then not motorroad=yes + else if access=private|no then false + else true + +# +# calculate logical bike access +# +assign bikeaccess = + if bicycle= then + ( + if bicycle_road=yes then true + else if vehicle= then ( if highway=footway then false else defaultaccess ) + else not vehicle=private|no + ) + else not bicycle=private|no|dismount|use_sidepath + +# +# calculate logical foot access +# +assign footaccess = + if bicycle=dismount then true + else if foot= then defaultaccess + else not foot=private|no|use_sidepath + +# +# if not bike-, but foot-acess, just a moderate penalty, +# otherwise access is forbidden +# +assign accesspenalty = + if bikeaccess then 0 + else if footaccess then 4 + else if any_cycleroute then 15 + else 10000 + +# +# handle one-ways. On primary roads, wrong-oneways should +# be close to forbidden, while on other ways we just add +# 4 to the costfactor (making it at least 5 - you are allowed +# to push your bike) +# +assign badoneway = + if reversedirection=yes then + if oneway:bicycle=yes then true + else if oneway= then junction=roundabout + else oneway=yes|true|1 + else oneway=-1 + +assign onewaypenalty = + if ( badoneway ) then + ( + if ( cycleway=opposite|opposite_lane|opposite_track ) then 0 + else if ( cycleway:left=opposite|opposite_lane|opposite_track ) then 0 + else if ( cycleway:right=opposite|opposite_lane|opposite_track ) then 0 + else if ( oneway:bicycle=no ) then 0 + else if ( cycleway:left:oneway=no ) then 0 + else if ( cycleway:right:oneway=no ) then 0 + else if ( not footaccess ) then 100 + else if ( junction=roundabout|circular ) then 60 + else if ( highway=primary|primary_link ) then 50 + else if ( highway=secondary|secondary_link ) then 30 + else if ( highway=tertiary|tertiary_link ) then 20 + else 4.0 + ) + else 0.0 + +# add estimate tags +assign traffic_penalty + switch consider_traffic + switch estimated_traffic_class= 0 + switch estimated_traffic_class=1|2 0.2 + switch estimated_traffic_class=3 0.4 + switch estimated_traffic_class=4 0.6 + switch estimated_traffic_class=5 0.8 + switch estimated_traffic_class=6|7 1 99 0 + + +assign noise_penalty + switch consider_noise + switch estimated_noise_class= 0 + switch estimated_noise_class=1 0.3 + switch estimated_noise_class=2 0.5 + switch estimated_noise_class=3 0.8 + switch estimated_noise_class=4 1.4 + switch estimated_noise_class=5 1.7 + switch estimated_noise_class=6 2 0 0 + +assign no_river_penalty + switch consider_river + switch estimated_river_class= 2 + switch estimated_river_class=1 1.3 + switch estimated_river_class=2 1 + switch estimated_river_class=3 0.7 + switch estimated_river_class=4 0.4 + switch estimated_river_class=5 0.1 + switch estimated_river_class=6 0 99 0 + +assign no_forest_penalty + switch consider_forest + switch estimated_forest_class= 1 + switch estimated_forest_class=1 0.5 + switch estimated_forest_class=2 0.4 + switch estimated_forest_class=3 0.25 + switch estimated_forest_class=4 0.15 + switch estimated_forest_class=5 0.1 + switch estimated_forest_class=6 0 99 0 + +assign town_penalty + switch consider_town + switch estimated_town_class= 0 + switch estimated_town_class=1 0.5 + switch estimated_town_class=2 0.9 + switch estimated_town_class=3 1.2 + switch estimated_town_class=4 1.3 + switch estimated_town_class=5 1.4 + switch estimated_town_class=6 1.6 99 0 + +# +# calculate the cost-factor, which is the factor +# by which the distance of a way-segment is multiplied +# to calculate the cost of that segment. The costfactor +# must be >=1 and it's supposed to be close to 1 for +# the type of way the routing profile is searching for +# +assign isresidentialorliving = or highway=residential|living_street living_street=yes +assign costfactor + + # + # exclude rivers, rails etc. + # + if ( and highway= not route=ferry ) then 10000 + + # + # exclude motorways and proposed roads + # + else if ( highway=motorway|motorway_link ) then 10000 + else if ( highway=proposed|abandoned ) then 10000 + + # + # all other exclusions below (access, steps, ferries,..) + # should not be deleted by the decoder, to be available + # in voice-hint-processing + # + else min 9999 + + add town_penalty + add no_forest_penalty + add no_river_penalty + add noise_penalty + add traffic_penalty + + # + # apply oneway-and access-penalties + # + add max onewaypenalty accesspenalty + + # + # steps and ferries are special. Note this is handled + # before the cycleroute-switch, to be able + # to really exclude them be setting cost to infinity + # + if ( highway=steps ) then ( if allow_steps then 40 else 10000 ) + else if ( route=ferry ) then ( if allow_ferries then 5.67 else 10000 ) + + # + # handle long-distance cycle-routes. + # + else if ( is_ldcr ) then 1 # always treated as perfect (=1) + else + add ( if stick_to_cycleroutes then 0.5 else 0.05 ) # everything else somewhat up + + # + # some other highway types + # + if ( highway=pedestrian ) then 3 + else if ( highway=bridleway ) then 5 + else if ( highway=cycleway ) then 1 + else if ( isresidentialorliving ) then ( if isunpaved then 1.5 else 1.1 ) + else if ( highway=service ) then ( if isunpaved then 1.6 else 1.3 ) + + # + # tracks and track-like ways are rated mainly be tracktype/grade + # But note that if no tracktype is given (mainly for road/path/footway) + # it can be o.k. if there's any other hint for quality + # + else if ( highway=track|road|path|footway ) then + ( + if ( tracktype=grade1 ) then ( if probablyGood then 1.0 else 1.3 ) + else if ( tracktype=grade2 ) then ( if probablyGood then 1.1 else 2.0 ) + else if ( tracktype=grade3 ) then ( if probablyGood then 1.5 else 3.0 ) + else if ( tracktype=grade4 ) then ( if probablyGood then 2.0 else 5.0 ) + else if ( tracktype=grade5 ) then ( if probablyGood then 3.0 else 5.0 ) + else ( if probablyGood then 1.0 else 5.0 ) + ) + + # + # When avoiding unsafe ways, avoid highways without a bike hint + # + else add ( if ( and avoid_unsafe not isbike ) then 2 else 0 ) + + # + # actuals roads are o.k. if we have a bike hint + # + if ( highway=trunk|trunk_link ) then ( if isbike then 1.5 else 10 ) + else if ( highway=primary|primary_link ) then ( if isbike then 1.2 else 3 ) + else if ( highway=secondary|secondary_link ) then ( if isbike then 1.1 else 1.6 ) + else if ( highway=tertiary|tertiary_link ) then ( if isbike then 1.0 else 1.4 ) + else if ( highway=unclassified ) then ( if isbike then 1.0 else 1.3 ) + + # + # default for any other highway type not handled above + # + else 2.0 + + +# way priorities used for voice hint generation + +assign priorityclassifier = + + if ( highway=motorway ) then 30 + else if ( highway=motorway_link ) then 29 + else if ( highway=trunk ) then 28 + else if ( highway=trunk_link ) then 27 + else if ( highway=primary ) then 26 + else if ( highway=primary_link ) then 25 + else if ( highway=secondary ) then 24 + else if ( highway=secondary_link ) then 23 + else if ( highway=tertiary ) then 22 + else if ( highway=tertiary_link ) then 21 + else if ( highway=unclassified ) then 20 + else if ( isresidentialorliving ) then 6 + else if ( highway=service ) then 6 + else if ( highway=cycleway ) then 6 + else if ( or bicycle=designated bicycle_road=yes ) then 6 + else if ( highway=track ) then if tracktype=grade1 then 6 else 4 + else if ( highway=bridleway|road|path|footway ) then 4 + else if ( highway=steps ) then 2 + else if ( highway=pedestrian ) then 2 + else 0 + +# some more classifying bits used for voice hint generation... + +assign isbadoneway = not equal onewaypenalty 0 +assign isgoodoneway = if reversedirection=yes then oneway=-1 + else if oneway= then junction=roundabout else oneway=yes|true|1 +assign isroundabout = junction=roundabout +assign islinktype = highway=motorway_link|trunk_link|primary_link|secondary_link|tertiary_link +assign isgoodforcars = if greater priorityclassifier 6 then true + else if ( or isresidentialorliving highway=service ) then true + else if ( and highway=track tracktype=grade1 ) then true + else false + +# ... encoded into a bitmask + +assign classifiermask add isbadoneway + add multiply isgoodoneway 2 + add multiply isroundabout 4 + add multiply islinktype 8 + multiply isgoodforcars 16 + +# include `smoothness=` tags in the response's WayTags for track analysis +assign dummyUsage = smoothness= + +---context:node # following code refers to node tags + +assign defaultaccess = + if ( access= ) then true # add default barrier restrictions here! + else if ( access=private|no ) then false + else true + +assign bikeaccess = + if nodeaccessgranted=yes then true + else if bicycle= then + ( + if vehicle= then defaultaccess + else not vehicle=private|no + ) + else not bicycle=private|no|dismount + +assign footaccess = + if bicycle=dismount then true + else if foot= then defaultaccess + else not foot=private|no + +assign initialcost = + if bikeaccess then 0 + else ( if footaccess then 100 else 1000000 ) diff --git a/misc/profiles2/shortest.brf b/misc/profiles2/shortest.brf index f4e66db..cd1034e 100644 --- a/misc/profiles2/shortest.brf +++ b/misc/profiles2/shortest.brf @@ -12,6 +12,9 @@ assign validForFoot 1 ---context:way # following code refers to way-tags +# show unused tags +assign processUnusedTags = true + assign any_cycleroute or route_bicycle_icn=yes or route_bicycle_ncn=yes or route_bicycle_rcn=yes route_bicycle_lcn=yes assign nodeaccessgranted or any_cycleroute lcn=yes diff --git a/misc/profiles2/trekking.brf b/misc/profiles2/trekking.brf index bc10708..231a939 100644 --- a/misc/profiles2/trekking.brf +++ b/misc/profiles2/trekking.brf @@ -49,7 +49,7 @@ assign turnInstructionCatchingRange = 40 # %turnInstructionCatchingRange% | W assign turnInstructionRoundabouts = true # %turnInstructionRoundabouts% | Set "false" to avoid generating special turning instructions for roundabouts | boolean assign considerTurnRestrictions = true # %considerTurnRestrictions% | Set true to take turn restrictions into account | boolean -assign processUnusedTags = false # %processUnusedTags% | Set true to output unused tags in data tab | boolean +assign processUnusedTags = true # %processUnusedTags% | Set true to output unused tags in data tab | boolean ---context:way # following code refers to way-tags From f8eb93f46219a7806119e765046224b3434ed062 Mon Sep 17 00:00:00 2001 From: Ben Varick Date: Fri, 19 Sep 2025 09:07:02 -0700 Subject: [PATCH 34/37] added lts_score tag to lookups.dat --- misc/profiles2/lookups.dat | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/misc/profiles2/lookups.dat b/misc/profiles2/lookups.dat index e8dafc1..64c994c 100644 --- a/misc/profiles2/lookups.dat +++ b/misc/profiles2/lookups.dat @@ -3,6 +3,12 @@ ---context:way +lts_score;2000000001 1 +lts_score;2000000002 2 +lts_score;2000000003 3 +lts_score;2000000004 4 + + highway;0029035962 residential highway;0010319731 service highway;0007688809 track From 5d62da4ce2cd6bfda731aaf6e20a76494d7af827 Mon Sep 17 00:00:00 2001 From: Ben Varick Date: Fri, 19 Sep 2025 09:07:59 -0700 Subject: [PATCH 35/37] changed README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index cecb125..b712aeb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +Forked from https://github.com/abrensch/brouter +changed processUnused to true for safety, shortest, and trekking +added lts_score to lookups.dat + + BRouter ======= From 409d32978901c55bb6dbd50d6109987e53768d0a Mon Sep 17 00:00:00 2001 From: Ben Varick Date: Fri, 19 Sep 2025 09:09:46 -0700 Subject: [PATCH 36/37] edited README --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b712aeb..4f6f9d3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ Forked from https://github.com/abrensch/brouter -changed processUnused to true for safety, shortest, and trekking -added lts_score to lookups.dat + +- changed processUnused to true for safety, shortest, and trekking +- added lts_score to lookups.dat BRouter From c9d70461a5151075bfe8299b99113c07961e73ab Mon Sep 17 00:00:00 2001 From: Ben Varick Date: Fri, 19 Sep 2025 09:53:08 -0700 Subject: [PATCH 37/37] edited lookups.dat --- misc/profiles2/lookups.dat | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/misc/profiles2/lookups.dat b/misc/profiles2/lookups.dat index 64c994c..d51f455 100644 --- a/misc/profiles2/lookups.dat +++ b/misc/profiles2/lookups.dat @@ -3,12 +3,6 @@ ---context:way -lts_score;2000000001 1 -lts_score;2000000002 2 -lts_score;2000000003 3 -lts_score;2000000004 4 - - highway;0029035962 residential highway;0010319731 service highway;0007688809 track @@ -717,6 +711,11 @@ estimated_town_class;0000000001 4 estimated_town_class;0000000001 5 estimated_town_class;0000000001 6 +lts_score;2000000001 1 +lts_score;2000000002 2 +lts_score;2000000003 3 +lts_score;2000000004 4 + ---context:node