From 82fecf95f6f1803cbff5acd172516b6d8897fc8e Mon Sep 17 00:00:00 2001 From: Henrik Fehlauer Date: Tue, 11 Jul 2023 17:57:14 +0000 Subject: [PATCH 001/173] Revert voice hint indexing change in JSON API to restore compatibility a9e8731 made voice hints available from `formatAsGeoJson()`, which is used both in the GeoJSON HTTP API and in the JSON Java API. To indicate a specific type of voice hint, it was chosen to include its numeric id in the output JSON array among other data. The full list of available ids was defined in `class VoiceHint`, e.g. `static final int C = 1;`. Consumers of the API now depended on the mapping from id to intended voice hint not changing, since otherwise incorrect voice hints could be displayed. Unfortunately that API contract was broken in c9ae7c8, where instead of assigning unused ids to new commands, the meaning of existing ids was changed. This broke compatibility: Clients adapted to the change did not work with the old indexing anymore, and clients not yet adapted would break with newer BRouter releases, e.g. they would suddenly display "Off route" for a "right u-turn". To restore compatibility, the indexing is reverted to its old state. This will unbreak GeoJSON/JSON API users no yet adapted to BRouter 1.7.0 or 1.7.1, e.g. BRouter-Web as well as unmaintained clients. While API users which already patched ids would need to undo or special-case their changes, the impact is believed to be low, as no such users are currently known and the breakage was released only recently. The changed meaning of `TU` in output formats (before: `u-turn-left`, now: `u-turn-180`) has not been reverted for now, since either that command is mapped to fallback solutions anyway (e.g. Orux, old Locus, Gpsies), the change has already been implemented in clients (new Locus, Cruiser) or was only planned to be implemented in the future (OsmAnd). Fixes #584 Test Plan: - `./gradlew test` - Run BRouter with an unpatched BRouter-Web and confirm voice hint ids have been restored to the same ones as emitted by BRouter 1.6.3. --- .../src/main/java/btools/router/VoiceHint.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/VoiceHint.java b/brouter-core/src/main/java/btools/router/VoiceHint.java index 5fea2d7..dd270c5 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHint.java +++ b/brouter-core/src/main/java/btools/router/VoiceHint.java @@ -20,11 +20,11 @@ public class VoiceHint { static final int KL = 8; // keep left static final int KR = 9; // keep right static final int TLU = 10; // U-turn - static final int TU = 11; // 180 degree u-turn - static final int TRU = 12; // Right U-turn - static final int OFFR = 13; // Off route - static final int RNDB = 14; // Roundabout - static final int RNLB = 15; // Roundabout left + static final int TRU = 11; // Right U-turn + static final int OFFR = 12; // Off route + static final int RNDB = 13; // Roundabout + static final int RNLB = 14; // Roundabout left + static final int TU = 15; // 180 degree u-turn static final int BL = 16; // Beeline routing int ilon; From d98b1060d48dac056b39fcb423ec967c26792517 Mon Sep 17 00:00:00 2001 From: Henrik Fehlauer Date: Wed, 12 Jul 2023 08:59:12 +0000 Subject: [PATCH 002/173] Explicitly map internal voice hint ids to external JSON API ids As c9ae7c8 showed, changing internal ids without being aware of the possible impact might easily lead to break the external API. While ids could be fixated by adding respective tests, an even more elegant solution is to make the mapping from internal ids to external ids explicit, similar how it is already done for other voice hint formats. To underline the purpose of the mapping even more, the respective method is renamed appropriately. Test Plan: - `./gradlew test` - Export a complex route in BRouter-Web and check voice hints have not been changed. --- .../src/main/java/btools/router/OsmTrack.java | 2 +- .../main/java/btools/router/VoiceHint.java | 39 ++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index 89ffdac..8bb74d2 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -1000,7 +1000,7 @@ public final class OsmTrack { for (VoiceHint hint : voiceHints.list) { sb.append(" ["); sb.append(hint.indexInTrack); - sb.append(',').append(hint.getCommand()); + sb.append(',').append(hint.getJsonCommandIndex()); sb.append(',').append(hint.getExitNumber()); sb.append(',').append(hint.distanceToNext); sb.append(',').append((int) hint.angle); diff --git a/brouter-core/src/main/java/btools/router/VoiceHint.java b/brouter-core/src/main/java/btools/router/VoiceHint.java index dd270c5..2f858be 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHint.java +++ b/brouter-core/src/main/java/btools/router/VoiceHint.java @@ -61,8 +61,43 @@ public class VoiceHint { badWays.add(badWay); } - public int getCommand() { - return cmd; + public int getJsonCommandIndex() { + switch (cmd) { + case TLU: + return 10; + case TU: + return 15; + case TSHL: + return 4; + case TL: + return 2; + case TSLL: + return 3; + case KL: + return 8; + case C: + return 1; + case KR: + return 9; + case TSLR: + return 6; + case TR: + return 5; + case TSHR: + return 7; + case TRU: + return 11; + case RNDB: + return 13; + case RNLB: + return 14; + case BL: + return 16; + case OFFR: + return 12; + default: + throw new IllegalArgumentException("unknown command: " + cmd); + } } public int getExitNumber() { From 0d89754ecffb1b85a3210cbf05601a36d0722161 Mon Sep 17 00:00:00 2001 From: moving-bits Date: Sat, 15 Jul 2023 16:31:34 +0200 Subject: [PATCH 003/173] Fix minor spelling issues --- .../src/main/java/btools/expressions/BExpression.java | 4 ++-- .../src/main/java/btools/mapaccess/PhysicalFile.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpression.java b/brouter-expressions/src/main/java/btools/expressions/BExpression.java index 45718ad..ded5b34 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpression.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpression.java @@ -66,11 +66,11 @@ final class BExpression { } else { BExpression eCollapsed = e.tryCollapse(); if (e != eCollapsed) { - e = eCollapsed; // allow breakspoint.. + e = eCollapsed; // allow breakpoint.. } BExpression eEvaluated = e.tryEvaluateConstant(); if (e != eEvaluated) { - e = eEvaluated; // allow breakspoint.. + e = eEvaluated; // allow breakpoint.. } } } diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java b/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java index 7bfada5..2c2a186 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java @@ -69,7 +69,7 @@ final public class PhysicalFile { DataBuffers dataBuffers = new DataBuffers(); pf = new PhysicalFile(f, dataBuffers, -1, -1); int div = pf.divisor; - for (int lonDegree = 0; lonDegree < 5; lonDegree++) { // does'nt really matter.. + for (int lonDegree = 0; lonDegree < 5; lonDegree++) { // doesn't really matter.. for (int latDegree = 0; latDegree < 5; latDegree++) { // ..where on earth we are OsmFile osmf = new OsmFile(pf, lonDegree, latDegree, dataBuffers); if (osmf.hasData()) From 3706c0cb57b6031c3f52031bed931b1af3880fe3 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sun, 16 Jul 2023 12:52:47 +0200 Subject: [PATCH 004/173] update to new beta version --- brouter-core/src/main/java/btools/router/OsmTrack.java | 4 ++-- brouter-routing-app/build.gradle | 2 +- build.gradle | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index 89ffdac..7f57a0c 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -36,8 +36,8 @@ import btools.util.FrozenLongMap; import btools.util.StringUtils; public final class OsmTrack { - final public static String version = "1.7.1"; - final public static String versionDate = "12072023"; + final public static String version = "1.7.2-beta-1"; + final public static String versionDate = "12072023+"; // 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 d0f9f2a..143dfe2 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -11,7 +11,7 @@ android { namespace 'btools.routingapp' applicationId "btools.routingapp" - versionCode 48 + versionCode 49 versionName project.version resValue('string', 'app_version', defaultConfig.versionName) diff --git a/build.gradle b/build.gradle index 657cd43..bedc99d 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ allprojects { // app: build.gradle (versionCode only) // OsmTrack (version and versionDate) // docs revisions.md (version and versionDate) - project.version "1.7.1" + project.version "1.7.2-beta-1" group 'org.btools' repositories { From 9125481aed58ce5adc6862094961e88b40fa6d8e Mon Sep 17 00:00:00 2001 From: quaelnix <122357328+quaelnix@users.noreply.github.com> Date: Sun, 16 Jul 2023 19:33:34 +0200 Subject: [PATCH 005/173] Fix regression in fastbike profile https://github.com/abrensch/brouter/commit/e66468b091506833eb5890656a0ef3a538397ab2 broke the logic that handled highway=path. This patch reverts the problematic change in behavior. See: https://github.com/nrenner/brouter-web/issues/756 --- misc/profiles2/fastbike.brf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/misc/profiles2/fastbike.brf b/misc/profiles2/fastbike.brf index 1c48994..ab21a45 100644 --- a/misc/profiles2/fastbike.brf +++ b/misc/profiles2/fastbike.brf @@ -261,8 +261,7 @@ assign costfactor switch highway=cycleway 1.3 switch isresidentialorliving switch isunpaved 10 1.2 switch highway=service switch isunpaved 10 1.2 - switch highway=path switch avoid_path 2.1 1.1 - switch or highway=track or highway=road highway=footway + switch or highway=track or highway=road or highway=path highway=footway switch tracktype=grade1 switch isunpaved 3 1.2 switch tracktype=grade2 switch isunpaved 10 3 switch tracktype=grade3 10.0 From 188280b448b16131d7819e8ab659ed2641f4017c Mon Sep 17 00:00:00 2001 From: quaelnix <122357328+quaelnix@users.noreply.github.com> Date: Mon, 17 Jul 2023 14:48:16 +0200 Subject: [PATCH 006/173] Fix regression in trekking profile The 'avoid_path' logic which was added in https://github.com/abrensch/brouter/commit/89b71c2bfbffe885ad4f6d59b697161f4abaa514 ignores the cycleroute logic and makes no sense. --- misc/profiles2/trekking.brf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/misc/profiles2/trekking.brf b/misc/profiles2/trekking.brf index 011ee43..a08e361 100644 --- a/misc/profiles2/trekking.brf +++ b/misc/profiles2/trekking.brf @@ -300,8 +300,7 @@ assign costfactor # else if ( highway=track|road|path|footway ) then ( - if ( and highway=path avoid_path ) then ( 10.0 ) - else if ( tracktype=grade1 ) then ( if probablyGood then 1.0 else 1.3 ) + 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 ) From 38fc780055e789ee1beb85b5e4439be7a38d8adc Mon Sep 17 00:00:00 2001 From: quaelnix <122357328+quaelnix@users.noreply.github.com> Date: Mon, 17 Jul 2023 14:52:28 +0200 Subject: [PATCH 007/173] Remove unused profile options --- misc/profiles2/fastbike.brf | 2 -- misc/profiles2/hiking-mountain.brf | 1 - misc/profiles2/trekking.brf | 1 - 3 files changed, 4 deletions(-) diff --git a/misc/profiles2/fastbike.brf b/misc/profiles2/fastbike.brf index ab21a45..c303154 100644 --- a/misc/profiles2/fastbike.brf +++ b/misc/profiles2/fastbike.brf @@ -24,8 +24,6 @@ assign allow_ferries = true # %allow_ferries% | set to false to disall assign allow_motorways = false # %allow_motorways% | Set to true to allow motorways (useful in Asia / Oceania for example) | boolean assign ignore_cycleroutes = false # %ignore_cycleroutes% | Set to true for better elevation results | boolean assign stick_to_cycleroutes = false # %stick_to_cycleroutes% | Set to true to just follow cycleroutes | boolean -assign avoid_unsafe = false # %avoid_unsafe% | Set to true to avoid standard highways | boolean -assign avoid_path = false # %avoid_path% | Set to true to avoid pathes | boolean assign consider_traffic = false # %consider_traffic% | Activate to avoid traffic | boolean assign consider_noise = false # %consider_noise% | Activate to prefer a low-noise route | boolean diff --git a/misc/profiles2/hiking-mountain.brf b/misc/profiles2/hiking-mountain.brf index fc61759..c4ce1bc 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 or sees | 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 avoid_path = false # %avoid_path% | Activate to avoid pathes | 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 diff --git a/misc/profiles2/trekking.brf b/misc/profiles2/trekking.brf index a08e361..3c8841a 100644 --- a/misc/profiles2/trekking.brf +++ b/misc/profiles2/trekking.brf @@ -14,7 +14,6 @@ assign allow_ferries = true # %allow_ferries% | Set false to disa 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 = false # %avoid_unsafe% | Set true to avoid standard highways | boolean -assign avoid_path = false # %avoid_path% | Set true to avoid pathes | 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 or sees | boolean From ead951d1492f2b658432b69ab6c7e21b17d4bbae Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 18 Jul 2023 11:01:04 +0200 Subject: [PATCH 008/173] change java version in git actions --- .github/workflows/gradle-publish.yml | 4 ++-- .github/workflows/gradle.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 8ac8cf6..a1d281b 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -24,7 +24,7 @@ jobs: uses: actions/setup-java@v3 with: java-version: '11' - distribution: 'adopt' + distribution: 'temurin' server-id: github # Value of the distributionManagement/repository/id field of the pom.xml settings-path: ${{ github.workspace }} # location for the settings.xml file @@ -38,7 +38,7 @@ jobs: ORG_GRADLE_PROJECT_RELEASE_STORE_FILE: ${{ secrets.BROUTER_KEYSTORE_FILE }} ORG_GRADLE_PROJECT_RELEASE_KEY_ALIAS: ${{ secrets.BROUTER_KEY_ALIAS }} ORG_GRADLE_PROJECT_RELEASE_KEY_PASSWORD: ${{ secrets.BROUTER_KEY_PASSWORD }} - ORG_GRADLE_PROJECT_RELEASE_STORE_PASSWORD: ${{ secrets.BROUTER_STORE_PASSWORD }} + ORG_GRADLE_PROJECT_RELEASE_STORE_PASSWORD: ${{ secrets.BROUTER_STORE_PASSWORD }} run: gradle build # The USERNAME and TOKEN need to correspond to the credentials environment variables used in diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index c2c9b86..39fdb4e 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -20,7 +20,7 @@ jobs: uses: actions/setup-java@v3 with: java-version: '11' - distribution: 'zulu' + distribution: 'temurin' cache: gradle - name: Create local.properties run: touch local.properties From 3eed1e18c867de7556f8d7768b43f4ac6310b370 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 18 Jul 2023 12:30:58 +0200 Subject: [PATCH 009/173] add manual start, add Android bundle --- .github/workflows/gradle-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index a1d281b..3e376a5 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -4,7 +4,7 @@ name: Gradle Package on: - + workflow_dispatch: release: types: [created] @@ -39,7 +39,7 @@ jobs: ORG_GRADLE_PROJECT_RELEASE_KEY_ALIAS: ${{ secrets.BROUTER_KEY_ALIAS }} ORG_GRADLE_PROJECT_RELEASE_KEY_PASSWORD: ${{ secrets.BROUTER_KEY_PASSWORD }} ORG_GRADLE_PROJECT_RELEASE_STORE_PASSWORD: ${{ secrets.BROUTER_STORE_PASSWORD }} - run: gradle build + run: gradle build bundle # The USERNAME and TOKEN need to correspond to the credentials environment variables used in # the publishing section of your build.gradle From ab60c442de62fe1518d511ac0028f01680a92147 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 18 Jul 2023 12:45:52 +0200 Subject: [PATCH 010/173] add doc for new version --- docs/revisions.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/revisions.md b/docs/revisions.md index 3c6e3cf..d2160de 100644 --- a/docs/revisions.md +++ b/docs/revisions.md @@ -2,7 +2,13 @@ (ZIP-Archives including APK, readme + profiles) -### [brouter-1.7.1.zip](../brouter_bin/brouter-1.7.1.zip) (current revision, 12.07.2023) +### [brouter-1.7.2.zip](../brouter_bin/brouter-1.7.2.zip) (current revision, 19.07.2023) + +- Re-index Json output + Note: This is different to releases 1.7.0 and 1.7.1. It is recommended to use the current version to avoid breaks in voice hint output for GeoJson. + + +### [brouter-1.7.1.zip](../brouter_bin/brouter-1.7.1.zip) (12.07.2023) Android From f14b8795ea04d6eb9a991363558a3a6952d82cd6 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 18 Jul 2023 12:53:27 +0200 Subject: [PATCH 011/173] change new version number and date --- brouter-core/src/main/java/btools/router/OsmTrack.java | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index 5c69443..684b355 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -36,8 +36,8 @@ import btools.util.FrozenLongMap; import btools.util.StringUtils; public final class OsmTrack { - final public static String version = "1.7.2-beta-1"; - final public static String versionDate = "12072023+"; + final public static String version = "1.7.2"; + final public static String versionDate = "19072023"; // 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/build.gradle b/build.gradle index bedc99d..0be4859 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ allprojects { // app: build.gradle (versionCode only) // OsmTrack (version and versionDate) // docs revisions.md (version and versionDate) - project.version "1.7.2-beta-1" + project.version "1.7.2" group 'org.btools' repositories { From 0b6608eddbeaad443e54825a27af769cd510557a Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 20 Jul 2023 12:58:00 +0200 Subject: [PATCH 012/173] Update gradle-publish.yml Removed bundle from gradle build --- .github/workflows/gradle-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 3e376a5..1d23647 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -39,7 +39,7 @@ jobs: ORG_GRADLE_PROJECT_RELEASE_KEY_ALIAS: ${{ secrets.BROUTER_KEY_ALIAS }} ORG_GRADLE_PROJECT_RELEASE_KEY_PASSWORD: ${{ secrets.BROUTER_KEY_PASSWORD }} ORG_GRADLE_PROJECT_RELEASE_STORE_PASSWORD: ${{ secrets.BROUTER_STORE_PASSWORD }} - run: gradle build bundle + run: gradle build # The USERNAME and TOKEN need to correspond to the credentials environment variables used in # the publishing section of your build.gradle From 67f923b96ee565407c326b3ec72176daf43b878a Mon Sep 17 00:00:00 2001 From: quaelnix <122357328+quaelnix@users.noreply.github.com> Date: Mon, 24 Jul 2023 17:00:49 +0200 Subject: [PATCH 013/173] Implement 'allow_steps' and 'allow_ferries' in fastbike.brf --- misc/profiles2/fastbike.brf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/profiles2/fastbike.brf b/misc/profiles2/fastbike.brf index c303154..8b9fb79 100644 --- a/misc/profiles2/fastbike.brf +++ b/misc/profiles2/fastbike.brf @@ -253,8 +253,8 @@ assign costfactor switch or highway=tertiary highway=tertiary_link 1.0 switch highway=unclassified switch isunpaved 10 1.1 switch highway=pedestrian 10 - switch highway=steps 1000 - switch route=ferry 5.67 + switch highway=steps switch allow_steps 1000 10000 + switch route=ferry switch allow_ferries 5.67 10000 switch highway=bridleway 5 switch highway=cycleway 1.3 switch isresidentialorliving switch isunpaved 10 1.2 From c38a9186fd9ce4c50a8a3a1063678fe079fb5be7 Mon Sep 17 00:00:00 2001 From: quaelnix <122357328+quaelnix@users.noreply.github.com> Date: Mon, 24 Jul 2023 17:01:04 +0200 Subject: [PATCH 014/173] Remove unused profile options --- misc/profiles2/fastbike.brf | 2 -- 1 file changed, 2 deletions(-) diff --git a/misc/profiles2/fastbike.brf b/misc/profiles2/fastbike.brf index 8b9fb79..effeb57 100644 --- a/misc/profiles2/fastbike.brf +++ b/misc/profiles2/fastbike.brf @@ -22,8 +22,6 @@ assign validForBikes = true assign allow_steps = true # %allow_steps% | Set to false to disallow steps | boolean 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 ignore_cycleroutes = false # %ignore_cycleroutes% | Set to true for better elevation results | boolean -assign stick_to_cycleroutes = false # %stick_to_cycleroutes% | Set to true to just follow cycleroutes | boolean assign consider_traffic = false # %consider_traffic% | Activate to avoid traffic | boolean assign consider_noise = false # %consider_noise% | Activate to prefer a low-noise route | boolean From 5ccc6ef766e775117e27c5abcde1ff5dfac41865 Mon Sep 17 00:00:00 2001 From: quaelnix <122357328+quaelnix@users.noreply.github.com> Date: Mon, 24 Jul 2023 17:11:14 +0200 Subject: [PATCH 015/173] Exploit constant expression optimization in fastbike.brf --- misc/profiles2/fastbike.brf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/misc/profiles2/fastbike.brf b/misc/profiles2/fastbike.brf index effeb57..5c9e6a5 100644 --- a/misc/profiles2/fastbike.brf +++ b/misc/profiles2/fastbike.brf @@ -155,6 +155,8 @@ 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 @@ -179,6 +181,8 @@ assign trafficpenalty0 = else 0 ) else 0 + ) + else 0 assign trafficpenalty = if consider_traffic then From 838141f5972644ece5ec6efa9fac94ffdeec58ac Mon Sep 17 00:00:00 2001 From: quaelnix <122357328+quaelnix@users.noreply.github.com> Date: Thu, 27 Jul 2023 11:29:59 +0200 Subject: [PATCH 016/173] Improve mapcreation readme --- misc/scripts/mapcreation/readme_database.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/misc/scripts/mapcreation/readme_database.md b/misc/scripts/mapcreation/readme_database.md index 648d28f..737f702 100644 --- a/misc/scripts/mapcreation/readme_database.md +++ b/misc/scripts/mapcreation/readme_database.md @@ -12,29 +12,29 @@ Import new tags for noise, green and water feature - prepare database ``` -# postgres createdb --encoding=UTF8 -U postgres osm +# postgres createdb --encoding=UTF8 -U postgres osm -# postgres psql -U postgres osm --command='CREATE EXTENSION postgis;' +# postgres psql -U postgres osm --command='CREATE EXTENSION postgis;' ``` - import to database and create ``` -# osm2pgsql -c -s -d osm -U postgres -W -H localhost -P 5432 -O flex -S brouter_cfg.lua /path/to/file.pbf +# osm2pgsql -c -s -d osm -U postgres -W -H localhost -P 5432 -O flex -S brouter_cfg.lua /path/to/file.pbf ``` -- generate new tags inside the database +- generate new tags inside the database (use `<` if `-f` does not work) ``` -# psql -d osm -U postgres -H localhost -P 5432 -f brouter.sql +# postgres psql -d osm -U postgres -f brouter.sql ``` - prepare generation of pbf - when using database and new tagging an other lookups.dat is needed, use lookups_db.dat and rename - - script needs a jdbc in the classpath + - script needs a jdbc in the classpath (on UNIX and Linux use a colon `:` as delimiter) `... -cp ../postgresql-42.6.0.jar;../brouter_fc.jar ...` From a610255529680aec8fe5c429555362662d3d16eb Mon Sep 17 00:00:00 2001 From: quaelnix <122357328+quaelnix@users.noreply.github.com> Date: Thu, 27 Jul 2023 18:03:36 +0200 Subject: [PATCH 017/173] Fix bug in exclusion of small water bodies --- misc/scripts/mapcreation/brouter.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/scripts/mapcreation/brouter.sql b/misc/scripts/mapcreation/brouter.sql index 14aa658..5c37a1a 100644 --- a/misc/scripts/mapcreation/brouter.sql +++ b/misc/scripts/mapcreation/brouter.sql @@ -77,7 +77,7 @@ FROM polygons p WHERE -- do not consider small surfaces - st_area (p.way) > 1000 + st_area (st_transform (p.way, 4326)::geography) > 1000 AND p.natural IN ('water') OR (p.landuse IN ('forest', 'allotments', 'flowerbed', 'orchard', 'vineyard', 'recreation_ground', 'village_green') OR p.leisure IN ('garden', 'park', 'nature_reserve')); @@ -93,7 +93,7 @@ FROM polygons p WHERE -- do not consider small surfaces - st_area (p.way) > 1000 + st_area (st_transform (p.way, 4326)::geography) > 1000 AND p.natural IN ('water') OR (p.landuse IN ('forest', 'allotments', 'flowerbed', 'orchard', 'vineyard', 'recreation_ground', 'village_green') OR p.leisure IN ('garden', 'park', 'nature_reserve')); From 71dfbac13cdf5e275f36b8a829214d83a4749854 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 31 Jul 2023 10:24:14 +0200 Subject: [PATCH 018/173] check for NPE on badWays --- .../src/main/java/btools/router/VoiceHintProcessor.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java index 6b4f37e..1a63a0a 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java +++ b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java @@ -250,8 +250,10 @@ public final class VoiceHintProcessor { if (input.cmd == VoiceHint.C && !input.goodWay.isLinktType()) { int badWayPrio = 0; - for (MessageData md : input.badWays) { - badWayPrio = Math.max(badWayPrio, md.getPrio()); + if (input.badWays != null) { + for (MessageData md : input.badWays) { + badWayPrio = Math.max(badWayPrio, md.getPrio()); + } } if (input.goodWay.getPrio() < badWayPrio) { results.add(input); From 2dbb57dd4eb45c451419e5c8c0e5549c563ab5d4 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 31 Jul 2023 10:25:58 +0200 Subject: [PATCH 019/173] check for NPE on config --- .../src/main/java/btools/routingapp/BRouterView.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 e2ea815..ff50885 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -845,8 +845,13 @@ public class BRouterView extends View { for (int i = 0; i < 6; i++) { if (checkedModes[i]) { writeRawTrackToMode(routingModes[i]); - String s = map.get(routingModes[i]).params; - String p = map.get(routingModes[i]).profile; + ServiceModeConfig sm = map.get(routingModes[i]); + String s = null; + String p = null; + if (sm != null) { + s = sm.params; + p = sm.profile; + } if (s == null || !p.equals(profileName)) s = "noparams"; ServiceModeConfig smc = new ServiceModeConfig(routingModes[i], profileName, s); for (OsmNodeNamed nogo : nogoVetoList) { From 071ff5863f16230e73c04904f48337e800bc7050 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 31 Jul 2023 10:30:40 +0200 Subject: [PATCH 020/173] break on config change prevented --- brouter-routing-app/src/main/AndroidManifest.xml | 1 + .../main/java/btools/routingapp/RoutingParameterDialog.java | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/brouter-routing-app/src/main/AndroidManifest.xml b/brouter-routing-app/src/main/AndroidManifest.xml index a2f9dfa..e210ef7 100644 --- a/brouter-routing-app/src/main/AndroidManifest.xml +++ b/brouter-routing-app/src/main/AndroidManifest.xml @@ -92,6 +92,7 @@ android:name=".RoutingParameterDialog" android:exported="true" android:launchMode="singleTask" + android:configChanges="orientation|screenSize|keyboardHidden" /> Date: Mon, 31 Jul 2023 10:34:22 +0200 Subject: [PATCH 021/173] monochrome icon added --- .../src/main/res/mipmap-anydpi-v26/ic_launcher.xml | 3 ++- .../src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/brouter-routing-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/brouter-routing-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index bbd3e02..93542a7 100644 --- a/brouter-routing-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/brouter-routing-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,5 @@ - \ No newline at end of file + + diff --git a/brouter-routing-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/brouter-routing-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index bbd3e02..93542a7 100644 --- a/brouter-routing-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/brouter-routing-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -2,4 +2,5 @@ - \ No newline at end of file + + From 76265e7713ad203ee6679a810121d2bd22f78074 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Wed, 2 Aug 2023 13:27:21 +0200 Subject: [PATCH 022/173] latest scripts for db generation --- misc/scripts/mapcreation/brouter.sql | 63 +++++++++++++++++++++--- misc/scripts/mapcreation/brouter_cfg.lua | 55 ++++++++++++++++++--- 2 files changed, 104 insertions(+), 14 deletions(-) diff --git a/misc/scripts/mapcreation/brouter.sql b/misc/scripts/mapcreation/brouter.sql index 14aa658..43a4c29 100644 --- a/misc/scripts/mapcreation/brouter.sql +++ b/misc/scripts/mapcreation/brouter.sql @@ -44,7 +44,6 @@ SELECT -- "buffer radius" was initially created with 50 meters at a lat 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 @@ -361,7 +360,7 @@ SELECT now(); -- create tags for noise --- create raw data +-- 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 @@ -418,6 +417,47 @@ GROUP BY ORDER BY sum_noise_factor DESC; +-- 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, @@ -435,7 +475,7 @@ SELECT '6' END AS noise_class INTO TABLE noise_tags FROM - noise_tmp2 y + noise_tmp3 y WHERE y.sum_noise_factor > 0.01; @@ -707,7 +747,8 @@ SELECT ----------------------------------------- -- OSM data used to calculate/estimate the traffic: -- population of towns (+ distance from position to the towns) --- industrial areas (landuse=industrial) (+ surface of the areas and distance from position) +-- industrial& retail areas (landuse=industrial/retail) (consider surface of the areas and distance from position) +-- airports international -- motorway density (traffic on motorways decreases traffic on primary/secondary/tertiary) calculated on grid -- highway density (traffic decreases when more primary/secondary/tertiary highways are available) calculated on grid -- exceptions: near junctions between motorways and primary/secondary/tertiary the traffic increases on the primary/secondary/tertiary.. @@ -841,7 +882,8 @@ SELECT now(); -- --- traffic due to industrial parcs ... +-- traffic due to industrial or retail areas ... (exceptions/not considered: solar & wind parks!) +-- traffic due to aerodromes -- SELECT now(); @@ -852,8 +894,15 @@ SELECT 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 TABLE poly_industri FROM polygons -WHERE - landuse = 'industrial'; +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 name, diff --git a/misc/scripts/mapcreation/brouter_cfg.lua b/misc/scripts/mapcreation/brouter_cfg.lua index 5ed6d53..b1a7979 100644 --- a/misc/scripts/mapcreation/brouter_cfg.lua +++ b/misc/scripts/mapcreation/brouter_cfg.lua @@ -2,8 +2,9 @@ local srid = 3857 --- 3857 SHOULD BE USED here for distance calculation ... (not srid = 4326 !) --- https://gis.stackexchange.com/questions/48949/epsg-3857-or-4326-for-web-mapping + +-- 3857 (projection) SHOULD BE USED here for distance calculation ... (not srid = 4326 !) +-- https://gis.stackexchange.com/questions/48949/epsg-3857-or-4326-for-web-mapping local tables = {} @@ -28,6 +29,11 @@ tables.polygons = osm2pgsql.define_area_table('polygons', { { column = 'leisure', type = 'text' }, { column = 'natural', type = 'text' }, { column = 'water', type = 'text' }, + { column = 'power', type = 'text' }, + { column = 'plant_method', type = 'text' }, + { column = 'plant_source', type = 'text' }, + { column = 'aeroway', type = 'text' }, + { column = 'aerodrome', type = 'text' }, { column = 'way', type = 'geometry', projection = srid, not_null = true }, }) @@ -73,7 +79,34 @@ function has_area_tags(tags) end return tags.place - or tags.population + or tags.population +end + +function get_plant_source(tags) + local source = nil + + for _, key in ipairs({'source'}) do + local a = tags['plant:' .. key] + if a then + source = a + end + end + + return source +end + + +function get_plant_method(tags) + local method = nil + + for _, key in ipairs({'method'}) do + local a = tags['plant:' .. key] + if a then + method = a + end + end + + return method end function osm2pgsql.process_node(object) @@ -87,7 +120,6 @@ function osm2pgsql.process_node(object) population = object.tags.population, way = object:as_point() }) - end if (object.tags.natural == 'peak') then @@ -115,8 +147,13 @@ function osm2pgsql.process_way(object) leisure = object.tags.leisure, natural = object.tags.natural, water = object.tags.water, + power = object.tags.power, + plant_source = get_plant_source(object.tags), + plant_method = get_plant_method(object.tags), + aeroway = object.tags.aeroway, + aerodrome = object.tags.aerodrome, way = object:as_polygon() - }) + }) end if ( object.tags.highway ~= nil) or ( object.tags.waterway ~= nil) then @@ -131,7 +168,6 @@ function osm2pgsql.process_way(object) }) end - end function osm2pgsql.process_relation(object) @@ -149,6 +185,11 @@ function osm2pgsql.process_relation(object) leisure = object.tags.leisure, natural = object.tags.natural, water = object.tags.water, + power = object.tags.power, + plant_source = get_plant_source(object.tags), + plant_method = get_plant_method(object.tags), + aeroway = object.tags.aeroway, + aerodrome = object.tags.aerodrome, way = object:as_multipolygon() }) @@ -166,4 +207,4 @@ function osm2pgsql.process_relation(object) }) end -end \ No newline at end of file +end From f6c5953241da165c213e34de376ec0dd93c02663 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Wed, 2 Aug 2023 15:32:59 +0200 Subject: [PATCH 023/173] added missed idle parameter --- .../src/main/aidl/btools/routingapp/IBRouterService.aidl | 1 + 1 file changed, 1 insertion(+) diff --git a/brouter-routing-app/src/main/aidl/btools/routingapp/IBRouterService.aidl b/brouter-routing-app/src/main/aidl/btools/routingapp/IBRouterService.aidl index bfd2f81..b233082 100644 --- a/brouter-routing-app/src/main/aidl/btools/routingapp/IBRouterService.aidl +++ b/brouter-routing-app/src/main/aidl/btools/routingapp/IBRouterService.aidl @@ -10,6 +10,7 @@ interface IBRouterService { // "maxRunningTime"-->String with a number of seconds for the routing timeout, default = 60 // "turnInstructionFormat"-->String selecting the format for turn-instructions values: osmand, locus // "trackFormat"-->[kml|gpx|json] default = gpx + // "acceptCompressedResult"-->[true] sends a compressed result when output format is gpx // "lats"-->double[] array of latitudes; 2 values at least. // "lons"-->double[] array of longitudes; 2 values at least. // "nogoLats"-->double[] array of nogo latitudes; may be null. From 1600c4356ee9d43797a732c615c7a40fe58ed17b Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 15 Aug 2023 16:31:06 +0200 Subject: [PATCH 024/173] code cleanup, rework --- .../expressions/BExpressionContext.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java index feb86a1..f4fafa9 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -588,7 +588,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { } value = String.format(Locale.US, "%3.1f", foot * 0.3048f); } - if (value.toLowerCase().contains("'")) { + if (value.contains("'")) { float foot = 0f; int inch = 0; String[] sa = value.toLowerCase().trim().split("'"); @@ -601,9 +601,9 @@ public abstract class BExpressionContext implements IByteArrayUnifier { foot += inch / 12f; } value = String.format(Locale.US, "%3.1f", foot * 0.3048f); - } else if (value.contains("in") || value.contains("\"")) { + } else if (value.toLowerCase().contains("in") || value.contains("\"")) { float inch = 0f; - if (value.indexOf("in") > 0) value = value.substring(0, value.indexOf("in")); + if (value.toLowerCase().indexOf("in") > 0) value = value.substring(0, value.toLowerCase().indexOf("in")); if (value.indexOf("\"") > 0) value = value.substring(0, value.indexOf("\"")); inch = Float.parseFloat(value.trim()); value = String.format(Locale.US, "%3.1f", inch * 0.0254f); @@ -613,20 +613,18 @@ public abstract class BExpressionContext implements IByteArrayUnifier { feet = Float.parseFloat(s.trim()); value = String.format(Locale.US, "%3.1f", feet * 0.3048f); } else if (value.toLowerCase().contains("fathom") || value.toLowerCase().contains("fm")) { - float fathom = 0f; String s = value.substring(0, value.toLowerCase().indexOf("f")); - fathom = Float.parseFloat(s.trim()); + float fathom = Float.parseFloat(s.trim()); value = String.format(Locale.US, "%3.1f", fathom * 1.8288f); - } else if (value.contains("cm")) { - String[] sa = value.trim().split("cm"); - if (sa.length == 1) value = sa[0].trim(); + } else if (value.toLowerCase().contains("cm")) { + String[] sa = value.toLowerCase().trim().split("cm"); + if (sa.length >= 1) value = sa[0].trim(); float cm = Float.parseFloat(value.trim()); - value = String.format(Locale.US, "%3.1f", cm * 100f); + value = String.format(Locale.US, "%3.1f", cm / 100f); } else if (value.toLowerCase().contains("meter")) { String s = value.substring(0, value.toLowerCase().indexOf("m")); value = s.trim(); } else if (value.toLowerCase().contains("mph")) { - value = value.replace("_", ""); String[] sa = value.trim().toLowerCase().split("mph"); if (sa.length >= 1) value = sa[0].trim(); float mph = Float.parseFloat(value.trim()); @@ -636,10 +634,10 @@ public abstract class BExpressionContext implements IByteArrayUnifier { if (sa.length >= 1) value = sa[0].trim(); float nm = Float.parseFloat(value.trim()); value = String.format(Locale.US, "%3.1f", nm * 1.852f); - } else if (value.contains("kmh") || value.contains("km/h") || value.contains("kph")) { - String[] sa = value.trim().split("k"); - if (sa.length == 1) value = sa[0].trim(); - } else if (value.contains("m")) { + } else if (value.toLowerCase().contains("kmh") || value.toLowerCase().contains("km/h") || value.toLowerCase().contains("kph")) { + String[] sa = value.toLowerCase().trim().split("k"); + if (sa.length > 1) value = sa[0].trim(); + } else if (value.toLowerCase().contains("m")) { String s = value.substring(0, value.toLowerCase().indexOf("m")); value = s.trim(); } else if (value.contains("(")) { From b15bdf3192bcdce8877bab3d948eec703171b824 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 15 Aug 2023 16:35:52 +0200 Subject: [PATCH 025/173] update locus to new output --- .../src/main/java/btools/routingapp/BRouterWorker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e140561..7e37818 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java @@ -64,7 +64,7 @@ public class BRouterWorker { if ("osmand".equalsIgnoreCase(tiFormat)) { rc.turnInstructionMode = 3; } else if ("locus".equalsIgnoreCase(tiFormat)) { - rc.turnInstructionMode = 7; + rc.turnInstructionMode = 2; } } if (params.containsKey("timode")) { From dad4ea583c24cbe3fa4a7f76aa4e416b0e8082d5 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 17 Aug 2023 18:03:03 +0200 Subject: [PATCH 026/173] change version number --- brouter-core/src/main/java/btools/router/OsmTrack.java | 4 ++-- brouter-routing-app/build.gradle | 2 +- build.gradle | 2 +- docs/revisions.md | 7 ++++++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index 684b355..ac3b51f 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -36,8 +36,8 @@ import btools.util.FrozenLongMap; import btools.util.StringUtils; public final class OsmTrack { - final public static String version = "1.7.2"; - final public static String versionDate = "19072023"; + final public static String version = "1.7.3"; + final public static String versionDate = "19082023"; // 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 143dfe2..9cd72f4 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -11,7 +11,7 @@ android { namespace 'btools.routingapp' applicationId "btools.routingapp" - versionCode 49 + versionCode 50 versionName project.version resValue('string', 'app_version', defaultConfig.versionName) diff --git a/build.gradle b/build.gradle index 0be4859..6f0c33d 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ allprojects { // app: build.gradle (versionCode only) // OsmTrack (version and versionDate) // docs revisions.md (version and versionDate) - project.version "1.7.2" + project.version "1.7.3" group 'org.btools' repositories { diff --git a/docs/revisions.md b/docs/revisions.md index d2160de..4169388 100644 --- a/docs/revisions.md +++ b/docs/revisions.md @@ -2,7 +2,12 @@ (ZIP-Archives including APK, readme + profiles) -### [brouter-1.7.2.zip](../brouter_bin/brouter-1.7.2.zip) (current revision, 19.07.2023) +### [brouter-1.7.3.zip](../brouter_bin/brouter-1.7.3.zip) (current revision, 19.08.2023) + +- Minor bug fixes + + +### [brouter-1.7.2.zip](../brouter_bin/brouter-1.7.2.zip) (19.07.2023) - Re-index Json output Note: This is different to releases 1.7.0 and 1.7.1. It is recommended to use the current version to avoid breaks in voice hint output for GeoJson. From 5fea70d588af3587fc7013a17d86938f4d0c1734 Mon Sep 17 00:00:00 2001 From: moving-bits Date: Sun, 20 Aug 2023 13:04:41 +0200 Subject: [PATCH 027/173] Replace multiple occurrences of toLowerCase() --- .../expressions/BExpressionContext.java | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java index f4fafa9..7232323 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -578,7 +578,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { if (value.toLowerCase().contains("ft")) { float foot = 0f; int inch = 0; - String[] sa = value.toLowerCase().trim().split("ft"); + String[] sa = value.trim().toLowerCase().split("ft"); if (sa.length >= 1) foot = Float.parseFloat(sa[0].trim()); if (sa.length == 2) { value = sa[1]; @@ -588,10 +588,11 @@ public abstract class BExpressionContext implements IByteArrayUnifier { } value = String.format(Locale.US, "%3.1f", foot * 0.3048f); } + String valueLowerCase = value.toLowerCase(Locale.US); if (value.contains("'")) { float foot = 0f; int inch = 0; - String[] sa = value.toLowerCase().trim().split("'"); + String[] sa = valueLowerCase.trim().split("'"); if (sa.length >= 1) foot = Float.parseFloat(sa[0].trim()); if (sa.length == 2) { value = sa[1]; @@ -601,47 +602,47 @@ public abstract class BExpressionContext implements IByteArrayUnifier { foot += inch / 12f; } value = String.format(Locale.US, "%3.1f", foot * 0.3048f); - } else if (value.toLowerCase().contains("in") || value.contains("\"")) { + } else if (valueLowerCase.contains("in") || value.contains("\"")) { float inch = 0f; - if (value.toLowerCase().indexOf("in") > 0) value = value.substring(0, value.toLowerCase().indexOf("in")); + if (valueLowerCase.indexOf("in") > 0) value = value.substring(0, valueLowerCase.indexOf("in")); if (value.indexOf("\"") > 0) value = value.substring(0, value.indexOf("\"")); inch = Float.parseFloat(value.trim()); value = String.format(Locale.US, "%3.1f", inch * 0.0254f); - } else if (value.toLowerCase().contains("feet") || value.toLowerCase().contains("foot")) { + } else if (valueLowerCase.contains("feet") || valueLowerCase.contains("foot")) { float feet = 0f; - String s = value.substring(0, value.toLowerCase().indexOf("f")); + String s = value.substring(0, valueLowerCase.indexOf("f")); feet = Float.parseFloat(s.trim()); value = String.format(Locale.US, "%3.1f", feet * 0.3048f); - } else if (value.toLowerCase().contains("fathom") || value.toLowerCase().contains("fm")) { - String s = value.substring(0, value.toLowerCase().indexOf("f")); + } else if (valueLowerCase.contains("fathom") || valueLowerCase.contains("fm")) { + String s = value.substring(0, valueLowerCase.indexOf("f")); float fathom = Float.parseFloat(s.trim()); value = String.format(Locale.US, "%3.1f", fathom * 1.8288f); - } else if (value.toLowerCase().contains("cm")) { - String[] sa = value.toLowerCase().trim().split("cm"); + } else if (valueLowerCase.contains("cm")) { + String[] sa = valueLowerCase.trim().split("cm"); if (sa.length >= 1) value = sa[0].trim(); float cm = Float.parseFloat(value.trim()); value = String.format(Locale.US, "%3.1f", cm / 100f); - } else if (value.toLowerCase().contains("meter")) { - String s = value.substring(0, value.toLowerCase().indexOf("m")); + } else if (valueLowerCase.contains("meter")) { + String s = value.substring(0, valueLowerCase.indexOf("m")); value = s.trim(); - } else if (value.toLowerCase().contains("mph")) { - String[] sa = value.trim().toLowerCase().split("mph"); + } else if (valueLowerCase.contains("mph")) { + String[] sa = valueLowerCase.trim().split("mph"); if (sa.length >= 1) value = sa[0].trim(); float mph = Float.parseFloat(value.trim()); value = String.format(Locale.US, "%3.1f", mph * 1.609344f); - } else if (value.toLowerCase().contains("knot")) { - String[] sa = value.trim().toLowerCase().split("knot"); + } else if (valueLowerCase.contains("knot")) { + String[] sa = valueLowerCase.trim().split("knot"); if (sa.length >= 1) value = sa[0].trim(); float nm = Float.parseFloat(value.trim()); value = String.format(Locale.US, "%3.1f", nm * 1.852f); - } else if (value.toLowerCase().contains("kmh") || value.toLowerCase().contains("km/h") || value.toLowerCase().contains("kph")) { - String[] sa = value.toLowerCase().trim().split("k"); + } else if (valueLowerCase.contains("kmh") || valueLowerCase.contains("km/h") || valueLowerCase.contains("kph")) { + String[] sa = valueLowerCase.trim().split("k"); if (sa.length > 1) value = sa[0].trim(); - } else if (value.toLowerCase().contains("m")) { - String s = value.substring(0, value.toLowerCase().indexOf("m")); + } else if (valueLowerCase.contains("m")) { + String s = value.substring(0, valueLowerCase.indexOf("m")); value = s.trim(); } else if (value.contains("(")) { - String s = value.substring(0, value.toLowerCase().indexOf("(")); + String s = value.substring(0, valueLowerCase.indexOf("(")); value = s.trim(); } // found negative maxdraft values From 5ed52599124c68b5d5532bef80cefc517aa09667 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 24 Aug 2023 10:04:08 +0200 Subject: [PATCH 028/173] reworked command line start --- .../java/btools/router/RoutingEngine.java | 24 +++- .../src/main/java/btools/server/BRouter.java | 117 ++++++++++-------- 2 files changed, 87 insertions(+), 54 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index 59cb4f7..baff3dc 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -158,16 +158,21 @@ public class RoutingEngine extends Thread { switch (engineMode) { case BROUTER_ENGINEMODE_ROUTING: + if (waypoints.size() < 2) { + throw new IllegalArgumentException("we need two lat/lon points at least!"); + } doRouting(maxRunningTime); break; case BROUTER_ENGINEMODE_SEED: /* do nothing, handled the old way */ - break; + throw new IllegalArgumentException("not a valid engine mode"); case BROUTER_ENGINEMODE_GETELEV: + if (waypoints.size() < 1) { + throw new IllegalArgumentException("we need one lat/lon point at least!"); + } doGetElev(); break; default: - doRouting(maxRunningTime); - break; + throw new IllegalArgumentException("not a valid engine mode"); } } @@ -201,6 +206,9 @@ public class RoutingEngine extends Thread { continue; } oldTrack = null; + track.exportWaypoints = routingContext.exportWaypoints; + // doesn't work at the moment + // use routingContext.outputFormat track.writeGpx(filename); foundTrack = track; alternativeIndex = i; @@ -300,8 +308,16 @@ public class RoutingEngine extends Thread { OsmNodeNamed n = new OsmNodeNamed(listOne.get(0).crosspoint); n.selev = startNode != null ? startNode.getSElev() : Short.MIN_VALUE; + // doesn't work at the moment + // use routingContext.outputFormat outputMessage = OsmTrack.formatAsGpxWaypoint(n); - + if (outfileBase != null) { + String filename = outfileBase + ".gpx"; + File out = new File(filename); + FileWriter fw = new FileWriter(filename); + fw.write(outputMessage); + fw.close(); + } long endTime = System.currentTimeMillis(); logInfo("execution time = " + (endTime - startTime) / 1000. + " seconds"); } catch (Exception e) { diff --git a/brouter-server/src/main/java/btools/server/BRouter.java b/brouter-server/src/main/java/btools/server/BRouter.java index 90ded9c..dbfff6d 100644 --- a/brouter-server/src/main/java/btools/server/BRouter.java +++ b/brouter-server/src/main/java/btools/server/BRouter.java @@ -2,72 +2,55 @@ package btools.server; import java.io.BufferedOutputStream; import java.io.DataOutputStream; +import java.io.File; import java.io.FileOutputStream; import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; -import java.io.File; +import java.util.Map; import btools.router.OsmNodeNamed; import btools.router.OsmTrack; import btools.router.RoutingContext; import btools.router.RoutingEngine; +import btools.router.RoutingParamCollector; import btools.router.SearchBoundary; public class BRouter { public static void main(String[] args) throws Exception { - if (args.length == 2) { // cgi-input-mode + if (args.length == 3) { // cgi-input-mode try { - String queryString = args[1]; - int sepIdx = queryString.indexOf('='); - if (sepIdx >= 0) queryString = queryString.substring(sepIdx + 1); + System.setProperty("segmentBaseDir", args[0]); + System.setProperty("profileBaseDir", args[1]); + String queryString = args[2]; + queryString = URLDecoder.decode(queryString, "ISO-8859-1"); - int ntokens = 1; - for (int ic = 0; ic < queryString.length(); ic++) { - if (queryString.charAt(ic) == '_') ntokens++; - } - String[] a2 = new String[ntokens + 1]; - int idx = 1; - int pos = 0; - for (; ; ) { - int p = queryString.indexOf('_', pos); - if (p < 0) { - a2[idx++] = queryString.substring(pos); - break; - } - a2[idx++] = queryString.substring(pos, p); - pos = p + 1; - } + + int lonIdx = queryString.indexOf("lonlats="); + int sepIdx = queryString.indexOf("&", lonIdx); + String lonlats = queryString.substring(lonIdx+8, sepIdx); + + RoutingContext rc = new RoutingContext(); + RoutingParamCollector routingParamCollector = new RoutingParamCollector(); + List wplist = routingParamCollector.getWayPointList(lonlats); + + Map params = routingParamCollector.getUrlParams(queryString); + routingParamCollector.setParams(rc, wplist, params); // cgi-header System.out.println("Content-type: text/plain"); System.out.println(); - OsmNodeNamed from = readPosition(a2, 1, "from"); - OsmNodeNamed to = readPosition(a2, 3, "to"); - int airDistance = from.calcDistance(to); - - String airDistanceLimit = System.getProperty("airDistanceLimit"); - if (airDistanceLimit != null) { - int maxKm = Integer.parseInt(airDistanceLimit); - if (airDistance > maxKm * 1000) { - System.out.println("airDistance " + (airDistance / 1000) + "km exceeds limit for online router (" + maxKm + "km)"); - return; - } - } - long maxRunningTime = 60000; // the cgi gets a 1 Minute timeout String sMaxRunningTime = System.getProperty("maxRunningTime"); if (sMaxRunningTime != null) { maxRunningTime = Integer.parseInt(sMaxRunningTime) * 1000; } - List wplist = new ArrayList<>(); - wplist.add(from); - wplist.add(to); - RoutingEngine re = new RoutingEngine(null, null, new File(args[0]), wplist, readRoutingContext(a2)); + RoutingEngine re = new RoutingEngine(null, null, new File(args[0]), wplist, rc); + re.doRun(maxRunningTime); if (re.getErrorMessage() != null) { System.out.println(re.getErrorMessage()); @@ -78,15 +61,17 @@ public class BRouter { System.exit(0); } System.out.println("BRouter " + OsmTrack.version + " / " + OsmTrack.versionDate); - if (args.length < 6) { + if (args.length < 5) { System.out.println("Find routes in an OSM map"); - System.out.println("usage: java -jar brouter.jar "); - return; + System.out.println("usage: java -jar brouter.jar [parameter-list] [profile-parameter-list] "); + System.out.println(" or: java -cp %CLASSPATH% btools.server.BRouter > [parameter-list] [profile-parameter-list]"); + System.out.println(" or: java -jar brouter.jar "); + System.exit(0); } - List wplist = new ArrayList<>(); - wplist.add(readPosition(args, 1, "from")); RoutingEngine re = null; if ("seed".equals(args[3])) { + List wplist = new ArrayList<>(); + wplist.add(readPosition(args, 1, "from")); int searchRadius = Integer.parseInt(args[4]); // if = 0 search a 5x5 square String filename = SearchBoundary.getFileName(wplist.get(0)); @@ -108,14 +93,46 @@ public class BRouter { } dos.close(); } else { - wplist.add(readPosition(args, 3, "to")); - RoutingContext rc = readRoutingContext(args); - re = new RoutingEngine("mytrack", "mylog", new File(args[0]), wplist, rc); - re.doRun(0); + int engineMode = 0; + try { + engineMode = Integer.parseInt(args[2]); + } catch (NumberFormatException e) { + } - } - if (re.getErrorMessage() != null) { - System.out.println(re.getErrorMessage()); + RoutingParamCollector routingParamCollector = new RoutingParamCollector(); + List wplist = routingParamCollector.getWayPointList(args[4]); + + System.setProperty("segmentBaseDir", args[0]); + System.setProperty("profileBaseDir", args[1]); + String moreParams = null; + String profileParams = null; + if (args.length >= 6) { + moreParams = args[5]; + } + if (args.length == 7) { + profileParams = args[6]; + } + + RoutingContext rc = new RoutingContext(); + rc.localFunction = args[3]; + if (moreParams != null) { + Map params = routingParamCollector.getUrlParams(moreParams); + routingParamCollector.setParams(rc, wplist, params); + } + if (profileParams != null) { + Map params = routingParamCollector.getUrlParams(profileParams); + routingParamCollector.setProfileParams(rc, params); + } + try { + if (engineMode==RoutingEngine.BROUTER_ENGINEMODE_GETELEV) { + re = new RoutingEngine("testinfo", null, new File(args[0]), wplist, rc, engineMode); + } else { + re = new RoutingEngine("testtrack", null, new File(args[0]), wplist, rc, engineMode); + } + re.doRun(0); + } catch (Exception e) { + System.out.println(e.getMessage()); + } } } From ed7f473556710b5fcaac887c679cf37ef6d81553 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 24 Aug 2023 10:05:38 +0200 Subject: [PATCH 029/173] added one place for parameter --- .../java/btools/router/RoutingContext.java | 3 + .../btools/router/RoutingParamCollector.java | 337 ++++++++++++++++++ 2 files changed, 340 insertions(+) create mode 100644 brouter-core/src/main/java/btools/router/RoutingParamCollector.java diff --git a/brouter-core/src/main/java/btools/router/RoutingContext.java b/brouter-core/src/main/java/btools/router/RoutingContext.java index 43bd733..0cf8adf 100644 --- a/brouter-core/src/main/java/btools/router/RoutingContext.java +++ b/brouter-core/src/main/java/btools/router/RoutingContext.java @@ -216,6 +216,9 @@ public final class RoutingContext { public boolean inverseRouting; public boolean showTime; + public String outputFormat = "gpx"; + public boolean exportWaypoints = false; + public OsmPrePath firstPrePath; public int turnInstructionMode; // 0=none, 1=auto, 2=locus, 3=osmand, 4=comment-style, 5=gpsies-style diff --git a/brouter-core/src/main/java/btools/router/RoutingParamCollector.java b/brouter-core/src/main/java/btools/router/RoutingParamCollector.java new file mode 100644 index 0000000..adc08fe --- /dev/null +++ b/brouter-core/src/main/java/btools/router/RoutingParamCollector.java @@ -0,0 +1,337 @@ +package btools.router; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +public class RoutingParamCollector { + + final static boolean DEBUG = false; + + /** + * get a list of points and optional extra info for the points + * @param lonLats - linked list separated by ';' or '|' + * @return - a list + */ + public List getWayPointList(String lonLats) { + if (lonLats == null) throw new IllegalArgumentException("lonlats parameter not set"); + + String[] coords = lonLats.split(";|\\|"); // use both variantes + if (coords.length < 1 || !coords[0].contains(",")) + throw new IllegalArgumentException("we need one lat/lon point at least!"); + + List wplist = new ArrayList<>(); + for (int i = 0; i < coords.length; i++) { + String[] lonLat = coords[i].split(","); + if (lonLat.length < 1) + throw new IllegalArgumentException("we need one lat/lon point at least!"); + wplist.add(readPosition(lonLat[0], lonLat[1], "via" + i)); + if (lonLat.length > 2) { + if (lonLat[2].equals("d")) { + wplist.get(wplist.size() - 1).direct = true; + } else { + wplist.get(wplist.size() - 1).name = lonLat[2]; + } + } + } + + if (wplist.get(0).name.startsWith("via")) wplist.get(0).name = "from"; + if (wplist.get(wplist.size() - 1).name.startsWith("via")) { + wplist.get(wplist.size() - 1).name = "to"; + } + + return wplist; + } + + /** + * get a list of points (old style, positions only) + * @param lons - array with longitudes + * @param lats - array with latitudes + * @return - a list + */ + public List readPositions(double[] lons, double[] lats) { + List wplist = new ArrayList<>(); + + if (lats == null || lats.length < 2 || lons == null || lons.length < 2) { + return wplist; + } + + for (int i = 0; i < lats.length && i < lons.length; i++) { + OsmNodeNamed n = new OsmNodeNamed(); + n.name = "via" + i; + n.ilon = (int) ((lons[i] + 180.) * 1000000. + 0.5); + n.ilat = (int) ((lats[i] + 90.) * 1000000. + 0.5); + wplist.add(n); + } + + if (wplist.get(0).name.startsWith("via")) wplist.get(0).name = "from"; + if (wplist.get(wplist.size() - 1).name.startsWith("via")) { + wplist.get(wplist.size() - 1).name = "to"; + } + + return wplist; + } + + private OsmNodeNamed readPosition(String vlon, String vlat, String name) { + if (vlon == null) throw new IllegalArgumentException("lon " + name + " not found in input"); + if (vlat == null) throw new IllegalArgumentException("lat " + name + " not found in input"); + + return readPosition(Double.parseDouble(vlon), Double.parseDouble(vlat), name); + } + + private OsmNodeNamed readPosition(double lon, double lat, String name) { + OsmNodeNamed n = new OsmNodeNamed(); + n.name = name; + n.ilon = (int) ((lon + 180.) * 1000000. + 0.5); + n.ilat = (int) ((lat + 90.) * 1000000. + 0.5); + return n; + } + + /** + * read a url like parameter list linked with '&' + * @param url - parameter list + * @return - a hashmap of the parameter + * @throws UnsupportedEncodingException + */ + public Map getUrlParams(String url) throws UnsupportedEncodingException { + HashMap params = new HashMap<>(); + String decoded = URLDecoder.decode(url, "UTF-8"); + StringTokenizer tk = new StringTokenizer(decoded, "?&"); + while (tk.hasMoreTokens()) { + String t = tk.nextToken(); + StringTokenizer tk2 = new StringTokenizer(t, "="); + if (tk2.hasMoreTokens()) { + String key = tk2.nextToken(); + if (tk2.hasMoreTokens()) { + String value = tk2.nextToken(); + params.put(key, value); + } + } + } + return params; + } + + /** + * fill a parameter map into the routing context + * @param rctx - the context + * @param wplist - the list of way points needed for 'straight' parameter + * @param params - the list of parameters + */ + public void setParams(RoutingContext rctx, List wplist, Map params) { + if (params != null) { + if (params.size() == 0) return; + + // prepare nogos extra + if (params.containsKey("profile")) { + rctx.localFunction = params.get("profile"); + } + if (params.containsKey("nogoLats")) { + List nogoList = readNogos(params.get("nogoLons"), params.get("nogoLats"), params.get("nogoRadi")); + if (nogoList != null) { + RoutingContext.prepareNogoPoints(nogoList); + rctx.nogopoints = nogoList; + } + params.remove("nogoLats"); + params.remove("nogoLons"); + params.remove("nogoRadi"); + } + if (params.containsKey("nogos")) { + List nogoList = readNogoList(params.get("nogos")); + if (nogoList != null) { + RoutingContext.prepareNogoPoints(nogoList); + rctx.nogopoints = nogoList; + } + params.remove("nogos"); + } + if (params.containsKey("polylines")) { + List result = new ArrayList<>(); + parseNogoPolygons(params.get("polylines"), result, false); + if (rctx.nogopoints == null) { + rctx.nogopoints = result; + } else { + rctx.nogopoints.addAll(result); + } + params.remove("polylines"); + } + if (params.containsKey("polygons")) { + List result = new ArrayList<>(); + parseNogoPolygons(params.get("polygons"), result, true); + if (rctx.nogopoints == null) { + rctx.nogopoints = result; + } else { + rctx.nogopoints.addAll(result); + } + params.remove("polygons"); + } + + for (Map.Entry e : params.entrySet()) { + String key = e.getKey(); + String value = e.getValue(); + if (DEBUG) System.out.println("params " + key + " " + value); + + if (key.equals("straight")) { + try { + String[] sa = value.split(","); + for (int i = 0; i < sa.length; i++) { + int v = Integer.parseInt(sa[i]); + if (wplist.size() > v) wplist.get(v).direct = true; + } + } catch (Exception ex) { + System.err.println("error " + ex.getStackTrace()[0].getLineNumber() + " " + ex.getStackTrace()[0] + "\n" + ex); + } + } else if (key.equals("pois")) { + rctx.poipoints = readPoisList(value); + } else if (key.equals("heading")) { + rctx.startDirection = Integer.valueOf(value); + rctx.forceUseStartDirection = true; + } else if (key.equals("direction")) { + rctx.startDirection = Integer.valueOf(value); + } else if (key.equals("alternativeidx")) { + rctx.setAlternativeIdx(Integer.parseInt(value)); + } else if (key.equals("turnInstructionMode")) { + rctx.turnInstructionMode = Integer.parseInt(value); + } else if (key.equals("timode")) { + rctx.turnInstructionMode = Integer.parseInt(value); + } else if (key.equals("exportWaypoints")) { + rctx.exportWaypoints = (Integer.parseInt(value) == 1); + } else if (key.equals("format")) { + rctx.outputFormat = ((String) value).toLowerCase(); + } else if (key.equals("trackFormat")) { + rctx.outputFormat = ((String) value).toLowerCase(); + } else if (key.startsWith("profile:")) { + if (rctx.keyValues == null) rctx.keyValues = new HashMap<>(); + rctx.keyValues.put(key.substring(8), value); + } + // ignore other params + } + } + } + + /** + * fill profile parameter list + * @param rctx - the routing context + * @param params - the list of parameters + */ + public void setProfileParams(RoutingContext rctx, Map params) { + if (params != null) { + if (params.size() == 0) return; + if (rctx.keyValues == null) rctx.keyValues = new HashMap<>(); + for (Map.Entry e : params.entrySet()) { + String key = e.getKey(); + String value = e.getValue(); + if (DEBUG) System.out.println("params " + key + " " + value); + rctx.keyValues.put(key, value); + } + } + } + + private void parseNogoPolygons(String polygons, List result, boolean closed) { + if (polygons != null) { + String[] polygonList = polygons.split("\\|"); + for (int i = 0; i < polygonList.length; i++) { + String[] lonLatList = polygonList[i].split(","); + if (lonLatList.length > 1) { + OsmNogoPolygon polygon = new OsmNogoPolygon(closed); + int j; + for (j = 0; j < 2 * (lonLatList.length / 2) - 1; ) { + String slon = lonLatList[j++]; + String slat = lonLatList[j++]; + int lon = (int) ((Double.parseDouble(slon) + 180.) * 1000000. + 0.5); + int lat = (int) ((Double.parseDouble(slat) + 90.) * 1000000. + 0.5); + polygon.addVertex(lon, lat); + } + + String nogoWeight = "NaN"; + if (j < lonLatList.length) { + nogoWeight = lonLatList[j]; + } + polygon.nogoWeight = Double.parseDouble(nogoWeight); + if (polygon.points.size() > 0) { + polygon.calcBoundingCircle(); + result.add(polygon); + } + } + } + } + } + + public List readPoisList(String pois) { + // lon,lat,name|... + if (pois == null) return null; + + String[] lonLatNameList = pois.split("\\|"); + + List poisList = new ArrayList<>(); + for (int i = 0; i < lonLatNameList.length; i++) { + String[] lonLatName = lonLatNameList[i].split(","); + + if (lonLatName.length != 3) + continue; + + OsmNodeNamed n = new OsmNodeNamed(); + n.ilon = (int) ((Double.parseDouble(lonLatName[0]) + 180.) * 1000000. + 0.5); + n.ilat = (int) ((Double.parseDouble(lonLatName[1]) + 90.) * 1000000. + 0.5); + n.name = lonLatName[2]; + poisList.add(n); + } + + return poisList; + } + + public List readNogoList(String nogos) { + // lon,lat,radius[,weight]|... + + if (nogos == null) return null; + + String[] lonLatRadList = nogos.split("\\|"); + + List nogoList = new ArrayList<>(); + for (int i = 0; i < lonLatRadList.length; i++) { + String[] lonLatRad = lonLatRadList[i].split(","); + String nogoWeight = "NaN"; + if (lonLatRad.length > 3) { + nogoWeight = lonLatRad[3]; + } + nogoList.add(readNogo(lonLatRad[0], lonLatRad[1], lonLatRad[2], nogoWeight)); + } + + return nogoList; + } + + public List readNogos(String nogoLons, String nogoLats, String nogoRadi) { + if (nogoLons == null || nogoLats == null || nogoRadi == null) return null; + List nogoList = new ArrayList<>(); + + String[] lons = nogoLons.split(","); + String[] lats = nogoLats.split(","); + String[] radi = nogoRadi.split(","); + String nogoWeight = "undefined"; + for (int i = 0; i < lons.length && i < lats.length && i < radi.length; i++) { + OsmNodeNamed n = readNogo(lons[i].trim(), lats[i].trim(), radi[i].trim(), nogoWeight); + nogoList.add(n); + } + return nogoList; + } + + + private OsmNodeNamed readNogo(String lon, String lat, String radius, String nogoWeight) { + double weight = "undefined".equals(nogoWeight) ? Double.NaN : Double.parseDouble(nogoWeight); + return readNogo(Double.parseDouble(lon), Double.parseDouble(lat), (int) Double.parseDouble(radius), weight); + } + + private OsmNodeNamed readNogo(double lon, double lat, int radius, double nogoWeight) { + OsmNodeNamed n = new OsmNodeNamed(); + n.name = "nogo" + radius; + n.ilon = (int) ((lon + 180.) * 1000000. + 0.5); + n.ilat = (int) ((lat + 90.) * 1000000. + 0.5); + n.isNogo = true; + n.nogoWeight = nogoWeight; + return n; + } + + +} From 6b3cfb4c9141403f17db71402aad6c6d887072c5 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 24 Aug 2023 10:06:00 +0200 Subject: [PATCH 030/173] added a parameter test --- .../java/btools/router/RouteParamTest.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 brouter-core/src/test/java/btools/router/RouteParamTest.java diff --git a/brouter-core/src/test/java/btools/router/RouteParamTest.java b/brouter-core/src/test/java/btools/router/RouteParamTest.java new file mode 100644 index 0000000..4a27d2c --- /dev/null +++ b/brouter-core/src/test/java/btools/router/RouteParamTest.java @@ -0,0 +1,68 @@ +package btools.router; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public class RouteParamTest { + + @Test(expected = IllegalArgumentException.class) + public void readWptsNull() { + + RoutingParamCollector rpc = new RoutingParamCollector(); + List map = rpc.getWayPointList(null); + + Assert.assertEquals("result content null", 0, map.size()); + + } + + @Test + public void readWpts() { + String data = "1.0,1.2;2.0,2.2"; + RoutingParamCollector rpc = new RoutingParamCollector(); + List map = rpc.getWayPointList(data); + + Assert.assertEquals("result content 1 ", 2, map.size()); + + data = "1.0,1.1|2.0,2.2|3.0,3.3"; + map = rpc.getWayPointList(data); + + Assert.assertEquals("result content 2 ", 3, map.size()); + + data = "1.0,1.2,Name;2.0,2.2"; + map = rpc.getWayPointList(data); + + Assert.assertEquals("result content 3 ", "Name", map.get(0).name); + + data = "1.0,1.2,d;2.0,2.2"; + map = rpc.getWayPointList(data); + + Assert.assertTrue("result content 4 ", map.get(0).direct); + } + + @Test + public void readUrlParams() throws UnsupportedEncodingException { + String url = "lonlats=1,1;2,2&profile=test&more=1"; + RoutingParamCollector rpc = new RoutingParamCollector(); + Map map = rpc.getUrlParams(url); + + Assert.assertEquals("result content ", 3, map.size()); + } + + @Test + public void readParamsFromList() throws UnsupportedEncodingException { + Map params = new HashMap<>(); + params.put("timode", "3"); + RoutingContext rc = new RoutingContext(); + RoutingParamCollector rpc = new RoutingParamCollector(); + rpc.setParams(rc, null, params); + + Assert.assertEquals("result content timode ", 3, rc.turnInstructionMode); + } + +} From 1ffd42904b9de70d75e46315cbcb215e86782be1 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 26 Aug 2023 17:28:21 +0200 Subject: [PATCH 031/173] update rework #616 and remove trim --- .../expressions/BExpressionContext.java | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java index 7232323..eff1bbb 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -565,85 +565,85 @@ public abstract class BExpressionContext implements IByteArrayUnifier { String org = value; try { // remove some unused characters - value = value.replace(",", "."); - value = value.replace(">", ""); - value = value.replace("_", ""); + value = value.replaceAll(",", "."); + value = value.replaceAll(">", ""); + value = value.replaceAll("_", ""); + value = value.replaceAll(" ", ""); + //value = value.replaceAll(" ", ""); // nbsp + value = value.replaceAll("’", "'"); + value = value.replaceAll("”", "\""); if (value.indexOf("-") == 0) value = value.substring(1); - if (value.indexOf("~") == 0) value = value.substring(1); - if (value.contains("-")) { // replace eg. 1.4-1.6 m - String tmp = value.substring(value.indexOf("-") + 1).replaceAll("[0-9.,-]", ""); - value = value.substring(0, value.indexOf("-")) + tmp; + if (value.contains("-")) { // replace eg. 1.4-1.6 m but also 1'-6" + value = value.substring(0, value.indexOf("-")); // + tmp; } + value = value.toLowerCase(Locale.US); + // do some value conversion - if (value.toLowerCase().contains("ft")) { + if (value.contains("ft")) { float foot = 0f; int inch = 0; - String[] sa = value.trim().toLowerCase().split("ft"); - if (sa.length >= 1) foot = Float.parseFloat(sa[0].trim()); + String[] sa = value.split("ft"); + if (sa.length >= 1) foot = Float.parseFloat(sa[0]); if (sa.length == 2) { value = sa[1]; if (value.indexOf("in") > 0) value = value.substring(0, value.indexOf("in")); - inch = Integer.parseInt(value.trim()); + inch = Integer.parseInt(value); foot += inch / 12f; } value = String.format(Locale.US, "%3.1f", foot * 0.3048f); } - String valueLowerCase = value.toLowerCase(Locale.US); if (value.contains("'")) { float foot = 0f; int inch = 0; - String[] sa = valueLowerCase.trim().split("'"); - if (sa.length >= 1) foot = Float.parseFloat(sa[0].trim()); + String[] sa = value.split("'"); + if (sa.length >= 1) foot = Float.parseFloat(sa[0]); if (sa.length == 2) { value = sa[1]; if (value.indexOf("''") > 0) value = value.substring(0, value.indexOf("''")); if (value.indexOf("\"") > 0) value = value.substring(0, value.indexOf("\"")); - inch = Integer.parseInt(value.trim()); + inch = Integer.parseInt(value); foot += inch / 12f; } value = String.format(Locale.US, "%3.1f", foot * 0.3048f); - } else if (valueLowerCase.contains("in") || value.contains("\"")) { + } else if (value.contains("in") || value.contains("\"")) { float inch = 0f; - if (valueLowerCase.indexOf("in") > 0) value = value.substring(0, valueLowerCase.indexOf("in")); + if (value.indexOf("in") > 0) value = value.substring(0, value.indexOf("in")); if (value.indexOf("\"") > 0) value = value.substring(0, value.indexOf("\"")); - inch = Float.parseFloat(value.trim()); + inch = Float.parseFloat(value); value = String.format(Locale.US, "%3.1f", inch * 0.0254f); - } else if (valueLowerCase.contains("feet") || valueLowerCase.contains("foot")) { + } else if (value.contains("feet") || value.contains("foot")) { float feet = 0f; - String s = value.substring(0, valueLowerCase.indexOf("f")); - feet = Float.parseFloat(s.trim()); + String s = value.substring(0, value.indexOf("f")); + feet = Float.parseFloat(s); value = String.format(Locale.US, "%3.1f", feet * 0.3048f); - } else if (valueLowerCase.contains("fathom") || valueLowerCase.contains("fm")) { - String s = value.substring(0, valueLowerCase.indexOf("f")); - float fathom = Float.parseFloat(s.trim()); + } else if (value.contains("fathom") || value.contains("fm")) { + String s = value.substring(0, value.indexOf("f")); + float fathom = Float.parseFloat(s); value = String.format(Locale.US, "%3.1f", fathom * 1.8288f); - } else if (valueLowerCase.contains("cm")) { - String[] sa = valueLowerCase.trim().split("cm"); - if (sa.length >= 1) value = sa[0].trim(); - float cm = Float.parseFloat(value.trim()); + } else if (value.contains("cm")) { + String[] sa = value.split("cm"); + if (sa.length >= 1) value = sa[0]; + float cm = Float.parseFloat(value); value = String.format(Locale.US, "%3.1f", cm / 100f); - } else if (valueLowerCase.contains("meter")) { - String s = value.substring(0, valueLowerCase.indexOf("m")); - value = s.trim(); - } else if (valueLowerCase.contains("mph")) { - String[] sa = valueLowerCase.trim().split("mph"); - if (sa.length >= 1) value = sa[0].trim(); - float mph = Float.parseFloat(value.trim()); + } else if (value.contains("meter")) { + value = value.substring(0, value.indexOf("m")); + } else if (value.toLowerCase().contains("mph")) { + String[] sa = value.split("mph"); + if (sa.length >= 1) value = sa[0]; + float mph = Float.parseFloat(value); value = String.format(Locale.US, "%3.1f", mph * 1.609344f); - } else if (valueLowerCase.contains("knot")) { - String[] sa = valueLowerCase.trim().split("knot"); - if (sa.length >= 1) value = sa[0].trim(); - float nm = Float.parseFloat(value.trim()); + } else if (value.contains("knot")) { + String[] sa = value.split("knot"); + if (sa.length >= 1) value = sa[0]; + float nm = Float.parseFloat(value); value = String.format(Locale.US, "%3.1f", nm * 1.852f); - } else if (valueLowerCase.contains("kmh") || valueLowerCase.contains("km/h") || valueLowerCase.contains("kph")) { - String[] sa = valueLowerCase.trim().split("k"); - if (sa.length > 1) value = sa[0].trim(); - } else if (valueLowerCase.contains("m")) { - String s = value.substring(0, valueLowerCase.indexOf("m")); - value = s.trim(); + } else if (value.contains("kmh") || value.contains("km/h") || value.contains("kph")) { + String[] sa = value.split("k"); + if (sa.length > 1) value = sa[0]; + } else if (value.contains("m")) { + value = value.substring(0, value.indexOf("m")); } else if (value.contains("(")) { - String s = value.substring(0, valueLowerCase.indexOf("(")); - value = s.trim(); + value = value.substring(0, value.indexOf("(")); } // found negative maxdraft values // no negative values From e697734d64ff4345e6e1f0b0d493ac8a785017c8 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sun, 27 Aug 2023 17:24:54 +0200 Subject: [PATCH 032/173] remove a last lower case --- .../src/main/java/btools/expressions/BExpressionContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java index eff1bbb..cc1ab29 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -627,7 +627,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { value = String.format(Locale.US, "%3.1f", cm / 100f); } else if (value.contains("meter")) { value = value.substring(0, value.indexOf("m")); - } else if (value.toLowerCase().contains("mph")) { + } else if (value.contains("mph")) { String[] sa = value.split("mph"); if (sa.length >= 1) value = sa[0]; float mph = Float.parseFloat(value); From e954d2174baa5ba6206b7e1715f7540941ec20a1 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 29 Aug 2023 16:49:29 +0200 Subject: [PATCH 033/173] added check for from-to units --- .../main/java/btools/expressions/BExpressionContext.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java index cc1ab29..c2f1ad6 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -573,8 +573,13 @@ public abstract class BExpressionContext implements IByteArrayUnifier { value = value.replaceAll("’", "'"); value = value.replaceAll("”", "\""); if (value.indexOf("-") == 0) value = value.substring(1); - if (value.contains("-")) { // replace eg. 1.4-1.6 m but also 1'-6" - value = value.substring(0, value.indexOf("-")); // + tmp; + if (value.contains("-")) { + // replace eg. 1.4-1.6 m to 1.4m + // but also 1'-6" to 1' + // keep the unit of measure + String tmp = value.substring(value.indexOf("-") + 1).replaceAll("[0-9.,-]", ""); + value = value.substring(0, value.indexOf("-")); + if (value.matches("\\d+(\\.\\d+)?")) value += tmp; } value = value.toLowerCase(Locale.US); From d26f77380d45b6680a408e7f86cdef05b095aaab Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 29 Aug 2023 16:51:59 +0200 Subject: [PATCH 034/173] replaced windows characters --- .../src/main/java/btools/expressions/BExpressionContext.java | 5 ++--- 1 file changed, 2 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 c2f1ad6..25b90eb 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -569,9 +569,8 @@ public abstract class BExpressionContext implements IByteArrayUnifier { value = value.replaceAll(">", ""); value = value.replaceAll("_", ""); value = value.replaceAll(" ", ""); - //value = value.replaceAll(" ", ""); // nbsp - value = value.replaceAll("’", "'"); - value = value.replaceAll("”", "\""); + value = value.replace((char) 8217, '\''); + value = value.replace((char) 8221, '"'); if (value.indexOf("-") == 0) value = value.substring(1); if (value.contains("-")) { // replace eg. 1.4-1.6 m to 1.4m From 2b17ae9255d3f748f0ad75b30b2ac3f5b8f3920c Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 29 Aug 2023 16:55:41 +0200 Subject: [PATCH 035/173] added new character check --- .../src/main/java/btools/expressions/BExpressionContext.java | 1 + 1 file changed, 1 insertion(+) diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java index 25b90eb..13482dd 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -569,6 +569,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { value = value.replaceAll(">", ""); value = value.replaceAll("_", ""); value = value.replaceAll(" ", ""); + value = value.replaceAll("~", ""); value = value.replace((char) 8217, '\''); value = value.replace((char) 8221, '"'); if (value.indexOf("-") == 0) value = value.substring(1); From 093d400c5bc165e4b9976bc9174b03ffaded0bbb Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 29 Aug 2023 16:58:52 +0200 Subject: [PATCH 036/173] added from/to check in test --- .../src/test/java/btools/expressions/EncodeDecodeTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brouter-expressions/src/test/java/btools/expressions/EncodeDecodeTest.java b/brouter-expressions/src/test/java/btools/expressions/EncodeDecodeTest.java index 6979387..b731ea0 100644 --- a/brouter-expressions/src/test/java/btools/expressions/EncodeDecodeTest.java +++ b/brouter-expressions/src/test/java/btools/expressions/EncodeDecodeTest.java @@ -29,7 +29,7 @@ public class EncodeDecodeTest { "depth=1'6\"", // "depth=6 feet", "maxheight=5.1m", - "maxdraft=~3 mt", + "maxdraft=~3 m - 4 m", "reversedirection=yes" }; From 9d22709017d8dbb88228d2936bba4409849400d8 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Wed, 30 Aug 2023 16:59:47 +0200 Subject: [PATCH 037/173] added to if-else-tree --- .../src/main/java/btools/expressions/BExpressionContext.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java index 13482dd..d542a29 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -596,8 +596,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { foot += inch / 12f; } value = String.format(Locale.US, "%3.1f", foot * 0.3048f); - } - if (value.contains("'")) { + } else if (value.contains("'")) { float foot = 0f; int inch = 0; String[] sa = value.split("'"); From 8f793150b0f378e2514935eb6ada5229b738e11e Mon Sep 17 00:00:00 2001 From: afischerdev Date: Wed, 30 Aug 2023 17:03:45 +0200 Subject: [PATCH 038/173] changed wording for foot/feet --- .../btools/expressions/BExpressionContext.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java index d542a29..786439a 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -585,30 +585,30 @@ public abstract class BExpressionContext implements IByteArrayUnifier { // do some value conversion if (value.contains("ft")) { - float foot = 0f; + float feet = 0f; int inch = 0; String[] sa = value.split("ft"); - if (sa.length >= 1) foot = Float.parseFloat(sa[0]); + if (sa.length >= 1) feet = Float.parseFloat(sa[0]); if (sa.length == 2) { value = sa[1]; if (value.indexOf("in") > 0) value = value.substring(0, value.indexOf("in")); inch = Integer.parseInt(value); - foot += inch / 12f; + feet += inch / 12f; } - value = String.format(Locale.US, "%3.1f", foot * 0.3048f); + value = String.format(Locale.US, "%3.1f", feet * 0.3048f); } else if (value.contains("'")) { - float foot = 0f; + float feet = 0f; int inch = 0; String[] sa = value.split("'"); - if (sa.length >= 1) foot = Float.parseFloat(sa[0]); + if (sa.length >= 1) feet = Float.parseFloat(sa[0]); if (sa.length == 2) { value = sa[1]; if (value.indexOf("''") > 0) value = value.substring(0, value.indexOf("''")); if (value.indexOf("\"") > 0) value = value.substring(0, value.indexOf("\"")); inch = Integer.parseInt(value); - foot += inch / 12f; + feet += inch / 12f; } - value = String.format(Locale.US, "%3.1f", foot * 0.3048f); + value = String.format(Locale.US, "%3.1f", feet * 0.3048f); } else if (value.contains("in") || value.contains("\"")) { float inch = 0f; if (value.indexOf("in") > 0) value = value.substring(0, value.indexOf("in")); From 93b13be1d45bcda20c8bc886ed24adb566bcf748 Mon Sep 17 00:00:00 2001 From: quaelnix <122357328+quaelnix@users.noreply.github.com> Date: Sat, 9 Sep 2023 10:17:06 +0200 Subject: [PATCH 039/173] Add gravel profile --- misc/profiles2/gravel.brf | 338 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 misc/profiles2/gravel.brf diff --git a/misc/profiles2/gravel.brf b/misc/profiles2/gravel.brf new file mode 100644 index 0000000..9afd822 --- /dev/null +++ b/misc/profiles2/gravel.brf @@ -0,0 +1,338 @@ +# "gravel.brf" -- Version 09.09.2023 +# This customizeable profile, developed by quaelnix, is designed for gravel cyclists who want to avoid traffic as much +# as possible, but still get to their destination efficiently - taking into account the capabilities of a gravel bike. + +---context:global + +assign turnInstructionRoundabouts true # %turnInstructionRoundabouts% | Special turn-by-turn directions for roundabouts | boolean +assign considerTurnRestrictions true +assign turnInstructionMode 1 # %turnInstructionMode% | Mode for the generated turn-by-turn directions | [0=none, 1=auto-choose, 2=locus-style, 3=osmand-style, 4=comment-style, 5=gpsies-style, 6=oruxmaps-style] + +#assign processUnusedTags true +assign pass1coefficient 4 +assign validForBikes true + +# +++ Kinematic model parameters (travel time computation) +assign totalMass = 90 # %totalMass% | Mass (kg) of the bike + biker | number +assign maxSpeed = 35 # %maxSpeed% | Absolute maximum speed (km/h) | number +assign S_C_x = 0.370 # %S_C_x% | Drag coefficient times reference area (m^2) times half air density (kg/m^3) +assign C_r = 0.005 # %C_r% | Rolling resistance coefficient (dimensionless) +assign bikerPower = 150 # %bikerPower% | Average power (W) provided by the biker | number + +assign consider_traffic_estimate false # %consider_traffic_estimate% | Enable to consider traffic estimates | boolean +assign assume_wet_conditions false # %assume_wet_conditions% | Enable to assume wet conditions | boolean +assign prefer_unpaved_paths false # %prefer_unpaved_paths% | Enable to prefer unpaved paths | boolean +assign avoid_steep_inclines false # %avoid_steep_inclines% | Enable to avoid steep inclines | boolean +assign prefer_cycle_routes false # %prefer_cycle_routes% | Enable to prefer cycle routes | boolean +assign consider_elevation false # %consider_elevation% | Enable to consider elevation | boolean +assign prefer_forests false # %prefer_forests% | Enable to prefer forest areas | boolean +assign prefer_rivers false # %prefer_rivers% | Enable to prefer river valleys | boolean +assign avoid_towns false # %avoid_towns% | Enable to avoid urban areas | boolean +assign avoid_noise false # %avoid_noise% | Enable to avoid noisy areas | boolean + +---context:way + +assign downhillcost switch consider_elevation 40 switch avoid_steep_inclines 80 0.0 +assign downhillcutoff switch and not consider_elevation avoid_steep_inclines 8 1.5 +assign uphillcost switch consider_elevation 80 switch avoid_steep_inclines 160 0.0 +assign uphillcutoff switch and not consider_elevation avoid_steep_inclines 8 1.5 + +assign has_decent_surface surface=asphalt|concrete|paved|paving_stones|fine_gravel|compacted +assign bad_when_steep ( and highway=track|path not ( or tracktype=grade1|grade2 has_decent_surface ) ) + +assign downhillcost switch bad_when_steep ( max 80 downhillcost ) downhillcost +assign downhillcutoff switch bad_when_steep ( min 6 downhillcutoff ) downhillcutoff +assign uphillcost switch bad_when_steep ( max 160 uphillcost ) uphillcost +assign uphillcutoff switch bad_when_steep ( min 6 uphillcutoff ) uphillcutoff + +assign any_cycleway or cycleway=track|lane|shared_lane|shared + or and cycleway:right=track|lane|shared_lane reversedirection= + and cycleway:left=track|lane|shared_lane reversedirection=yes + +assign any_cycleroute or route_bicycle_icn=yes or route_bicycle_ncn=yes or route_bicycle_rcn=yes route_bicycle_lcn=yes + +assign turncost switch junction=roundabout 15 65 + +assign is_main_road highway=primary|primary_link|secondary|secondary_link|tertiary|tertiary_link +assign initialclassifier switch route=ferry 4 switch is_main_road 3 switch footway=crossing 2 1 +assign initialcost switch route=ferry 20000 switch is_main_road 800 switch assume_wet_conditions 100 20 + +assign nobikeaccess not switch bicycle= ( not access=no|private ) ( not bicycle=no|private|dismount|use_sidepath ) +assign nofootaccess not switch foot= ( not access=no|private ) ( not foot=no|private|use_sidepath ) + +assign badoneway + switch not reversedirection=yes oneway=-1 + switch oneway=yes|reversible not oneway:bicycle=no + or junction=roundabout oneway:bicycle=yes + +assign onewaypenalty + switch or ( not badoneway ) ( or cycleway=opposite|opposite_lane|opposite_track oneway:bicycle=no ) 1 + switch not junction=roundabout 5 + 20 + +assign smoothnesspenalty + switch smoothness=excellent switch not assume_wet_conditions 1.0 1.0 + switch smoothness=good switch not assume_wet_conditions 1.1 1.2 + switch smoothness=intermediate switch not assume_wet_conditions 1.3 1.3 + switch smoothness=bad switch not assume_wet_conditions 1.5 2.5 + switch smoothness=very_bad switch not assume_wet_conditions 3.0 4.0 + switch smoothness=horrible switch not assume_wet_conditions 8.0 9.8 + switch smoothness=very_horrible switch not assume_wet_conditions 9.4 100 + switch smoothness=impassable switch not assume_wet_conditions 100 200 + # estimate smoothness off surface + switch not cycleway:surface= + switch cycleway:surface=asphalt switch not assume_wet_conditions 1.1 1.1 + switch cycleway:surface=fine_gravel|compacted switch not assume_wet_conditions 1.2 1.2 + switch cycleway:surface=concrete|paving_stones switch not assume_wet_conditions 1.3 1.3 + switch cycleway:surface=paved switch not assume_wet_conditions 1.4 1.4 + switch cycleway:surface=gravel switch not assume_wet_conditions 1.5 1.5 + switch cycleway:surface=unpaved switch not assume_wet_conditions 1.6 2.0 + switch cycleway:surface=cobblestone|sett switch not assume_wet_conditions 1.7 2.4 + switch not assume_wet_conditions 1.6 1.8 + switch surface=asphalt switch not assume_wet_conditions 1.1 1.1 + switch concrete=plates|lanes switch not assume_wet_conditions 1.2 1.2 + switch surface=fine_gravel|compacted switch not assume_wet_conditions 1.2 1.2 + switch surface=concrete|paving_stones|wood|metal switch not assume_wet_conditions 1.3 1.3 + switch surface=paved switch not assume_wet_conditions 1.4 1.4 + switch surface=gravel switch not assume_wet_conditions 1.5 1.5 + switch surface=cobblestone|sett switch not assume_wet_conditions 1.7 2.5 + switch surface=grass_paver switch not assume_wet_conditions 1.8 2.2 + switch surface=pebblestone switch not assume_wet_conditions 1.9 2.8 + switch highway=track + # estimate smoothness off tracktype + switch tracktype=grade1 switch not assume_wet_conditions 1.2 1.2 + switch tracktype=grade2 switch not assume_wet_conditions 1.4 1.5 + switch tracktype=grade3 switch not assume_wet_conditions 1.6 2.6 + switch tracktype=grade4 switch not assume_wet_conditions 2.6 3.8 + switch tracktype=grade5 switch not assume_wet_conditions 2.8 4.4 + switch not assume_wet_conditions 3.6 6.2 # assume the worst + # estimate smoothness off highway type + switch highway=primary|primary_link|secondary|secondary_link|tertiary|tertiary_link 1.0 + switch highway=service|unclassified|living_street|residential 1.1 + switch highway=cycleway 1.2 + # handle non-specific tags last + switch surface=unpaved switch not assume_wet_conditions 1.6 3.0 + switch surface=ground|grass|dirt|earth|mud|clay|sand switch not assume_wet_conditions 2.6 4.0 + # default smoothness + switch not assume_wet_conditions 1.6 3.2 + +assign footwaypenalty + switch bicycle=yes|designated + switch segregated=yes 1.8 + 2.6 + switch not footway=sidewalk 3.2 + 4.9 + +assign cyclewaypenalty + switch not foot=yes|designated 1.0 + switch segregated=yes 1.2 + 2.4 + +assign pathpenalty + switch bicycle=yes|designated + switch not foot=yes|designated 1.1 + switch segregated=yes 1.8 + 2.4 + switch foot=designated 4.9 + # neither designated foot- nor cycle path + switch lesser smoothnesspenalty 1.6 1.1 + switch not and surface= smoothness= 3.0 + 4.9 + +assign stepspenalty + switch ramp:stroller=yes 75 + switch ramp:bicycle=yes 150 + switch surface=paving_stones 180 + switch not assume_wet_conditions 1000 + 1500 + +assign tracktypepenalty + switch tracktype=grade1 switch not prefer_unpaved_paths 1.0 1.1 + switch tracktype=grade2 switch not prefer_unpaved_paths 1.1 1.0 + switch tracktype=grade3 switch not assume_wet_conditions 1.2 3.0 + switch tracktype=grade4 switch not assume_wet_conditions 1.5 4.0 + switch tracktype=grade5 switch not assume_wet_conditions 1.8 5.0 + switch highway=track switch not assume_wet_conditions 1.3 3.5 + 1.0 + +assign is_paved + and not surface=ground|grass|dirt|earth|mud|clay|sand|pebblestone|gravel|fine_gravel|compacted + or highway=primary|primary_link|secondary|secondary_link|tertiary|tertiary_link + or surface=asphalt|concrete|paved|paving_stones|sett|cobblestone|metal + or highway=road|unclassified|residential|living_street|service + or smoothness=excellent|good + tracktype=grade1 + +assign initialcost + multiply initialcost ( switch and prefer_unpaved_paths is_paved 2 1 ) + +assign smoothnesspenalty + multiply smoothnesspenalty ( switch and prefer_unpaved_paths is_paved 2 1 ) + +assign smoothnesspenalty + max 1 multiply smoothnesspenalty ( switch and prefer_unpaved_paths ( lesser smoothnesspenalty 1.6 ) 0.84 1 ) + +assign hikingpenalty + if sac_scale= then 1.0 else if sac_scale=hiking then 2.5 else 10000 + +assign mtbpenalty + if mtb:scale= then 1.0 else if mtb:scale=0- then 1.2 else + if mtb:scale=0 then 1.5 else if mtb:scale=0+ then 3.0 else 9.0 + +assign notcycleroutepenalty switch any_cycleroute 1.0 switch not prefer_cycle_routes 1.03 1.85 +assign notcyclewaypenalty switch bicycle_road=yes 1.0 switch any_cycleway 1.02 1.06 + +assign maxspeedpenalty + switch maxspeed=50 1.0 + switch maxspeed=60 1.1 + switch maxspeed=70 1.3 + switch maxspeed=80 1.4 + switch maxspeed=90 1.5 + switch maxspeed=100 1.6 1 + +assign trafficpenalty + switch consider_traffic_estimate + switch estimated_traffic_class= 1.0 + switch estimated_traffic_class=1|2 1.1 + switch estimated_traffic_class=3 1.3 + switch estimated_traffic_class=4 2.0 + switch estimated_traffic_class=5 2.5 + switch estimated_traffic_class=6|7 3.0 1 1 + +assign noisepenalty + switch avoid_noise + switch estimated_noise_class= 1.0 + switch estimated_noise_class=1 1.2 + switch estimated_noise_class=2 1.3 + switch estimated_noise_class=3 1.5 + switch estimated_noise_class=4 1.8 + switch estimated_noise_class=5 2.0 + switch estimated_noise_class=6 2.2 1 1 + +assign noriverpenalty + switch prefer_rivers + switch estimated_river_class= 3.15 + switch estimated_river_class=1 3.00 + switch estimated_river_class=2 1.50 + switch estimated_river_class=3 1.15 + switch estimated_river_class=4 1.10 + switch estimated_river_class=5 1.05 + switch estimated_river_class=6 1.00 1 1 + +assign noforestpenalty + switch prefer_forests + switch estimated_forest_class= 2.5 + switch estimated_forest_class=1 2.2 + switch estimated_forest_class=2 2.1 + switch estimated_forest_class=3 2.0 + switch estimated_forest_class=4 1.8 + switch estimated_forest_class=5 1.4 + switch estimated_forest_class=6 1.0 1 1 + +assign townpenalty + switch avoid_towns + switch estimated_town_class= 1.0 + switch estimated_town_class=1 1.2 + switch estimated_town_class=2 1.4 + switch estimated_town_class=3 1.6 + switch estimated_town_class=4 1.8 + switch estimated_town_class=5 2.0 + switch estimated_town_class=6 2.2 1 1 + +assign costfactor + multiply notcycleroutepenalty multiply noriverpenalty + multiply notcyclewaypenalty multiply maxspeedpenalty + multiply smoothnesspenalty multiply onewaypenalty + multiply noforestpenalty multiply noisepenalty + multiply trafficpenalty multiply townpenalty + multiply hikingpenalty multiply mtbpenalty + multiply tracktypepenalty + switch or highway=motorway|motorway_link motorroad=yes 10000 + switch and nobikeaccess nofootaccess 10000 + switch highway= switch not route=ferry 10000 2 + switch highway=trunk|trunk_link switch any_cycleroute 20 80 + switch highway=bridleway|raceway switch not bicycle=yes 20 5 + switch highway=pedestrian switch not bicycle=yes 8 3 + switch or vehicle=no|private nobikeaccess 6.0 + switch highway=primary|primary_link 4.8 + switch highway=secondary|secondary_link 4.2 + switch highway=tertiary|tertiary_link 3.6 + switch highway=corridor 3.2 + switch highway=road 2.8 + switch highway=unclassified switch not any_cycleroute 2.6 1.4 + switch highway=living_street 2.5 + switch highway=service switch not service=parking_aisle 2.4 3.0 + switch highway=residential switch not maxspeed=30 2.2 1.8 + switch highway=track 1.0 + switch highway=path pathpenalty + switch highway=steps stepspenalty + switch highway=footway footwaypenalty + switch highway=cycleway cyclewaypenalty + 10000 + +# 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 23 + else if ( highway=primary_link ) then 23 + else if ( highway=secondary ) then 22 + else if ( highway=secondary_link ) then 22 + else if ( highway=tertiary ) then 21 + else if ( highway=tertiary_link ) then 21 + else if ( highway=residential|living_street ) then 15 + else if ( highway=unclassified ) then 8 + else if ( highway=service ) then 8 + else if ( highway=cycleway ) then 8 + else if ( bicycle=designated ) then 8 + else if ( highway=track|road|path ) + then if or surface=asphalt|paved|concrete|wood|metal tracktype=grade1 then 8 else 6 + 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 highway=residential|living_street|service then true + else if ( and highway=track tracktype=grade1 ) then true + else false + +# ... encoded into a bitmask + +assign classifiermask +# add isbadoneway # no voice hint if 1 of the 2 possibilities is badoneway + add multiply isgoodoneway 2 + add multiply isroundabout 4 + add multiply islinktype 8 + multiply isgoodforcars 16 + +---context:node + +assign nobikeaccess not switch bicycle= ( not access=no|private ) ( not bicycle=no|private|dismount ) +assign nofootaccess not switch foot= ( not access=no|private ) ( not foot=no|private ) + +assign barrierpenalty + switch barrier= 0 + switch barrier=block|bollard 25 + switch barrier=gate|swing_gate 50 + switch barrier=cycle_barrier 87 + 139 + +assign initialcost + add barrierpenalty + switch and nobikeaccess nofootaccess 1000000 + switch railway=level_crossing 300 + switch nobikeaccess 200 + switch railway=crossing 35 + switch not traffic_calming= 25 + switch highway=traffic_signals|crossing 20 + 0 From 0177b8fea46da0e66c7d4612a07c7ed7d1110901 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Tue, 19 Sep 2023 14:55:59 +0200 Subject: [PATCH 040/173] `bundle update` docs dependencies --- docs/Gemfile.lock | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index e50be30..96e2833 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,19 +1,20 @@ GEM remote: https://rubygems.org/ specs: - activesupport (7.0.4.3) + activesupport (7.0.8) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.4) + addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) + base64 (0.1.1) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.11.1) colorator (1.1.0) - commonmarker (0.23.9) + commonmarker (0.23.10) concurrent-ruby (1.2.2) dnsruby (1.70.0) simpleidn (~> 0.2.1) @@ -23,8 +24,9 @@ GEM ethon (0.16.0) ffi (>= 1.15.0) eventmachine (1.2.7) - execjs (2.8.1) - faraday (2.7.4) + execjs (2.9.1) + faraday (2.7.11) + base64 faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) @@ -86,7 +88,7 @@ GEM activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.8.0) - i18n (1.13.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) jekyll (3.9.3) addressable (~> 2.4) @@ -209,8 +211,8 @@ GEM jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.18.0) - nokogiri (1.15.0-x86_64-linux) + minitest (5.20.0) + nokogiri (1.15.4-x86_64-linux) racc (~> 1.4) octokit (4.25.1) faraday (>= 1, < 3) @@ -218,11 +220,11 @@ GEM pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (4.0.7) - racc (1.6.2) + racc (1.7.1) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rexml (3.2.5) + rexml (3.2.6) rouge (3.26.0) ruby2_keywords (0.0.5) rubyzip (2.3.2) @@ -258,4 +260,4 @@ DEPENDENCIES wdm (~> 0.1.1) BUNDLED WITH - 2.4.12 + 2.4.13 From 90fbe8345a5c16fc2857b08e922a20b62974c230 Mon Sep 17 00:00:00 2001 From: Stapawe <46088164+Stapawe@users.noreply.github.com> Date: Sun, 24 Sep 2023 09:08:07 +0100 Subject: [PATCH 041/173] environmental_considerations_and_pseudo_tags.md (#612) Document the new pseudo tag logic --- ...onmental_considerations_and_pseudo_tags.md | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 docs/developers/environmental_considerations_and_pseudo_tags.md diff --git a/docs/developers/environmental_considerations_and_pseudo_tags.md b/docs/developers/environmental_considerations_and_pseudo_tags.md new file mode 100644 index 0000000..84a3344 --- /dev/null +++ b/docs/developers/environmental_considerations_and_pseudo_tags.md @@ -0,0 +1,87 @@ +Environmental considerations (penalties for traffic, noise, town, no river, no forest) are possible due to the creation of pseudo tags during processing OSM data by spatial SQL queries in https://github.com/abrensch/brouter/blob/master/misc/scripts/mapcreation/brouter.sql. During this processing, roads are extended by a 32 m buffer creating 64 m wide lines. Then it is calculated what percentage of such line is at a specific distance to a noise source or within a forest, for example. The percentage is converted to a factor and the factor is assigned to a class. Ways that pass through different environments and are represented by a single OSM way can be problematic because the class is always based on the average environment along an entire OSM way. For traffic, calculations are on another level of complexity. + +### consider_noise, noise_penalty +For proximity of noisy roads (secondary and higher). The noise factor represents the proportion of a road's buffer area that lies within the 64-meter buffer of noisy roads. This proportion is reduced: +- for motorways and trunk roads with max speed < 105 by 1.5 +- for primary roads 2 times +- 3 times if maxspeed is 75 - 105 for primary and secondary +- other secondary roads 5 times + +Noise class is roughly proportional to the noise factor: + +noise_factor = noise class +- < 0.1 = '1' +- < 0.25 = '2' +- < 0.4 = '3' +- < 0.55 = '4' +- < 0.8 = '5' +- ELSE = '6' + +To be classified as noise class 6, a way must be less than 13 m on average from the middle of the carriageway of a motorway with a maximum speed exceeding 105. For a class 5, the distance must be up to 35 meters. (1 - noise factor) * 64 m for a given class determines the distance + +**Max noise class:** +| Max speed | Motorway, trunk |Primary|Secondary | +|--- |:---: |:---: |:---: | +| >105 |6 |4 | 3 | +| 105 |5 |4 |3 | +| 75 |5 |3 |2 | + + +### consider_river, no_river_penalty +OSM data recognized as river: +- waterway: river, canal +- natural: water (except wastewater) + +Waterways have 32 m wide buffers. Water areas have 77 m wide buffers. + +river_see = river class +- < 0.17 = '1' +- < 0.35 = '2' +- < 0.57 = '3' +- < 0.80 = '4' +- < 0.95 = '5' +- ELSE = '6' + +### consider_forest, no_forest_penalty +OSM data recognized as forest: +- landuse: forest, allotments, flowerbed, orchard, vineyard, recreation_ground, village_green +- leisure: garden, park, nature_reserve + +No forest buffers are used. + +Imagine you trace the way with a pencil drawing lines 62 meters wide. Then estimated_forest_class=6 corresponds to the case that at least 98% of the line is in the woodland. This number is called a green factor. + +green_factor = forest class +- < 0.1 = NULL +- < 0.2 = '1' +- < 0.4 = '2' +- < 0.6 = '3' +- < 0.8 = '4' +- < 0.98 = '5' +- ELSE = '6' + + + +### consider_town, town_penalty +Town_class is determined by population data from OSM. + +Class +- 1 = 50-80 k people +- 2 = 80-150 k people +- 3 = 150 - 400 k people +- 4 = 400 - 1,000 k people +- 5 = 1 - 2 million people +- 6 = > 2 million people + +### consider_traffic, traffic_penalty +(modified copy from the sql file). +OSM data used to estimate the traffic: +- population of towns (+ distance from position to the towns) +- size of industrial areas (landuse=industrial) and distance to them. Not considered: solar & wind farms. +- airports international +- motorway and trunk road density - traffic on motorways decreases traffic on primary/secondary/tertiary. Calculated on a grid (100 km^2) +- density of highways (tertiary and higher) calculated on a grid (100 km^2). Traffic decreases when more such roads are available. Exceptions: near junctions between motorways and other roads the traffic increases on these roads. +- mountain-ranges calculated as density of peaks > 400 m traffic is generally on highways in such regions higher as only generated by the local population or industrial areas +- calculate traffic from the population (for each segment of type primary secondary tertiary) +- SUM of (population of each town < 100 km) / ( town-radius + 2500 + dist(segment-position to the town) ** 2 ) +- town-radius is calculated as sqrt(population) From 42f0ac662738097a70ae5ac2bf97cfd99040b2f6 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 30 Sep 2023 11:01:21 +0200 Subject: [PATCH 042/173] added app specific vars --- .../main/java/btools/router/RoutingParamCollector.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/brouter-core/src/main/java/btools/router/RoutingParamCollector.java b/brouter-core/src/main/java/btools/router/RoutingParamCollector.java index adc08fe..38f6aaf 100644 --- a/brouter-core/src/main/java/btools/router/RoutingParamCollector.java +++ b/brouter-core/src/main/java/btools/router/RoutingParamCollector.java @@ -196,6 +196,12 @@ public class RoutingParamCollector { rctx.turnInstructionMode = Integer.parseInt(value); } else if (key.equals("timode")) { rctx.turnInstructionMode = Integer.parseInt(value); + } else if (key.equals("turnInstructionFormat")) { + if ("osmand".equalsIgnoreCase(value)) { + rctx.turnInstructionMode = 3; + } else if ("locus".equalsIgnoreCase(value)) { + rctx.turnInstructionMode = 2; + } } else if (key.equals("exportWaypoints")) { rctx.exportWaypoints = (Integer.parseInt(value) == 1); } else if (key.equals("format")) { @@ -305,6 +311,9 @@ public class RoutingParamCollector { public List readNogos(String nogoLons, String nogoLats, String nogoRadi) { if (nogoLons == null || nogoLats == null || nogoRadi == null) return null; List nogoList = new ArrayList<>(); + nogoLons = nogoLons.replace("[", "").replace("]", ""); + nogoLats = nogoLats.replace("[", "").replace("]", ""); + nogoRadi = nogoRadi.replace("[", "").replace("]", ""); String[] lons = nogoLons.split(","); String[] lats = nogoLats.split(","); From fe08674632f4cd9d7c4be98c97109c7cfcb6406a Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 30 Sep 2023 11:04:34 +0200 Subject: [PATCH 043/173] added test if nogo array exists --- .../java/btools/router/RoutingParamCollector.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/RoutingParamCollector.java b/brouter-core/src/main/java/btools/router/RoutingParamCollector.java index 38f6aaf..75a3135 100644 --- a/brouter-core/src/main/java/btools/router/RoutingParamCollector.java +++ b/brouter-core/src/main/java/btools/router/RoutingParamCollector.java @@ -133,7 +133,11 @@ public class RoutingParamCollector { List nogoList = readNogos(params.get("nogoLons"), params.get("nogoLats"), params.get("nogoRadi")); if (nogoList != null) { RoutingContext.prepareNogoPoints(nogoList); - rctx.nogopoints = nogoList; + if (rctx.nogopoints == null) { + rctx.nogopoints = nogoList; + } else { + rctx.nogopoints.addAll(nogoList); + } } params.remove("nogoLats"); params.remove("nogoLons"); @@ -143,7 +147,11 @@ public class RoutingParamCollector { List nogoList = readNogoList(params.get("nogos")); if (nogoList != null) { RoutingContext.prepareNogoPoints(nogoList); - rctx.nogopoints = nogoList; + if (rctx.nogopoints == null) { + rctx.nogopoints = nogoList; + } else { + rctx.nogopoints.addAll(nogoList); + } } params.remove("nogos"); } From 3acb0b1fdbb82db9242c13fca4021206b360a212 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 30 Sep 2023 11:13:57 +0200 Subject: [PATCH 044/173] updated doc entries --- .../btools/router/RoutingParamCollector.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/RoutingParamCollector.java b/brouter-core/src/main/java/btools/router/RoutingParamCollector.java index 75a3135..fde32c6 100644 --- a/brouter-core/src/main/java/btools/router/RoutingParamCollector.java +++ b/brouter-core/src/main/java/btools/router/RoutingParamCollector.java @@ -14,8 +14,9 @@ public class RoutingParamCollector { /** * get a list of points and optional extra info for the points - * @param lonLats - linked list separated by ';' or '|' - * @return - a list + * + * @param lonLats linked list separated by ';' or '|' + * @return a list */ public List getWayPointList(String lonLats) { if (lonLats == null) throw new IllegalArgumentException("lonlats parameter not set"); @@ -49,9 +50,10 @@ public class RoutingParamCollector { /** * get a list of points (old style, positions only) - * @param lons - array with longitudes - * @param lats - array with latitudes - * @return - a list + * + * @param lons array with longitudes + * @param lats array with latitudes + * @return a list */ public List readPositions(double[] lons, double[] lats) { List wplist = new ArrayList<>(); @@ -93,9 +95,10 @@ public class RoutingParamCollector { /** * read a url like parameter list linked with '&' - * @param url - parameter list - * @return - a hashmap of the parameter - * @throws UnsupportedEncodingException + * + * @param url parameter list + * @return a hashmap of the parameter + * @throws UnsupportedEncodingException */ public Map getUrlParams(String url) throws UnsupportedEncodingException { HashMap params = new HashMap<>(); @@ -117,9 +120,10 @@ public class RoutingParamCollector { /** * fill a parameter map into the routing context - * @param rctx - the context - * @param wplist - the list of way points needed for 'straight' parameter - * @param params - the list of parameters + * + * @param rctx the context + * @param wplist the list of way points needed for 'straight' parameter + * @param params the list of parameters */ public void setParams(RoutingContext rctx, List wplist, Map params) { if (params != null) { @@ -227,8 +231,9 @@ public class RoutingParamCollector { /** * fill profile parameter list - * @param rctx - the routing context - * @param params - the list of parameters + * + * @param rctx the routing context + * @param params the list of parameters */ public void setProfileParams(RoutingContext rctx, Map params) { if (params != null) { From 298893352c196690e8621b739bc94d745d72e4b6 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 30 Sep 2023 17:15:15 +0200 Subject: [PATCH 045/173] added param collector --- .../java/btools/routingapp/BRouterWorker.java | 90 +++++++++++++++++-- 1 file changed, 81 insertions(+), 9 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 7e37818..87cda71 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java @@ -12,10 +12,10 @@ import java.util.List; import java.util.StringTokenizer; import btools.router.OsmNodeNamed; -import btools.router.OsmNogoPolygon; import btools.router.OsmTrack; import btools.router.RoutingContext; import btools.router.RoutingEngine; +import btools.router.RoutingParamCollector; public class BRouterWorker { private static final int OUTPUT_FORMAT_GPX = 0; @@ -59,6 +59,42 @@ public class BRouterWorker { rc.rawTrackPath = rawTrackPath; rc.localFunction = profilePath; + RoutingParamCollector routingParamCollector = new RoutingParamCollector(); + + // parameter pre control + if (params.containsKey("lonlats")) { + waypoints = routingParamCollector.getWayPointList(params.getString("lonlats")); + params.remove("lonlats"); + } + if (params.containsKey("lats")) { + double[] lats = params.getDoubleArray("lats"); + double[] lons = params.getDoubleArray("lons"); + waypoints = routingParamCollector.readPositions(lons, lats); + params.remove("lons"); + params.remove("lats"); + } + + if (waypoints == null) { + throw new IllegalArgumentException("no points!"); + } + if (engineMode == 0) { + if (waypoints.size() < 2) { + throw new IllegalArgumentException("we need two lat/lon points at least!"); + } + } else { + if (waypoints.size() < 1) { + throw new IllegalArgumentException("we need two lat/lon points at least!"); + } + } + + if (nogoList != null && nogoList.size() > 0) { + // forward already read nogos from filesystem + if (rc.nogopoints == null) { + rc.nogopoints = nogoList; + } else { + rc.nogopoints.addAll(nogoList); + } + String tiFormat = params.getString("turnInstructionFormat"); if (tiFormat != null) { if ("osmand".equalsIgnoreCase(tiFormat)) { @@ -147,6 +183,45 @@ public class BRouterWorker { } } + Map theParams = new HashMap<>(); + for (String key : params.keySet()) { + Object value = params.get(key); + if (value instanceof double[]) { + theParams.put(key, Arrays.toString(params.getDoubleArray(key))); + } else { + theParams.put(key, value.toString()); + } + } + routingParamCollector.setParams(rc, waypoints, theParams); + + if (params.containsKey("extraParams")) { + Map profileparams = null; + try { + profileparams = routingParamCollector.getUrlParams(params.getString("extraParams")); + routingParamCollector.setProfileParams(rc, profileparams); + } catch (UnsupportedEncodingException e) { + // ignore + } + } + + + String pathToFileResult = params.getString("pathToFileResult"); + + if (pathToFileResult != null) { + File f = new File(pathToFileResult); + File dir = f.getParentFile(); + if (!dir.exists() || !dir.canWrite()) { + return "file folder does not exists or can not be written!"; + } + } + + long maxRunningTime = 60000; + String sMaxRunningTime = params.getString("maxRunningTime"); + if (sMaxRunningTime != null) { + maxRunningTime = Integer.parseInt(sMaxRunningTime) * 1000L; + } + + try { writeTimeoutData(rc); } catch (Exception e) { @@ -170,18 +245,15 @@ public class BRouterWorker { return cr.getErrorMessage(); } - String format = params.getString("trackFormat"); int writeFromat = OUTPUT_FORMAT_GPX; - if (format != null) { - if ("kml".equals(format)) writeFromat = OUTPUT_FORMAT_KML; - if ("json".equals(format)) writeFromat = OUTPUT_FORMAT_JSON; + if (rc.outputFormat != null) { + if ("kml".equals(rc.outputFormat)) writeFromat = OUTPUT_FORMAT_KML; + if ("json".equals(rc.outputFormat)) writeFromat = OUTPUT_FORMAT_JSON; } OsmTrack track = cr.getFoundTrack(); if (track != null) { - if (params.containsKey("exportWaypoints")) { - track.exportWaypoints = (params.getInt("exportWaypoints", 0) == 1); - } + track.exportWaypoints = rc.exportWaypoints; if (pathToFileResult == null) { switch (writeFromat) { case OUTPUT_FORMAT_GPX: @@ -431,7 +503,7 @@ public class BRouterWorker { bw.write(rc.rawTrackPath); bw.write("\n"); writeWPList(bw, waypoints); - writeWPList(bw, nogoList); + writeWPList(bw, rc.nogopoints); bw.close(); } From 48c8c3edd1247b8bba7c16f6d9353441eb38a72b Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 30 Sep 2023 17:17:01 +0200 Subject: [PATCH 046/173] removed old param handling --- .../java/btools/routingapp/BRouterWorker.java | 305 +----------------- 1 file changed, 3 insertions(+), 302 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 87cda71..01423cc 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java @@ -6,10 +6,11 @@ import android.os.Bundle; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; -import java.util.ArrayList; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; import java.util.HashMap; import java.util.List; -import java.util.StringTokenizer; +import java.util.Map; import btools.router.OsmNodeNamed; import btools.router.OsmTrack; @@ -39,22 +40,6 @@ public class BRouterWorker { engineMode = params.getInt("engineMode", 0); } - String pathToFileResult = params.getString("pathToFileResult"); - - if (pathToFileResult != null) { - File f = new File(pathToFileResult); - File dir = f.getParentFile(); - if (!dir.exists() || !dir.canWrite()) { - return "file folder does not exists or can not be written!"; - } - } - - long maxRunningTime = 60000; - String sMaxRunningTime = params.getString("maxRunningTime"); - if (sMaxRunningTime != null) { - maxRunningTime = Integer.parseInt(sMaxRunningTime) * 1000; - } - RoutingContext rc = new RoutingContext(); rc.rawTrackPath = rawTrackPath; rc.localFunction = profilePath; @@ -95,92 +80,6 @@ public class BRouterWorker { rc.nogopoints.addAll(nogoList); } - String tiFormat = params.getString("turnInstructionFormat"); - if (tiFormat != null) { - if ("osmand".equalsIgnoreCase(tiFormat)) { - rc.turnInstructionMode = 3; - } else if ("locus".equalsIgnoreCase(tiFormat)) { - rc.turnInstructionMode = 2; - } - } - if (params.containsKey("timode")) { - rc.turnInstructionMode = params.getInt("timode"); - } - - if (params.containsKey("direction")) { - rc.startDirection = params.getInt("direction"); - } - if (params.containsKey("heading")) { - rc.startDirection = params.getInt("heading"); - rc.forceUseStartDirection = true; - } - if (params.containsKey("alternativeidx")) { - rc.alternativeIdx = params.getInt("alternativeidx"); - } - - readNogos(params); // add interface provided nogos - if (nogoList != null) { - RoutingContext.prepareNogoPoints(nogoList); - if (rc.nogopoints == null) { - rc.nogopoints = nogoList; - } else { - rc.nogopoints.addAll(nogoList); - } - } - if (rc.nogopoints == null) { - rc.nogopoints = nogoPolygonsList; - } else if (nogoPolygonsList != null) { - rc.nogopoints.addAll(nogoPolygonsList); - } - List poisList = readPoisList(params); - rc.poipoints = poisList; - - if (params.containsKey("lats")) { - waypoints = readPositions(params); - } - if (params.containsKey("lonlats")) { - waypoints = readLonlats(params, engineMode); - } - - if (waypoints == null) return "no pts "; - - if (params.containsKey("straight")) { - try { - String straight = params.getString("straight"); - String[] sa = straight.split(","); - for (int i = 0; i < sa.length; i++) { - int v = Integer.parseInt(sa[i]); - if (waypoints.size() > v) waypoints.get(v).direct = true; - } - } catch (NumberFormatException e) { - } - } - - String extraParams = null; - if (params.containsKey("extraParams")) { // add user params - extraParams = params.getString("extraParams"); - } - if (extraParams != null && this.profileParams != null) { - // don't overwrite incoming values - extraParams = this.profileParams + "&" + extraParams; - } else if (this.profileParams != null) { - extraParams = this.profileParams; - } - - if (params.containsKey("extraParams")) { // add user params - if (rc.keyValues == null) rc.keyValues = new HashMap<>(); - StringTokenizer tk = new StringTokenizer(extraParams, "?&"); - while (tk.hasMoreTokens()) { - String t = tk.nextToken(); - StringTokenizer tk2 = new StringTokenizer(t, "="); - if (tk2.hasMoreTokens()) { - String key = tk2.nextToken(); - if (tk2.hasMoreTokens()) { - String value = tk2.nextToken(); - rc.keyValues.put(key, value); - } - } - } } Map theParams = new HashMap<>(); @@ -295,204 +194,6 @@ public class BRouterWorker { return null; } - private List readPositions(Bundle params) { - List wplist = new ArrayList<>(); - - double[] lats = params.getDoubleArray("lats"); - double[] lons = params.getDoubleArray("lons"); - - if (lats == null || lats.length < 2 || lons == null || lons.length < 2) { - throw new IllegalArgumentException("we need two lat/lon points at least!"); - } - - for (int i = 0; i < lats.length && i < lons.length; i++) { - OsmNodeNamed n = new OsmNodeNamed(); - n.name = "via" + i; - n.ilon = (int) ((lons[i] + 180.) * 1000000. + 0.5); - n.ilat = (int) ((lats[i] + 90.) * 1000000. + 0.5); - wplist.add(n); - } - if (wplist.get(0).name.startsWith("via")) wplist.get(0).name = "from"; - if (wplist.get(wplist.size() - 1).name.startsWith("via")) - wplist.get(wplist.size() - 1).name = "to"; - - return wplist; - } - - private List readLonlats(Bundle params, int mode) { - List wplist = new ArrayList<>(); - - String lonLats = params.getString("lonlats"); - if (lonLats == null) throw new IllegalArgumentException("lonlats parameter not set"); - - String[] coords; - if (mode == 0) { - coords = lonLats.split("\\|"); - if (coords.length < 2) - throw new IllegalArgumentException("we need two lat/lon points at least!"); - } else { - coords = new String[1]; - coords[0] = lonLats; - } - for (int i = 0; i < coords.length; i++) { - String[] lonLat = coords[i].split(","); - if (lonLat.length < 2) - throw new IllegalArgumentException("we need a lat and lon point at least!"); - wplist.add(readPosition(lonLat[0], lonLat[1], "via" + i)); - if (lonLat.length > 2) { - if (lonLat[2].equals("d")) { - wplist.get(wplist.size() - 1).direct = true; - } else { - wplist.get(wplist.size() - 1).name = lonLat[2]; - } - } - } - - if (wplist.get(0).name.startsWith("via")) wplist.get(0).name = "from"; - if (wplist.get(wplist.size() - 1).name.startsWith("via")) - wplist.get(wplist.size() - 1).name = "to"; - - return wplist; - } - - private static OsmNodeNamed readPosition(String vlon, String vlat, String name) { - if (vlon == null) throw new IllegalArgumentException("lon " + name + " not found in input"); - if (vlat == null) throw new IllegalArgumentException("lat " + name + " not found in input"); - - return readPosition(Double.parseDouble(vlon), Double.parseDouble(vlat), name); - } - - private static OsmNodeNamed readPosition(double lon, double lat, String name) { - OsmNodeNamed n = new OsmNodeNamed(); - n.name = name; - n.ilon = (int) ((lon + 180.) * 1000000. + 0.5); - n.ilat = (int) ((lat + 90.) * 1000000. + 0.5); - return n; - } - - - private void readNogos(Bundle params) { - if (params.containsKey("nogoLats")) { - double[] lats = params.getDoubleArray("nogoLats"); - double[] lons = params.getDoubleArray("nogoLons"); - double[] radi = params.getDoubleArray("nogoRadi"); - - if (lats == null || lons == null || radi == null) return; - - for (int i = 0; i < lats.length && i < lons.length && i < radi.length; i++) { - OsmNodeNamed n = new OsmNodeNamed(); - n.name = "nogo" + (int) radi[i]; - n.ilon = (int) ((lons[i] + 180.) * 1000000. + 0.5); - n.ilat = (int) ((lats[i] + 90.) * 1000000. + 0.5); - n.isNogo = true; - n.nogoWeight = Double.NaN; - AppLogger.log("added interface provided nogo: " + n); - nogoList.add(n); - } - } - if (params.containsKey("nogos")) { - nogoList = readNogoList(params); - } - if (params.containsKey("polylines") || - params.containsKey("polygons")) { - nogoPolygonsList = readNogoPolygons(params); - } - } - - private List readNogoList(Bundle params) { - // lon,lat,radius|... - String nogos = params.getString("nogos"); - if (nogos == null) return null; - - String[] lonLatRadList = nogos.split("\\|"); - - List nogoList = new ArrayList<>(); - for (int i = 0; i < lonLatRadList.length; i++) { - String[] lonLatRad = lonLatRadList[i].split(","); - String nogoWeight = "NaN"; - if (lonLatRad.length > 3) { - nogoWeight = lonLatRad[3]; - } - nogoList.add(readNogo(lonLatRad[0], lonLatRad[1], lonLatRad[2], nogoWeight)); - } - - return nogoList; - } - - private static OsmNodeNamed readNogo(String lon, String lat, String radius, String nogoWeight) { - double weight = "undefined".equals(nogoWeight) ? Double.NaN : Double.parseDouble(nogoWeight); - return readNogo(Double.parseDouble(lon), Double.parseDouble(lat), Integer.parseInt(radius), weight); - } - - private static OsmNodeNamed readNogo(double lon, double lat, int radius, double nogoWeight) { - OsmNodeNamed n = new OsmNodeNamed(); - n.name = "nogo" + radius; - n.ilon = (int) ((lon + 180.) * 1000000. + 0.5); - n.ilat = (int) ((lat + 90.) * 1000000. + 0.5); - n.isNogo = true; - n.nogoWeight = nogoWeight; - return n; - } - - private List readNogoPolygons(Bundle params) { - List result = new ArrayList<>(); - parseNogoPolygons(params.getString("polylines"), result, false); - parseNogoPolygons(params.getString("polygons"), result, true); - return result.size() > 0 ? result : null; - } - - private static void parseNogoPolygons(String polygons, List result, boolean closed) { - if (polygons != null) { - String[] polygonList = polygons.split("\\|"); - for (int i = 0; i < polygonList.length; i++) { - String[] lonLatList = polygonList[i].split(","); - if (lonLatList.length > 1) { - OsmNogoPolygon polygon = new OsmNogoPolygon(closed); - polygon.name = "nogo" + i; - int j; - for (j = 0; j < 2 * (lonLatList.length / 2) - 1; ) { - String slon = lonLatList[j++]; - String slat = lonLatList[j++]; - int lon = (int) ((Double.parseDouble(slon) + 180.) * 1000000. + 0.5); - int lat = (int) ((Double.parseDouble(slat) + 90.) * 1000000. + 0.5); - polygon.addVertex(lon, lat); - } - - String nogoWeight = "NaN"; - if (j < lonLatList.length) { - nogoWeight = lonLatList[j]; - } - polygon.nogoWeight = Double.parseDouble(nogoWeight); - - if (polygon.points.size() > 0) { - polygon.calcBoundingCircle(); - result.add(polygon); - } - } - } - } - } - - private List readPoisList(Bundle params) { - // lon,lat,name|... - String pois = params.getString("pois"); - if (pois == null) return null; - - String[] lonLatNameList = pois.split("\\|"); - - List poisList = new ArrayList<>(); - for (int i = 0; i < lonLatNameList.length; i++) { - String[] lonLatName = lonLatNameList[i].split(","); - - OsmNodeNamed n = new OsmNodeNamed(); - n.ilon = (int) ((Double.parseDouble(lonLatName[0]) + 180.) * 1000000. + 0.5); - n.ilat = (int) ((Double.parseDouble(lonLatName[1]) + 90.) * 1000000. + 0.5); - n.name = lonLatName[2]; - poisList.add(n); - } - - return poisList; - } private void writeTimeoutData(RoutingContext rc) throws Exception { String timeoutFile = baseDir + "/brouter/modes/timeoutdata.txt"; From 8d4012211e4601c180a584c0912d881d04fddaf6 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 30 Sep 2023 17:20:33 +0200 Subject: [PATCH 047/173] changed debug logging --- .../java/btools/routingapp/BRouterService.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java index 95dbadd..8d6ea4e 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java @@ -19,6 +19,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.zip.GZIPOutputStream; @@ -39,10 +40,6 @@ public class BRouterService extends Service { BRouterWorker worker = new BRouterWorker(); - for (String key : params.keySet()) { - // Log.d("BS", "income " + key + " = " + params.get(key)); - } - int engineMode = 0; if (params.containsKey("engineMode")) { engineMode = params.getInt("engineMode", 0); @@ -277,8 +274,13 @@ public class BRouterService extends Service { private void logBundle(Bundle params) { if (AppLogger.isLogging()) { for (String k : params.keySet()) { - Object val = "remoteProfile".equals(k) ? "<..cut..>" : params.getString(k); - String desc = "key=" + k + (val == null ? "" : " class=" + val.getClass() + " val=" + val.toString()); + Object val = "remoteProfile".equals(k) ? "<..cut..>" : params.get(k); + String desc = "key=" + k + (val == null ? "" : " class=" + val.getClass() + " val="); + if (val instanceof double[]) { + desc += Arrays.toString(params.getDoubleArray(k)); + } else { + desc += val.toString(); + } AppLogger.log(desc); } } From 90cc045404ddbb7b296025e1fba815ed6e57a64c Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 5 Oct 2023 12:48:06 +0200 Subject: [PATCH 048/173] moved string control to app worker --- .../src/main/java/btools/router/RoutingParamCollector.java | 5 +---- .../src/main/java/btools/routingapp/BRouterWorker.java | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/RoutingParamCollector.java b/brouter-core/src/main/java/btools/router/RoutingParamCollector.java index fde32c6..99f5b3b 100644 --- a/brouter-core/src/main/java/btools/router/RoutingParamCollector.java +++ b/brouter-core/src/main/java/btools/router/RoutingParamCollector.java @@ -133,7 +133,7 @@ public class RoutingParamCollector { if (params.containsKey("profile")) { rctx.localFunction = params.get("profile"); } - if (params.containsKey("nogoLats")) { + if (params.containsKey("nogoLats") && params.get("nogoLats").length() > 0) { List nogoList = readNogos(params.get("nogoLons"), params.get("nogoLats"), params.get("nogoRadi")); if (nogoList != null) { RoutingContext.prepareNogoPoints(nogoList); @@ -324,9 +324,6 @@ public class RoutingParamCollector { public List readNogos(String nogoLons, String nogoLats, String nogoRadi) { if (nogoLons == null || nogoLats == null || nogoRadi == null) return null; List nogoList = new ArrayList<>(); - nogoLons = nogoLons.replace("[", "").replace("]", ""); - nogoLats = nogoLats.replace("[", "").replace("]", ""); - nogoRadi = nogoRadi.replace("[", "").replace("]", ""); String[] lons = nogoLons.split(","); String[] lats = nogoLats.split(","); 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 01423cc..5823db5 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java @@ -86,7 +86,9 @@ public class BRouterWorker { for (String key : params.keySet()) { Object value = params.get(key); if (value instanceof double[]) { - theParams.put(key, Arrays.toString(params.getDoubleArray(key))); + String s = Arrays.toString(params.getDoubleArray(key)); + s = s.replace("[", "").replace("]", ""); + theParams.put(key, s); } else { theParams.put(key, value.toString()); } From c07454a8ba5511be4f195b5b7dc4eabb9d484e21 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 6 Oct 2023 14:57:46 +0200 Subject: [PATCH 049/173] enable new post process on voicehints --- .../main/java/btools/router/VoiceHint.java | 1 + .../btools/router/VoiceHintProcessor.java | 114 ++++++++++-------- 2 files changed, 63 insertions(+), 52 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/VoiceHint.java b/brouter-core/src/main/java/btools/router/VoiceHint.java index 2f858be..df39933 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHint.java +++ b/brouter-core/src/main/java/btools/router/VoiceHint.java @@ -44,6 +44,7 @@ public class VoiceHint { float angle = Float.MAX_VALUE; boolean turnAngleConsumed; boolean needsRealTurn; + int maxBadPrio; int roundaboutExit; diff --git a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java index 1a63a0a..2e61c65 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java +++ b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java @@ -147,6 +147,7 @@ public final class VoiceHintProcessor { if (badPrio > maxPrioCandidates) { maxPrioCandidates = badPrio; + input.maxBadPrio = Math.max(input.maxBadPrio, badPrio); } if (badTurn > maxAngle) { maxAngle = badTurn; @@ -158,6 +159,7 @@ public final class VoiceHintProcessor { } boolean hasSomethingMoreStraight = (Math.abs(turnAngle) - minAbsAngeRaw) > 20.; + //boolean hasSomethingMoreStraight = (Math.abs(turnAngle - minAbsAngeRaw)) > 20.; // unconditional triggers are all junctions with // - higher detour prios than the minimum route prio (except link->highway junctions) @@ -244,80 +246,88 @@ public final class VoiceHintProcessor { List results = new ArrayList<>(); double distance = 0; VoiceHint inputLast = null; - ArrayList tmpList = new ArrayList<>(); for (int hintIdx = 0; hintIdx < inputs.size(); hintIdx++) { VoiceHint input = inputs.get(hintIdx); + VoiceHint nextInput = null; + if (hintIdx + 1 < inputs.size()) { + nextInput = inputs.get(hintIdx + 1); + } - if (input.cmd == VoiceHint.C && !input.goodWay.isLinktType()) { - int badWayPrio = 0; - if (input.badWays != null) { - for (MessageData md : input.badWays) { - badWayPrio = Math.max(badWayPrio, md.getPrio()); + if (nextInput == null) { + if (input.cmd == VoiceHint.C && !input.goodWay.isLinktType()) { + if (input.goodWay.getPrio() < input.maxBadPrio) { + results.add(input); + } else { + if (inputLast != null) { // when drop add distance to last + inputLast.distanceToNext += input.distanceToNext; + } + continue; } - } - if (input.goodWay.getPrio() < badWayPrio) { - results.add(input); } else { - if (inputLast != null) { // when drop add distance to last - inputLast.distanceToNext += input.distanceToNext; - } - continue; + results.add(input); } } else { - if (input.distanceToNext < catchingRange) { + if (input.cmd == VoiceHint.C && !input.goodWay.isLinktType()) { + if (input.goodWay.getPrio() < input.maxBadPrio) { + results.add(input); + } else { + if (inputLast != null) { // when drop add distance to last + inputLast.distanceToNext += input.distanceToNext; + } + } + } else if (input.distanceToNext < catchingRange) { + int badWayPrio = 0; double dist = input.distanceToNext; float angles = input.angle; int i = 1; - boolean save = true; - tmpList.clear(); - while (dist < catchingRange && hintIdx + i < inputs.size()) { - VoiceHint h2 = inputs.get(hintIdx + i); - dist += h2.distanceToNext; - angles += h2.angle; - if (VoiceHint.is180DegAngle(input.angle) || VoiceHint.is180DegAngle(h2.angle)) { // u-turn, 180 degree + boolean save = false; + + dist += nextInput.distanceToNext; + angles += nextInput.angle; + + if (input.cmd == VoiceHint.C && !input.goodWay.isLinktType()) { + if (input.goodWay.getPrio() < input.maxBadPrio) { save = true; - break; - } else if (Math.abs(angles) > 180 - SIGNIFICANT_ANGLE) { // u-turn, collects e.g. two left turns in range - input.angle = angles; - input.calcCommand(); - input.distanceToNext += h2.distanceToNext; - save = true; - hintIdx++; - break; - } else if (Math.abs(angles) < SIGNIFICANT_ANGLE && input.distanceToNext < minRange) { - input.angle = angles; - input.calcCommand(); - input.distanceToNext += h2.distanceToNext; - save = true; - hintIdx++; - break; - } else if (Math.abs(input.angle) > SIGNIFICANT_ANGLE) { - tmpList.add(h2); - hintIdx++; - } else if (dist > catchingRange) { // distance reached - break; - } else { - if (inputLast != null) { // when drop add distance to last - inputLast.distanceToNext += input.distanceToNext; - } - save = false; } - i++; + } else if (VoiceHint.is180DegAngle(input.angle)) { //|| VoiceHint.is180DegAngle(nextInput.angle)) { // u-turn, 180 degree + //System.out.println("uturn < dist next!=null " + input.indexInTrack); + save = true; + } else if (Math.abs(angles) > 180 - SIGNIFICANT_ANGLE) { // u-turn, collects e.g. two left turns in range + input.angle = angles; + input.calcCommand(); + input.distanceToNext += nextInput.distanceToNext; + save = true; + hintIdx++; + } else if (Math.abs(angles) < SIGNIFICANT_ANGLE && input.distanceToNext < minRange) { + input.angle = angles; + input.calcCommand(); + input.distanceToNext += nextInput.distanceToNext; + save = true; + } else if (Math.abs(input.angle) > SIGNIFICANT_ANGLE) { + results.add(input); // add when last + save = false; + } else if (Math.abs(input.angle) < SIGNIFICANT_ANGLE) { + results.add(input); // add when last + save = false; + } else { + if (inputLast != null) { // when drop add distance to last + inputLast.distanceToNext += input.distanceToNext; + } + save = false; } + if (save) { results.add(input); // add when last - if (tmpList.size() > 0) { // add when something in stock - results.addAll(tmpList); - hintIdx += tmpList.size() - 1; - } } } else { results.add(input); } - inputLast = input; } + inputLast = input; } + return results; } + } From 582504784705dba806c5bec3a0d36e393e8a0fdb Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 7 Oct 2023 18:10:54 +0200 Subject: [PATCH 050/173] remove seed, added file param --- .../src/main/java/btools/server/BRouter.java | 150 ++++++------------ 1 file changed, 52 insertions(+), 98 deletions(-) diff --git a/brouter-server/src/main/java/btools/server/BRouter.java b/brouter-server/src/main/java/btools/server/BRouter.java index dbfff6d..3788e51 100644 --- a/brouter-server/src/main/java/btools/server/BRouter.java +++ b/brouter-server/src/main/java/btools/server/BRouter.java @@ -1,11 +1,7 @@ package btools.server; -import java.io.BufferedOutputStream; -import java.io.DataOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.net.URLDecoder; -import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -14,11 +10,10 @@ import btools.router.OsmTrack; import btools.router.RoutingContext; import btools.router.RoutingEngine; import btools.router.RoutingParamCollector; -import btools.router.SearchBoundary; public class BRouter { public static void main(String[] args) throws Exception { - if (args.length == 3) { // cgi-input-mode + if (args.length == 3 || args.length == 4) { // cgi-input-mode try { System.setProperty("segmentBaseDir", args[0]); System.setProperty("profileBaseDir", args[1]); @@ -28,7 +23,7 @@ public class BRouter { int lonIdx = queryString.indexOf("lonlats="); int sepIdx = queryString.indexOf("&", lonIdx); - String lonlats = queryString.substring(lonIdx+8, sepIdx); + String lonlats = queryString.substring(lonIdx + 8, sepIdx); RoutingContext rc = new RoutingContext(); RoutingParamCollector routingParamCollector = new RoutingParamCollector(); @@ -37,10 +32,14 @@ public class BRouter { Map params = routingParamCollector.getUrlParams(queryString); routingParamCollector.setParams(rc, wplist, params); - // cgi-header - System.out.println("Content-type: text/plain"); - System.out.println(); - + String exportName = null; + if (args.length == 4) { + exportName = args[3]; + } else { + // cgi-header + System.out.println("Content-type: text/plain"); + System.out.println(); + } long maxRunningTime = 60000; // the cgi gets a 1 Minute timeout String sMaxRunningTime = System.getProperty("maxRunningTime"); @@ -48,9 +47,7 @@ public class BRouter { maxRunningTime = Integer.parseInt(sMaxRunningTime) * 1000; } - - RoutingEngine re = new RoutingEngine(null, null, new File(args[0]), wplist, rc); - + RoutingEngine re = new RoutingEngine(exportName, null, new File(args[0]), wplist, rc); re.doRun(maxRunningTime); if (re.getErrorMessage() != null) { System.out.println(re.getErrorMessage()); @@ -65,96 +62,53 @@ public class BRouter { System.out.println("Find routes in an OSM map"); System.out.println("usage: java -jar brouter.jar [parameter-list] [profile-parameter-list] "); System.out.println(" or: java -cp %CLASSPATH% btools.server.BRouter > [parameter-list] [profile-parameter-list]"); - System.out.println(" or: java -jar brouter.jar "); + System.out.println(" or: java -jar brouter.jar [output-filename]"); System.exit(0); } - RoutingEngine re = null; - if ("seed".equals(args[3])) { - List wplist = new ArrayList<>(); - wplist.add(readPosition(args, 1, "from")); - int searchRadius = Integer.parseInt(args[4]); // if = 0 search a 5x5 square - String filename = SearchBoundary.getFileName(wplist.get(0)); - DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("traffic/" + filename))); - - for (int direction = 0; direction < 8; direction++) { - RoutingContext rc = readRoutingContext(args); - SearchBoundary boundary = new SearchBoundary(wplist.get(0), searchRadius, direction / 2); - rc.trafficOutputStream = dos; - rc.inverseDirection = (direction & 1) != 0; - re = new RoutingEngine("mytrack", "mylog", new File(args[0]), wplist, rc); - re.boundary = boundary; - re.airDistanceCostFactor = rc.trafficDirectionFactor; - rc.countTraffic = true; - re.doSearch(); - if (re.getErrorMessage() != null) { - break; - } - } - dos.close(); - } else { - int engineMode = 0; - try { - engineMode = Integer.parseInt(args[2]); - } catch (NumberFormatException e) { - } - - RoutingParamCollector routingParamCollector = new RoutingParamCollector(); - List wplist = routingParamCollector.getWayPointList(args[4]); - - System.setProperty("segmentBaseDir", args[0]); - System.setProperty("profileBaseDir", args[1]); - String moreParams = null; - String profileParams = null; - if (args.length >= 6) { - moreParams = args[5]; - } - if (args.length == 7) { - profileParams = args[6]; - } - - RoutingContext rc = new RoutingContext(); - rc.localFunction = args[3]; - if (moreParams != null) { - Map params = routingParamCollector.getUrlParams(moreParams); - routingParamCollector.setParams(rc, wplist, params); - } - if (profileParams != null) { - Map params = routingParamCollector.getUrlParams(profileParams); - routingParamCollector.setProfileParams(rc, params); - } - try { - if (engineMode==RoutingEngine.BROUTER_ENGINEMODE_GETELEV) { - re = new RoutingEngine("testinfo", null, new File(args[0]), wplist, rc, engineMode); - } else { - re = new RoutingEngine("testtrack", null, new File(args[0]), wplist, rc, engineMode); - } - re.doRun(0); - } catch (Exception e) { - System.out.println(e.getMessage()); - } + int engineMode = 0; + try { + engineMode = Integer.parseInt(args[2]); + } catch (NumberFormatException e) { } - } + RoutingParamCollector routingParamCollector = new RoutingParamCollector(); + List wplist = routingParamCollector.getWayPointList(args[4]); - private static OsmNodeNamed readPosition(String[] args, int idx, String name) { - OsmNodeNamed n = new OsmNodeNamed(); - n.name = name; - n.ilon = (int) ((Double.parseDouble(args[idx]) + 180.) * 1000000. + 0.5); - n.ilat = (int) ((Double.parseDouble(args[idx + 1]) + 90.) * 1000000. + 0.5); - return n; - } - - private static RoutingContext readRoutingContext(String[] args) { - RoutingContext c = new RoutingContext(); - if (args.length > 5) { - c.localFunction = args[5]; - if (args.length > 6) { - c.setAlternativeIdx(Integer.parseInt(args[6])); - } + System.setProperty("segmentBaseDir", args[0]); + System.setProperty("profileBaseDir", args[1]); + String moreParams = null; + String profileParams = null; + if (args.length >= 6) { + moreParams = args[5]; } - c.memoryclass = (int) (Runtime.getRuntime().maxMemory() / 1024 / 1024); - // c.startDirection= Integer.valueOf( 150 ); - return c; + if (args.length == 7) { + profileParams = args[6]; + } + + RoutingContext rc = new RoutingContext(); + rc.localFunction = args[3]; + if (moreParams != null) { + Map params = routingParamCollector.getUrlParams(moreParams); + routingParamCollector.setParams(rc, wplist, params); + } + if (profileParams != null) { + Map params = routingParamCollector.getUrlParams(profileParams); + routingParamCollector.setProfileParams(rc, params); + } + try { + RoutingEngine re = null; + if (engineMode == RoutingEngine.BROUTER_ENGINEMODE_GETELEV) { + re = new RoutingEngine("testinfo", null, new File(args[0]), wplist, rc, engineMode); + } else { + re = new RoutingEngine("testtrack", null, new File(args[0]), wplist, rc, engineMode); + } + re.doRun(0); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } + + } From 3fae9246d6d2ec5f6e15862df1a98dc5a59bd2d9 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Wed, 18 Oct 2023 12:37:05 +0200 Subject: [PATCH 051/173] add param collector and calls --- .../src/main/java/btools/server/RouteServer.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/brouter-server/src/main/java/btools/server/RouteServer.java b/brouter-server/src/main/java/btools/server/RouteServer.java index c0e5846..27da0c9 100644 --- a/brouter-server/src/main/java/btools/server/RouteServer.java +++ b/brouter-server/src/main/java/btools/server/RouteServer.java @@ -29,6 +29,7 @@ import btools.router.OsmTrack; import btools.router.ProfileCache; import btools.router.RoutingContext; import btools.router.RoutingEngine; +import btools.router.RoutingParamCollector; import btools.server.request.ProfileUploadHandler; import btools.server.request.RequestHandler; import btools.server.request.ServerHandler; @@ -146,7 +147,9 @@ public class RouteServer extends Thread implements Comparable { } String url = getline.split(" ")[1]; - Map params = getUrlParams(url); + + RoutingParamCollector routingParamCollector = new RoutingParamCollector(); + Map params = routingParamCollector.getUrlParams(url); long maxRunningTime = getMaxRunningTime(); @@ -186,12 +189,17 @@ public class RouteServer extends Thread implements Comparable { return; } RoutingContext rc = handler.readRoutingContext(); - List wplist = handler.readWayPointList(); + List wplist = routingParamCollector.getWayPointList(params.get("lonlats")); if (wplist.size() < 10) { SuspectManager.nearRecentWps.add(wplist); } int engineMode = 0; + if (params.containsKey("engineMode")) { + engineMode = Integer.parseInt(params.get("engineMode")); + } + routingParamCollector.setParams(rc, wplist, params); + for (Map.Entry e : params.entrySet()) { if ("engineMode".equals(e.getKey())) { engineMode = Integer.parseInt(e.getValue()); From 890e7f9824effc75bb0c6bd117dd5bd480c1c933 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Wed, 18 Oct 2023 12:50:53 +0200 Subject: [PATCH 052/173] removed older param handling --- .../main/java/btools/server/RouteServer.java | 21 --- .../btools/server/request/RequestHandler.java | 4 - .../btools/server/request/ServerHandler.java | 165 ------------------ 3 files changed, 190 deletions(-) diff --git a/brouter-server/src/main/java/btools/server/RouteServer.java b/brouter-server/src/main/java/btools/server/RouteServer.java index 27da0c9..bb5e98c 100644 --- a/brouter-server/src/main/java/btools/server/RouteServer.java +++ b/brouter-server/src/main/java/btools/server/RouteServer.java @@ -200,27 +200,6 @@ public class RouteServer extends Thread implements Comparable { } routingParamCollector.setParams(rc, wplist, params); - for (Map.Entry e : params.entrySet()) { - if ("engineMode".equals(e.getKey())) { - engineMode = Integer.parseInt(e.getValue()); - } else if ("timode".equals(e.getKey())) { - rc.turnInstructionMode = Integer.parseInt(e.getValue()); - } else if ("heading".equals(e.getKey())) { - rc.startDirection = Integer.parseInt(e.getValue()); - rc.forceUseStartDirection = true; - } else if (e.getKey().startsWith("profile:")) { - if (rc.keyValues == null) { - rc.keyValues = new HashMap<>(); - } - rc.keyValues.put(e.getKey().substring(8), e.getValue()); - } else if (e.getKey().equals("straight")) { - String[] sa = e.getValue().split(","); - for (int i = 0; i < sa.length; i++) { - int v = Integer.parseInt(sa[i]); - if (wplist.size() > v) wplist.get(v).direct = true; - } - } - } cr = new RoutingEngine(null, null, serviceContext.segmentDir, wplist, rc, engineMode); cr.quite = true; cr.doRun(maxRunningTime); diff --git a/brouter-server/src/main/java/btools/server/request/RequestHandler.java b/brouter-server/src/main/java/btools/server/request/RequestHandler.java index 5364c5b..6fbb816 100644 --- a/brouter-server/src/main/java/btools/server/request/RequestHandler.java +++ b/brouter-server/src/main/java/btools/server/request/RequestHandler.java @@ -1,9 +1,7 @@ package btools.server.request; -import java.util.List; import java.util.Map; -import btools.router.OsmNodeNamed; import btools.router.OsmTrack; import btools.router.RoutingContext; import btools.server.ServiceContext; @@ -19,8 +17,6 @@ public abstract class RequestHandler { public abstract RoutingContext readRoutingContext(); - public abstract List readWayPointList(); - public abstract String formatTrack(OsmTrack track); public abstract String getMimeType(); 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 fb8f520..898121c 100644 --- a/brouter-server/src/main/java/btools/server/request/ServerHandler.java +++ b/brouter-server/src/main/java/btools/server/request/ServerHandler.java @@ -3,12 +3,8 @@ package btools.server.request; import java.io.BufferedWriter; import java.io.File; import java.io.StringWriter; -import java.util.ArrayList; -import java.util.List; import java.util.Map; -import btools.router.OsmNodeNamed; -import btools.router.OsmNogoPolygon; import btools.router.OsmTrack; import btools.router.RoutingContext; import btools.server.ServiceContext; @@ -62,59 +58,9 @@ public class ServerHandler extends RequestHandler { } rc.localFunction = profile; - rc.setAlternativeIdx(Integer.parseInt(params.get("alternativeidx"))); - - List poisList = readPoisList(); - rc.poipoints = poisList; - - List nogoList = readNogoList(); - List nogoPolygonsList = readNogoPolygons(); - - if (nogoList != null) { - RoutingContext.prepareNogoPoints(nogoList); - rc.nogopoints = nogoList; - } - - if (rc.nogopoints == null) { - rc.nogopoints = nogoPolygonsList; - } else if (nogoPolygonsList != null) { - rc.nogopoints.addAll(nogoPolygonsList); - } - return rc; } - @Override - public List readWayPointList() { - // lon,lat|... - String lonLats = params.get("lonlats"); - if (lonLats == null) throw new IllegalArgumentException("lonlats parameter not set"); - - String[] coords = lonLats.split("\\|"); - if (coords.length < 2) - throw new IllegalArgumentException("we need two lat/lon points at least!"); - - List wplist = new ArrayList<>(); - for (int i = 0; i < coords.length; i++) { - String[] lonLat = coords[i].split(","); - if (lonLat.length < 2) - throw new IllegalArgumentException("we need two lat/lon points at least!"); - wplist.add(readPosition(lonLat[0], lonLat[1], "via" + i)); - if (lonLat.length > 2) { - if (lonLat[2].equals("d")) { - wplist.get(wplist.size()-1).direct = true; - } else { - wplist.get(wplist.size()-1).name = lonLat[2]; - } - } - } - - if (wplist.get(0).name.startsWith("via")) wplist.get(0).name = "from"; - if (wplist.get(wplist.size() - 1).name.startsWith("via")) wplist.get(wplist.size() - 1).name = "to"; - - return wplist; - } - @Override public String formatTrack(OsmTrack track) { String result; @@ -191,115 +137,4 @@ public class ServerHandler extends RequestHandler { return params.get("trackname") == null ? null : params.get("trackname").replaceAll("[^a-zA-Z0-9 \\._\\-]+", ""); } - private static OsmNodeNamed readPosition(String vlon, String vlat, String name) { - if (vlon == null) throw new IllegalArgumentException("lon " + name + " not found in input"); - if (vlat == null) throw new IllegalArgumentException("lat " + name + " not found in input"); - - return readPosition(Double.parseDouble(vlon), Double.parseDouble(vlat), name); - } - - private static OsmNodeNamed readPosition(double lon, double lat, String name) { - OsmNodeNamed n = new OsmNodeNamed(); - n.name = name; - n.ilon = (int) ((lon + 180.) * 1000000. + 0.5); - n.ilat = (int) ((lat + 90.) * 1000000. + 0.5); - return n; - } - - private List readPoisList() { - // lon,lat,name|... - String pois = params.get("pois"); - if (pois == null) return null; - - String[] lonLatNameList = pois.split("\\|"); - - List poisList = new ArrayList<>(); - for (int i = 0; i < lonLatNameList.length; i++) { - String[] lonLatName = lonLatNameList[i].split(","); - - if (lonLatName.length != 3) - continue; - - OsmNodeNamed n = new OsmNodeNamed(); - n.ilon = (int) ((Double.parseDouble(lonLatName[0]) + 180.) * 1000000. + 0.5); - n.ilat = (int) ((Double.parseDouble(lonLatName[1]) + 90.) * 1000000. + 0.5); - n.name = lonLatName[2]; - poisList.add(n); - } - - return poisList; - } - - private List readNogoList() { - // lon,lat,radius|... - String nogos = params.get("nogos"); - if (nogos == null) return null; - - String[] lonLatRadList = nogos.split("\\|"); - - List nogoList = new ArrayList<>(); - for (int i = 0; i < lonLatRadList.length; i++) { - String[] lonLatRad = lonLatRadList[i].split(","); - String nogoWeight = "NaN"; - if (lonLatRad.length > 3) { - nogoWeight = lonLatRad[3]; - } - nogoList.add(readNogo(lonLatRad[0], lonLatRad[1], lonLatRad[2], nogoWeight)); - } - - return nogoList; - } - - private static OsmNodeNamed readNogo(String lon, String lat, String radius, String nogoWeight) { - double weight = "undefined".equals(nogoWeight) ? Double.NaN : Double.parseDouble(nogoWeight); - return readNogo(Double.parseDouble(lon), Double.parseDouble(lat), Integer.parseInt(radius), weight); - } - - private static OsmNodeNamed readNogo(double lon, double lat, int radius, double nogoWeight) { - OsmNodeNamed n = new OsmNodeNamed(); - n.name = "nogo" + radius; - n.ilon = (int) ((lon + 180.) * 1000000. + 0.5); - n.ilat = (int) ((lat + 90.) * 1000000. + 0.5); - n.isNogo = true; - n.nogoWeight = nogoWeight; - return n; - } - - private List readNogoPolygons() { - List result = new ArrayList<>(); - parseNogoPolygons(params.get("polylines"), result, false); - parseNogoPolygons(params.get("polygons"), result, true); - return result.size() > 0 ? result : null; - } - - private static void parseNogoPolygons(String polygons, List result, boolean closed) { - if (polygons != null) { - String[] polygonList = polygons.split("\\|"); - for (int i = 0; i < polygonList.length; i++) { - String[] lonLatList = polygonList[i].split(","); - if (lonLatList.length > 1) { - OsmNogoPolygon polygon = new OsmNogoPolygon(closed); - int j; - for (j = 0; j < 2 * (lonLatList.length / 2) - 1; ) { - String slon = lonLatList[j++]; - String slat = lonLatList[j++]; - int lon = (int) ((Double.parseDouble(slon) + 180.) * 1000000. + 0.5); - int lat = (int) ((Double.parseDouble(slat) + 90.) * 1000000. + 0.5); - polygon.addVertex(lon, lat); - } - - String nogoWeight = "NaN"; - if (j < lonLatList.length) { - nogoWeight = lonLatList[j]; - } - polygon.nogoWeight = Double.parseDouble(nogoWeight); - - if (polygon.points.size() > 0) { - polygon.calcBoundingCircle(); - result.add(polygon); - } - } - } - } - } } From 94b37278406ecfb76cacf818c56e0e78c88c8659 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Wed, 18 Oct 2023 21:23:59 +0200 Subject: [PATCH 053/173] Format pseudo tags docs --- ...onmental_considerations_and_pseudo_tags.md | 98 ++++++++++--------- 1 file changed, 54 insertions(+), 44 deletions(-) diff --git a/docs/developers/environmental_considerations_and_pseudo_tags.md b/docs/developers/environmental_considerations_and_pseudo_tags.md index 84a3344..7d86603 100644 --- a/docs/developers/environmental_considerations_and_pseudo_tags.md +++ b/docs/developers/environmental_considerations_and_pseudo_tags.md @@ -1,87 +1,97 @@ Environmental considerations (penalties for traffic, noise, town, no river, no forest) are possible due to the creation of pseudo tags during processing OSM data by spatial SQL queries in https://github.com/abrensch/brouter/blob/master/misc/scripts/mapcreation/brouter.sql. During this processing, roads are extended by a 32 m buffer creating 64 m wide lines. Then it is calculated what percentage of such line is at a specific distance to a noise source or within a forest, for example. The percentage is converted to a factor and the factor is assigned to a class. Ways that pass through different environments and are represented by a single OSM way can be problematic because the class is always based on the average environment along an entire OSM way. For traffic, calculations are on another level of complexity. ### consider_noise, noise_penalty -For proximity of noisy roads (secondary and higher). The noise factor represents the proportion of a road's buffer area that lies within the 64-meter buffer of noisy roads. This proportion is reduced: + +For proximity of noisy roads (secondary and higher). The noise factor represents the proportion of a road's buffer area that lies within the 64-meter buffer of noisy roads. This proportion is reduced: + - for motorways and trunk roads with max speed < 105 by 1.5 -- for primary roads 2 times -- 3 times if maxspeed is 75 - 105 for primary and secondary -- other secondary roads 5 times +- for primary roads 2 times +- 3 times if maxspeed is 75 - 105 for primary and secondary +- other secondary roads 5 times Noise class is roughly proportional to the noise factor: noise_factor = noise class -- < 0.1 = '1' -- < 0.25 = '2' -- < 0.4 = '3' -- < 0.55 = '4' -- < 0.8 = '5' -- ELSE = '6' -To be classified as noise class 6, a way must be less than 13 m on average from the middle of the carriageway of a motorway with a maximum speed exceeding 105. For a class 5, the distance must be up to 35 meters. (1 - noise factor) * 64 m for a given class determines the distance +- < 0.1 = '1' +- < 0.25 = '2' +- < 0.4 = '3' +- < 0.55 = '4' +- < 0.8 = '5' +- ELSE = '6' + +To be classified as noise class 6, a way must be less than 13 m on average from the middle of the carriageway of a motorway with a maximum speed exceeding 105. For a class 5, the distance must be up to 35 meters. (1 - noise factor) \* 64 m for a given class determines the distance **Max noise class:** -| Max speed | Motorway, trunk |Primary|Secondary | -|--- |:---: |:---: |:---: | -| >105 |6 |4 | 3 | -| 105 |5 |4 |3 | -| 75 |5 |3 |2 | - +| Max speed | Motorway, trunk |Primary|Secondary | +|--- |:---: |:---: |:---: | +| >105 |6 |4 | 3 | +| 105 |5 |4 |3 | +| 75 |5 |3 |2 | ### consider_river, no_river_penalty + OSM data recognized as river: + - waterway: river, canal - natural: water (except wastewater) Waterways have 32 m wide buffers. Water areas have 77 m wide buffers. -river_see = river class -- < 0.17 = '1' -- < 0.35 = '2' -- < 0.57 = '3' -- < 0.80 = '4' -- < 0.95 = '5' -- ELSE = '6' +river_see = river class + +- < 0.17 = '1' +- < 0.35 = '2' +- < 0.57 = '3' +- < 0.80 = '4' +- < 0.95 = '5' +- ELSE = '6' ### consider_forest, no_forest_penalty -OSM data recognized as forest: + +OSM data recognized as forest: + - landuse: forest, allotments, flowerbed, orchard, vineyard, recreation_ground, village_green - leisure: garden, park, nature_reserve - + No forest buffers are used. Imagine you trace the way with a pencil drawing lines 62 meters wide. Then estimated_forest_class=6 corresponds to the case that at least 98% of the line is in the woodland. This number is called a green factor. green_factor = forest class -- < 0.1 = NULL -- < 0.2 = '1' -- < 0.4 = '2' -- < 0.6 = '3' -- < 0.8 = '4' -- < 0.98 = '5' -- ELSE = '6' +- < 0.1 = NULL +- < 0.2 = '1' +- < 0.4 = '2' +- < 0.6 = '3' +- < 0.8 = '4' +- < 0.98 = '5' +- ELSE = '6' +### consider_town, town_penalty -### consider_town, town_penalty Town_class is determined by population data from OSM. Class -- 1 = 50-80 k people + +- 1 = 50-80 k people - 2 = 80-150 k people -- 3 = 150 - 400 k people +- 3 = 150 - 400 k people - 4 = 400 - 1,000 k people - 5 = 1 - 2 million people - 6 = > 2 million people ### consider_traffic, traffic_penalty + (modified copy from the sql file). OSM data used to estimate the traffic: -- population of towns (+ distance from position to the towns) -- size of industrial areas (landuse=industrial) and distance to them. Not considered: solar & wind farms. -- airports international -- motorway and trunk road density - traffic on motorways decreases traffic on primary/secondary/tertiary. Calculated on a grid (100 km^2) -- density of highways (tertiary and higher) calculated on a grid (100 km^2). Traffic decreases when more such roads are available. Exceptions: near junctions between motorways and other roads the traffic increases on these roads. -- mountain-ranges calculated as density of peaks > 400 m traffic is generally on highways in such regions higher as only generated by the local population or industrial areas + +- population of towns (+ distance from position to the towns) +- size of industrial areas (landuse=industrial) and distance to them. Not considered: solar & wind farms. +- airports international +- motorway and trunk road density - traffic on motorways decreases traffic on primary/secondary/tertiary. Calculated on a grid (100 km^2) +- density of highways (tertiary and higher) calculated on a grid (100 km^2). Traffic decreases when more such roads are available. Exceptions: near junctions between motorways and other roads the traffic increases on these roads. +- mountain-ranges calculated as density of peaks > 400 m traffic is generally on highways in such regions higher as only generated by the local population or industrial areas - calculate traffic from the population (for each segment of type primary secondary tertiary) -- SUM of (population of each town < 100 km) / ( town-radius + 2500 + dist(segment-position to the town) ** 2 ) -- town-radius is calculated as sqrt(population) +- SUM of (population of each town < 100 km) / ( town-radius + 2500 + dist(segment-position to the town) \*\* 2 ) +- town-radius is calculated as sqrt(population) From eb43a6cf2a9a719d2bea85a126de60659536653d Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Wed, 18 Oct 2023 21:24:48 +0200 Subject: [PATCH 054/173] Clarify some wordings & format table --- ...onmental_considerations_and_pseudo_tags.md | 105 ++++++++++-------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/docs/developers/environmental_considerations_and_pseudo_tags.md b/docs/developers/environmental_considerations_and_pseudo_tags.md index 7d86603..aa4ef90 100644 --- a/docs/developers/environmental_considerations_and_pseudo_tags.md +++ b/docs/developers/environmental_considerations_and_pseudo_tags.md @@ -1,6 +1,12 @@ -Environmental considerations (penalties for traffic, noise, town, no river, no forest) are possible due to the creation of pseudo tags during processing OSM data by spatial SQL queries in https://github.com/abrensch/brouter/blob/master/misc/scripts/mapcreation/brouter.sql. During this processing, roads are extended by a 32 m buffer creating 64 m wide lines. Then it is calculated what percentage of such line is at a specific distance to a noise source or within a forest, for example. The percentage is converted to a factor and the factor is assigned to a class. Ways that pass through different environments and are represented by a single OSM way can be problematic because the class is always based on the average environment along an entire OSM way. For traffic, calculations are on another level of complexity. +--- +parent: Developers +--- -### consider_noise, noise_penalty +# Environmental considerations + +Environmental considerations (penalties for traffic, noise, town, no river, no forest) are possible due to the creation of pseudo tags during processing OSM data by spatial SQL queries in [brouter.sql](https://github.com/abrensch/brouter/blob/master/misc/scripts/mapcreation/brouter.sql). During this processing, roads are extended by a 32 m buffer creating 64 m wide lines. Then it is calculated what percentage of such line is at a specific distance to a noise source or within a forest, for example. The percentage is converted to a factor and the factor is assigned to a class. Ways that pass through different environments and are represented by a single OSM way can be problematic because the class is always based on the average environment along an entire OSM way. For traffic, calculations are on another level of complexity. + +### noise_class For proximity of noisy roads (secondary and higher). The noise factor represents the proportion of a road's buffer area that lies within the 64-meter buffer of noisy roads. This proportion is reduced: @@ -9,27 +15,32 @@ For proximity of noisy roads (secondary and higher). The noise factor represents - 3 times if maxspeed is 75 - 105 for primary and secondary - other secondary roads 5 times -Noise class is roughly proportional to the noise factor: +`noise_class` is roughly proportional to the noise factor: -noise_factor = noise class +| `noise_factor` | `noise_class` | +| -------------- | ------------- | +| < 0.1 | 1 | +| < 0.25 | 2 | +| < 0.4 | 3 | +| < 0.55 | 4 | +| < 0.8 | 5 | +| ELSE | 6 | -- < 0.1 = '1' -- < 0.25 = '2' -- < 0.4 = '3' -- < 0.55 = '4' -- < 0.8 = '5' -- ELSE = '6' +To be classified as noise class 6, a way must be less than 13 m on average from the middle of the carriageway of a motorway with a maximum speed exceeding 105. For a class 5, the distance must be up to 35 meters. (1 - noise_factor) \* 64 m for a given class determines the distance -To be classified as noise class 6, a way must be less than 13 m on average from the middle of the carriageway of a motorway with a maximum speed exceeding 105. For a class 5, the distance must be up to 35 meters. (1 - noise factor) \* 64 m for a given class determines the distance +| highway | maxspeed | max `noise_class` | +| -------------- | -------- | ----------------- | +| motorway,trunk | > 105 | 6 | +| motorway,trunk | 105 | 5 | +| motorway,trunk | 75 | 5 | +| primary | > 105 | 4 | +| primary | 105 | 4 | +| primary | 75 | 3 | +| secondary | > 105 | 3 | +| secondary | 105 | 3 | +| secondary | 75 | 2 | -**Max noise class:** -| Max speed | Motorway, trunk |Primary|Secondary | -|--- |:---: |:---: |:---: | -| >105 |6 |4 | 3 | -| 105 |5 |4 |3 | -| 75 |5 |3 |2 | - -### consider_river, no_river_penalty +### river_class OSM data recognized as river: @@ -38,16 +49,16 @@ OSM data recognized as river: Waterways have 32 m wide buffers. Water areas have 77 m wide buffers. -river_see = river class +| `river_see` | `river_class` | +| ----------- | ------------- | +| < 0.1 | 1 | +| < 0.3 | 2 | +| < 0.5 | 3 | +| < 0.8 | 4 | +| < 0.9 | 5 | +| ELSE | 6 | -- < 0.17 = '1' -- < 0.35 = '2' -- < 0.57 = '3' -- < 0.80 = '4' -- < 0.95 = '5' -- ELSE = '6' - -### consider_forest, no_forest_penalty +### forest_class OSM data recognized as forest: @@ -58,30 +69,30 @@ No forest buffers are used. Imagine you trace the way with a pencil drawing lines 62 meters wide. Then estimated_forest_class=6 corresponds to the case that at least 98% of the line is in the woodland. This number is called a green factor. -green_factor = forest class +| `green_factor` | `forest_class` | +| -------------- | -------------- | +| < 0.1 | NULL | +| < 0.2 | 1 | +| < 0.4 | 2 | +| < 0.6 | 3 | +| < 0.8 | 4 | +| < 0.98 | 5 | +| ELSE | 6 | -- < 0.1 = NULL -- < 0.2 = '1' -- < 0.4 = '2' -- < 0.6 = '3' -- < 0.8 = '4' -- < 0.98 = '5' -- ELSE = '6' - -### consider_town, town_penalty +### town_class Town_class is determined by population data from OSM. -Class +| population | `town_class` | +| ------------------ | ------------ | +| < 80 k people | 1 | +| < 150 k people | 2 | +| < 400 k people | 3 | +| < 1 million people | 4 | +| < 2 million people | 5 | +| > 2 million people | 6 | -- 1 = 50-80 k people -- 2 = 80-150 k people -- 3 = 150 - 400 k people -- 4 = 400 - 1,000 k people -- 5 = 1 - 2 million people -- 6 = > 2 million people - -### consider_traffic, traffic_penalty +### traffic_class (modified copy from the sql file). OSM data used to estimate the traffic: From 94a823f805c17e8474a266822a62d68360648410 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Wed, 18 Oct 2023 21:26:21 +0200 Subject: [PATCH 055/173] Add webrick to fix jekyll serve on ruby 3 --- docs/Gemfile | 2 ++ docs/Gemfile.lock | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/Gemfile b/docs/Gemfile index 7bfae82..587276e 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -27,3 +27,5 @@ end # Performance-booster for watching directories on Windows gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] + +gem "webrick", "~> 1.8" diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 96e2833..068424c 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -249,6 +249,7 @@ GEM unf_ext unf_ext (0.0.8.2) unicode-display_width (1.8.0) + webrick (1.8.1) PLATFORMS x86_64-linux @@ -258,6 +259,7 @@ DEPENDENCIES tzinfo (~> 1.2) tzinfo-data wdm (~> 0.1.1) + webrick (~> 1.8) BUNDLED WITH - 2.4.13 + 2.4.20 From 94089d2b60ca3cf31c92307e24056751112fbb27 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Wed, 18 Oct 2023 22:39:02 +0200 Subject: [PATCH 056/173] Explain website generation using jekyll --- docs/_README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs/_README.md diff --git a/docs/_README.md b/docs/_README.md new file mode 100644 index 0000000..cdda75c --- /dev/null +++ b/docs/_README.md @@ -0,0 +1,15 @@ +# BRouter Docs + +This documentation can be used to generate a static website using [jekyll](https://jekyllrb.com/). + +## Dependencies + +jekyll uses ruby and therefore installs dependencies using gem. It is recommended to use bundler to manage those dependencies. + +`bundle install` installs all dependencies. To update dependencies use `bundle update`. + +## Preview + +jekyll provides a built-in webserver which can be used for fast feedback during editing. + +`bundle exec jekyll serve` From 254aff19b8370a973ca79771185ec58283a1a9c3 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 27 Oct 2023 16:21:58 +0200 Subject: [PATCH 057/173] added 1sec srtm use --- .../java/btools/mapcreator/PosUnifier.java | 84 ++++++++++++++----- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java b/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java index bccbf70..10e6605 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java @@ -23,6 +23,9 @@ import btools.util.FrozenLongSet; * @author ab */ public class PosUnifier extends MapCreatorBase { + + public static final boolean UseLidarRd5FileName = false; + private DiffCoderDataOutputStream nodesOutStream; private DiffCoderDataOutputStream borderNodesOut; private File nodeTilesOut; @@ -33,6 +36,7 @@ public class PosUnifier extends MapCreatorBase { private int lastSrtmLatIdx; private SrtmRaster lastSrtmRaster; private String srtmdir; + private String srtmfallbackdir; private CompactLongSet borderNids; @@ -46,25 +50,40 @@ public class PosUnifier extends MapCreatorBase { double lat = Double.parseDouble(args[2]); NodeData n = new NodeData(1, lon, lat); - SrtmRaster srtm = posu.hgtForNode(n.ilon, n.ilat); short selev = Short.MIN_VALUE; + SrtmRaster srtm = null; + /* + // check hgt direct + srtm = posu.hgtForNode(n.ilon, n.ilat); + if (srtm != null) { + selev = srtm.getElevation(n.ilon, n.ilat); + } else { + System.out.println("hgtForNode no data"); + } + posu.resetSrtm(); + System.out.println("-----> selv for hgt " + lat + ", " + lon + " = " + selev + " = " + (selev / 4.)); + srtm = null; + selev = Short.MIN_VALUE; + */ if (srtm == null) { srtm = posu.srtmForNode(n.ilon, n.ilat); } if (srtm != null) selev = srtm.getElevation(n.ilon, n.ilat); posu.resetSrtm(); - System.out.println("-----> selv for " + lat + ", " + lon + " = " + selev + " = " + (selev / 4.)); + System.out.println("-----> selv for bef " + lat + ", " + lon + " = " + selev + " = " + (selev / 4.)); return; - } else if (args.length != 5) { - System.out.println("usage: java PosUnifier "); + } else if (args.length != 5 && args.length != 6) { + System.out.println("usage: java PosUnifier [srtm-fallback-data-dir]"); + System.out.println("or java PosUnifier "); return; } - new PosUnifier().process(new File(args[0]), new File(args[1]), new File(args[2]), new File(args[3]), args[4]); + new PosUnifier().process(new File(args[0]), new File(args[1]), new File(args[2]), new File(args[3]), args[4], (args.length == 6 ? args[5] : null)); } - public void process(File nodeTilesIn, File nodeTilesOut, File bordernidsinfile, File bordernodesoutfile, String srtmdir) throws Exception { + public void process(File nodeTilesIn, File nodeTilesOut, File bordernidsinfile, File bordernodesoutfile, String srtmdir, String srtmfallbackdir) throws Exception { this.nodeTilesOut = nodeTilesOut; this.srtmdir = srtmdir; + this.srtmfallbackdir = srtmfallbackdir; // read border nids set DataInputStream dis = createInStream(bordernidsinfile); @@ -178,15 +197,17 @@ public class PosUnifier extends MapCreatorBase { lastSrtmLonIdx = srtmLonIdx; lastSrtmLatIdx = srtmLatIdx; - String slonidx = "0" + srtmLonIdx; - String slatidx = "0" + srtmLatIdx; - String filename = "srtm_" + slonidx.substring(slonidx.length() - 2) + "_" + slatidx.substring(slatidx.length() - 2); + String filename; + if (UseLidarRd5FileName) { + filename = genFilenameRd5(ilon, ilat); + } else { + filename = genFilenameXY(srtmLonIdx, srtmLatIdx); + } lastSrtmRaster = srtmmap.get(filename); if (lastSrtmRaster == null && !srtmmap.containsKey(filename)) { File f = new File(new File(srtmdir), filename + ".bef"); if (f.exists()) { - System.out.println("*** reading: " + f); try { InputStream isc = new BufferedInputStream(new FileInputStream(f)); lastSrtmRaster = new RasterCoder().decodeRaster(isc); @@ -194,19 +215,24 @@ public class PosUnifier extends MapCreatorBase { } catch (Exception e) { System.out.println("**** ERROR reading " + f + " ****"); } + System.out.println("*** reading: " + f + " " + lastSrtmRaster.ncols); srtmmap.put(filename, lastSrtmRaster); return lastSrtmRaster; } - - f = new File(new File(srtmdir), filename + ".zip"); - // System.out.println("reading: " + f + " ilon=" + ilon + " ilat=" + ilat); - if (f.exists()) { - try { - lastSrtmRaster = new SrtmData(f).getRaster(); + if (srtmfallbackdir != null) { + f = new File(new File(srtmfallbackdir), filename + ".bef"); + if (f.exists()) { + try { + InputStream isc = new BufferedInputStream(new FileInputStream(f)); + //lastSrtmRaster = new StatRasterCoder().decodeRaster(isc); + lastSrtmRaster = new RasterCoder().decodeRaster(isc); + isc.close(); + } catch (Exception e) { + System.out.println("**** ERROR reading " + f + " ****"); + } + System.out.println("*** reading: " + f + " " + lastSrtmRaster.cellsize); srtmmap.put(filename, lastSrtmRaster); return lastSrtmRaster; - } catch (Exception e) { - System.out.println("**** ERROR reading " + f + " ****"); } } srtmmap.put(filename, lastSrtmRaster); @@ -214,6 +240,24 @@ public class PosUnifier extends MapCreatorBase { return lastSrtmRaster; } + static String genFilenameXY(int srtmLonIdx, int srtmLatIdx) { + String slonidx = "0" + srtmLonIdx; + String slatidx = "0" + srtmLatIdx; + return "srtm_" + slonidx.substring(slonidx.length() - 2) + "_" + slatidx.substring(slatidx.length() - 2); + } + + static String genFilenameRd5(int ilon, int ilat) { + int lonDegree = ilon / 1000000; + int latDegree = ilat / 1000000; + int lonMod5 = lonDegree % 5; + int latMod5 = latDegree % 5; + lonDegree = lonDegree - 180 - lonMod5; + latDegree = latDegree - 90 - latMod5; + return String.format("srtm_%s_%s", lonDegree < 0 ? "W" + (-lonDegree) : "E" + lonDegree, + latDegree < 0 ? "S" + (-latDegree) : "N" + latDegree); + } + + private SrtmRaster hgtForNode(int ilon, int ilat) throws Exception { double lon = (ilon - 180000000) / 1000000.; double lat = (ilat - 90000000) / 1000000.; @@ -222,13 +266,13 @@ public class PosUnifier extends MapCreatorBase { // don't block lastSrtmRaster SrtmRaster srtm = srtmmap.get(filename); if (srtm == null) { - File f = new File(new File(srtmdir), filename + ".hgt"); + File f = new File(new File(srtmdir), filename + ".zip"); if (f.exists()) { srtm = new ConvertLidarTile().getRaster(f, lon, lat); srtmmap.put(filename, srtm); return srtm; } - f = new File(new File(srtmdir), filename + ".zip"); + f = new File(new File(srtmdir), filename + ".hgt"); if (f.exists()) { srtm = new ConvertLidarTile().getRaster(f, lon, lat); srtmmap.put(filename, srtm); From c7786f03ecb410d059daa683b1a04722896ab5e0 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 27 Oct 2023 16:22:52 +0200 Subject: [PATCH 058/173] added 1sec bef generation --- .../btools/mapcreator/ConvertLidarTile.java | 276 +++++++++++++++--- 1 file changed, 236 insertions(+), 40 deletions(-) diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/ConvertLidarTile.java b/brouter-map-creator/src/main/java/btools/mapcreator/ConvertLidarTile.java index 2a00e96..cab1d0b 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/ConvertLidarTile.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/ConvertLidarTile.java @@ -9,13 +9,15 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; public class ConvertLidarTile { - private static int NROWS; - private static int NCOLS; + + public static final boolean DEBUG = false; public static final short NODATA2 = -32767; // hgt-formats nodata public static final short NODATA = Short.MIN_VALUE; @@ -25,17 +27,89 @@ public class ConvertLidarTile { private static final int HGT_3ASEC_ROWS = 1201; // 3 arc second resolution (90m) private static final int HGT_3ASEC_FILE_SIZE = HGT_3ASEC_ROWS * HGT_3ASEC_ROWS * Short.BYTES; private static final int HGT_1ASEC_ROWS = 3601; // 1 arc second resolution (30m) + private static final int SRTM3_ROW_LENGTH = 1200; // number of elevation values per line + private static final int SRTM1_ROW_LENGTH = 3600; + private static final boolean SRTM_NO_ZERO = true; - static short[] imagePixels; + private int NROWS; + private int NCOLS; + private int ROW_LENGTH; + private short[] imagePixels; - private static void readHgtZip(String filename, int rowOffset, int colOffset) throws Exception { + + public static void main(String[] args) throws Exception { + if (args.length == 3 || args.length == 4 || args.length == 5) { + String filename90 = args[0]; + if ("all".equals(filename90)) { + //if (DEBUG) + System.out.println("lidar convert all "); + new ConvertLidarTile().doConvertAll(args[1], args[2], (args.length > 3 ? args[3] : null), (args.length == 5 ? args[4] : null)); + return; + } + // old filenames only + String filename30 = filename90 + ".bef"; //filename90.substring(0, filename90.length() - 3) + "bef"; + + int srtmLonIdx = Integer.parseInt(filename90.substring(5, 7).toLowerCase()); + int srtmLatIdx = Integer.parseInt(filename90.substring(8, 10).toLowerCase()); + + int ilon_base = (srtmLonIdx - 1) * 5 - 180; + int ilat_base = 150 - srtmLatIdx * 5 - 90; + int row_length = SRTM3_ROW_LENGTH; + String fallbackdir = null; + if (args.length > 3) { + row_length = (Integer.parseInt(args[3]) == 1 ? SRTM1_ROW_LENGTH : SRTM3_ROW_LENGTH); + fallbackdir = (args.length == 5 ? args[4] : null); + } + //if (DEBUG) + System.out.println("lidar convert " + ilon_base + " " + ilat_base + " from " + srtmLonIdx + " " + srtmLatIdx + " f: " + filename90 + " rowl " + row_length); + + new ConvertLidarTile().doConvert(args[1], ilon_base, ilat_base, args[2] + "/" + filename30, row_length, fallbackdir); + } else { + System.out.println("usage: java [arc seconds (1 or 3,default=3)] [hgt-fallback-data-dir]"); + System.out.println("or java all [arc seconds (1 or 3, default=3)] [hgt-fallback-data-dir]"); + return; + } + } + + private void doConvertAll(String hgtdata, String outdir, String rlen, String hgtfallbackdata) throws Exception { + int row_length = SRTM3_ROW_LENGTH; + if (rlen != null) { + row_length = (Integer.parseInt(rlen) == 1 ? SRTM1_ROW_LENGTH : SRTM3_ROW_LENGTH); + } + String filename30; + for (int ilon_base = -180; ilon_base < 180; ilon_base += 5) { + for (int ilat_base = 85; ilat_base > -90; ilat_base -= 5) { + if (PosUnifier.UseLidarRd5FileName) { + filename30 = genFilenameRd5(ilon_base, ilat_base); + } else { + filename30 = genFilenameOld(ilon_base, ilat_base); + } + if (DEBUG) + System.out.println("lidar convert all: " + filename30); + doConvert(hgtdata, ilon_base, ilat_base, outdir + "/" + filename30, row_length, hgtfallbackdata); + } + } + } + + static String genFilenameOld(int ilon_base, int ilat_base) { + int srtmLonIdx = ((ilon_base + 180) / 5) + 1; + int srtmLatIdx = (60 - ilat_base) / 5; + return String.format("srtm_%02d_%02d.bef", srtmLonIdx, srtmLatIdx); + } + + static String genFilenameRd5(int ilon_base, int ilat_base) { + return String.format("srtm_%s_%s.bef", ilon_base < 0 ? "W" + (-ilon_base) : "E" + ilon_base, + ilat_base < 0 ? "S" + (-ilat_base) : "N" + ilat_base); + } + + private void readHgtZip(String filename, int rowOffset, int colOffset, int row_length) throws Exception { ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(filename))); try { for (; ; ) { ZipEntry ze = zis.getNextEntry(); if (ze == null) break; if (ze.getName().toLowerCase().endsWith(HGT_FILE_EXT)) { - readHgtFromStream(zis, rowOffset, colOffset, HGT_3ASEC_ROWS); + readHgtFromStream(zis, rowOffset, colOffset, row_length, 1); return; } } @@ -44,14 +118,14 @@ public class ConvertLidarTile { } } - private static void readHgtFromStream(InputStream is, int rowOffset, int colOffset, int rowLength) + private void readHgtFromStream(InputStream is, int rowOffset, int colOffset, int rowLength, int scale) throws Exception { DataInputStream dis = new DataInputStream(new BufferedInputStream(is)); for (int ir = 0; ir < rowLength; ir++) { - int row = rowOffset + ir; + int row = rowOffset + ir * scale; for (int ic = 0; ic < rowLength; ic++) { - int col = colOffset + ic; + int col = colOffset + ic * scale; int i1 = dis.read(); // msb first! int i0 = dis.read(); @@ -64,19 +138,52 @@ public class ConvertLidarTile { if (val == NODATA2) { val = NODATA; } - - setPixel(row, col, val); + if (scale == 3) { + setPixel(row, col, val); + setPixel(row + 1, col, val); + setPixel(row + 2, col, val); + setPixel(row, col + 1, val); + setPixel(row + 1, col + 1, val); + setPixel(row + 2, col + 1, val); + setPixel(row, col + 2, val); + setPixel(row + 1, col + 2, val); + setPixel(row + 2, col + 2, val); + } else { + setPixel(row, col, val); + } } } } - private static void setPixel(int row, int col, short val) { + private void readFallbackFile(File file, int rowOffset, int colOffset, int row_length) + throws Exception { + int rowLength; + int scale; + if (file.length() > HGT_3ASEC_FILE_SIZE) { + rowLength = HGT_1ASEC_ROWS; + scale = 1; + } else { + rowLength = HGT_3ASEC_ROWS; + scale = 3; + } + if (DEBUG) + System.out.println("read fallback: " + file + " " + rowLength); + + FileInputStream fis = new FileInputStream(file); + try { + readHgtFromStream(fis, rowOffset, colOffset, rowLength, scale); + } finally { + fis.close(); + } + } + + private void setPixel(int row, int col, short val) { if (row >= 0 && row < NROWS && col >= 0 && col < NCOLS) { imagePixels[row * NCOLS + col] = val; } } - private static short getPixel(int row, int col) { + private short getPixel(int row, int col) { if (row >= 0 && row < NROWS && col >= 0 && col < NCOLS) { return imagePixels[row * NCOLS + col]; } @@ -84,36 +191,137 @@ public class ConvertLidarTile { } - public static void doConvert(String inputDir, int lonDegreeStart, int latDegreeStart, String outputFile) throws Exception { + public void doConvert(String inputDir, int lonDegreeStart, int latDegreeStart, String outputFile, int row_length, String hgtfallbackdata) throws Exception { int extraBorder = 0; - NROWS = 5 * 1200 + 1 + 2 * extraBorder; - NCOLS = 5 * 1200 + 1 + 2 * extraBorder; + List foundList = new ArrayList<>(); + List notfoundList = new ArrayList<>(); - imagePixels = new short[NROWS * NCOLS]; // 650 MB ! + boolean found = false; - // prefill as NODATA - for (int row = 0; row < NROWS; row++) { - for (int col = 0; col < NCOLS; col++) { - imagePixels[row * NCOLS + col] = NODATA; + if (row_length == SRTM1_ROW_LENGTH) { + // check for sources w/o border + for (int latIdx = 0; latIdx < 5; latIdx++) { + int latDegree = latDegreeStart + latIdx; + + for (int lonIdx = 0; lonIdx < 5; lonIdx++) { + int lonDegree = lonDegreeStart + lonIdx; + + String filename = inputDir + "/" + formatLat(latDegree) + formatLon(lonDegree) + ".zip"; + File f = new File(filename); + if (f.exists() && f.length() > 0) { + found = true; + break; + } + } } + } else { + // ignore when srtm3 + found = true; + } + if (found) { // init when found + NROWS = 5 * row_length + 1 + 2 * extraBorder; + NCOLS = 5 * row_length + 1 + 2 * extraBorder; + imagePixels = new short[NROWS * NCOLS]; // 650 MB ! + + // prefill as NODATA + Arrays.fill(imagePixels, NODATA); + } else { + if (DEBUG) + System.out.println("none 1sec data: " + lonDegreeStart + " " + latDegreeStart); + return; } for (int latIdx = -1; latIdx <= 5; latIdx++) { int latDegree = latDegreeStart + latIdx; - int rowOffset = extraBorder + (4 - latIdx) * 1200; + int rowOffset = extraBorder + (4 - latIdx) * row_length; for (int lonIdx = -1; lonIdx <= 5; lonIdx++) { int lonDegree = lonDegreeStart + lonIdx; - int colOffset = extraBorder + lonIdx * 1200; + int colOffset = extraBorder + lonIdx * row_length; String filename = inputDir + "/" + formatLat(latDegree) + formatLon(lonDegree) + ".zip"; File f = new File(filename); if (f.exists() && f.length() > 0) { - System.out.println("exist: " + filename); - readHgtZip(filename, rowOffset, colOffset); + if (DEBUG) + System.out.println("exist: " + filename); + readHgtZip(filename, rowOffset, colOffset, row_length + 1); } else { - System.out.println("none : " + filename); + if (hgtfallbackdata != null) { + String filenamehgt = hgtfallbackdata + "/" + formatLat(latDegree) + formatLon(lonDegree) + ".hgt"; + f = new File(filenamehgt); + if (f.exists() && f.length() > 0) { + readFallbackFile(f, rowOffset, colOffset, row_length + 1); + /*if (imagePixels == null) { + imagePixels = new short[NROWS * NCOLS]; + Arrays.fill(imagePixels, NODATA); + found = true; + } + */ + /* + int rowLength; + int arcspace; + if (f.length() > HGT_3ASEC_FILE_SIZE) { + rowLength = HGT_1ASEC_ROWS; + arcspace = 1; + } else { + rowLength = HGT_3ASEC_ROWS; + arcspace = 3; + } + if (DEBUG) + System.out.println("read fallback: " + f + " " + rowLength); + + FileInputStream fis = new FileInputStream(f); + DataInputStream dis = new DataInputStream(new BufferedInputStream(fis)); + for (int ir = 0; ir < rowLength; ir++) { + int row = rowOffset + ir * arcspace; + + for (int ic = 0; ic < rowLength; ic++) { + int col = colOffset + ic * arcspace; + + int i1 = dis.read(); // msb first! + int i0 = dis.read(); + + if (i0 == -1 || i1 == -1) + throw new RuntimeException("unexpected end of file reading hgt entry!"); + + short val = (short) ((i1 << 8) | i0); + + if (val == NODATA2) { + val = NODATA; + } + if (arcspace == 3) { + setPixel(row, col, val); + setPixel(row+1, col, val); + setPixel(row+2, col, val); + setPixel(row, col+1, val); + setPixel(row+1, col+1, val); + setPixel(row+2, col+1, val); + setPixel(row, col+2, val); + setPixel(row+1, col+2, val); + setPixel(row+2, col+2, val); + } else { + setPixel(row, col, val); + } + } + } + fis.close(); + */ + } else { + if (DEBUG) + System.out.println("none : " + filename); + } + } + + } + } + } + + // post fill zero + if (SRTM_NO_ZERO) { + for (int row = 0; row < NROWS; row++) { + for (int col = 0; col < NCOLS; col++) { + if (imagePixels[row * NCOLS + col] == 0) imagePixels[row * NCOLS + col] = NODATA; } } } @@ -126,7 +334,7 @@ public class ConvertLidarTile { raster.ncols = NCOLS; raster.halfcol = halfCol5; raster.noDataValue = NODATA; - raster.cellsize = 1 / 1200.; + raster.cellsize = 1. / row_length; raster.xllcorner = lonDegreeStart - (0.5 + extraBorder) * raster.cellsize; raster.yllcorner = latDegreeStart - (0.5 + extraBorder) * raster.cellsize; raster.eval_array = imagePixels; @@ -156,6 +364,7 @@ public class ConvertLidarTile { } } } + imagePixels = null; } private static String formatLon(int lon) { @@ -181,19 +390,6 @@ public class ConvertLidarTile { return s + n.substring(n.length() - 2); } - public static void main(String[] args) throws Exception { - String filename90 = args[0]; - String filename30 = filename90.substring(0, filename90.length() - 3) + "bef"; - - int srtmLonIdx = Integer.parseInt(filename90.substring(5, 7).toLowerCase()); - int srtmLatIdx = Integer.parseInt(filename90.substring(8, 10).toLowerCase()); - - int ilon_base = (srtmLonIdx - 1) * 5 - 180; - int ilat_base = 150 - srtmLatIdx * 5 - 90; - - doConvert(args[1], ilon_base, ilat_base, filename30); - } - public SrtmRaster getRaster(File f, double lon, double lat) throws Exception { long fileSize; InputStream inputStream; @@ -231,7 +427,7 @@ public class ConvertLidarTile { // prefill as NODATA Arrays.fill(imagePixels, NODATA); - readHgtFromStream(inputStream, 0, 0, rowLength); + readHgtFromStream(inputStream, 0, 0, rowLength, 1); inputStream.close(); SrtmRaster raster = new SrtmRaster(); From 859b401c8b9e1a9ebe206989a27093f1e0b6e553 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 27 Oct 2023 16:25:44 +0200 Subject: [PATCH 059/173] unable raster weighting --- .../src/main/java/btools/mapcreator/RasterCoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/RasterCoder.java b/brouter-map-creator/src/main/java/btools/mapcreator/RasterCoder.java index 233e3ea..72b028c 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/RasterCoder.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/RasterCoder.java @@ -45,7 +45,7 @@ public class RasterCoder { _decodeRaster(raster, is); - raster.usingWeights = raster.ncols > 6001; + raster.usingWeights = false; // raster.ncols > 6001; long t1 = System.currentTimeMillis(); System.out.println("finished decoding in " + (t1 - t0) + " ms ncols=" + raster.ncols + " nrows=" + raster.nrows); From 69489c2b7e3063e156e9d22cfc0633686e0347ae Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 27 Oct 2023 16:26:21 +0200 Subject: [PATCH 060/173] updated test --- .../src/test/java/btools/mapcreator/MapcreatorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brouter-map-creator/src/test/java/btools/mapcreator/MapcreatorTest.java b/brouter-map-creator/src/test/java/btools/mapcreator/MapcreatorTest.java index beb4cbd..b509bff 100644 --- a/brouter-map-creator/src/test/java/btools/mapcreator/MapcreatorTest.java +++ b/brouter-map-creator/src/test/java/btools/mapcreator/MapcreatorTest.java @@ -42,7 +42,7 @@ public class MapcreatorTest { File unodes55 = new File(tmpdir, "unodes55"); File bordernodes = new File(tmpdir, "bordernodes.dat"); unodes55.mkdir(); - new PosUnifier().process(nodes55, unodes55, borderFile, bordernodes, workingDir.getAbsolutePath()); + new PosUnifier().process(nodes55, unodes55, borderFile, bordernodes, workingDir.getAbsolutePath(), null); // run WayLinker File segments = new File(tmpdir, "segments"); From 2be7e0c19c8a6329f28bb8d4b754bc529e7f8c6d Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 6 Nov 2023 12:04:47 +0100 Subject: [PATCH 061/173] rename SrmtRaster --- .../btools/mapcreator/ElevationRaster.java | 264 ++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 brouter-map-creator/src/main/java/btools/mapcreator/ElevationRaster.java diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/ElevationRaster.java b/brouter-map-creator/src/main/java/btools/mapcreator/ElevationRaster.java new file mode 100644 index 0000000..478811c --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/ElevationRaster.java @@ -0,0 +1,264 @@ +package btools.mapcreator; + +import btools.util.ReducedMedianFilter; + +/** + * Container for a elevation raster + it's meta-data + * + * @author ab + */ +public class ElevationRaster { + public int ncols; + public int nrows; + public boolean halfcol; + public double xllcorner; + public double yllcorner; + public double cellsize; + public short[] eval_array; + public short noDataValue; + + public boolean usingWeights = false; + + private boolean missingData = false; + + public short getElevation(int ilon, int ilat) { + double lon = ilon / 1000000. - 180.; + double lat = ilat / 1000000. - 90.; + + if (usingWeights) { + return getElevationFromShiftWeights(lon, lat); + } + + // no weights calculated, use 2d linear interpolation + double dcol = (lon - xllcorner) / cellsize - 0.5; + double drow = (lat - yllcorner) / cellsize - 0.5; + int row = (int) drow; + int col = (int) dcol; + if (col < 0) col = 0; + if (col >= ncols - 1) col = ncols - 2; + if (row < 0) row = 0; + if (row >= nrows - 1) row = nrows - 2; + double wrow = drow - row; + double wcol = dcol - col; + missingData = false; + +// System.out.println( "wrow=" + wrow + " wcol=" + wcol + " row=" + row + " col=" + col ); + double eval = (1. - wrow) * (1. - wcol) * get(row, col) + + (wrow) * (1. - wcol) * get(row + 1, col) + + (1. - wrow) * (wcol) * get(row, col + 1) + + (wrow) * (wcol) * get(row + 1, col + 1); +// System.out.println( "eval=" + eval ); + return missingData ? Short.MIN_VALUE : (short) (eval * 4); + } + + private short get(int r, int c) { + short e = eval_array[(nrows - 1 - r) * ncols + c]; + if (e == Short.MIN_VALUE) missingData = true; + return e; + } + + private short getElevationFromShiftWeights(double lon, double lat) { + // calc lat-idx and -weight + double alat = lat < 0. ? -lat : lat; + alat /= 5.; + int latIdx = (int) alat; + double wlat = alat - latIdx; + + double dcol = (lon - xllcorner) / cellsize; + double drow = (lat - yllcorner) / cellsize; + int row = (int) drow; + int col = (int) dcol; + + double dgx = (dcol - col) * gridSteps; + double dgy = (drow - row) * gridSteps; + +// System.out.println( "wrow=" + wrow + " wcol=" + wcol + " row=" + row + " col=" + col ); + + int gx = (int) (dgx); + int gy = (int) (dgy); + + double wx = dgx - gx; + double wy = dgy - gy; + + double w00 = (1. - wx) * (1. - wy); + double w01 = (1. - wx) * (wy); + double w10 = (wx) * (1. - wy); + double w11 = (wx) * (wy); + + Weights[][] w0 = getWeights(latIdx); + Weights[][] w1 = getWeights(latIdx + 1); + + missingData = false; + + double m0 = w00 * getElevation(w0[gx][gy], row, col) + + w01 * getElevation(w0[gx][gy + 1], row, col) + + w10 * getElevation(w0[gx + 1][gy], row, col) + + w11 * getElevation(w0[gx + 1][gy + 1], row, col); + double m1 = w00 * getElevation(w1[gx][gy], row, col) + + w01 * getElevation(w1[gx][gy + 1], row, col) + + w10 * getElevation(w1[gx + 1][gy], row, col) + + w11 * getElevation(w1[gx + 1][gy + 1], row, col); + + if (missingData) return Short.MIN_VALUE; + double m = (1. - wlat) * m0 + wlat * m1; + return (short) (m * 2); + } + + private ReducedMedianFilter rmf = new ReducedMedianFilter(256); + + private double getElevation(Weights w, int row, int col) { + if (missingData) { + return 0.; + } + int nx = w.nx; + int ny = w.ny; + int mx = nx / 2; // mean pixels + int my = ny / 2; + + // System.out.println( "nx="+ nx + " ny=" + ny ); + + rmf.reset(); + + for (int ix = 0; ix < nx; ix++) { + for (int iy = 0; iy < ny; iy++) { + short val = get(row + iy - my, col + ix - mx); + rmf.addSample(w.getWeight(ix, iy), val); + } + } + return missingData ? 0. : rmf.calcEdgeReducedMedian(filterCenterFraction); + } + + + private static class Weights { + int nx; + int ny; + double[] weights; + long total = 0; + + Weights(int nx, int ny) { + this.nx = nx; + this.ny = ny; + weights = new double[nx * ny]; + } + + void inc(int ix, int iy) { + weights[iy * nx + ix] += 1.; + total++; + } + + void normalize(boolean verbose) { + for (int iy = 0; iy < ny; iy++) { + StringBuilder sb = verbose ? new StringBuilder() : null; + for (int ix = 0; ix < nx; ix++) { + weights[iy * nx + ix] /= total; + if (sb != null) { + int iweight = (int) (1000 * weights[iy * nx + ix] + 0.5); + String sval = " " + iweight; + sb.append(sval.substring(sval.length() - 4)); + } + } + if (sb != null) { + System.out.println(sb); + System.out.println(); + } + } + } + + double getWeight(int ix, int iy) { + return weights[iy * nx + ix]; + } + } + + private static int gridSteps = 10; + private static Weights[][][] allShiftWeights = new Weights[17][][]; + + private static double filterCenterFraction = 0.2; + private static double filterDiscRadius = 4.999; // in pixels + + static { + String sRadius = System.getProperty("filterDiscRadius"); + if (sRadius != null && sRadius.length() > 0) { + filterDiscRadius = Integer.parseInt(sRadius); + System.out.println("using filterDiscRadius = " + filterDiscRadius); + } + String sFraction = System.getProperty("filterCenterFraction"); + if (sFraction != null && sFraction.length() > 0) { + filterCenterFraction = Integer.parseInt(sFraction) / 100.; + System.out.println("using filterCenterFraction = " + filterCenterFraction); + } + } + + + // calculate interpolation weights from the overlap of a probe disc of given radius at given latitude + // ( latIndex = 0 -> 0 deg, latIndex = 16 -> 80 degree) + + private static Weights[][] getWeights(int latIndex) { + int idx = latIndex < 16 ? latIndex : 16; + + Weights[][] res = allShiftWeights[idx]; + if (res == null) { + res = calcWeights(idx); + allShiftWeights[idx] = res; + } + return res; + } + + private static Weights[][] calcWeights(int latIndex) { + double coslat = Math.cos(latIndex * 5. / 57.3); + + // radius in pixel units + double ry = filterDiscRadius; + double rx = ry / coslat; + + // gridsize is 2*radius + 1 cell + int nx = ((int) rx) * 2 + 3; + int ny = ((int) ry) * 2 + 3; + + System.out.println("nx=" + nx + " ny=" + ny); + + int mx = nx / 2; // mean pixels + int my = ny / 2; + + // create a matrix for the relative intergrid-position + + Weights[][] shiftWeights = new Weights[gridSteps + 1][]; + + // loop the intergrid-position + for (int gx = 0; gx <= gridSteps; gx++) { + shiftWeights[gx] = new Weights[gridSteps + 1]; + double x0 = mx + ((double) gx) / gridSteps; + + for (int gy = 0; gy <= gridSteps; gy++) { + double y0 = my + ((double) gy) / gridSteps; + + // create the weight-matrix + Weights weights = new Weights(nx, ny); + shiftWeights[gx][gy] = weights; + + double sampleStep = 0.001; + + for (double x = -1. + sampleStep / 2.; x < 1.; x += sampleStep) { + double mx2 = 1. - x * x; + + int x_idx = (int) (x0 + x * rx); + + for (double y = -1. + sampleStep / 2.; y < 1.; y += sampleStep) { + if (y * y > mx2) { + continue; + } + // we are in the ellipse, see what pixel we are on + int y_idx = (int) (y0 + y * ry); + weights.inc(x_idx, y_idx); + } + } + weights.normalize(true); + } + } + return shiftWeights; + } + + @Override + public String toString() { + return ncols + "," + nrows + "," + halfcol + "," + xllcorner + "," + yllcorner + "," + cellsize + "," + noDataValue + "," + usingWeights; + } +} From 50eb62361daca00b9838e3e3effccdc79542ea6f Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 6 Nov 2023 12:06:43 +0100 Subject: [PATCH 062/173] rename RasterCoder --- .../mapcreator/ElevationRasterCoder.java | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 brouter-map-creator/src/main/java/btools/mapcreator/ElevationRasterCoder.java diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/ElevationRasterCoder.java b/brouter-map-creator/src/main/java/btools/mapcreator/ElevationRasterCoder.java new file mode 100644 index 0000000..02a841d --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/ElevationRasterCoder.java @@ -0,0 +1,117 @@ +package btools.mapcreator; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import btools.util.MixCoderDataInputStream; +import btools.util.MixCoderDataOutputStream; + +// +// Encode/decode a raster +// + +public class ElevationRasterCoder { + public void encodeRaster(ElevationRaster raster, OutputStream os) throws IOException { + DataOutputStream dos = new DataOutputStream(os); + + long t0 = System.currentTimeMillis(); + + dos.writeInt(raster.ncols); + dos.writeInt(raster.nrows); + dos.writeBoolean(raster.halfcol); + dos.writeDouble(raster.xllcorner); + dos.writeDouble(raster.yllcorner); + dos.writeDouble(raster.cellsize); + dos.writeShort(raster.noDataValue); + + _encodeRaster(raster, os); + long t1 = System.currentTimeMillis(); + + System.out.println("finished encoding in " + (t1 - t0) + " ms"); + } + + public ElevationRaster decodeRaster(InputStream is) throws IOException { + DataInputStream dis = new DataInputStream(is); + + long t0 = System.currentTimeMillis(); + + ElevationRaster raster = new ElevationRaster(); + raster.ncols = dis.readInt(); + raster.nrows = dis.readInt(); + raster.halfcol = dis.readBoolean(); + raster.xllcorner = dis.readDouble(); + raster.yllcorner = dis.readDouble(); + raster.cellsize = dis.readDouble(); + raster.noDataValue = dis.readShort(); + raster.eval_array = new short[raster.ncols * raster.nrows]; + + _decodeRaster(raster, is); + + raster.usingWeights = false; // raster.ncols > 6001; + + long t1 = System.currentTimeMillis(); + System.out.println("finished decoding in " + (t1 - t0) + " ms ncols=" + raster.ncols + " nrows=" + raster.nrows); + return raster; + } + + + private void _encodeRaster(ElevationRaster raster, OutputStream os) throws IOException { + MixCoderDataOutputStream mco = new MixCoderDataOutputStream(os); + int nrows = raster.nrows; + int ncols = raster.ncols; + short[] pixels = raster.eval_array; + int colstep = raster.halfcol ? 2 : 1; + + for (int row = 0; row < nrows; row++) { + short lastval = Short.MIN_VALUE; // nodata + for (int col = 0; col < ncols; col += colstep) { + short val = pixels[row * ncols + col]; + if (val == -32766) { + val = lastval; // replace remaining (border) skips + } else { + lastval = val; + } + + // remap nodata + int code = val == Short.MIN_VALUE ? -1 : (val < 0 ? val - 1 : val); + mco.writeMixed(code); + } + } + mco.flush(); + } + + private void _decodeRaster(ElevationRaster raster, InputStream is) throws IOException { + MixCoderDataInputStream mci = new MixCoderDataInputStream(is); + int nrows = raster.nrows; + int ncols = raster.ncols; + short[] pixels = raster.eval_array; + int colstep = raster.halfcol ? 2 : 1; + + for (int row = 0; row < nrows; row++) { + for (int col = 0; col < ncols; col += colstep) { + int code = mci.readMixed(); + + // remap nodata + int v30 = code == -1 ? Short.MIN_VALUE : (code < 0 ? code + 1 : code); + if (raster.usingWeights && v30 > -32766) { + v30 *= 2; + } + pixels[row * ncols + col] = (short) (v30); + } + if (raster.halfcol) { + for (int col = 1; col < ncols - 1; col += colstep) { + int l = (int) pixels[row * ncols + col - 1]; + int r = (int) pixels[row * ncols + col + 1]; + short v30 = Short.MIN_VALUE; // nodata + if (l > -32766 && r > -32766) { + v30 = (short) ((l + r) / 2); + } + pixels[row * ncols + col] = v30; + } + } + } + } +} From 5198559c7795d140237282de073b18243b564ef4 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 6 Nov 2023 12:48:42 +0100 Subject: [PATCH 063/173] moved converter to one file --- .../ElevationRasterTileConverter.java | 546 ++++++++++++++++++ 1 file changed, 546 insertions(+) create mode 100644 brouter-map-creator/src/main/java/btools/mapcreator/ElevationRasterTileConverter.java diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/ElevationRasterTileConverter.java b/brouter-map-creator/src/main/java/btools/mapcreator/ElevationRasterTileConverter.java new file mode 100644 index 0000000..9b251fc --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/ElevationRasterTileConverter.java @@ -0,0 +1,546 @@ +package btools.mapcreator; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Locale; +import java.util.StringTokenizer; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class ElevationRasterTileConverter { + + public static final boolean DEBUG = false; + + public static final short NODATA2 = -32767; // hgt-formats nodata + public static final short NODATA = Short.MIN_VALUE; + + private static final String HGT_FILE_EXT = ".hgt"; + private static final int HGT_BORDER_OVERLAP = 1; + private static final int HGT_3ASEC_ROWS = 1201; // 3 arc second resolution (90m) + private static final int HGT_3ASEC_FILE_SIZE = HGT_3ASEC_ROWS * HGT_3ASEC_ROWS * Short.BYTES; + private static final int HGT_1ASEC_ROWS = 3601; // 1 arc second resolution (30m) + private static final int SRTM3_ROW_LENGTH = 1200; // number of elevation values per line + private static final int SRTM1_ROW_LENGTH = 3600; + private static final boolean SRTM_NO_ZERO = true; + + private int NROWS; + private int NCOLS; + private int ROW_LENGTH; + private short[] imagePixels; + + /** + * This generates elevation raster files with a 5x5 degree scope + * The output can be for 1sec (18000x18000 points) + * or for 3sec (6000x6000 points) + * When using 1sec input files a not found area can be called from 3sec pool + * The input can be 1x1 degree 1sec/3sec hgt files (also packed as zip) + * or 5x5 degree 3sec asc files (delivered as zip) + * Arguments for single file generation: + * ElevationRasterTileConverter [arc seconds (1 or 3,default=3)] [hgt-fallback-data-dir] + * Samples + * $ ... ElevationRasterTileConverter srtm_34_-1 ./srtm/hgt3sec ./srtm/srtm3_bef + * $ ... ElevationRasterTileConverter srtm_34_-1 ./srtm/hgt1sec ./srtm/srtm1_bef 1 + * $ ... ElevationRasterTileConverter srtm_34_-1 ./srtm/hgt1sec ./srtm/srtm1_bef 1 ./srtm/hgt3sec + *

+ * Arguments for multi file generation (world wide): + * $ ... ElevationRasterTileConverter all ./srtm/hgt3sec ./srtm/srtm3_bef + * $ ... ElevationRasterTileConverter all ./srtm/hgt1sec ./srtm/srtm1_bef 1 + * $ ... ElevationRasterTileConverter all ./srtm/hgt1sec ./srtm/srtm1_bef 1 ./srtm/hgt3sec + * + * @param args + * @throws Exception + */ + public static void main(String[] args) throws Exception { + if (args.length == 3 || args.length == 4 || args.length == 5) { + String filename90 = args[0]; + if ("all".equals(filename90)) { + //if (DEBUG) + System.out.println("raster convert all "); + new ElevationRasterTileConverter().doConvertAll(args[1], args[2], (args.length > 3 ? args[3] : null), (args.length == 5 ? args[4] : null)); + return; + } + // old filenames only + String filename30 = filename90 + ".bef"; //filename90.substring(0, filename90.length() - 3) + "bef"; + + int srtmLonIdx = Integer.parseInt(filename90.substring(5, 7).toLowerCase()); + int srtmLatIdx = Integer.parseInt(filename90.substring(8, 10).toLowerCase()); + + int ilon_base = (srtmLonIdx - 1) * 5 - 180; + int ilat_base = 150 - srtmLatIdx * 5 - 90; + int row_length = SRTM3_ROW_LENGTH; + String fallbackdir = null; + if (args.length > 3) { + row_length = (Integer.parseInt(args[3]) == 1 ? SRTM1_ROW_LENGTH : SRTM3_ROW_LENGTH); + fallbackdir = (args.length == 5 ? args[4] : null); + } + //if (DEBUG) + System.out.println("raster convert " + ilon_base + " " + ilat_base + " from " + srtmLonIdx + " " + srtmLatIdx + " f: " + filename90 + " rowl " + row_length); + + new ElevationRasterTileConverter().doConvert(args[1], ilon_base, ilat_base, args[2] + "/" + filename30, row_length, fallbackdir); + } else { + System.out.println("usage: java [arc seconds (1 or 3,default=3)] [hgt-fallback-data-dir]"); + System.out.println("or java all [arc seconds (1 or 3, default=3)] [hgt-fallback-data-dir]"); + return; + } + } + + private void doConvertAll(String hgtdata, String outdir, String rlen, String hgtfallbackdata) throws Exception { + int row_length = SRTM3_ROW_LENGTH; + if (rlen != null) { + row_length = (Integer.parseInt(rlen) == 1 ? SRTM1_ROW_LENGTH : SRTM3_ROW_LENGTH); + } + String filename30; + for (int ilon_base = -180; ilon_base < 180; ilon_base += 5) { + for (int ilat_base = 85; ilat_base > -90; ilat_base -= 5) { + if (PosUnifier.UseRasterRd5FileName) { + filename30 = genFilenameRd5(ilon_base, ilat_base); + } else { + filename30 = genFilenameOld(ilon_base, ilat_base); + } + if (DEBUG) + System.out.println("lidar convert all: " + filename30); + doConvert(hgtdata, ilon_base, ilat_base, outdir + "/" + filename30, row_length, hgtfallbackdata); + } + } + } + + static String genFilenameOld(int ilon_base, int ilat_base) { + int srtmLonIdx = ((ilon_base + 180) / 5) + 1; + int srtmLatIdx = (60 - ilat_base) / 5; + return String.format(Locale.US, "srtm_%02d_%02d.bef", srtmLonIdx, srtmLatIdx); + } + + static String genFilenameRd5(int ilon_base, int ilat_base) { + return String.format("srtm_%s_%s.bef", ilon_base < 0 ? "W" + (-ilon_base) : "E" + ilon_base, + ilat_base < 0 ? "S" + (-ilat_base) : "N" + ilat_base); + } + + private void readHgtZip(String filename, int rowOffset, int colOffset, int row_length, int scale) throws Exception { + ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(filename))); + try { + for (; ; ) { + ZipEntry ze = zis.getNextEntry(); + if (ze == null) break; + if (ze.getName().toLowerCase().endsWith(HGT_FILE_EXT)) { + readHgtFromStream(zis, rowOffset, colOffset, row_length, scale); + return; + } + } + } finally { + zis.close(); + } + } + + private void readHgtFromStream(InputStream is, int rowOffset, int colOffset, int rowLength, int scale) + throws Exception { + DataInputStream dis = new DataInputStream(new BufferedInputStream(is)); + for (int ir = 0; ir < rowLength; ir++) { + int row = rowOffset + ir * scale; + + for (int ic = 0; ic < rowLength; ic++) { + int col = colOffset + ic * scale; + + int i1 = dis.read(); // msb first! + int i0 = dis.read(); + + if (i0 == -1 || i1 == -1) + throw new RuntimeException("unexpected end of file reading hgt entry!"); + + short val = (short) ((i1 << 8) | i0); + + if (val == NODATA2) { + val = NODATA; + } + if (scale == 3) { + setPixel(row, col, val); + setPixel(row + 1, col, val); + setPixel(row + 2, col, val); + setPixel(row, col + 1, val); + setPixel(row + 1, col + 1, val); + setPixel(row + 2, col + 1, val); + setPixel(row, col + 2, val); + setPixel(row + 1, col + 2, val); + setPixel(row + 2, col + 2, val); + } else { + setPixel(row, col, val); + } + } + } + } + + private void readHgtFile(File file, int rowOffset, int colOffset, int row_length, int scale) + throws Exception { + + if (DEBUG) + System.out.println("read: " + file + " " + row_length); + + FileInputStream fis = new FileInputStream(file); + try { + readHgtFromStream(fis, rowOffset, colOffset, row_length, scale); + } finally { + fis.close(); + } + } + + /* + private void readFallbackFile(File file, int rowOffset, int colOffset, int row_length) + throws Exception { + int rowLength; + int scale; + if (file.length() > HGT_3ASEC_FILE_SIZE) { + rowLength = HGT_1ASEC_ROWS; + scale = 1; + } else { + rowLength = HGT_3ASEC_ROWS; + scale = 3; + } + if (DEBUG) + System.out.println("read fallback: " + file + " " + rowLength); + + FileInputStream fis = new FileInputStream(file); + try { + readHgtFromStream(fis, rowOffset, colOffset, rowLength, scale); + } finally { + fis.close(); + } + } + */ + + private void readAscZip(File file, ElevationRaster raster) throws Exception { + + ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(file))); + try { + for (; ; ) { + ZipEntry ze = zis.getNextEntry(); + if (ze.getName().endsWith(".asc")) { + readAscFromStream(zis, raster); + return; + } + } + } finally { + zis.close(); + } + } + + private String secondToken(String s) { + StringTokenizer tk = new StringTokenizer(s, " "); + tk.nextToken(); + return tk.nextToken(); + } + + private void readAscFromStream(InputStream is, ElevationRaster raster) throws Exception { + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + int linenr = 0; + for (; ; ) { + linenr++; + if (linenr <= 6) { + String line = br.readLine(); + if (linenr == 1) + raster.ncols = Integer.parseInt(secondToken(line)); + else if (linenr == 2) + raster.nrows = Integer.parseInt(secondToken(line)); + else if (linenr == 3) + raster.xllcorner = Double.parseDouble(secondToken(line)); + else if (linenr == 4) + raster.yllcorner = Double.parseDouble(secondToken(line)); + else if (linenr == 5) + raster.cellsize = Double.parseDouble(secondToken(line)); + else if (linenr == 6) { + // nodata ignored here ( < -250 assumed nodata... ) + // raster.noDataValue = Short.parseShort( secondToken( line ) ); + raster.eval_array = new short[raster.ncols * raster.nrows]; + } + } else { + int row = 0; + int col = 0; + int n = 0; + boolean negative = false; + for (; ; ) { + int c = br.read(); + if (c < 0) + break; + if (c == ' ') { + if (negative) + n = -n; + short val = n < -250 ? Short.MIN_VALUE : (short) (n); + + raster.eval_array[row * raster.ncols + col] = val; + if (++col == raster.ncols) { + col = 0; + ++row; + } + n = 0; + negative = false; + } else if (c >= '0' && c <= '9') { + n = 10 * n + (c - '0'); + } else if (c == '-') { + negative = true; + } + } + break; + } + } + br.close(); + } + + + private void setPixel(int row, int col, short val) { + if (row >= 0 && row < NROWS && col >= 0 && col < NCOLS) { + imagePixels[row * NCOLS + col] = val; + } + } + + private short getPixel(int row, int col) { + if (row >= 0 && row < NROWS && col >= 0 && col < NCOLS) { + return imagePixels[row * NCOLS + col]; + } + return NODATA; + } + + + public void doConvert(String inputDir, int lonDegreeStart, int latDegreeStart, String outputFile, int row_length, String hgtfallbackdata) throws Exception { + int extraBorder = 0; + + //List foundList = new ArrayList<>(); + //List notfoundList = new ArrayList<>(); + + boolean hgtfound = false; + boolean ascfound = false; + String filename = null; + //if (row_length == SRTM1_ROW_LENGTH) + { + // check for sources w/o border + for (int latIdx = 0; latIdx < 5; latIdx++) { + int latDegree = latDegreeStart + latIdx; + + for (int lonIdx = 0; lonIdx < 5; lonIdx++) { + int lonDegree = lonDegreeStart + lonIdx; + + filename = inputDir + "/" + formatLat(latDegree) + formatLon(lonDegree) + ".zip"; + File f = new File(filename); + if (f.exists() && f.length() > 0) { + hgtfound = true; + break; + } + filename = filename.substring(0, filename.length() - 4) + ".hgt"; + f = new File(filename); + if (f.exists() && f.length() > 0) { + hgtfound = true; + break; + } + } + } + if (!hgtfound) { + filename = inputDir + "/" + genFilenameOld(lonDegreeStart, latDegreeStart).substring(0, 10) + ".zip"; + File f = new File(filename); + if (f.exists() && f.length() > 0) { + ascfound = true; + } + } + } + if (hgtfound) { // init when found + NROWS = 5 * row_length + 1 + 2 * extraBorder; + NCOLS = 5 * row_length + 1 + 2 * extraBorder; + imagePixels = new short[NROWS * NCOLS]; // 650 MB ! + + // prefill as NODATA + Arrays.fill(imagePixels, NODATA); + } else if (!ascfound) { + if (DEBUG) + System.out.println("none data: " + lonDegreeStart + " " + latDegreeStart); + return; + } + + if (hgtfound) { + for (int latIdx = -1; latIdx <= 5; latIdx++) { + int latDegree = latDegreeStart + latIdx; + int rowOffset = extraBorder + (4 - latIdx) * row_length; + + for (int lonIdx = -1; lonIdx <= 5; lonIdx++) { + int lonDegree = lonDegreeStart + lonIdx; + int colOffset = extraBorder + lonIdx * row_length; + + filename = inputDir + "/" + formatLat(latDegree) + formatLon(lonDegree) + ".zip"; + File f = new File(filename); + if (f.exists() && f.length() > 0) { + if (DEBUG) + System.out.println("exist: " + filename); + readHgtZip(filename, rowOffset, colOffset, row_length + 1, 1); + continue; + } + filename = filename.substring(0, filename.length() - 4) + ".hgt"; + f = new File(filename); + if (f.exists() && f.length() > 0) { + if (DEBUG) + System.out.println("exist: " + filename); + readHgtFile(f, rowOffset, colOffset, row_length + 1, 1); + continue; + } else { + if (hgtfallbackdata != null) { + filename = hgtfallbackdata + "/" + formatLat(latDegree) + formatLon(lonDegree) + ".hgt"; + f = new File(filename); + if (f.exists() && f.length() > 0) { + readHgtFile(f, rowOffset, colOffset, SRTM3_ROW_LENGTH + 1, 3); + continue; + } + filename = filename.substring(0, filename.length() - 4) + ".zip"; + f = new File(filename); + if (f.exists() && f.length() > 0) { + readHgtZip(filename, rowOffset, colOffset, SRTM3_ROW_LENGTH + 1, 3); + } else { + if (DEBUG) + System.out.println("none : " + filename); + } + } + + } + } + } + + // post fill zero + if (SRTM_NO_ZERO) { + for (int row = 0; row < NROWS; row++) { + for (int col = 0; col < NCOLS; col++) { + if (imagePixels[row * NCOLS + col] == 0) imagePixels[row * NCOLS + col] = NODATA; + } + } + } + } + + boolean halfCol5 = false; // no halfcol tiles in lidar data (?) + + + ElevationRaster raster = new ElevationRaster(); + if (hgtfound) { + raster.nrows = NROWS; + raster.ncols = NCOLS; + raster.halfcol = halfCol5; + raster.noDataValue = NODATA; + raster.cellsize = 1. / row_length; + raster.xllcorner = lonDegreeStart - (0.5 + extraBorder) * raster.cellsize; + raster.yllcorner = latDegreeStart - (0.5 + extraBorder) * raster.cellsize; + raster.eval_array = imagePixels; + } + + if (ascfound) { + File f = new File(filename); + readAscZip(f, raster); + } + + // encode the raster + OutputStream os = new BufferedOutputStream(new FileOutputStream(outputFile)); + new ElevationRasterCoder().encodeRaster(raster, os); + os.close(); + + // decode the raster + InputStream is = new BufferedInputStream(new FileInputStream(outputFile)); + ElevationRaster raster2 = new ElevationRasterCoder().decodeRaster(is); + is.close(); + + short[] pix2 = raster2.eval_array; + if (pix2.length != raster.eval_array.length) + throw new RuntimeException("length mismatch!"); + + // compare decoding result + for (int row = 0; row < raster.nrows; row++) { + int colstep = halfCol5 ? 2 : 1; + for (int col = 0; col < raster.ncols; col += colstep) { + int idx = row * raster.ncols + col; + short p2 = pix2[idx]; + if (p2 != raster.eval_array[idx]) { + throw new RuntimeException("content mismatch: p2=" + p2 + " p1=" + raster.eval_array[idx]); + } + } + } + imagePixels = null; + } + + private static String formatLon(int lon) { + if (lon >= 180) + lon -= 180; // TODO: w180 oder E180 ? + + String s = "E"; + if (lon < 0) { + lon = -lon; + s = "W"; + } + String n = "000" + lon; + return s + n.substring(n.length() - 3); + } + + private static String formatLat(int lat) { + String s = "N"; + if (lat < 0) { + lat = -lat; + s = "S"; + } + String n = "00" + lat; + return s + n.substring(n.length() - 2); + } + + + public ElevationRaster getRaster(File f, double lon, double lat) throws Exception { + long fileSize; + InputStream inputStream; + + if (f.getName().toLowerCase().endsWith(".zip")) { + ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(f))); + for (; ; ) { + ZipEntry ze = zis.getNextEntry(); + if (ze == null) { + throw new FileNotFoundException(f.getName() + " doesn't contain a " + HGT_FILE_EXT + " file."); + } + if (ze.getName().toLowerCase().endsWith(HGT_FILE_EXT)) { + fileSize = ze.getSize(); + inputStream = zis; + break; + } + } + } else { + fileSize = f.length(); + inputStream = new FileInputStream(f); + } + + int rowLength; + if (fileSize > HGT_3ASEC_FILE_SIZE) { + rowLength = HGT_1ASEC_ROWS; + } else { + rowLength = HGT_3ASEC_ROWS; + } + + // stay at 1 deg * 1 deg raster + NROWS = rowLength; + NCOLS = rowLength; + + imagePixels = new short[NROWS * NCOLS]; + + // prefill as NODATA + Arrays.fill(imagePixels, NODATA); + readHgtFromStream(inputStream, 0, 0, rowLength, 1); + inputStream.close(); + + ElevationRaster raster = new ElevationRaster(); + raster.nrows = NROWS; + raster.ncols = NCOLS; + raster.halfcol = false; // assume full resolution + raster.noDataValue = NODATA; + raster.cellsize = 1. / (double) (rowLength - HGT_BORDER_OVERLAP); + raster.xllcorner = (int) (lon < 0 ? lon - 1 : lon); //onDegreeStart - raster.cellsize; + raster.yllcorner = (int) (lat < 0 ? lat - 1 : lat); //latDegreeStart - raster.cellsize; + raster.eval_array = imagePixels; + + return raster; + } + + +} From 36d692da84d96d8454fa2c7876ea3fd63452c65c Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 6 Nov 2023 12:50:50 +0100 Subject: [PATCH 064/173] set new raster calls --- .../java/btools/mapcreator/PosUnifier.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java b/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java index 10e6605..e9a43fe 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java @@ -7,7 +7,6 @@ import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.HashMap; -import java.util.Locale; import java.util.Map; import btools.util.CompactLongSet; @@ -24,17 +23,17 @@ import btools.util.FrozenLongSet; */ public class PosUnifier extends MapCreatorBase { - public static final boolean UseLidarRd5FileName = false; + public static final boolean UseRasterRd5FileName = false; private DiffCoderDataOutputStream nodesOutStream; private DiffCoderDataOutputStream borderNodesOut; private File nodeTilesOut; private CompactLongSet[] positionSets; - private Map srtmmap; + private Map srtmmap; private int lastSrtmLonIdx; private int lastSrtmLatIdx; - private SrtmRaster lastSrtmRaster; + private ElevationRaster lastSrtmRaster; private String srtmdir; private String srtmfallbackdir; @@ -51,7 +50,7 @@ public class PosUnifier extends MapCreatorBase { NodeData n = new NodeData(1, lon, lat); short selev = Short.MIN_VALUE; - SrtmRaster srtm = null; + ElevationRaster srtm = null; /* // check hgt direct srtm = posu.hgtForNode(n.ilon, n.ilat); @@ -60,7 +59,7 @@ public class PosUnifier extends MapCreatorBase { } else { System.out.println("hgtForNode no data"); } - posu.resetSrtm(); + posu.resetElevationRaster(); System.out.println("-----> selv for hgt " + lat + ", " + lon + " = " + selev + " = " + (selev / 4.)); srtm = null; selev = Short.MIN_VALUE; @@ -69,7 +68,7 @@ public class PosUnifier extends MapCreatorBase { srtm = posu.srtmForNode(n.ilon, n.ilat); } if (srtm != null) selev = srtm.getElevation(n.ilon, n.ilat); - posu.resetSrtm(); + posu.resetElevationRaster(); System.out.println("-----> selv for bef " + lat + ", " + lon + " = " + selev + " = " + (selev / 4.)); return; } else if (args.length != 5 && args.length != 6) { @@ -107,7 +106,7 @@ public class PosUnifier extends MapCreatorBase { @Override public void nodeFileStart(File nodefile) throws Exception { - resetSrtm(); + resetElevationRaster(); nodesOutStream = createOutStream(fileFromTemplate(nodefile, nodeTilesOut, "u5d")); @@ -125,7 +124,7 @@ public class PosUnifier extends MapCreatorBase { srtm = srtmForNode(n.ilon, n.ilat); } */ - SrtmRaster srtm = srtmForNode(n.ilon, n.ilat); + ElevationRaster srtm = srtmForNode(n.ilon, n.ilat); if (srtm != null) n.selev = srtm.getElevation(n.ilon, n.ilat); findUniquePos(n); @@ -139,7 +138,7 @@ public class PosUnifier extends MapCreatorBase { @Override public void nodeFileEnd(File nodeFile) throws Exception { nodesOutStream.close(); - resetSrtm(); + resetElevationRaster(); } private boolean checkAdd(int lon, int lat) { @@ -187,7 +186,7 @@ public class PosUnifier extends MapCreatorBase { * get the srtm data set for a position srtm coords are * srtm__ where srtmLon = 180 + lon, srtmLat = 60 - lat */ - private SrtmRaster srtmForNode(int ilon, int ilat) throws Exception { + private ElevationRaster srtmForNode(int ilon, int ilat) throws Exception { int srtmLonIdx = (ilon + 5000000) / 5000000; int srtmLatIdx = (654999999 - ilat) / 5000000 - 100; // ugly negative rounding... @@ -198,7 +197,7 @@ public class PosUnifier extends MapCreatorBase { lastSrtmLatIdx = srtmLatIdx; String filename; - if (UseLidarRd5FileName) { + if (UseRasterRd5FileName) { filename = genFilenameRd5(ilon, ilat); } else { filename = genFilenameXY(srtmLonIdx, srtmLatIdx); @@ -210,7 +209,7 @@ public class PosUnifier extends MapCreatorBase { if (f.exists()) { try { InputStream isc = new BufferedInputStream(new FileInputStream(f)); - lastSrtmRaster = new RasterCoder().decodeRaster(isc); + lastSrtmRaster = new ElevationRasterCoder().decodeRaster(isc); isc.close(); } catch (Exception e) { System.out.println("**** ERROR reading " + f + " ****"); @@ -225,7 +224,7 @@ public class PosUnifier extends MapCreatorBase { try { InputStream isc = new BufferedInputStream(new FileInputStream(f)); //lastSrtmRaster = new StatRasterCoder().decodeRaster(isc); - lastSrtmRaster = new RasterCoder().decodeRaster(isc); + lastSrtmRaster = new ElevationRasterCoder().decodeRaster(isc); isc.close(); } catch (Exception e) { System.out.println("**** ERROR reading " + f + " ****"); @@ -258,23 +257,23 @@ public class PosUnifier extends MapCreatorBase { } - private SrtmRaster hgtForNode(int ilon, int ilat) throws Exception { + private ElevationRaster hgtForNode(int ilon, int ilat) throws Exception { double lon = (ilon - 180000000) / 1000000.; double lat = (ilat - 90000000) / 1000000.; String filename = buildHgtFilename(lat, lon); // don't block lastSrtmRaster - SrtmRaster srtm = srtmmap.get(filename); + ElevationRaster srtm = srtmmap.get(filename); if (srtm == null) { File f = new File(new File(srtmdir), filename + ".zip"); if (f.exists()) { - srtm = new ConvertLidarTile().getRaster(f, lon, lat); + srtm = new ElevationRasterTileConverter().getRaster(f, lon, lat); srtmmap.put(filename, srtm); return srtm; } f = new File(new File(srtmdir), filename + ".hgt"); if (f.exists()) { - srtm = new ConvertLidarTile().getRaster(f, lon, lat); + srtm = new ElevationRasterTileConverter().getRaster(f, lon, lat); srtmmap.put(filename, srtm); return srtm; } @@ -282,6 +281,7 @@ public class PosUnifier extends MapCreatorBase { return srtm; } + private String buildHgtFilename(double llat, double llon) { int lat = (int) llat; int lon = (int) llon; @@ -297,10 +297,10 @@ public class PosUnifier extends MapCreatorBase { lon = -lon + 1; } - return String.format(Locale.US, "%s%02d%s%03d", latPref, lat, lonPref, lon); + return String.format("%s%02d%s%03d", latPref, lat, lonPref, lon); } - private void resetSrtm() { + private void resetElevationRaster() { srtmmap = new HashMap<>(); lastSrtmLonIdx = -1; lastSrtmLatIdx = -1; From fbad69474644853fb86f6d5072a54333d069323c Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 6 Nov 2023 12:55:50 +0100 Subject: [PATCH 065/173] removed unused files --- .../btools/mapcreator/ConvertLidarTile.java | 446 ------------------ .../btools/mapcreator/ConvertSrtmTile.java | 260 ---------- .../btools/mapcreator/ConvertUrlList.java | 53 --- .../java/btools/mapcreator/RasterCoder.java | 112 ----- .../main/java/btools/mapcreator/SrtmData.java | 166 ------- .../java/btools/mapcreator/SrtmRaster.java | 264 ----------- 6 files changed, 1301 deletions(-) delete mode 100644 brouter-map-creator/src/main/java/btools/mapcreator/ConvertLidarTile.java delete mode 100644 brouter-map-creator/src/main/java/btools/mapcreator/ConvertSrtmTile.java delete mode 100644 brouter-map-creator/src/main/java/btools/mapcreator/ConvertUrlList.java delete mode 100644 brouter-map-creator/src/main/java/btools/mapcreator/RasterCoder.java delete mode 100644 brouter-map-creator/src/main/java/btools/mapcreator/SrtmData.java delete mode 100644 brouter-map-creator/src/main/java/btools/mapcreator/SrtmRaster.java diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/ConvertLidarTile.java b/brouter-map-creator/src/main/java/btools/mapcreator/ConvertLidarTile.java deleted file mode 100644 index cab1d0b..0000000 --- a/brouter-map-creator/src/main/java/btools/mapcreator/ConvertLidarTile.java +++ /dev/null @@ -1,446 +0,0 @@ -package btools.mapcreator; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.DataInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -public class ConvertLidarTile { - - public static final boolean DEBUG = false; - - public static final short NODATA2 = -32767; // hgt-formats nodata - public static final short NODATA = Short.MIN_VALUE; - - private static final String HGT_FILE_EXT = ".hgt"; - private static final int HGT_BORDER_OVERLAP = 1; - private static final int HGT_3ASEC_ROWS = 1201; // 3 arc second resolution (90m) - private static final int HGT_3ASEC_FILE_SIZE = HGT_3ASEC_ROWS * HGT_3ASEC_ROWS * Short.BYTES; - private static final int HGT_1ASEC_ROWS = 3601; // 1 arc second resolution (30m) - private static final int SRTM3_ROW_LENGTH = 1200; // number of elevation values per line - private static final int SRTM1_ROW_LENGTH = 3600; - private static final boolean SRTM_NO_ZERO = true; - - private int NROWS; - private int NCOLS; - private int ROW_LENGTH; - private short[] imagePixels; - - - public static void main(String[] args) throws Exception { - if (args.length == 3 || args.length == 4 || args.length == 5) { - String filename90 = args[0]; - if ("all".equals(filename90)) { - //if (DEBUG) - System.out.println("lidar convert all "); - new ConvertLidarTile().doConvertAll(args[1], args[2], (args.length > 3 ? args[3] : null), (args.length == 5 ? args[4] : null)); - return; - } - // old filenames only - String filename30 = filename90 + ".bef"; //filename90.substring(0, filename90.length() - 3) + "bef"; - - int srtmLonIdx = Integer.parseInt(filename90.substring(5, 7).toLowerCase()); - int srtmLatIdx = Integer.parseInt(filename90.substring(8, 10).toLowerCase()); - - int ilon_base = (srtmLonIdx - 1) * 5 - 180; - int ilat_base = 150 - srtmLatIdx * 5 - 90; - int row_length = SRTM3_ROW_LENGTH; - String fallbackdir = null; - if (args.length > 3) { - row_length = (Integer.parseInt(args[3]) == 1 ? SRTM1_ROW_LENGTH : SRTM3_ROW_LENGTH); - fallbackdir = (args.length == 5 ? args[4] : null); - } - //if (DEBUG) - System.out.println("lidar convert " + ilon_base + " " + ilat_base + " from " + srtmLonIdx + " " + srtmLatIdx + " f: " + filename90 + " rowl " + row_length); - - new ConvertLidarTile().doConvert(args[1], ilon_base, ilat_base, args[2] + "/" + filename30, row_length, fallbackdir); - } else { - System.out.println("usage: java [arc seconds (1 or 3,default=3)] [hgt-fallback-data-dir]"); - System.out.println("or java all [arc seconds (1 or 3, default=3)] [hgt-fallback-data-dir]"); - return; - } - } - - private void doConvertAll(String hgtdata, String outdir, String rlen, String hgtfallbackdata) throws Exception { - int row_length = SRTM3_ROW_LENGTH; - if (rlen != null) { - row_length = (Integer.parseInt(rlen) == 1 ? SRTM1_ROW_LENGTH : SRTM3_ROW_LENGTH); - } - String filename30; - for (int ilon_base = -180; ilon_base < 180; ilon_base += 5) { - for (int ilat_base = 85; ilat_base > -90; ilat_base -= 5) { - if (PosUnifier.UseLidarRd5FileName) { - filename30 = genFilenameRd5(ilon_base, ilat_base); - } else { - filename30 = genFilenameOld(ilon_base, ilat_base); - } - if (DEBUG) - System.out.println("lidar convert all: " + filename30); - doConvert(hgtdata, ilon_base, ilat_base, outdir + "/" + filename30, row_length, hgtfallbackdata); - } - } - } - - static String genFilenameOld(int ilon_base, int ilat_base) { - int srtmLonIdx = ((ilon_base + 180) / 5) + 1; - int srtmLatIdx = (60 - ilat_base) / 5; - return String.format("srtm_%02d_%02d.bef", srtmLonIdx, srtmLatIdx); - } - - static String genFilenameRd5(int ilon_base, int ilat_base) { - return String.format("srtm_%s_%s.bef", ilon_base < 0 ? "W" + (-ilon_base) : "E" + ilon_base, - ilat_base < 0 ? "S" + (-ilat_base) : "N" + ilat_base); - } - - private void readHgtZip(String filename, int rowOffset, int colOffset, int row_length) throws Exception { - ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(filename))); - try { - for (; ; ) { - ZipEntry ze = zis.getNextEntry(); - if (ze == null) break; - if (ze.getName().toLowerCase().endsWith(HGT_FILE_EXT)) { - readHgtFromStream(zis, rowOffset, colOffset, row_length, 1); - return; - } - } - } finally { - zis.close(); - } - } - - private void readHgtFromStream(InputStream is, int rowOffset, int colOffset, int rowLength, int scale) - throws Exception { - DataInputStream dis = new DataInputStream(new BufferedInputStream(is)); - for (int ir = 0; ir < rowLength; ir++) { - int row = rowOffset + ir * scale; - - for (int ic = 0; ic < rowLength; ic++) { - int col = colOffset + ic * scale; - - int i1 = dis.read(); // msb first! - int i0 = dis.read(); - - if (i0 == -1 || i1 == -1) - throw new RuntimeException("unexpected end of file reading hgt entry!"); - - short val = (short) ((i1 << 8) | i0); - - if (val == NODATA2) { - val = NODATA; - } - if (scale == 3) { - setPixel(row, col, val); - setPixel(row + 1, col, val); - setPixel(row + 2, col, val); - setPixel(row, col + 1, val); - setPixel(row + 1, col + 1, val); - setPixel(row + 2, col + 1, val); - setPixel(row, col + 2, val); - setPixel(row + 1, col + 2, val); - setPixel(row + 2, col + 2, val); - } else { - setPixel(row, col, val); - } - } - } - } - - private void readFallbackFile(File file, int rowOffset, int colOffset, int row_length) - throws Exception { - int rowLength; - int scale; - if (file.length() > HGT_3ASEC_FILE_SIZE) { - rowLength = HGT_1ASEC_ROWS; - scale = 1; - } else { - rowLength = HGT_3ASEC_ROWS; - scale = 3; - } - if (DEBUG) - System.out.println("read fallback: " + file + " " + rowLength); - - FileInputStream fis = new FileInputStream(file); - try { - readHgtFromStream(fis, rowOffset, colOffset, rowLength, scale); - } finally { - fis.close(); - } - } - - private void setPixel(int row, int col, short val) { - if (row >= 0 && row < NROWS && col >= 0 && col < NCOLS) { - imagePixels[row * NCOLS + col] = val; - } - } - - private short getPixel(int row, int col) { - if (row >= 0 && row < NROWS && col >= 0 && col < NCOLS) { - return imagePixels[row * NCOLS + col]; - } - return NODATA; - } - - - public void doConvert(String inputDir, int lonDegreeStart, int latDegreeStart, String outputFile, int row_length, String hgtfallbackdata) throws Exception { - int extraBorder = 0; - - List foundList = new ArrayList<>(); - List notfoundList = new ArrayList<>(); - - boolean found = false; - - if (row_length == SRTM1_ROW_LENGTH) { - // check for sources w/o border - for (int latIdx = 0; latIdx < 5; latIdx++) { - int latDegree = latDegreeStart + latIdx; - - for (int lonIdx = 0; lonIdx < 5; lonIdx++) { - int lonDegree = lonDegreeStart + lonIdx; - - String filename = inputDir + "/" + formatLat(latDegree) + formatLon(lonDegree) + ".zip"; - File f = new File(filename); - if (f.exists() && f.length() > 0) { - found = true; - break; - } - } - } - } else { - // ignore when srtm3 - found = true; - } - if (found) { // init when found - NROWS = 5 * row_length + 1 + 2 * extraBorder; - NCOLS = 5 * row_length + 1 + 2 * extraBorder; - imagePixels = new short[NROWS * NCOLS]; // 650 MB ! - - // prefill as NODATA - Arrays.fill(imagePixels, NODATA); - } else { - if (DEBUG) - System.out.println("none 1sec data: " + lonDegreeStart + " " + latDegreeStart); - return; - } - - for (int latIdx = -1; latIdx <= 5; latIdx++) { - int latDegree = latDegreeStart + latIdx; - int rowOffset = extraBorder + (4 - latIdx) * row_length; - - for (int lonIdx = -1; lonIdx <= 5; lonIdx++) { - int lonDegree = lonDegreeStart + lonIdx; - int colOffset = extraBorder + lonIdx * row_length; - - String filename = inputDir + "/" + formatLat(latDegree) + formatLon(lonDegree) + ".zip"; - File f = new File(filename); - if (f.exists() && f.length() > 0) { - if (DEBUG) - System.out.println("exist: " + filename); - readHgtZip(filename, rowOffset, colOffset, row_length + 1); - } else { - if (hgtfallbackdata != null) { - String filenamehgt = hgtfallbackdata + "/" + formatLat(latDegree) + formatLon(lonDegree) + ".hgt"; - f = new File(filenamehgt); - if (f.exists() && f.length() > 0) { - readFallbackFile(f, rowOffset, colOffset, row_length + 1); - /*if (imagePixels == null) { - imagePixels = new short[NROWS * NCOLS]; - Arrays.fill(imagePixels, NODATA); - found = true; - } - */ - /* - int rowLength; - int arcspace; - if (f.length() > HGT_3ASEC_FILE_SIZE) { - rowLength = HGT_1ASEC_ROWS; - arcspace = 1; - } else { - rowLength = HGT_3ASEC_ROWS; - arcspace = 3; - } - if (DEBUG) - System.out.println("read fallback: " + f + " " + rowLength); - - FileInputStream fis = new FileInputStream(f); - DataInputStream dis = new DataInputStream(new BufferedInputStream(fis)); - for (int ir = 0; ir < rowLength; ir++) { - int row = rowOffset + ir * arcspace; - - for (int ic = 0; ic < rowLength; ic++) { - int col = colOffset + ic * arcspace; - - int i1 = dis.read(); // msb first! - int i0 = dis.read(); - - if (i0 == -1 || i1 == -1) - throw new RuntimeException("unexpected end of file reading hgt entry!"); - - short val = (short) ((i1 << 8) | i0); - - if (val == NODATA2) { - val = NODATA; - } - if (arcspace == 3) { - setPixel(row, col, val); - setPixel(row+1, col, val); - setPixel(row+2, col, val); - setPixel(row, col+1, val); - setPixel(row+1, col+1, val); - setPixel(row+2, col+1, val); - setPixel(row, col+2, val); - setPixel(row+1, col+2, val); - setPixel(row+2, col+2, val); - } else { - setPixel(row, col, val); - } - } - } - fis.close(); - */ - } else { - if (DEBUG) - System.out.println("none : " + filename); - } - } - - } - } - } - - // post fill zero - if (SRTM_NO_ZERO) { - for (int row = 0; row < NROWS; row++) { - for (int col = 0; col < NCOLS; col++) { - if (imagePixels[row * NCOLS + col] == 0) imagePixels[row * NCOLS + col] = NODATA; - } - } - } - - boolean halfCol5 = false; // no halfcol tiles in lidar data (?) - - - SrtmRaster raster = new SrtmRaster(); - raster.nrows = NROWS; - raster.ncols = NCOLS; - raster.halfcol = halfCol5; - raster.noDataValue = NODATA; - raster.cellsize = 1. / row_length; - raster.xllcorner = lonDegreeStart - (0.5 + extraBorder) * raster.cellsize; - raster.yllcorner = latDegreeStart - (0.5 + extraBorder) * raster.cellsize; - raster.eval_array = imagePixels; - - // encode the raster - OutputStream os = new BufferedOutputStream(new FileOutputStream(outputFile)); - new RasterCoder().encodeRaster(raster, os); - os.close(); - - // decode the raster - InputStream is = new BufferedInputStream(new FileInputStream(outputFile)); - SrtmRaster raster2 = new RasterCoder().decodeRaster(is); - is.close(); - - short[] pix2 = raster2.eval_array; - if (pix2.length != imagePixels.length) - throw new RuntimeException("length mismatch!"); - - // compare decoding result - for (int row = 0; row < NROWS; row++) { - int colstep = halfCol5 ? 2 : 1; - for (int col = 0; col < NCOLS; col += colstep) { - int idx = row * NCOLS + col; - short p2 = pix2[idx]; - if (p2 != imagePixels[idx]) { - throw new RuntimeException("content mismatch: p2=" + p2 + " p1=" + imagePixels[idx]); - } - } - } - imagePixels = null; - } - - private static String formatLon(int lon) { - if (lon >= 180) - lon -= 180; // TODO: w180 oder E180 ? - - String s = "E"; - if (lon < 0) { - lon = -lon; - s = "W"; - } - String n = "000" + lon; - return s + n.substring(n.length() - 3); - } - - private static String formatLat(int lat) { - String s = "N"; - if (lat < 0) { - lat = -lat; - s = "S"; - } - String n = "00" + lat; - return s + n.substring(n.length() - 2); - } - - public SrtmRaster getRaster(File f, double lon, double lat) throws Exception { - long fileSize; - InputStream inputStream; - - if (f.getName().toLowerCase().endsWith(".zip")) { - ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(f))); - for (; ; ) { - ZipEntry ze = zis.getNextEntry(); - if (ze == null) { - throw new FileNotFoundException(f.getName() + " doesn't contain a " + HGT_FILE_EXT + " file."); - } - if (ze.getName().toLowerCase().endsWith(HGT_FILE_EXT)) { - fileSize = ze.getSize(); - inputStream = zis; - break; - } - } - } else { - fileSize = f.length(); - inputStream = new FileInputStream(f); - } - - int rowLength; - if (fileSize > HGT_3ASEC_FILE_SIZE) { - rowLength = HGT_1ASEC_ROWS; - } else { - rowLength = HGT_3ASEC_ROWS; - } - - // stay at 1 deg * 1 deg raster - NROWS = rowLength; - NCOLS = rowLength; - - imagePixels = new short[NROWS * NCOLS]; - - // prefill as NODATA - Arrays.fill(imagePixels, NODATA); - readHgtFromStream(inputStream, 0, 0, rowLength, 1); - inputStream.close(); - - SrtmRaster raster = new SrtmRaster(); - raster.nrows = NROWS; - raster.ncols = NCOLS; - raster.halfcol = false; // assume full resolution - raster.noDataValue = NODATA; - raster.cellsize = 1. / (double) (rowLength - HGT_BORDER_OVERLAP); - raster.xllcorner = (int) (lon < 0 ? lon - 1 : lon); //onDegreeStart - raster.cellsize; - raster.yllcorner = (int) (lat < 0 ? lat - 1 : lat); //latDegreeStart - raster.cellsize; - raster.eval_array = imagePixels; - - return raster; - } - -} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/ConvertSrtmTile.java b/brouter-map-creator/src/main/java/btools/mapcreator/ConvertSrtmTile.java deleted file mode 100644 index e18c664..0000000 --- a/brouter-map-creator/src/main/java/btools/mapcreator/ConvertSrtmTile.java +++ /dev/null @@ -1,260 +0,0 @@ -package btools.mapcreator; - -import java.io.*; -import java.util.zip.*; - -public class ConvertSrtmTile { - public static int NROWS; - public static int NCOLS; - - public static final short SKIPDATA = -32766; // >50 degree skipped pixel - public static final short NODATA2 = -32767; // bil-formats nodata - public static final short NODATA = Short.MIN_VALUE; - - static short[] imagePixels; - - public static int[] diffs = new int[100]; - - private static void readBilZip(String filename, int rowOffset, int colOffset, boolean halfCols) throws Exception { - ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(filename))); - try { - for (; ; ) { - ZipEntry ze = zis.getNextEntry(); - if (ze.getName().endsWith(".bil")) { - readBilFromStream(zis, rowOffset, colOffset, halfCols); - return; - } - } - } finally { - zis.close(); - } - } - - private static void readBilFromStream(InputStream is, int rowOffset, int colOffset, boolean halfCols) - throws Exception { - DataInputStream dis = new DataInputStream(new BufferedInputStream(is)); - for (int ir = 0; ir < 3601; ir++) { - int row = rowOffset + ir; - - for (int ic = 0; ic < 3601; ic++) { - int col = colOffset + ic; - - if ((ic % 2) == 1 && halfCols) { - if (getPixel(row, col) == NODATA) { - setPixel(row, col, SKIPDATA); - } - continue; - } - - int i0 = dis.read(); - int i1 = dis.read(); - - if (i0 == -1 || i1 == -1) - throw new RuntimeException("unexcepted end of file reading bil entry!"); - - short val = (short) ((i1 << 8) | i0); - - if (val == NODATA2) { - val = NODATA; - } - - setPixel(row, col, val); - } - } - } - - - private static void setPixel(int row, int col, short val) { - if (row >= 0 && row < NROWS && col >= 0 && col < NCOLS) { - imagePixels[row * NCOLS + col] = val; - } - } - - private static short getPixel(int row, int col) { - if (row >= 0 && row < NROWS && col >= 0 && col < NCOLS) { - return imagePixels[row * NCOLS + col]; - } - return NODATA; - } - - - public static void doConvert(String inputDir, String v1Dir, int lonDegreeStart, int latDegreeStart, String outputFile, SrtmRaster raster90) throws Exception { - int extraBorder = 10; - int datacells = 0; - int mismatches = 0; - - NROWS = 5 * 3600 + 1 + 2 * extraBorder; - NCOLS = 5 * 3600 + 1 + 2 * extraBorder; - - imagePixels = new short[NROWS * NCOLS]; // 650 MB ! - - // prefill as NODATA - for (int row = 0; row < NROWS; row++) { - for (int col = 0; col < NCOLS; col++) { - imagePixels[row * NCOLS + col] = NODATA; - } - } - - for (int latIdx = -1; latIdx <= 5; latIdx++) { - int latDegree = latDegreeStart + latIdx; - int rowOffset = extraBorder + (4 - latIdx) * 3600; - - for (int lonIdx = -1; lonIdx <= 5; lonIdx++) { - int lonDegree = lonDegreeStart + lonIdx; - int colOffset = extraBorder + lonIdx * 3600; - - String filename = inputDir + "/" + formatLat(latDegree) + "_" + formatLon(lonDegree) + "_1arc_v3_bil.zip"; - File f = new File(filename); - if (f.exists() && f.length() > 0) { - System.out.println("exist: " + filename); - boolean halfCol = latDegree >= 50 || latDegree < -50; - readBilZip(filename, rowOffset, colOffset, halfCol); - } else { - System.out.println("none : " + filename); - } - } - } - - boolean halfCol5 = latDegreeStart >= 50 || latDegreeStart < -50; - - for (int row90 = 0; row90 < 6001; row90++) { - int crow = 3 * row90 + extraBorder; // center row of 3x3 - for (int col90 = 0; col90 < 6001; col90++) { - int ccol = 3 * col90 + extraBorder; // center col of 3x3 - - // evaluate 3x3 area - if (raster90 != null && (!halfCol5 || (col90 % 2) == 0)) { - short v90 = raster90.eval_array[row90 * 6001 + col90]; - - int sum = 0; - int nodatas = 0; - int datas = 0; - int colstep = halfCol5 ? 2 : 1; - for (int row = crow - 1; row <= crow + 1; row++) { - for (int col = ccol - colstep; col <= ccol + colstep; col += colstep) { - short v30 = imagePixels[row * NCOLS + col]; - if (v30 == NODATA) { - nodatas++; - } else if (v30 != SKIPDATA) { - sum += v30; - datas++; - } - } - } - boolean doReplace = nodatas > 0 || v90 == NODATA || datas < 7; - if (!doReplace) { - datacells++; - int diff = sum - datas * v90; - if (diff < -4 || diff > 4) { - doReplace = true; - mismatches++; - } - - if (diff > -50 && diff < 50 && (row90 % 1200) != 0 && (col90 % 1200) != 0) { - diffs[diff + 50]++; - } - } - if (doReplace) { - for (int row = crow - 1; row <= crow + 1; row++) { - for (int col = ccol - colstep; col <= ccol + colstep; col += colstep) { - imagePixels[row * NCOLS + col] = v90; - } - } - } - } - } - } - - SrtmRaster raster = new SrtmRaster(); - raster.nrows = NROWS; - raster.ncols = NCOLS; - raster.halfcol = halfCol5; - raster.noDataValue = NODATA; - raster.cellsize = 1 / 3600.; - raster.xllcorner = lonDegreeStart - (0.5 + extraBorder) * raster.cellsize; - raster.yllcorner = latDegreeStart - (0.5 + extraBorder) * raster.cellsize; - raster.eval_array = imagePixels; - - // encode the raster - OutputStream os = new BufferedOutputStream(new FileOutputStream(outputFile)); - new RasterCoder().encodeRaster(raster, os); - os.close(); - - // decode the raster - InputStream is = new BufferedInputStream(new FileInputStream(outputFile)); - SrtmRaster raster2 = new RasterCoder().decodeRaster(is); - is.close(); - - short[] pix2 = raster2.eval_array; - if (pix2.length != imagePixels.length) - throw new RuntimeException("length mismatch!"); - - // compare decoding result - for (int row = 0; row < NROWS; row++) { - int colstep = halfCol5 ? 2 : 1; - for (int col = 0; col < NCOLS; col += colstep) { - int idx = row * NCOLS + col; - if (imagePixels[idx] == SKIPDATA) { - continue; - } - short p2 = pix2[idx]; - if (p2 > SKIPDATA) { - p2 /= 2; - } - if (p2 != imagePixels[idx]) { - throw new RuntimeException("content mismatch!"); - } - } - } - - for (int i = 1; i < 100; i++) System.out.println("diff[" + (i - 50) + "] = " + diffs[i]); - System.out.println("datacells=" + datacells + " mismatch%=" + (100. * mismatches) / datacells); - btools.util.MixCoderDataOutputStream.stats(); - // test( raster ); - // raster.calcWeights( 50. ); - // test( raster ); - // 39828330 &lon=3115280&layer=OpenStreetMap - } - - private static void test(SrtmRaster raster) { - int lat0 = 39828330; - int lon0 = 3115280; - - for (int iy = -9; iy <= 9; iy++) { - StringBuilder sb = new StringBuilder(); - for (int ix = -9; ix <= 9; ix++) { - int lat = lat0 + 90000000 - 100 * iy; - int lon = lon0 + 180000000 + 100 * ix; - int ival = (int) (raster.getElevation(lon, lat) / 4.); - String sval = " " + ival; - sb.append(sval.substring(sval.length() - 4)); - } - System.out.println(sb); - System.out.println(); - } - } - - private static String formatLon(int lon) { - if (lon >= 180) - lon -= 180; // TODO: w180 oder E180 ? - - String s = "e"; - if (lon < 0) { - lon = -lon; - s = "w"; - } - String n = "000" + lon; - return s + n.substring(n.length() - 3); - } - - private static String formatLat(int lat) { - String s = "n"; - if (lat < 0) { - lat = -lat; - s = "s"; - } - String n = "00" + lat; - return s + n.substring(n.length() - 2); - } - -} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/ConvertUrlList.java b/brouter-map-creator/src/main/java/btools/mapcreator/ConvertUrlList.java deleted file mode 100644 index ab5b8db..0000000 --- a/brouter-map-creator/src/main/java/btools/mapcreator/ConvertUrlList.java +++ /dev/null @@ -1,53 +0,0 @@ -package btools.mapcreator; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; - -public class ConvertUrlList { - public static final short NODATA = -32767; - - public static void main(String[] args) throws Exception { - BufferedReader br = new BufferedReader(new FileReader(args[0])); - - for (; ; ) { - String line = br.readLine(); - if (line == null) { - break; - } - int idx1 = line.indexOf("srtm_"); - if (idx1 < 0) { - continue; - } - - String filename90 = line.substring(idx1); - String filename30 = filename90.substring(0, filename90.length() - 3) + "bef"; - - if (new File(filename30).exists()) { - continue; - } - - // int srtmLonIdx = (ilon+5000000)/5000000; -> ilon = (srtmLonIdx-1)*5 - // int srtmLatIdx = (154999999-ilat)/5000000; -> ilat = 155 - srtmLatIdx*5 - - int srtmLonIdx = Integer.parseInt(filename90.substring(5, 7).toLowerCase()); - int srtmLatIdx = Integer.parseInt(filename90.substring(8, 10).toLowerCase()); - - int ilon_base = (srtmLonIdx - 1) * 5 - 180; - int ilat_base = 150 - srtmLatIdx * 5 - 90; - - SrtmRaster raster90 = null; - - File file90 = new File(new File(args[1]), filename90); - if (file90.exists()) { - System.out.println("reading " + file90); - raster90 = new SrtmData(file90).getRaster(); - } - - ConvertSrtmTile.doConvert(args[2], args[3], ilon_base, ilat_base, filename30, raster90); - } - br.close(); - } - - -} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/RasterCoder.java b/brouter-map-creator/src/main/java/btools/mapcreator/RasterCoder.java deleted file mode 100644 index 72b028c..0000000 --- a/brouter-map-creator/src/main/java/btools/mapcreator/RasterCoder.java +++ /dev/null @@ -1,112 +0,0 @@ -package btools.mapcreator; - -import java.io.*; - -import btools.util.*; - -// -// Encode/decode a raster -// - -public class RasterCoder { - public void encodeRaster(SrtmRaster raster, OutputStream os) throws IOException { - DataOutputStream dos = new DataOutputStream(os); - - long t0 = System.currentTimeMillis(); - - dos.writeInt(raster.ncols); - dos.writeInt(raster.nrows); - dos.writeBoolean(raster.halfcol); - dos.writeDouble(raster.xllcorner); - dos.writeDouble(raster.yllcorner); - dos.writeDouble(raster.cellsize); - dos.writeShort(raster.noDataValue); - - _encodeRaster(raster, os); - long t1 = System.currentTimeMillis(); - - System.out.println("finished encoding in " + (t1 - t0) + " ms"); - } - - public SrtmRaster decodeRaster(InputStream is) throws IOException { - DataInputStream dis = new DataInputStream(is); - - long t0 = System.currentTimeMillis(); - - SrtmRaster raster = new SrtmRaster(); - raster.ncols = dis.readInt(); - raster.nrows = dis.readInt(); - raster.halfcol = dis.readBoolean(); - raster.xllcorner = dis.readDouble(); - raster.yllcorner = dis.readDouble(); - raster.cellsize = dis.readDouble(); - raster.noDataValue = dis.readShort(); - raster.eval_array = new short[raster.ncols * raster.nrows]; - - _decodeRaster(raster, is); - - raster.usingWeights = false; // raster.ncols > 6001; - - long t1 = System.currentTimeMillis(); - System.out.println("finished decoding in " + (t1 - t0) + " ms ncols=" + raster.ncols + " nrows=" + raster.nrows); - return raster; - } - - - private void _encodeRaster(SrtmRaster raster, OutputStream os) throws IOException { - MixCoderDataOutputStream mco = new MixCoderDataOutputStream(os); - int nrows = raster.nrows; - int ncols = raster.ncols; - short[] pixels = raster.eval_array; - int colstep = raster.halfcol ? 2 : 1; - - for (int row = 0; row < nrows; row++) { - short lastval = Short.MIN_VALUE; // nodata - for (int col = 0; col < ncols; col += colstep) { - short val = pixels[row * ncols + col]; - if (val == -32766) { - val = lastval; // replace remaining (border) skips - } else { - lastval = val; - } - - // remap nodata - int code = val == Short.MIN_VALUE ? -1 : (val < 0 ? val - 1 : val); - mco.writeMixed(code); - } - } - mco.flush(); - } - - private void _decodeRaster(SrtmRaster raster, InputStream is) throws IOException { - MixCoderDataInputStream mci = new MixCoderDataInputStream(is); - int nrows = raster.nrows; - int ncols = raster.ncols; - short[] pixels = raster.eval_array; - int colstep = raster.halfcol ? 2 : 1; - - for (int row = 0; row < nrows; row++) { - for (int col = 0; col < ncols; col += colstep) { - int code = mci.readMixed(); - - // remap nodata - int v30 = code == -1 ? Short.MIN_VALUE : (code < 0 ? code + 1 : code); - if (raster.usingWeights && v30 > -32766) { - v30 *= 2; - } - pixels[row * ncols + col] = (short) (v30); - } - if (raster.halfcol) { - for (int col = 1; col < ncols - 1; col += colstep) { - int l = (int) pixels[row * ncols + col - 1]; - int r = (int) pixels[row * ncols + col + 1]; - short v30 = Short.MIN_VALUE; // nodata - if (l > -32766 && r > -32766) { - v30 = (short) ((l + r) / 2); - } - pixels[row * ncols + col] = v30; - } - } - } - } -} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/SrtmData.java b/brouter-map-creator/src/main/java/btools/mapcreator/SrtmData.java deleted file mode 100644 index b101cd8..0000000 --- a/brouter-map-creator/src/main/java/btools/mapcreator/SrtmData.java +++ /dev/null @@ -1,166 +0,0 @@ -package btools.mapcreator; - -/** - * This is a wrapper for a 5*5 degree srtm file in ascii/zip-format - *

- * - filter out unused nodes according to the way file - * - enhance with SRTM elevation data - * - split further in smaller (5*5 degree) tiles - * - * @author ab - */ - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.util.StringTokenizer; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -public class SrtmData { - private SrtmRaster raster; - - public SrtmData(File file) throws Exception { - raster = new SrtmRaster(); - - ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(file))); - try { - for (; ; ) { - ZipEntry ze = zis.getNextEntry(); - if (ze.getName().endsWith(".asc")) { - readFromStream(zis); - return; - } - } - } finally { - zis.close(); - } - } - - public SrtmRaster getRaster() { - return raster; - } - - private String secondToken(String s) { - StringTokenizer tk = new StringTokenizer(s, " "); - tk.nextToken(); - return tk.nextToken(); - } - - public void readFromStream(InputStream is) throws Exception { - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - int linenr = 0; - for (; ; ) { - linenr++; - if (linenr <= 6) { - String line = br.readLine(); - if (linenr == 1) - raster.ncols = Integer.parseInt(secondToken(line)); - else if (linenr == 2) - raster.nrows = Integer.parseInt(secondToken(line)); - else if (linenr == 3) - raster.xllcorner = Double.parseDouble(secondToken(line)); - else if (linenr == 4) - raster.yllcorner = Double.parseDouble(secondToken(line)); - else if (linenr == 5) - raster.cellsize = Double.parseDouble(secondToken(line)); - else if (linenr == 6) { - // nodata ignored here ( < -250 assumed nodata... ) - // raster.noDataValue = Short.parseShort( secondToken( line ) ); - raster.eval_array = new short[raster.ncols * raster.nrows]; - } - } else { - int row = 0; - int col = 0; - int n = 0; - boolean negative = false; - for (; ; ) { - int c = br.read(); - if (c < 0) - break; - if (c == ' ') { - if (negative) - n = -n; - short val = n < -250 ? Short.MIN_VALUE : (short) (n); - - raster.eval_array[row * raster.ncols + col] = val; - if (++col == raster.ncols) { - col = 0; - ++row; - } - n = 0; - negative = false; - } else if (c >= '0' && c <= '9') { - n = 10 * n + (c - '0'); - } else if (c == '-') { - negative = true; - } - } - break; - } - } - br.close(); - } - - public static void main(String[] args) throws Exception { - String fromDir = args[0]; - String toDir = args[1]; - - File[] files = new File(fromDir).listFiles(); - for (File f : files) { - if (!f.getName().endsWith(".zip")) { - continue; - } - System.out.println("*** reading: " + f); - long t0 = System.currentTimeMillis(); - SrtmRaster raster = new SrtmData(f).getRaster(); - long t1 = System.currentTimeMillis(); - String name = f.getName(); - - long zipTime = t1 - t0; - - File fbef = new File(new File(toDir), name.substring(0, name.length() - 3) + "bef"); - System.out.println("recoding: " + f + " to " + fbef); - OutputStream osbef = new BufferedOutputStream(new FileOutputStream(fbef)); - new RasterCoder().encodeRaster(raster, osbef); - osbef.close(); - - System.out.println("*** re-reading: " + fbef); - - long t2 = System.currentTimeMillis(); - InputStream isc = new BufferedInputStream(new FileInputStream(fbef)); - SrtmRaster raster2 = new RasterCoder().decodeRaster(isc); - isc.close(); - long t3 = System.currentTimeMillis(); - - long befTime = t3 - t2; - - System.out.println("*** zip-time: " + zipTime + "*** bef-time: " + befTime); - - String s1 = raster.toString(); - String s2 = raster2.toString(); - - if (!s1.equals(s2)) { - throw new IllegalArgumentException("missmatch: " + s1 + "<--->" + s2); - } - - int cols = raster.ncols; - int rows = raster.nrows; - for (int c = 0; c < cols; c++) { - for (int r = 0; r < rows; r++) { - int idx = r * cols + c; - - if (raster.eval_array[idx] != raster2.eval_array[idx]) { - throw new IllegalArgumentException("missmatch: at " + c + "," + r + ": " + raster.eval_array[idx] + "<--->" + raster2.eval_array[idx]); - } - } - } - } - } -} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/SrtmRaster.java b/brouter-map-creator/src/main/java/btools/mapcreator/SrtmRaster.java deleted file mode 100644 index 3a307bf..0000000 --- a/brouter-map-creator/src/main/java/btools/mapcreator/SrtmRaster.java +++ /dev/null @@ -1,264 +0,0 @@ -package btools.mapcreator; - -import btools.util.ReducedMedianFilter; - -/** - * Container for a srtm-raster + it's meta-data - * - * @author ab - */ -public class SrtmRaster { - public int ncols; - public int nrows; - public boolean halfcol; - public double xllcorner; - public double yllcorner; - public double cellsize; - public short[] eval_array; - public short noDataValue; - - public boolean usingWeights = false; - - private boolean missingData = false; - - public short getElevation(int ilon, int ilat) { - double lon = ilon / 1000000. - 180.; - double lat = ilat / 1000000. - 90.; - - if (usingWeights) { - return getElevationFromShiftWeights(lon, lat); - } - - // no weights calculated, use 2d linear interpolation - double dcol = (lon - xllcorner) / cellsize - 0.5; - double drow = (lat - yllcorner) / cellsize - 0.5; - int row = (int) drow; - int col = (int) dcol; - if (col < 0) col = 0; - if (col >= ncols - 1) col = ncols - 2; - if (row < 0) row = 0; - if (row >= nrows - 1) row = nrows - 2; - double wrow = drow - row; - double wcol = dcol - col; - missingData = false; - -// System.out.println( "wrow=" + wrow + " wcol=" + wcol + " row=" + row + " col=" + col ); - double eval = (1. - wrow) * (1. - wcol) * get(row, col) - + (wrow) * (1. - wcol) * get(row + 1, col) - + (1. - wrow) * (wcol) * get(row, col + 1) - + (wrow) * (wcol) * get(row + 1, col + 1); -// System.out.println( "eval=" + eval ); - return missingData ? Short.MIN_VALUE : (short) (eval * 4); - } - - private short get(int r, int c) { - short e = eval_array[(nrows - 1 - r) * ncols + c]; - if (e == Short.MIN_VALUE) missingData = true; - return e; - } - - private short getElevationFromShiftWeights(double lon, double lat) { - // calc lat-idx and -weight - double alat = lat < 0. ? -lat : lat; - alat /= 5.; - int latIdx = (int) alat; - double wlat = alat - latIdx; - - double dcol = (lon - xllcorner) / cellsize; - double drow = (lat - yllcorner) / cellsize; - int row = (int) drow; - int col = (int) dcol; - - double dgx = (dcol - col) * gridSteps; - double dgy = (drow - row) * gridSteps; - -// System.out.println( "wrow=" + wrow + " wcol=" + wcol + " row=" + row + " col=" + col ); - - int gx = (int) (dgx); - int gy = (int) (dgy); - - double wx = dgx - gx; - double wy = dgy - gy; - - double w00 = (1. - wx) * (1. - wy); - double w01 = (1. - wx) * (wy); - double w10 = (wx) * (1. - wy); - double w11 = (wx) * (wy); - - Weights[][] w0 = getWeights(latIdx); - Weights[][] w1 = getWeights(latIdx + 1); - - missingData = false; - - double m0 = w00 * getElevation(w0[gx][gy], row, col) - + w01 * getElevation(w0[gx][gy + 1], row, col) - + w10 * getElevation(w0[gx + 1][gy], row, col) - + w11 * getElevation(w0[gx + 1][gy + 1], row, col); - double m1 = w00 * getElevation(w1[gx][gy], row, col) - + w01 * getElevation(w1[gx][gy + 1], row, col) - + w10 * getElevation(w1[gx + 1][gy], row, col) - + w11 * getElevation(w1[gx + 1][gy + 1], row, col); - - if (missingData) return Short.MIN_VALUE; - double m = (1. - wlat) * m0 + wlat * m1; - return (short) (m * 2); - } - - private ReducedMedianFilter rmf = new ReducedMedianFilter(256); - - private double getElevation(Weights w, int row, int col) { - if (missingData) { - return 0.; - } - int nx = w.nx; - int ny = w.ny; - int mx = nx / 2; // mean pixels - int my = ny / 2; - - // System.out.println( "nx="+ nx + " ny=" + ny ); - - rmf.reset(); - - for (int ix = 0; ix < nx; ix++) { - for (int iy = 0; iy < ny; iy++) { - short val = get(row + iy - my, col + ix - mx); - rmf.addSample(w.getWeight(ix, iy), val); - } - } - return missingData ? 0. : rmf.calcEdgeReducedMedian(filterCenterFraction); - } - - - private static class Weights { - int nx; - int ny; - double[] weights; - long total = 0; - - Weights(int nx, int ny) { - this.nx = nx; - this.ny = ny; - weights = new double[nx * ny]; - } - - void inc(int ix, int iy) { - weights[iy * nx + ix] += 1.; - total++; - } - - void normalize(boolean verbose) { - for (int iy = 0; iy < ny; iy++) { - StringBuilder sb = verbose ? new StringBuilder() : null; - for (int ix = 0; ix < nx; ix++) { - weights[iy * nx + ix] /= total; - if (sb != null) { - int iweight = (int) (1000 * weights[iy * nx + ix] + 0.5); - String sval = " " + iweight; - sb.append(sval.substring(sval.length() - 4)); - } - } - if (sb != null) { - System.out.println(sb); - System.out.println(); - } - } - } - - double getWeight(int ix, int iy) { - return weights[iy * nx + ix]; - } - } - - private static int gridSteps = 10; - private static Weights[][][] allShiftWeights = new Weights[17][][]; - - private static double filterCenterFraction = 0.2; - private static double filterDiscRadius = 4.999; // in pixels - - static { - String sRadius = System.getProperty("filterDiscRadius"); - if (sRadius != null && sRadius.length() > 0) { - filterDiscRadius = Integer.parseInt(sRadius); - System.out.println("using filterDiscRadius = " + filterDiscRadius); - } - String sFraction = System.getProperty("filterCenterFraction"); - if (sFraction != null && sFraction.length() > 0) { - filterCenterFraction = Integer.parseInt(sFraction) / 100.; - System.out.println("using filterCenterFraction = " + filterCenterFraction); - } - } - - - // calculate interpolation weights from the overlap of a probe disc of given radius at given latitude - // ( latIndex = 0 -> 0 deg, latIndex = 16 -> 80 degree) - - private static Weights[][] getWeights(int latIndex) { - int idx = latIndex < 16 ? latIndex : 16; - - Weights[][] res = allShiftWeights[idx]; - if (res == null) { - res = calcWeights(idx); - allShiftWeights[idx] = res; - } - return res; - } - - private static Weights[][] calcWeights(int latIndex) { - double coslat = Math.cos(latIndex * 5. / 57.3); - - // radius in pixel units - double ry = filterDiscRadius; - double rx = ry / coslat; - - // gridsize is 2*radius + 1 cell - int nx = ((int) rx) * 2 + 3; - int ny = ((int) ry) * 2 + 3; - - System.out.println("nx=" + nx + " ny=" + ny); - - int mx = nx / 2; // mean pixels - int my = ny / 2; - - // create a matrix for the relative intergrid-position - - Weights[][] shiftWeights = new Weights[gridSteps + 1][]; - - // loop the intergrid-position - for (int gx = 0; gx <= gridSteps; gx++) { - shiftWeights[gx] = new Weights[gridSteps + 1]; - double x0 = mx + ((double) gx) / gridSteps; - - for (int gy = 0; gy <= gridSteps; gy++) { - double y0 = my + ((double) gy) / gridSteps; - - // create the weight-matrix - Weights weights = new Weights(nx, ny); - shiftWeights[gx][gy] = weights; - - double sampleStep = 0.001; - - for (double x = -1. + sampleStep / 2.; x < 1.; x += sampleStep) { - double mx2 = 1. - x * x; - - int x_idx = (int) (x0 + x * rx); - - for (double y = -1. + sampleStep / 2.; y < 1.; y += sampleStep) { - if (y * y > mx2) { - continue; - } - // we are in the ellipse, see what pixel we are on - int y_idx = (int) (y0 + y * ry); - weights.inc(x_idx, y_idx); - } - } - weights.normalize(true); - } - } - return shiftWeights; - } - - @Override - public String toString() { - return ncols + "," + nrows + "," + halfcol + "," + xllcorner + "," + yllcorner + "," + cellsize + "," + noDataValue + "," + usingWeights; - } -} From cae367025f966c0ea93f2074b5415bebba3c1fa2 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 6 Nov 2023 17:48:10 +0100 Subject: [PATCH 066/173] added an elevation raster generation part --- docs/developers/build_segments.md | 36 ++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/developers/build_segments.md b/docs/developers/build_segments.md index 513fab5..79bd296 100644 --- a/docs/developers/build_segments.md +++ b/docs/developers/build_segments.md @@ -26,7 +26,7 @@ official BRouter segments files are the ones provided by [CGIAR](https://cgiarcsi.community/data/srtm-90m-digital-elevation-database-v4-1/). If you are working with rather small geographical extracts, you can download tiles manually using [this -interface](http://srtm.csi.cgiar.org/SELECTION/inputCoord.asp) (use the +interface](https://srtm.csi.cgiar.org/srtmdata/) (use the "ArcInfo ASCII" format), instead of having to ask for an access for bulk download of data. There is no need to unzip the downloaded files, the `process_pbf_planet.sh` script expects a folder with all the ZIP files inside @@ -48,3 +48,37 @@ and set the `PLANET_FILE` variable to point to it. _Note:_ It is possible that you encounter an error complaining about not being able to run `bash^M` on Linux/Mac OS. You can fix this one by running `sed -i -e 's/\r$//' process_pbf_planet.sh`. + + +## Run a generation for elevation data tiles + +To match the 5x5 OSM data grid (*.rd5) files from BRouter, there are elevation +data in a 5x5 degree format (*.bef). At the moment (end of 2023) the naming of +this elevation tiles follows the konvention used by srtm.csi.cgiar.org: srtm_x_y + +As the srtm files are only available between 60N and 60S the filenames above 60N +contains negative values. e.g. srtm_34_-1 as a tile above srtm_34_00. + +Please see OSM wiki for more info on [srtm](https://wiki.openstreetmap.org/wiki/SRTM). + +The converter generates bef tiles from `hgt` files, `zipped hgt` files and `zipped 'ESRI' asc` files. + +Converter call with arguments for a single tile generation: + +`ElevationRasterTileConverter [arc seconds (1 or 3,default=3)] [hgt-fallback-data-dir] +Samples: +$ ... ElevationRasterTileConverter srtm_34_-1 ./srtm/hgt3sec ./srtm/srtm3_bef +$ ... ElevationRasterTileConverter srtm_34_-1 ./srtm/hgt1sec ./srtm/srtm1_bef 1 +$ ... ElevationRasterTileConverter srtm_34_-1 ./srtm/hgt1sec ./srtm/srtm1_bef 1 ./srtm/hgt3sec +` +Arguments for multi file generation (world wide): + +`$ ... ElevationRasterTileConverter all ./srtm/hgt3sec ./srtm/srtm3_bef +$ ... ElevationRasterTileConverter all ./srtm/hgt1sec ./srtm/srtm1_bef 1 +$ ... ElevationRasterTileConverter all ./srtm/hgt1sec ./srtm/srtm1_bef 1 ./srtm/hgt3sec +` + +To use 1sec and 3sec bef tiles at rd5 generation time you need an extra parameter to the fallback folder. +E.g. +`$ ... PosUnifier nodes55 unodes55 bordernids.dat bordernodes.dat ../srtm/srtm1_bef ../srtm/srtm3_bef +` From 56dbd52065a8c99d5ef61987ff6ad4b54860a5d9 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 20 Nov 2023 17:08:20 +0100 Subject: [PATCH 067/173] added new format classes --- .../main/java/btools/router/FormatCsv.java | 43 ++ .../main/java/btools/router/FormatGpx.java | 532 ++++++++++++++++++ .../main/java/btools/router/FormatJson.java | 246 ++++++++ .../main/java/btools/router/FormatKml.java | 91 +++ .../main/java/btools/router/Formatter.java | 73 +++ 5 files changed, 985 insertions(+) create mode 100644 brouter-core/src/main/java/btools/router/FormatCsv.java create mode 100644 brouter-core/src/main/java/btools/router/FormatGpx.java create mode 100644 brouter-core/src/main/java/btools/router/FormatJson.java create mode 100644 brouter-core/src/main/java/btools/router/FormatKml.java create mode 100644 brouter-core/src/main/java/btools/router/Formatter.java diff --git a/brouter-core/src/main/java/btools/router/FormatCsv.java b/brouter-core/src/main/java/btools/router/FormatCsv.java new file mode 100644 index 0000000..37b7408 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/FormatCsv.java @@ -0,0 +1,43 @@ +package btools.router; + +import java.io.BufferedWriter; +import java.io.StringWriter; + +public class FormatCsv extends Formatter { + + + public FormatCsv(RoutingContext rc) { + super(rc); + } + + @Override + public String format(OsmTrack t) { + try { + StringWriter sw = new StringWriter(); + BufferedWriter bw = new BufferedWriter(sw); + writeMessages(bw, t); + return sw.toString(); + } catch (Exception ex) { + return "Error: " + ex.getMessage(); + } + } + + public void writeMessages(BufferedWriter bw, OsmTrack t) throws Exception { + dumpLine(bw, MESSAGES_HEADER); + for (String m : t.aggregateMessages()) { + dumpLine(bw, m); + } + if (bw != null) + bw.close(); + } + + private void dumpLine(BufferedWriter bw, String s) throws Exception { + if (bw == null) { + System.out.println(s); + } else { + bw.write(s); + bw.write("\n"); + } + } + +} diff --git a/brouter-core/src/main/java/btools/router/FormatGpx.java b/brouter-core/src/main/java/btools/router/FormatGpx.java new file mode 100644 index 0000000..e162292 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/FormatGpx.java @@ -0,0 +1,532 @@ +package btools.router; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.util.Map; + +import btools.mapaccess.MatchedWaypoint; +import btools.util.StringUtils; + +public class FormatGpx extends Formatter { + public FormatGpx(RoutingContext rc) { + super(rc); + } + + @Override + public String format(OsmTrack t) { + try { + StringWriter sw = new StringWriter(8192); + BufferedWriter bw = new BufferedWriter(sw); + formatAsGpx(bw, t); + bw.close(); + return sw.toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public String formatAsGpx(BufferedWriter sb, OsmTrack t) throws IOException { + int turnInstructionMode = t.voiceHints != null ? t.voiceHints.turnInstructionMode : 0; + + sb.append("\n"); + if (turnInstructionMode != 9) { + for (int i = t.messageList.size() - 1; i >= 0; i--) { + String message = t.messageList.get(i); + if (i < t.messageList.size() - 1) + message = "(alt-index " + i + ": " + message + " )"; + if (message != null) + sb.append("\n"); + } + } + + if (turnInstructionMode == 4) { // comment style + sb.append("\n"); + sb.append("\n"); + sb.append("\n"); + } + sb.append("\n"); + } else { + sb.append(" creator=\"BRouter-" + t.version + "\" version=\"1.1\">\n"); + } + if (turnInstructionMode == 9) { + sb.append(" \n"); + sb.append(" ").append(t.name).append("\n"); + sb.append(" \n"); + sb.append(" ").append(t.messageList.get(0)).append("\n"); + if (t.params != null && t.params.size() > 0) { + sb.append(" e : t.params.entrySet()) { + if (i++ != 0) sb.append("&"); + sb.append(e.getKey()).append("=").append(e.getValue()); + } + sb.append("]]>\n"); + } + sb.append(" \n"); + sb.append(" \n"); + } + if (turnInstructionMode == 3 || turnInstructionMode == 8) { // osmand style, cruiser + float lastRteTime = 0; + + sb.append(" \n"); + + float rteTime = t.getVoiceHintTime(0); + StringBuffer first = new StringBuffer(); + // define start point + { + first.append(" \n") + .append(" start\n \n"); + if (rteTime != lastRteTime) { // add timing only if available + double ti = rteTime - lastRteTime; + first.append(" \n"); + lastRteTime = rteTime; + } + first.append(" 0\n \n \n"); + } + if (turnInstructionMode == 8) { + if (t.matchedWaypoints.get(0).direct && t.voiceHints.list.get(0).indexInTrack == 0) { + // has a voice hint do nothing, voice hint will do + } else { + sb.append(first.toString()); + } + } else { + sb.append(first.toString()); + } + + for (int i = 0; i < t.voiceHints.list.size(); i++) { + VoiceHint hint = t.voiceHints.list.get(i); + sb.append(" \n") + .append(" ") + .append(turnInstructionMode == 3 ? hint.getMessageString() : hint.getCruiserMessageString()) + .append("\n \n"); + + rteTime = t.getVoiceHintTime(i + 1); + + if (rteTime != lastRteTime) { // add timing only if available + double ti = rteTime - lastRteTime; + sb.append(" \n"); + lastRteTime = rteTime; + } + sb.append(" ") + .append(turnInstructionMode == 3 ? hint.getCommandString() : hint.getCruiserCommandString()) + .append("\n ").append("" + (int) hint.angle) + .append("\n ").append("" + hint.indexInTrack).append("\n \n \n"); + } + sb.append(" \n") + .append(" destination\n \n"); + sb.append(" \n"); + sb.append(" ").append("" + (t.nodes.size() - 1)).append("\n \n \n"); + + sb.append("\n"); + } + + if (turnInstructionMode == 7) { // old locus style + float lastRteTime = t.getVoiceHintTime(0); + + for (int i = 0; i < t.voiceHints.list.size(); i++) { + VoiceHint hint = t.voiceHints.list.get(i); + sb.append(" ") + .append(hint.selev == Short.MIN_VALUE ? "" : "" + (hint.selev / 4.) + "") + .append("").append(hint.getMessageString()).append("") + .append("").append("" + hint.distanceToNext).append(""); + float rteTime = t.getVoiceHintTime(i + 1); + if (rteTime != lastRteTime) { // add timing only if available + double ti = rteTime - lastRteTime; + double speed = hint.distanceToNext / ti; + sb.append("").append("" + ti).append("") + .append("").append("" + speed).append(""); + lastRteTime = rteTime; + } + sb.append("").append("" + hint.getLocusAction()).append("") + .append("\n"); + } + } + if (turnInstructionMode == 5) { // gpsies style + for (VoiceHint hint : t.voiceHints.list) { + sb.append(" ") + .append("").append(hint.getMessageString()).append("") + .append("").append(hint.getSymbolString().toLowerCase()).append("") + .append("").append(hint.getSymbolString()).append("") + .append("\n"); + } + } + + if (turnInstructionMode == 6) { // orux style + for (VoiceHint hint : t.voiceHints.list) { + sb.append(" ") + .append(hint.selev == Short.MIN_VALUE ? "" : "" + (hint.selev / 4.) + "") + .append("\n" + + " \n" + + " ").append("" + hint.getOruxAction()) + .append("\n" + + " \n" + + " \n" + + " \n"); + } + } + + for (int i = 0; i <= t.pois.size() - 1; i++) { + OsmNodeNamed poi = t.pois.get(i); + sb.append(" \n") + .append(" ").append(StringUtils.escapeXml10(poi.name)).append("\n") + .append(" \n"); + } + + 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"); + } else if (i == t.matchedWaypoints.size() - 1) { + sb.append(" to\n"); + } else { + sb.append(" via\n"); + } + sb.append(" \n"); + } + } + sb.append(" \n"); + if (turnInstructionMode == 9 + || turnInstructionMode == 2 + || turnInstructionMode == 8 + || turnInstructionMode == 4) { // Locus, comment, cruise, brouter style + sb.append(" ").append(t.name).append("\n"); + sb.append(" ").append(t.voiceHints.getTransportMode()).append("\n"); + } else { + sb.append(" ").append(t.name).append("\n"); + } + + if (turnInstructionMode == 7) { + sb.append(" \n"); + sb.append(" ").append("" + t.voiceHints.getLocusRouteType()).append("\n"); + sb.append(" 1\n"); + sb.append(" \n"); + } + + + // all points + sb.append(" \n"); + String lastway = ""; + boolean bNextDirect = false; + OsmPathElement nn = null; + String aSpeed; + + for (int idx = 0; idx < t.nodes.size(); idx++) { + OsmPathElement n = t.nodes.get(idx); + String sele = n.getSElev() == Short.MIN_VALUE ? "" : "" + n.getElev() + ""; + VoiceHint hint = t.getVoiceHint(idx); + MatchedWaypoint mwpt = t.getMatchedWaypoint(idx); + + if (t.showTime) { + sele += ""; + } + if (turnInstructionMode == 8) { + if (mwpt != null && + !mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) { + sele += "" + mwpt.name + ""; + } + } + boolean bNeedHeader = false; + if (turnInstructionMode == 9) { // trkpt/sym style + + if (hint != null) { + + if (mwpt != null && + !mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) { + sele += "" + mwpt.name + ""; + } + sele += "" + hint.getCruiserMessageString() + ""; + sele += "" + hint.getCommandString(hint.cmd) + ""; + if (mwpt != null) { + sele += "Via"; + } + sele += ""; + if (t.showspeed) { + double speed = 0; + if (nn != null) { + int dist = n.calcDistance(nn); + float dt = n.getTime() - nn.getTime(); + if (dt != 0.f) { + speed = ((3.6f * dist) / dt + 0.5); + } + } + sele += "" + (((int) (speed * 10)) / 10.f) + ""; + } + + sele += "" + hint.getCommandString() + ";" + (int) (hint.distanceToNext) + "," + hint.formatGeometry() + ""; + if (n.message != null && n.message.wayKeyValues != null && !n.message.wayKeyValues.equals(lastway)) { + sele += "" + n.message.wayKeyValues + ""; + lastway = n.message.wayKeyValues; + } + if (n.message != null && n.message.nodeKeyValues != null) { + sele += "" + n.message.nodeKeyValues + ""; + } + sele += ""; + + } + if (idx == 0 && hint == null) { + if (mwpt != null && mwpt.direct) { + sele += "beeline"; + } else { + sele += "start"; + } + sele += "Via"; + + } else if (idx == t.nodes.size() - 1 && hint == null) { + + sele += "end"; + sele += "Via"; + + } else { + if (mwpt != null && hint == null) { + if (mwpt.direct) { + // bNextDirect = true; + sele += "beeline"; + } else { + sele += "" + mwpt.name + ""; + } + sele += "Via"; + bNextDirect = false; + } + } + + + if (hint == null) { + bNeedHeader = (t.showspeed || (n.message != null && n.message.wayKeyValues != null && !n.message.wayKeyValues.equals(lastway))) || + (n.message != null && n.message.nodeKeyValues != null); + if (bNeedHeader) { + sele += ""; + if (t.showspeed) { + double speed = 0; + if (nn != null) { + int dist = n.calcDistance(nn); + float dt = n.getTime() - nn.getTime(); + if (dt != 0.f) { + speed = ((3.6f * dist) / dt + 0.5); + } + } + sele += "" + (((int) (speed * 10)) / 10.f) + ""; + } + if (n.message != null && n.message.wayKeyValues != null && !n.message.wayKeyValues.equals(lastway)) { + sele += "" + n.message.wayKeyValues + ""; + lastway = n.message.wayKeyValues; + } + if (n.message != null && n.message.nodeKeyValues != null) { + sele += "" + n.message.nodeKeyValues + ""; + } + sele += ""; + } + } + } + + if (turnInstructionMode == 2) { // locus style new + if (hint != null) { + if (mwpt != null) { + if (!mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) { + sele += "" + mwpt.name + ""; + } + if (mwpt.direct && bNextDirect) { + sele += "" + hint.getLocusSymbolString() + "pass_placeShaping"; + // bNextDirect = false; + } else if (mwpt.direct) { + if (idx == 0) + sele += "pass_placeVia"; + else + sele += "pass_placeShaping"; + bNextDirect = true; + } else if (bNextDirect) { + sele += "beeline" + hint.getLocusSymbolString() + "Shaping"; + bNextDirect = false; + } else { + sele += "" + hint.getLocusSymbolString() + "Via"; + } + } else { + sele += "" + hint.getLocusSymbolString() + ""; + } + } else { + if (idx == 0 && hint == null) { + + int pos = sele.indexOf(""; + if (mwpt != null && mwpt.direct) { + bNextDirect = true; + } + sele += "pass_place"; + sele += "Via"; + + } else if (idx == t.nodes.size() - 1 && hint == null) { + + int pos = sele.indexOf(""; + if (bNextDirect) { + sele += "beeline"; + } + sele += "pass_place"; + sele += "Via"; + + } else { + if (mwpt != null) { + if (!mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) { + sele += "" + mwpt.name + ""; + } + if (mwpt.direct && bNextDirect) { + sele += "beelinepass_placeShaping"; + } else if (mwpt.direct) { + if (idx == 0) + sele += "pass_placeVia"; + else + sele += "pass_placeShaping"; + bNextDirect = true; + } else if (bNextDirect) { + sele += "beelinepass_placeShaping"; + bNextDirect = false; + } else if (mwpt.name.startsWith("via") || + mwpt.name.startsWith("from") || + mwpt.name.startsWith("to")) { + if (bNextDirect) { + sele += "beelinepass_placeShaping"; + } else { + sele += "pass_placeVia"; + } + bNextDirect = false; + } else { + sele += "" + mwpt.name + ""; + sele += "pass_placeVia"; + } + } + } + } + } + sb.append(" ").append(sele).append("\n"); + + nn = n; + } + + sb.append(" \n"); + sb.append(" \n"); + sb.append("\n"); + + return sb.toString(); + } + + public String formatAsWaypoint(OsmNodeNamed n) { + try { + StringWriter sw = new StringWriter(8192); + BufferedWriter bw = new BufferedWriter(sw); + formatGpxHeader(bw); + formatWaypointGpx(bw, n); + formatGpxFooter(bw); + bw.close(); + sw.close(); + return sw.toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void formatGpxHeader(BufferedWriter sb) throws IOException { + sb.append("\n"); + sb.append("\n"); + } + + public void formatGpxFooter(BufferedWriter sb) throws IOException { + sb.append("\n"); + } + + public void formatWaypointGpx(BufferedWriter sb, OsmNodeNamed n) throws IOException { + sb.append(" "); + if (n.getSElev() != Short.MIN_VALUE) { + sb.append("").append("" + n.getElev()).append(""); + } + if (n.name != null) { + sb.append("").append(StringUtils.escapeXml10(n.name)).append(""); + } + if (n.nodeDescription != null && rc != null) { + sb.append("").append(rc.expctxWay.getKeyValueDescription(false, n.nodeDescription)).append(""); + } + sb.append("\n"); + } + + public static String getWaypoint(int ilon, int ilat, String name, String desc) { + return "" + name + "" + (desc != null ? "" + desc + "" : "") + ""; + } + + public OsmTrack read(String filename) throws Exception { + File f = new File(filename); + if (!f.exists()) { + return null; + } + OsmTrack track = new OsmTrack(); + BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f))); + + for (; ; ) { + String line = br.readLine(); + if (line == null) + break; + + int idx0 = line.indexOf("= 0) { + idx0 = line.indexOf(" lon=\""); + idx0 += 6; + int idx1 = line.indexOf('"', idx0); + int ilon = (int) ((Double.parseDouble(line.substring(idx0, idx1)) + 180.) * 1000000. + 0.5); + int idx2 = line.indexOf(" lat=\""); + if (idx2 < 0) + continue; + idx2 += 6; + int idx3 = line.indexOf('"', idx2); + int ilat = (int) ((Double.parseDouble(line.substring(idx2, idx3)) + 90.) * 1000000. + 0.5); + track.nodes.add(OsmPathElement.create(ilon, ilat, (short) 0, null, false)); + } + } + br.close(); + return track; + } + +} diff --git a/brouter-core/src/main/java/btools/router/FormatJson.java b/brouter-core/src/main/java/btools/router/FormatJson.java new file mode 100644 index 0000000..a2c5b7a --- /dev/null +++ b/brouter-core/src/main/java/btools/router/FormatJson.java @@ -0,0 +1,246 @@ +package btools.router; + +import java.io.BufferedWriter; +import java.io.StringWriter; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.List; +import java.util.Locale; + +import btools.mapaccess.MatchedWaypoint; +import btools.util.StringUtils; + +public class FormatJson extends Formatter { + + public FormatJson(RoutingContext rc) { + super(rc); + } + + @Override + public String format(OsmTrack t) { + int turnInstructionMode = t.voiceHints != null ? t.voiceHints.turnInstructionMode : 0; + + StringBuilder sb = new StringBuilder(8192); + + sb.append("{\n"); + sb.append(" \"type\": \"FeatureCollection\",\n"); + sb.append(" \"features\": [\n"); + sb.append(" {\n"); + sb.append(" \"type\": \"Feature\",\n"); + sb.append(" \"properties\": {\n"); + sb.append(" \"creator\": \"BRouter-" + t.version + "\",\n"); + sb.append(" \"name\": \"").append(t.name).append("\",\n"); + sb.append(" \"track-length\": \"").append(t.distance).append("\",\n"); + sb.append(" \"filtered ascend\": \"").append(t.ascend).append("\",\n"); + sb.append(" \"plain-ascend\": \"").append(t.plainAscend).append("\",\n"); + sb.append(" \"total-time\": \"").append(t.getTotalSeconds()).append("\",\n"); + sb.append(" \"total-energy\": \"").append(t.energy).append("\",\n"); + sb.append(" \"cost\": \"").append(t.cost).append("\",\n"); + if (t.voiceHints != null && !t.voiceHints.list.isEmpty()) { + sb.append(" \"voicehints\": [\n"); + for (VoiceHint hint : t.voiceHints.list) { + sb.append(" ["); + sb.append(hint.indexInTrack); + sb.append(',').append(hint.getJsonCommandIndex()); + sb.append(',').append(hint.getExitNumber()); + sb.append(',').append(hint.distanceToNext); + sb.append(',').append((int) hint.angle); + + // not always include geometry because longer and only needed for comment style + if (turnInstructionMode == 4) { // comment style + sb.append(",\"").append(hint.formatGeometry()).append("\""); + } + + sb.append("],\n"); + } + sb.deleteCharAt(sb.lastIndexOf(",")); + sb.append(" ],\n"); + } + if (t.showSpeedProfile) { // set in profile + List sp = t.aggregateSpeedProfile(); + if (sp.size() > 0) { + sb.append(" \"speedprofile\": [\n"); + for (int i = sp.size() - 1; i >= 0; i--) { + sb.append(" [").append(sp.get(i)).append(i > 0 ? "],\n" : "]\n"); + } + sb.append(" ],\n"); + } + } + // ... traditional message list + { + sb.append(" \"messages\": [\n"); + sb.append(" [\"").append(MESSAGES_HEADER.replaceAll("\t", "\", \"")).append("\"],\n"); + for (String m : t.aggregateMessages()) { + sb.append(" [\"").append(m.replaceAll("\t", "\", \"")).append("\"],\n"); + } + sb.deleteCharAt(sb.lastIndexOf(",")); + sb.append(" ],\n"); + } + + if (t.getTotalSeconds() > 0) { + sb.append(" \"times\": ["); + DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getInstance(Locale.ENGLISH); + decimalFormat.applyPattern("0.###"); + for (OsmPathElement n : t.nodes) { + sb.append(decimalFormat.format(n.getTime())).append(","); + } + sb.deleteCharAt(sb.lastIndexOf(",")); + sb.append("]\n"); + } else { + sb.deleteCharAt(sb.lastIndexOf(",")); + } + + sb.append(" },\n"); + + if (t.iternity != null) { + sb.append(" \"iternity\": [\n"); + for (String s : t.iternity) { + sb.append(" \"").append(s).append("\",\n"); + } + sb.deleteCharAt(sb.lastIndexOf(",")); + sb.append(" ],\n"); + } + sb.append(" \"geometry\": {\n"); + sb.append(" \"type\": \"LineString\",\n"); + sb.append(" \"coordinates\": [\n"); + + OsmPathElement nn = null; + for (OsmPathElement n : t.nodes) { + String sele = n.getSElev() == Short.MIN_VALUE ? "" : ", " + n.getElev(); + if (t.showspeed) { // hack: show speed instead of elevation + double speed = 0; + if (nn != null) { + int dist = n.calcDistance(nn); + float dt = n.getTime() - nn.getTime(); + if (dt != 0.f) { + speed = ((3.6f * dist) / dt + 0.5); + } + } + sele = ", " + (((int) (speed * 10)) / 10.f); + } + sb.append(" [").append(formatILon(n.getILon())).append(", ").append(formatILat(n.getILat())) + .append(sele).append("],\n"); + nn = n; + } + sb.deleteCharAt(sb.lastIndexOf(",")); + + sb.append(" ]\n"); + sb.append(" }\n"); + if (t.exportWaypoints || !t.pois.isEmpty()) { + sb.append(" },\n"); + for (int i = 0; i <= t.pois.size() - 1; i++) { + OsmNodeNamed poi = t.pois.get(i); + addFeature(sb, "poi", poi.name, poi.ilat, poi.ilon); + if (i < t.matchedWaypoints.size() - 1) { + sb.append(","); + } + sb.append(" \n"); + } + if (t.exportWaypoints) { + for (int i = 0; i <= t.matchedWaypoints.size() - 1; i++) { + String type; + if (i == 0) { + type = "from"; + } else if (i == t.matchedWaypoints.size() - 1) { + type = "to"; + } else { + type = "via"; + } + + MatchedWaypoint wp = t.matchedWaypoints.get(i); + addFeature(sb, type, wp.name, wp.waypoint.ilat, wp.waypoint.ilon); + if (i < t.matchedWaypoints.size() - 1) { + sb.append(","); + } + sb.append(" \n"); + } + } + } else { + sb.append(" }\n"); + } + sb.append(" ]\n"); + sb.append("}\n"); + + return sb.toString(); + } + + private void addFeature(StringBuilder sb, String type, String name, int ilat, int ilon) { + sb.append(" {\n"); + sb.append(" \"type\": \"Feature\",\n"); + sb.append(" \"properties\": {\n"); + sb.append(" \"name\": \"" + StringUtils.escapeJson(name) + "\",\n"); + sb.append(" \"type\": \"" + type + "\"\n"); + sb.append(" },\n"); + sb.append(" \"geometry\": {\n"); + sb.append(" \"type\": \"Point\",\n"); + sb.append(" \"coordinates\": [\n"); + sb.append(" " + formatILon(ilon) + ",\n"); + sb.append(" " + formatILat(ilat) + "\n"); + sb.append(" ]\n"); + sb.append(" }\n"); + sb.append(" }"); + } + + public String formatAsWaypoint(OsmNodeNamed n) { + try { + StringWriter sw = new StringWriter(8192); + BufferedWriter bw = new BufferedWriter(sw); + addJsonHeader(bw); + addJsonFeature(bw, "info", "wpinfo", n.ilon, n.ilat, n.getElev(), (n.nodeDescription != null ? rc.expctxWay.getKeyValueDescription(false, n.nodeDescription) : null)); + addJsonFooter(bw); + bw.close(); + sw.close(); + return sw.toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void addJsonFeature(BufferedWriter sb, String type, String name, int ilon, int ilat, double elev, String desc) { + try { + sb.append(" {\n"); + sb.append(" \"type\": \"Feature\",\n"); + sb.append(" \"properties\": {\n"); + sb.append(" \"creator\": \"BRouter-" + OsmTrack.version + "\",\n"); + sb.append(" \"name\": \"" + StringUtils.escapeJson(name) + "\",\n"); + sb.append(" \"type\": \"" + type + "\""); + if (desc != null) { + sb.append(",\n \"message\": \"" + desc + "\"\n"); + } else { + sb.append("\n"); + } + sb.append(" },\n"); + sb.append(" \"geometry\": {\n"); + sb.append(" \"type\": \"Point\",\n"); + sb.append(" \"coordinates\": [\n"); + sb.append(" " + formatILon(ilon) + ",\n"); + sb.append(" " + formatILat(ilat) + ",\n"); + sb.append(" " + elev + "\n"); + sb.append(" ]\n"); + sb.append(" }\n"); + sb.append(" }\n"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void addJsonHeader(BufferedWriter sb) { + try { + sb.append("{\n"); + sb.append(" \"type\": \"FeatureCollection\",\n"); + sb.append(" \"features\": [\n"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void addJsonFooter(BufferedWriter sb) { + try { + sb.append(" ]\n"); + sb.append("}\n"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/brouter-core/src/main/java/btools/router/FormatKml.java b/brouter-core/src/main/java/btools/router/FormatKml.java new file mode 100644 index 0000000..5798c5c --- /dev/null +++ b/brouter-core/src/main/java/btools/router/FormatKml.java @@ -0,0 +1,91 @@ +package btools.router; + +import java.util.List; + +import btools.mapaccess.MatchedWaypoint; +import btools.util.StringUtils; + +public class FormatKml extends Formatter { + public FormatKml(RoutingContext rc) { + super(rc); + } + + @Override + public String format(OsmTrack t) { + StringBuilder sb = new StringBuilder(8192); + + sb.append("\n"); + + sb.append("\n"); + sb.append(" \n"); + sb.append(" KML Samples\n"); + sb.append(" 1\n"); + sb.append(" 3.497064\n"); + sb.append(" 872\n"); + sb.append(" To enable simple instructions add: 'instructions=1' as parameter to the URL\n"); + sb.append(" \n"); + sb.append(" Paths\n"); + sb.append(" 0\n"); + sb.append(" Examples of paths.\n"); + sb.append(" \n"); + sb.append(" Tessellated\n"); + sb.append(" 0\n"); + sb.append(" tag has a value of 1, the line will contour to the underlying terrain]]>\n"); + sb.append(" \n"); + sb.append(" 1\n"); + sb.append(" "); + + for (OsmPathElement n : t.nodes) { + sb.append(formatILon(n.getILon())).append(",").append(formatILat(n.getILat())).append("\n"); + } + + sb.append(" \n"); + sb.append(" \n"); + sb.append(" \n"); + sb.append(" \n"); + if (t.exportWaypoints || !t.pois.isEmpty()) { + if (!t.pois.isEmpty()) { + sb.append(" \n"); + sb.append(" poi\n"); + for (int i = 0; i < t.pois.size(); i++) { + OsmNodeNamed poi = t.pois.get(i); + createPlaceMark(sb, poi.name, poi.ilat, poi.ilon); + } + sb.append(" \n"); + } + + if (t.exportWaypoints) { + int size = t.matchedWaypoints.size(); + createFolder(sb, "start", t.matchedWaypoints.subList(0, 1)); + if (t.matchedWaypoints.size() > 2) { + createFolder(sb, "via", t.matchedWaypoints.subList(1, size - 1)); + } + createFolder(sb, "end", t.matchedWaypoints.subList(size - 1, size)); + } + } + sb.append(" \n"); + sb.append("\n"); + + return sb.toString(); + } + + private void createFolder(StringBuilder sb, String type, List waypoints) { + sb.append(" \n"); + sb.append(" " + type + "\n"); + for (int i = 0; i < waypoints.size(); i++) { + MatchedWaypoint wp = waypoints.get(i); + createPlaceMark(sb, wp.name, wp.waypoint.ilat, wp.waypoint.ilon); + } + sb.append(" \n"); + } + + private void createPlaceMark(StringBuilder sb, String name, int ilat, int ilon) { + sb.append(" \n"); + sb.append(" " + StringUtils.escapeXml10(name) + "\n"); + sb.append(" \n"); + sb.append(" " + formatILon(ilon) + "," + formatILat(ilat) + "\n"); + sb.append(" \n"); + sb.append(" \n"); + } + +} diff --git a/brouter-core/src/main/java/btools/router/Formatter.java b/brouter-core/src/main/java/btools/router/Formatter.java new file mode 100644 index 0000000..190d279 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/Formatter.java @@ -0,0 +1,73 @@ +package btools.router; + +import java.io.BufferedWriter; +import java.io.FileWriter; + +public abstract class Formatter { + private static final int OUTPUT_FORMAT_GPX = 0; + private static final int OUTPUT_FORMAT_KML = 1; + private static final int OUTPUT_FORMAT_JSON = 2; + private static final int OUTPUT_FORMAT_CSV = 3; + + static final String MESSAGES_HEADER = "Longitude\tLatitude\tElevation\tDistance\tCostPerKm\tElevCost\tTurnCost\tNodeCost\tInitialCost\tWayTags\tNodeTags\tTime\tEnergy"; + + RoutingContext rc; + + Formatter() { + } + + Formatter(RoutingContext rc) { + this.rc = rc; + } + + /** + * writes the track in gpx-format to a file + * + * @param filename the filename to write to + * @param t the track to write + */ + public void write(String filename, OsmTrack t) throws Exception { + BufferedWriter bw = new BufferedWriter(new FileWriter(filename)); + bw.write(format(t)); + bw.close(); + } + + public OsmTrack read(String filename) throws Exception { + return null; + } + + /** + * writes the track in a selected output format to a string + * + * @param t the track to format + * @return the formatted string + */ + public abstract String format(OsmTrack t); + + + static String formatILon(int ilon) { + return formatPos(ilon - 180000000); + } + + static String formatILat(int ilat) { + return formatPos(ilat - 90000000); + } + + private static String formatPos(int p) { + boolean negative = p < 0; + if (negative) + p = -p; + char[] ac = new char[12]; + int i = 11; + while (p != 0 || i > 3) { + ac[i--] = (char) ('0' + (p % 10)); + p /= 10; + if (i == 5) + ac[i--] = '.'; + } + if (negative) + ac[i--] = '-'; + return new String(ac, i + 1, 11 - i); + } + +} From c47444e0f881fe146385dcecaf0b6357c5050de6 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 20 Nov 2023 17:09:14 +0100 Subject: [PATCH 068/173] enabled functions for public --- .../src/main/java/btools/router/OsmTrack.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index ac3b51f..d070256 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -66,7 +66,7 @@ public final class OsmTrack { private CompactLongMap detourMap; - private VoiceHintList voiceHints; + public VoiceHintList voiceHints; public String message = null; public List messageList = null; @@ -178,7 +178,7 @@ public final class OsmTrack { nodesMap = new FrozenLongMap<>(nodesMap); } - private List aggregateMessages() { + public List aggregateMessages() { ArrayList res = new ArrayList<>(); MessageData current = null; for (OsmPathElement n : nodes) { @@ -200,7 +200,7 @@ public final class OsmTrack { return res; } - private List aggregateSpeedProfile() { + public List aggregateSpeedProfile() { ArrayList res = new ArrayList<>(); int vmax = -1; int vmaxe = -1; @@ -1140,7 +1140,7 @@ public final class OsmTrack { sb.append(" }"); } - private VoiceHint getVoiceHint(int i) { + public VoiceHint getVoiceHint(int i) { if (voiceHints == null) return null; for (VoiceHint hint : voiceHints.list) { if (hint.indexInTrack == i) { @@ -1150,7 +1150,7 @@ public final class OsmTrack { return null; } - private MatchedWaypoint getMatchedWaypoint(int idx) { + public MatchedWaypoint getMatchedWaypoint(int idx) { if (matchedWaypoints == null) return null; for (MatchedWaypoint wp : matchedWaypoints) { if (idx == wp.indexInTrack) { @@ -1168,7 +1168,7 @@ public final class OsmTrack { return vnode0 < vnode1 ? vnode0 : vnode1; } - private int getTotalSeconds() { + public int getTotalSeconds() { float s = nodes.size() < 2 ? 0 : nodes.get(nodes.size() - 1).getTime() - nodes.get(0).getTime(); return (int) (s + 0.5); } @@ -1398,7 +1398,7 @@ public final class OsmTrack { return 2; } - private float getVoiceHintTime(int i) { + public float getVoiceHintTime(int i) { if (voiceHints.list.isEmpty()) { return 0f; } From ad3db9c00450abeaddb96f442a26fc95f7e78c2b Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 20 Nov 2023 17:33:09 +0100 Subject: [PATCH 069/173] updated output for command line --- .../java/btools/router/RoutingEngine.java | 80 +++++++++++++++---- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index baff3dc..c3c2597 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -199,27 +199,63 @@ public class RoutingEngine extends Thread { messageList.add(track.message); track.messageList = messageList; if (outfileBase != null) { - String filename = outfileBase + i + ".gpx"; - OsmTrack oldTrack = new OsmTrack(); - oldTrack.readGpx(filename); - if (track.equalsTrack(oldTrack)) { + String filename = outfileBase + i + "." + routingContext.outputFormat; + OsmTrack oldTrack = null; + switch (routingContext.outputFormat) { + case "gpx": + oldTrack = new FormatGpx(routingContext).read(filename); + break; + case "geojson": // read only gpx at the moment + case "json": + // oldTrack = new FormatJson(routingContext).read(filename); + break; + case "kml": + // oldTrack = new FormatJson(routingContext).read(filename); + break; + default: + break; + } + if (oldTrack != null && track.equalsTrack(oldTrack)) { continue; } oldTrack = null; track.exportWaypoints = routingContext.exportWaypoints; - // doesn't work at the moment - // use routingContext.outputFormat - track.writeGpx(filename); + filename = outfileBase + i + "." + routingContext.outputFormat; + switch (routingContext.outputFormat) { + case "gpx": + outputMessage = new FormatGpx(routingContext).format(track); + break; + case "geojson": + case "json": + outputMessage = new FormatJson(routingContext).format(track); + break; + case "kml": + outputMessage = new FormatKml(routingContext).format(track); + break; + case "csv": + default: + outputMessage = null; + break; + } + if (outputMessage != null) { + File out = new File(filename); + FileWriter fw = new FileWriter(filename); + fw.write(outputMessage); + fw.close(); + outputMessage = null; + } + foundTrack = track; alternativeIndex = i; outfile = filename; } else { if (i == routingContext.getAlternativeIdx(0, 3)) { if ("CSV".equals(System.getProperty("reportFormat"))) { - track.dumpMessages(null, routingContext); + String filename = outfileBase + i + ".csv"; + new FormatCsv(routingContext).write(filename, track); } else { if (!quite) { - System.out.println(track.formatAsGpx()); + System.out.println(new FormatGpx(routingContext).format(track)); } } foundTrack = track; @@ -229,7 +265,7 @@ public class RoutingEngine extends Thread { } if (logfileBase != null) { String logfilename = logfileBase + i + ".csv"; - track.dumpMessages(logfilename, routingContext); + new FormatCsv(routingContext).write(logfilename, track); } break; } @@ -308,15 +344,27 @@ public class RoutingEngine extends Thread { OsmNodeNamed n = new OsmNodeNamed(listOne.get(0).crosspoint); n.selev = startNode != null ? startNode.getSElev() : Short.MIN_VALUE; - // doesn't work at the moment - // use routingContext.outputFormat - outputMessage = OsmTrack.formatAsGpxWaypoint(n); + switch (routingContext.outputFormat) { + case "gpx": + outputMessage = new FormatGpx(routingContext).formatAsWaypoint(n); + break; + case "geojson": + case "json": + outputMessage = new FormatJson(routingContext).formatAsWaypoint(n); + break; + case "kml": + case "csv": + default: + outputMessage = null; + break; + } if (outfileBase != null) { - String filename = outfileBase + ".gpx"; + String filename = outfileBase + "." + routingContext.outputFormat; File out = new File(filename); FileWriter fw = new FileWriter(filename); fw.write(outputMessage); fw.close(); + outputMessage = null; } long endTime = System.currentTimeMillis(); logInfo("execution time = " + (endTime - startTime) / 1000. + " seconds"); @@ -951,7 +999,7 @@ public class RoutingEngine extends Thread { if (track == null) { for (int cfi = 0; cfi < airDistanceCostFactors.length; cfi++) { - if (cfi > 0) lastAirDistanceCostFactor = airDistanceCostFactors[cfi-1]; + if (cfi > 0) lastAirDistanceCostFactor = airDistanceCostFactors[cfi - 1]; airDistanceCostFactor = airDistanceCostFactors[cfi]; if (airDistanceCostFactor < 0.) { @@ -1447,7 +1495,7 @@ public class RoutingEngine extends Thread { boolean inRadius = boundary == null || boundary.isInBoundary(nextNode, bestPath.cost); - if (inRadius && (isFinalLink || bestPath.cost + bestPath.airdistance <= (lastAirDistanceCostFactor != 0. ? maxTotalCost*lastAirDistanceCostFactor : maxTotalCost) + addDiff)) { + if (inRadius && (isFinalLink || bestPath.cost + bestPath.airdistance <= (lastAirDistanceCostFactor != 0. ? maxTotalCost * lastAirDistanceCostFactor : maxTotalCost) + addDiff)) { // add only if this may beat an existing path for that link OsmLinkHolder dominator = link.getFirstLinkHolder(currentNode); while (!trafficSim && dominator != null) { From 149b83056e45df9cf4444e7a397b600c084e459d Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 20 Nov 2023 17:39:29 +0100 Subject: [PATCH 070/173] updated output for server --- .../btools/server/request/ServerHandler.java | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) 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 898121c..505fd2b 100644 --- a/brouter-server/src/main/java/btools/server/request/ServerHandler.java +++ b/brouter-server/src/main/java/btools/server/request/ServerHandler.java @@ -1,10 +1,12 @@ package btools.server.request; -import java.io.BufferedWriter; import java.io.File; -import java.io.StringWriter; import java.util.Map; +import btools.router.FormatCsv; +import btools.router.FormatGpx; +import btools.router.FormatJson; +import btools.router.FormatKml; import btools.router.OsmTrack; import btools.router.RoutingContext; import btools.server.ServiceContext; @@ -76,23 +78,17 @@ public class ServerHandler extends RequestHandler { } if (format == null || "gpx".equals(format)) { - result = track.formatAsGpx(); + result = new FormatGpx(rc).format(track); } else if ("kml".equals(format)) { - result = track.formatAsKml(); + result = new FormatKml(rc).format(track); } else if ("geojson".equals(format)) { - result = track.formatAsGeoJson(); + result = new FormatJson(rc).format(track); } else if ("csv".equals(format)) { - try { - StringWriter sw = new StringWriter(); - BufferedWriter bw = new BufferedWriter(sw); - track.writeMessages(bw, rc); - return sw.toString(); - } catch (Exception ex) { - return "Error: " + ex.getMessage(); - } + result = new FormatCsv(rc).format(track); } else { System.out.println("unknown track format '" + format + "', using default"); - result = track.formatAsGpx(); + //result = track.formatAsGpx(); + result = new FormatGpx(rc).format(track); } return result; From 24e15b54663e3366f2858ca60003136d88d8b8a9 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 20 Nov 2023 18:12:21 +0100 Subject: [PATCH 071/173] updated output for app --- .../java/btools/routingapp/BRouterWorker.java | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 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 5823db5..64e73d5 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java @@ -12,6 +12,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import btools.router.FormatGpx; +import btools.router.FormatJson; +import btools.router.FormatKml; import btools.router.OsmNodeNamed; import btools.router.OsmTrack; import btools.router.RoutingContext; @@ -151,42 +154,41 @@ public class BRouterWorker { if ("kml".equals(rc.outputFormat)) writeFromat = OUTPUT_FORMAT_KML; if ("json".equals(rc.outputFormat)) writeFromat = OUTPUT_FORMAT_JSON; } - - OsmTrack track = cr.getFoundTrack(); + OsmTrack track = null; + track = cr.getFoundTrack(); if (track != null) { track.exportWaypoints = rc.exportWaypoints; if (pathToFileResult == null) { switch (writeFromat) { - case OUTPUT_FORMAT_GPX: - return track.formatAsGpx(); case OUTPUT_FORMAT_KML: - return track.formatAsKml(); + return new FormatKml(rc).format(track); case OUTPUT_FORMAT_JSON: - return track.formatAsGeoJson(); + return new FormatJson(rc).format(track); + case OUTPUT_FORMAT_GPX: default: - return track.formatAsGpx(); + return new FormatGpx(rc).format(track); } } - try { - switch (writeFromat) { - case OUTPUT_FORMAT_GPX: - track.writeGpx(pathToFileResult); - break; - case OUTPUT_FORMAT_KML: - track.writeKml(pathToFileResult); - break; - case OUTPUT_FORMAT_JSON: - track.writeJson(pathToFileResult); - break; - default: - track.writeGpx(pathToFileResult); - break; - } - } catch (Exception e) { - return "error writing file: " + e; - } + } + try { + switch (writeFromat) { + case OUTPUT_FORMAT_KML: + new FormatKml(rc).write(pathToFileResult, track); + break; + case OUTPUT_FORMAT_JSON: + new FormatJson(rc).write(pathToFileResult, track); + break; + case OUTPUT_FORMAT_GPX: + default: + new FormatGpx(rc).write(pathToFileResult, track); + break; + } + } catch (Exception e) { + return "error writing file: " + e; + } + } else { // get other infos if (cr.getErrorMessage() != null) { return cr.getErrorMessage(); From ac0d3ae518a73d5776facfb21d63d2f56fe0ba93 Mon Sep 17 00:00:00 2001 From: Joachim Lengacher Date: Tue, 21 Nov 2023 12:26:15 +0100 Subject: [PATCH 072/173] new Dockerfile --- Dockerfile | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f72e698 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM gradle:jdk17-jammy as build + +RUN mkdir /tmp/brouter +WORKDIR /tmp/brouter +COPY . . +RUN ./gradlew clean build + +FROM openjdk:17.0.1-jdk-slim +COPY --from=build /tmp/brouter/brouter-server/build/libs/brouter-*-all.jar /brouter.jar +COPY --from=build /tmp/brouter/misc/scripts/standalone/server.sh /bin/ + +CMD /bin/server.sh + From fcbaf598aa29698ebf3525c1b348b896ee4683ce Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 21 Nov 2023 13:00:39 +0100 Subject: [PATCH 073/173] moved some format routines --- .../main/java/btools/router/FormatGpx.java | 2 +- .../main/java/btools/router/Formatter.java | 41 +++++++++++++++++++ .../java/btools/router/RoutingEngine.java | 4 +- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/FormatGpx.java b/brouter-core/src/main/java/btools/router/FormatGpx.java index e162292..0ed6cc5 100644 --- a/brouter-core/src/main/java/btools/router/FormatGpx.java +++ b/brouter-core/src/main/java/btools/router/FormatGpx.java @@ -250,7 +250,7 @@ public class FormatGpx extends Formatter { MatchedWaypoint mwpt = t.getMatchedWaypoint(idx); if (t.showTime) { - sele += ""; + sele += ""; } if (turnInstructionMode == 8) { if (mwpt != null && diff --git a/brouter-core/src/main/java/btools/router/Formatter.java b/brouter-core/src/main/java/btools/router/Formatter.java index 190d279..b9c3276 100644 --- a/brouter-core/src/main/java/btools/router/Formatter.java +++ b/brouter-core/src/main/java/btools/router/Formatter.java @@ -2,6 +2,10 @@ package btools.router; import java.io.BufferedWriter; import java.io.FileWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; public abstract class Formatter { private static final int OUTPUT_FORMAT_GPX = 0; @@ -70,4 +74,41 @@ public abstract class Formatter { return new String(ac, i + 1, 11 - i); } + public static String getFormattedTime2(int s) { + int seconds = (int) (s + 0.5); + int hours = seconds / 3600; + int minutes = (seconds - hours * 3600) / 60; + seconds = seconds - hours * 3600 - minutes * 60; + String time = ""; + if (hours != 0) + time = "" + hours + "h "; + if (minutes != 0) + time = time + minutes + "m "; + if (seconds != 0) + time = time + seconds + "s"; + return time; + } + + static public String getFormattedEnergy(int energy) { + return format1(energy / 3600000.) + "kwh"; + } + + static private String format1(double n) { + String s = "" + (long) (n * 10 + 0.5); + int len = s.length(); + return s.substring(0, len - 1) + "." + s.charAt(len - 1); + } + + + static final String dateformat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + + static public String getFormattedTime3(float time) { + SimpleDateFormat TIMESTAMP_FORMAT = new SimpleDateFormat(dateformat, Locale.US); + TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + // yyyy-mm-ddThh:mm:ss.SSSZ + Date d = new Date((long) (time * 1000f)); + return TIMESTAMP_FORMAT.format(d); + } + + } diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index c3c2597..a3c2d1e 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -192,7 +192,7 @@ public class RoutingEngine extends Thread { track.message = "track-length = " + track.distance + " filtered ascend = " + track.ascend + " plain-ascend = " + track.plainAscend + " cost=" + track.cost; if (track.energy != 0) { - track.message += " energy=" + track.getFormattedEnergy() + " time=" + track.getFormattedTime2(); + track.message += " energy=" + Formatter.getFormattedEnergy(track.energy) + " time=" + Formatter.getFormattedTime2(track.getTotalSeconds()); } track.name = "brouter_" + routingContext.getProfileName() + "_" + i; @@ -1676,7 +1676,7 @@ public class RoutingEngine extends Thread { } public String getTime() { - return foundTrack.getFormattedTime2(); + return Formatter.getFormattedTime2(foundTrack.getTotalSeconds()); } public OsmTrack getFoundTrack() { From 8a7fa9fa810466751c68831a587e6a076ebfb560 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 21 Nov 2023 13:26:05 +0100 Subject: [PATCH 074/173] changes for engineMode 2 --- .../java/btools/router/RoutingEngine.java | 4 +++ .../src/main/java/btools/server/BRouter.java | 6 +++- .../main/java/btools/server/RouteServer.java | 31 +++++++++++++------ 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index a3c2d1e..105ed09 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -365,6 +365,10 @@ public class RoutingEngine extends Thread { fw.write(outputMessage); fw.close(); outputMessage = null; + } else { + if (!quite && outputMessage != null) { + System.out.println(outputMessage); + } } long endTime = System.currentTimeMillis(); logInfo("execution time = " + (endTime - startTime) / 1000. + " seconds"); diff --git a/brouter-server/src/main/java/btools/server/BRouter.java b/brouter-server/src/main/java/btools/server/BRouter.java index 3788e51..a6dfe10 100644 --- a/brouter-server/src/main/java/btools/server/BRouter.java +++ b/brouter-server/src/main/java/btools/server/BRouter.java @@ -30,6 +30,10 @@ public class BRouter { List wplist = routingParamCollector.getWayPointList(lonlats); Map params = routingParamCollector.getUrlParams(queryString); + int engineMode = 0; + if (params.containsKey("engineMode")) { + engineMode = Integer.parseInt(params.get("engineMode")); + } routingParamCollector.setParams(rc, wplist, params); String exportName = null; @@ -47,7 +51,7 @@ public class BRouter { maxRunningTime = Integer.parseInt(sMaxRunningTime) * 1000; } - RoutingEngine re = new RoutingEngine(exportName, null, new File(args[0]), wplist, rc); + RoutingEngine re = new RoutingEngine(exportName, null, new File(args[0]), wplist, rc, engineMode); re.doRun(maxRunningTime); if (re.getErrorMessage() != null) { System.out.println(re.getErrorMessage()); diff --git a/brouter-server/src/main/java/btools/server/RouteServer.java b/brouter-server/src/main/java/btools/server/RouteServer.java index bb5e98c..ba421f9 100644 --- a/brouter-server/src/main/java/btools/server/RouteServer.java +++ b/brouter-server/src/main/java/btools/server/RouteServer.java @@ -211,18 +211,29 @@ public class RouteServer extends Thread implements Comparable { } else { OsmTrack track = cr.getFoundTrack(); + if (engineMode == 2) { + // no zip for this engineMode + encodings = null; + } String headers = encodings == null || encodings.indexOf("gzip") < 0 ? null : "Content-Encoding: gzip\n"; writeHttpHeader(bw, handler.getMimeType(), handler.getFileName(), headers, HTTP_STATUS_OK); - if (track != null) { - if (headers != null) { // compressed - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - Writer w = new OutputStreamWriter(new GZIPOutputStream(baos), "UTF-8"); - w.write(handler.formatTrack(track)); - w.close(); - bw.flush(); - clientSocket.getOutputStream().write(baos.toByteArray()); - } else { - bw.write(handler.formatTrack(track)); + if (engineMode == 0) { + if (track != null) { + if (headers != null) { // compressed + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Writer w = new OutputStreamWriter(new GZIPOutputStream(baos), "UTF-8"); + w.write(handler.formatTrack(track)); + w.close(); + bw.flush(); + clientSocket.getOutputStream().write(baos.toByteArray()); + } else { + bw.write(handler.formatTrack(track)); + } + } + } else if (engineMode == 2) { + String s = cr.getFoundInfo(); + if (s != null) { + bw.write(s); } } } From cb0b1d88554a09acb73daa9e45a4846edeacda38 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 21 Nov 2023 14:05:19 +0100 Subject: [PATCH 075/173] removed unused formatting --- .../main/java/btools/router/Formatter.java | 4 - .../src/main/java/btools/router/OsmTrack.java | 873 ------------------ 2 files changed, 877 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/Formatter.java b/brouter-core/src/main/java/btools/router/Formatter.java index b9c3276..09bdb1d 100644 --- a/brouter-core/src/main/java/btools/router/Formatter.java +++ b/brouter-core/src/main/java/btools/router/Formatter.java @@ -8,10 +8,6 @@ import java.util.Locale; import java.util.TimeZone; public abstract class Formatter { - private static final int OUTPUT_FORMAT_GPX = 0; - private static final int OUTPUT_FORMAT_KML = 1; - private static final int OUTPUT_FORMAT_JSON = 2; - private static final int OUTPUT_FORMAT_CSV = 3; static final String MESSAGES_HEADER = "Longitude\tLatitude\tElevation\tDistance\tCostPerKm\tElevCost\tTurnCost\tNodeCost\tInitialCost\tWayTags\tNodeTags\tTime\tEnergy"; diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index d070256..9ccff98 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -7,33 +7,20 @@ package btools.router; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.StringWriter; -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.TimeZone; import btools.mapaccess.MatchedWaypoint; import btools.mapaccess.OsmPos; import btools.util.CompactLongMap; import btools.util.FrozenLongMap; -import btools.util.StringUtils; public final class OsmTrack { final public static String version = "1.7.3"; @@ -395,751 +382,8 @@ public final class OsmTrack { public int plainAscend; public int cost; public int energy; - - /** - * writes the track in gpx-format to a file - * - * @param filename the filename to write to - */ - public void writeGpx(String filename) throws Exception { - BufferedWriter bw = new BufferedWriter(new FileWriter(filename)); - formatAsGpx(bw); - bw.close(); - } - - public String formatAsGpx() { - try { - StringWriter sw = new StringWriter(8192); - BufferedWriter bw = new BufferedWriter(sw); - formatAsGpx(bw); - bw.close(); - return sw.toString(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public String formatAsGpx(BufferedWriter sb) throws IOException { - int turnInstructionMode = voiceHints != null ? voiceHints.turnInstructionMode : 0; - - sb.append("\n"); - if (turnInstructionMode != 9) { - for (int i = messageList.size() - 1; i >= 0; i--) { - String message = messageList.get(i); - if (i < messageList.size() - 1) - message = "(alt-index " + i + ": " + message + " )"; - if (message != null) - sb.append("\n"); - } - } - - if (turnInstructionMode == 4) { // comment style - sb.append("\n"); - sb.append("\n"); - sb.append("\n"); - } - sb.append("\n"); - } else { - sb.append(" creator=\"BRouter-" + version + "\" version=\"1.1\">\n"); - } - if (turnInstructionMode == 9) { - sb.append(" \n"); - sb.append(" ").append(name).append("\n"); - sb.append(" \n"); - sb.append(" ").append(messageList.get(0)).append("\n"); - if (params != null && params.size() > 0) { - sb.append(" e : params.entrySet()) { - if (i++ != 0) sb.append("&"); - sb.append(e.getKey()).append("=").append(e.getValue()); - } - sb.append("]]>\n"); - } - sb.append(" \n"); - sb.append(" \n"); - } - if (turnInstructionMode == 3 || turnInstructionMode == 8) { // osmand style, cruiser - float lastRteTime = 0; - - sb.append(" \n"); - - float rteTime = getVoiceHintTime(0); - StringBuffer first = new StringBuffer(); - // define start point - { - first.append(" \n") - .append(" start\n \n"); - if (rteTime != lastRteTime) { // add timing only if available - double t = rteTime - lastRteTime; - first.append(" \n"); - lastRteTime = rteTime; - } - first.append(" 0\n \n \n"); - } - if (turnInstructionMode == 8) { - if (matchedWaypoints.get(0).direct && voiceHints.list.get(0).indexInTrack == 0) { - // has a voice hint do nothing, voice hint will do - } else { - sb.append(first.toString()); - } - } else { - sb.append(first.toString()); - } - - for (int i = 0; i < voiceHints.list.size(); i++) { - VoiceHint hint = voiceHints.list.get(i); - sb.append(" \n") - .append(" ") - .append(turnInstructionMode == 3 ? hint.getMessageString() : hint.getCruiserMessageString()) - .append("\n \n"); - - rteTime = getVoiceHintTime(i + 1); - - if (rteTime != lastRteTime) { // add timing only if available - double t = rteTime - lastRteTime; - sb.append(" \n"); - lastRteTime = rteTime; - } - sb.append(" ") - .append(turnInstructionMode == 3 ? hint.getCommandString() : hint.getCruiserCommandString()) - .append("\n ").append("" + (int) hint.angle) - .append("\n ").append("" + hint.indexInTrack).append("\n \n \n"); - } - sb.append(" \n") - .append(" destination\n \n"); - sb.append(" \n"); - sb.append(" ").append("" + (nodes.size() - 1)).append("\n \n \n"); - - sb.append("\n"); - } - - if (turnInstructionMode == 7) { // old locus style - float lastRteTime = getVoiceHintTime(0); - - for (int i = 0; i < voiceHints.list.size(); i++) { - VoiceHint hint = voiceHints.list.get(i); - sb.append(" ") - .append(hint.selev == Short.MIN_VALUE ? "" : "" + (hint.selev / 4.) + "") - .append("").append(hint.getMessageString()).append("") - .append("").append("" + hint.distanceToNext).append(""); - float rteTime = getVoiceHintTime(i + 1); - if (rteTime != lastRteTime) { // add timing only if available - double t = rteTime - lastRteTime; - double speed = hint.distanceToNext / t; - sb.append("").append("" + t).append("") - .append("").append("" + speed).append(""); - lastRteTime = rteTime; - } - sb.append("").append("" + hint.getLocusAction()).append("") - .append("\n"); - } - } - if (turnInstructionMode == 5) { // gpsies style - for (VoiceHint hint : voiceHints.list) { - sb.append(" ") - .append("").append(hint.getMessageString()).append("") - .append("").append(hint.getSymbolString().toLowerCase()).append("") - .append("").append(hint.getSymbolString()).append("") - .append("\n"); - } - } - - if (turnInstructionMode == 6) { // orux style - for (VoiceHint hint : voiceHints.list) { - sb.append(" ") - .append(hint.selev == Short.MIN_VALUE ? "" : "" + (hint.selev / 4.) + "") - .append("\n" + - " \n" + - " ").append("" + hint.getOruxAction()) - .append("\n" + - " \n" + - " \n" + - " \n"); - } - } - - for (int i = 0; i <= pois.size() - 1; i++) { - OsmNodeNamed poi = pois.get(i); - sb.append(" \n") - .append(" ").append(StringUtils.escapeXml10(poi.name)).append("\n") - .append(" \n"); - } - - if (exportWaypoints) { - for (int i = 0; i <= matchedWaypoints.size() - 1; i++) { - MatchedWaypoint wt = matchedWaypoints.get(i); - sb.append(" \n") - .append(" ").append(StringUtils.escapeXml10(wt.name)).append("\n"); - if (i == 0) { - sb.append(" from\n"); - } else if (i == matchedWaypoints.size() - 1) { - sb.append(" to\n"); - } else { - sb.append(" via\n"); - } - sb.append(" \n"); - } - } - sb.append(" \n"); - if (turnInstructionMode == 9 - || turnInstructionMode == 2 - || turnInstructionMode == 8 - || turnInstructionMode == 4) { // Locus, comment, cruise, brouter style - sb.append(" ").append(name).append("\n"); - sb.append(" ").append(voiceHints.getTransportMode()).append("\n"); - } else { - sb.append(" ").append(name).append("\n"); - } - - if (turnInstructionMode == 7) { - sb.append(" \n"); - sb.append(" ").append("" + voiceHints.getLocusRouteType()).append("\n"); - sb.append(" 1\n"); - sb.append(" \n"); - } - - - // all points - sb.append(" \n"); - String lastway = ""; - boolean bNextDirect = false; - OsmPathElement nn = null; - String aSpeed; - - for (int idx = 0; idx < nodes.size(); idx++) { - OsmPathElement n = nodes.get(idx); - String sele = n.getSElev() == Short.MIN_VALUE ? "" : "" + n.getElev() + ""; - VoiceHint hint = getVoiceHint(idx); - MatchedWaypoint mwpt = getMatchedWaypoint(idx); - - if (showTime) { - sele += ""; - } - if (turnInstructionMode == 8) { - if (mwpt != null && - !mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) { - sele += "" + mwpt.name + ""; - } - } - boolean bNeedHeader = false; - if (turnInstructionMode == 9) { // trkpt/sym style - - if (hint != null) { - - if (mwpt != null && - !mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) { - sele += "" + mwpt.name + ""; - } - sele += "" + hint.getCruiserMessageString() + ""; - sele += "" + hint.getCommandString(hint.cmd) + ""; - if (mwpt != null) { - sele += "Via"; - } - sele += ""; - if (showspeed) { - double speed = 0; - if (nn != null) { - int dist = n.calcDistance(nn); - float dt = n.getTime() - nn.getTime(); - if (dt != 0.f) { - speed = ((3.6f * dist) / dt + 0.5); - } - } - sele += "" + (((int) (speed * 10)) / 10.f) + ""; - } - - sele += "" + hint.getCommandString() + ";" + (int) (hint.distanceToNext) + "," + hint.formatGeometry() + ""; - if (n.message != null && n.message.wayKeyValues != null && !n.message.wayKeyValues.equals(lastway)) { - sele += "" + n.message.wayKeyValues + ""; - lastway = n.message.wayKeyValues; - } - if (n.message != null && n.message.nodeKeyValues != null) { - sele += "" + n.message.nodeKeyValues + ""; - } - sele += ""; - - } - if (idx == 0 && hint == null) { - if (mwpt != null && mwpt.direct) { - sele += "beeline"; - } else { - sele += "start"; - } - sele += "Via"; - - } else if (idx == nodes.size() - 1 && hint == null) { - - sele += "end"; - sele += "Via"; - - } else { - if (mwpt != null && hint == null) { - if (mwpt.direct) { - // bNextDirect = true; - sele += "beeline"; - } else { - sele += "" + mwpt.name + ""; - } - sele += "Via"; - bNextDirect = false; - } - } - - - if (hint == null) { - bNeedHeader = (showspeed || (n.message != null && n.message.wayKeyValues != null && !n.message.wayKeyValues.equals(lastway))) || - (n.message != null && n.message.nodeKeyValues != null); - if (bNeedHeader) { - sele += ""; - if (showspeed) { - double speed = 0; - if (nn != null) { - int dist = n.calcDistance(nn); - float dt = n.getTime() - nn.getTime(); - if (dt != 0.f) { - speed = ((3.6f * dist) / dt + 0.5); - } - } - sele += "" + (((int) (speed * 10)) / 10.f) + ""; - } - if (n.message != null && n.message.wayKeyValues != null && !n.message.wayKeyValues.equals(lastway)) { - sele += "" + n.message.wayKeyValues + ""; - lastway = n.message.wayKeyValues; - } - if (n.message != null && n.message.nodeKeyValues != null) { - sele += "" + n.message.nodeKeyValues + ""; - } - sele += ""; - } - } - } - - if (turnInstructionMode == 2) { // locus style new - if (hint != null) { - if (mwpt != null) { - if (!mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) { - sele += "" + mwpt.name + ""; - } - if (mwpt.direct && bNextDirect) { - sele += "" + hint.getLocusSymbolString() + "pass_placeShaping"; - // bNextDirect = false; - } else if (mwpt.direct) { - if (idx == 0) - sele += "pass_placeVia"; - else - sele += "pass_placeShaping"; - bNextDirect = true; - } else if (bNextDirect) { - sele += "beeline" + hint.getLocusSymbolString() + "Shaping"; - bNextDirect = false; - } else { - sele += "" + hint.getLocusSymbolString() + "Via"; - } - } else { - sele += "" + hint.getLocusSymbolString() + ""; - } - } else { - if (idx == 0 && hint == null) { - - int pos = sele.indexOf(""; - if (mwpt != null && mwpt.direct) { - bNextDirect = true; - } - sele += "pass_place"; - sele += "Via"; - - } else if (idx == nodes.size() - 1 && hint == null) { - - int pos = sele.indexOf(""; - if (bNextDirect) { - sele += "beeline"; - } - sele += "pass_place"; - sele += "Via"; - - } else { - if (mwpt != null) { - if (!mwpt.name.startsWith("via") && !mwpt.name.startsWith("from") && !mwpt.name.startsWith("to")) { - sele += "" + mwpt.name + ""; - } - if (mwpt.direct && bNextDirect) { - sele += "beelinepass_placeShaping"; - } else if (mwpt.direct) { - if (idx == 0) - sele += "pass_placeVia"; - else - sele += "pass_placeShaping"; - bNextDirect = true; - } else if (bNextDirect) { - sele += "beelinepass_placeShaping"; - bNextDirect = false; - } else if (mwpt.name.startsWith("via") || - mwpt.name.startsWith("from") || - mwpt.name.startsWith("to")) { - if (bNextDirect) { - sele += "beelinepass_placeShaping"; - } else { - sele += "pass_placeVia"; - } - bNextDirect = false; - } else { - sele += "" + mwpt.name + ""; - sele += "pass_placeVia"; - } - } - } - } - } - sb.append(" ").append(sele).append("\n"); - - nn = n; - } - - sb.append(" \n"); - sb.append(" \n"); - sb.append("\n"); - - return sb.toString(); - } - - static public String formatAsGpxWaypoint(OsmNodeNamed n) { - try { - StringWriter sw = new StringWriter(8192); - BufferedWriter bw = new BufferedWriter(sw); - formatGpxHeader(bw); - formatWaypointGpx(bw, n); - formatGpxFooter(bw); - bw.close(); - sw.close(); - return sw.toString(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - static public void formatGpxHeader(BufferedWriter sb) throws IOException { - sb.append("\n"); - sb.append("\n"); - } - - static public void formatGpxFooter(BufferedWriter sb) throws IOException { - sb.append("\n"); - } - - static public void formatWaypointGpx(BufferedWriter sb, OsmNodeNamed n) throws IOException { - sb.append(" "); - if (n.getSElev() != Short.MIN_VALUE) { - sb.append("").append("" + n.getElev()).append(""); - } - if (n.name != null) { - sb.append("").append(StringUtils.escapeXml10(n.name)).append(""); - } - if (n.nodeDescription != null) { - sb.append("").append("hat desc").append(""); - } - sb.append("\n"); - } - - public void writeKml(String filename) throws Exception { - BufferedWriter bw = new BufferedWriter(new FileWriter(filename)); - - bw.write(formatAsKml()); - bw.close(); - } - - public String formatAsKml() { - StringBuilder sb = new StringBuilder(8192); - - sb.append("\n"); - - sb.append("\n"); - sb.append(" \n"); - sb.append(" KML Samples\n"); - sb.append(" 1\n"); - sb.append(" 3.497064\n"); - sb.append(" 872\n"); - sb.append(" To enable simple instructions add: 'instructions=1' as parameter to the URL\n"); - sb.append(" \n"); - sb.append(" Paths\n"); - sb.append(" 0\n"); - sb.append(" Examples of paths.\n"); - sb.append(" \n"); - sb.append(" Tessellated\n"); - sb.append(" 0\n"); - sb.append(" tag has a value of 1, the line will contour to the underlying terrain]]>\n"); - sb.append(" \n"); - sb.append(" 1\n"); - sb.append(" "); - - for (OsmPathElement n : nodes) { - sb.append(formatILon(n.getILon())).append(",").append(formatILat(n.getILat())).append("\n"); - } - - sb.append(" \n"); - sb.append(" \n"); - sb.append(" \n"); - sb.append(" \n"); - if (exportWaypoints || !pois.isEmpty()) { - if (!pois.isEmpty()) { - sb.append(" \n"); - sb.append(" poi\n"); - for (int i = 0; i < pois.size(); i++) { - OsmNodeNamed poi = pois.get(i); - createPlaceMark(sb, poi.name, poi.ilat, poi.ilon); - } - sb.append(" \n"); - } - - if (exportWaypoints) { - int size = matchedWaypoints.size(); - createFolder(sb, "start", matchedWaypoints.subList(0, 1)); - if (matchedWaypoints.size() > 2) { - createFolder(sb, "via", matchedWaypoints.subList(1, size - 1)); - } - createFolder(sb, "end", matchedWaypoints.subList(size - 1, size)); - } - } - sb.append(" \n"); - sb.append("\n"); - - return sb.toString(); - } - - private void createFolder(StringBuilder sb, String type, List waypoints) { - sb.append(" \n"); - sb.append(" " + type + "\n"); - for (int i = 0; i < waypoints.size(); i++) { - MatchedWaypoint wp = waypoints.get(i); - createPlaceMark(sb, wp.name, wp.waypoint.ilat, wp.waypoint.ilon); - } - sb.append(" \n"); - } - - private void createPlaceMark(StringBuilder sb, String name, int ilat, int ilon) { - sb.append(" \n"); - sb.append(" " + StringUtils.escapeXml10(name) + "\n"); - sb.append(" \n"); - sb.append(" " + formatILon(ilon) + "," + formatILat(ilat) + "\n"); - sb.append(" \n"); - sb.append(" \n"); - } - public List iternity; - public void writeJson(String filename) throws Exception { - BufferedWriter bw = new BufferedWriter(new FileWriter(filename)); - - bw.write(formatAsGeoJson()); - bw.close(); - } - - - public String formatAsGeoJson() { - int turnInstructionMode = voiceHints != null ? voiceHints.turnInstructionMode : 0; - - StringBuilder sb = new StringBuilder(8192); - - sb.append("{\n"); - sb.append(" \"type\": \"FeatureCollection\",\n"); - sb.append(" \"features\": [\n"); - sb.append(" {\n"); - sb.append(" \"type\": \"Feature\",\n"); - sb.append(" \"properties\": {\n"); - sb.append(" \"creator\": \"BRouter-" + version + "\",\n"); - sb.append(" \"name\": \"").append(name).append("\",\n"); - sb.append(" \"track-length\": \"").append(distance).append("\",\n"); - sb.append(" \"filtered ascend\": \"").append(ascend).append("\",\n"); - sb.append(" \"plain-ascend\": \"").append(plainAscend).append("\",\n"); - sb.append(" \"total-time\": \"").append(getTotalSeconds()).append("\",\n"); - sb.append(" \"total-energy\": \"").append(energy).append("\",\n"); - sb.append(" \"cost\": \"").append(cost).append("\",\n"); - if (voiceHints != null && !voiceHints.list.isEmpty()) { - sb.append(" \"voicehints\": [\n"); - for (VoiceHint hint : voiceHints.list) { - sb.append(" ["); - sb.append(hint.indexInTrack); - sb.append(',').append(hint.getJsonCommandIndex()); - sb.append(',').append(hint.getExitNumber()); - sb.append(',').append(hint.distanceToNext); - sb.append(',').append((int) hint.angle); - - // not always include geometry because longer and only needed for comment style - if (turnInstructionMode == 4) { // comment style - sb.append(",\"").append(hint.formatGeometry()).append("\""); - } - - sb.append("],\n"); - } - sb.deleteCharAt(sb.lastIndexOf(",")); - sb.append(" ],\n"); - } - if (showSpeedProfile) { // set in profile - List sp = aggregateSpeedProfile(); - if (sp.size() > 0) { - sb.append(" \"speedprofile\": [\n"); - for (int i = sp.size() - 1; i >= 0; i--) { - sb.append(" [").append(sp.get(i)).append(i > 0 ? "],\n" : "]\n"); - } - sb.append(" ],\n"); - } - } - // ... traditional message list - { - sb.append(" \"messages\": [\n"); - sb.append(" [\"").append(MESSAGES_HEADER.replaceAll("\t", "\", \"")).append("\"],\n"); - for (String m : aggregateMessages()) { - sb.append(" [\"").append(m.replaceAll("\t", "\", \"")).append("\"],\n"); - } - sb.deleteCharAt(sb.lastIndexOf(",")); - sb.append(" ],\n"); - } - - if (getTotalSeconds() > 0) { - sb.append(" \"times\": ["); - DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getInstance(Locale.ENGLISH); - decimalFormat.applyPattern("0.###"); - for (OsmPathElement n : nodes) { - sb.append(decimalFormat.format(n.getTime())).append(","); - } - sb.deleteCharAt(sb.lastIndexOf(",")); - sb.append("]\n"); - } else { - sb.deleteCharAt(sb.lastIndexOf(",")); - } - - sb.append(" },\n"); - - if (iternity != null) { - sb.append(" \"iternity\": [\n"); - for (String s : iternity) { - sb.append(" \"").append(s).append("\",\n"); - } - sb.deleteCharAt(sb.lastIndexOf(",")); - sb.append(" ],\n"); - } - sb.append(" \"geometry\": {\n"); - sb.append(" \"type\": \"LineString\",\n"); - sb.append(" \"coordinates\": [\n"); - - OsmPathElement nn = null; - for (OsmPathElement n : nodes) { - String sele = n.getSElev() == Short.MIN_VALUE ? "" : ", " + n.getElev(); - if (showspeed) { // hack: show speed instead of elevation - double speed = 0; - if (nn != null) { - int dist = n.calcDistance(nn); - float dt = n.getTime() - nn.getTime(); - if (dt != 0.f) { - speed = ((3.6f * dist) / dt + 0.5); - } - } - sele = ", " + (((int) (speed * 10)) / 10.f); - } - sb.append(" [").append(formatILon(n.getILon())).append(", ").append(formatILat(n.getILat())) - .append(sele).append("],\n"); - nn = n; - } - sb.deleteCharAt(sb.lastIndexOf(",")); - - sb.append(" ]\n"); - sb.append(" }\n"); - if (exportWaypoints || !pois.isEmpty()) { - sb.append(" },\n"); - for (int i = 0; i <= pois.size() - 1; i++) { - OsmNodeNamed poi = pois.get(i); - addFeature(sb, "poi", poi.name, poi.ilat, poi.ilon); - if (i < matchedWaypoints.size() - 1) { - sb.append(","); - } - sb.append(" \n"); - } - if (exportWaypoints) { - for (int i = 0; i <= matchedWaypoints.size() - 1; i++) { - String type; - if (i == 0) { - type = "from"; - } else if (i == matchedWaypoints.size() - 1) { - type = "to"; - } else { - type = "via"; - } - - MatchedWaypoint wp = matchedWaypoints.get(i); - addFeature(sb, type, wp.name, wp.waypoint.ilat, wp.waypoint.ilon); - if (i < matchedWaypoints.size() - 1) { - sb.append(","); - } - sb.append(" \n"); - } - } - } else { - sb.append(" }\n"); - } - sb.append(" ]\n"); - sb.append("}\n"); - - return sb.toString(); - } - - private void addFeature(StringBuilder sb, String type, String name, int ilat, int ilon) { - sb.append(" {\n"); - sb.append(" \"type\": \"Feature\",\n"); - sb.append(" \"properties\": {\n"); - sb.append(" \"name\": \"" + StringUtils.escapeJson(name) + "\",\n"); - sb.append(" \"type\": \"" + type + "\"\n"); - sb.append(" },\n"); - sb.append(" \"geometry\": {\n"); - sb.append(" \"type\": \"Point\",\n"); - sb.append(" \"coordinates\": [\n"); - sb.append(" " + formatILon(ilon) + ",\n"); - sb.append(" " + formatILat(ilat) + "\n"); - sb.append(" ]\n"); - sb.append(" }\n"); - sb.append(" }"); - } - public VoiceHint getVoiceHint(int i) { if (voiceHints == null) return null; for (VoiceHint hint : voiceHints.list) { @@ -1173,123 +417,6 @@ public final class OsmTrack { return (int) (s + 0.5); } - public String getFormattedTime() { - return format1(getTotalSeconds() / 60.) + "m"; - } - - public String getFormattedTime2() { - int seconds = (int) (getTotalSeconds() + 0.5); - int hours = seconds / 3600; - int minutes = (seconds - hours * 3600) / 60; - seconds = seconds - hours * 3600 - minutes * 60; - String time = ""; - if (hours != 0) - time = "" + hours + "h "; - if (minutes != 0) - time = time + minutes + "m "; - if (seconds != 0) - time = time + seconds + "s"; - return time; - } - - SimpleDateFormat TIMESTAMP_FORMAT; - - public String getFormattedTime3(float time) { - if (TIMESTAMP_FORMAT == null) { - TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); - TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); - } - // yyyy-mm-ddThh:mm:ss.SSSZ - Date d = new Date((long) (time * 1000f)); - return TIMESTAMP_FORMAT.format(d); - } - - public String getFormattedEnergy() { - return format1(energy / 3600000.) + "kwh"; - } - - private static String formatILon(int ilon) { - return formatPos(ilon - 180000000); - } - - private static String formatILat(int ilat) { - return formatPos(ilat - 90000000); - } - - private static String formatPos(int p) { - boolean negative = p < 0; - if (negative) - p = -p; - char[] ac = new char[12]; - int i = 11; - while (p != 0 || i > 3) { - ac[i--] = (char) ('0' + (p % 10)); - p /= 10; - if (i == 5) - ac[i--] = '.'; - } - if (negative) - ac[i--] = '-'; - return new String(ac, i + 1, 11 - i); - } - - private String format1(double n) { - String s = "" + (long) (n * 10 + 0.5); - int len = s.length(); - return s.substring(0, len - 1) + "." + s.charAt(len - 1); - } - - public void dumpMessages(String filename, RoutingContext rc) throws Exception { - BufferedWriter bw = filename == null ? null : new BufferedWriter(new FileWriter(filename)); - writeMessages(bw, rc); - } - - public void writeMessages(BufferedWriter bw, RoutingContext rc) throws Exception { - dumpLine(bw, MESSAGES_HEADER); - for (String m : aggregateMessages()) { - dumpLine(bw, m); - } - if (bw != null) - bw.close(); - } - - private void dumpLine(BufferedWriter bw, String s) throws Exception { - if (bw == null) { - System.out.println(s); - } else { - bw.write(s); - bw.write("\n"); - } - } - - public void readGpx(String filename) throws Exception { - File f = new File(filename); - if (!f.exists()) - return; - BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f))); - - for (; ; ) { - String line = br.readLine(); - if (line == null) - break; - - int idx0 = line.indexOf("= 0) { - idx0 += 12; - int idx1 = line.indexOf('"', idx0); - int ilon = (int) ((Double.parseDouble(line.substring(idx0, idx1)) + 180.) * 1000000. + 0.5); - int idx2 = line.indexOf(" lat=\""); - if (idx2 < 0) - continue; - idx2 += 6; - int idx3 = line.indexOf('"', idx2); - int ilat = (int) ((Double.parseDouble(line.substring(idx2, idx3)) + 90.) * 1000000. + 0.5); - nodes.add(OsmPathElement.create(ilon, ilat, (short) 0, null, false)); - } - } - br.close(); - } - public boolean equalsTrack(OsmTrack t) { if (nodes.size() != t.nodes.size()) return false; From c52bd86dd5ad9b4f695a5a03982ec6af8837f70e Mon Sep 17 00:00:00 2001 From: Emux Date: Mon, 20 Nov 2023 15:22:20 +0200 Subject: [PATCH 076/173] Translations: Arabic, Catalan, Dutch, French, German, Greek, Italian, Polish, Spanish --- brouter-routing-app/build.gradle | 2 -- .../btools/routingapp/BInstallerActivity.java | 2 +- .../src/main/res/values-ar/strings.xml | 16 +++++++++ .../src/main/res/values-ca/strings.xml | 16 +++++++++ .../src/main/res/values-de/strings.xml | 16 +++++++++ .../src/main/res/values-el/strings.xml | 16 +++++++++ .../src/main/res/values-es/strings.xml | 16 +++++++++ .../src/main/res/values-fr/strings.xml | 16 +++++++++ .../src/main/res/values-it/strings.xml | 16 +++++++++ .../src/main/res/values-nl/strings.xml | 16 +++++++++ .../src/main/res/values-pl/strings.xml | 16 +++++++++ .../res/values/donottranslate-strings.xml | 7 ++++ .../src/main/res/values/strings.xml | 35 ++++--------------- 13 files changed, 159 insertions(+), 31 deletions(-) create mode 100644 brouter-routing-app/src/main/res/values-ar/strings.xml create mode 100644 brouter-routing-app/src/main/res/values-ca/strings.xml create mode 100644 brouter-routing-app/src/main/res/values-de/strings.xml create mode 100644 brouter-routing-app/src/main/res/values-el/strings.xml create mode 100644 brouter-routing-app/src/main/res/values-es/strings.xml create mode 100644 brouter-routing-app/src/main/res/values-fr/strings.xml create mode 100644 brouter-routing-app/src/main/res/values-it/strings.xml create mode 100644 brouter-routing-app/src/main/res/values-nl/strings.xml create mode 100644 brouter-routing-app/src/main/res/values-pl/strings.xml create mode 100644 brouter-routing-app/src/main/res/values/donottranslate-strings.xml diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index 9cd72f4..2300600 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -20,8 +20,6 @@ android { minSdkVersion 14 targetSdkVersion 33 - resConfigs "en" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java index c49a6be..f9ffe61 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java @@ -171,7 +171,7 @@ public class BInstallerActivity extends AppCompatActivity { } mButtonDownload.setText(getString(R.string.action_download, getSegmentsPlural(selectedTilesDownload.size()))); mButtonDownload.setEnabled(true); - mSummaryInfo.setText(getString(R.string.summary_segments, Formatter.formatFileSize(this, tileSize), Formatter.formatFileSize(this, getAvailableSpace(mBaseDir.getAbsolutePath())))); + mSummaryInfo.setText(String.format(getString(R.string.summary_segments), Formatter.formatFileSize(this, tileSize), Formatter.formatFileSize(this, getAvailableSpace(mBaseDir.getAbsolutePath())))); } else if (selectedTilesUpdate.size() > 0) { mButtonDownload.setText(getString(R.string.action_update, getSegmentsPlural(selectedTilesUpdate.size()))); mButtonDownload.setEnabled(true); diff --git a/brouter-routing-app/src/main/res/values-ar/strings.xml b/brouter-routing-app/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000..43e2663 --- /dev/null +++ b/brouter-routing-app/src/main/res/values-ar/strings.xml @@ -0,0 +1,16 @@ + + + + %d قطعة + %d قطع + + إلغاء التنزيل + استيراد مل٠تعري٠+ تنزيل %s + حذ٠%s + تحديث %s + حدد القطع + إيقا٠التنزيل + الحجم=%1$s\nالحجم Ø§Ù„Ù…ØªÙˆÙØ±=%2$s + تحميل القطع + diff --git a/brouter-routing-app/src/main/res/values-ca/strings.xml b/brouter-routing-app/src/main/res/values-ca/strings.xml new file mode 100644 index 0000000..13aeaea --- /dev/null +++ b/brouter-routing-app/src/main/res/values-ca/strings.xml @@ -0,0 +1,16 @@ + + + + %d segment + %d segments + + Cancel·lar descàrrega + Importar perfil + Descàrrega %s + Eliminar %s + Actualitzar %s + Seleccionar segments + Aturar Descàrrega + Espai=%1$s\nLliure=%2$s + Descarregar segments + diff --git a/brouter-routing-app/src/main/res/values-de/strings.xml b/brouter-routing-app/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..85002f5 --- /dev/null +++ b/brouter-routing-app/src/main/res/values-de/strings.xml @@ -0,0 +1,16 @@ + + + + %d Segment + %d Segmente + + Download abbrechen + Profil importieren + %s downloaden + %s löschen + %s aktualisieren + Segmente auswählen + Download stoppen + Größe=%1$s\nFrei=%2$s + Segmente herunterladen + diff --git a/brouter-routing-app/src/main/res/values-el/strings.xml b/brouter-routing-app/src/main/res/values-el/strings.xml new file mode 100644 index 0000000..0ef6b6c --- /dev/null +++ b/brouter-routing-app/src/main/res/values-el/strings.xml @@ -0,0 +1,16 @@ + + + + %d τμήμα + %d τμήματα + + ΑκÏÏωση λήψης + Εισαγωγή Ï€Ïοφίλ + Λήψη %s + ΔιαγÏαφή %s + ΕνημέÏωση %s + Επιλογή τμημάτων + Διακοπή λήψης + Μέγεθος=%1$s\nΕλεÏθεÏο=%2$s + Λήψη τμημάτων + diff --git a/brouter-routing-app/src/main/res/values-es/strings.xml b/brouter-routing-app/src/main/res/values-es/strings.xml new file mode 100644 index 0000000..3929ff0 --- /dev/null +++ b/brouter-routing-app/src/main/res/values-es/strings.xml @@ -0,0 +1,16 @@ + + + + %d segmento + %d segmentos + + Cancelar descarga + Importar perfil + Descargar %s + Eliminar %s + Actualizar %s + Seleccionar segmentos + Detener descarga + Tamaño=%1$s\nGratis=%2$s + Descargar segmentos + diff --git a/brouter-routing-app/src/main/res/values-fr/strings.xml b/brouter-routing-app/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000..b390687 --- /dev/null +++ b/brouter-routing-app/src/main/res/values-fr/strings.xml @@ -0,0 +1,16 @@ + + + + %d segment + %d segments + + Annuler le téléchargement + Importer le profil + Télécharger %s + Supprimer %s + Mettre à jour %s + Sélectionner les segments + Arrêter le téléchargement + Taille=%1$s\nGratuit=%2$s + Télécharger les segments + diff --git a/brouter-routing-app/src/main/res/values-it/strings.xml b/brouter-routing-app/src/main/res/values-it/strings.xml new file mode 100644 index 0000000..10f29fe --- /dev/null +++ b/brouter-routing-app/src/main/res/values-it/strings.xml @@ -0,0 +1,16 @@ + + + + %d segmento + %d segmenti + + Annulla download + Importa profilo + Scarica %s + Elimina %s + Aggiorna %s + Seleziona segmenti + Interrompi download + Taglia=%1$s\nGratis=%2$s + Scarica segmenti + diff --git a/brouter-routing-app/src/main/res/values-nl/strings.xml b/brouter-routing-app/src/main/res/values-nl/strings.xml new file mode 100644 index 0000000..300d668 --- /dev/null +++ b/brouter-routing-app/src/main/res/values-nl/strings.xml @@ -0,0 +1,16 @@ + + + + %d segment + %d segmenten + + Download annuleren + Profiel importeren + Downloaden %s + Verwijderen %s + Bijwerken %s + Segmenten selecteren + Download stoppen + Grootte=%1$s\nGratis=%2$s + Segmenten downloaden + diff --git a/brouter-routing-app/src/main/res/values-pl/strings.xml b/brouter-routing-app/src/main/res/values-pl/strings.xml new file mode 100644 index 0000000..2de0f3c --- /dev/null +++ b/brouter-routing-app/src/main/res/values-pl/strings.xml @@ -0,0 +1,16 @@ + + + + %d segment + %d segmenty/ów + + Anuluj pobieranie + Importuj profil + Pobierz %s + UsuÅ„ %s + Zaktualizuj %s + Wybierz segmenty + Zatrzymaj pobieranie + Rozmiar=%1$s\nDostÄ™pne=%2$s + Pobieranie segmentów + diff --git a/brouter-routing-app/src/main/res/values/donottranslate-strings.xml b/brouter-routing-app/src/main/res/values/donottranslate-strings.xml new file mode 100644 index 0000000..254dd8f --- /dev/null +++ b/brouter-routing-app/src/main/res/values/donottranslate-strings.xml @@ -0,0 +1,7 @@ + + + BRouter + filename.brf + brouter_download + Downloads + diff --git a/brouter-routing-app/src/main/res/values/strings.xml b/brouter-routing-app/src/main/res/values/strings.xml index 168a26a..3ddedd0 100644 --- a/brouter-routing-app/src/main/res/values/strings.xml +++ b/brouter-routing-app/src/main/res/values/strings.xml @@ -1,37 +1,16 @@ - - - - + + %d segment %d segments - BRouter - Cancel Download - Import Profile - filename.brf - Starting download… - Cancelling… + Cancel download + Import profile Download %s Delete %s Update %s Select segments - Stop Download - Size=%s\nFree=%s - brouter_download - Download Segments - Downloads + Stop download + Size=%1$s\nFree=%2$s + Download segments From 0e6cd542cf23aaa47e6be71fd93900663d4e011b Mon Sep 17 00:00:00 2001 From: Joachim Lengacher Date: Thu, 23 Nov 2023 16:24:13 +0100 Subject: [PATCH 077/173] Added description for Docker usage. --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index b1fcbc2..92f7c41 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,21 @@ The API endpoints exposed by this HTTP server are documented in the [`brouter-server/src/main/java/btools/server/request/ServerHandler.java`](brouter-server/src/main/java/btools/server/request/ServerHandler.java) file. +## BRouter with Docker + +To build the Docker image run (in the project's to level directory): + +``` +docker build -t brouter . +``` + +Download the segment files as described in the previous chapter. The folder containing the +segment files and the one containing the profiles can be mounted into the container. Run +BRouter as follows: + +``` +docker run --rm -v ./misc/scripts/segments4:/segments4 -v ./misc/profiles2:/profiles2 brouter +``` ## Documentation From 477c675d466c9bf3d0c774bdc04f1795afb695f6 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 28 Nov 2023 15:04:14 +0100 Subject: [PATCH 078/173] prepare elevation type in rd5 --- .../src/main/java/btools/mapcreator/PosUnifier.java | 11 +++++++++-- .../src/main/java/btools/mapcreator/WayLinker.java | 11 ++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java b/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java index e9a43fe..cea8cb5 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java @@ -28,6 +28,7 @@ public class PosUnifier extends MapCreatorBase { private DiffCoderDataOutputStream nodesOutStream; private DiffCoderDataOutputStream borderNodesOut; private File nodeTilesOut; + private File outNodeFile; private CompactLongSet[] positionSets; private Map srtmmap; @@ -107,8 +108,8 @@ public class PosUnifier extends MapCreatorBase { @Override public void nodeFileStart(File nodefile) throws Exception { resetElevationRaster(); - - nodesOutStream = createOutStream(fileFromTemplate(nodefile, nodeTilesOut, "u5d")); + outNodeFile = fileFromTemplate(nodefile, nodeTilesOut, "u5d"); + nodesOutStream = createOutStream(outNodeFile); positionSets = new CompactLongSet[2500]; } @@ -138,6 +139,12 @@ public class PosUnifier extends MapCreatorBase { @Override public void nodeFileEnd(File nodeFile) throws Exception { nodesOutStream.close(); + if (outNodeFile != null) { + if (lastSrtmRaster != null) { + String newName = outNodeFile.getAbsolutePath() + (lastSrtmRaster.nrows > 6001 ? "_1": "_3"); + outNodeFile.renameTo(new File(newName)); + } + } resetElevationRaster(); } diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java b/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java index 91c70a9..0a01b93 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java @@ -51,6 +51,7 @@ public class WayLinker extends MapCreatorBase implements Runnable { private short lookupMinorVersion; private long creationTimeStamp; + private byte elevationType; private BExpressionContextWay expctxWay; @@ -217,7 +218,14 @@ public class WayLinker extends MapCreatorBase implements Runnable { File trafficFile = fileFromTemplate(wayfile, trafficTilesIn, "trf"); // process corresponding node-file, if any - File nodeFile = fileFromTemplate(wayfile, nodeTilesIn, "u5d"); + elevationType = 3; + File nodeFile = fileFromTemplate(wayfile, nodeTilesIn, "u5d_1"); + if (nodeFile.exists()) { + elevationType = 1; + } else { + nodeFile = fileFromTemplate(wayfile, nodeTilesIn, "u5d_3"); + if (!nodeFile.exists()) nodeFile = fileFromTemplate(wayfile, nodeTilesIn, "u5d"); + } if (nodeFile.exists()) { reset(); @@ -539,6 +547,7 @@ public class WayLinker extends MapCreatorBase implements Runnable { for (int i55 = 0; i55 < 25; i55++) { os.writeInt(fileHeaderCrcs[i55]); } + os.writeByte(elevationType); os.close(); From 16d019c1d09e2e060f860c497a938bf62c86b694 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 28 Nov 2023 15:14:13 +0100 Subject: [PATCH 079/173] read elevation type from rd5 --- .../main/java/btools/mapaccess/NodesCache.java | 16 ++++++++++++++++ .../src/main/java/btools/mapaccess/OsmFile.java | 2 ++ .../main/java/btools/mapaccess/PhysicalFile.java | 8 ++++++++ 3 files changed, 26 insertions(+) diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/NodesCache.java b/brouter-mapaccess/src/main/java/btools/mapaccess/NodesCache.java index f2a6316..e16b8d2 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/NodesCache.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/NodesCache.java @@ -385,4 +385,20 @@ public final class NodesCache { } } } + + public int getElevationType(int ilon, int ilat) { + int lonDegree = ilon / 1000000; + int latDegree = ilat / 1000000; + OsmFile[] fileRow = fileRows[latDegree]; + int ndegrees = fileRow == null ? 0 : fileRow.length; + for (int i = 0; i < ndegrees; i++) { + if (fileRow[i].lonDegree == lonDegree) { + OsmFile osmf = fileRow[i]; + if (osmf != null) return osmf.elevationType; + break; + } + } + return 3; + } + } diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java index 0f773cf..67eff29 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java @@ -33,6 +33,7 @@ final class OsmFile { private int cellsize; private int ncaches; private int indexsize; + protected byte elevationType = 3; public OsmFile(PhysicalFile rafile, int lonDegree, int latDegree, DataBuffers dataBuffers) throws IOException { this.lonDegree = lonDegree; @@ -43,6 +44,7 @@ final class OsmFile { if (rafile != null) { divisor = rafile.divisor; + elevationType = rafile.elevationType; cellsize = 1000000 / divisor; ncaches = divisor * divisor; diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java b/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java index 2c2a186..e61f469 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java @@ -24,6 +24,7 @@ final public class PhysicalFile { String fileName; public int divisor = 80; + public byte elevationType = 3; public static void main(String[] args) { MicroCache.debug = true; @@ -113,6 +114,10 @@ final public class PhysicalFile { if (len == pos) return; // old format o.k. + if ((len-pos) > extraLen) { + extraLen++; + } + if (len < pos + extraLen) { // > is o.k. for future extensions! throw new IOException("file of size " + len + " too short, should be " + (pos + extraLen)); } @@ -134,5 +139,8 @@ final public class PhysicalFile { for (int i = 0; i < 25; i++) { fileHeaderCrcs[i] = dis.readInt(); } + try { + elevationType = dis.readByte(); + } catch (Exception e) {} } } From 068a5ff714e6924ac0acc3a9c116e42c0b9dc009 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 28 Nov 2023 15:17:51 +0100 Subject: [PATCH 080/173] use elevation type for filter value --- .../java/btools/router/RoutingEngine.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index baff3dc..dd7a2af 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -20,6 +20,7 @@ import btools.mapaccess.OsmLink; import btools.mapaccess.OsmLinkHolder; import btools.mapaccess.OsmNode; import btools.mapaccess.OsmNodePairSet; +import btools.mapaccess.OsmPos; import btools.util.CompactLongMap; import btools.util.SortedHeap; import btools.util.StackSampler; @@ -855,11 +856,12 @@ public class RoutingEngine extends Thread { if (ele_last != Short.MIN_VALUE) { ehb = ehb + (ele_last - ele) * eleFactor; } + double filter = elevationFilter(n); if (ehb > 0) { ascend += ehb; ehb = 0; - } else if (ehb < -10) { - ehb = -10; + } else if (ehb < filter) { + ehb = filter; } } @@ -896,6 +898,21 @@ public class RoutingEngine extends Thread { logInfo("filtered ascend = " + t.ascend); } + /** + * find the elevation type for position + * to determine the filter value + * + * @param n the point + * @return the filter value for 1sec / 3sec elevation source + */ + double elevationFilter(OsmPos n) { + if (nodesCache != null) { + int r = nodesCache.getElevationType(n.getILon(), n.getILat()); + if (r == 1) return -5.; + } + return -10.; + } + // geometric position matching finding the nearest routable way-section private void matchWaypointsToNodes(List unmatchedWaypoints) { resetCache(false); From a3d561e1446545aa93341158f0a913e75529f783 Mon Sep 17 00:00:00 2001 From: Emux Date: Sat, 9 Dec 2023 14:00:50 +0200 Subject: [PATCH 081/173] Android targetSdk 34 --- brouter-routing-app/src/main/AndroidManifest.xml | 5 +++++ .../src/main/java/btools/routingapp/DownloadWorker.java | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/brouter-routing-app/src/main/AndroidManifest.xml b/brouter-routing-app/src/main/AndroidManifest.xml index e210ef7..831deea 100644 --- a/brouter-routing-app/src/main/AndroidManifest.xml +++ b/brouter-routing-app/src/main/AndroidManifest.xml @@ -10,6 +10,7 @@ + + = Build.VERSION_CODES.Q) + setForegroundAsync(new ForegroundInfo(NOTIFICATION_ID, notificationBuilder.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)); + else + setForegroundAsync(new ForegroundInfo(NOTIFICATION_ID, notificationBuilder.build())); try { if (DEBUG) Log.d(LOG_TAG, "Download lookup & profiles"); if (!downloadLookup()) { From 03bbbcfc0c063c121195121d220be1cf5f98d8b8 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 9 Dec 2023 15:28:17 +0100 Subject: [PATCH 082/173] enabled compressed json output for app --- .../src/main/java/btools/routingapp/BRouterService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java index 8d6ea4e..90b875c 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java @@ -100,7 +100,7 @@ public class BRouterService extends Service { boolean canCompress = "true".equals(params.getString("acceptCompressedResult")); try { String gpxMessage = worker.getTrackFromParams(params); - if (canCompress && gpxMessage.startsWith("<")) { + if (canCompress) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write("z64".getBytes(Charset.forName("UTF-8"))); // marker prefix From 2669ae95582043adddb5e8b79eaf75768f295e49 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sun, 10 Dec 2023 12:16:03 +0100 Subject: [PATCH 083/173] added new resources from hardcoded string --- .../btools/routingapp/BRouterActivity.java | 82 +++++++++---------- .../src/main/res/values/strings.xml | 41 ++++++++++ 2 files changed, 79 insertions(+), 44 deletions(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java index 1dca201..f633b47 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java @@ -58,7 +58,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat private static final int DIALOG_PICKWAYPOINT_ID = 10; private static final int DIALOG_SELECTBASEDIR_ID = 11; private static final int DIALOG_MAINACTION_ID = 12; - private static final int DIALOG_OLDDATAHINT_ID = 13; + //private static final int DIALOG_OLDDATAHINT_ID = 13; private static final int DIALOG_SHOW_REPEAT_TIMEOUT_HELP_ID = 16; private final Set dialogIds = new HashSet<>(); private BRouterView mBRouterView; @@ -133,7 +133,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat switch (id) { case DIALOG_SELECTPROFILE_ID: - builder.setTitle("Select a routing profile"); + builder.setTitle(R.string.action_select_profile); builder.setItems(availableProfiles, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { selectedProfile = availableProfiles[item]; @@ -142,9 +142,9 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat }); return builder.create(); case DIALOG_MAINACTION_ID: - builder.setTitle("Select Main Action"); + builder.setTitle(R.string.main_action); builder.setItems( - new String[]{"Download Manager", "BRouter App"}, + new String[]{getString(R.string.main_action_1), getString(R.string.main_action_2)}, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { if (item == 0) @@ -153,7 +153,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat showADialog(DIALOG_SELECTPROFILE_ID); } }) - .setNegativeButton("Close", new DialogInterface.OnClickListener() { + .setNegativeButton(getString(R.string.close), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { finish(); } @@ -161,19 +161,15 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat return builder.create(); case DIALOG_SHOW_DM_INFO_ID: builder - .setTitle("BRouter Download Manager") - .setMessage( - "*** Attention: ***\n\n" + "The Download Manager is used to download routing-data " - + "files which can be up to 170MB each. Do not start the Download Manager " - + "on a cellular data connection without a data plan! " - + "Download speed is restricted to 16 MBit/s.") - .setPositiveButton("I know", new DialogInterface.OnClickListener() { + .setTitle(R.string.title_download) + .setMessage(R.string.summary_download) + .setPositiveButton(R.string.i_know, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { Intent intent = new Intent(BRouterActivity.this, BInstallerActivity.class); startActivity(intent); showNewDialog(DIALOG_MAINACTION_ID); } - }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { finish(); } @@ -181,18 +177,15 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat return builder.create(); case DIALOG_SHOW_REPEAT_TIMEOUT_HELP_ID: builder - .setTitle("Successfully prepared a timeout-free calculation") - .setMessage( - "You successfully repeated a calculation that previously run into a timeout " - + "when started from your map-tool. If you repeat the same request from your " - + "maptool, with the exact same destination point and a close-by starting point, " - + "this request is guaranteed not to time out.") - .setNegativeButton("Exit", new DialogInterface.OnClickListener() { + .setTitle(R.string.title_timeoutfree) + .setMessage(R.string.summary_timeoutfree) + .setNegativeButton(R.string.exit, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { finish(); } }); return builder.create(); + /* case DIALOG_OLDDATAHINT_ID: builder .setTitle("Local setup needs reset") @@ -202,12 +195,13 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat + "Before downloading new datafiles made for the new table, " + "you have to reset your local setup by 'moving away' (or deleting) " + "your /brouter directory and start a new setup by calling the " + "BRouter App again.") - .setPositiveButton("OK", new DialogInterface.OnClickListener() { + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { finish(); } }); return builder.create(); + */ case DIALOG_ROUTINGMODES_ID: builder.setTitle(message); builder.setMultiChoiceItems(routingModes, routingModesChecked, @@ -217,7 +211,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat routingModesChecked[which] = isChecked; } }); - builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mBRouterView.configureService(routingModes, routingModesChecked); } @@ -225,9 +219,9 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat return builder.create(); case DIALOG_EXCEPTION_ID: builder - .setTitle("An Error occured") + .setTitle(R.string.error) .setMessage(errorMessage) - .setPositiveButton("OK", + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { mBRouterView.continueProcessing(); @@ -235,12 +229,12 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat }); return builder.create(); case DIALOG_TEXTENTRY_ID: - builder.setTitle("Enter SDCARD base dir:"); + builder.setTitle(R.string.title_sdcard); builder.setMessage(message); final EditText input = new EditText(this); // input.setText(defaultbasedir); builder.setView(input); - builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { String basedir = input.getText().toString(); mBRouterView.startSetup(new File(basedir), true, false); @@ -248,7 +242,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat }); return builder.create(); case DIALOG_SELECTBASEDIR_ID: - builder.setTitle("Choose brouter data base dir:"); + builder.setTitle(getString(R.string.action_choose_folder)); // builder.setMessage( message ); builder.setSingleChoiceItems(basedirOptions, 0, new DialogInterface.OnClickListener() { @Override @@ -256,7 +250,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat selectedBasedir = item; } }); - builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { if (selectedBasedir < availableBasedirs.size()) { mBRouterView.startSetup(availableBasedirs.get(selectedBasedir), true, false); @@ -267,7 +261,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat }); return builder.create(); case DIALOG_VIASELECT_ID: - builder.setTitle("Check VIA Selection:"); + builder.setTitle(R.string.action_via_select); builder.setMultiChoiceItems(availableVias, getCheckedBooleanArray(availableVias.length), new DialogInterface.OnMultiChoiceClickListener() { @Override @@ -279,7 +273,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat } } }); - builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mBRouterView.updateViaList(selectedVias); mBRouterView.startProcessing(selectedProfile); @@ -287,7 +281,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat }); return builder.create(); case DIALOG_NOGOSELECT_ID: - builder.setTitle("Check NoGo Selection:"); + builder.setTitle(R.string.action_nogo_select); String[] nogoNames = new String[nogoList.size()]; for (int i = 0; i < nogoList.size(); i++) nogoNames[i] = nogoList.get(i).name; @@ -299,7 +293,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat nogoEnabled[which] = isChecked; } }); - builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mBRouterView.updateNogoList(nogoEnabled); mBRouterView.startProcessing(selectedProfile); @@ -325,21 +319,21 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat List slist = new ArrayList<>(); // Neutral button if (wpCount == 0) { - slist.add("Server-Mode"); + slist.add(getString(R.string.action_servermode)); } else if (wpCount == -3) { - slist.add("Info"); + slist.add(getString(R.string.action_info)); } else if (wpCount >= 2) { - slist.add("Calc Route"); + slist.add(getString(R.string.action_calc_route)); } if (wpCount == 0) { - slist.add("Profile Settings"); + slist.add(getString(R.string.action_profile_settings)); } // Positive button if (wpCount == -3 || wpCount == -1) { - slist.add("Share GPX"); + slist.add(getString(R.string.action_share)); } else if (wpCount >= 0) { - String selectLabel = wpCount == 0 ? "Select from" : "Select to/via"; + String selectLabel = wpCount == 0 ? getString(R.string.action_select_from) : getString(R.string.action_select_to); slist.add(selectLabel); } @@ -407,16 +401,16 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat */ // Negative button - builder.setNegativeButton("Exit", (dialog, which) -> { + builder.setNegativeButton(R.string.exit, (dialog, which) -> { finish(); }); return builder.create(); case DIALOG_MODECONFIGOVERVIEW_ID: builder - .setTitle("Success") + .setTitle(R.string.success) .setMessage(message) - .setPositiveButton("Exit", + .setPositiveButton(R.string.exit, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { finish(); @@ -424,7 +418,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat }); return builder.create(); case DIALOG_PICKWAYPOINT_ID: - builder.setTitle(wpCount > 0 ? "Select to/via" : "Select from"); + builder.setTitle(wpCount == 0 ? getString(R.string.action_select_from) : getString(R.string.action_select_to)); builder.setItems(availableWaypoints, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { mBRouterView.updateWaypointList(availableWaypoints[item]); @@ -472,11 +466,11 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat //startActivityForResult(i, 100); someActivityResultLauncher.launch(i); } else { - Toast.makeText(this, "no profile data", Toast.LENGTH_LONG).show(); + Toast.makeText(this, R.string.msg_no_profile, Toast.LENGTH_LONG).show(); finish(); } } else { - Toast.makeText(this, selectedProfile + ", no used profile", Toast.LENGTH_LONG).show(); + Toast.makeText(this, selectedProfile + getString(R.string.msg_no_used_profile), Toast.LENGTH_LONG).show(); finish(); } } diff --git a/brouter-routing-app/src/main/res/values/strings.xml b/brouter-routing-app/src/main/res/values/strings.xml index 3ddedd0..91ed26c 100644 --- a/brouter-routing-app/src/main/res/values/strings.xml +++ b/brouter-routing-app/src/main/res/values/strings.xml @@ -13,4 +13,45 @@ Stop download Size=%1$s\nFree=%2$s Download segments + + Choose brouter data base dir: + Select a routing profile + Select Main Action + Download Manager + BRouter App + + Cancel + I know + Close + Exit + OK + Success + An Error occurred + + BRouter Download Manager + *** Attention:*** + \n\nThe Download Manager is used to download routing-data + files which can be up to 170MB each. Do not start the Download Manager + on a cellular data connection without a data plan! + Download speed is restricted to 16 MBit/s. + Successfully prepared a timeout-free calculation + You successfully repeated a calculation that previously run into a timeout + when started from your map-tool. If you repeat the same request from your + maptool, with the exact same destination point and a close-by starting point, + this request is guaranteed not to time out. + Enter SDCARD base dir: + + Check VIA Selection: + Check NoGo Selection: + Server-Mode + Info + Calc Route + Profile Settings + Share GPX + Select from + Select to/via + + no profile data + , no used profile + From b9cd75b7a04db7c77a9335cbad48b44bd8713109 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sun, 10 Dec 2023 12:26:20 +0100 Subject: [PATCH 084/173] more new resources from hardcoded string --- .../src/main/java/btools/routingapp/BInstallerActivity.java | 5 +++-- brouter-routing-app/src/main/res/values/strings.xml | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java index f9ffe61..693882a 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java @@ -214,7 +214,8 @@ public class BInstallerActivity extends AppCompatActivity { .build(); } catch (IllegalStateException e) { - Toast.makeText(this, "Too much data for download. Please reduce.", Toast.LENGTH_LONG).show(); + Object data; + Toast.makeText(this, R.string.msg_too_much_data, Toast.LENGTH_LONG).show(); e.printStackTrace(); return; @@ -264,7 +265,7 @@ public class BInstallerActivity extends AppCompatActivity { } if (workInfo.getState() == WorkInfo.State.ENQUEUED) { - Toast.makeText(this, "Download scheduled. Check internet connection if it doesn't start.", Toast.LENGTH_LONG).show(); + Toast.makeText(this, R.string.msg_download_start, Toast.LENGTH_LONG).show(); mProgressIndicator.hide(); mProgressIndicator.setIndeterminate(true); mProgressIndicator.show(); diff --git a/brouter-routing-app/src/main/res/values/strings.xml b/brouter-routing-app/src/main/res/values/strings.xml index 91ed26c..903d95c 100644 --- a/brouter-routing-app/src/main/res/values/strings.xml +++ b/brouter-routing-app/src/main/res/values/strings.xml @@ -53,5 +53,7 @@ no profile data , no used profile + Too much data for download. Please reduce. + Download scheduled. Check internet connection if it doesn\'t start. From 3acbd96575d7ef3200f6674142977da60bcd5a32 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sun, 10 Dec 2023 13:19:54 +0100 Subject: [PATCH 085/173] more new resources from hardcoded messages --- .../java/btools/routingapp/BRouterView.java | 25 +++++++++++-------- .../src/main/res/values/strings.xml | 13 ++++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) 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 ff50885..5c7be2b 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -380,14 +380,14 @@ public class BRouterView extends View { try { cor.readAllPoints(); } catch (Exception e) { - msg = "Error reading waypoints: " + e; + msg = getContext().getString(R.string.msg_read_wpt_error)+ ": " + e; } int size = cor.allpoints.size(); if (size < 1) - msg = "coordinate source does not contain any waypoints!"; + msg = getContext().getString(R.string.msg_no_wpt); if (size > 1000) - msg = "coordinate source contains too much waypoints: " + size + "(please use from/to/via names)"; + msg = getContext().getString(R.string.msg_to_much_wpts, size); } if (msg != null) { @@ -471,13 +471,13 @@ public class BRouterView extends View { if (needsWaypointSelection) { StringBuilder msg; if (wpList.size() == 0) { - msg = new StringBuilder("Expecting waypoint selection\n" + "(coordinate-source: " + cor.basedir + cor.rootdir + ")"); + msg = new StringBuilder(getContext().getString(R.string.msg_no_wpt_selection) + "(coordinate-source: " + cor.basedir + cor.rootdir + ")"); } else { - msg = new StringBuilder("current waypoint selection:\n"); + msg = new StringBuilder(getContext().getString(R.string.msg_wpt_selection)); for (int i = 0; i < wpList.size(); i++) msg.append(i > 0 ? "->" : "").append(wpList.get(i).name); } - ((BRouterActivity) getContext()).showResultMessage("Select Action", msg.toString(), wpList.size()); + ((BRouterActivity) getContext()).showResultMessage(getContext().getString(R.string.title_action), msg.toString(), wpList.size()); return; } @@ -718,8 +718,13 @@ public class BRouterView extends View { ((BRouterActivity) getContext()).showErrorMessage(cr.getErrorMessage()); } else { String memstat = memoryClass + "mb pathPeak " + ((cr.getPathPeak() + 500) / 1000) + "k"; - String result = "version = BRouter-" + getContext().getString(R.string.app_version) + "\n" + "mem = " + memstat + "\ndistance = " + cr.getDistance() / 1000. + " km\n" + "filtered ascend = " + cr.getAscend() - + " m\n" + "plain ascend = " + cr.getPlainAscend() + " m\n" + "estimated time = " + cr.getTime(); + String result = getContext().getString(R.string.msg_status_result, + getContext().getString(R.string.app_version), + memstat, + Double.toString(cr.getDistance() / 1000.), + Integer.toString(cr.getAscend()), + Integer.toString(cr.getPlainAscend()), + cr.getTime()); rawTrack = cr.getFoundRawTrack(); @@ -728,9 +733,9 @@ public class BRouterView extends View { writeRawTrackToPath(rawTrackPath); } - String title = "Success"; + String title = getContext().getString(R.string.success); if (cr.getAlternativeIndex() > 0) - title += " / " + cr.getAlternativeIndex() + ". Alternative"; + title += " / " + cr.getAlternativeIndex() + ". " + getContext().getString(R.string.msg_alternative); ((BRouterActivity) getContext()).showResultMessage(title, result, rawTrackPath == null ? -1 : -3); trackOutfile = cr.getOutfile(); diff --git a/brouter-routing-app/src/main/res/values/strings.xml b/brouter-routing-app/src/main/res/values/strings.xml index 903d95c..2c1b384 100644 --- a/brouter-routing-app/src/main/res/values/strings.xml +++ b/brouter-routing-app/src/main/res/values/strings.xml @@ -40,6 +40,7 @@ maptool, with the exact same destination point and a close-by starting point, this request is guaranteed not to time out. Enter SDCARD base dir: + Select Action Check VIA Selection: Check NoGo Selection: @@ -55,5 +56,17 @@ , no used profile Too much data for download. Please reduce. Download scheduled. Check internet connection if it doesn\'t start. + current waypoint selection:\n + Expecting waypoint selection\n + Alternative + version = BRouter-%1$s \n + mem = %2$s \n + distance = %3$s km\n + filtered ascend = %4$s m\n + plain ascend = %5$s m\n + estimated time = %6$s + Error reading waypoints + coordinate source does not contain any waypoints! + "coordinate source contains too much waypoints: %1$d (please use from/to/via names) From 28ca2b7217ea886b93ad708dac8de18291ba7a13 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sun, 10 Dec 2023 13:24:31 +0100 Subject: [PATCH 086/173] move new resources to all xml --- .../src/main/res/values-ar/strings.xml | 56 +++++++++++++++++++ .../src/main/res/values-ca/strings.xml | 56 +++++++++++++++++++ .../src/main/res/values-de/strings.xml | 56 +++++++++++++++++++ .../src/main/res/values-el/strings.xml | 56 +++++++++++++++++++ .../src/main/res/values-es/strings.xml | 56 +++++++++++++++++++ .../src/main/res/values-fr/strings.xml | 56 +++++++++++++++++++ .../src/main/res/values-it/strings.xml | 56 +++++++++++++++++++ .../src/main/res/values-nl/strings.xml | 56 +++++++++++++++++++ .../src/main/res/values-pl/strings.xml | 56 +++++++++++++++++++ 9 files changed, 504 insertions(+) diff --git a/brouter-routing-app/src/main/res/values-ar/strings.xml b/brouter-routing-app/src/main/res/values-ar/strings.xml index 43e2663..c08e7ec 100644 --- a/brouter-routing-app/src/main/res/values-ar/strings.xml +++ b/brouter-routing-app/src/main/res/values-ar/strings.xml @@ -13,4 +13,60 @@ إيقا٠التنزيل الحجم=%1$s\nالحجم Ø§Ù„Ù…ØªÙˆÙØ±=%2$s تحميل القطع + + Choose brouter data base dir: + Select a routing profile + Select Main Action + Download Manager + BRouter App + + Cancel + I know + Close + Exit + OK + Success + An Error occurred + + BRouter Download Manager + *** Attention:*** + \n\nThe Download Manager is used to download routing-data + files which can be up to 170MB each. Do not start the Download Manager + on a cellular data connection without a data plan! + Download speed is restricted to 16 MBit/s. + Successfully prepared a timeout-free calculation + You successfully repeated a calculation that previously run into a timeout + when started from your map-tool. If you repeat the same request from your + maptool, with the exact same destination point and a close-by starting point, + this request is guaranteed not to time out. + Enter SDCARD base dir: + Select Action + + Check VIA Selection: + Check NoGo Selection: + Server-Mode + Info + Calc Route + Profile Settings + Share GPX + Select from + Select to/via + + no profile data + , no used profile + Too much data for download. Please reduce. + Download scheduled. Check internet connection if it doesn\'t start. + current waypoint selection:\n + Expecting waypoint selection\n + Alternative + version = BRouter-%1$s \n + mem = %2$s \n + distance = %3$s km\n + filtered ascend = %4$s m\n + plain ascend = %5$s m\n + estimated time = %6$s + Error reading waypoints + coordinate source does not contain any waypoints! + "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + diff --git a/brouter-routing-app/src/main/res/values-ca/strings.xml b/brouter-routing-app/src/main/res/values-ca/strings.xml index 13aeaea..54b2760 100644 --- a/brouter-routing-app/src/main/res/values-ca/strings.xml +++ b/brouter-routing-app/src/main/res/values-ca/strings.xml @@ -13,4 +13,60 @@ Aturar Descàrrega Espai=%1$s\nLliure=%2$s Descarregar segments + + Choose brouter data base dir: + Select a routing profile + Select Main Action + Download Manager + BRouter App + + Cancel + I know + Close + Exit + OK + Success + An Error occurred + + BRouter Download Manager + *** Attention:*** + \n\nThe Download Manager is used to download routing-data + files which can be up to 170MB each. Do not start the Download Manager + on a cellular data connection without a data plan! + Download speed is restricted to 16 MBit/s. + Successfully prepared a timeout-free calculation + You successfully repeated a calculation that previously run into a timeout + when started from your map-tool. If you repeat the same request from your + maptool, with the exact same destination point and a close-by starting point, + this request is guaranteed not to time out. + Enter SDCARD base dir: + Select Action + + Check VIA Selection: + Check NoGo Selection: + Server-Mode + Info + Calc Route + Profile Settings + Share GPX + Select from + Select to/via + + no profile data + , no used profile + Too much data for download. Please reduce. + Download scheduled. Check internet connection if it doesn\'t start. + current waypoint selection:\n + Expecting waypoint selection\n + Alternative + version = BRouter-%1$s \n + mem = %2$s \n + distance = %3$s km\n + filtered ascend = %4$s m\n + plain ascend = %5$s m\n + estimated time = %6$s + Error reading waypoints + coordinate source does not contain any waypoints! + "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + diff --git a/brouter-routing-app/src/main/res/values-de/strings.xml b/brouter-routing-app/src/main/res/values-de/strings.xml index 85002f5..39bb025 100644 --- a/brouter-routing-app/src/main/res/values-de/strings.xml +++ b/brouter-routing-app/src/main/res/values-de/strings.xml @@ -13,4 +13,60 @@ Download stoppen Größe=%1$s\nFrei=%2$s Segmente herunterladen + + Choose brouter data base dir: + Select a routing profile + Select Main Action + Download Manager + BRouter App + + Cancel + I know + Close + Exit + OK + Success + An Error occurred + + BRouter Download Manager + *** Attention:*** + \n\nThe Download Manager is used to download routing-data + files which can be up to 170MB each. Do not start the Download Manager + on a cellular data connection without a data plan! + Download speed is restricted to 16 MBit/s. + Successfully prepared a timeout-free calculation + You successfully repeated a calculation that previously run into a timeout + when started from your map-tool. If you repeat the same request from your + maptool, with the exact same destination point and a close-by starting point, + this request is guaranteed not to time out. + Enter SDCARD base dir: + Select Action + + Check VIA Selection: + Check NoGo Selection: + Server-Mode + Info + Calc Route + Profile Settings + Share GPX + Select from + Select to/via + + no profile data + , no used profile + Too much data for download. Please reduce. + Download scheduled. Check internet connection if it doesn\'t start. + current waypoint selection:\n + Expecting waypoint selection\n + Alternative + version = BRouter-%1$s \n + mem = %2$s \n + distance = %3$s km\n + filtered ascend = %4$s m\n + plain ascend = %5$s m\n + estimated time = %6$s + Error reading waypoints + coordinate source does not contain any waypoints! + "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + diff --git a/brouter-routing-app/src/main/res/values-el/strings.xml b/brouter-routing-app/src/main/res/values-el/strings.xml index 0ef6b6c..a8d74fb 100644 --- a/brouter-routing-app/src/main/res/values-el/strings.xml +++ b/brouter-routing-app/src/main/res/values-el/strings.xml @@ -13,4 +13,60 @@ Διακοπή λήψης Μέγεθος=%1$s\nΕλεÏθεÏο=%2$s Λήψη τμημάτων + + Choose brouter data base dir: + Select a routing profile + Select Main Action + Download Manager + BRouter App + + Cancel + I know + Close + Exit + OK + Success + An Error occurred + + BRouter Download Manager + *** Attention:*** + \n\nThe Download Manager is used to download routing-data + files which can be up to 170MB each. Do not start the Download Manager + on a cellular data connection without a data plan! + Download speed is restricted to 16 MBit/s. + Successfully prepared a timeout-free calculation + You successfully repeated a calculation that previously run into a timeout + when started from your map-tool. If you repeat the same request from your + maptool, with the exact same destination point and a close-by starting point, + this request is guaranteed not to time out. + Enter SDCARD base dir: + Select Action + + Check VIA Selection: + Check NoGo Selection: + Server-Mode + Info + Calc Route + Profile Settings + Share GPX + Select from + Select to/via + + no profile data + , no used profile + Too much data for download. Please reduce. + Download scheduled. Check internet connection if it doesn\'t start. + current waypoint selection:\n + Expecting waypoint selection\n + Alternative + version = BRouter-%1$s \n + mem = %2$s \n + distance = %3$s km\n + filtered ascend = %4$s m\n + plain ascend = %5$s m\n + estimated time = %6$s + Error reading waypoints + coordinate source does not contain any waypoints! + "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + diff --git a/brouter-routing-app/src/main/res/values-es/strings.xml b/brouter-routing-app/src/main/res/values-es/strings.xml index 3929ff0..ff1327f 100644 --- a/brouter-routing-app/src/main/res/values-es/strings.xml +++ b/brouter-routing-app/src/main/res/values-es/strings.xml @@ -13,4 +13,60 @@ Detener descarga Tamaño=%1$s\nGratis=%2$s Descargar segmentos + + Choose brouter data base dir: + Select a routing profile + Select Main Action + Download Manager + BRouter App + + Cancel + I know + Close + Exit + OK + Success + An Error occurred + + BRouter Download Manager + *** Attention:*** + \n\nThe Download Manager is used to download routing-data + files which can be up to 170MB each. Do not start the Download Manager + on a cellular data connection without a data plan! + Download speed is restricted to 16 MBit/s. + Successfully prepared a timeout-free calculation + You successfully repeated a calculation that previously run into a timeout + when started from your map-tool. If you repeat the same request from your + maptool, with the exact same destination point and a close-by starting point, + this request is guaranteed not to time out. + Enter SDCARD base dir: + Select Action + + Check VIA Selection: + Check NoGo Selection: + Server-Mode + Info + Calc Route + Profile Settings + Share GPX + Select from + Select to/via + + no profile data + , no used profile + Too much data for download. Please reduce. + Download scheduled. Check internet connection if it doesn\'t start. + current waypoint selection:\n + Expecting waypoint selection\n + Alternative + version = BRouter-%1$s \n + mem = %2$s \n + distance = %3$s km\n + filtered ascend = %4$s m\n + plain ascend = %5$s m\n + estimated time = %6$s + Error reading waypoints + coordinate source does not contain any waypoints! + "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + diff --git a/brouter-routing-app/src/main/res/values-fr/strings.xml b/brouter-routing-app/src/main/res/values-fr/strings.xml index b390687..293b40d 100644 --- a/brouter-routing-app/src/main/res/values-fr/strings.xml +++ b/brouter-routing-app/src/main/res/values-fr/strings.xml @@ -13,4 +13,60 @@ Arrêter le téléchargement Taille=%1$s\nGratuit=%2$s Télécharger les segments + + Choose brouter data base dir: + Select a routing profile + Select Main Action + Download Manager + BRouter App + + Cancel + I know + Close + Exit + OK + Success + An Error occurred + + BRouter Download Manager + *** Attention:*** + \n\nThe Download Manager is used to download routing-data + files which can be up to 170MB each. Do not start the Download Manager + on a cellular data connection without a data plan! + Download speed is restricted to 16 MBit/s. + Successfully prepared a timeout-free calculation + You successfully repeated a calculation that previously run into a timeout + when started from your map-tool. If you repeat the same request from your + maptool, with the exact same destination point and a close-by starting point, + this request is guaranteed not to time out. + Enter SDCARD base dir: + Select Action + + Check VIA Selection: + Check NoGo Selection: + Server-Mode + Info + Calc Route + Profile Settings + Share GPX + Select from + Select to/via + + no profile data + , no used profile + Too much data for download. Please reduce. + Download scheduled. Check internet connection if it doesn\'t start. + current waypoint selection:\n + Expecting waypoint selection\n + Alternative + version = BRouter-%1$s \n + mem = %2$s \n + distance = %3$s km\n + filtered ascend = %4$s m\n + plain ascend = %5$s m\n + estimated time = %6$s + Error reading waypoints + coordinate source does not contain any waypoints! + "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + diff --git a/brouter-routing-app/src/main/res/values-it/strings.xml b/brouter-routing-app/src/main/res/values-it/strings.xml index 10f29fe..af2be5c 100644 --- a/brouter-routing-app/src/main/res/values-it/strings.xml +++ b/brouter-routing-app/src/main/res/values-it/strings.xml @@ -13,4 +13,60 @@ Interrompi download Taglia=%1$s\nGratis=%2$s Scarica segmenti + + Choose brouter data base dir: + Select a routing profile + Select Main Action + Download Manager + BRouter App + + Cancel + I know + Close + Exit + OK + Success + An Error occurred + + BRouter Download Manager + *** Attention:*** + \n\nThe Download Manager is used to download routing-data + files which can be up to 170MB each. Do not start the Download Manager + on a cellular data connection without a data plan! + Download speed is restricted to 16 MBit/s. + Successfully prepared a timeout-free calculation + You successfully repeated a calculation that previously run into a timeout + when started from your map-tool. If you repeat the same request from your + maptool, with the exact same destination point and a close-by starting point, + this request is guaranteed not to time out. + Enter SDCARD base dir: + Select Action + + Check VIA Selection: + Check NoGo Selection: + Server-Mode + Info + Calc Route + Profile Settings + Share GPX + Select from + Select to/via + + no profile data + , no used profile + Too much data for download. Please reduce. + Download scheduled. Check internet connection if it doesn\'t start. + current waypoint selection:\n + Expecting waypoint selection\n + Alternative + version = BRouter-%1$s \n + mem = %2$s \n + distance = %3$s km\n + filtered ascend = %4$s m\n + plain ascend = %5$s m\n + estimated time = %6$s + Error reading waypoints + coordinate source does not contain any waypoints! + "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + diff --git a/brouter-routing-app/src/main/res/values-nl/strings.xml b/brouter-routing-app/src/main/res/values-nl/strings.xml index 300d668..cbc9a87 100644 --- a/brouter-routing-app/src/main/res/values-nl/strings.xml +++ b/brouter-routing-app/src/main/res/values-nl/strings.xml @@ -13,4 +13,60 @@ Download stoppen Grootte=%1$s\nGratis=%2$s Segmenten downloaden + + Choose brouter data base dir: + Select a routing profile + Select Main Action + Download Manager + BRouter App + + Cancel + I know + Close + Exit + OK + Success + An Error occurred + + BRouter Download Manager + *** Attention:*** + \n\nThe Download Manager is used to download routing-data + files which can be up to 170MB each. Do not start the Download Manager + on a cellular data connection without a data plan! + Download speed is restricted to 16 MBit/s. + Successfully prepared a timeout-free calculation + You successfully repeated a calculation that previously run into a timeout + when started from your map-tool. If you repeat the same request from your + maptool, with the exact same destination point and a close-by starting point, + this request is guaranteed not to time out. + Enter SDCARD base dir: + Select Action + + Check VIA Selection: + Check NoGo Selection: + Server-Mode + Info + Calc Route + Profile Settings + Share GPX + Select from + Select to/via + + no profile data + , no used profile + Too much data for download. Please reduce. + Download scheduled. Check internet connection if it doesn\'t start. + current waypoint selection:\n + Expecting waypoint selection\n + Alternative + version = BRouter-%1$s \n + mem = %2$s \n + distance = %3$s km\n + filtered ascend = %4$s m\n + plain ascend = %5$s m\n + estimated time = %6$s + Error reading waypoints + coordinate source does not contain any waypoints! + "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + diff --git a/brouter-routing-app/src/main/res/values-pl/strings.xml b/brouter-routing-app/src/main/res/values-pl/strings.xml index 2de0f3c..f797ee1 100644 --- a/brouter-routing-app/src/main/res/values-pl/strings.xml +++ b/brouter-routing-app/src/main/res/values-pl/strings.xml @@ -13,4 +13,60 @@ Zatrzymaj pobieranie Rozmiar=%1$s\nDostÄ™pne=%2$s Pobieranie segmentów + + Choose brouter data base dir: + Select a routing profile + Select Main Action + Download Manager + BRouter App + + Cancel + I know + Close + Exit + OK + Success + An Error occurred + + BRouter Download Manager + *** Attention:*** + \n\nThe Download Manager is used to download routing-data + files which can be up to 170MB each. Do not start the Download Manager + on a cellular data connection without a data plan! + Download speed is restricted to 16 MBit/s. + Successfully prepared a timeout-free calculation + You successfully repeated a calculation that previously run into a timeout + when started from your map-tool. If you repeat the same request from your + maptool, with the exact same destination point and a close-by starting point, + this request is guaranteed not to time out. + Enter SDCARD base dir: + Select Action + + Check VIA Selection: + Check NoGo Selection: + Server-Mode + Info + Calc Route + Profile Settings + Share GPX + Select from + Select to/via + + no profile data + , no used profile + Too much data for download. Please reduce. + Download scheduled. Check internet connection if it doesn\'t start. + current waypoint selection:\n + Expecting waypoint selection\n + Alternative + version = BRouter-%1$s \n + mem = %2$s \n + distance = %3$s km\n + filtered ascend = %4$s m\n + plain ascend = %5$s m\n + estimated time = %6$s + Error reading waypoints + coordinate source does not contain any waypoints! + "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + From 30d4fbb0b00572dfdb5243f911373f7b8d42f845 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sun, 10 Dec 2023 13:58:46 +0100 Subject: [PATCH 087/173] xml error correction --- brouter-routing-app/src/main/res/values-ar/strings.xml | 2 +- brouter-routing-app/src/main/res/values-ca/strings.xml | 2 +- brouter-routing-app/src/main/res/values-de/strings.xml | 2 +- brouter-routing-app/src/main/res/values-el/strings.xml | 2 +- brouter-routing-app/src/main/res/values-es/strings.xml | 2 +- brouter-routing-app/src/main/res/values-fr/strings.xml | 2 +- brouter-routing-app/src/main/res/values-it/strings.xml | 2 +- brouter-routing-app/src/main/res/values-nl/strings.xml | 2 +- brouter-routing-app/src/main/res/values-pl/strings.xml | 2 +- brouter-routing-app/src/main/res/values/strings.xml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/brouter-routing-app/src/main/res/values-ar/strings.xml b/brouter-routing-app/src/main/res/values-ar/strings.xml index c08e7ec..850445e 100644 --- a/brouter-routing-app/src/main/res/values-ar/strings.xml +++ b/brouter-routing-app/src/main/res/values-ar/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-ca/strings.xml b/brouter-routing-app/src/main/res/values-ca/strings.xml index 54b2760..1fc3b7e 100644 --- a/brouter-routing-app/src/main/res/values-ca/strings.xml +++ b/brouter-routing-app/src/main/res/values-ca/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-de/strings.xml b/brouter-routing-app/src/main/res/values-de/strings.xml index 39bb025..0968c2f 100644 --- a/brouter-routing-app/src/main/res/values-de/strings.xml +++ b/brouter-routing-app/src/main/res/values-de/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-el/strings.xml b/brouter-routing-app/src/main/res/values-el/strings.xml index a8d74fb..f25ef54 100644 --- a/brouter-routing-app/src/main/res/values-el/strings.xml +++ b/brouter-routing-app/src/main/res/values-el/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-es/strings.xml b/brouter-routing-app/src/main/res/values-es/strings.xml index ff1327f..75d4f0e 100644 --- a/brouter-routing-app/src/main/res/values-es/strings.xml +++ b/brouter-routing-app/src/main/res/values-es/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-fr/strings.xml b/brouter-routing-app/src/main/res/values-fr/strings.xml index 293b40d..e6baa53 100644 --- a/brouter-routing-app/src/main/res/values-fr/strings.xml +++ b/brouter-routing-app/src/main/res/values-fr/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-it/strings.xml b/brouter-routing-app/src/main/res/values-it/strings.xml index af2be5c..03a4726 100644 --- a/brouter-routing-app/src/main/res/values-it/strings.xml +++ b/brouter-routing-app/src/main/res/values-it/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-nl/strings.xml b/brouter-routing-app/src/main/res/values-nl/strings.xml index cbc9a87..dcb449a 100644 --- a/brouter-routing-app/src/main/res/values-nl/strings.xml +++ b/brouter-routing-app/src/main/res/values-nl/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-pl/strings.xml b/brouter-routing-app/src/main/res/values-pl/strings.xml index f797ee1..ada4d41 100644 --- a/brouter-routing-app/src/main/res/values-pl/strings.xml +++ b/brouter-routing-app/src/main/res/values-pl/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values/strings.xml b/brouter-routing-app/src/main/res/values/strings.xml index 2c1b384..cfc2d16 100644 --- a/brouter-routing-app/src/main/res/values/strings.xml +++ b/brouter-routing-app/src/main/res/values/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - "coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) From 1e9783819b57373112a30e4c30c1f8f9da3e4ab2 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 11 Dec 2023 10:52:22 +0100 Subject: [PATCH 088/173] added str format for resource strings --- .../src/main/java/btools/routingapp/BRouterView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 5c7be2b..e87f593 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -387,7 +387,7 @@ public class BRouterView extends View { if (size < 1) msg = getContext().getString(R.string.msg_no_wpt); if (size > 1000) - msg = getContext().getString(R.string.msg_to_much_wpts, size); + msg = String.format(getContext().getString(R.string.msg_to_much_wpts), size); } if (msg != null) { @@ -718,7 +718,7 @@ public class BRouterView extends View { ((BRouterActivity) getContext()).showErrorMessage(cr.getErrorMessage()); } else { String memstat = memoryClass + "mb pathPeak " + ((cr.getPathPeak() + 500) / 1000) + "k"; - String result = getContext().getString(R.string.msg_status_result, + String result = String.format(getContext().getString(R.string.msg_status_result), getContext().getString(R.string.app_version), memstat, Double.toString(cr.getDistance() / 1000.), From a21dee5923e2c050969bd8ba8b9a530a3cb82e6f Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 11 Dec 2023 14:02:45 +0100 Subject: [PATCH 089/173] string name misspelling --- .../src/main/java/btools/routingapp/BRouterView.java | 2 +- brouter-routing-app/src/main/res/values-ar/strings.xml | 2 +- brouter-routing-app/src/main/res/values-ca/strings.xml | 2 +- brouter-routing-app/src/main/res/values-de/strings.xml | 2 +- brouter-routing-app/src/main/res/values-el/strings.xml | 2 +- brouter-routing-app/src/main/res/values-es/strings.xml | 2 +- brouter-routing-app/src/main/res/values-fr/strings.xml | 2 +- brouter-routing-app/src/main/res/values-it/strings.xml | 2 +- brouter-routing-app/src/main/res/values-nl/strings.xml | 2 +- brouter-routing-app/src/main/res/values-pl/strings.xml | 2 +- brouter-routing-app/src/main/res/values/strings.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) 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 e87f593..a14dfcf 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -387,7 +387,7 @@ public class BRouterView extends View { if (size < 1) msg = getContext().getString(R.string.msg_no_wpt); if (size > 1000) - msg = String.format(getContext().getString(R.string.msg_to_much_wpts), size); + msg = String.format(getContext().getString(R.string.msg_too_much_wpts), size); } if (msg != null) { diff --git a/brouter-routing-app/src/main/res/values-ar/strings.xml b/brouter-routing-app/src/main/res/values-ar/strings.xml index 850445e..9f804d5 100644 --- a/brouter-routing-app/src/main/res/values-ar/strings.xml +++ b/brouter-routing-app/src/main/res/values-ar/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-ca/strings.xml b/brouter-routing-app/src/main/res/values-ca/strings.xml index 1fc3b7e..3bd69c2 100644 --- a/brouter-routing-app/src/main/res/values-ca/strings.xml +++ b/brouter-routing-app/src/main/res/values-ca/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-de/strings.xml b/brouter-routing-app/src/main/res/values-de/strings.xml index 0968c2f..ae6d47c 100644 --- a/brouter-routing-app/src/main/res/values-de/strings.xml +++ b/brouter-routing-app/src/main/res/values-de/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-el/strings.xml b/brouter-routing-app/src/main/res/values-el/strings.xml index f25ef54..1b34a3a 100644 --- a/brouter-routing-app/src/main/res/values-el/strings.xml +++ b/brouter-routing-app/src/main/res/values-el/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-es/strings.xml b/brouter-routing-app/src/main/res/values-es/strings.xml index 75d4f0e..4fe540e 100644 --- a/brouter-routing-app/src/main/res/values-es/strings.xml +++ b/brouter-routing-app/src/main/res/values-es/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-fr/strings.xml b/brouter-routing-app/src/main/res/values-fr/strings.xml index e6baa53..58f0f56 100644 --- a/brouter-routing-app/src/main/res/values-fr/strings.xml +++ b/brouter-routing-app/src/main/res/values-fr/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-it/strings.xml b/brouter-routing-app/src/main/res/values-it/strings.xml index 03a4726..c9e56a5 100644 --- a/brouter-routing-app/src/main/res/values-it/strings.xml +++ b/brouter-routing-app/src/main/res/values-it/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-nl/strings.xml b/brouter-routing-app/src/main/res/values-nl/strings.xml index dcb449a..2b58d1d 100644 --- a/brouter-routing-app/src/main/res/values-nl/strings.xml +++ b/brouter-routing-app/src/main/res/values-nl/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values-pl/strings.xml b/brouter-routing-app/src/main/res/values-pl/strings.xml index ada4d41..4ad50c0 100644 --- a/brouter-routing-app/src/main/res/values-pl/strings.xml +++ b/brouter-routing-app/src/main/res/values-pl/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) diff --git a/brouter-routing-app/src/main/res/values/strings.xml b/brouter-routing-app/src/main/res/values/strings.xml index cfc2d16..3a08420 100644 --- a/brouter-routing-app/src/main/res/values/strings.xml +++ b/brouter-routing-app/src/main/res/values/strings.xml @@ -67,6 +67,6 @@ estimated time = %6$s Error reading waypoints coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + coordinate source contains too much waypoints: %1$d (please use from/to/via names) From f37c77267a92abaa0d6dfc5e1f9decc9d8d62644 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 14 Dec 2023 11:47:35 +0100 Subject: [PATCH 090/173] added translations and new messages --- .../btools/routingapp/BInstallerActivity.java | 39 +++--- .../src/main/res/values-ar/strings.xml | 121 ++++++++++-------- .../src/main/res/values-ca/strings.xml | 118 ++++++++++------- .../src/main/res/values-de/strings.xml | 115 ++++++++++------- .../src/main/res/values-el/strings.xml | 121 ++++++++++-------- .../src/main/res/values-es/strings.xml | 116 ++++++++++------- .../src/main/res/values-fr/strings.xml | 113 +++++++++------- .../src/main/res/values-it/strings.xml | 119 ++++++++++------- .../src/main/res/values-nl/strings.xml | 107 +++++++++------- .../src/main/res/values-pl/strings.xml | 119 ++++++++++------- .../src/main/res/values/strings.xml | 22 ++++ 11 files changed, 656 insertions(+), 454 deletions(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java index 693882a..7ceac35 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java @@ -10,7 +10,6 @@ import android.annotation.SuppressLint; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; -import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.Build; @@ -296,13 +295,13 @@ public class BInstallerActivity extends AppCompatActivity { String result; switch (workInfo.getState()) { case FAILED: - result = "Download failed"; + result = getString(R.string.msg_download_faild); break; case CANCELLED: - result = "Download cancelled"; + result = getString(R.string.msg_download_cancel); break; case SUCCEEDED: - result = "Download succeeded"; + result = getString(R.string.msg_download_succeed); break; default: result = ""; @@ -350,12 +349,12 @@ public class BInstallerActivity extends AppCompatActivity { switch (id) { case DIALOG_CONFIRM_DELETE_ID: builder - .setTitle("Confirm Delete") - .setMessage("Really delete?").setPositiveButton("Yes", new DialogInterface.OnClickListener() { + .setTitle(R.string.title_delete) + .setMessage(R.string.summary_delete).setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { deleteSelectedTiles(); } - }).setNegativeButton("No", new DialogInterface.OnClickListener() { + }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { } }); @@ -363,9 +362,9 @@ public class BInstallerActivity extends AppCompatActivity { case DIALOG_CONFIRM_NEXTSTEPS_ID: builder - .setTitle("Version Problem") - .setMessage("The base version for tiles has changed. What to do?") - .setPositiveButton("Continue with current download, delete other old data", new DialogInterface.OnClickListener() { + .setTitle(R.string.title_version) + .setMessage(R.string.summary_version) + .setPositiveButton(R.string.action_version1, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { ArrayList allTiles = mBInstallerView.getSelectedTiles(MASK_INSTALLED_RD5); @@ -377,11 +376,11 @@ public class BInstallerActivity extends AppCompatActivity { } downloadSelectedTiles(); } - }).setNegativeButton("Select all for download and start", new DialogInterface.OnClickListener() { + }).setNegativeButton(R.string.action_version2, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { downloadInstalledTiles(); } - }).setNeutralButton("Cancel now, complete on an other day", new DialogInterface.OnClickListener() { + }).setNeutralButton(R.string.action_version3, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { File tmplookupFile = new File(mBaseDir, "brouter/profiles2/lookups.dat.tmp"); tmplookupFile.delete(); @@ -392,17 +391,17 @@ public class BInstallerActivity extends AppCompatActivity { case DIALOG_CONFIRM_GETDIFFS_ID: builder - .setTitle("Version Differences") - .setMessage("The base version for some tiles is different. What to do?") - .setPositiveButton("Download all different tiles", new DialogInterface.OnClickListener() { + .setTitle(R.string.title_version_diff) + .setMessage(R.string.summary_version_diff) + .setPositiveButton(R.string.action_version_diff1, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { downloadDiffVersionTiles(); } - }).setNegativeButton("Drop all different tiles", new DialogInterface.OnClickListener() { + }).setNegativeButton(R.string.action_version_diff2, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dropDiffVersionTiles(); } - }).setNeutralButton("Cancel now, complete on an other day", new DialogInterface.OnClickListener() { + }).setNeutralButton(R.string.action_version_diff3, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { finish(); } @@ -410,9 +409,9 @@ public class BInstallerActivity extends AppCompatActivity { return builder.create(); case DIALOG_NEW_APP_NEEDED_ID: builder - .setTitle("App Version") - .setMessage("The new data version needs a new app. Please update BRouter first") - .setPositiveButton("Ok", new DialogInterface.OnClickListener() { + .setTitle(R.string.title_version) + .setMessage(R.string.summary_new_version) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { finish(); } diff --git a/brouter-routing-app/src/main/res/values-ar/strings.xml b/brouter-routing-app/src/main/res/values-ar/strings.xml index 9f804d5..74679da 100644 --- a/brouter-routing-app/src/main/res/values-ar/strings.xml +++ b/brouter-routing-app/src/main/res/values-ar/strings.xml @@ -14,59 +14,80 @@ الحجم=%1$s\nالحجم Ø§Ù„Ù…ØªÙˆÙØ±=%2$s تحميل القطع - Choose brouter data base dir: - Select a routing profile - Select Main Action - Download Manager - BRouter App + اختر مكان قاعدة بيانات brouter: + اختر مل٠تعري٠التوجيه + حدد الإجراء الرئيسي + مدير التنزيلات + تطبيق BRouter - Cancel - I know - Close - Exit - OK - Success - An Error occurred + إلغاء + أعر٠ذلك + إغلاق + خروج + مواÙÙ‚ + تم بنجاح + حدث خطأ ما - BRouter Download Manager - *** Attention:*** - \n\nThe Download Manager is used to download routing-data - files which can be up to 170MB each. Do not start the Download Manager - on a cellular data connection without a data plan! - Download speed is restricted to 16 MBit/s. - Successfully prepared a timeout-free calculation - You successfully repeated a calculation that previously run into a timeout - when started from your map-tool. If you repeat the same request from your - maptool, with the exact same destination point and a close-by starting point, - this request is guaranteed not to time out. - Enter SDCARD base dir: - Select Action + مدير تنزيلات BRouter + *** تحذير:*** + \n\nيتم استخدام مدير التنزيل لتنزيل Ù…Ù„ÙØ§Øª بيانات التوجيه عبر الطرق + والتي يمكن أن يصل حجمها إلى 170 ميجابايت، لذلك لا تقم بتشغيل + "مدير التنزيلات" حينما تكون متصلاً عبر البيانات الخلوية ومحدوداً بحجم التنزيلات! + سرعة التنزيل محدودة على 16 ميجابايت/بالثانية. + أنجزت عملية الحساب بنجاح وبدون تأخير + لقد نجحت ÙÙŠ تكرار عملية حسابية كانت قد انتهت ÙÙŠ السابق + عند بدايتها من خلال أداة الخريطة، إذا قمت بتكرار Ù†ÙØ³ الطلب من + أداة الخريطة الخاصة بك ومع Ù†ÙØ³ الوجهة بالضبط وبنقطة بداية + قريبة، ÙØ³ÙŠØ¶Ù…Ù† لك التطبيق عدم انتهاء مهلة هذا الطلب. + أدخل مسار الذاكرة الخارجية: + اختر الإجراء - Check VIA Selection: - Check NoGo Selection: - Server-Mode - Info - Calc Route - Profile Settings - Share GPX - Select from - Select to/via + التحقق من النقاط الوسيطة: + التحقق من النقاط المسبتعدة: + وضع-الخادم + معلومات + حساب الطريق + إعدادات مل٠التعري٠+ مشاركة مل٠GPX + اختر من + اختر إلى/عبر - no profile data - , no used profile - Too much data for download. Please reduce. - Download scheduled. Check internet connection if it doesn\'t start. - current waypoint selection:\n - Expecting waypoint selection\n - Alternative - version = BRouter-%1$s \n - mem = %2$s \n - distance = %3$s km\n - filtered ascend = %4$s m\n - plain ascend = %5$s m\n - estimated time = %6$s - Error reading waypoints - coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + لا توجد بيانات مل٠التعري٠+ ØŒ لا يوجد مل٠تعري٠مستخدم + هناك الكثير من البيانات للتنزيل، من ÙØ¶Ù„Ùƒ قم بتقليلها. + تمت جدولة التنزيل، وتحقق من اتصالك بالإنترنت إذا لم يبدأ التنزيل. + نقاط الطريق الحالية المحددة:\n + نقاط الطريق المتوقعة المحددة\n + البديل + الإصدار = BRouter-%1$s \n + الذاكرة = %2$s \n + Ø§Ù„Ù…Ø³Ø§ÙØ© = %3$s كم\n + التصاعد المرشح = %4$s Ù…\n + التصاعد العادي = %5$s Ù…\n + الوقت المقدر = %6$s + خطأ ÙÙŠ قراءة نقاط الطريق + لا يحتوي مصدر الإحداثيات على أي نقاط الطريق! + مصدر الإحداثيات يحتوي على عدد كبير جدًا من نقاط الطريق: %1$d (يرجى استخدام أسماء النقاط من/إلى/عبر from/to/via) + + No + Yes + + Confirm Delete + Really delete? + Version Problem + The base version for tiles has changed. What to do? + Continue with current download, delete other old data + Select all for download and start + Cancel now, complete on an other day + Version Differences + The base version for some tiles is different. What to do? + Download all different tiles + Drop all different tiles + Cancel now, complete on an other day + The new data version needs a new app. Please update BRouter first + + Download failed + Download cancelled + Download succeeded diff --git a/brouter-routing-app/src/main/res/values-ca/strings.xml b/brouter-routing-app/src/main/res/values-ca/strings.xml index 3bd69c2..3ebbf3e 100644 --- a/brouter-routing-app/src/main/res/values-ca/strings.xml +++ b/brouter-routing-app/src/main/res/values-ca/strings.xml @@ -14,59 +14,79 @@ Espai=%1$s\nLliure=%2$s Descarregar segments - Choose brouter data base dir: - Select a routing profile - Select Main Action - Download Manager - BRouter App + Escollir directori base brouter: + Seleccionar un perfil d\'enrutament + Seleccionar Acció Principal + Gestor de Baixades + App BRouter - Cancel - I know - Close - Exit - OK - Success - An Error occurred + Cancel·lar + Ho sé + Tancar + Sortir + D\'acord + Èxit + Ha aparegut un error - BRouter Download Manager - *** Attention:*** - \n\nThe Download Manager is used to download routing-data - files which can be up to 170MB each. Do not start the Download Manager - on a cellular data connection without a data plan! - Download speed is restricted to 16 MBit/s. - Successfully prepared a timeout-free calculation - You successfully repeated a calculation that previously run into a timeout - when started from your map-tool. If you repeat the same request from your - maptool, with the exact same destination point and a close-by starting point, - this request is guaranteed not to time out. - Enter SDCARD base dir: - Select Action + Gestor de baixades BRouter + *** Atenció:*** + \n\nEl Gestor de Baixades es fa servir per descarregar fitxers de dades + d\'enrutament, que poden arribar als 170 Mb cadascun. No inicies el Gestor + des d\'un mòbil sense un pla de dades! + La velocitat de baixada està limitada a 16 Mbit/s. + S\'ha preparat correctament un càlcul sense límit de temps + S\'ha repetit amb èxit un càlcul a través de l\'aplicació de cartografia que prèviament havia + superat el temps d\'espera. Si repeteixes la mateixa petició amb exactament el mateix destí i un punt de partida proper, + es garanteix que la petició no esgotarà el límit de temps. + Introdueix el directori base de la tarja SD: + Seleccionar Acció - Check VIA Selection: - Check NoGo Selection: - Server-Mode + Comprovar Selecció VIA: + Comprovar Selecció NoGo: + Mode-Servidor Info - Calc Route - Profile Settings - Share GPX - Select from - Select to/via + Calc Ruta + Ajustaments del perfil + Compartir GPX + Seleccionar des de + Seleccionar a/via - no profile data - , no used profile - Too much data for download. Please reduce. - Download scheduled. Check internet connection if it doesn\'t start. - current waypoint selection:\n - Expecting waypoint selection\n - Alternative - version = BRouter-%1$s \n - mem = %2$s \n - distance = %3$s km\n - filtered ascend = %4$s m\n - plain ascend = %5$s m\n - estimated time = %6$s - Error reading waypoints - coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + no hi ha dades al perfil + , cap perfil utilitzat + Massa dades per baixar. Si us plau, redueix-les. + Baixada programada. Comprovar la connexió a internet si no comença. + selecció actual de fita:\n + Esperant selecció de fita\n + Alternativa + versió = BRouter-%1$s \n + memòria = %2$s \n + distància = %3$s km\n + ascensió filtrada = %4$s m\n + ascens en pla = %5$s m\n + temps estimat = %6$s + Error llegint fites + la font de coordenades no conté cap fita! + la font de coordenades conté massa fites: %1$d (si us plau, fes servir noms des de/a/via) + + No + Yes + + Confirm Delete + Really delete? + Version Problem + The base version for tiles has changed. What to do? + Continue with current download, delete other old data + Select all for download and start + Cancel now, complete on an other day + Version Differences + The base version for some tiles is different. What to do? + Download all different tiles + Drop all different tiles + Cancel now, complete on an other day + The new data version needs a new app. Please update BRouter first + + Download failed + Download cancelled + Download succeeded diff --git a/brouter-routing-app/src/main/res/values-de/strings.xml b/brouter-routing-app/src/main/res/values-de/strings.xml index ae6d47c..9a7bed9 100644 --- a/brouter-routing-app/src/main/res/values-de/strings.xml +++ b/brouter-routing-app/src/main/res/values-de/strings.xml @@ -14,59 +14,80 @@ Größe=%1$s\nFrei=%2$s Segmente herunterladen - Choose brouter data base dir: - Select a routing profile - Select Main Action - Download Manager - BRouter App + BRouter-Datenbankverzeichnis wählen: + Routenprofil wählen + Standardaktion wählen + Downloadmanager + BRouter-App - Cancel - I know - Close - Exit + Abbrechen + Ich weiß + Schließen + Verlassen OK - Success - An Error occurred + Erfolg + Ein Fehler ist aufgetreten - BRouter Download Manager - *** Attention:*** - \n\nThe Download Manager is used to download routing-data - files which can be up to 170MB each. Do not start the Download Manager - on a cellular data connection without a data plan! - Download speed is restricted to 16 MBit/s. - Successfully prepared a timeout-free calculation - You successfully repeated a calculation that previously run into a timeout - when started from your map-tool. If you repeat the same request from your - maptool, with the exact same destination point and a close-by starting point, - this request is guaranteed not to time out. - Enter SDCARD base dir: - Select Action + BRouter Downloadmanager + *** Achtung:*** + \n\nDer Downloadmanager wird zum Herunterladen von Routingdaten verwendet + die jeweils bis zu 170MB groß sein können. Starten Sie den Downloadmanager + nicht auf Mobiltelefonen ohne Mobilfunktdatentarif! + Die Downloadgeschwindigkeit ist auf 16 MBit/s begrenzt. + Berechnung ohne Zeitüberschreitung erfolgreich + Sie haben eine Berechnung erfolgreich wiederholt, bei + der zuvor beim Start von Ihrem Karten-Tool aus eine Zeitüberschreitung auftrat. + Wenn Sie dieselbe Anfrage von Ihrem Karten-Tool aus wiederholen, mit genau demselben + Zielpunkt und einem nahe gelegenen Startpunkt, wird diese Anfrage garantiert nicht abbrechen. + SD-Karten Verzeichnis wählen: + Aktion wählen - Check VIA Selection: - Check NoGo Selection: - Server-Mode + Via-Auswahl prüfen: + NoGo-Auswahl prüfen: + Server-Modus Info - Calc Route - Profile Settings - Share GPX - Select from - Select to/via + Route berechnen + Profileinstellungen + GPX teilen + "Von" wählen + "Nach"/Via wählen - no profile data - , no used profile - Too much data for download. Please reduce. - Download scheduled. Check internet connection if it doesn\'t start. - current waypoint selection:\n - Expecting waypoint selection\n + Keine Profildaten + , kein verwendetes Profil + Zu viele Daten für den Download. Bitte reduzieren. + Download geplant. Überprüfen Sie die Internetverbindung, wenn der Download nicht startet. + aktuelle Wegpunktauswahl:\n + Erwarte Wegpunktauswahl\n Alternative - version = BRouter-%1$s \n - mem = %2$s \n - distance = %3$s km\n - filtered ascend = %4$s m\n - plain ascend = %5$s m\n - estimated time = %6$s - Error reading waypoints - coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + Version = BRouter-%1$s\n + Speicher = %2$s \n + Abstand = %3$s km\n + gefilterter Aufstieg = %4$s m\n + schlichter Aufstieg = %5$s m\n + geschätzte Zeit = %6$s + Fehler beim Lesen von Wegpunkten + Die Koordinatenquelle enthält keine Wegpunkte! + Die Koordinatenquelle enthält zu viele Wegpunkte: %1$d (bitte von/nach/via-Namen verwenden) + + No + Yes + + Confirm Delete + Really delete? + Version Problem + The base version for tiles has changed. What to do? + Continue with current download, delete other old data + Select all for download and start + Cancel now, complete on an other day + Version Differences + The base version for some tiles is different. What to do? + Download all different tiles + Drop all different tiles + Cancel now, complete on an other day + The new data version needs a new app. Please update BRouter first + + Download failed + Download cancelled + Download succeeded diff --git a/brouter-routing-app/src/main/res/values-el/strings.xml b/brouter-routing-app/src/main/res/values-el/strings.xml index 1b34a3a..742b9a3 100644 --- a/brouter-routing-app/src/main/res/values-el/strings.xml +++ b/brouter-routing-app/src/main/res/values-el/strings.xml @@ -1,7 +1,7 @@ - %d τμήμα + %d τμήμαcompare cl %d τμήματα ΑκÏÏωση λήψης @@ -14,59 +14,80 @@ Μέγεθος=%1$s\nΕλεÏθεÏο=%2$s Λήψη τμημάτων - Choose brouter data base dir: - Select a routing profile - Select Main Action - Download Manager - BRouter App + Επιλέξτε φάκελο δεδομένων brouter: + Επιλέξτε ένα Ï€Ïοφίλ δÏομολόγησης + Επιλέξτε ενέÏγεια + ΔιαχειÏιστής λήψεων + ΕφαÏμογή BRouter - Cancel - I know - Close - Exit + ΑκÏÏωση + ΓνωÏίζω + Κλείσιμο + Έξοδος OK - Success - An Error occurred + Επιτυχία + ΠαÏουσιάστηκε σφάλμα - BRouter Download Manager - *** Attention:*** - \n\nThe Download Manager is used to download routing-data - files which can be up to 170MB each. Do not start the Download Manager - on a cellular data connection without a data plan! - Download speed is restricted to 16 MBit/s. - Successfully prepared a timeout-free calculation - You successfully repeated a calculation that previously run into a timeout - when started from your map-tool. If you repeat the same request from your - maptool, with the exact same destination point and a close-by starting point, - this request is guaranteed not to time out. - Enter SDCARD base dir: - Select Action + BRouter διαχειÏιστής λήψεων + *** ΠÏοσοχή:*** + \n\nΟ διαχειÏιστής λήψεων χÏησιμοποιείται για τη λήψη αÏχείων δεδομένων δÏομολόγησης + που μποÏεί να είναι έως 170MB το καθένα. Μην ξεκινήσετε το διαχειÏιστή λήψεων + σε σÏνδεση δεδομένων κινητής τηλεφωνίας χωÏίς Ï€ÏόγÏαμμα δεδομένων! + Η ταχÏτητα λήψης πεÏιοÏίζεται στα 16 MBit/s. + Ετοιμάστηκε επιτυχώς ένας υπολογισμός χωÏίς χÏονικό ÏŒÏιο + Επαναλάβατε με επιτυχία έναν υπολογισμό που Ï€Ïιν είχε χÏονικό ÏŒÏιο + όταν ξεκίνησε από το εÏγαλείο χαÏτών σας. Εάν επαναλάβετε το ίδιο αίτημα + από το εÏγαλείο χαÏτών σας, με τον ίδιο ακÏιβώς Ï€ÏοοÏισμό και μια κοντινή αφετηÏία, + αυτό το αίτημα είναι εγγυημένο ότι δεν θα λήξει. + Εισάγετε φάκελο SDCARD: + Επιλέξτε ενέÏγεια - Check VIA Selection: - Check NoGo Selection: - Server-Mode - Info - Calc Route - Profile Settings - Share GPX - Select from - Select to/via + Ελέγξτε την επιλογή VIA: + Ελέγξτε την επιλογή NoGo: + ΛειτουÏγία διακομιστή + ΠληÏοφοÏίες + Υπολογισμός διαδÏομής + Ρυθμίσεις Ï€Ïοφίλ + Κοινή χÏήση GPX + Επιλέξτε από + Επιλέξτε Ï€Ïος/μέσω - no profile data - , no used profile - Too much data for download. Please reduce. - Download scheduled. Check internet connection if it doesn\'t start. - current waypoint selection:\n - Expecting waypoint selection\n - Alternative - version = BRouter-%1$s \n - mem = %2$s \n - distance = %3$s km\n - filtered ascend = %4$s m\n - plain ascend = %5$s m\n - estimated time = %6$s - Error reading waypoints - coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + μη δεδομένα Ï€Ïοφίλ + , μη χÏησιμοποιημένο Ï€Ïοφίλ + ΠάÏα πολλά δεδομένα για λήψη. ΠαÏακαλώ μειώστε. + ΠÏογÏαμματισμένη λήψη. Ελέγξτε τη σÏνδεση στο διαδίκτυο εάν δεν ξεκινά. + Ï„Ïέχουσα επιλογή σημείου:\n + Αναμένεται επιλογή σημείου\n + Εναλλακτική + έκδοση = BRouter-%1$s \n + μνήμη = %2$s \n + απόσταση = %3$s km\n + φιλτÏαÏισμένη άνοδος = %4$s m\n + απλή άνοδος = %5$s m\n + εκτιμώμενος χÏόνος = %6$s + Σφάλμα ανάγνωσης σημείων + η πηγή συντεταγμένων δεν πεÏιέχει σημεία! + η πηγή συντεταγμένων πεÏιέχει πάÏα πολλά σημεία: %1$d (παÏακαλώ χÏησιμοποιήστε από/Ï€Ïος/μέσω ονόματα) + + No + Yes + + Confirm Delete + Really delete? + Version Problem + The base version for tiles has changed. What to do? + Continue with current download, delete other old data + Select all for download and start + Cancel now, complete on an other day + Version Differences + The base version for some tiles is different. What to do? + Download all different tiles + Drop all different tiles + Cancel now, complete on an other day + The new data version needs a new app. Please update BRouter first + + Download failed + Download cancelled + Download succeeded diff --git a/brouter-routing-app/src/main/res/values-es/strings.xml b/brouter-routing-app/src/main/res/values-es/strings.xml index 4fe540e..6570697 100644 --- a/brouter-routing-app/src/main/res/values-es/strings.xml +++ b/brouter-routing-app/src/main/res/values-es/strings.xml @@ -14,59 +14,79 @@ Tamaño=%1$s\nGratis=%2$s Descargar segmentos - Choose brouter data base dir: - Select a routing profile - Select Main Action - Download Manager - BRouter App + Seleccionar directorio base brouter: + Seleccionar un perfil de enrutamiento + Seleccionar Acción Principal + Gestor de Descargas + App BRouter - Cancel - I know - Close - Exit + Cancelar + Lo se + Cerrar + Salir OK - Success - An Error occurred + Salir + Ha aparecido un error - BRouter Download Manager - *** Attention:*** - \n\nThe Download Manager is used to download routing-data - files which can be up to 170MB each. Do not start the Download Manager - on a cellular data connection without a data plan! - Download speed is restricted to 16 MBit/s. - Successfully prepared a timeout-free calculation - You successfully repeated a calculation that previously run into a timeout - when started from your map-tool. If you repeat the same request from your - maptool, with the exact same destination point and a close-by starting point, - this request is guaranteed not to time out. - Enter SDCARD base dir: - Select Action + Gestor de descargas BRouter + *** Atención:*** + \n\nEl Gestor de Descargas se usa para descargar ficheros de datos + de ruta, que pueden llegar a los 170 Mb cada uno. No inicies el Gestor + desde un terminal sin plan de datos! + La velocidad de descarga está limitada a 16 Mbit/s. + Se ha preparado correctamente un cálculo sin límite de tiempo + Se ha repetido un cálculo a través de la aplicación de mapas que previamente + había superado el tiempo de espera. Si repites la misma petición con exactamente el mismo destino y un punto de partida cercano, + se garantiza que la petición no agotará el límite de tiempo. + Introduce el directorio base de la tarjeta SD: + Seleccionar Acción - Check VIA Selection: - Check NoGo Selection: - Server-Mode + Comprobar Selección VIA: + Comprobar Selección NoGo: + Modo-Servidor Info - Calc Route - Profile Settings - Share GPX - Select from - Select to/via + Calc Ruta + Ajustes del perfil + Compartir GPX + Seleccionar desde + Seleccionar a/vía - no profile data - , no used profile - Too much data for download. Please reduce. - Download scheduled. Check internet connection if it doesn\'t start. - current waypoint selection:\n - Expecting waypoint selection\n - Alternative - version = BRouter-%1$s \n - mem = %2$s \n - distance = %3$s km\n - filtered ascend = %4$s m\n - plain ascend = %5$s m\n - estimated time = %6$s - Error reading waypoints - coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + no hay datos en el perfil + , ningún perfil utilizado + Demasiados datos a descargar. Por favor, reducidlos. + Descarga programada. Comprueba la conexión a internet si no se inicia. + selección actual de waypoint:\n + Esperando selección de waypoint\n + Alternativa + versión = BRouter-%1$s \n + memoria = %2$s \n + distancia = %3$s km\n + ascensión filtrada = %4$s m\n + ascensión en llano = %5$s m\n + tiempo estimado = %6$s + Error leyendo waypoints + la fuente de coordenadas no contiene ningún waypoint! + la fuente de coordenadas contiene demasiados waypoints: %1$d (por favor, usa nombres desde/a/vía) + + No + Yes + + Confirm Delete + Really delete? + Version Problem + The base version for tiles has changed. What to do? + Continue with current download, delete other old data + Select all for download and start + Cancel now, complete on an other day + Version Differences + The base version for some tiles is different. What to do? + Download all different tiles + Drop all different tiles + Cancel now, complete on an other day + The new data version needs a new app. Please update BRouter first + + Download failed + Download cancelled + Download succeeded diff --git a/brouter-routing-app/src/main/res/values-fr/strings.xml b/brouter-routing-app/src/main/res/values-fr/strings.xml index 58f0f56..be54330 100644 --- a/brouter-routing-app/src/main/res/values-fr/strings.xml +++ b/brouter-routing-app/src/main/res/values-fr/strings.xml @@ -14,59 +14,80 @@ Taille=%1$s\nGratuit=%2$s Télécharger les segments - Choose brouter data base dir: - Select a routing profile - Select Main Action - Download Manager - BRouter App + Choisissez le répertoire de la base de données brouter: + Sélectionnez un profil de routage + Sélectionner l\'action principale + Gestionnaire de téléchargement + Application BRouter - Cancel - I know - Close - Exit + Annuler + Je sais + Fermer + Quitter OK - Success - An Error occurred + Succès + Une erreur s\'est produite - BRouter Download Manager - *** Attention:*** - \n\nThe Download Manager is used to download routing-data - files which can be up to 170MB each. Do not start the Download Manager - on a cellular data connection without a data plan! - Download speed is restricted to 16 MBit/s. - Successfully prepared a timeout-free calculation - You successfully repeated a calculation that previously run into a timeout - when started from your map-tool. If you repeat the same request from your - maptool, with the exact same destination point and a close-by starting point, - this request is guaranteed not to time out. - Enter SDCARD base dir: - Select Action + Gestionnaire de téléchargement BRouter + *** Attention :*** + \n\nLe gestionnaire de téléchargement est utilisé pour télécharger les données de routage + fichiers pouvant atteindre 170MB chacun. Ne démarrez pas le gestionnaire de téléchargement + sur une connexion de données cellulaires sans forfait de données ! + La vitesse de téléchargement est limitée à 16 MBit/s. + Préparation réussie d\'un calcul sans délai d\'attente + Vous avez répété avec succès un calcul qui avait précédemment expiré + lorsqu\'il est démarré à partir de votre outil cartographique. Si vous répétez la même demande de votre + maptool, avec exactement le même point de destination et un point de départ à proximité, + il est garanti que cette requête n\'expirera pas. + Entrez le répertoire de base de la SDCARD: + Sélectionner une action - Check VIA Selection: - Check NoGo Selection: - Server-Mode - Info - Calc Route - Profile Settings - Share GPX - Select from - Select to/via + Vérifier la sélection VIA: + Vérifier la sélection NoGo: + Mode serveur + Informations + Calculer l\'itinéraire + Paramètres du profil + Partager GPX + Sélectionner de + Sélectionner vers/via - no profile data - , no used profile - Too much data for download. Please reduce. - Download scheduled. Check internet connection if it doesn\'t start. - current waypoint selection:\n - Expecting waypoint selection\n + aucune donnée de profil + , aucun profil utilisé + Trop de données à télécharger. Veuillez réduire. + Téléchargement planifié. Vérifiez la connexion Internet si elle ne démarre pas. + sélection actuelle du waypoint:\n + Sélection de waypoint en attente\n Alternative version = BRouter-%1$s \n - mem = %2$s \n + mémoire = %2$s \n distance = %3$s km\n - filtered ascend = %4$s m\n - plain ascend = %5$s m\n - estimated time = %6$s - Error reading waypoints - coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + montée filtrée = %4$s m\n + montée simple = %5$s m\n + temps estimé = %6$s + Erreur lors de la lecture des waypoints + la source de coordonnées ne contient aucun waypoint! + la source de coordonnées contient trop de waypoints: %1$d (veuillez utiliser les noms de/vers/via) + + No + Yes + + Confirm Delete + Really delete? + Version Problem + The base version for tiles has changed. What to do? + Continue with current download, delete other old data + Select all for download and start + Cancel now, complete on an other day + Version Differences + The base version for some tiles is different. What to do? + Download all different tiles + Drop all different tiles + Cancel now, complete on an other day + The new data version needs a new app. Please update BRouter first + + Download failed + Download cancelled + Download succeeded diff --git a/brouter-routing-app/src/main/res/values-it/strings.xml b/brouter-routing-app/src/main/res/values-it/strings.xml index c9e56a5..298978b 100644 --- a/brouter-routing-app/src/main/res/values-it/strings.xml +++ b/brouter-routing-app/src/main/res/values-it/strings.xml @@ -14,59 +14,80 @@ Taglia=%1$s\nGratis=%2$s Scarica segmenti - Choose brouter data base dir: - Select a routing profile - Select Main Action - Download Manager - BRouter App + Seleziona la directory del database brouter: + Seleziona un profilo di instradamento + Seleziona azione principale + Gestore dei download + Applicazione BRouter - Cancel - I know - Close - Exit + Annulla + Lo so + Chiudi + Esci OK - Success - An Error occurred + Successo + Si è verificato un errore - BRouter Download Manager - *** Attention:*** - \n\nThe Download Manager is used to download routing-data - files which can be up to 170MB each. Do not start the Download Manager - on a cellular data connection without a data plan! - Download speed is restricted to 16 MBit/s. - Successfully prepared a timeout-free calculation - You successfully repeated a calculation that previously run into a timeout - when started from your map-tool. If you repeat the same request from your - maptool, with the exact same destination point and a close-by starting point, - this request is guaranteed not to time out. - Enter SDCARD base dir: - Select Action + Gestore download BRouter + *** Attenzione:*** + \n\nIl Download Manager viene utilizzato per scaricare i dati di routing + file che possono pesare fino a 170 MB ciascuno. Non avviare il Download Manager + su una connessione dati cellulare senza un piano dati! + La velocità di download è limitata a 16 MBit/s. + Preparato con successo un calcolo senza timeout + Hai ripetuto con successo un calcolo che in precedenza era terminato con un timeout + quando avviato dal tuo strumento mappa. Se ripeti la stessa richiesta dal tuo + maptool, con lo stesso identico punto di destinazione e un punto di partenza vicino, + è garantito che questa richiesta non vada in timeout. + Inserisci la directory base della SDCARD: + Seleziona azione - Check VIA Selection: - Check NoGo Selection: - Server-Mode - Info - Calc Route - Profile Settings - Share GPX - Select from - Select to/via + Controlla la selezione VIA: + Controlla la selezione NoGo: + Modalità server + Informazioni + Calcola percorso + Impostazioni profilo + Condividi GPX + Seleziona da + Seleziona a/via - no profile data - , no used profile - Too much data for download. Please reduce. - Download scheduled. Check internet connection if it doesn\'t start. - current waypoint selection:\n - Expecting waypoint selection\n - Alternative - version = BRouter-%1$s \n - mem = %2$s \n - distance = %3$s km\n - filtered ascend = %4$s m\n - plain ascend = %5$s m\n - estimated time = %6$s - Error reading waypoints - coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + nessun dato del profilo + , nessun profilo utilizzato + Troppi dati per il download. Per favore riduci. + Download programmato. Controlla la connessione Internet se non si avvia. + selezione del waypoint corrente:\n + Attesa selezione waypoint\n + Alternativa + versione = BRouter-%1$s \n + memoria = %2$s \n + distanza = %3$s km\n + salita filtrata = %4$s m\n + salita semplice = %5$s m\n + tempo stimato = %6$s + Errore durante la lettura dei waypoint + la fonte delle coordinate non contiene alcun waypoint! + la fonte delle coordinate contiene troppi waypoint: %1$d (usa i nomi da/a/via) + + No + Yes + + Confirm Delete + Really delete? + Version Problem + The base version for tiles has changed. What to do? + Continue with current download, delete other old data + Select all for download and start + Cancel now, complete on an other day + Version Differences + The base version for some tiles is different. What to do? + Download all different tiles + Drop all different tiles + Cancel now, complete on an other day + The new data version needs a new app. Please update BRouter first + + Download failed + Download cancelled + Download succeeded diff --git a/brouter-routing-app/src/main/res/values-nl/strings.xml b/brouter-routing-app/src/main/res/values-nl/strings.xml index 2b58d1d..7d05ca9 100644 --- a/brouter-routing-app/src/main/res/values-nl/strings.xml +++ b/brouter-routing-app/src/main/res/values-nl/strings.xml @@ -14,59 +14,74 @@ Grootte=%1$s\nGratis=%2$s Segmenten downloaden - Choose brouter data base dir: - Select a routing profile - Select Main Action - Download Manager + Kies de brouter database map: + Selecteer een routingprofiel + Selecteer de hoofdactie + Download beheerder BRouter App - Cancel - I know - Close - Exit + Annuleer + Ik weet het + Sluiten + Verlaat OK - Success - An Error occurred + Succes + Er is een fout opgetreden BRouter Download Manager - *** Attention:*** - \n\nThe Download Manager is used to download routing-data - files which can be up to 170MB each. Do not start the Download Manager - on a cellular data connection without a data plan! - Download speed is restricted to 16 MBit/s. - Successfully prepared a timeout-free calculation - You successfully repeated a calculation that previously run into a timeout - when started from your map-tool. If you repeat the same request from your - maptool, with the exact same destination point and a close-by starting point, - this request is guaranteed not to time out. - Enter SDCARD base dir: - Select Action + *** OPgelet:*** + \n\nDe Download Manager wordt gebruikt voor het downloaden van routing-gegevens bestanden die elk maximaal 170 MB groot kunnen zijn. Start de Download Manager niet op een mobiele dataverbinding zonder data-abonnement! De downloadsnelheid is beperkt tot 16 MBit/s. + Een time-outvrije berekening is succesvol voorbereid + Je hebt met succes een berekening herhaald die eerder op een time-out stuitte toen deze vanuit je map-tool werd gestart. Als u hetzelfde verzoek herhaalt vanuit uw maptool, met exact hetzelfde bestemmingspunt en een dichtbijgelegen beginpunt zal deze aanvraag gegarandeerd geen time-out krijgen. + De SDCARD basis directory invoeren: + Actie selecteren - Check VIA Selection: - Check NoGo Selection: - Server-Mode + Controleer de VIA selectie: + Controleer de NoGo selectie: + Server-mode Info - Calc Route - Profile Settings - Share GPX - Select from - Select to/via + Bereken route + Profiel instellingen + GPX delen + Selecteer van + Selecteer naar/via - no profile data - , no used profile - Too much data for download. Please reduce. - Download scheduled. Check internet connection if it doesn\'t start. - current waypoint selection:\n - Expecting waypoint selection\n - Alternative - version = BRouter-%1$s \n - mem = %2$s \n - distance = %3$s km\n - filtered ascend = %4$s m\n - plain ascend = %5$s m\n - estimated time = %6$s - Error reading waypoints - coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + geen profiel data + , geen gebruikt profiel + Te veel gegevens om te downloaden. Verminder a.u.b. + Download is gepland. Controleer de internetverbinding als deze niet start. + huidige waypointselectie:\n + Verwacht waypointselectie\n + Alternatief + versie = BRouter-%1$s \n + geheugen = %2$s \n + afstand = %3$s km\n + gefilterde opstijging = %4$s m\n + simpel opstijging = %5$s m\n + geschatte tijd = %6$s + Fout bij het lezen van waypoints + coördinaten bron bevat geen waypoints! + De coördinatenbron bevat te veel waypoints: %1$d (gebruik van/naar/via namen) + + No + Yes + + Confirm Delete + Really delete? + Version Problem + The base version for tiles has changed. What to do? + Continue with current download, delete other old data + Select all for download and start + Cancel now, complete on an other day + Version Differences + The base version for some tiles is different. What to do? + Download all different tiles + Drop all different tiles + Cancel now, complete on an other day + The new data version needs a new app. Please update BRouter first + + Download failed + Download cancelled + Download succeeded diff --git a/brouter-routing-app/src/main/res/values-pl/strings.xml b/brouter-routing-app/src/main/res/values-pl/strings.xml index 4ad50c0..6bca420 100644 --- a/brouter-routing-app/src/main/res/values-pl/strings.xml +++ b/brouter-routing-app/src/main/res/values-pl/strings.xml @@ -14,59 +14,80 @@ Rozmiar=%1$s\nDostÄ™pne=%2$s Pobieranie segmentów - Choose brouter data base dir: - Select a routing profile - Select Main Action - Download Manager - BRouter App + Wybierz katalog bazy danych BRoutera: + Wybierz profil routingu + Wybierz akcjÄ™ głównÄ… + Menedżer pobierania + Aplikacja BRouter - Cancel - I know - Close - Exit + Anuluj + Rozumiem + Zamknij + Wyjdź OK - Success - An Error occurred + Sukces + WystÄ…piÅ‚ błąd - BRouter Download Manager - *** Attention:*** - \n\nThe Download Manager is used to download routing-data - files which can be up to 170MB each. Do not start the Download Manager - on a cellular data connection without a data plan! - Download speed is restricted to 16 MBit/s. - Successfully prepared a timeout-free calculation - You successfully repeated a calculation that previously run into a timeout - when started from your map-tool. If you repeat the same request from your - maptool, with the exact same destination point and a close-by starting point, - this request is guaranteed not to time out. - Enter SDCARD base dir: - Select Action + Menedżer pobierania BRoutera + *** Uwaga: *** + \n\nMenedżer pobierania sÅ‚uży do pobierania plików danych routingu, + które mogÄ… mieć do 170 MB każdy. Nie uruchamiaj Menedżera pobierania, + jeżeli korzystasz z komórkowej transmisji danych bez planu taryfowego! + PrÄ™dkość pobierania jest ograniczona do 16 MBit/s. + PomyÅ›lnie przygotowano obliczenia nie przekraczajÄ…c limitu czasu + PomyÅ›lnie powtórzono obliczenia, które wczeÅ›niej przekroczyÅ‚y limit czasu + po uruchomieniu z narzÄ™dzia mapowego. JeÅ›li powtórzysz to samo żądanie ze swojego + narzÄ™dzia mapowego, z dokÅ‚adnie tym samym punktem docelowym i pobliskim punktem poczÄ…tkowym, + gwarantujÄ™, że to żądanie nie przekroczy limitu czasu. + Wybierz katalog SDCARD dla bazy: + Wybierz akcjÄ™ - Check VIA Selection: - Check NoGo Selection: - Server-Mode - Info - Calc Route - Profile Settings - Share GPX - Select from - Select to/via + Sprawdź wybór VIA: + Sprawdź wybór NoGo: + Tryb serwera + Informacje + Oblicz trasÄ™ + Ustawienia profilu + UdostÄ™pnij GPX + Wybierz z + Wybierz do/przez - no profile data - , no used profile - Too much data for download. Please reduce. - Download scheduled. Check internet connection if it doesn\'t start. - current waypoint selection:\n - Expecting waypoint selection\n - Alternative - version = BRouter-%1$s \n - mem = %2$s \n - distance = %3$s km\n - filtered ascend = %4$s m\n - plain ascend = %5$s m\n - estimated time = %6$s - Error reading waypoints - coordinate source does not contain any waypoints! - coordinate source contains too much waypoints: %1$d (please use from/to/via names) + brak danych profilu + , brak używanego profilu + Za dużo danych do pobrania. ProszÄ™ ogranicz. + Pobieranie zaplanowane. JeÅ›li siÄ™ nie rozpocznie, sprawdź połączenie internetowe. + bieżący wybór punktu trasy:\n + OczekujÄ™ na wybór punktu trasy\n + Alternatywa + wersja = BRouter-%1$s \n + pamięć = %2$s \n + odlegÅ‚ość = %3$s km\n + filtrowane wznoszenie = %4$sm\n + zwykÅ‚e wznoszenie = %5$sm\n + szacowany czas = %6$s + Błąd podczas odczytu punktów trasy + ŹródÅ‚o współrzÄ™dnych nie zawiera żadnych punktów trasy! + ŹródÅ‚o współrzÄ™dnych zawiera zbyt dużo punktów trasy: %1$d (proszÄ™ używać nazw z/do/przez) + + No + Yes + + Confirm Delete + Really delete? + Version Problem + The base version for tiles has changed. What to do? + Continue with current download, delete other old data + Select all for download and start + Cancel now, complete on an other day + Version Differences + The base version for some tiles is different. What to do? + Download all different tiles + Drop all different tiles + Cancel now, complete on an other day + The new data version needs a new app. Please update BRouter first + + Download failed + Download cancelled + Download succeeded diff --git a/brouter-routing-app/src/main/res/values/strings.xml b/brouter-routing-app/src/main/res/values/strings.xml index 3a08420..ad2e783 100644 --- a/brouter-routing-app/src/main/res/values/strings.xml +++ b/brouter-routing-app/src/main/res/values/strings.xml @@ -69,4 +69,26 @@ coordinate source does not contain any waypoints! coordinate source contains too much waypoints: %1$d (please use from/to/via names) + No + Yes + + Confirm Delete + Really delete? + Version Problem + The base version for tiles has changed. What to do? + Continue with current download, delete other old data + Select all for download and start + Cancel now, complete on an other day + Version Differences + The base version for some tiles is different. What to do? + Download all different tiles + Drop all different tiles + Cancel now, complete on an other day + The new data version needs a new app. Please update BRouter first + + Download failed + Download cancelled + Download succeeded + + From e918700bca1898947372006382b2408744e26718 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 14 Dec 2023 12:01:29 +0100 Subject: [PATCH 091/173] added new message and rename msg --- .../src/main/java/btools/routingapp/BInstallerActivity.java | 4 ++-- brouter-routing-app/src/main/res/values-ar/strings.xml | 3 ++- brouter-routing-app/src/main/res/values-ca/strings.xml | 3 ++- brouter-routing-app/src/main/res/values-de/strings.xml | 3 ++- brouter-routing-app/src/main/res/values-el/strings.xml | 3 ++- brouter-routing-app/src/main/res/values-es/strings.xml | 3 ++- brouter-routing-app/src/main/res/values-fr/strings.xml | 3 ++- brouter-routing-app/src/main/res/values-it/strings.xml | 3 ++- brouter-routing-app/src/main/res/values-nl/strings.xml | 3 ++- brouter-routing-app/src/main/res/values-pl/strings.xml | 3 ++- brouter-routing-app/src/main/res/values/strings.xml | 3 ++- 11 files changed, 22 insertions(+), 12 deletions(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java index 7ceac35..5d375d6 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java @@ -279,7 +279,7 @@ public class BInstallerActivity extends AppCompatActivity { String segmentName = progress.getString(DownloadWorker.PROGRESS_SEGMENT_NAME); int percent = progress.getInt(DownloadWorker.PROGRESS_SEGMENT_PERCENT, 0); if (percent > 0) { - mDownloadSummaryInfo.setText("Downloading .. " + segmentName); + mDownloadSummaryInfo.setText(getString(R.string.msg_download_started) + segmentName); } if (percent > 0) { mProgressIndicator.setIndeterminate(false); @@ -295,7 +295,7 @@ public class BInstallerActivity extends AppCompatActivity { String result; switch (workInfo.getState()) { case FAILED: - result = getString(R.string.msg_download_faild); + result = getString(R.string.msg_download_failed); break; case CANCELLED: result = getString(R.string.msg_download_cancel); diff --git a/brouter-routing-app/src/main/res/values-ar/strings.xml b/brouter-routing-app/src/main/res/values-ar/strings.xml index 74679da..b447e67 100644 --- a/brouter-routing-app/src/main/res/values-ar/strings.xml +++ b/brouter-routing-app/src/main/res/values-ar/strings.xml @@ -86,8 +86,9 @@ Cancel now, complete on an other day The new data version needs a new app. Please update BRouter first - Download failed + Download failed Download cancelled Download succeeded + "Downloading .. " diff --git a/brouter-routing-app/src/main/res/values-ca/strings.xml b/brouter-routing-app/src/main/res/values-ca/strings.xml index 3ebbf3e..0272aba 100644 --- a/brouter-routing-app/src/main/res/values-ca/strings.xml +++ b/brouter-routing-app/src/main/res/values-ca/strings.xml @@ -85,8 +85,9 @@ Cancel now, complete on an other day The new data version needs a new app. Please update BRouter first - Download failed + Download failed Download cancelled Download succeeded + "Downloading .. " diff --git a/brouter-routing-app/src/main/res/values-de/strings.xml b/brouter-routing-app/src/main/res/values-de/strings.xml index 9a7bed9..1c7e0b9 100644 --- a/brouter-routing-app/src/main/res/values-de/strings.xml +++ b/brouter-routing-app/src/main/res/values-de/strings.xml @@ -86,8 +86,9 @@ Cancel now, complete on an other day The new data version needs a new app. Please update BRouter first - Download failed + Download failed Download cancelled Download succeeded + "Downloading .. " diff --git a/brouter-routing-app/src/main/res/values-el/strings.xml b/brouter-routing-app/src/main/res/values-el/strings.xml index 742b9a3..a1f2c68 100644 --- a/brouter-routing-app/src/main/res/values-el/strings.xml +++ b/brouter-routing-app/src/main/res/values-el/strings.xml @@ -86,8 +86,9 @@ Cancel now, complete on an other day The new data version needs a new app. Please update BRouter first - Download failed + Download failed Download cancelled Download succeeded + "Downloading .. " diff --git a/brouter-routing-app/src/main/res/values-es/strings.xml b/brouter-routing-app/src/main/res/values-es/strings.xml index 6570697..7dc62e7 100644 --- a/brouter-routing-app/src/main/res/values-es/strings.xml +++ b/brouter-routing-app/src/main/res/values-es/strings.xml @@ -85,8 +85,9 @@ Cancel now, complete on an other day The new data version needs a new app. Please update BRouter first - Download failed + Download failed Download cancelled Download succeeded + "Downloading .. " diff --git a/brouter-routing-app/src/main/res/values-fr/strings.xml b/brouter-routing-app/src/main/res/values-fr/strings.xml index be54330..c04ed1b 100644 --- a/brouter-routing-app/src/main/res/values-fr/strings.xml +++ b/brouter-routing-app/src/main/res/values-fr/strings.xml @@ -86,8 +86,9 @@ Cancel now, complete on an other day The new data version needs a new app. Please update BRouter first - Download failed + Download failed Download cancelled Download succeeded + "Downloading .. " diff --git a/brouter-routing-app/src/main/res/values-it/strings.xml b/brouter-routing-app/src/main/res/values-it/strings.xml index 298978b..a459048 100644 --- a/brouter-routing-app/src/main/res/values-it/strings.xml +++ b/brouter-routing-app/src/main/res/values-it/strings.xml @@ -86,8 +86,9 @@ Cancel now, complete on an other day The new data version needs a new app. Please update BRouter first - Download failed + Download failed Download cancelled Download succeeded + "Downloading .. " diff --git a/brouter-routing-app/src/main/res/values-nl/strings.xml b/brouter-routing-app/src/main/res/values-nl/strings.xml index 7d05ca9..a1806b1 100644 --- a/brouter-routing-app/src/main/res/values-nl/strings.xml +++ b/brouter-routing-app/src/main/res/values-nl/strings.xml @@ -80,8 +80,9 @@ Cancel now, complete on an other day The new data version needs a new app. Please update BRouter first - Download failed + Download failed Download cancelled Download succeeded + "Downloading .. " diff --git a/brouter-routing-app/src/main/res/values-pl/strings.xml b/brouter-routing-app/src/main/res/values-pl/strings.xml index 6bca420..bf0c8be 100644 --- a/brouter-routing-app/src/main/res/values-pl/strings.xml +++ b/brouter-routing-app/src/main/res/values-pl/strings.xml @@ -86,8 +86,9 @@ Cancel now, complete on an other day The new data version needs a new app. Please update BRouter first - Download failed + Download failed Download cancelled Download succeeded + "Downloading .. " diff --git a/brouter-routing-app/src/main/res/values/strings.xml b/brouter-routing-app/src/main/res/values/strings.xml index ad2e783..74e54d0 100644 --- a/brouter-routing-app/src/main/res/values/strings.xml +++ b/brouter-routing-app/src/main/res/values/strings.xml @@ -86,9 +86,10 @@ Cancel now, complete on an other day The new data version needs a new app. Please update BRouter first - Download failed + Download failed Download cancelled Download succeeded + "Downloading .. " From de70dec44a5ca60c4ed52946a340a49f32153991 Mon Sep 17 00:00:00 2001 From: Emux Date: Thu, 14 Dec 2023 18:46:25 +0200 Subject: [PATCH 092/173] Fix some Lint issues --- .../src/main/java/btools/mapaccess/WaypointMatcherImpl.java | 2 +- .../src/main/java/btools/routingapp/BRouterService.java | 1 + .../src/main/java/btools/routingapp/BRouterWorker.java | 1 + brouter-util/src/main/java/btools/util/CompactLongMap.java | 2 ++ brouter-util/src/main/java/btools/util/SortedHeap.java | 1 + 5 files changed, 6 insertions(+), 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 e70d3be..85ab9b5 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/WaypointMatcherImpl.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/WaypointMatcherImpl.java @@ -51,7 +51,7 @@ public final class WaypointMatcherImpl implements WaypointMatcher { } // sort result list - comparator = new Comparator<>() { + comparator = new Comparator() { @Override public int compare(MatchedWaypoint mw1, MatchedWaypoint mw2) { int cmpDist = Double.compare(mw1.radius, mw2.radius); diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java index 90b875c..1bb72af 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java @@ -271,6 +271,7 @@ public class BRouterService extends Service { } } + @SuppressWarnings("deprecation") private void logBundle(Bundle params) { if (AppLogger.isLogging()) { for (String k : params.keySet()) { 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 64e73d5..f7a74ff 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java @@ -36,6 +36,7 @@ public class BRouterWorker { public List nogoPolygonsList; public String profileParams; + @SuppressWarnings("deprecation") public String getTrackFromParams(Bundle params) { int engineMode = 0; diff --git a/brouter-util/src/main/java/btools/util/CompactLongMap.java b/brouter-util/src/main/java/btools/util/CompactLongMap.java index b29d83a..49efc08 100644 --- a/brouter-util/src/main/java/btools/util/CompactLongMap.java +++ b/brouter-util/src/main/java/btools/util/CompactLongMap.java @@ -224,6 +224,7 @@ public class CompactLongMap { // does sorted array "a" contain "id" ? + @SuppressWarnings("unchecked") private boolean contains(int idx, long id, boolean doPut) { long[] a = al[idx]; int offset = a.length; @@ -243,6 +244,7 @@ public class CompactLongMap { return false; } + @SuppressWarnings("unchecked") protected void moveToFrozenArrays(long[] faid, List flv) { for (int i = 1; i < MAXLISTS; i++) { pa[i] = 0; diff --git a/brouter-util/src/main/java/btools/util/SortedHeap.java b/brouter-util/src/main/java/btools/util/SortedHeap.java index 3f2c4f1..b350d63 100644 --- a/brouter-util/src/main/java/btools/util/SortedHeap.java +++ b/brouter-util/src/main/java/btools/util/SortedHeap.java @@ -19,6 +19,7 @@ public final class SortedHeap { /** * @return the lowest key value, or null if none */ + @SuppressWarnings("unchecked") public V popLowestKeyValue() { SortedBin bin = firstNonEmpty; if (firstNonEmpty == null) { From a70c95c57656e6e48f4af6a137a48b79ae1fbea2 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 14 Dec 2023 17:50:14 +0100 Subject: [PATCH 093/173] added translation for new messages and fixes --- .../src/main/res/values-ar/strings.xml | 38 +++++++++--------- .../src/main/res/values-ca/strings.xml | 36 ++++++++--------- .../src/main/res/values-de/strings.xml | 40 +++++++++---------- .../src/main/res/values-el/strings.xml | 38 +++++++++--------- .../src/main/res/values-es/strings.xml | 36 ++++++++--------- .../src/main/res/values-fr/strings.xml | 38 +++++++++--------- .../src/main/res/values-it/strings.xml | 36 ++++++++--------- .../src/main/res/values-nl/strings.xml | 38 +++++++++--------- .../src/main/res/values-pl/strings.xml | 38 +++++++++--------- .../src/main/res/values/strings.xml | 6 +-- 10 files changed, 172 insertions(+), 172 deletions(-) diff --git a/brouter-routing-app/src/main/res/values-ar/strings.xml b/brouter-routing-app/src/main/res/values-ar/strings.xml index b447e67..8759f4a 100644 --- a/brouter-routing-app/src/main/res/values-ar/strings.xml +++ b/brouter-routing-app/src/main/res/values-ar/strings.xml @@ -69,26 +69,26 @@ لا يحتوي مصدر الإحداثيات على أي نقاط الطريق! مصدر الإحداثيات يحتوي على عدد كبير جدًا من نقاط الطريق: %1$d (يرجى استخدام أسماء النقاط من/إلى/عبر from/to/via) - No - Yes + لا + نعم - Confirm Delete - Really delete? - Version Problem - The base version for tiles has changed. What to do? - Continue with current download, delete other old data - Select all for download and start - Cancel now, complete on an other day - Version Differences - The base version for some tiles is different. What to do? - Download all different tiles - Drop all different tiles - Cancel now, complete on an other day - The new data version needs a new app. Please update BRouter first + تأكيد الحذ٠+ هل ترغب Ø¨Ø§Ù„Ø­Ø°ÙØŸ + مشكلة ÙÙŠ الإصدار + لقد تغير الإصدار الأساسي للصور المتجانبة، ما الإجراء المناسب Ù„ÙØ¹Ù„ه؟ + متابعة التنزيل الحالي، وحذ٠البيانات الأخرى القديمة + تحديد الكل للتنزيل والبدء + قم بالإلغاء ÙÙŠ الوقت الحالي، واستكمله لاحقاً + Ø§Ø®ØªÙ„Ø§ÙØ§Øª الإصدارات + الإصدار الأساسي لبعض الصور المتجانبة مختلÙ. ما الإجراء المناسب Ù„ÙØ¹Ù„ه؟ + تحميل المختل٠من الصور المتجانبة + حذ٠كل الصور المتجانبة Ø§Ù„Ù…Ø®ØªÙ„ÙØ© عن الأخرى + قم بالإلغاء ÙÙŠ الوقت الحالي، واستكمله لاحقاً + يحتاج إصدار البيانات الجديد إلى وجود التطبيق الجديد، لذا نرجوا منك تحديث BRouter أولاً - Download failed - Download cancelled - Download succeeded - "Downloading .. " + ÙØ´Ù„ التنزيل + تم إلغاء التنزيل + تم التنزيل بنجاح + جار التنزيل… diff --git a/brouter-routing-app/src/main/res/values-ca/strings.xml b/brouter-routing-app/src/main/res/values-ca/strings.xml index 0272aba..6f2e8cf 100644 --- a/brouter-routing-app/src/main/res/values-ca/strings.xml +++ b/brouter-routing-app/src/main/res/values-ca/strings.xml @@ -69,25 +69,25 @@ la font de coordenades conté massa fites: %1$d (si us plau, fes servir noms des de/a/via) No - Yes + Si - Confirm Delete - Really delete? - Version Problem - The base version for tiles has changed. What to do? - Continue with current download, delete other old data - Select all for download and start - Cancel now, complete on an other day - Version Differences - The base version for some tiles is different. What to do? - Download all different tiles - Drop all different tiles - Cancel now, complete on an other day - The new data version needs a new app. Please update BRouter first + Confirmar Eliminar + Voleu realment eliminar? + Problema de versió + La versió base per les tesel·les ha canviat. Què vols fer? + Continuar amb la baixada, eliminar altres dades antigues + Seleccionar-ho tot per baixar i iniciar + Cancel·lar ara, completar un altre dia + Diferències de Versió + La versió base de diverses tesel·les és diferent. Què vols fer? + Baixar totes les tesel·les diferents + Descartar les tesel·les diferents + Cancel·lar ara, completar un altre dia + La nova versió de dades necessita una aplicació nova. Sisplau, actualitza BRouter primer - Download failed - Download cancelled - Download succeeded - "Downloading .. " + Baixada fallida + Baixada cancel·lada + Baixada exitosa + Baixant… diff --git a/brouter-routing-app/src/main/res/values-de/strings.xml b/brouter-routing-app/src/main/res/values-de/strings.xml index 1c7e0b9..902aa4d 100644 --- a/brouter-routing-app/src/main/res/values-de/strings.xml +++ b/brouter-routing-app/src/main/res/values-de/strings.xml @@ -63,32 +63,32 @@ Speicher = %2$s \n Abstand = %3$s km\n gefilterter Aufstieg = %4$s m\n - schlichter Aufstieg = %5$s m\n + flacher Aufstieg = %5$s m\n geschätzte Zeit = %6$s Fehler beim Lesen von Wegpunkten Die Koordinatenquelle enthält keine Wegpunkte! Die Koordinatenquelle enthält zu viele Wegpunkte: %1$d (bitte von/nach/via-Namen verwenden) - No - Yes + Nein + Ja - Confirm Delete - Really delete? - Version Problem - The base version for tiles has changed. What to do? - Continue with current download, delete other old data - Select all for download and start - Cancel now, complete on an other day - Version Differences - The base version for some tiles is different. What to do? - Download all different tiles - Drop all different tiles - Cancel now, complete on an other day - The new data version needs a new app. Please update BRouter first + Löschen bestätigen + Wirklich löschen? + Versionsproblem + Die Datenversion der Kacheln hat sich geändert. Bitte auswählen: + Download fortsetzen und alte Daten löschen? + Alle auswählen und Download starten + Abbrechen und später fortsetzen + Versionskonflikt + Die Datenversion für einige Kacheln ist unterschiedlich. Bitte auswählen: + Download alle unterschiedlichen Kacheln + Überspringe alle unterschiedlichen Kacheln + Abbrechen und später fortsetzen + Die neue Datenversion verlangt ein BRouter-Update. Bitte aktualisieren - Download failed - Download cancelled - Download succeeded - "Downloading .. " + Download fehlgeschlagen + Download abgebrochen + Download erfolgreich + Download läuft… diff --git a/brouter-routing-app/src/main/res/values-el/strings.xml b/brouter-routing-app/src/main/res/values-el/strings.xml index a1f2c68..4f2578e 100644 --- a/brouter-routing-app/src/main/res/values-el/strings.xml +++ b/brouter-routing-app/src/main/res/values-el/strings.xml @@ -69,26 +69,26 @@ η πηγή συντεταγμένων δεν πεÏιέχει σημεία! η πηγή συντεταγμένων πεÏιέχει πάÏα πολλά σημεία: %1$d (παÏακαλώ χÏησιμοποιήστε από/Ï€Ïος/μέσω ονόματα) - No - Yes + Όχι + Îαι - Confirm Delete - Really delete? - Version Problem - The base version for tiles has changed. What to do? - Continue with current download, delete other old data - Select all for download and start - Cancel now, complete on an other day - Version Differences - The base version for some tiles is different. What to do? - Download all different tiles - Drop all different tiles - Cancel now, complete on an other day - The new data version needs a new app. Please update BRouter first + Επιβεβαίωση διαγÏαφής + Îα γίνει διαγÏαφή; + ΠÏόβλημα έκδοσης + Η βασική έκδοση για τμήματα άλλαξε. Τι να κάνετε; + Συνέχεια με την Ï„Ïέχουσα λήψη, διαγÏαφή άλλων παλαιών δεδομένων + Επιλέξτε όλα για λήψη και έναÏξη + ΑκÏÏωση τώÏα, ολοκλήÏωση άλλη μέÏα + ΔιαφοÏές έκδοσης + Η βασική έκδοση για οÏισμένα τμήματα είναι διαφοÏετική. Τι να κάνετε; + Λήψη όλων των διαφοÏετικών τμημάτων + ΑπόÏÏιψη όλων των διαφοÏετικών τμημάτων + ΑκÏÏωση τώÏα, ολοκλήÏωση άλλη μέÏα + Η νέα έκδοση δεδομένων χÏειάζεται νέα εφαÏμογή. ΕνημεÏώστε Ï€Ïώτα το BRouter - Download failed - Download cancelled - Download succeeded - "Downloading .. " + Η λήψη απέτυχε + Η λήψη ακυÏώθηκε + Η λήψη ολοκληÏώθηκε + Λήψη… diff --git a/brouter-routing-app/src/main/res/values-es/strings.xml b/brouter-routing-app/src/main/res/values-es/strings.xml index 7dc62e7..9d2413a 100644 --- a/brouter-routing-app/src/main/res/values-es/strings.xml +++ b/brouter-routing-app/src/main/res/values-es/strings.xml @@ -69,25 +69,25 @@ la fuente de coordenadas contiene demasiados waypoints: %1$d (por favor, usa nombres desde/a/vía) No - Yes + Si - Confirm Delete - Really delete? - Version Problem - The base version for tiles has changed. What to do? - Continue with current download, delete other old data - Select all for download and start - Cancel now, complete on an other day - Version Differences - The base version for some tiles is different. What to do? - Download all different tiles - Drop all different tiles - Cancel now, complete on an other day - The new data version needs a new app. Please update BRouter first + Confirmar Eliminar + ¿Quieres realmente eliminar? + Problema de versión + La versión base para as teselas ha cambiado. ¿Qué quieres hacer? + Continuar con la descarga, eliminar otros datos antiguos + Seleccionar todo para descargar e iniciar + Cancelar ahora, completar otro día + Diferencias de versión + La versión base de varias teselas es distinta. ¿Qué quieres hacer? + Descargar todas las teselas distintas + Descartar las teselas distintas + Cancelar ahora, completar otro día + La nueva versión de datos necesita un aplicación nueva. Por favor, actualiza BRouter primero - Download failed - Download cancelled - Download succeeded - "Downloading .. " + Descarga fallida + Descarga cancelada + Descarga exitosa + Descargando… diff --git a/brouter-routing-app/src/main/res/values-fr/strings.xml b/brouter-routing-app/src/main/res/values-fr/strings.xml index c04ed1b..0b028f7 100644 --- a/brouter-routing-app/src/main/res/values-fr/strings.xml +++ b/brouter-routing-app/src/main/res/values-fr/strings.xml @@ -69,26 +69,26 @@ la source de coordonnées ne contient aucun waypoint! la source de coordonnées contient trop de waypoints: %1$d (veuillez utiliser les noms de/vers/via) - No - Yes + Non + Oui - Confirm Delete - Really delete? - Version Problem - The base version for tiles has changed. What to do? - Continue with current download, delete other old data - Select all for download and start - Cancel now, complete on an other day - Version Differences - The base version for some tiles is different. What to do? - Download all different tiles - Drop all different tiles - Cancel now, complete on an other day - The new data version needs a new app. Please update BRouter first + Confirmer la suppression + Vraiment supprimer? + Problème de version + La version de base des vignettes a changé. Que faire? + Continuer le téléchargement en cours, supprimer les autres anciennes données + Sélectionnez tout pour télécharger et démarrer + Annuler maintenant, terminer un autre jour + Différences de version + La version de base de certaines vignettes est différente. Que faire? + Télécharger toutes les différentes vignettes + Supprimez toutes les différentes vignettes + Annuler maintenant, terminer un autre jour + La nouvelle version des données nécessite une nouvelle application. Veuillez d\'abord mettre à jour BRouter - Download failed - Download cancelled - Download succeeded - "Downloading .. " + Téléchargement échoué + Téléchargement annulé + Téléchargement réussi + Téléchargement… diff --git a/brouter-routing-app/src/main/res/values-it/strings.xml b/brouter-routing-app/src/main/res/values-it/strings.xml index a459048..c82a6e2 100644 --- a/brouter-routing-app/src/main/res/values-it/strings.xml +++ b/brouter-routing-app/src/main/res/values-it/strings.xml @@ -70,25 +70,25 @@ la fonte delle coordinate contiene troppi waypoint: %1$d (usa i nomi da/a/via) No - Yes + Sì - Confirm Delete - Really delete? - Version Problem - The base version for tiles has changed. What to do? - Continue with current download, delete other old data - Select all for download and start - Cancel now, complete on an other day - Version Differences - The base version for some tiles is different. What to do? - Download all different tiles - Drop all different tiles - Cancel now, complete on an other day - The new data version needs a new app. Please update BRouter first + Conferma eliminazione + Eliminare davvero? + Problema di versione + La versione base per i riquadri è cambiata. Cosa fare? + Continua con il download corrente, elimina altri vecchi dati + Seleziona tutto per il download e avvia + Annulla ora, completa un altro giorno + Differenze di versione + La versione base di alcuni riquadri è diversa. Cosa fare? + Scarica tutti i diversi riquadri + Rilascia tutti i diversi riquadri + Annulla ora, completa un altro giorno + La nuova versione dei dati necessita di una nuova app. Per favore aggiorna prima BRouter - Download failed - Download cancelled - Download succeeded - "Downloading .. " + Download fallito + Download annullato + Download riuscito + Download in corso… diff --git a/brouter-routing-app/src/main/res/values-nl/strings.xml b/brouter-routing-app/src/main/res/values-nl/strings.xml index a1806b1..fb8d201 100644 --- a/brouter-routing-app/src/main/res/values-nl/strings.xml +++ b/brouter-routing-app/src/main/res/values-nl/strings.xml @@ -63,26 +63,26 @@ coördinaten bron bevat geen waypoints! De coördinatenbron bevat te veel waypoints: %1$d (gebruik van/naar/via namen) - No - Yes + Nee + Ja - Confirm Delete - Really delete? - Version Problem - The base version for tiles has changed. What to do? - Continue with current download, delete other old data - Select all for download and start - Cancel now, complete on an other day - Version Differences - The base version for some tiles is different. What to do? - Download all different tiles - Drop all different tiles - Cancel now, complete on an other day - The new data version needs a new app. Please update BRouter first + Verwijderen bevestigen + Echt verwijderen? + Versie probleem + De basisversie voor de tegels is gewijzigd. Wat moet er gebeuren? + Doorgaan met de huidige download, verwijder de oude gegevens. + Selecteer alles om te downloaden en start + Annuleer nu, voltooi op een andere dag + Versieverschillen + De basisversie voor sommige tiles is anders. Wat moet er gebeuren? + Download alle verschillende tegels + Laat alle verschillende tegels vallen + Annuleer nu, en voltooi op een andere dag + Voor de nieuwe gegevensversie is een nieuwe app nodig. Update eerst de BRouter - Download failed - Download cancelled - Download succeeded - "Downloading .. " + Download is mislukt + Download is geannuleerd + Download is geslaagd + Downloaden… diff --git a/brouter-routing-app/src/main/res/values-pl/strings.xml b/brouter-routing-app/src/main/res/values-pl/strings.xml index bf0c8be..6901240 100644 --- a/brouter-routing-app/src/main/res/values-pl/strings.xml +++ b/brouter-routing-app/src/main/res/values-pl/strings.xml @@ -69,26 +69,26 @@ ŹródÅ‚o współrzÄ™dnych nie zawiera żadnych punktów trasy! ŹródÅ‚o współrzÄ™dnych zawiera zbyt dużo punktów trasy: %1$d (proszÄ™ używać nazw z/do/przez) - No - Yes + Nie + Tak - Confirm Delete - Really delete? - Version Problem - The base version for tiles has changed. What to do? - Continue with current download, delete other old data - Select all for download and start - Cancel now, complete on an other day - Version Differences - The base version for some tiles is different. What to do? - Download all different tiles - Drop all different tiles - Cancel now, complete on an other day - The new data version needs a new app. Please update BRouter first + Potwierdź usuniÄ™cie + NaprawdÄ™ usunąć? + Problem z wersjÄ… + ZmieniÅ‚a siÄ™ podstawowa wersja kafelków. Co robić? + Kontynuuj bieżące pobieranie, usuÅ„ inne stare dane + Wybierz wszystko i rozpocznij pobieranie + Anuluj teraz, dokoÅ„cz innego dnia + Różnice wersji + Wersja podstawowa dla niektórych kafelków jest inna. Co robić? + Pobierz różne kafelki + Opuść wszystkie różne kafelki + Anuluj teraz, dokoÅ„cz innego dnia + Nowa wersja danych wymaga nowej aplikacji. Najpierw zaktualizuj BRoutera - Download failed - Download cancelled - Download succeeded - "Downloading .. " + Pobieranie nie powiodÅ‚o siÄ™ + Pobieranie anulowane + Pobieranie powiodÅ‚o siÄ™ + Pobieranie… diff --git a/brouter-routing-app/src/main/res/values/strings.xml b/brouter-routing-app/src/main/res/values/strings.xml index 74e54d0..166323d 100644 --- a/brouter-routing-app/src/main/res/values/strings.xml +++ b/brouter-routing-app/src/main/res/values/strings.xml @@ -78,18 +78,18 @@ The base version for tiles has changed. What to do? Continue with current download, delete other old data Select all for download and start - Cancel now, complete on an other day + Cancel now, complete on another day Version Differences The base version for some tiles is different. What to do? Download all different tiles Drop all different tiles - Cancel now, complete on an other day + Cancel now, complete on another day The new data version needs a new app. Please update BRouter first Download failed Download cancelled Download succeeded - "Downloading .. " + Downloading… From 77014ffddbef98913651f7be0e0a243f348876b0 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 14 Dec 2023 19:59:12 +0100 Subject: [PATCH 094/173] remove unused text --- brouter-routing-app/src/main/res/values-el/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brouter-routing-app/src/main/res/values-el/strings.xml b/brouter-routing-app/src/main/res/values-el/strings.xml index 4f2578e..f5d098b 100644 --- a/brouter-routing-app/src/main/res/values-el/strings.xml +++ b/brouter-routing-app/src/main/res/values-el/strings.xml @@ -1,7 +1,7 @@ - %d τμήμαcompare cl + %d τμήμα %d τμήματα ΑκÏÏωση λήψης From 158dc5e54d3e94d0693730e960556b2cc245fa83 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 15 Dec 2023 14:01:13 +0100 Subject: [PATCH 095/173] do only compress gpx/json --- .../src/main/java/btools/routingapp/BRouterService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java index 90b875c..3e4f55e 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java @@ -100,7 +100,7 @@ public class BRouterService extends Service { boolean canCompress = "true".equals(params.getString("acceptCompressedResult")); try { String gpxMessage = worker.getTrackFromParams(params); - if (canCompress) { + if (canCompress && (gpxMessage.startsWith("<") || gpxMessage.startsWith("{"))) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write("z64".getBytes(Charset.forName("UTF-8"))); // marker prefix From 9ef31e6d2c76663f0901ec724b8f9d09581de17b Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 15 Dec 2023 14:10:20 +0100 Subject: [PATCH 096/173] remove profile param when handled in app --- .../src/main/java/btools/routingapp/BRouterService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java index 3e4f55e..1f16947 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java @@ -96,6 +96,8 @@ public class BRouterService extends Service { if (errMsg != null) { return errMsg; } + // profile is already done + params.remove("profile"); boolean canCompress = "true".equals(params.getString("acceptCompressedResult")); try { From ec3461d8a237e8c3cddb8dc27d4a312fe1daccf4 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 15 Dec 2023 14:25:03 +0100 Subject: [PATCH 097/173] changed handle back pressed logic --- .../routingapp/RoutingParameterDialog.java | 70 +++++++++++-------- 1 file changed, 41 insertions(+), 29 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 8eb4365..356e2d3 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java @@ -10,6 +10,7 @@ import android.os.Bundle; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; +import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -168,41 +169,52 @@ public class RoutingParameterDialog extends AppCompatActivity { new OnBackInvokedCallback() { @Override public void onBackInvoked() { - StringBuilder sb = null; - if (sharedValues != null) { - // fill preference with used params - // for direct use in the BRouter interface "extraParams" - sb = new StringBuilder(); - for (Map.Entry entry : sharedValues.getAll().entrySet()) { - if (!entry.getKey().equals("params")) { - sb.append(sb.length() > 0 ? "&" : "") - .append(entry.getKey()) - .append("="); - String s = entry.getValue().toString(); - if (s.equals("true")) s = "1"; - else if (s.equals("false")) s = "0"; - sb.append(s); - } - } - } - // and return the array - // one should be enough - Intent i = new Intent(); - // i.putExtra("PARAMS", listParams); - i.putExtra("PROFILE", profile); - i.putExtra("PROFILE_HASH", profile_hash); - if (sb != null) i.putExtra("PARAMS_VALUES", sb.toString()); - - setResult(Activity.RESULT_OK, i); - finish(); + handleBackPressed(); } } ); - - + } else { + OnBackPressedCallback callback = new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + handleBackPressed(); + } + }; + getOnBackPressedDispatcher().addCallback(this, callback); } } + private void handleBackPressed() { + StringBuilder sb = null; + if (sharedValues != null) { + // fill preference with used params + // for direct use in the BRouter interface "extraParams" + sb = new StringBuilder(); + for (Map.Entry entry : sharedValues.getAll().entrySet()) { + if (!entry.getKey().equals("params")) { + sb.append(sb.length() > 0 ? "&" : "") + .append(entry.getKey()) + .append("="); + String s = entry.getValue().toString(); + if (s.equals("true")) s = "1"; + else if (s.equals("false")) s = "0"; + sb.append(s); + } + } + } + // and return the array + // one should be enough + Intent i = new Intent(); + // i.putExtra("PARAMS", listParams); + i.putExtra("PROFILE", profile); + i.putExtra("PROFILE_HASH", profile_hash); + if (sb != null) i.putExtra("PARAMS_VALUES", sb.toString()); + + setResult(Activity.RESULT_OK, i); + finish(); + + } + @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); From 12309f298cafd8878e731553318c671c6a1cda71 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 19 Dec 2023 17:25:02 +0100 Subject: [PATCH 098/173] update for some translations --- brouter-routing-app/src/main/res/values-ca/strings.xml | 5 +++-- brouter-routing-app/src/main/res/values-es/strings.xml | 5 +++-- brouter-routing-app/src/main/res/values-nl/strings.xml | 10 ++++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/brouter-routing-app/src/main/res/values-ca/strings.xml b/brouter-routing-app/src/main/res/values-ca/strings.xml index 6f2e8cf..9df7107 100644 --- a/brouter-routing-app/src/main/res/values-ca/strings.xml +++ b/brouter-routing-app/src/main/res/values-ca/strings.xml @@ -35,8 +35,9 @@ des d\'un mòbil sense un pla de dades! La velocitat de baixada està limitada a 16 Mbit/s. S\'ha preparat correctament un càlcul sense límit de temps - S\'ha repetit amb èxit un càlcul a través de l\'aplicació de cartografia que prèviament havia - superat el temps d\'espera. Si repeteixes la mateixa petició amb exactament el mateix destí i un punt de partida proper, + S\'ha repetit amb èxit un càlcul a través + de l\'aplicació de cartografia que prèviament havia superat el temps d\'espera. + Si repeteixes la mateixa petició amb exactament el mateix destí i un punt de partida proper, es garanteix que la petició no esgotarà el límit de temps. Introdueix el directori base de la tarja SD: Seleccionar Acció diff --git a/brouter-routing-app/src/main/res/values-es/strings.xml b/brouter-routing-app/src/main/res/values-es/strings.xml index 9d2413a..37094da 100644 --- a/brouter-routing-app/src/main/res/values-es/strings.xml +++ b/brouter-routing-app/src/main/res/values-es/strings.xml @@ -35,8 +35,9 @@ desde un terminal sin plan de datos! La velocidad de descarga está limitada a 16 Mbit/s. Se ha preparado correctamente un cálculo sin límite de tiempo - Se ha repetido un cálculo a través de la aplicación de mapas que previamente - había superado el tiempo de espera. Si repites la misma petición con exactamente el mismo destino y un punto de partida cercano, + Se ha repetido un cálculo a través + de la aplicación de mapas que previamente había superado el tiempo de espera. + Si repites la misma petición con exactamente el mismo destino y un punto de partida cercano, se garantiza que la petición no agotará el límite de tiempo. Introduce el directorio base de la tarjeta SD: Seleccionar Acción diff --git a/brouter-routing-app/src/main/res/values-nl/strings.xml b/brouter-routing-app/src/main/res/values-nl/strings.xml index fb8d201..fea694a 100644 --- a/brouter-routing-app/src/main/res/values-nl/strings.xml +++ b/brouter-routing-app/src/main/res/values-nl/strings.xml @@ -30,9 +30,15 @@ BRouter Download Manager *** OPgelet:*** - \n\nDe Download Manager wordt gebruikt voor het downloaden van routing-gegevens bestanden die elk maximaal 170 MB groot kunnen zijn. Start de Download Manager niet op een mobiele dataverbinding zonder data-abonnement! De downloadsnelheid is beperkt tot 16 MBit/s. + \n\nDe Download Manager wordt gebruikt voor het downloaden van routing-gegevens + bestanden die elk maximaal 170 MB groot kunnen zijn. Start de Download Manager + niet op een mobiele dataverbinding zonder data-abonnement! + De downloadsnelheid is beperkt tot 16 MBit/s. Een time-outvrije berekening is succesvol voorbereid - Je hebt met succes een berekening herhaald die eerder op een time-out stuitte toen deze vanuit je map-tool werd gestart. Als u hetzelfde verzoek herhaalt vanuit uw maptool, met exact hetzelfde bestemmingspunt en een dichtbijgelegen beginpunt zal deze aanvraag gegarandeerd geen time-out krijgen. + Je hebt met succes een berekening herhaald die eerder op een time-out + stuitte toen deze vanuit je map tool werd gestart. Als u hetzelfde verzoek herhaalt + vanuit uw map tool, met exact hetzelfde bestemmingspunt en een dichtbijgelegen beginpunt, + zal deze aanvraag gegarandeerd geen time-out krijgen. De SDCARD basis directory invoeren: Actie selecteren From 29673062b5a513696a6dee59a22af382a0df9b9d Mon Sep 17 00:00:00 2001 From: Emux Date: Thu, 21 Dec 2023 20:32:44 +0200 Subject: [PATCH 099/173] Fix Canvas.drawRect on older Android --- .../src/main/java/btools/routingapp/BInstallerView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java index 4eaddac..8fb8949 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java @@ -175,7 +175,7 @@ public class BInstallerView extends View { int tidx = gridPos2Tileindex(ix, iy); int tilesize = BInstallerSizes.getRd5Size(tidx); if (tilesize > 0) { - canvas.drawRect(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, paintGrid); + canvas.drawRect(fw * ix, fh * iy, fw * (ix + 1), fh * (iy + 1), paintGrid); } } } @@ -214,7 +214,7 @@ public class BInstallerView extends View { canvas.drawLine(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, pnt); // draw frame - canvas.drawRect(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, pnt); + canvas.drawRect(fw * ix, fh * iy, fw * (ix + 1), fh * (iy + 1), pnt); } } } From f405b0e16ea4b036c224e1f08cb4fcfdc76801cb Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 22 Dec 2023 17:51:04 +0100 Subject: [PATCH 100/173] switch to numeric transport mode --- .../main/java/btools/router/VoiceHint.java | 2 +- .../java/btools/router/VoiceHintList.java | 29 ++++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/VoiceHint.java b/brouter-core/src/main/java/btools/router/VoiceHint.java index df39933..3d0b425 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHint.java +++ b/brouter-core/src/main/java/btools/router/VoiceHint.java @@ -44,7 +44,7 @@ public class VoiceHint { float angle = Float.MAX_VALUE; boolean turnAngleConsumed; boolean needsRealTurn; - int maxBadPrio; + int maxBadPrio = -1; int roundaboutExit; diff --git a/brouter-core/src/main/java/btools/router/VoiceHintList.java b/brouter-core/src/main/java/btools/router/VoiceHintList.java index 229f4ab..215d2f7 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHintList.java +++ b/brouter-core/src/main/java/btools/router/VoiceHintList.java @@ -10,23 +10,44 @@ import java.util.ArrayList; import java.util.List; public class VoiceHintList { - private String transportMode; + + static final int TRANS_MODE_NONE = 0; + static final int TRANS_MODE_FOOT = 1; + static final int TRANS_MODE_BIKE = 2; + static final int TRANS_MODE_CAR = 3; + + private int transportMode = TRANS_MODE_BIKE; int turnInstructionMode; List list = new ArrayList<>(); public void setTransportMode(boolean isCar, boolean isBike) { - transportMode = isCar ? "car" : (isBike ? "bike" : "foot"); + transportMode = isCar ? TRANS_MODE_CAR : (isBike ? TRANS_MODE_BIKE : TRANS_MODE_FOOT); + } + + public void setTransportMode(int mode) { + transportMode = mode; } public String getTransportMode() { + String ret; + switch(transportMode) { + case TRANS_MODE_FOOT: ret = "foot"; break; + case TRANS_MODE_CAR : ret = "car"; break; + case TRANS_MODE_BIKE: + default: ret = "bike"; + } + return ret; + } + + public int transportMode() { return transportMode; } public int getLocusRouteType() { - if ("car".equals(transportMode)) { + if (transportMode == TRANS_MODE_CAR) { return 0; } - if ("bike".equals(transportMode)) { + if (transportMode == TRANS_MODE_BIKE) { return 5; } return 3; // foot From 0f8bdfee6cbdc8d7bf4cef8d29115f93cdbcbf49 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 22 Dec 2023 17:52:15 +0100 Subject: [PATCH 101/173] switch gpx name to point name - temp only --- brouter-core/src/main/java/btools/router/FormatGpx.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/brouter-core/src/main/java/btools/router/FormatGpx.java b/brouter-core/src/main/java/btools/router/FormatGpx.java index 0ed6cc5..39bb0de 100644 --- a/brouter-core/src/main/java/btools/router/FormatGpx.java +++ b/brouter-core/src/main/java/btools/router/FormatGpx.java @@ -153,7 +153,10 @@ public class FormatGpx extends Formatter { sb.append(" ") .append(hint.selev == Short.MIN_VALUE ? "" : "" + (hint.selev / 4.) + "") - .append("").append(hint.getMessageString()).append("") + .append("P") + .append(""+hint.indexInTrack+"_") + .append(hint.getMessageString()) + .append("") .append("").append("" + hint.distanceToNext).append(""); float rteTime = t.getVoiceHintTime(i + 1); if (rteTime != lastRteTime) { // add timing only if available From c31c38a5d63e0aecb24849420aa58351ea1aaf23 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 22 Dec 2023 17:56:48 +0100 Subject: [PATCH 102/173] added transport mode param, switch morestraight logic --- .../src/main/java/btools/router/VoiceHintProcessor.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java index 2e61c65..d11a923 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java +++ b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java @@ -15,10 +15,12 @@ public final class VoiceHintProcessor { // private double catchingRange; // range to catch angles and merge turns private boolean explicitRoundabouts; + private int transportMode; - public VoiceHintProcessor(double catchingRange, boolean explicitRoundabouts) { + public VoiceHintProcessor(double catchingRange, boolean explicitRoundabouts, int transportMode) { // this.catchingRange = catchingRange; this.explicitRoundabouts = explicitRoundabouts; + this.transportMode = transportMode; } private float sumNonConsumedWithinCatchingRange(List inputs, int offset) { @@ -158,8 +160,8 @@ public final class VoiceHintProcessor { } } - boolean hasSomethingMoreStraight = (Math.abs(turnAngle) - minAbsAngeRaw) > 20.; - //boolean hasSomethingMoreStraight = (Math.abs(turnAngle - minAbsAngeRaw)) > 20.; + // boolean hasSomethingMoreStraight = (Math.abs(turnAngle) - minAbsAngeRaw) > 20.; + boolean hasSomethingMoreStraight = (Math.abs(turnAngle - minAbsAngeRaw)) > 20. && input.badWays != null; // && !ignoreBadway; // unconditional triggers are all junctions with // - higher detour prios than the minimum route prio (except link->highway junctions) From c5f158ec4319960f890fb7b7a355465a8f952eab Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 22 Dec 2023 18:05:07 +0100 Subject: [PATCH 103/173] calls with transport mode --- .../src/main/java/btools/router/OsmTrack.java | 12 ++++++------ .../src/main/java/btools/router/VoiceHintList.java | 14 ++++++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index 9ccff98..c336c38 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -498,7 +498,8 @@ public final class OsmTrack { node = node.origin; } - VoiceHintProcessor vproc = new VoiceHintProcessor(rc.turnInstructionCatchingRange, rc.turnInstructionRoundabouts); + int transportMode = voiceHints.transportMode(); + VoiceHintProcessor vproc = new VoiceHintProcessor(rc.turnInstructionCatchingRange, rc.turnInstructionRoundabouts, transportMode); List results = vproc.process(inputs); double minDistance = getMinDistance(); @@ -511,13 +512,12 @@ public final class OsmTrack { int getMinDistance() { if (voiceHints != null) { - switch (voiceHints.getTransportMode()) { - case "car": + switch (voiceHints.transportMode()) { + case VoiceHintList.TRANS_MODE_CAR: return 20; - case "bike": - return 5; - case "foot": + case VoiceHintList.TRANS_MODE_FOOT: return 3; + case VoiceHintList.TRANS_MODE_BIKE: default: return 5; } diff --git a/brouter-core/src/main/java/btools/router/VoiceHintList.java b/brouter-core/src/main/java/btools/router/VoiceHintList.java index 215d2f7..e377e24 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHintList.java +++ b/brouter-core/src/main/java/btools/router/VoiceHintList.java @@ -30,11 +30,17 @@ public class VoiceHintList { public String getTransportMode() { String ret; - switch(transportMode) { - case TRANS_MODE_FOOT: ret = "foot"; break; - case TRANS_MODE_CAR : ret = "car"; break; + switch (transportMode) { + case TRANS_MODE_FOOT: + ret = "foot"; + break; + case TRANS_MODE_CAR: + ret = "car"; + break; case TRANS_MODE_BIKE: - default: ret = "bike"; + default: + ret = "bike"; + break; } return ret; } From 01ac57a929abf17a7a74d0274a8cafd388288e9f Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 22 Dec 2023 18:09:44 +0100 Subject: [PATCH 104/173] change for voicehint list first step --- .../src/main/java/btools/router/VoiceHintProcessor.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java index d11a923..b31e6dc 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java +++ b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java @@ -129,10 +129,7 @@ public final class VoiceHintProcessor { if (badPrio > maxPrioAll && !isBadHighway2Link) { maxPrioAll = badPrio; - } - - if (badWay.costfactor < 20.f && Math.abs(badTurn) < minAbsAngeRaw) { - minAbsAngeRaw = Math.abs(badTurn); + input.maxBadPrio = Math.max(input.maxBadPrio, badPrio); } if (badPrio < minPrio) { @@ -147,6 +144,10 @@ public final class VoiceHintProcessor { continue; // ways from the back should not trigger a slight turn } + if (badWay.costfactor < 20.f && Math.abs(badTurn) < minAbsAngeRaw) { + minAbsAngeRaw = Math.abs(badTurn); + } + if (badPrio > maxPrioCandidates) { maxPrioCandidates = badPrio; input.maxBadPrio = Math.max(input.maxBadPrio, badPrio); From e73d0e8001cf3a589364e39f2ee3c27aa54b8ecd Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 22 Dec 2023 18:25:09 +0100 Subject: [PATCH 105/173] change for voicehint list second step --- .../btools/router/VoiceHintProcessor.java | 82 +++++++++++++++---- 1 file changed, 64 insertions(+), 18 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java index b31e6dc..4aaa230 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java +++ b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java @@ -249,6 +249,7 @@ public final class VoiceHintProcessor { List results = new ArrayList<>(); double distance = 0; VoiceHint inputLast = null; + VoiceHint inputLastSaved = null; for (int hintIdx = 0; hintIdx < inputs.size(); hintIdx++) { VoiceHint input = inputs.get(hintIdx); VoiceHint nextInput = null; @@ -258,7 +259,7 @@ public final class VoiceHintProcessor { if (nextInput == null) { if (input.cmd == VoiceHint.C && !input.goodWay.isLinktType()) { - if (input.goodWay.getPrio() < input.maxBadPrio) { + if (input.goodWay.getPrio() < input.maxBadPrio && (inputLastSaved != null && inputLastSaved.distanceToNext > catchingRange)) { results.add(input); } else { if (inputLast != null) { // when drop add distance to last @@ -270,16 +271,37 @@ public final class VoiceHintProcessor { results.add(input); } } else { - if (input.cmd == VoiceHint.C && !input.goodWay.isLinktType()) { - if (input.goodWay.getPrio() < input.maxBadPrio) { - results.add(input); - } else { - if (inputLast != null) { // when drop add distance to last - inputLast.distanceToNext += input.distanceToNext; + if ((inputLastSaved != null && inputLastSaved.distanceToNext > catchingRange) || input.distanceToNext > catchingRange) { + if (input.cmd == VoiceHint.C && !input.goodWay.isLinktType()) { + if (input.goodWay.getPrio() < input.maxBadPrio + && (inputLastSaved != null && inputLastSaved.distanceToNext > minRange) + && (input.distanceToNext > minRange)) { + // add only on prio + results.add(input); + inputLastSaved = input; + } else { + if (inputLastSaved != null) { // when drop add distance to last + inputLastSaved.distanceToNext += input.distanceToNext; + } + } + } + else { + // add all others + // ignore motorway / primary continue + if ( + ((input.goodWay.getPrio() != 28) && + (input.goodWay.getPrio() != 30) && + (input.goodWay.getPrio() != 26)) + || Math.abs(input.angle) > 5.f) { // motorway / primary exit + results.add(input); + inputLastSaved = input; + } else { + if (inputLastSaved != null) { // when drop add distance to last + inputLastSaved.distanceToNext += input.distanceToNext; + } } } } else if (input.distanceToNext < catchingRange) { - int badWayPrio = 0; double dist = input.distanceToNext; float angles = input.angle; int i = 1; @@ -290,12 +312,32 @@ public final class VoiceHintProcessor { if (input.cmd == VoiceHint.C && !input.goodWay.isLinktType()) { if (input.goodWay.getPrio() < input.maxBadPrio) { - save = true; + if (inputLastSaved != null && inputLastSaved.cmd != VoiceHint.C + && (inputLastSaved != null && inputLastSaved.distanceToNext > minRange) + && transportMode != VoiceHintList.TRANS_MODE_CAR) + { + // add when straight and not linktype + // and last vh not straight + save = true; + // remove when next straight and not linktype + if (nextInput != null && + nextInput.cmd == VoiceHint.C && + !nextInput.goodWay.isLinktType()) { + input.distanceToNext += nextInput.distanceToNext; + hintIdx++; + } + } + + } else { + if (inputLastSaved != null) { // when drop add distance to last + inputLastSaved.distanceToNext += input.distanceToNext; + } } - } else if (VoiceHint.is180DegAngle(input.angle)) { //|| VoiceHint.is180DegAngle(nextInput.angle)) { // u-turn, 180 degree - //System.out.println("uturn < dist next!=null " + input.indexInTrack); + } else if (VoiceHint.is180DegAngle(input.angle)) { + // add u-turn, 180 degree save = true; - } else if (Math.abs(angles) > 180 - SIGNIFICANT_ANGLE) { // u-turn, collects e.g. two left turns in range + } else if (transportMode == VoiceHintList.TRANS_MODE_CAR && Math.abs(angles) > 180 - SIGNIFICANT_ANGLE) { + // add when inc car mode and u-turn, collects e.g. two left turns in range input.angle = angles; input.calcCommand(); input.distanceToNext += nextInput.distanceToNext; @@ -306,24 +348,28 @@ public final class VoiceHintProcessor { input.calcCommand(); input.distanceToNext += nextInput.distanceToNext; save = true; + hintIdx++; } else if (Math.abs(input.angle) > SIGNIFICANT_ANGLE) { - results.add(input); // add when last - save = false; + // add when angle above 22.5 deg + save = true; } else if (Math.abs(input.angle) < SIGNIFICANT_ANGLE) { - results.add(input); // add when last - save = false; + // add when angle below 22.5 deg + save = true; } else { - if (inputLast != null) { // when drop add distance to last - inputLast.distanceToNext += input.distanceToNext; + // otherwise ignore but add distance to next + if (nextInput != null) { // when drop add distance to last + nextInput.distanceToNext += input.distanceToNext; } save = false; } if (save) { results.add(input); // add when last + inputLastSaved = input; } } else { results.add(input); + inputLastSaved = input; } } inputLast = input; From 152833386b7d338f6ca7f14ef67cbccf787bd994 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 23 Dec 2023 14:37:37 +0100 Subject: [PATCH 106/173] new lang Korean translations --- .../src/main/res/values-ko/strings.xml | 94 +++++++++++++++++++ .../src/main/res/values/strings.xml | 4 +- 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 brouter-routing-app/src/main/res/values-ko/strings.xml diff --git a/brouter-routing-app/src/main/res/values-ko/strings.xml b/brouter-routing-app/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000..1c316ed --- /dev/null +++ b/brouter-routing-app/src/main/res/values-ko/strings.xml @@ -0,0 +1,94 @@ + + + + %d 구간 + %d 구간 + + 다운로드 취소 + 프로필 가져오기 + 다운로드 %s + ì‚­ì œ %s + ì—…ë°ì´íЏ %s + 구간 ì„ íƒ + 다운로드 중지 + í¬ê¸°=%1$s\n여유=%2$s + 구간 다운로드 + + brouter ë°ì´í„° ë² ì´ìФ ì„ íƒ dir: + 루트 프로필 ì„ íƒ + 주 í™œë™ ì„ íƒ + 다운로드 ê´€ë¦¬ìž + BRouter 앱 + + 취소 + 알고 있습니다 + 가까운 + 나가기 + OK + 성공 + ì—러가 ë°œìƒë습니다 + + BRouter 다운로드 ê´€ë¦¬ìž + *** 주ì˜:*** + \n\n다운로드 관리ìžëŠ” ë¼ìš°íŒ… ë°ì´í„°ë¥¼ 다운로드하는 ë° ì‚¬ìš©ë©ë‹ˆë‹¤. + 파ì¼ë‹¹ 최대 170MB까지 가능합니다. 다운로드 관리ìžë¥¼ 시작하지 마십시오 + ë°ì´í„° 요금제 ì—†ì´ ì…€ë£°ëŸ¬ ë°ì´í„° 연결로! + 다운로드 ì†ë„는 16 MBit/s로 제한ë©ë‹ˆë‹¤. + 시간 초과 없는 ê³„ì‚°ì„ ì„±ê³µì ìœ¼ë¡œ 준비했습니다 + ì´ì „ì— ì‹œê°„ 초과가 ë°œìƒí•œ ê³„ì‚°ì„ ì„±ê³µì ìœ¼ë¡œ 반복하셨습니다. + ì§€ë„ ë„구ì—서 시작할 때. 귀하가 귀하로부터 ë™ì¼í•œ ìš”ì²­ì„ ë°˜ë³µí•˜ëŠ” 경우 + 정확히 ë™ì¼í•œ 목ì ì§€ì™€ 가까운 시작ì ì´ 있는 ì§€ë„ ë„구, + ì´ ìš”ì²­ì€ ì‹œê°„ 초과ë˜ì§€ 않ìŒì´ 보장ë©ë‹ˆë‹¤. + SDCARD 기본 디렉토리 ìž…ë ¥: + 작업 ì„ íƒ + + 경유 ì„ íƒ ì²´í¬: + NoGo ì„ íƒ ì²´í¬: + 서버-모드 + ì •ë³´ + 루트 계산 + 프로필 설정 + GPX 공유 + ~로부터 ì„ íƒ + ì„ íƒ ~로/경유하여 + + 프로필 ë°ì´í„° ì—†ìŒ + , ì‚¬ìš©ëœ í”„ë¡œí•„ ì—†ìŒ + 다운로드 í•˜ê¸°ì— ë„ˆë¬´ ë§Žì€ ë°ì´í„°. 줄ì´ë„ë¡ í•˜ì„¸ìš”. + 다운로드 계íšë˜ì–´ 있ìŒ. ì¸í„°ë„· ì—°ê²° ì²´í¬ ê·¸ë ‡ì§€ 않다면 시작. + 현재 장소 ì„ íƒ:\n + 장소 ì„ íƒ ê¸°ëŒ€\n + 대체 가능한 + 버전 = BRouter-%1$s \n + 메모리 = %2$s \n + 거리 = %3$s km\n + í•„í„°ë§ëœ 오름차순 = %4$s m\n + ì¼ë°˜ 오름차순 = %5$s m\n + ì˜ˆìƒ ì‹œê°„ = %6$s + 장소 ì½ëŠ”ë° ì—러 + 좌표정보는 ì–´ë–¤ ìž¥ì†Œë„ í¬í•¨í•˜ê³  있지 않습니다! + 좌표정보는 너무 ë§Žì€ ìž¥ì†Œë¥¼ í¬í•¨í•˜ê³  있습니다: %1$d (~로 부터/~로/경유 ì´ë¦„ 사용하세요) + + 아니요 + 예 + + ì‚­ì œ í™•ì¸ + ì •ë§ ì‚­ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ? + 버전 문제 + 타ì¼ì„ 위한 기본 ë²„ì „ì´ ë°”ë€Œì—ˆìŠµë‹ˆë‹¤. ë¬´ì—‡ì„ í•´ì•¼ì£ ? + 현재 다운로드 계ì†, 다른 ì˜¤ëž˜ëœ ë°ì´í„° ì‚­ì œ + 다운로드 위해 ëª¨ë‘ ì„ íƒí•˜ê³  시작 + 지금 취소, 다른 ë‚  완료 + 버전 ì°¨ì´ + ì–´ë–¤ 타ì¼ì„ 위한 기본 ë²„ì „ì€ ë‹¤ë¦…ë‹ˆë‹¤, ë¬´ì—‡ì„ í•´ì•¼ì£ ? + 모든 다른 íƒ€ì¼ ë‹¤ìš´ë¡œë“œ + 모든 다른 íƒ€ì¼ ë°±ì§€í™” + 지금 취소, 다른 ë‚  완료 + 새로운 ë°ì´í„° ë²„ì „ì€ ìƒˆë¡œìš´ ì•±ì„ í•„ìš”ë¡œ 합니다. ìš°ì„  BRouter를 ì—…ë°ì´íЏ 하세요 + + 다운로드 실패 + 다운로드 ì·¨ì†Œë¨ + 다운로드 성공 + 다운로딩… + + diff --git a/brouter-routing-app/src/main/res/values/strings.xml b/brouter-routing-app/src/main/res/values/strings.xml index 166323d..fe274ab 100644 --- a/brouter-routing-app/src/main/res/values/strings.xml +++ b/brouter-routing-app/src/main/res/values/strings.xml @@ -36,8 +36,8 @@ Download speed is restricted to 16 MBit/s. Successfully prepared a timeout-free calculation You successfully repeated a calculation that previously run into a timeout - when started from your map-tool. If you repeat the same request from your - maptool, with the exact same destination point and a close-by starting point, + when started from your map tool. If you repeat the same request from your + map tool, with the exact same destination point and a close-by starting point, this request is guaranteed not to time out. Enter SDCARD base dir: Select Action From ae951d9aa5a2806b3c4960462b0bebfe52ff4793 Mon Sep 17 00:00:00 2001 From: Waldir Pimenta Date: Fri, 12 Jan 2024 23:05:48 +0000 Subject: [PATCH 107/173] Fix 'sees' typo in consider_river switch Use wording proposed by @gnbl on https://github.com/nrenner/brouter-web/issues/747 --- misc/profiles2/fastbike.brf | 2 +- misc/profiles2/hiking-mountain.brf | 2 +- misc/profiles2/trekking.brf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/misc/profiles2/fastbike.brf b/misc/profiles2/fastbike.brf index 5c9e6a5..d51a880 100644 --- a/misc/profiles2/fastbike.brf +++ b/misc/profiles2/fastbike.brf @@ -25,7 +25,7 @@ assign allow_motorways = false # %allow_motorways% | Set to true to allow assign consider_traffic = false # %consider_traffic% | Activate to avoid traffic | 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 or sees | 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 diff --git a/misc/profiles2/hiking-mountain.brf b/misc/profiles2/hiking-mountain.brf index c4ce1bc..688da66 100644 --- a/misc/profiles2/hiking-mountain.brf +++ b/misc/profiles2/hiking-mountain.brf @@ -12,7 +12,7 @@ assign consider_elevation = false # %consider_elevation% | Activate to prefer a route with few elevation meters | 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 or sees | 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 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] diff --git a/misc/profiles2/trekking.brf b/misc/profiles2/trekking.brf index 3c8841a..b4aa6ed 100644 --- a/misc/profiles2/trekking.brf +++ b/misc/profiles2/trekking.brf @@ -16,7 +16,7 @@ assign stick_to_cycleroutes = false # %stick_to_cycleroutes% | Set true t assign avoid_unsafe = false # %avoid_unsafe% | Set true to avoid standard highways | 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 or sees | 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 From 226f677b265675b9e942155ada0e331d094a5d55 Mon Sep 17 00:00:00 2001 From: Joachim Lengacher Date: Tue, 16 Jan 2024 17:36:35 +0100 Subject: [PATCH 108/173] Provide default profiles including variants. --- Dockerfile | 1 + README.md | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index f72e698..2152dc8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ RUN ./gradlew clean build FROM openjdk:17.0.1-jdk-slim COPY --from=build /tmp/brouter/brouter-server/build/libs/brouter-*-all.jar /brouter.jar COPY --from=build /tmp/brouter/misc/scripts/standalone/server.sh /bin/ +COPY --from=build /tmp/brouter/misc/* /profiles2 CMD /bin/server.sh diff --git a/README.md b/README.md index 92f7c41..6a223a7 100644 --- a/README.md +++ b/README.md @@ -139,18 +139,25 @@ file. ## BRouter with Docker -To build the Docker image run (in the project's to level directory): +To build the Docker image run (in the project's top level directory): ``` docker build -t brouter . ``` Download the segment files as described in the previous chapter. The folder containing the -segment files and the one containing the profiles can be mounted into the container. Run -BRouter as follows: +segment files can be mounted into the container. Run BRouter as follows: ``` -docker run --rm -v ./misc/scripts/segments4:/segments4 -v ./misc/profiles2:/profiles2 brouter +docker run --rm -v ./misc/scripts/segments4:/segments4 brouter +``` + +This will start brouter with a set of default routing profiles. + +If you want to provide your own routing profiles, you can also mount the folder containing the custom profiles: + +``` +docker run --rm -v ./misc/scripts/segments4:/segments4 -v /path/to/custom/profiles:/profiles2 brouter ``` ## Documentation From 2f1422352e0af3e56dd62810b8fc697b24468df8 Mon Sep 17 00:00:00 2001 From: simdens <45457844+simdens@users.noreply.github.com> Date: Wed, 17 Jan 2024 16:34:52 +0100 Subject: [PATCH 109/173] Add "DIVIDE" command and new "maxslope" and "maxslopecost" parameters (#642) * Added 'DIV' expression for profiles * Added 'uphillmaxbuffercost' and 'downhillmaxbuffercost' parameter. This makes it possible to penalize very steep path sections * Added 'div by zero' check in BExpression.java DIV command * Simplify maxbuffercostdiv logic * Added documentation about new features * Fix typo * Rename new DIV command * Redesign the new commands - Allow to set both the maxslope and the maxslopecost in the way context separately for uphill and downhill - New names for the new commands that better reflect what they actually do * Adapt the profile developers guide to the latest changes * Improve wording --------- Co-authored-by: quaelnix <122357328+quaelnix@users.noreply.github.com> --- .../src/main/java/btools/router/StdPath.java | 34 +++++++++++++++++-- .../java/btools/expressions/BExpression.java | 24 +++++++++---- .../expressions/BExpressionContextWay.java | 18 +++++++++- docs/developers/profile_developers_guide.md | 27 ++++++++++----- 4 files changed, 84 insertions(+), 19 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/StdPath.java b/brouter-core/src/main/java/btools/router/StdPath.java index 925b06a..d7164a4 100644 --- a/brouter-core/src/main/java/btools/router/StdPath.java +++ b/brouter-core/src/main/java/btools/router/StdPath.java @@ -49,6 +49,8 @@ final class StdPath extends OsmPath { float turncostbase = rc.expctxWay.getTurncost(); float uphillcutoff = rc.expctxWay.getUphillcutoff() * 10000; float downhillcutoff = rc.expctxWay.getDownhillcutoff() * 10000; + float uphillmaxslope = rc.expctxWay.getUphillmaxslope() * 10000; + float downhillmaxslope = rc.expctxWay.getDownhillmaxslope() * 10000; float cfup = rc.expctxWay.getUphillCostfactor(); float cfdown = rc.expctxWay.getDownhillCostfactor(); float cf = rc.expctxWay.getCostfactor(); @@ -60,11 +62,27 @@ final class StdPath extends OsmPath { downhillcostdiv = 1000000 / downhillcostdiv; } + int downhillmaxslopecostdiv = (int) rc.expctxWay.getDownhillmaxslopecost(); + if (downhillmaxslopecostdiv > 0) { + downhillmaxslopecostdiv = 1000000 / downhillmaxslopecostdiv; + } else { + // if not given, use legacy behavior + downhillmaxslopecostdiv = downhillcostdiv; + } + uphillcostdiv = (int) rc.expctxWay.getUphillcost(); if (uphillcostdiv > 0) { uphillcostdiv = 1000000 / uphillcostdiv; } + int uphillmaxslopecostdiv = (int) rc.expctxWay.getUphillmaxslopecost(); + if (uphillmaxslopecostdiv > 0) { + uphillmaxslopecostdiv = 1000000 / uphillmaxslopecostdiv; + } else { + // if not given, use legacy behavior + uphillmaxslopecostdiv = uphillcostdiv; + } + int dist = (int) distance; // legacy arithmetics needs int // penalty for turning angle @@ -99,8 +117,14 @@ final class StdPath extends OsmPath { reduce = excess; } ehbd -= reduce; + float elevationCost = 0.f; if (downhillcostdiv > 0) { - int elevationCost = reduce / downhillcostdiv; + elevationCost += Math.min(reduce, dist * downhillmaxslope) / downhillcostdiv; + } + if (downhillmaxslopecostdiv > 0) { + elevationCost += Math.max(0, reduce - dist * downhillmaxslope) / downhillmaxslopecostdiv; + } + if (elevationCost > 0) { sectionCost += elevationCost; if (message != null) { message.linkelevationcost += elevationCost; @@ -125,8 +149,14 @@ final class StdPath extends OsmPath { reduce = excess; } ehbu -= reduce; + float elevationCost = 0.f; if (uphillcostdiv > 0) { - int elevationCost = reduce / uphillcostdiv; + elevationCost += Math.min(reduce, dist * uphillmaxslope) / uphillcostdiv; + } + if (uphillmaxslopecostdiv > 0) { + elevationCost += Math.max(0, reduce - dist * uphillmaxslope) / uphillmaxslopecostdiv; + } + if (elevationCost > 0) { sectionCost += elevationCost; if (message != null) { message.linkelevationcost += elevationCost; diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpression.java b/brouter-expressions/src/main/java/btools/expressions/BExpression.java index ded5b34..b083f58 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpression.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpression.java @@ -9,14 +9,15 @@ final class BExpression { private static final int ADD_EXP = 20; private static final int MULTIPLY_EXP = 21; - private static final int MAX_EXP = 22; - private static final int EQUAL_EXP = 23; - private static final int GREATER_EXP = 24; - private static final int MIN_EXP = 25; + private static final int DIVIDE_EXP = 22; + private static final int MAX_EXP = 23; + private static final int EQUAL_EXP = 24; + private static final int GREATER_EXP = 25; + private static final int MIN_EXP = 26; - private static final int SUB_EXP = 26; - private static final int LESSER_EXP = 27; - private static final int XOR_EXP = 28; + private static final int SUB_EXP = 27; + private static final int LESSER_EXP = 28; + private static final int XOR_EXP = 29; private static final int SWITCH_EXP = 30; private static final int ASSIGN_EXP = 31; @@ -144,6 +145,8 @@ final class BExpression { exp.typ = AND_EXP; } else if ("multiply".equals(operator)) { exp.typ = MULTIPLY_EXP; + } else if ("divide".equals(operator)) { + exp.typ = DIVIDE_EXP; } else if ("add".equals(operator)) { exp.typ = ADD_EXP; } else if ("max".equals(operator)) { @@ -277,6 +280,8 @@ final class BExpression { return op1.evaluate(ctx) - op2.evaluate(ctx); case MULTIPLY_EXP: return op1.evaluate(ctx) * op2.evaluate(ctx); + case DIVIDE_EXP: + return divide(op1.evaluate(ctx), op2.evaluate(ctx)); case MAX_EXP: return max(op1.evaluate(ctx), op2.evaluate(ctx)); case MIN_EXP: @@ -360,6 +365,11 @@ final class BExpression { return v1 < v2 ? v1 : v2; } + private float divide(float v1, float v2) { + if (v2 == 0f) throw new IllegalArgumentException("div by zero"); + return v1 / v2; + } + @Override public String toString() { if (typ == NUMBER_EXP) { diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContextWay.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContextWay.java index 5b37b7c..0a8338c 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContextWay.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContextWay.java @@ -12,7 +12,7 @@ public final class BExpressionContextWay extends BExpressionContext implements T private boolean decodeForbidden = true; private static String[] buildInVariables = - {"costfactor", "turncost", "uphillcostfactor", "downhillcostfactor", "initialcost", "nodeaccessgranted", "initialclassifier", "trafficsourcedensity", "istrafficbackbone", "priorityclassifier", "classifiermask", "maxspeed", "uphillcost", "downhillcost", "uphillcutoff", "downhillcutoff"}; + {"costfactor", "turncost", "uphillcostfactor", "downhillcostfactor", "initialcost", "nodeaccessgranted", "initialclassifier", "trafficsourcedensity", "istrafficbackbone", "priorityclassifier", "classifiermask", "maxspeed", "uphillcost", "downhillcost", "uphillcutoff", "downhillcutoff", "uphillmaxslope", "downhillmaxslope", "uphillmaxslopecost", "downhillmaxslopecost"}; protected String[] getBuildInVariableNames() { return buildInVariables; @@ -82,6 +82,22 @@ public final class BExpressionContextWay extends BExpressionContext implements T return getBuildInVariable(15); } + public float getUphillmaxslope() { + return getBuildInVariable(16); + } + + public float getDownhillmaxslope() { + return getBuildInVariable(17); + } + + public float getUphillmaxslopecost() { + return getBuildInVariable(18); + } + + public float getDownhillmaxslopecost() { + return getBuildInVariable(19); + } + public BExpressionContextWay(BExpressionMetaData meta) { super("way", meta); } diff --git a/docs/developers/profile_developers_guide.md b/docs/developers/profile_developers_guide.md index 200732c..f5b69bf 100644 --- a/docs/developers/profile_developers_guide.md +++ b/docs/developers/profile_developers_guide.md @@ -72,12 +72,16 @@ Some variable names are pre-defined and accessed by the routing engine: - for the global section these are: - - 7 elevation configuration parameters: + - 11 elevation configuration parameters: - `downhillcost` - `downhillcutoff` + - `downhillmaxslope` + - `downhillmaxslopecost` - `uphillcost` - `uphillcutoff` + - `uphillmaxslope` + - `uphillmaxslopecost` - `elevationpenaltybuffer` - `elevationmaxbuffer` - `elevationbufferreduce` @@ -172,6 +176,7 @@ All expressions have one of the following basic forms: - `and ` - `xor ` - `multiply ` + - `div ` - `add ` - `sub ` - `max ` @@ -276,33 +281,37 @@ it climbed only 10 m on those 500 m, all 10 m would be *swallowed* by cutoff, together with up to 5 m from the buffer, if there were any. When elevation does not fit the buffer of size `elevationmaxbuffer`, it is -converted by up/downhillcost ratio to Elevationcost portion of Equivalentlength. -Up/downhillcostfactors are used, if defined, otherwise costfactor is used. +converted by `up/downhill[maxslope]cost` ratio to Elevationcost portion of Equivalentlength. +`up/downhillcostfactors` are used, if defined, otherwise `costfactor` is used. - `elevationpenaltybuffer` - default 5(m). The variable value is used for 2 purposes - with `buffer content > elevationpenaltybuffer`, it starts partially convert - the buffered elevation to ElevationCost by Up/downhillcost + the buffered elevation to ElevationCost by `up/downhillcost` - with `elevation taken = MIN (buffer content - elevationpenaltybuffer, WayLength[km] * elevationbufferreduce*10` - Up/downhillcost factor takes place instead of costfactor at the percentage + The `up/downhillcostfactor` takes place instead of `costfactor` at the percentage of how much is `WayLength[km] * elevationbufferreduce*10` is saturated by the buffer content above elevationpenaltybuffer. - `elevationmaxbuffer` - default 10(m) is the size of the buffer, above which all elevation is converted to - Elevationcost by Up/Downhillcost ratio, and - if defined - - Up/downhillcostfactor fully replaces costfactor in way cost calculation. + Elevationcost by `up/downhill[maxslope]cost` ratio, and - if defined - + `up/downhillcostfactor` fully replaces `costfactor` in way cost calculation. - `elevationbufferreduce` - default 0(slope%) is rate of conversion of the buffer content above elevationpenaltybuffer to ElevationCost. For a way of length L, the amount of converted elevation is L[km] * elevationbufferreduce[%] * 10. The elevation to Elevationcost - conversion ratio is given by Up/downhillcost. + conversion ratio is given by `up/downhill[maxslope]cost`. + +Whether `up/downhillmaxslope` or `up/downhillmaxslopecost` is used as conversion +ratio depends on whether the elevation was accumulated below or above the slope +threshold values defined in `up/downhillmaxslope`. Example: Let's examine steady slopes with `elevationmaxbuffer=10`, `elevationpenaltybuffer=5`, `elevationbufferreduce=0.5`, `cutoffs=1.5`, @@ -313,7 +322,7 @@ All slopes within 0 .. 1.5% are swallowed by the cutoff. - For slope 1.75%, there will remain 0.25%. That saturates the elevationbufferreduce 0.5% by 50%. That gives Way cost to - be calculated 50% from costfactor and 50% from Up/downhillcostfactor. + be calculated 50% from `costfactor` and 50% from `up/downhillcostfactor`. Additionally, 0.25% gives 2.5m per 1km, converted to 2.5*60 = 150m of Elevationcost. From bf07e2e6d2dbba314b074365f0f19ff13436b253 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 18 Jan 2024 18:39:13 +0100 Subject: [PATCH 110/173] prepared CLI for raw testing --- .../src/main/java/btools/server/BRouter.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/brouter-server/src/main/java/btools/server/BRouter.java b/brouter-server/src/main/java/btools/server/BRouter.java index a6dfe10..0d296e7 100644 --- a/brouter-server/src/main/java/btools/server/BRouter.java +++ b/brouter-server/src/main/java/btools/server/BRouter.java @@ -105,13 +105,27 @@ public class BRouter { if (engineMode == RoutingEngine.BROUTER_ENGINEMODE_GETELEV) { re = new RoutingEngine("testinfo", null, new File(args[0]), wplist, rc, engineMode); } else { + rc.rawTrackPath = "testtrack.raw"; re = new RoutingEngine("testtrack", null, new File(args[0]), wplist, rc, engineMode); } re.doRun(0); + + if (engineMode == RoutingEngine.BROUTER_ENGINEMODE_ROUTING) { + // store new reference track if any + // (can exist for timed-out search) + if (re.getFoundRawTrack() != null) { + try { + re.getFoundRawTrack().writeBinary(rc.rawTrackPath); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } catch (Exception e) { System.out.println(e.getMessage()); } - + } From ae7411d4a0388443c725c932e2a5cfc1a37d3308 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 19 Jan 2024 10:16:42 +0100 Subject: [PATCH 111/173] added nogo for heading calc --- .../main/java/btools/router/RoutingEngine.java | 18 ++++++++++++++++++ .../src/main/java/btools/util/CheapRuler.java | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index 105ed09..edf013d 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -20,6 +20,8 @@ import btools.mapaccess.OsmLink; import btools.mapaccess.OsmLinkHolder; import btools.mapaccess.OsmNode; import btools.mapaccess.OsmNodePairSet; +import btools.util.CheapAngleMeter; +import btools.util.CheapRuler; import btools.util.CompactLongMap; import btools.util.SortedHeap; import btools.util.StackSampler; @@ -587,6 +589,22 @@ public class RoutingEngine extends Thread { matchedWaypoints.add(mwp); } matchWaypointsToNodes(matchedWaypoints); + if (routingContext.startDirection != null) { + // add a nogo not to turn back + double angle = CheapAngleMeter.normalize(180 + routingContext.startDirection); + int[] np = CheapRuler.destination(matchedWaypoints.get(0).crosspoint.ilon, matchedWaypoints.get(0).crosspoint.ilat, 10, angle); + OsmNodeNamed n = new OsmNodeNamed(); + n.name = "nogo8"; + n.ilon = np[0]; + n.ilat = np[1]; + n.isNogo = true; + n.radius = 8; + n.nogoWeight = 9999; + if (routingContext.nogopoints == null) { + routingContext.nogopoints = new ArrayList<>(); + } + routingContext.nogopoints.add(n); + } routingContext.checkMatchedWaypointAgainstNogos(matchedWaypoints); diff --git a/brouter-util/src/main/java/btools/util/CheapRuler.java b/brouter-util/src/main/java/btools/util/CheapRuler.java index 7a7dc8f..bdd9360 100644 --- a/brouter-util/src/main/java/btools/util/CheapRuler.java +++ b/brouter-util/src/main/java/btools/util/CheapRuler.java @@ -77,4 +77,21 @@ public final class CheapRuler { double dlat = (ilat1 - ilat2) * kxky[1]; return Math.sqrt(dlat * dlat + dlon * dlon); // in m } + + public static int[] destination(int lon1, int lat1, double distance, double angle) { + + double[] lonlat2m = CheapRuler.getLonLatToMeterScales(lat1); + double lon2m = lonlat2m[0]; + double lat2m = lonlat2m[1]; + angle = 90. - angle; + double st = Math.sin(angle * Math.PI / 180.); + double ct = Math.cos(angle * Math.PI / 180.); + + int lon2 = (int) (0.5 + lon1 + ct * distance / lon2m); + int lat2 = (int) (0.5 + lat1 + st * distance / lat2m); + int[] ret = new int[2]; + ret[0] = lon2; + ret[1] = lat2; + return ret; + } } From e5ecd14ce1cd6c7623ca795e48c7e654f7b4aa7f Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 19 Jan 2024 16:37:29 +0100 Subject: [PATCH 112/173] prepare rerouting --- .../src/main/java/btools/router/RoutingEngine.java | 14 ++++++++++++++ .../src/main/java/btools/server/BRouter.java | 3 ++- 2 files changed, 16 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 edf013d..7b9605f 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -32,6 +32,8 @@ public class RoutingEngine extends Thread { public final static int BROUTER_ENGINEMODE_SEED = 1; public final static int BROUTER_ENGINEMODE_GETELEV = 2; + public final static int BROUTER_ENGINEMODE_PREPARE_REROUTE = 6; + private NodesCache nodesCache; private SortedHeap openSet = new SortedHeap<>(); private boolean finished = false; @@ -160,6 +162,7 @@ public class RoutingEngine extends Thread { switch (engineMode) { case BROUTER_ENGINEMODE_ROUTING: + case BROUTER_ENGINEMODE_PREPARE_REROUTE: if (waypoints.size() < 2) { throw new IllegalArgumentException("we need two lat/lon points at least!"); } @@ -191,6 +194,9 @@ public class RoutingEngine extends Thread { ArrayList messageList = new ArrayList<>(); for (int i = 0; ; i++) { track = findTrack(refTracks, lastTracks); + + if (engineMode==BROUTER_ENGINEMODE_PREPARE_REROUTE) break; // no output for rerouting prepare + track.message = "track-length = " + track.distance + " filtered ascend = " + track.ascend + " plain-ascend = " + track.plainAscend + " cost=" + track.cost; if (track.energy != 0) { @@ -577,6 +583,10 @@ public class RoutingEngine extends Thread { boolean dirty = found && nearbyTrack.isDirty; logInfo("read referenceTrack, found=" + found + " dirty=" + dirty + " " + debugInfo); } + if (nearbyTrack != null && + engineMode==BROUTER_ENGINEMODE_PREPARE_REROUTE) { + return null; // already rerouting prepared + } } if (matchedWaypoints == null) { // could exist from the previous alternative level @@ -1067,6 +1077,10 @@ public class RoutingEngine extends Thread { track.profileTimestamp = routingContext.profileTimestamp; track.isDirty = isDirty; foundRawTrack = track; + if (engineMode==BROUTER_ENGINEMODE_PREPARE_REROUTE) { + return null; // rerouting prepared + } + } if (!wasClean && isDirty) { diff --git a/brouter-server/src/main/java/btools/server/BRouter.java b/brouter-server/src/main/java/btools/server/BRouter.java index 0d296e7..1600c7e 100644 --- a/brouter-server/src/main/java/btools/server/BRouter.java +++ b/brouter-server/src/main/java/btools/server/BRouter.java @@ -110,7 +110,8 @@ public class BRouter { } re.doRun(0); - if (engineMode == RoutingEngine.BROUTER_ENGINEMODE_ROUTING) { + if (engineMode == RoutingEngine.BROUTER_ENGINEMODE_ROUTING || + engineMode == RoutingEngine.BROUTER_ENGINEMODE_PREPARE_REROUTE) { // store new reference track if any // (can exist for timed-out search) if (re.getFoundRawTrack() != null) { From 1bf367b43e76fba6765f117cbe853afaed403d89 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 19 Jan 2024 16:40:56 +0100 Subject: [PATCH 113/173] allow multiple segments for rerouting --- .../src/main/java/btools/router/RoutingEngine.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index 7b9605f..f940f2f 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -659,7 +659,8 @@ public class RoutingEngine extends Thread { routingContext.inverseDirection = false; wptIndex = i + 1; } else { - seg = searchTrack(matchedWaypoints.get(i), matchedWaypoints.get(i + 1), i == matchedWaypoints.size() - 2 ? nearbyTrack : null, refTracks[i]); + //seg = searchTrack(matchedWaypoints.get(i), matchedWaypoints.get(i + 1), i == matchedWaypoints.size() - 2 ? nearbyTrack : null, refTracks[i]); + seg = searchTrack(matchedWaypoints.get(i), matchedWaypoints.get(i + 1), nearbyTrack, refTracks[i]); wptIndex = i; } if (seg == null) @@ -1076,7 +1077,14 @@ public class RoutingEngine extends Thread { track.nogoChecksums = routingContext.getNogoChecksums(); track.profileTimestamp = routingContext.profileTimestamp; track.isDirty = isDirty; - foundRawTrack = track; + if (foundRawTrack == null) { + foundRawTrack = track; + } else { + for (OsmPathElement n : track.nodes) { + foundRawTrack.nodes.add(n); + } + foundRawTrack.endPoint = endWp; + } if (engineMode==BROUTER_ENGINEMODE_PREPARE_REROUTE) { return null; // rerouting prepared } From 26879159daa97224a56283ab6d5276651aaf7139 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sun, 21 Jan 2024 16:48:14 +0100 Subject: [PATCH 114/173] updated service doc --- docs/developers/android_service.md | 43 ++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/docs/developers/android_service.md b/docs/developers/android_service.md index 0feaddf..5bbc475 100644 --- a/docs/developers/android_service.md +++ b/docs/developers/android_service.md @@ -39,19 +39,52 @@ Please note: when they have a parameter 'weight' the result is not an absolute n This parameters are needed to tell BRouter what to do. +### using profiles + +For calulation BRouter uses a set of rules defined in a profile. See description of profile [rules](https://github.com/abrensch/brouter/blob/master/docs/developers/profile_developers_guide.md). + +Here we talk about how we let BRouter know witch profile to use. +There are three ways: + +1. use the parameter 'v' and 'fast' +``` + "v"-->[motorcar|bicycle|foot] + "fast"-->[0|1] + This enables BRouter to look into the file serviceconfig.dat. + In there BRouter find the profile associated for e.g bicyle_fast trekking + This could be changed by the user calling the BRouter app server-mode. +``` + +2. use the profile parameter +``` + profile=trekking + It needs an available file in the BRouter profile folder e.g. trekking.brf +``` + +3. use a remote profile +``` + remoteProfile=a long string with routing rules + This is saved in BRouter profile folder temporary with the file name 'remote.brf' +``` + + ### profile parameter Profile parameters affect the result of a profile. +The variables inside a profile predefine a value e.g. avoidsteps=1 +A parameter call gives the chance to change this start value without changing the profile e.g. avoidsteps=0 For the app it is a list of params concatenated by '&'. E.g. extraParams=avoidferry=1&avoidsteps=0 The server calls profile params by a prefix 'profile:'. E.g. ...&profile:avoidferry=1&profile:avoidsteps=0 +By using this parameter logic, there is no need to edit a profile before sending. + ### using profile parameter inside an app To be flexible it is possible to send a profile to BRouter - server or app. Another variant is to send parameters for an existing profile that are different from the original profile. -With the version 1.7.1 it is possible to collect parameters from the profile. +With the version 1.7.1 it is possible to collect parameters from the profile. The variable parameters are defined like this ``` assign avoid_path = false # %avoid_path% | Set to true to avoid pathes | boolean @@ -61,9 +94,9 @@ Now you could do that with an calling app. What to do to get it work? -- First copy the [RoutingParam](brouter-routing-app/src/main/java/btools/routingapp/RoutingParam.java) class to your source - use the same name and package name. +- First copy the [RoutingParam](brouter-routing-app/src/main/java/btools/routingapp/RoutingParam.java) class to your source - use the same name and package name. - Second analyze the profile for which you need the parameter. - This [BRouter routine](https://github.com/abrensch/brouter/blob/086503e529da7c044cc0f88f86c394fdb574d6cf/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java#L103) can do that, just copy it to your source to use it in your app. + This [BRouter routine](https://github.com/abrensch/brouter/blob/086503e529da7c044cc0f88f86c394fdb574d6cf/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java#L103) can do that, just copy it to your source to use it in your app. It builds a List you could send to BRouter app. - You find the call of BRouter app in comment at [RoutingParameterDialog](https://github.com/abrensch/brouter/blob/086503e529da7c044cc0f88f86c394fdb574d6cf/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java#L33) @@ -79,7 +112,7 @@ intent.putExtra("runsilent", true); startActivity(intent); ``` -This suppress the first question after installation for the BRouter path, generates the BRouter folders in main space and starts the download dialog. +This suppress the first question after installation for the BRouter path, generates the BRouter folders in main space and starts the download dialog. ### silent app call @@ -92,7 +125,7 @@ intent.putExtra("runsilent", true); startActivity(intent); ``` -This suppress the first question after installation for the BRouter path, generates the BRouter folders in main space and starts the download dialog. +This suppress the first question after installation for the BRouter path, generates the BRouter folders in main space and starts the download dialog. ## other routing engine modes in app From b3002a78e3e4937ac8ec5be27b1d0c3a90b1cdea Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 30 Jan 2024 18:12:51 +0100 Subject: [PATCH 115/173] rework for vh on roundabouts #664 --- .../btools/router/VoiceHintProcessor.java | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java index 4aaa230..33b607a 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java +++ b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java @@ -98,10 +98,10 @@ public final class VoiceHintProcessor { } if (roundaboutExit > 0) { roundAboutTurnAngle += sumNonConsumedWithinCatchingRange(inputs, hintIdx); - double startTurn = (roundaboudStartIdx != -1 ? inputs.get(roundaboudStartIdx).goodWay.turnangle : turnAngle); + double startTurn = (roundaboudStartIdx != -1 ? inputs.get(roundaboudStartIdx + 1).goodWay.turnangle : turnAngle); input.angle = roundAboutTurnAngle; input.distanceToNext = distance; - input.roundaboutExit = startTurn < 0 ? -roundaboutExit : roundaboutExit; + input.roundaboutExit = startTurn < 0 ? roundaboutExit : -roundaboutExit; distance = 0.; results.add(input); roundAboutTurnAngle = 0.f; @@ -284,15 +284,14 @@ public final class VoiceHintProcessor { inputLastSaved.distanceToNext += input.distanceToNext; } } - } - else { + } else { // add all others // ignore motorway / primary continue - if ( - ((input.goodWay.getPrio() != 28) && - (input.goodWay.getPrio() != 30) && - (input.goodWay.getPrio() != 26)) - || Math.abs(input.angle) > 5.f) { // motorway / primary exit + if (((input.goodWay.getPrio() != 28) && + (input.goodWay.getPrio() != 30) && + (input.goodWay.getPrio() != 26)) + || input.isRoundabout() + || Math.abs(input.angle) > 21.f) { results.add(input); inputLastSaved = input; } else { @@ -314,8 +313,7 @@ public final class VoiceHintProcessor { if (input.goodWay.getPrio() < input.maxBadPrio) { if (inputLastSaved != null && inputLastSaved.cmd != VoiceHint.C && (inputLastSaved != null && inputLastSaved.distanceToNext > minRange) - && transportMode != VoiceHintList.TRANS_MODE_CAR) - { + && transportMode != VoiceHintList.TRANS_MODE_CAR) { // add when straight and not linktype // and last vh not straight save = true; @@ -353,8 +351,8 @@ public final class VoiceHintProcessor { // add when angle above 22.5 deg save = true; } else if (Math.abs(input.angle) < SIGNIFICANT_ANGLE) { - // add when angle below 22.5 deg - save = true; + // add when angle below 22.5 deg ??? + // save = true; } else { // otherwise ignore but add distance to next if (nextInput != null) { // when drop add distance to last From 91ccb858dd80da8395128e8bc8265c9f2df7bf6e Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 30 Jan 2024 18:31:34 +0100 Subject: [PATCH 116/173] undo tmp gpx name --- brouter-core/src/main/java/btools/router/FormatGpx.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/FormatGpx.java b/brouter-core/src/main/java/btools/router/FormatGpx.java index 39bb0de..8a6f761 100644 --- a/brouter-core/src/main/java/btools/router/FormatGpx.java +++ b/brouter-core/src/main/java/btools/router/FormatGpx.java @@ -153,8 +153,7 @@ public class FormatGpx extends Formatter { sb.append(" ") .append(hint.selev == Short.MIN_VALUE ? "" : "" + (hint.selev / 4.) + "") - .append("P") - .append(""+hint.indexInTrack+"_") + .append("") .append(hint.getMessageString()) .append("") .append("").append("" + hint.distanceToNext).append(""); From 6a0f69d546325d0f804127a6e47a46f655dfb102 Mon Sep 17 00:00:00 2001 From: quaelnix <122357328+quaelnix@users.noreply.github.com> Date: Mon, 5 Feb 2024 11:00:57 +0100 Subject: [PATCH 117/173] Fix typo in profile_developers_guide.md --- docs/developers/profile_developers_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developers/profile_developers_guide.md b/docs/developers/profile_developers_guide.md index f5b69bf..cbc6fc3 100644 --- a/docs/developers/profile_developers_guide.md +++ b/docs/developers/profile_developers_guide.md @@ -176,7 +176,7 @@ All expressions have one of the following basic forms: - `and ` - `xor ` - `multiply ` - - `div ` + - `divide ` - `add ` - `sub ` - `max ` From 9c5b380105c969a275e92cdc50279258538780b8 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 6 Feb 2024 17:02:31 +0100 Subject: [PATCH 118/173] enabled custum profiles on server --- brouter-server/src/main/java/btools/server/RouteServer.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/brouter-server/src/main/java/btools/server/RouteServer.java b/brouter-server/src/main/java/btools/server/RouteServer.java index ba421f9..0685eac 100644 --- a/brouter-server/src/main/java/btools/server/RouteServer.java +++ b/brouter-server/src/main/java/btools/server/RouteServer.java @@ -194,6 +194,10 @@ public class RouteServer extends Thread implements Comparable { if (wplist.size() < 10) { SuspectManager.nearRecentWps.add(wplist); } + if (params.containsKey("profile")) { + // already handled in readRoutingContext + params.remove("profile"); + } int engineMode = 0; if (params.containsKey("engineMode")) { engineMode = Integer.parseInt(params.get("engineMode")); From e2895714715995d4780131611e5b7123ddba77b8 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 9 Feb 2024 12:25:25 +0100 Subject: [PATCH 119/173] added docker doc with command samples --- docs/developers/docker_guide.md | 125 +++++++++++++++++++++ docs/users/osmand.md | 26 +++++ docs/users/osmand/brouter-osmand-4.7.1.png | Bin 0 -> 237157 bytes 3 files changed, 151 insertions(+) create mode 100644 docs/developers/docker_guide.md create mode 100644 docs/users/osmand/brouter-osmand-4.7.1.png diff --git a/docs/developers/docker_guide.md b/docs/developers/docker_guide.md new file mode 100644 index 0000000..990d8e9 --- /dev/null +++ b/docs/developers/docker_guide.md @@ -0,0 +1,125 @@ +--- +parent: Developers +--- + +# Docker help + +In addition to the intro in readme.md about Docker, here are a few commands for daily work with the system. + +Build the Docker with a version based name +``` +$ docker build -t brouter-1.7.2 . +``` + +Start Docker with name additional to the Docker image name. +Please note: +The path for segments are on a Windows system. +Here the port used in server.sh is published. +``` +$ docker run --rm -v "I:/Data/test/segment4":/segments4 --publish 17777:17777 --name brouter-1.7.2 brouter-1.7.2 +``` + +and with a mount for profiles as well +``` +$ docker run --rm -v "I:/Data/test/segment4":/segments4 -v "I:/Data/test/profiles2":/profiles2 --name brouter-1.7.2 brouter-1.7.2 +``` + +Show the running Docker processes +``` +$ docker ps + +output: +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +b23518e8791d brouter-1.7.2 "/bin/sh -c /bin/ser…" 5 minutes ago Up 5 minutes 0.0.0.0:17777->17777/tcp brouter-1.7.2 +``` + +Fire some curl or wget commands to test if is realy useful running. + +Stop a running Docker image - please note, this only works when starts docker image with name, see above +``` +$ docker stop brouter-1.7.2 +``` + +Docker available images + +``` +$ docker images + +output: +REPOSITORY TAG IMAGE ID CREATED SIZE +brouter-1.7.2 latest e39703dec2fa 2 hours ago 410MB +brouter latest 728f122c7388 3 hours ago 410MB +``` + +Control +## Docker with docker-compose + +Use a git clone to build a local folder with last version. +Make a Docker container with version number inside your repository folder. +``` +$ docker build -t brouter:1.7.2 . + +$ docker images + +REPOSITORY TAG IMAGE ID CREATED SIZE +brouter-1.7.2 latest e39703dec2fa 3 hours ago 410MB +brouter 1.7.2 e39703dec2fa 3 hours ago 410MB +``` + +Start a container with composer +This needs a docker config file docker-compose.yml +Something like this: +``` +version: '2' +services: + brouter: + image: brouter:1.7.2 + restart: unless-stopped + ports: + - 17777:17777 + volumes: + - type: bind + source: "I:/Data/test/segment4" + target: /segments4 +# - type: bind +# source: "I:/Data/test/profiles2" +# target: /profiles2 +``` + +Start it +``` +$ docker-compose up -d +``` + +Have a look what is running +``` +$ docker-compose ps +or +$ docker-compose ls +or +$ docker ps +``` + + +Now update your repository (git pull) and build your Docker container with the new version tag +``` +$ docker build -t brouter:1.7.3 . + +$ docker images + +REPOSITORY TAG IMAGE ID CREATED SIZE +brouter 1.7.3 5edc998cb5ae 3 hours ago 410MB +brouter-1.7.2 latest e39703dec2fa 6 hours ago 410MB +``` + +Replace the version in Docker config file docker-compose.yml +``` + image: brouter:1.7.3 +``` + +Stop old running container and start the new one +``` +$ docker-compose down + +$ docker-compose up -d +``` diff --git a/docs/users/osmand.md b/docs/users/osmand.md index db751f7..a1c2f03 100644 --- a/docs/users/osmand.md +++ b/docs/users/osmand.md @@ -67,3 +67,29 @@ application profiles"/> The BRouter app should be launched before OsmAnd for this specific entry to appear in OsmAnd. Therefore, if you cannot find "BRouter (offline)" navigation option, you should force quit OsmAnd and restart it. + + +## OsmAnd version 4.7.1 + +From version 4.7.1 upwards Osmand supports the profile parameter for mapping: +Since Osmand version 3, many profiles can be defined in Osmand and the user can easily switch between these profiles. +This allow now when using the service-interface to address different brouter-profiles in a more flexible and better comprehensive way. + +- If in Osmand a profile has "BRouter" defined as navigation service +- AND the profile-name looks like "Brouter[mysting] + +==> then the profile "mystring" will be used in the Brouter-app! +(this new mapping replaces in that case the basic mapping defined above and based on the file "serviceconfig.dat) + +### Examples: Osmand-profile name Brouter-app +``` +[Brouter[trekking] "trekking" profile will be used (file trekking.brf) +[Brouter[racebike] "racebike" profile will be used (file racebike.brf) +.... +``` +Remark: +Currently Osmand do not check the defined name (case sensitiv) for the Brouter-profile (mystring). +If no profile is found, the routing will fail with "Could not calculate route.."! + +BRouter configuration in OsmAnd
+application profiles diff --git a/docs/users/osmand/brouter-osmand-4.7.1.png b/docs/users/osmand/brouter-osmand-4.7.1.png new file mode 100644 index 0000000000000000000000000000000000000000..42dfe802c55cfa176bdd4f7f32fb0f8ae26c2d21 GIT binary patch literal 237157 zcmd3M^K&Lq*X&9~4| zMZ4)evwa<>!-s|Rn+0-TyL9}mMt^}%t?MjT$Mt&6VstR6FNT26>)~XsXq1-Zy9si( zNFwf^;bV7RCF%d#tNwquymMDe0*7jRfoI@*+5$c1l??#qru_H~^|#~tDRPwSS#ZnX zY2F0eQsz(Y`}LZ@#qeOSZ9YK%?Jf=0C+^N?O+h0a;Bf_=`##!X;eOTh)L7JQt5|m@ z8%H1@+`X@NlQ>?S?p92zy&30mss{^}MnR|KC1UKjjE<36Py6JKdniLG!gI{vcS(yq@gIUkZcbaVF&NK9F_foMkS~F&!k#b1pZ|~~J z4bgc?oQmSk{VQ^zA}T3xX{5xhXSQE%j2-(kw=i?LD*2;vY{4Cv4ltlHnSxNlmcb^) z_x);UFlA_@(ZyWM>7F4RAlVuLreVJ0t1b5>avAXA;m}Aqap=MOA$ld=xkcb=P-sa7 zfiRfVLPJyiL_lUpR8%UVdlVT}5G05Wlj0&JI1JQG$pWzyRS8rqK^UrpLN-Bs5XG)0 z6=K|R$+M^tSg-}jTVQ5+iLtPZQT`kW#1lI=JHU2{1%3#il;w<>wHA{i(|At?*YB9EByFE)tia8s9tDyPQK!@HYbOCIR2qibTvXJ(0P*p(qh@p}S-yj-rg6oKlg3)5P8V^ftLp#;%PDHrjDYu|8S z`fn&Ka2In?Dj+^J_>>{i@2NsC!E>?>m3w4;WHC}zsh}ZIQV{2Wa`y>QD`aLODOpq{ zVRYbp1T-i%I@vB7H5?PfjJ0$aph-~Nav&QgMG2or+$*x8oC-QKf`091FtpqvlnN@3 zMQ=-qfodm;lVFgbCA1Ep5Vi8(|AdMwh_!YbfCI&u;b0v2s9%nTMy14M%bCB*`Py;yft3O1(>LgAt|G+qz(Hh5~eRB{Fn0mj)k@?F0+tQ~_mL=}DhX^KfSFW&X*=oZX7Z>O> zJ6Axs{5~VxnDYj%&LdTyce9g>+!Io5HL46zQe{EaM&m0~)Xd*7S;$Oa21W(;NMbN-Jo`Z^VXZ3| zWE%D7-F~l_Qt8NNP!T9a>8vv7;;bbk#ImrclD>gMCq)O+=$UONH7_$E;X4w8d)+ls z=yWw}crk2XMS{?8hU&9N|6v zB}p9Bc(7UF1-CRkVE(zfIbC=A4!MJPAgHTe8l&(EzH|w-RsGPE*Z|eyP&|(-j770B z6cY?Ooo>q(PlqN2l2ATH0EE`vhG-`GxfCp)cjIUr)rGiPJ`$^meK3}quM5k$M(VO| z)9XZAnubAbiIEkbzpn0;YBaF!Cc=EXP|4XyM-kq7-IN5%F)_c}XcGVX?Z#JRq3a6g zw^bDLJVdz3)rF7k_|;h_RId`f67*3ZGRk~#4ggrO{I1~@0ox?O3mPFE4AK!$?9x&J zG|FPHu*ciH3GJ_0nVf}DJϖ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%0Ddt~jOhZiGj@=M__7 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

QR2PdTKl$5RJ}hK-?& zK=-Mpo$V@@47xj2ZNN75BHG^-iE%%9zXxr1uiqE<)L@+ANvp=6_lyU2hLUgz*lAu# zMv4tw18gy>HT!v_V5o(yRT?iwPdO*utpA|-yqKW4(uwehh+tceii(PHwlJKvegA^v zpwey00JznXw(n=FzHHCS=4NXgxDzS@h99gVp=ES*lsw4%&U$ac@)@lgTk>8AD86Rv zbm;Bx_e5jOHW3_5DgBL5@E6*t57ELVWd!l{bN;{gmK&79W)1CAvcus-G9!j-&Glk( zYDz{*>hbwV60R&kfd>IT$~0DyXnt|tp!j&z+385wy7etotamh3iCj@M`IPeU>FIzH zl?%3wWW>Z|_03e8%>%qzFrBjLEaw`KSp5U8{sJWQp4D&K9rc?l>4Q(2n;-li_BMnr}u2Le)t&iZ?c9jA%Wh{}4i>5@?c8 z&nxN(*+Hj6vv&Dbl2f-eEL+e zCU(SIo&H;MDK;@!Oyb=5Lsxt@p-IBNZVs+!UupT}tUFEh>HdEliGpO=Gd{+t8|1{FQq$F(_uR*$k0js!%J0wc24z!aHm(d3+cU}Yv^$Q)|vwwfrrDMYk zb-;T8RDWex4v2FTTqO!m`7{W=XMo|s_Ifj=d82~qfTAzH8x)|7+qN;O{#jp#{m*ND zNGeh+Ra?uK<^L9eNu?TT634wiI!Ic=cLE&-FvD8+8D`h*HytZidtbhM2{>Xxb|AF1 z$cn7>VBl?Ykc6$f5vc@LqEPi?L%oip4)54jZlyU^WTVG8;2fQ*!Qy_6A|w8RR`cs|HgA6j=J-U&{|TbB)R!>{*)=>uD>fytUHa z@V;4xZC|849aJ9z0)R&L!WjfPtb)&qP|3n)SrHx{LJLPt#;ZxRJHO_KftMMcJ-Z2m ziJXE$r=7TLFC{O_u3f8bJM>T93X0rvLGJq2A)uyY&<}w#I3l9C6+B=i$ulVIo5}pv zH#qqF2CHSD0e#Z;E43SF^*3-cA;m+i%hm5Aa{Kn}OP{JTJEVQVz@Yb`X~p!CCg?^O zlSIzeK_>nai3EK;J*Tox5^iw4xN-9)_U)87o97{+ElSZ3XdLrN}60XG5;)oHUBKwibg7cX2WFfM*S<9;$&ML1p@ z+c0`5xNsFczjGD40>623DP>xM20w-y1C+S zGC-UIW^T=Le^(c1U|DXbgYz0TBlxI3cz@% zFMFho4#Y#`6P}zG^x;BOw}C(?_N4?yhG#i}77W$<{nAWf_IeRp#M{lIPZflf2XB>|=i%;Sddp2Wap$pKU5iyIgifd#QGuYF*EB0M6frHgL%rQG zd$pX%3+Wg6Rs(e_3sNcVzbMI(J_TNGPr$|^5-VsJi+B>wvcG#_cjh_RCy#}X+cK_F zhFp+NNlQ&dpvfi_Vqg~U9N52Rk4v`jbom5GTGgyEZ+}0J)M?0^5uaoMkz1h(cdfN=EKEzVsTfOB9wq|iz&`}hc| zE_7wEgRso2=l3FYpiVXXeXH{yh5FY6jz z?`NE;_ODpxd^1^r3L;Rcl#ic3ORG%700>30#8iLdUn#D(X4AJd)+O~-Y{PC8gQr4n z1qv^B0Kr#hO5oHJ+##?5dSW+j9W>`m&vvS@O@PVC`EXEjv-}v<$}^Y&NT1k=ZT;i*UuPrToSbG+_K{BCq1{5-p$gIp zi(W43bfF<6em2h&MQONKJY!DZ6#Fpu6HTLL4wJYSVCre>&TS?=0-=auai}t@wfH_c zsUgEsaXP%p8(0_qK=Uf3{@VIX<$=)aG?UR)-4*9K&!5L32EN%Znb||@>H7D2ydh97 zvdQbL-1YznMS#!;y$qvzF&IUku-Tuj2?kS3YfINML@KBbAMUc^ENE;s=gHb8btmqW zGn)u;wKKJtc*hJ5tXEv4O?pj`%7JjmbCI(xP8&|dM z3&ypzc9Vu*G1LPKI6%&kLeVES2X#nqe%7LGE6*9r__wjIeGRV4kgINc<}HtT4e-o} z(eXu^_U;^MNB@Bmw+y(3h{0lTp0f&xWh+hYTKjB6v?$25KoS^_kcd95VY8Fh$zfoj zq137!XwZ2_3S<;bw(i~FBEQ*(yW}F6Blf29Qz%f)kq4NVSkO0$~o2G*Os}}RSoy4tM4~U3hrw=6!#EK4CJfNog zMXM97Q`T!+5Tz%!n8Blm>>q5ZiYC6V`J(AU{n`J%JK%mT`%^2|yem6YTa zaB1@iSbyfl#h9=cyk(yuT)##8o+urEkWd3dN_`B=N!=t@Z67^Kj(rl2XEhGdC4|O= zd@s}VO#k*r`PRK36(Rqtv+edDMDngjBLC4I@z*Ep;)zjZwpp0C^1387G z^f8nt5Z`#zDMvp#fBFXB%Wt4T5xjKmTEi9>VlWR;6?mXQVO-~=L!^nk>YsfK)t5j0 zZG>s4vQjc?0{B=!dPYZ0*zdo}KVnTD{CQ~|ne!6#3b|GjjTuuw&s=Nq=%kRJpC7b@ zXhy)ksjCgVdxxx}J;`srB6rBCCtJhzSg#r-=@aA*2nj(D7Fomyg`SN%yCDefx!Z@I zt)akxJ`aGph~?9y1D~|6Y4&KCI$1qn4m!ud$@yPM_#NlW7^TaQ&h+BzzcE$sdMnL_MZrsKff-?JjK?d?R^ef5&ta7o z*TAt{n5*!yGc%Qo-yd~|@Lz%&pSkkTX03HAE#S|!L>?lLO~?^Il?L;WRiCJ!P_(WL z4h#%Hoq#h1r@HgQX>VeXnE%G&3$WbyTx7E#hpWv)mg}b=G;(l2T-`i5A`zTgwwC;T zb)p;BK4J?9!6?#wi3=xOT}3Un#O?y|-Q6uJDOrh%-`9$g*AAJf@MEIcHXM2(J*>pF z$t*CS<>s~stU|QC5k{bW`3_@D?Iwa}9o_Yb$ym@x(i-?DxOZ=STpX9&6>45IKSrXl zu8r_m;u&jywWB)c*QG}%c#CrrHuEyahpL~wwWOG5$K%DydLfqKftGzn-qu9>+Ya^} ze^MT2KjoTxMnK+-DXsu+!MSsLSXd;lEH_=bb@axevfTI``FGtEq#5F;;u% z0F@Gn%EV1zLwv912_}cKQ&sII<|X-TqQV`6dCEe9zjNh`H!UUWGAWGnRybW9(U2@- z7UDP>^Uy3>mS%<0eQUhY>ieX@JpMW%r#`{Wm7N}5ExPxo4}CeYw?o>Y;FDbo*R+OkMFS ze1*;COVG#dXQqWrKKX55+CO_ZUQA*0x}<0pr>bZ>Xfs&;xn6**#16`~fRziJl%xtB zz|CU^Z?zY~zonp1Us17x_sje|OgDQtId4ByG>$+mj&cKp#eZ^HAi>+*2!^TUQTKR= zid|DvQ`q#NluDm6I`AG@Ku|WIRk&1FOg35O16c=F21OL4h#>U7e6bVi&Wk1n-v!!Z zT#U{feCG@&v;>EUEx1V&6B8g(Esc%ydmNB*k$Uf*DiL*jBo7hY7%jyHrk4i4qQ9tSJ^=i2QMTAyq5 zZ^+NbJ3y`ibSFPIxBKP!ORLy^IPT#2!rBII0f~8kzl?}T)Q7=xvwNiMz8To7!QZd1 zuhXcUEHp&n1Zpfy+)#2Ol$!A>SO3S4>1kl3J=B=1O7g>EdBN4hshIXb{iW{69wpDib_khh$S!xKhcC3&i&do zIeB^bYNF=pHw>Wkz@z)&!+GfxTx|fAkpFfr%7!uxC_Q#gB{X3}{0U*fJk##-@@)V& z(IT3cSy7Zcx8;P(AybEJSUbw_xUn$+mAsx_q(d3_TR5#57|`$-`}q7pNOP6hJQN-H zVBbhchK9Thj5t|a1~h(ae)P|c;BS&o-Lb`?*@JEff_@_J0c{r-7gx@Ml|UH6I1@ov z7Zn|dngC6%abeHiugD6Z4Qa;h*t?geO`jABIpmTewjP%Mq(O1<@$Yd(MA>j5`1tt9 z_28s!=v}PWjSWAcrklTi0%+#`$l?UFohXn;;@}2*q^}}yI_tZsqu)u_rk^{~prKOQ4>F3};jAL~d1!5y5bqy(Bd?&UBlb}C^1|)q7 zDoOHPXAUTX3KJ1|Cm&E=E`_M=^=ped_x*YY=CttaMsX|T_s_*>&{slsxoelEy1F9b z#jwqx^@pTy2gjY8((eIQ0EmEL5^;QVewpLSTfRf?0*LDLf%wv=OIzD(GBY#l>r)tg zpr8kKOH#%477<}wJ@>K3jmq}h+#FcPtxL%n8C`A`s;a?g#JDeJ&cZ~DqY~Ad-=CR+ ztkclFx-0KLg78QO$#hw_?A+W?O*W^hy8L`W$X-yjMTduH#zC|{TH~fB80+op3v_9> z^xt4B1}!LB)tsFlzHz3wp_wBG1qz_|lu2>~YDb6mi4%bDLc;$Mv+!1IHfyILMU|Fr zSv_F4Q-H$U1vtpYnj4__13On0a19`loFA!;J+z6(?SJ%#a&mHHMeYFh)bZ(k)pbh);srmQDRPY@JmuTvGkoywK_6E&}2>*e%7mI?Zkj2PB;zkiQ( z%nXQfU=_zyaiZLVz9u&dh?2Dza7!5V?-(D&I;B=gLRW*7FzgLN&1lUaH|CUfHYvBe zmG<{2CEH;M$I~ZK&*859hnHd7Uy(m9I3h)-MwA1{#_%*Y_Oy*nHiTvWr655Gs_ZE; z(VT%V4;-$IZT2lE75oT{rmRr9gUyDo`B4wZU;%W3CLftNN|Dy5k@Hu zi^J||*1RkzM3GX?LkzB{ScY0_V`BrYF80;4XGfv)Bxj!6ckUW$hUfzEwp)Y8W@qcH znu9@TsF3jApxytCV;xwvT=UC>*F>p&u)G9Sl5yEDf@@;lz2EpvjY(V7`HLE3svXq!q6}uJyh}YF| z*mCXKwQJ9wg|V@2xNJt{+^4%Q`!Aog66>5#g&+3H* zPW>jQ5=%+Bak1hc!OLf7K`+wNB_TGT!SWwWHU>NC7)FrOcA}t_>=b}-`5;RKt`jyS zY=&yeFQ46>sDZ)=@6+tD4!$}tkWgYwFHwx$0{|IWrqs!dB=PNA&8vdDd zQlwbdGLBKih5($$6QiI&4iNr$e;!23rMK7^>OB__07)ZehPWYg1pYR91= zMpjRO(ZM4(rXjd*AA+Cx(77Pj6s;b%$sr`>@TLAhjB^56$4LM9@nfjx{;j=G!skNq z#LdC+YRD^;^ui6wZU$J3HSfj7qKE;r5>7fGEUb6@cvZ$8Dx=P2KWGWTkE3`WL&ht|E68%n!~D zoYV-k0rjJ{fXC#LbP5nkz{vBHlQ&2$Y*<5Kp`n2#$B!LDR$xWAH1V8l6r|D{;1#H- zsv)bBCs|F2wxi3Olzsc@)1DDajz^}6+&Azd$^-lN>*(s*q;V1_nN+O=HS}^`MUjvs zBh{?}1QtZn-VsF*3u|j@WYShMXAzGPE?TZy7g;QV8w81QC+akZ3D3N9>a<3MIMH34 zoK%TqfAQv@pDS;nWaB#OSAzu(MXI16ZQJg2dC%{uCkl2@^Wu=j1dYln+^Fklv#}n^ zj=o}CL}v%tHhNSt1U+SGP1X>=j&EUXoRF5*fIw3+E3Y8h9n7PyrG>1kZTIf*3lAch zqaC)7%roN)T0+GB0$~75@`)TKyN+=VWcxbV z=QxL(%0R1zy3+h|1Gps3VL^tgFb_nIz*$f8HIxi_)6>y09Y+)q(KHy^nLGo!t2^~( zKppY)Z%vV+QNeyo(rIgJ3p|ipKKcDS<7_@+P74clBpYz^xY^n1N}6ArRyr^_jAM_f z7qlL0Bun{@t_!=~jCWi+A|xavCU(k-QI4{JnFF?*bh)d71KMRK*g0?V-)SQ89rWD! zum`H{`EJ_FVL~TKp#TBUu`@X3TYdHN@*;~noh9j%a9qN-#A^pBFW!GezMXvQe+cJN z6L}%gK<5D^GTN)^%EU_KE$8^Zu zb4=6}4*4a>b2{)IpXlIyV`3IwaYwUtc?0No;RPycq{&lg-(VrmGkH}bEI-hGG$ygC z)U-nIz=5q?-^ahQ67T8eK_FrN%ugUQqWIvqe;4Xns3^H|p9Pws z7D1n;{0TmV?ymG78dHMtTn&~=X1}RD;1x~xp*5U`ND@*R*FG=5M>R zqEFsJ$3+kfyLnTUzx(t;oaAnFGaWskT!q4>TWek&%%xNt}nv1n10c z^v675kk1&dpB7PhtC{-;l5!$1nvJsj+sPUhU`)tISE!Sd(?AqSRS?yX4o59Bk&Z4a zTYO}rg$?_~(~YH0a>@QJ*#0bEq!>%EiZS~m#k;@#m6D_{-G^7^=awHHvhN}%WJ`=Y zUnv%|?I-f$*ZvaBz}3%um}AM>L3_5#-%!j_rys0xQw!C#6|_1~s%-2^z z5&B4>`>mpH$mzR`TAVhyTf`#%K6K>c z?st%E$qz=NT8cE?ZaKHl)P^hMg8(Y@q?Ze)K$B&@4VymqVH;m4l$E%iBdGPkEhHro zuQz%x3x*pzJAZS1|1J8Q(JWKRPO8>-B{oW#J+Ylo^B$39$IuvgyA7Cv~8KQ#2ljh-_98rGl-Z``<2b9sAt+~q;M z%@NP*iHeF^1w7@U2u$W7L{x~juZAU+C$2Khs;gI@3bB@k?7QNc&b$}Fj}=oMoPI*= z41{MJE!8mVMWeqJ_ZL{UTO19DU}L3wvU;>h4GA44aJ%85etrI`wKX|Ws>eZ*sYpbw zxI)SKBSQj6*`v#^B@klq_SGvCB0Wx3r~uGr;m_%Oy+Mpb0yfoTV&ED1wdQ*o&GsV? zGQ7@2r*o7r%_?ZS^hwz=amqzWB2&``wDZr22$pDf%o2g-x&BwG-o1FA6} zLR`VQ&i?#^Ck17QNi)aJJr9G(2BMFs5Q)5Ksz6i&t1dHW^1o{*OBck=&1RdjFUwZY z+iIUc!=96kFTPgy5_#`xD<{OLCgh#G>!)z&w9}jO5_+2O#NXTVL4sBvkr(<+hLqyi zFOaf$S&{=0r0=|799Ay)y9cSbCPwjx{+2d$9hzw%nK7%%-=Z_%r`#GIuD#RFT|HFh z3B~5@Lc`*W?AwYv5?xHg;lbH_LMBYq8je)dYSU>fs?6G#WoloROr>tF+j{>}7)jye z6L`1zo8RKZ;QY6WeoN#4zQqC_UCppKv!F0l{;R7v=KKBW#Zx91&ssuqLK{ESsaMevFCx>ReatG~vuD|vY{ z-%a@I=4NLV_e$2)p0~F@o+`VDQB;*C!Vm)tqRx44TWl#>b9nFm{WCb5kyFMl>Cg?8 z4TW-Pe*sd~+9?K>ExE4$K%soJcvFlO)%*E)-`kCoGKQeeasZkSTwJKFNrApKVy^uwTeH zvww85rL|;yjAL7^NO+%K^&#rCW(Ud75pLJ6NgZ|kD{FM-L5as3;h{a)Skb$alk)++ zQV>Qd7vrPC!@FMlZ*dY|j&bR%K{#qYew`gm&?1&@RTa;iW6IrOzNWN7(T<`a6yg?YPw3z-|~ zGz3EG1(GV9GKyW4gnx|N&;5jT^tt((yyU-5&y|aP&4=?^Yj<~SY}xMQwg0$B=w0aB zvFjQcArBb2YbEoJPmle0=Fa}f^W$8SU{vN4zO(n}FZ;V5|qfn=NS72&0k&lW04)OX!`lBcx-!O(&)cQ2qXo+ zy~E{y$0Inni;}-6F)j`-nV@A(7yR{n@;69BJqqr46^Tj;f{6SR^Cz%k`pKikhbk>M z&e+fpaop+W0+qZ>r1bZMF|G;#F2=@ZWJJ!@wy+THyyD@p>K%6MTXvj5WI%gEl*$gt z_+R=Tpe!L9(55{l<8L8=v*;Kb7uW0J*PY+KE?uwv=-JNpNS37P>Jc7o zbnS*!gLcg8y?ggK3+C!4I>sV^1*q3Awj-;`Jr&<#u#u;Nc)P54$wZIRFmkN zCYhCdeySE|S^6S+z$$v%_U%Af{&^R$5J;SyoI7^p^v(EV&CmAuzl73CLew z_dVEHJ%^?q-JhY+5p3?e0vm6}_2L#}U!H~RZN=wEeqWB(c8jb}(0T^6z0P|v!Fa5i zOKdf`Zw9QwwljNvJGlcx#AFmqm`G-|RrVa4n=9VwYv!1ilG3@;{x9ip=lTiat;onW zT7*-@dHCPDS{^0ia82;~-$Z>$@AcEV6Q4iVl?7Ptb3fx11Y8DB3ARc6!9QTU7Fo3k zuO2dHsRC6(XcxPjH}m(!U{pxfJ)8jlpo|~?OGC-drU$GR*$nOIbzEk5A3yp2e7^f5zOsSKYF95hZu9k}uxn{#?!Kj+x8AeBOc#yhQ z;iS=fJMk<=&yNeN*}|!kj?BF336tC59{DleDwKiN;LJmlpyA%%aYS=*_{yJ{NT7c&1+Q$*(xOod}vj!h2pMN12P1+jl0cOIF7P1`+aM4zl5ZGe-@9!ok-Q#L%R5PI$U0zK7Y6ts@KHJXJ67oQ7 zZ>P8`@x7m1T1TZsQ@6@bL~|X9f8AZJPoq z;(BwmqRFle^gKy%@14jP#ri_yITMrc9xVlzsYj|t?{jknEL=t)v%$5v46S&@AarZU zv%OIz7@A47uf~0(bLrVK<3CJiSQxI}{@esGC15CWY=-4iyB2@+N)fTNky|cS57$a% z)2~lmQ$17nX2rLbW@Z7E{DqwsNH2u4 zkbqMKW(&!uow%2aFbb^yl5x;zzi`>tajhO2&%xsjR)dVxxyzRrA3WiUq~z4(*N8kK0xA zR8vv1Uq+0!%r{v0j+MMx&W`*1(VF3EUo+Kn^X~PP1ap_cCzs42>_jE|>NziA15;*Xy3`pLbU?~i`Yt< z2a78w6PqoHGxV!$^W1974|ZovU-ji$;};Qe!AS`TOzo)4Kdce_%f!OM89@rSIZA=L&JC29`SheK=3qpA%jU3Yfg9<{S$L#CGuN<<9L4bj+zuDQ`P8W%rGLNmLHvd^Lz?ke zA~!!D(_U*wS7#U+e{S-HvfXLNSzT&Y6(RNnf{4f;VFiH0%lHSKrQHW=sTX|fg(8q4 z`XZ8@Jet8Y0sHSJ#KWSjY|PYW!YgmJ)~c8(OJpxS>D|S=ZyTETgh_ITF;sP1Y?U0d zC9~mf{cnXU?vYNNUdqacV?W$b;68Zp0FjYL;bg{!Q#xfbw)(Bs`G@3(u&^*NyP`*q zq+QsjV}NmLyYYYk;HZx3WSfJ+@85dBzXcFs?`GVKimB)(UFX1Uqm%6ws@p}g( z>Cn60{=jCMaEf9Z9bK}dV=u5kw=a_#z4H1YnqO=KRAVx$;@$eM{q!3;!8B|K8{oC=| zQ7flg`_u7a^g{;Vv!jOzuJUQfY`|PT*zAvb*7V7jA`OXrN66Dcw6(*FiWtMF;@6N8 zj9duxs$uo{7nf)40@URH`CGrjPL_&+SMKhT+ob}Rm-c1&iSzS^kUqQ1yBWWzn9lz- zY7S9XL^h_r_yFq%v0gN1J0_ndA|Y`Vp9Ry-ek{uX zCi1J9D!wj_1x!F2`fgSA(%G6ys_idV zE*`w7= zy608cPV>KP{}*sord@K>NL*ceK0mE`U8PWA+XW!vK#7VfO8zpP9Bn_n@_GAwD7T{K)~BHH zl#D8c3CHRUxC_=|dR8-HzwR^}ft4qg zPN!>3-s!R!KH-(`(q%SGF?30<%jU<4mUWYQzLN?MynU(Dw~Y3#JWO02^tNp8TQvT= z9Bex$I(nfz_A{6`*z>#2xX8mj{_^OV5!?0trm7pXsj?WAvIOe zx`n!A|JN&tr+7%jM;4(;PurV!5_KJ+`a(~M9ANVJ#N`_i7e8to$KMcP&eF3uESvTg z1SENU_68Lb`JH7CtLj;vvk1({QsyC9bP;ok@smhx$uyMv|NI9V1M>f&EG3HVC;tpO z6PmHyRPoOUOnJ&M~GH-{?F!6-=C)tV^g zz1wwQ-gubXe|K$tNqs-vT9VM_H7OOgvow^=8hjuAU%zj!{l(a!Yf|%0q}?z0{fepn zl#H1^i)>qHIlWP1{>O{nFNswaCq-nR)cw8x((fvoWed5!uPw)J8?`qovd-^j7A{*AxpeAC@wWsHrQv4X zbdMa3`n>n)y$Phwh2;BFDwe0awDRY7d*xg>q?l4Y+Oq6FZA@G3`#Yej%0cG(z*^g-1qmiCUp{ zr4rETjjE8H-5*hom>V1_$f%HL2U-e6D(&{&T+GaTI`@plvq%O;F!y|&n!=pH@<)$g z6I}T|>N`+ojgNQZs{KAzHhe4~V#-EF@vY-OmY2zge5OhWQl+22@a!7Lz)BP&=wczm zg?Esd`5sIF?$alo50+T8EjE;t9Kx1WR@QANjvRDkFCe)Z}z>rIMSO zIXm7OhXGJy!ripAJKJvT-4_Zr+X?T5sy!kb$!v&eVc?dR6cP$QrlmUr>K{>A6yzy@ z0RPb!;Fmvaw(|NCqlF~kK~S0E#-)p#LTqA!o49qwAi6h@GeFg0 zE0#9%@sU5lS~F%*?UE+kiSm+)mZ3=#0}tZuB~aV+fH_rgk)5GXT<2m8^sIz2ta zxnR)S4y|gO+CL^_{g6vH4jrpWNu4X268M?Sg?Bw70f2>S3FV?A#L9ru3t(X>5PF3h zi{}p9^PN^xv^-~qz!YLMpyuCmMn*7=!-+Tm1s=L+Fw_@cl%rq(CyJruAs5FU6&K@g z3}F$WL89Hp(RPjT(K%^pX+Uk|5bGWr0n&haR1d@PFz&`h-X5b(;W{|Kv0ss9~ydfU4F;PAmvxG@!$UMx&DR{8z6Bp zwyaEyju)#4D2D#AV<|^`pTL)*uP7svC??o>#om4rF=ns#7z12F9)>iWsCVziP&QZH zS%E*O>KOCV&!30TTtIs4;<8)HiA5 zsZ3nl7gt$SAxM5is2Faymps%09`b1rk>U?_2VP!YdHFhAYAnsj#~_uFsB^;TJ)SLe zp@=z?l#?4Acg3U)P~Qdy;?4u2E_W-I+3LBSUn7CG(k^xz`I_2N0_bg2O_boXFPxiC3~9!fA0GsISNCDmAY zHCUDq`|i3uiDDGb7DSz9G^|?V5NP8r0*dZUyMD# zqkhz>3M~hU_F&I-m{CDXJ2*N%9(0x&b&p+rhii{y28yN`cSd&cP$f^@8E6o($F$;& z8aF*$l)Mlt0`1^)wqqww?3OsMXKZW?Z138&syF8WYM2-s?|OBBAcSuwB62=*Cku;c zJ8WT?*M=Qy%Qg=6{g+%^{1HC=(D)!hKu{2k1D}M11Ooh_1KPt*SeffTEG8DiT9uh; z1tNB@ghd-Mo`y!H4HkV zf~yklL?KSrzyMQrqJym690p(D>c~BlfHbf2(^9;&krAZ9D5DDR7_03cg|>kJqUQ*t z2hebbmGG+Fd}#9cGY}-iG~g6^+0oH-n=FDD84=d$MOxGmyABeUKE5_m;OY<}hIw zgVqKcJ&;%UuA!Y0&JykvYGos5wLv8c5NW_7B0Kfy_4{H8_wKc@{>H*Iges0LMbx?l zkCWIWm|+}s2ueGaFbN||SF*$w$sTu#CM!`_jUCpJ*Rj+*>eIxe{UP$1Xb(E}9&OkP zfP0u(3ND$ehCjC<3rUQO0CEOE(h`D(ztcJgvkOI)lmal84A;-^6yhSQ%y3we$9C)7 z`|$@8N1tK@JDvsXCf8)S9#nZQNih+r&v2ienYo{we2Z@uqs3+0#@NU~1L#i)9dF;# zCJJLd5>R1+D0)q>TIq;Xj#BupEC)N;lr#Y=a1%3Fkm3DhxAFsSTqX*_E9pmh)&?e# zfSS10Q9~jDAm=~ig?vU^k`o>w;fNI|gu!i+t55d1UXm#9p+m>b)A0#2hmZyMe9pca zdDx#A3HTyO3N<$`H&}MP@lpD>QE`FlW>{EPs$8rB28%$e2fn{{JT7q`gv*%3Lb?L* zcnb}U<1z9=N!9-{HC10_iTDvrWrmWj)gt{cm~QMc>S{tmu?k+#;9hrFRj&>I340sF zXXtaWff8JC*+1Hl(d}8>QfphA3DNeGFFy!k41fe?!)cs?K<%ccAt5s^d(opMln&PP zp*dhmu>ZEP%?(ye0cfbK2tw}-X5XhSaDL!1)uj?p@yhHk_bwtJQZxs<+_HHYhU#Sj zK{=d2eGD*NVto9K{52HBjKc5HmvCere4d+;aqh|$@jR9nhaVllh?yi3GaFk{YU=C5 zD5LHc6=5mk+^ord`QjmxKS2f?8DWp25n&HmdI*gD!J@;aE%f2?GjRzC*%|a*^1{O3 z;5u9fvh%X4uBAn_epE!K7pHIYWI*#(WTiw$N9${AmzkC5O^hDUF;G>d?2%2QB(&4# z#eE*Toq3pUHp*oPrhe>B82TNNZFuHPPL{zj1n8cNnp~2?*@Nvi@-b%8hy*cm$tn|7 z&M{x=D=gwbWy#5TjJW^6fh)%vQFM-^zr2cJU@tt%OoX|WK7p7*Yy-<3KSm6^6(eZt z>uZ%3gH!o2*JD}KKZo3kCj0Z%fMP{aKI+T#1K-YM^qGCP|L7Y777RN;zHn`=*zyo? znAeB6bsb$@%Z61{iFHYkv_d<&*rDZh06RY1$ONy%iGcN$nO0U|Z>IqV*}2)89%{567<^s@iB^vASFfVVE;an8YL=T}Ozez|kM3ZKS{Eqc8Tlb4;*tk)<@Jdg>Sv z@Ak5*ON;4-4)h<9q0RGik1 zJoIgKWT(brB`qX|qW!Zu#zl_%bj-LIw@Bk#4d64>Y%eo@OiOTkq_Sa#1#%8i5D>G%3N@?-JK8PYEPkG^n!-U!tVF}Nn&9%5DC?);GM7Hvnv z`H1Pt=!uJ#6N#hVQ)wRcn^>OEnIaH`JRcX#AmFwuK)rE_IxVO!A9kuv@1=bE{`Byn z_$LIHB?bZ+RwACeG#T*tmTPCIFj={uZt7q!p~QyZssiC%$8f?Ks~Eip!GbOLcJ%0m z&+AjCSPJ?If9~>#jd6LeRJwSVbPwA*c}&+kIQsYO-VOf?J}M%yptcs!S(`>6A=JW# zMjDQgwu9J8qKt z+)@5Iac-^VD_ymihvIIr{_;=Qu>-(r;Uq+DBPk<;l6xImK?p^=P-kJ%@eOlf#a0gY zLw=`9MrR3ukObdJdZ2iX_M^IkL4rN|_E~p%m(o>Xbny<4pVn;MJjc#v>o8$tRE51B zMhui*k&kf=-YvP+zeNd&?~HQon}FExzFi~SU2=po@!O)tXb(d|8=+(6=30?whQtE*K~eSXmm?aeVyGnZ7Nop=dI48)!Gr(($6!NI`#D&8x#H zn@_bS1?~+R!Th2k+;!+(_C#{&Fy>s!m)-8d*`Ki^r%AI_>VvSVBLunmU5!sk*xvP+g>3O`52Ok&oLoJEFqE49DOX9Ch(9n-q}qTXO|}p}XJ5kEDvh(_w|q@p}+k{oMU(Z90*}^UBQZI{>r))nID^tnhzYK0NX2=FV>u#HDjQF+0(k)VQ z!yw}JBv?h1MH_BEB1NSxsW;JI%y`RUiM6ZY-uY()^(?!sV5}oRm`E}Ak8Yd_h)eH| zorHdx_Dx6B#5;Txcr7B$sdj42$SNR`7F{vQL*R%-MG^g{b&;1g>XK-mEOv8voFWqh z44ct4j4B$*q1G2`#~wU5G=$MAZu-w@-W|bxkdre#6^CcwO0=#>EiMBbVwf$3P4LK> zZRFlQ=INjd=<`BJ!f;MD-<~ z_MMz4jo4q!^VX3_DEC8oD|C&Pfvw3%)1nJKxi4yCu}A^|dmTA11qy)w8XPh3NGN6!O9zvf;fn=wtA9q{$OsyTKCDygyXI)Gfhj<>lWf32 zA~nenZLW2{^9I%e3}9T7o`6z_Joyy(uC9yfB_#Rv1i!1pbIUE0$#4-Ih{9->W)Hzon7a?1 zd*+8dJAFTT$xIJl{9f|B-ICHsvEui2;Qq<{8X|mAFuSUJK5k@GG58c37jnKC5;y2S zWiOlekxO`vvkNU$gviI=zB#|kgU{ep?6QSYJzk83X@Nr4#AFSj>xc$G$|Wj$E;40B z-oW10jL!Z11C=oD%{!aNez>^Mr8giqxNqjm5@zu>BOlc-6D461dYAuA17j2v$VgE( z5Sl|0*k0T;DwZwdTp*MGgdQcs|4-p_8`J;A+;@j#-S>Z=_RNYwDkF@9*c2=Qy6{>7VPqy03Dc-|uI< z->>&;fYdM8+||YU_CX>v1(3gR*S{JIaKaP*%?ghn;JbdNZWY0%@__6V(l-6O01I?f6Wq|;+0Kgk zMn+(Ee-&LYDAg6?MO11~?p!$0Ja-4*CJ-wLGjsyxMA!kUW80E5?(R+FI58aFtuvjH z(>;wE+{}z?tHgYhJORfKkcP7jvaisQ;$(jLELl$qM?fA$m{z1qJ7}QtaB6ocOX+Zl z6ZQof(0Nz&(96<|J7JM+MEm_9t zJ8*S~4fdf@dho%c^>;o@UQ*#v-o-8VUE1sV{ZOB)RzpFJ-Z5Utm8gh&P$0q5OnVug z$bVyYWlrI7`A}?qodla61ng$FHd)*4obD?eILxZj{5fzj7KrG^7;9LvqK_klUXl)} z9WIu40AZ9ac^hxBrd6w($+LO!u6I>NMNp7pCEGsPT5-U;gB1my&$X2DT>&xwaiV01 z%?7VTk&nfXh49-g_WCJT8?5v>0ec&_q z4TjA-vyQ|Md%cw8=H&FlLpt=U`>^tR!MnREm-d${d?|{h*!{SJ4o3pjNqXu_g&B)y zB!w>0CJj6zmRtXlzBxN%h-+i{Rs(vam=F$fApni42%>E#{G13))2&ICsn6 zeRCJn$4p^W;CYXI;pTb~|)%*h_~~6V9u z5WXj^ty(%afv*A6`gmzfJ?d}5)6GnhilRDChUS1n^NegcI9ae40J3+LroNk5VZcd| zQ=tJBHO{H;&nW&m$KpBCyTCV9X_32K@bR;aJ_w-`hN7npL{2eH?>tXIGRX64nb+4< zvgzL0X}IutE>>o4&dWc_mqwx9c$+otT!Ysp%E`+E{K|POUh&LjiY3&&%Ue9dNCV%Z zg1_DSB0qkS{^L_a&*4VBv_*_!@7(oo8}z+$FJta>5z8=Vy$0?C$0}oeH#&hS@3kel zCl`-o@y*Q-6kp2c_Z|8-m-6s|^H07lwGTNbZRD1YUs-hXIPowmgIX=BY*W`pfb_-D zhm{%s3ofT-NyP;$tR;|NWTkf5vYAhtszI8-GplF&-yUR)K^QbJ*f#%8TLmt3T3U zGMGe{7!A!!7)F8HG}1Wfi;s!rjtYV-RGWU1gT7dq7#kKJGm?~TMVwSj#D9M6wR)cQ z4dXz*1zq}smt+Ef{(>8U8hIEgkaM<|uUes$#6{GyoA72Y>|-4Ec)4h8yxFDc6SYsH z701T8$~I9pYfH=~E5vx55>3)N6vEcmz{Qej#W9c201m+atrq3iD?`1xdoeQg+kseT~Lq~m^-;8G9c0=U=#qDF z1x>s$kMC*`)dI~s8pC@QFE~QB)D1gg>RPLRPUg>W#n+bjioVp-%SV3pR-GORvVQP( zfv;92F<_|q*u=tFw;^>aobXzYpJ6tO7T(nCT1*V^+T?@&Gx)g5WxomKHop=r2`})urL_NtZ%@$aPMgdq2##;{ypw#;atPK6(g@qPw%u`B0%= zE%`FQM;%}a1gt4-dS91vS;&$b2{P051&n|1+`c`wfW{D|FqoeIF4AouonaqoiTaEJ zJ2x*cJFZ!n_(CAU_t*WwF| zNB`b7w_Wg9!z)t$`>hPUthGV2l~^uBuBe1U=<^}3G|cBKO}_ ze!XL58@iyWbF-qhr^6Yr1`&IY$_cg^7&4>sP=N~d&R2{Sygoc>nf4>MCk(Xycl8m|oA8abu8lo=_Q-9$20Sw^Cg!xeI}x&0 zT`?eYe0InRh&jp-=kFDuP2m4# zwKz_LT8+LKa2S9^FR$WkG3*?ec{XD*kNh?xQJSP3JK2JcnDya1qsMdzqA)YVn}iqv z&-pzLchvZ(Er}mK3_MLsA*GLH7qI6bPX172`QK_LK-bWLDmciT_wH0cabrsqD@?`Vq& z{kJp_B(ZN_S!rp)-?kw4D5l~zL}bVEq8m8_uK}D{HCxahbS>lGTjO{KCB$N7!B@s* zh`2tYr3=!%b5c$KuYb+$VUO_=XHGfy@%a?|D@?hU+5;4`+%wS7+C}=+G`5M@% zn+~O7Mg*PV1#_@cYp&upgbH=On#Tzz*ii$6Yj`a2@p$>wt`9iqB~@Oe3Qln<+FKU& zV?RK(V5BRaJ19+=S$4rx2?rw49Re7v#P-a;J^s-6z7~P51_j{o3gT7@G=*d85 z$S;`j`@YONA$#uZF81$jhsDoN=O#V2$CVa>A?I732m+JChSh!e!DAi#?SuU%s&XXZ zVFTy@mr9g~j>U=lJ-;!aa$()JjM4gmd5fCC=n18Ie73-*v3j&X#?zz z-VHioN|=22uAw-;tn(|7q2ncl2k+u+9toZ#Jda4YNg_L3d<(Ngbg<8?pShXgF;-?E zGV9-0sW2Rr78+MuvWo^00Tin`g2E*(j=B4HGr>hjgHm}KyWf9S@AnGSpnsr^rrYZD z6D$>EZ7`Bzv%ihh4wY@9xuh4*cFUaJ%Srv^*n^apOG%DV*?ZbBD148rqH=Zu7PRk* z!>K#GEA1OE?T3F404}g;NGx?9TN2o0F&9!k3#2A9K_LS#fR2uiiWIv=aewpH^?AQC za$_YfK1@X{?*NyGO<4YYNz|6;ww(kk?cI}#3)(<0{XRJvb(70_Up(lw5V*8oD(R&l z6->vKW@QQF`FOVMMtIkbU{F!E2Y*@r`y)`1Z}caBOUHp^?h|`bTGa;8{$g4v(+U{^ zsH^#Vw@G9esw^cU5a{_AF*`!^JWDL68IYb~XPcGCvQ6PzUYgg7OAG62^f)e96kcKG z{Ve!ii4i8CfRCSTitKE_iiluCnxv(rxO2{^uG%95TNfS@1+4&;wuBL_z(q!T*HJy5 zdTJ6q+qxYW4@t0imRFi4n!5X1p<-Mq#(zL4b#DF&seL<59S%VUxBx+**NBmaO;z|T zcIhW)^^JYJfT7*J^d{<*C@UmChVNG~OcMxn>$62l)N#M<5Ti1uEB*TX1Lp=x2@pT< zRV8A_9~K07+RRg4xZ&=h$O^mZUBayv0LlRtG>s`@UkU5Jp4!>czvQ1dHO>NK{lRYXz!rm`y1UGIEHLB#cM` zaqLDhBK{BJaMi^heXzK+gj?HC_0QCOOw3JmH-0@(MXDR7pHb(ke$I|rgO>-g*Fj7@ z;l&01YB5c=O=+6eVSQ_}KHLVixKL2c*A^BPb$BQ^#i_P0s7Ot4X-3u@gqyNv2nO2vn)Zl4fEDjK-9>-E7ul*4t%X^SFC@YEn!G@R3Z-T0S2yye)G@@;WZvf$SV- zl0-OlctI9rL->?B4arV}-q*54bgfB@t$?IR;b$tUPa{CONvT$CmgL94aJiANjs|z5 z4v{*zl3~dOlOH6OLAxx4wKYjKzB>}PqYYFYsH}`*v;41{NVJ_@In9hpGPw;BhuV}M z6~9$~D2^2QkI_08-$N4=`cvn@+f}&z$hHEnCC1cZy3WqvHwhC*9AIn3z(2UHqD3D) zd>D-S^|9MWc_uqd5XHG5;hKE>ccW#Hsm3 zQ26Rb$ll;2#gHhpNX)}_Mo7mC`3F!}75oW}Xe8mm%E2E$9NGrtu>FGwA?$nQ>#TAxE53@d4ep--E%91>xv^RDH zEUOSyQo!z}QBs5COhUlecO0jv4@$uLA{q)#&zpO<`?Y--l$p#V!xH*Wu@6}5e9lo+VDO=9|aOO z&>Ks0j7#7h1KEYPkn!))l*QN?rh?FdRBhj~NLhKIR}akT4W>_b9gVIKJkV`;c7~^q zDqrFxheMW;k=pI5A>ONO9Q^#(u1hNlz%Gt3z(TBmoFH;I4iWNBaQ`W`_l)8Oeg>o$ zHT#`A%V;07?lMhc=KB7byZQcWnUG;)Y1%->_1%mv-Zsihc21XbPCn_0zMyDoU>*4dC> z%QRF8;@Zr95)Go63f_GVs9gZ{B3J;V9wZIwV<3t;q4BHJrMxAGVI*P!g+GO!31o54 zgEg!bkz?N_KvwE`Hi-iT751arQP<|LzXuyXGL=-y`HyM5GAMgDy9Am8enD1)aLllY z6xR#utFC5dch7yVG)Pl`sUAwpr`>RV3%-@0U9Fc^&!C}={9YGCrgl0hJv~?YU<`d} zYz6sqVYV1nLB-y0juW>dpj_(*cfo*aR`d#mgC9}jb_sN$7E(1yR0b}QO3-M8OgNtv2E!23OW;9K+P!3ImkyGF?bD?FXw zRPx^fc1!-!Zj@Imzp4{jioMjWSEmQL2KZVwPwLJxIHcCsue%)|?OXEiDk+vIJ7U#v z_hPfDrD$xnD@(>WbzXi%{>_V1F;r{Oj`l^KrgmOe&!d_Q?0+X$&=9TPFebwi?Dgo| z%$o|4-1zt|9Nj&QGiWe*k`q}QVZY2;r=M>?eP-<3fvR*6=k8l|;PeBzGfPDe8pvjE zpUJ&VxsR@9ixFN$=stn|w}W9&Tz6S78%~NSuPI2M!ZgGYc>x;Li?%zGHgA|F?kH;Q zst;<7-x040rxJ5?71Q1fO<(nym_(a(1t4f8ql*J`s*b(>dt`O~M? zDtGv3v!qF+uLI}fV+Wf@TckQeQw-Bi+wsOl$P`dg@aJ=wG0^Cgu&IO#iOYuXc-qcq zxpMdI>9g9oB1);b@1<4_Y%F}8-m3XPn(F&wzt>u{S)^m6szUz?9THbGYV%rH(+7pZ zM-MZFBT1Y7Qvw(Tg0%#F;pabWd1cTAV-ms|=1%1rn8%z<&#E7LUf=)xd4|n!(*d(e zj$~2P51SnHBQ?pc5b14^w1(C!5r9O1a$;e@;CcU^OB}{Gb5IC638Tkt9`ml4)5WFG z_>TvCBB)9}9#w)=sD^e2^?*;X4la|ON7H}fB>KsJgIgbg3|M{`sax#9jpeP!mbL;Y zzGAkx8bU=WS*b>O*7BsMe?PV9t;FgO8gtD;V{SL*WKJ*f=>oZGUP=LWBa)vQCs$Tv zZ_B6KXH>Hl1;b^iq+>`<jDv|( zcpPkt6`XyxI6gq{h4X&Y_K>NCmpkYN1g_u8?36ljUt}2~7DM?DbIa(7tMMoZ&2DaF zMRpb8SORiW#0_+_`VfL)#;=%)s{mevr_pL5XaJU)pP&S#3%=*~t*$Ca;bB1mJUTU2 z1Hmi8=Gy=!qR4v6-JKvLWBOtIFat9Q!~?Ae^W${Kww1}itbqu^0ExblUI|xr2q1+Y zkc?Ij*8v;^{KF)cW|g)^B#R;vFmb!0w>^F~3>$=LS)W+!!G}mi{xEKhcQ}B278$8S z9n#nw2@8sS8$WK_$4=QE;d!)=h1bTy>W~@Iv(R0dzZ|C%{(Xc-Gtcj>@nZR3KbNgE z`Y(=2IFOj585`)O?>B8x+eK0$c`@th5u!8D5KCL&TmkPNkobuVd(^30EXu4hy`7= zq8Gd=VvsC7hN56s1QfPE=g(xcZ@v2$(@&yjBK-!*7+?Dz6X_6e4uDC#=WI|J!(SMh z!x}spTx|dw(CWclu6!tXv>e>%`}JNg`<&xyapz-A7zVy;*(;@c8Yv=(8Qu{zdJ%L@ zLY9!JVs%p_<7iZY#G6sjun2rezwBNl+fOVYi#>NRa5L7$GyWWZ=#Tb;w+?K4Pq(2P zBA_9RzPXJQJOwET%>o+FzfC*VVHbxSqCc;MJ{x=P)i|3w-u#a2%_F)mA8-P^f>QZ)P7^cl!uR1j*OerX$qH-Q(2as4C$%!VK z#zyy?<6x3|2#W*|?t~zFg=jk4PkfMil=*<@28~;%{mxnfN*KKwx}%a^QCRphRtmP^ z!S9(liP`rzEBw%djoI?h8M_?wqS3)Cg(|o$!C3?Ec+4B3VHj@P&5vsi^w!=z2`hd7 zt7UJ$@-Sb4iA!5#>)681 zo&Yo!P#kRS@~bMoMxMC;+W8=O%RWA(9WG2PEJtCohxDHkeof;<5WfHeD##&l?h5LA z6t*Ch1_mEszxDcQ*v2~W;31Fa@Fhh345qKU%jY|zj#C!=%>)jL!iq8S8)l%9mNqE=tpKR86pH(L0cu8^bLdDkdH;^_j)K@LQ<6 z-L~;jD)~|~CuJOa#%{{YpR@HzTwNDFK1)-3dfsA|bLNV|G0BS;W|{hj{TJ?tuM13G zvHqdIDpirkZKOSS_M4fsFPEQc#&i%=x6hvw0m)wF_x-5NwZ$(thL?>j);SIjHdXr+ zNBMvEI?o$uGCmMiY|kUCUvlYk&)Rsu#MSu2tP-U&iKNIdDa!D0jcAXo*zJR%qL!?2 zY?KiXN3*i%UKl06A#wWEk?zy}E@l>&I`iFZXp3J~x7?lsBH?WIj1;%m3dpwXhi~`4 zu1ve);dyD~tk10k?v$Qfc2#TIYKB)V%Rk+(PA*sf)h&F+6==lK=ucmq`Zr|%WEEf zJTwP7cGAugrYjUwT&4`mTjW11t;_oBnkMj(`zc7;amJ4OO>WsfKi0>-?Q@{J%LYkH zjkcSeHsT`ron3|{FAqIcwi- zZRYSSdXb257BRID4@I*f(|M&6ywnnGv;w?5;fzn3{pQ2$9@dyl^^bhyT^)#|nOf#z zm+G4itxGZ?2fy7(b7?VKjL|NpSr`@YW!g=H2*wILSMGPki}ThTEo~Et7@-4`9WBl5 zp#U5Ve)pVhsHn%IY3f+rix*9;q&ADHIc{3a`}OAgrsTUF7te0HR}{l^Ade`f`Zai;WlS2ig1d*eTq!Kmvo_Ntk9R{ z2I-lf=ct+4T+e;FnS^wJLU^7KnFX?0C7gP`)%Lvmt*IBEclP~WS*=_9srQj@IQZl$ ziZIxN({;RLWYeI(z`61!vsKnUi|sarOv=eOzYc};zhu36g>I|apY@eKd65(SyIs(N zQ{_=z-W}*K zy;B+-RAppJFT4NFu3iS*3F@(!SpGhzbL9sct=$4WCA*Bu)(j3EN@{f|IVT{gdfZ6d ziP<^wcLAIJ`?$0<-p7Gq%AP03R@PTe>-4-P_dTSoZhW23Z)!)GMR%^AepPkymmbTl zP+lL+drqWgv4?RFB}C(<@irN0RHU;aNRM|rDn;W_*-pp(uz0D^vdHpx@Z?{w>}@SM zt{uU?sK^NBe1&n*Dx`b2BetBL% zEB%MpDUY6|XUE(I#A#hjd4m?{+`VNcYQQHU5*N z2wBp1<#Y1Q)|gBW?6Ee$4CLGBR@h*ITnMIBF0Ie`T9|0JdbS4&i;F+BIr)@nTFcNJ z(e}^(yb*Rp_=}qprJvTlFLkP!0W*c_`m$M!X8s&%CH4V>4uP^u&IazE)+xv|c9t3P z${*D?we_1)&gnhubTHqf=HI}Rf0Xg)n{RO+zSz0%t-Qm3@=a{lpqX3uMd6(hyd?T5 z9N6LRNncMERp2odH-2pbID6)(h!{KQn2}z^))ZeU=niV9n+(>6ZG2zPI=z4J%it!` zmGwVflA$@9y_t$!eQ|s2{>0H1En%ER-5c^DdM2k}qtxUUzv*p7b;=!P88Un3dvwO` z>1H_7>?mL(>82la*_D;wz>`~FHBk|2&LyB=A9b}uwnwi$^WDI`V;7~fWp9tsiKX1Q zfq}O!UEi*#rqXjOUU3wige{Mlu+;kIac;x?TWwUD!WW7JymlzhFb|3`dr2zS%a6SO z8ZIWFqc6{vn!)Mz(cS-zdhofli4l$SVh#-=x*Cm#R`VLN54M$`v*MlF8zP;r#UR2P z{YK4rje?I_#qwe}qX7Hv%WF-s?S2<>=B_krtI*i1iCGKE%z!vSDqkHhu&lP{aenYN zrjZ)3DlG}c!jnCl315pp7<{R7W_~{|de2J528}UHgNGQu(wrGNwW2ltM|Pg;$6ymZ ziK2jNa+A2a%#|6FEjDh9zgHL*b13RK?iFbGk$>!ZD5$fti+b;FXd|w3K$=Bs*B}HXdfkQ&3_>9EcJXk$Laf~G%rMUvU8>1 z+~6LH+qXXZMMWy9=3i5a1f^^G{KB2qEjBmeJyH)UzTeo$6k(rDz`K+Xf?pDlc8yxL zdbUe-DS*_y(!`~hdhIs;41CwiFp0Hm{AaLD^&pkq17-$>%B^w5;m=J-8sSS*yvb>H z4mU5m@mEdsS8cm>crBdmhFC(A#^LfWW~ryCeg3>klomL6+r)^k+2I!Dpti*=;hL$q zB6ZJ^fEQKeWW&bpPhD#p9kC4k3?IZm5jtcvrtUfbNZva*9B#LKmox1G_U2bNi@cGp zwS}#TQov-bw6yx=Mm9dakHaGkHccghmgFs&i*EV_4zk=pj zmNLhDR*hOXn1z~RZKBG{d?k?1$F+8AS=E{**^tqd9JjY27;X5YN47 zE#-`a(zijUN9?Rm(@_iQN{S+*`Wp3AMnr%eD+ft&5{I{=PNIUi5xE?ZN`XIflRKtJ zjO=WbRM)DxjN~HRGHMq+KfEvqNaDzyV)om+H_yQ@uiCt;I56@@+(|Ee7`OjHkI_TiNzxRZlIY@E_u!Hrk9`-azEhvC=%rWN4RT`EC zve((jk7|sQTa4sAJjiW#A3lznnp#xGzwsBp&jX(r zhZh%3IP8h8?ND96bbN+yEPbG_Z?DB;!ZORz=G53>$OE3VNv6r2zL~UKztnF9DLG&g zAaK*BwV63@9hFV}3mz1*%veP}#?15pAB&=|`0^d~VU-alRp-YA_KFud26Yn+rXFh7`ETm0&kRTa}oUL=5Q&6|cR${jM5$p>Ktc z9(@BioIQb&1&Va|5OY_#U10ALi_QKBGm;6N{?t4OxRIT)i24}3R!G4+G5ZB0XmDid z16OeZ6S;`z<+`U zMMGF=jPzqQi(>MT&K}+_js(VhQLdCW z42y;`4AC&$;2OYMoRib}OZs8MJJnfPSwI0WbcSyl{&(ke*`Y8_5{V^?!uNhQRqU6q zTkHc&_#(Da8C|p_nRfG@3TMoEqdrCoxuwPtEvi1a)uBUR9K5QQ^Oe}@9SroFijn0G zHwT-6{g4Eko4eLXU6H7rV4bS+@<>ya&(r6TYRR1FO&#tCymRTtv*^v3TwRNXBZu;- zZUJ(8M@OHW!M7hjVmemHv=f0fd0NSz7D6^2omy6ND3@t1u9_(ToK0!~>W>RgbLebm z$MzFGW3BRAizdy2rR5(D@XN4p>TQKuO4+w)d)WP(9Ita5X;`)ZndUxHP#HjVplQdz z%b#&Alme{HdAEiA)hIb8gy-`MpWi|vA;w4A}!=xD61mmn5K6n1Mjsgr?P^~>!=(GMIDg(Pl1 z6&NG7eT~I`h?mD@Zq3fcrAY>Q1_r-4MDmb-Gc(759ds!4H&nzYrXE2UV`pWhM@B>H z7zdk+hhYDJ=Y<@pIyW*IqX39S!AXI90p|#u;>7ms0aplTEeIqvA<6Sc1%0i#bpA0<$p{c@WZ(`A3graf`SCD9s16f82%9g zN;^AMpYdiq@@-=e$)+#}x^>GJuoOxxn*qrG!P$_i{wC|xpE_^4170ua_chXDrqF$X z14kDQ;29j(|N5|GXA22!-S3qi7ktM$#9BK=?&E{H3-9#W%l>SRzMW`LD@HlBUFq&c zM~?2ijk0~$X`F66QBee4XetzB_-+l(Sr|g#oO2?h(<1QqYeTwZ=(ExDo;h`j)HPQO zZl;tJk~T76tA;b7@qR*d1r842x`4nKxjfvcTO+!^1AGTx2*(A;_z92GNeyG(e9*z- z4Z;l(#DKK4qRhwqG0@T~?A?3K7ozB6#>S->4}kyp=fd0QQ@|>5qQE&o)qmz4IGd3! z0*-(PY;8xNTR>3DI{a8avz0c1Ge;h7{`#)Jf8y&`taMNOjLCDmQiTzYAqUA6T^Fls zUx8U_-boi1q*(vLGjj|-MxDZ>NSuKd72fwOqSQ^7@Y|qY!Uv-cwg9j+zh<8f(%GXE z&)#T1Ja+u}bHuf18sk9LisMly1HSu@vjC*#+9`?udCb3-?r)v>?aQuB%3v4}cTx>8 zIzSRPm=N0ch|H4SuC7E$P3pfOs4S#?!~;rVUPqU2_RYVeBUm9O08%ae1LlwiF#L&X zV&~xr>N7J3?NZ25UA+-Z+iwAX3CZfVZHT`PE8A>noQOj@zkpCQ;$aPXJcm*YG)X)y z{AHj?mgM9#JC<4pd^ibxI9)jAkkBY9@?8fF1>A>au$}d zh)`1TDF;1%{=7dhcRRa$>j72#06c4`rU@GyZth*juXc2Go$>Hk16=}d{G@)edNYjG zz{XNj)^~PwU73EnOIrFd7Rat$u6TcQ3*aXfZ`tN(|HAvOY%96h0NP@3u8NMkCrR2g zd}bU$Ta0`iHW>i{!YrpYWQk+84KW3+a{)-N+JL#Fo}nRkeKW%4?k#|rVa6#cEBgfC zDv6DQW85s|ZdcdwpJmy;9o-we>+PIB#vJ56#GYECBDL4-3fb}YC|iPsAs;)8j?M>4 zpF56!Vu0DaJ}ScpmtW9nVy2)LVy8MyPE1e#2>LD(ur&vk@F+L&NH>U)nI8*|?=Alb zG8x;vn9uR!{i++QUAR2ZH&Fc-heVB}4HUW|c>;x&Q0VN%~F_BLM zN7Kc1EYA3t7Tbh?NiVrP-AuK3AbF@GS|T1Nl~Mj?IaG7u&|C@46y`r@8vsRcyRU;2#5$U*oAx*qEzAY4 zXXKyzG~VRoh%y`A53Y(-eJw}b7ric!joZX?xaxK^G5)i`L4pg=@kaZ%qrc8da3ln8 z&lL1<%-hMH@^*ErPnzS$cOKkZ>A_wZGmx0{jV@oWCFc{RJ z)^LT@_M6FEAwL)GB1@_BpX9zNmvE5On0p8hWD+e4A7ajHO$B%Fc88o2HweTIcmrC- z6AgT2+2>LUCft~Q$dTyly~>URJ6P8ZD7EG?%pMRGZCP{9DsaOQi{LRCYbA+u5ENTUzoW!mXM$ z=-^jmL6U!6+X`}bEJk}$0dkr#USHgAJ3#aE4lm8u-C3^9R84} zelRa>elrHfb9-m!l|}8oxq#|T35z$a?3$0jEdIv8*|>Dy!oC^*Z}1NGdd~e?u4Kbw zw|J(1^QqGM^UO-`=K}On61BgaQTw@>=hhTjh7XT0_K!v+ON`|VDw?I+QI8)#hE8BP z)x8)^jUvu6d_;t)p+u1mTMsQCf_=}8<*DmEn`@{tJ=|=aE@*w_bwcfiFQf3Y=5Eup zheFy73qj2*7SF>^S(UsA;(atK9M#(9t1W|I$eh`OPVLIqdYPOU^VmFMzPMgb)Ol^# z4M7N)#U~;zFmIK6bvFX*`^c|Bq0o1Br_sP;mI+-j#Fyzxfs#UmMgx2|&-m1?P1gNlHnVJ+v1{JAYo-97HoPnJZV#%bHy zA#>HlWcP7HLQc=|`4YI^Aiq22KkC?hHz{eCgztxwNvpStLE0^S9y&CVe>tJPwI+;-aq{Gq=k~WVFY9v{gK2>-4cY;e z&2cq;TBk8WM(0TuG1;#z!g1h?$vC&LvMY`-4P^{}g`=7re*G3=iRH^2o98<|q}GgkH1Cd#b~nwzmvXSJRv+$N2iVcG#q2N z4~&2;xrFI6Ap}Xj47yhx7}DUGQL2GV2^AF6&VQiI&VZCvJc~=z3T9KUUPw$_`-b}$ zH!bM~u$-f^mKGLbSM;t$D6SV9xBH>uM-|JN6u|P}80SfN?2yqetw8WXpb57QW_04y zz*c-Av^clOJ`P7rER6}S@aK@5m1@P(zem1AOocd_gPH!Kp~&J#WstAifc0ER@=Pptz*nx+Tof zbSE(pjrQh*4i_*;1`n{(J#Pmks^w8USmz!-r4R7gp_OA{hhSbPy=gL_6~!jLE0KHs z`0+f1RfNNi#U6Cy0o5uB3eRh5)|89$JlT9@&Qy$D!MeqyzsR=s;QsyQIdTV4>B3|) zTMSj%e|}t)td2+$Pj71Cnw(?6uqW((u`QVHLb9=E_ikTBzG$3saC=_tKFk_3EuP?uxadl znJX*1KT_HdcDH76>qR3EJG-E4Gp5H@&B3V;%tJETl$~yNw&&b0n1M6P-ko=%-;aWr zU1ePijTrdkv48&R#SLhDE*KwDAz%C{sL6Dp=WdKdwB1t#M z!0LN9gj}phZnLy}eSnpT3A=KpH}`6#g)hVUghuk@-*aYN@1zi4WpMoO-_1&!lU6^F2X& zLpz8&qnXMeB&3Q>yJWaMI*!gZ<#HUY$Cx$8Yt2|}i^BO{=KDXq40gT$*6BE;ob*YV zo6&DOva7XGhfKBbx1>pA|jqvNRi6dAH8;t6->dY-ndkrtzzq1#j=p z2e-5(_n7Py<%l5*EnNK-fg>h{JkBkCi};O%-u)|MUOk zTGtQp|KS4s|Ma==S^U_@BI{h+NDT+-v*g|O#qAbiowT#J;_BOz?%VS_-S>0d)0Z(K zNhCb-n=Ipm8Xr9xj{JC>z-WD8`9UoHB()#y2ab?Q-TpQn0*<@tC0pOi4_G^Wq;EvF zk(6JpNCrFJkPe+XIa*AFEI|J17$u`c*4WchUsZh9HRTHbhYJSqw=JHHlljb<)H-+L z>4BNZ;&Ad$Uv1;y6l)1B}@MQi)& zKo@Gknjjx5v%c`?@4y8SzCrp8MmUTl-@Wdzdrd3n^-(tTSGcHR>jjPN~#<9o=fx~QphXe4=)EXl}>tFu-1 z9Q1DJFp12cu=T~epo!aWZ3a-yE<`|(j^|K+p@q*_+qSzhT>C*`j+t~T`PJo~f!Qis zE30$xt3WTT46}dS;hzv6JQiBrz2q6A*FG6+Ot9;~dBago5cl!PDx4itMUpshls;v& z86b=vtEP9(D^F}T25tja2e^`F!90DurEsF@j>sNPj4^j?RtT~0)1i$T#3e4kxvQ!aHlYP^5T<@LxJ0ri*&P$tBf zN4;``En9CgX~Rwne9JnN@DQz{OS!RMvv+l>CVuVa(a8C{ty0%2!pQnMI>Bg}BArpS z!%m9FA$H<^OLJA2UugwlAZ5k^tVdRS8P;7j4T;Hzp<^b^*z%#AASLiniE+~e%G*b?p4?+EaIT67-A$cqEIsSU*RbIb5=&tda z$OiyJ>9d9L1}JE_dBXtV092z5Cn{~Iu{xE0=pQ|LoU_F`1?Id2X_*9^@(e_iDC+8f z?4x2Dkeh>yEfr=WZJrfqf3VFc8#e9#$hLX&B1AYuq)UGODVRJHp%vXEnb&791&5;8 z5~V0U7Ml$AIrxC6Yv3T$R+}>%x)+TuVZ4F16m{CSfODYA0ZJ@}92kE9U)rwBFy995 z=4-2qn3(|mr6d6z0LWH@j+{ufwTTuhKowKFGBQ-*gRLXz^a#iae2u3bLT z43}@SkJmvV&cEGHBn1=d{GL5fW={=Qm#>!U4=lkyjSvL`UBwj(7iA#Lup910JR7Ix zY0C*w4&nkD3f0X( z0syXh_yU4g@Y(xI>{OvP2V8U#=#K5Kf55}~i*2q(LmCo3ups-ywCYr!9p;r8HwleOzKQ;^G@36pc+yP=e zL=i7Ti!oWokD?&~m6SI)ha*4kx|M#Xdt(-7(XZFL?vmg%(+hHLFf0!jdhkpdoX*0m z2HB_I@OBhpHJVXQ!K&;6Haz^3AVuK@ZgBqODEZOUpb^LhI7wI| zLqG#*Xdsq@Yw>oWx?jC|j&LY-J+Ws?Sdp`liOE?^?fF$g5~GP}22#Ugnp*l0748NB z71rne6Ax=@ATIfeM6^(d#YbQlv}kij`^4euHW7-f(v#s6=4)}Egde*NK0 z9T8eQlEg>dK%q#YGHQ1Je5vPMCVkcWd$qHlor)R!Y{JGOxj%aC7lL51oiAE2hmypp z84Ljm*|39$iIsK#Uk!Msd*f%ta$qzpix&n3C7KxMWx;3%pz;adhxjO%!fq0jQ4Kg0{1fWAhl6@RDU@;KCs$!%u zLqym$=1t_%VlNR6W*tlHFSi5S$3%3?gfSdo9v4|mDUa|9O;X><^v11Q_fMxL2Z%F@!v`1$3r=Fh4DWNj zJ|E2px9pAiDAnkuC}v(0Tic_VJbXe`IF&S8us~kz^KfxNh`VDHe1NasZ`Q$|hIw{o zYU)8aV}Cx;E?Qz$E{UNiY@L;ql=kl*2(lYkLbNeKF@hl{#0{K{y5nzwEBjnSGlk!R z;Rk_{DZZHta8J#f{exzcTPC{NzLBy7(Hn+g96O-f)X!87#-j#&WC~!$w{rtvU5wUx zi_`_5Kfg{3E@!WyeJ0HA_%4h#P<(EkuDXE98b$`O@fb}%n5{Y$YVa1{zqtDQ!Jb3# zpvbAD!0Co660u;Zh_Ch>Qt*-VSw#S@8467Nml)5UJeg}8g$mB+4jwWPGa^U5BBrVs z$x~-gerD_7Bw$}OkB&A`p>MLeards&NV@Dv6q4B!m`)5>`x@Y(pFR5%0Mkf!w;Fjo zmKxUC$MBcCxy^v}KJxy(SZDEUNO15f4qmJZ{IU%iqbX?(8@))^TzS02_WZIc`|Dgo zx`=u2F1uR?$gM+p1AJ;LYcZZ|S2hPP{~Gzn5q1Cd^_^^}(>BTKZ#aZ8HU9N1Jz^NmIblA5MMOWV9(PJ;99TKf z6vav60L2Lm`bhWX4i}uylZc#PA_05D;=N;>;#KkVpGvaPkph`#ulw$HT_U=@ac!@*%+E2Qfoh%l6Hotd4*j9}`VakL_Qx#7Ux zC}t360RIwmi#6iy2M1S%%p7X?j)7173D`dVO)FDCybNBBHTH zKGlb*3gM)@+P2@(odyL4_aw^6+a>Qb$>fhAvqi06w_Q_4xCSP{!r6JJG2K0L{xW^~ z(Mhr*x6E-slKRD>r;?PKjS*tNw&RZmdUR6$LR#@NjW^` zETr`0OIOrKD5#z~YJbD&AR9Hz^ofX+^M<820d+xCw1lRGtopr=fg^z@UXGidgsoLn zSa`VkzrfJjP=OHHmoRf-<&n0>cnsJk>Y4+Seq(iJvh`;|tImGaEtSC?>mntBV-X`C zkn!*iUd&LwZSb_W&k`(LKRQh5sBGeqCVZ3mTu-5{h9KO_nr}rkjY%KE?5Ko?7ha;L z$mEF9vQc|zx%5aqUFk~dyabTpGp1RAMH+J<~?T0 z^pKH>-iV4aSyPlO;C_rj%=s|qh3VG$pyIa8HE%hWa&|Y6C$l$8ymycl_xlbpxk61q z>)+HoLUIp{jR|f*tbjXGcHg(b*{B^*d(8TI5Cl5jkVxL0hz5A-J|Ic>4}5|p#poVS z#0W>jUx335R;Ue7V9!{&vi}rre};n{$KY~)%#Yy4&+ovOc!&1|{?A@5I*NwCi%dco zC2?Q-P-reCYp_G^$6IWeL+AhSNE!*jTA3|x1}(;UDZ6iw*=36bRM$cWayuzL+HsJU z9mKU46$5*}v2M44BkLq4cpW@fV`EcQ!!G#wJuT^4M%k2_$8LStIyqvpns!A9Q0(gVcYEZcpm0iZmvFHFDM6PnI`xrGIn|8?+NV@cNLNM0EEt&S zG-+r_zkFZCj#3trHphSEYgGT@oFynbs{V?SBBJsdf8UmDr=O_-JD;1GW#GI=ae|l- zGbB~utzvUNmwn)_sTnynwF$b~nLcZm!8kN0!S$NMdXE?&Q(qrAA0{%T|@zu1^>Nh16<#f|Gz3?1r1V`oLxdJkNr z;hEkVsF-ow)jmj(mX^{&fpe)Y`tQg*qK*}uBv|L3kuUS*Vd3~lM%KMn@-?FU|TY8UXQghB9J8J%2$3uj+j7t+$E1^$>J2T(r&7b|VqHl7| zTE{9ZoDW?Hd42=82VHwwPM(7nW%qDR-WwZpUbS>#3etl-p5wpT>EtGKL)Kq1kSe|W z-&LghyPZnp*iM7nQ-wk5>_a~)^NSh5A71Rcw)GOt|Kje=|EX--_WyGkibR$vq+%J8 znanCf%TQ98l`#rQGEXU#Y8j(B8A>XdrHGIUMaU46q$FdM(m;mKc3#i(eE*2=^}2t! zyROPw=WrayzU|xg-lKTG7AP)#ez!YmnXmQpw2sgJ4j(?DF|>M`ZDr_%rHXALMJ>NQ z&jiS+sqqtnZVhk#cEiIgmtciwzm4=qZzNmEHD#KKtg%%3G>(a@giaR&PO%`>zS3#Lw&2 z`k56H%1Qjf@59&dQE|edAEIr$-t$nfu!`&Z!}A-O9+Z^@11?%wh+JnR4hd+=snMT# zMhwisR}m|KxkOa`n=R_ptF(ip+@S9bsMGs_){hmIHAeOvD$Cly<28++#TUjh3 zK{jnvefo6TL9Lb4{Df1tKJ0a3^4T!hd!=JmiS?5&Q&R2qVS2dxD@{t%*I$)Q$v3&G zKCB4l_xj3d#K-&OMyCm9yxGnyV)wWAP0s;c?f21e^M>2?40HG4^>;ff!hX8n6xw!X zrE)NLjT*u29ye>}wk_e1+L~iy@zPHjsyOf~TU=Yic%Ms`FF4qN zI$&Cw#i%ZHXF0`QXJ5gp9lvus)Kprj=zrB6+)EnnxuUN7_NBhO1-6f~mDPd2f>lk~ zn?^cJBnoaWv~V-EsUABhq`Agw%EH8>E;jvP?U-JI$tS?(8uuP8l4CY|Ouu7B18DmO zA^k>mQwP_cmK#nKmX}ihqSS(nWa=D$mH`1x;{N-C9v&Q)yQ>u4S-H*i^`qbRYTASf<9FE? zrF3hsZ$N$AVd4GoPbMNI?fS|zL(My*6ZmQBnTrc$8LF~Lmu8wOg=;Z)1F?cEb-Bluj29PDgtf;z$ZIn6rXs@$r+S>Gx4 zlssRWwQ%tKMB%uM|2zId*}4=qV$9*Io)L>ltw}lehP(Za_MBIumRQX8@~9H6fxCXu z&kE=1_CF3={7|r5IL|n6y{6X2O%+LoT@%kmqDzf;#ckui-VhW`3Dnc^wXCuHY*yk3 z8988hXS*dz@AqhJ7ewwet^?_aA(%*p?(_fZ-`O~cmNu4^thci$pqVeIb;3qgQi2_B z|EFtVs9@&5h^%5`;~sZ%bd={~+gbAnWgV=Gu_X_dua79kz_tnBv(KlYO(N73gaZ0m zV}!tD=w*gFswF!3zpcwa0-Qe_k#Ul)2ifSBO4b4Vh{vGQ0Vn;FQ<#Y;?_0nx3u|gVM6gj8fEa}7 zut(J_|31@+`5~tf+U_K#=ZEGood+7KFtuLvHr2q8PI9i_F5+L3Kk0LsFz0<}w1*2T zhDk~oyiTttgH7J8`h2DD>njdZomUdnw`<=2dMH3MYWuj$fb@1@KCmOIY2cWwHxKH#5D&j%eD z74t9j6{1o6kUeiNq>nIA@B*CD3}NLTQthPDgK}&J!k8AO;NS{_ZE5iJHwemz(w9ML zKCui+4hhSN&B4qvM1AG}e~39~X>b1zIv;s^U4uU&oG-QWs>@74+E1kIG02SuFh>uH zNXQsS!P~>D2;GbnbU?#fy(PaW0q)J9u?|+Mqe2hfnSK9_-;%g5!dSaFfKj?=EHJL( zC=2&1fb3_Sq{> z<_+o3S7d~bZ&VI*gMNmU)4nqB5XKHrF;lL{p~4@q1?cOtii)cPgpR*B4t8e{luT0N zg{31Ok0-3CppT$t%bTdgOrxKfOA%W+p9vnr@2EeiL~_?BANIW5q@9tTenhTM1&23q zymk!z=nASvC;gg72ITfijA5@14?-7f_KkkWu;A`b3_Cch=LJgw=u+hY*|L5iK^3Pb z79B33r-u$r}(FPqD9xpVDFO6^i?Y5J0<9$HH(4n+jXF#_o> zc85+a3Cz2M^nH6Ja$P`Bq&YT<*DWpZG&cjYYYnrH%QlkzbSxxCXJUi+ET}v@KXBXQ z4rCO_b#hmc3k)iGTsnD0MKCx?T7}>6ULV`9vLIoI9Iv}e`2L#j-JCJdS|wH@a*i!d1#v& ztgQLt@e4mIjavO`RAPk{LM4d3FN=410&xbUHP>L@EuZ=Mi6w5^%B z0aYnfO{OSVD;P1)hXc>Aj{7|uc5LyRm-k1X<;-g}ms_!2d-Oiwk*gn{nEwH`72Ny#s9vW0)4EmXp8S#rN)?=_pOw=hVCKpHK1v5r?c`J=Kb zMRe=dMe=^bnNhzZ?o6rqy!VNJeFG%0fu@@_t(QA72h|51qE{Ff?dfxZiQ1B;^%K`b zW4QQwkd@sA7*|O07C3C~Zaf)Rce4`+R%0>x3gMOJKmEYjgfSaSav{JaB2QoYj2!&K z!{^ZK7VpJOC0DJ@K1WwujVB)RtX`6Riv zdZPA7yB5)F;Jx+OHtPR*0sI^pdxP}4ETebD-W6AW{Xwsdu;L=x*ote0XPo`F>hdrt zFr5}$UdS!V<2$(G%;S&UK6^`58tS-Nd_|XO^Dw&-^r+B{2c-Nf9AL5|Nm1DTz(~dS zhu`aj|Gf_oq+!JaY{e_J8|yZXKOyiN?@LP)t#gN$2!D$2Q~>G8zSf(#Hiiq9zHbQC zpmyduD-M?r_>Yi$4Pf%*L>H0+N5^0%o+q%N5KpqnXgWD^12s@^cV z@{X8vjKPoA{a7hg{&x2|*Qwpj8-AFb|3C>nE}6Q(M!+Wj z5Yd6wS!_?o5ON7Vz^NFv?`Qg_j7x`6`|UCQP7kp!=mrz&?3pu*Lr1e6>+df#NNo?% z5~BD^Tndypec@R&V{B(TH-BKd+5VSLnW9XznU+hWi&2ye-T>|)ONsUrzWFs_-^K)! zA?>KCl%3RbF*mG;>N3CH_C56!zOU{p+s&qYIoExSte3xcO2$GZvt`!xLiJ4cxb~#< zhZ1-FbG0ui(~b=>A&>9aAB|s=KYlxTxcmF{=yL}aC5D||JM25mHQlkwt*=e#mXuNd zKFgXXo0w{G=zv2#RvJE7$o?Kda;Y{HoxZ|P?^!;We%MzNF|dLE^V%Yhq~FgYq8Ha) zXOP3^VM27(`y-NJD^WmC@WeYXx2cgETv9#F5lj|>eT3qIoqpHd&dJ~<95>--@%;1_ zwiZ1d4;!yd*SsyHc3Y<;>p1MN%n}()*hD+cw>CM-Dy#GrXOv#87L&LY8+%Z?7`J8M z(25k(ZLjAXJgn|)ddBzBEWDLMF#Lv^wtKYf4=HHqA~g0TA9|mD<7q;|tD_U@N@?4a zN;e;yO*}fa@26k;E}a*`h65}3kHzhf*Ru_tIAY$|`iS#lYj|Tp{i}=K3(+3OE>gAM z{1B^QKH6gQE1%npX=kLnJrmH^dkiU-ni#JzZDIwto8=tm<1L4LC2n4)GMf;;|0T$txjQqL z;fa}JD2z%=A8Hbs`V&g)!JKIXFwRex)yb}6garM+g`^Q_ir)_ew2;Umy zb0PKbbG91RbL;=*pO@S5PQbAzF~wwh7qT9~!M{-*B)%=-FtI;p#U0*SgZm zwZu#F4R5T`19(r#u3YoL?L{0%2yI@oclK_a$8`1m{KkzAoaPQD&65_7p7EcSjTyTC z);qSb&gvG^5r4xY9Di6Q)T>?0gboW){BkTmUfMFT7~pNcEh?Ay5x-!5OttFxFeNvU zs%*rPz?+g3BxCJ=B&0X?es0rHZE^d?F)<=-BEj)}sp|T6#ocuH7a$5Cqp3n=N|Nty7+dCuWPLu00(@+3wb zrQ(@&Qrz=Ft_QYXBYt%iry8(6uyMM|<#l9dQS-m`ZQjbcrmSz&Fqbo9h2L38Q6=R| ze6I|}E~M2yt+;c8x0W@BiG?XA%}cP#_WSeY%hj*EyJ!^8Gx$;bNV{S}wHBup$4Ff* zXLBw^xpO1{CG*4cPMj2z1p@=VwVYv~YL1X2%C4|J1eJ{_2S$wy1j&YGps;iiivJj$K zT;N#!(vyBp-_&e*h>YNJ7nkMI`umQBFP*t?cEz~NF12p@*)zXZ#0mEl{CQM2voxk9 zqag94OJ26gYU;tpir@FO)@+Roe#05toW-=4ktTDQ**Vy2Al340l9jcMM)z6PvolGI z>diMt9`=VDM`Z<%`|V{N!(*Ox&ZSZwtcJYIvdK)@r)Hk-kT}l~yPpeF&&!K3!3)|8y8Oc_$7mqdDQ?_2 z?U?goC(c@0%QMT*t6$#E)Ed1}PcQXfV9KBuOKZhO`-GE(!*Z6P6Wv?>Sgy5@(~H{K ze&T)fCQ+uLG8a<=no>O1%grSQ=_W{~>M{SlSu(yVPgi<>IAhJ(kO?&<8D)8yvYaf} zV_mU9cUO2>WvuLel30CE&v$03GVX=C;@0e*Wh%e-e61bes&t^deY0ma^3_(k;tFQ9 zN1K(8htmCBtpfF}dw=I%xE}vwO~FO4^39v3Tfcr}5L_Sm|H;D0xi(4puub#MBf;x$ zGF;=bl{asv8$F8_X!1yKM9XV3y{&LEt?=*XC3b?no#)15VSZxSn+JO?dUd_u#VwN46&m0ouX_v`TlZ*4Tx~Q;pxBpqQOa5 zMMYUeDe9C?{;P}YgeorG&Fq^oyxFz*WB91kWB<9Rwo%!~zjPwJz}dbaxa$6 zPTZn|r+F>(=S(yhNf`355=rMwGH}TaX<_HEV`&@rnZTv0y(vK% ziBJCu@a^O*Dwy;z7rxD;6_Y6rEi{c!>aV%G`meccN=jGL8tXGY64W=bmBSW%@vT|} zr84q?NP9WUx0CEviBTH`c5AO;A`DpESq~GU%+t<&PP^^(2%jka7hGTOZRE86-k#F4 zoAFg3mw98%N~>oqQO@Q{I>pX-v7$L++6 zT6--Ui~_L~2r)$&o~1AAp-c1{z5o8QVRuOP6$M?t@59+!pY_Y67xKgoJ)@n8MyWe? zbb0G6x?TM~h8L#LNjD<~=_TPe*6&*Fdpa>`%x|yv4i*Zx zw4OG%tspa-lpt@{m-{gpvO75ZcdXbT`74$$IM^d#tEt5KOx`o%R{7i7sRE{;6wa926heOij+>P<<_o#G#3a-`{XsV9*U z9ocz!c$M0+mL)M?LZLg2MziAEl|!{V`TWMAm*ulf4kkHc>zQ(Lih@+)&Lx$sSi>tw zaf@0>Xl|sdNBL0OWi}rCt*w*1X1kb@%AB=&_g)6i^IL$cJ1`y4Gn3&Uh_~`GHtk{7 z#|yYz^Dk#!JL*V2=4)NGjnRh#qoMBEkp$)VdKKh!kkI*^@wHv}-A<|3CN?9DR=1b2 zZltJlMMa&lwff6bonR=n80DuM={jmIeNLLeoI_EURN_t@_SA5`)=Ds&6rZN)nVVH` zTo<5P5_+0WR6>tg({b-jo;ubT7J`|$#LT2j$m)6eDk*7OJ93H05dv;!q>rw+Wq9L$ zuEodjP|vFzdL9{k#_7Y?^zkVpl)DAbF&Khc{@GdJ3?ztXschct@b=20!2z8F^^nTc zw9J=!=9h}uH1;tz88M<*Uh0eOVT(#;i;a(rj$(`DqourP`O8QqqQpTwajB&;1UvVb z8;I+fpN`79$F!PTb@8m(EY*sTb@8`KIQ_ZxJZ~_O&PFGvD6;ebZTg@q3TXt&p-L9izf487R%x$ahV}E2u>FgQH<4_V_lAAuDf*OM$|`_ zwaHfp8$%Rq4lXMHNg7#7h@vXFeHy&8d3aEsX?NjT7DK;)m2B(>^`EXjz0!_Q+-DlM)@=??V%rB+nPzsB<>*Iyok?+SkjaJ5+u`>x? zzUuR-cxvdWn)L>m*i?as?klJ~(QE$R$-R)bd)*M@IdAx~fIZvo((dP-Pg9d;%Xso| zP5sZBJ$oE`ZX4}rz4raPuQ(-qgMJvY`hBGlEG+fHdOlaCg2j1UF07<+GcmNjvk|ok zF4iB0wn#C!{#=V+a`pfF6dH18oIZ9fU_7CK* zD&O&I6U?L7*&mxbdi{#k#;KL|GtK(8^@LQ+8O|Lh!UCCTB0TQ;3G9izVObwlwYbux zzX}YfX^SRo3Zzn=3xBlF-L>`ce!tt+>$!P!HU&hi6ysJ($s2VIO-K%usf+L>xTpEx z#KA-oXMKHrYwJt7xnQ)0JbLNP9rapUjAy&-v|~iUrnV{5d@46jbmv&1OjmmFvdU-M zW5U$auO_dt7}@*SuFAE}@E!3yX^0^#BPZG>ympnqgyGE;rbN-A64&43t5z-d`oyKK zP-ANY+xCIxlH4C_zr3)?Y&yzTH?{eD?<=1suB7|wr$72OP`IOSraw(O)1)iERNOYq z71D9FZbpB)|M3~a^n96D^178Jk7^G-fI40D9>S~JI#RT_Z+kYJq3`@H_Xf5I==tVzpx8VzIv=w zIm+d$EKYmJgVl*qJ=YdP0~eM$hQ>z8MNO-|p{p!LwOTY{ye^RL&T-$Uo-MJ8afg*= zVB<2wVf$h6kNm*ej#svqhZPH?i^sFMD}|(GxJv1|DuEqr_7|I;*4Lh-$6e(+8N>Z@ zzpu-i3%ovI%167LLZ&tN&t9<>(dSB|8e379zsp)L6krzY^57D$3d^#pM2AAIv!6*W z@M~2k@%G{@X`H=r0*9)HrXFpp+1Iwlf5vW~vRk?7J?vEZQqp}LFF+yO;G05Z(N~j^ zQ&sg|!)PF_!x7VT0`5E)Fxn#f&QWU1r-G%->YqtPxh6|XKby76voB;2_goA(It;GV zYjV8qo*hr)XZc8LX}Y+7k-Bs(^hJYPZ{DtTn-3Iu=HH7~ zT`tuyBgk_}v%q7?j_DG!huN+}oT-DS)Ckt6&4)tu-c-HvaXdEv{mkIk`P?7=9>)m?5XCqc}*RISz{%?b_vLAd&m5UYqQ*a-UhV@5}{vuTk z-H~)I7cp4xJKBj;L?Kl`?Ml64hGs?SLzj*HLHrA-9JeHkw#S%-}T^^cf@)@=p z$?V*OwTnAv6mivV^YawtHiqmdQ!%#^=Z%H#Q=(ZmhgcC1Wa^l4KUynq^jdHTmwNKt*y*_CyyD1E&$jx{vvZO&_!N=%vwA^;tbgk-XRVQE{%Y$!>P#L@U~4n|+0R_o`#g1$#f|fq z%&c%*xb^V~xBP6Ui{@pCT|aZZN<(h;sw65_O`V*UD;geMAM3rJE-{}sSIv9r%^&&k z!4E;P*^Z;cHSf!J%tcz7z3(#-+{*{e*jp)4B~nzq*9OP8^Rju2Cvi>XV+{F)M#MK@ zbgXVLQ_hCJ6B7UzA*;fcYm@g7CpJV0AHk&lROw_uD|}0MCkPiL3FETRs@@Mi$5JItkk476j8-t?iJK82j-$~|IbrSgds=@-0vc%k5l(%r;rHC67qPn`y=6tQQ z(8~Y>r!5X^JQ1S-Esi16&b*cmyVwFCi33J6^XGIWrXCbHKiR6I^B7$%uZM^zBnhkS zP;Db|5HRLyj{HyJ{4FxJyV5lGt6#^YjYFlq&04P;8?}PJ3KAsDDJN$Xb2yq{Blh;5 z`SS-o+B6`~4Gd1&{@S;()3t=LXj}(i2*Q<+{L$Pa^e`A6-djZH5nig3?~EjKzt6^G ze43!w0#{9OfzJ$HY=tX_F$yf91a2zyD|htd4fW`GG#=>m9@;oSQxWxg=X`7IX}6mO zQG425YcX7}ZhYLU?5BSN)77XZ@;4<8PIyV`K@X)1RYs~d7kmoTYYz`>UMKJ*e@{Vu78(WnfWSbM2DAfp%aWgAd_%K(Ya8u z1T2$g6hg0F^&z5yQE0a`xCzko4QCB*p8kN%^yG`t&9Flw-vH78Rze`mf5mrqcatD# zsNQfhCnnFAUEHX8KA;92I_yWilfl(u$mISnO9tcQz+K8;-zaF02sqixFq{WKx~(U= zuC7iqy%pAbYC9&y1ig-?`}=hsQn?pd9ol)APj+LhNrI54rp1ZXkLA*SqV8d&sLIQ? zOmspIlyj;QBwz-*yYm{FV0!@z0%qd+^<%hA2*1G0=V&L9g_oS0v`C;6f%pd|ePR3y zm>}3oL8CyIK?FhXtLuNM_s{K;@Qtkz{(Z=J zi$pB^ly^Jt72V-}&9dumK;wByC?fcgm_=98>y6fLU#Z8rzDXxU1<}gJMCd+?JEON@ zjwcf=K~+^1)*_-7_XZIctr4^s zvo_UlQCXyzJjojxywiE$eEL3$|BmJ6`KzX@CSpeqz4Oi9HnHIl*XK9sh(627!PI7k z#NM4li2kkqv~Etq1~YHYOPT^M7aJQkdh=|)O`dc%A{?ZJ1kD@xjK~>LI2TXX_~xv5 zK8o3ikA}{h*8M}+0Npl(d$v;HJ=IZDvo1R2H^&(<#JMZ8e4-22!>O8Zfhukwg_MJj zL*K`5@NzBuSz|9Fj5O4L0`Yin=?Lt`_ndFvzweJ3()2fQlaV9*fTJF~3g;%-G{as1 zGL;~57obNt*|(#QSc~q9BIp2i9y3FQbTXU2`Fkz%vp#kL>75V7`=8?*shZhifF*nb ziDo`!JKKFR;~bU{8nAQW_?k$v*FVMQ$Ho|n)52u%KV+MRvhJlLb$e>6sI7Q14Zv$lH(=ZInt4xn=5=vm#FntcMj{U{v^N3?BjKLC?gtJ$a!{aE!YqrC^nqL4{}Z9$f2EOWB!?5TUj{=L zqDb>Yv&vlVW8YbFo=>IG3V8P)qs{#OzkiVAz$hC<=E`2!L}xux_LEa4@Mbk!O~>h) z%&H(Tg+=u}2mx3uUmuR#A+cH^{jbE!)WwvMfkShlbH);xb~-})w=qlv+=jBRUH}vf zp6H)(Y44GOcaxKnl2TCE71AC1Nk>J+LUS5Fv35gGa708^hPaiTE}d`PI)!HO(PUQk z@SJgQk>JD796>tb!uLlfUzqfRqDRzWQ>d>OV%|elM*mGlW(5hnd+KsMZVJ!J-K*j= z{Kwjz6chOvY*NI_#>ShShst)EZ%a8%PLDsp2MwFjFNrY#?9X(IE^cX>4u%KvIng?6Cv6zs^`IC@6hq43RXT$qdbb z8b5u|-QkJYU?9LqGP1Ib9{kG%V=iodwW^Mzys!W3^vJzNM3Nd?S|qcu#tMtuQcGYO zzH?_86`99~-Sj$4nEEf|VR0*Be1G*^z?p9q4RuEwWO1=ph{-7`{*fu}Rf#px+*R+k zBCV#78IzVKfv*V;4n`UGFX%emzodOuZ1Z^zR*(@MJ<=@FKxzaY7n0S8eGU;Aa+F`? z$rWLJ0Ht6{+b4t3jnw1alllSrs=jv&6Q79OsudkX3(ipRh=ap>#58Z8Ws1#&IbiS? z`TLLF@H5N)VDWjkVuE`)wd8^hbu8UIzu@RK_3LJxsu)V_)8AHn)k2Y*T5lZuxc`fP zW7Tn(3{5=l3|&!y{rd0U(AHTrOwp_s9CTE;Vw)T2Oz6qEefuh)y8l4~Vv6YDn-azQ z3S4(|TvaBhza05bM?R8#?-O#Y%Ph32y;%Ep5dD|t2oC3ac^$6)&GNnSs!QS5MH7Xs zl?H6*hM0_2*1!?3dtzRbiX9zW3lUZCO1A9o9d>QJHf%^*4w}K&8kBkzVbr7^muRDs~rh z#GyXytZv4ejEAn_>PszARtwr80f1oC_pj9GI=fG~m>gn);{YSHQS!=YjSeBG?u~h+ z@P=W$$*n8b4CrL;5-H2mCo}qHBxrcGR+!7dY`>4j7q4vNK>i~q@xqjd(OY3^t8ubE z9_^^tx=SJi9K=ZLj2$y~JjAWMWsBnhbM9*{zw#Ke8DPkw-^_cn!lO&Cwc_PhncUn% z!UWD;s30~q;7o^iZzk-;~I+(I&6UryO|K8mzmxb*mc&kOH9p7IIExiHeFMN|H9A!s#$o>=NIr zIYP1n88R5JbH8(p@5C-QcG{8ankPW6f~gBBJ^K<@){zKcU1%xX1cT2nE1Kr)xaN|D zNEysXrM)EXQki$JqhmF4bIRvJLDY#&w(<&c-fcdKcjUnX)szVZP0hc#d4)V$?l5}d zyTkvG$Uw^Ho=K)eib*Qy71?H@gE)V~d42ZoQBldQE7;f`eNNJGb#r^l&FrdjZ3?DJ zU<7>BVXdJ6t|5K;g+^5{9uX&fY zZ-^0Suo@v2N?VmX?eA2x_f~@-|2|!B`1{naAAy0$RaRkt0c>m7d)y|v3y`gvR{M;j zCBFF+luKrv^pi)cBNqG*ytQr*E%ejoC2$i)ny;0HP7h)?1J_JsYlPj)+{ObtcJF=y z{}G5hwMvsA4PiT`#}J5Hfc+B2Vi;Ru{|LFkf?AE^`HZAUgJs04H+QXYVq6-DT=e)g z-i6WvR|p5IK@;3GuQxeJRSS216}oT`o^c+0Txx#&)hu!EJhjrIEW;v5oB3QxbZ~zBxnjGmDdCv8=1`JP>P6$d{S^wG{IcJH+I|^|Cs_+qD?FJH1sk=J2C;VE&ZycSV zd0^bPasy6y$9|2YZ1PySM`fL2xSfRd_iAc`*Wb9?J#L9~|C{2t&9u4D9`9T~Ok+tE zH`=ym{r2j`xBKq9R0T)OSg;=VpnratU_9QmU$r#iAkWHJ`(K@}b1qiKgpO63kJ&3` zOGe&HZu~D@lDRbRk)qo@(WpmXC-kS-yLzYFld+D2RU2D`Tf*Oo4!odNJI*Dx9rjFc z@v=ECd7guBEb!8UmqPugT55dCSc;VQ>DGGQ8U6gQ=-ldvhLC|&hIsp_=`8uR-)>Xn z8^cNyd2HJypXLoW+-3PJS+%d<`_r6vPP=uU?IXzx)t6KfgAU&gcUtK5ZuxbWqNs6d z+U1kL<<|q}KQud&` zZz}%k-!y$^rKgp3c24mGK{qipD@*RNR7=wHR z@WycZ)T<+ICjw&W{xvCLn|Zcxa9hc()*6BaabXlFbtD~UAAReC|DZS>wErdP93t^7 z`2T*-|B`g%@&4h@NSqh=U!>0VctA)=v`)6+|FU)dfAiO9v(b2T>p6&zX(3*T z?>MUshdy0d{np8FyTlXj|M)f2UrS$0Aulc|iPx5_sZb)4{zXM5p^s_eG{Q4g%1Lz4 zuuUPBjspvAOq9V!JsraqljAU|)W9%d-2Qv-&^tvr%W`qWB!%-1Z8_!3hFh|xtx7Il6r z#EA5x>5c>4k6uP(tX9RMJByjy_@aOmV{XWl!kW%E`3TT*9A6s#b+%ZckG8!p1yD>g z&r|^sk%&$+jpz~OOa=n-0RaKXwcX1dhbnNV| z2keICI2UGw4GT@@6<#EeF<*k^f1P&VOW+hRz?DoauP*PXl5^X>fk#AS9?Z)F2M(-U zqb>&qS>?DHF!9|>e-qUr0E7SncNGEY@2XBn;bP4q>sV?_N*cf8pA_eg+uR?l7Hqdn zt*3&3OY$eguEAJ|^kmy}zC@2O`BR6xJv|%Ge@r`e{6H1mbR;g+^mCBm@fn%e)lOdo zb$NPzRmMoy{qY}MyC6OwTo&_fM^pE)qDzH`e-i`#Gl$1oyvwAW&)(Fm<~Pn#PAc!U zA3j!qOg-8-|SjEm&= z_RRw&7S3!p&n0BBIOf`qZToCptMq=&^4`l0dl&fJ%v<6(!`}4gRkt)`gjN%$UYh3# zoF83IR&`@8qhSj?Lwh%LT3mA~;EvoEekeR7_I&l5_RuFYPLnPiMe={j#V5qI?6W(q zf0$CYS-ZH165J^m-u;{bby4@g!GnISAU4jylMT}|324Tf0qxix(9jZ*|FnoKH2mEU z9)trH4%q_8T6iBTP~o?@m)dGd5)qIkUL>vkoCfl4BE+kPo5tU9Y`S#$vP_%`P)Q)Z zV{=m^wX(0lkFRcd*y~M3^!MBcE=pcX%1GDt4a!BF&2{k(ynOYFIcjKV2t^=C`M^0f zE=y3>(%E?lZ8j#oz`z-Rc`I?uBXK*3DI@#X^fzKI;_?g&7m~{%0*#`SZzMoJBqGG2iqr8=upL}I7H;E-S$(7k~M3W*{j7g&67h+~Zc&DQ*uQ9ph#KCwL1Q)`4P z@QHmsRB35wTv5IZ1jYVmF{lM}BqZu4Lg}c4HR>n-qAN0Bbof39KhU4D?7;zmpV})z zu2c00mQhR~M#StW3M{;lkkX)hG?Chg`Ni&oi<|u#{`Ecmg_i_7)ubL?-Cetq1f%L! z@`l=^FX3_mSHMclU<@J7qpunpsi)pxJxG)Ud77-q;We(Dhe~)9xMv*seh#-B0}`of zP@MQkVt1aL*g{JJBL&}%F?#S`1Y{nNjZjo?a&KniOC4_ zZMpgz!^e=aP$cT=>lgkFEVUv5y&nH1`o+-wzRBHNe!U@O-z@HFh&~tygcEo*EcfIh z0|ckDkR67F<6!A%Z=ctfg0F|usx!{u@K%tz1G2?Mb4m^5S0~2^2S0YyczGWw!O~Ka z*@Ya+I35?DGI_=$AY94dLRp#3JcYX*i9G~eevu(HEY{z8iZV;W8GrG(#EK*7aYW_X zzH!NPy!qt8AD@dJ2xRz{`R~sM1KTT6Q6LZV_1#0$Z?|!tC$~k%$G`E`kyj!iq{7Yf z)xzXQneDABu!58g9xh~8H6y*r4hpMjnCA0d@c-aASRd<50LNixSDrK2q@ptQWa)&t zfwwnFV?(b1N$H;ay+A>zC5>Z&g?1%0QPGb$lHP5Ce)lGd%Wq8<%h07J_7sy0+HHeyYf-Nr7{_ zg#2@@0d#G_-f?vuBp==7)8@yIFF?q{=6oqF4dd=ee=#6f3&c}gj1iQ?SWmDBqSygd zuYEE&{@|6YWHiE{&EkMn|Kf!%y4=siWjukp=*(P{*tH%2hOjWLFjrs3D4t^h{yxmX z4!hHmu=BdQnMg7?wyiv?&2e*1g8-tY5r5nu{OM9VhCw!@Yd|l zk;AD{+5?lpzyLS|w5Vl8E&{M8q~nW~7F7Bj`0)6w_E=D!>0^w@PVpAvbh;Mc!o zNQA(!Le+)rwC*aq&Vg?atXwZzQ>WA87_@sWxDc}1laMJBl9HrO?bSWhk&SmaHUulY z@edX~_FHW}<;=DDdrN_m+|{p`$bbrm7^yynI#WqlKEiUN?6KkK8L63B^5G?N5(2Kn z2e=#e=Bs{K0aZxc4UQg%2aLo9$$81xKgVvzRy1OJXOCaPi;1jZ&z6oOPY$bJpF~a( zoJE4lDWq}$m&Mh_dPbgF0Hp_3f`k&B7vgm#z9SjN!Ep>ZtpXcaf~5Oo?bbehd8#N| zw)6IZ^P86>9Jy3553lpHVeHG-uNAel_L^UG|E0H*fxzlfeDF{!OFqxiUPM1-8=ws$ zAjOhaXg?T-&F5m<8(hhEU;NYU7lNW$&g~pGyU|%4iPHq0u>X>4@2*G8r?hiu zG*@l)8)ON6a1%IP^qPnpx#Q>#J^dab>J)SKED7ZLACQg9Ne1A0+M(Y?NtD`7Rp)aB z{J5!>4igwo{QVLlZI%6a)WU!M+O-Z-2C!F9v6i6QRXMqq29zPN5g7h?&UY;t`j3u| zuGEX;B5RKNFT>5(&@P(Z&qj4~b)}b8;L*W0SiaCPX=?hXymVar5XZ=p{uv@Ueu;=; zV{iY-p)|eTSCon8tTMqe9dZh`NP_9)2%nzgj=h#ztHtjMH$_D;b2ITMX+&*jKKslX z17wZ$KC=1NWQNvVe2Ze^;L69VYaWwQdg6 znvwCLz+Pj)GmRb5FX*dzGkIvo;WXOk!8rq6~r+Z4&u>S-g1k&Vb2B;WMgzn$70M1Ot=c zvoPmuxxlGak$+E3$#o2bJnsL7oey|K8%M`2=oaC`@~>f`+-ex0srtVsutiwj2z!l3hT#HksKzbOd$Qxz%DBrtx55i=-;|)5_-r_sE$V;m3Eb<@a zM*VsL8dixd#`F^JQw*HN3kN1_l9rz`~NF@dwf++xE465{*(StiMpfS=(A0`*FEo4ih0gT2f5W zt_@RICRKFMV-TYtQ26v>s0vt3KrG$|K^?8K^6BjrSy=`vXDSPNzXsX@EGYOGpQknR z><-ntRaFivpT-NwTkzFS25U^{@Y^JT{&Fz?=s+d4olNw<&cxq$LK&mn-HHBF9{C=y zPVR|^R)2YzDQKi%Dnh`|g7Nn2Z|ehg^@{8G-Ys4fmD|@~XJ%$*`e)zSV?WN|;D?5t z@p}edm1oZkN{qTE-jf3i-l!I2XAj{pWxRXXugC|y8}Bk0m}${m$=g-t4-k53!V_!N ztVfH*qpwkTMEwYZ>E($G*PX7pKW-el`nuB*4Yo&2 zL>fg;{#53grpqFW74Hu1J~tkA@W&15J@f2@-q`oMEQ&2{XKw;>hC=CtULEuZ^5A?# zZU=*#y0@U)#=&~uL z^apTch2`5!ikF6d8XD1|>Hnc`Bs%^VeN)-jeL!W_$2a=!x2?f8mh}ej8ZSgKwGIV- z$*41ORk0^sDzh(i7yTd};b7;qAx?U4$?MyryaO**7kO~RW+w*jN#9xKW1XN6)_V#@N;LWcCT-jM8nmZz)E=*78W+~ zF85M3`x?RRzaEamY|vo3=J?^3R`HCkT}D~b)P<*7&Isw_t4FN*y!P=@tY;4y700Ed z=)p<^I-5q|=$_B1bLhUJ7VVW@Ngo;+SxzuUeKlj=hIYLdY`yg-yVqMF6Nm3|r+Y+1 z^$I}PR+TBsF{3h6}q9S{+ui0`*kuXc|yTTfAvxZ`i3 zHkEtN*NX2{=b2iH>WTJT2~k2!oZpLjdEJNL=C8T;U2Z*#JK1GD`){|l7aL)BA^k(F zbNCMl_0AV+9~}05zl`P~_p9f?Ti^ULpGEd%*0U>jD;AtP^EedzD3@Bzs5P~$d-~qKq_T6fYu@14d;C-)J!+Nz z$yNvYEqiZQDr>0Gh;&b@85x+Jx9bewkmpP0DMMiW z>;ECea_HW(%uPYLcv6it_r6QApEfGTP-&q=`VcJ$vE%(q|xeM-A> z`Lcpj)!UvyF_3%&V&y`}$oHL$Q0M#=qOf3z_!XZh{H_YrMwz4d(@h_p!oFNGSo;LF z9za3|ZrYgh!2zJ$etvM=bl9{dowbbR4>k;-X9S*Svj8!kwk z@FhUG;$nF}mYXZ_*s%)((DmIuy4O-QMRhMc`F`=>c9s}A8_No1pODJKdC`X@x($*` zm{wcFG4%@sGLJkf9v&Ke&}s80a(T#!2$vTnmE-Ky3e_?v^t{t$@3*#}yS{$SR<&h( z!!-UI8>jxvA9Jh_8yLIS#7oh`+!LHx>rF89$mhwz?=sy2@j;Xd^n?a{X}REe(dXxX z!|X#+q($a|`-^&-I);fNq5ng}5emamV_%b&Pl;Vjr7c|{`r7|_0c>qKSeZ^KDo9F_ zQ$$=9CR2>p-Gj478?mY-v}n<>1RM2?uS%w&ZiZym>ti#cVmskzE06bJ`g}hP#d-5} zjol)ObNW^839-#OjKAnxba!=)ofoh4MDrd0`<%uRZzj@JIV;6clvHiti}v znm_clBgr@z&=I;oHh}HRc0MTxKMOr>7v{IO8?Qg&?Y+6YuYHQKrUcz@5Jf(LP}9DY zKyd-(93}5O5EOFvryXYmiWggs{O)P}5)tz~+0RkDS9mSwFLzNF-`vKLcfm%r(Sqj< z4ipt}Jg^g0`g?Mh%E!hBwL(mklgl_)vL>s%RmIxR(aLi+$%vaE@ZtcLU3Zu|(|`e`s+>dhg$^sc~BQ9;Fc-g2tNk z!okBzc%k7mR76Yre{ebBSP)T${y@YAXAS(PB_K9<@|f>Jbpf8eKZ5$L3M6Eh4^bE6 zq!fk%1Yj}|t`~)lk4cG+3|oKdvLzi75H@je1b7psFQ{od%lp=1`jFzE`GKW{0T4qA zb922wJHj}&gd!vE+7|JHA?765TfA|IepkbgNz3xZ4oPamW_6w>2>yi#|) zgvIyWRh3B;2aktS52Ffzd`Mz($hQ-JJ^RX)`9(z-yujD8JPO-#;#wV>-NiRbaf8X> zOnA63bLY>WKN#b$$Ax=!&to%r7uw!evF{N*KxAqC^0OR$mcVnD;Arbcu7Bh#yhJF1q^94tJ0nKj+m>YgSQ-y9!$Ul6}z;k^Ru zS{d2WXD_tu)gFHiVSaIYj+GL1iFaFI^mTEJJJuOI%1}l(>|Z3vP&Fkt(!Jw8IiTJCJssq^nw|-ph83nS zJ)wQ#bx%-JRsDbLz4t$s{U1Mk8X-j}8A(=Ul)a-QWrS=IWea5!4Nj7fLdY&$Ms`+K zMv`nHB&&gBR8~c*`+0o6_xFeUf4Dua$Mq=c>^zU-eZ1eV*Xz0H?h0grZ2RKm-ZYm~ z@#3PQ?yfFsGN|+P+0r?&3x|4ohwHu^E0&QKzpcY7=gw%O%R*)r{G1i1D9p#NL&1=$ zTU5%ea-_a`n^Us^bNx#a-KEH(!QIEo*GFUXXT*hERb#iY8gv#d-1$^DIlf|hSo&}< zFV8mN0Fp2*>FZqdA*HLLCVK%zqBjoTc@U;5f#b;hcx(2hP+LU55Ug)_>-~f4M>xC2 zclKlw)84(xz&Y`m&Mag-KCZr2vcaa0QvF5g--7)^7?b%Xj+e@#%s;ZG^1Ta}fPX-#x z@U>IY2#=e_Hn5J(Ojy`!^Vbr{SmJ6;p|@_`z;jBtaJo3P6z|GRLS?ZI3u1y4c4Xh7 zWQ_EN?+nJ0(#3o+MbHgy2Jfk+4{7tiwgyL8K&Hfe4?n&bnI?vXD3s ze)Obtr*|;x(YLTDRR7TMy?Gl3QCA@(@Cfm#v53gDKut1tlmx7(-~=(d{eRXJtcKESbU8>PyCyt;ten>MYkyPSM#Z)ZbWo=0WfW#E@~qTWtH zAx}%=D!}@V^KpMXS~ygd;I<;4lyTd(c`($>&EIiU%@viDOg_DQ4*rRwi^!<@4Z#3X z+O`%QC2uQT$G8=lSNsP)gH$OhSw2row>Q7Ic%t0#3@GE{u1M}8!pP7Sz2n|(H$}LC zz_q(oArMuCT7df>RYxV$V~6DA2%;pWxEQ>Ewg}^XmrsN6ro);_ij5tBLk||%|1vVO zq#ha@8m<8H0A~kbW$?JfNrIEYM7HwvZ=LEwAnqKtm}qO_77z*Y{=L#VfZPzbeFgTm zBbRhFG}tBWG(Zb4eMr=oHP_x5yvI1J&}9DQVbjl1L;u=d=0X62(a?bH7N)*SvIxLZ z!UpEk=mKA!&nFIW(5fI>*iGKQSxZ;IRz+B{^vx%W(U;rT_N(2_+<3LhbLUvlY}+>7 z0}7;as!b!Gk6&~qcT;EH-05ME*ij=g^xSpf*hX9Uf9E)Ljq=~<7`Cpfk4yP^J|epw zPfsLuwdkmjL)iW@j@BMC+?<2^&L1`D!kA!;oio$CIx@ht)OycvHnX$^hoJU1UH=iH&Z8ePi6uzX|pxJ+fRB~)9f9eVnaKOr= zKd};d(%#;_@}Zzs|DnrYB)*Q+41I8?^PxWD4IMnUgpC?N8;(Z)&uc@e38lswIWwM* zRcz>J75BSweTd6{%5h4kqvK3({R{cwRK|*5{z~;%H(zg|l(?ryzdH1Q^%jNQ!D9Cr zSrOgtz4NWK8+RWjNmt8SH%X=JzFcbh#$}UswEgfr+bTWX9+n|n3yt2qr96$XEt}hE zhHsI-%2DlWSK{9w&l;Pb(}l~Kv%i=wDaTsJSewPybJJKs1vS>b1`%RTM_tMEidA^ng8_t=7Z2jQpl zJKXWw18&7P8$w0f(?4&{3TN2>sIfVnHwg^pa-8OWZ&Po+ThD)KwidtSGk=hiLH@$E z`bF;G3##RRKKbakzQv#|z{JwSL|u#e>JCRo$Er8iY zj@@j%eN7rzqs{)RvNsX2wef9oNgEVRs)Wf*Xm4foMlCE{k(I7fH8RG#yBQKKr zt+s4;xdBS~v1rAq){xUD!ZUKtD!in+cImzSKW?u@$$)w-tv zysLKd_dAAJUrXye3g0CMG|CP_H4sWg1lTTptI_;(tY;BcRH8>nvpZv4X(D6ho+w1n zpWgB9G7J5rqKu*Z9j%%-{pGn1OP@G}#|-%-R6SERH)~%pOMPLTy{~J!eCb&@M&JqT zv?_cIBF28#{%zb+_l@6TMRk+s-k(Fqd+*lRN6)$S%Nvp@6oP8Dj%!ShRpu5xd6uu( zC+4Sp-7S!$%f)_zbdZTkl9@?TuIc3a@cKk1iHg>w9^DH}2Vzo9S2gbrr4^^H{Ga4o zu~&X@#0C~6@_g!4?)>_-k4|z)m)`YhC1~oDr?pj8?(aFEh9-QJKPR4@=USM_S0->d8YJrMoq7~ zNk@%q7xfeVw|dXy~@2G%-MS19;NUc>WY7o=J%x!iyJJrh0^{Hc)d z<@Kmb*SMN)F*kpbk$G#kKtXYTPY|ymo&JD4i{N%94$76R#DVGPD$ z8nY>*rVm&7>&)7{;~FYFmhRXVFFrRS^-zq_#j6yY;o4ZT^F8llcKL>7VxXS0#+$^i z3EJWFY`oqvWOjZJ7X=GyuLo>9KkvTEA7hbTVb47-_qV4Y&#L$)j8sFxYJ~E+vJY=OgHym zGab!ra$@arHJ42Gmz}?+v*|AWaraE&33LH>@s*BLcLtKztA*t5ot$&bS4_bamf z6S}5o?Rf;c&wHv?%8)LmQh4S>2Xj&h=#6nI80&V^CQ8?R+7g`exG5>v#B--m>VK!& zV!|{&45^&HbfsDJaI7!63p}i%-wPq_)88CyZLj9uZry)7^s}kSLgCSdjM*o9{mW*4 zHcy=s+q(TvO~c2M(S}a1VY9^LV|_ROCCav{Jt=6I>)Urs%*Zl;>(%NrJ2m&-U$4JE z>nM9vKreM>%4lQBFL!ya<%s&m?Ifj)retX(ENXtvC`q=g@w7kvTway=C5z^btM|iJ zL#T@4dfDs1#qYVITIcIk%XxxXKAJN9@d1YRLJ1O&03`!!PPkH9S5}4x<8qwz@@~0M z)mEdbf$V&G^}AB4J-ZlPsb5~F2+5ab`FEzZ%wTZ*#btlbRT=&hwl_)L)Nc;|P}J_! z=3MUWo}5m+6gXM>ad2zZj&0i?jonc3m@qk5tFLcC?L+r(tSsy8lT-J)5~AxcYa$4*NNe^Tr?wGKAu@c!o?9nO`my_m=0DF zptg`xo;^AT3`eh+9NSlMNIL?2fpo@pC2r zptdHHxyUJ`SD~k(?&(+5k zT_he~Z|g`7|~vSiI9YP}=V9+#yayZb}pqf6LHv96Eey>kV?%LM4ZD zEz|cLwJy#WA#wKTw6BGW*FAXTFG~ng?5cH6qhF)jAva(7iHl;Bg56C~Dd!xukXCs8 zc`6e87}VdZxRUcj6&W%dtHh;9JkgZ8)K#Btxb!HV2}p<5_-US~tPb9mC4>`E5OfNlX1b*aCanWIvpZ+AL`K6$Me4DN5mD2Okpp5TZRc@{GnU{r2 z5h3UyqQQ_YBYs;^`GVz<4gt;!GV4?vhQW1C5&2v9yI9w}8fgylFdEaA*|LR7^2Jwv z2vdASsp+gJx!$um_zq0m-{hobi`k<`l|xa@`f>Ylsy$6l`~RbVPJVw;BzoU%E7~2N z?tvdo$PLH)A}_kw2gJ8&sAt_DHQYW-9wuEXkk1iH>_6N}Nk`IRo7>X<@}|cb){>v+ z){Xg`w%Ynmm zlAQ^w!!9mH_vgR9PXv(2S>D-O%@JrS5cd&Jn@{l!iRwC(bUZ=ll z_Q3mAVP5g3Sy$#!dN0pizbBfXO+Dex_)Ecg;$TecS)rygKDSCP{cSMmqdCjZ%_S|d z{fWBO_w>YHynRdILA)XCJ6Rgad2*Q+{a@W%3;W9afvt<(i@7=`o@&y>Y!fzWmo;ww z;oHY0nU})7e)8=XOWvCIO!LnvR9Xz}P*p<*Jv9ZXeqO$FrTLBAJI|h;i>Yzm|2g#A zjEiN^%99gxsW}FmWL0X>IunzQGzPksFs86a+5h4xJk&^2VO=cgp(lf1=2IB1cFubT zeO2Bc`>;@jk$joKII8G(+1shP8FGg5ebKwxvwL>=XVxnHcfifE&$>9#V2q+yr`kzF zMNaBIRa1+?r;w&pVO#1Woca5Hf4HM`N2ErW$s@9wXW-4=Dl z>_V#hs79mfZfXV%3evsBFJjS~im$ycxxyhETzZrR&GY*wBE!O}5&wvI>$|xizY)f@ zVSba_jXdAAwYEz;;QnDnKz zQhPaRPJJv5rc*dLVnEpexdtfLs~Z1k+;wlPs81xXB5JTch96H?LiUhcs8f1AZV>YQg> z6_Zv@c!&yxwJ@vjUf$(<&3)7O#Gg7dY0}B4i_W(yNKZ`N9Hkke$>etCI$ zNEN{01xyGF9j@0eU#Tx$d`rO*E2gK`3kX2eyn+rfC;9ow-41o*x@s`FXpq=wjr-ak z7ndrv_o>93S>J@2-}Q@HPa^0d*Kes;J0})2BzqZ8r_S3je;>eq4C5#v0GyGk%!j1||Qg{i__~EBE#E zDCxrFNnUr(nhiZn`|nlC*28crMy4S0E9~uGwzf`OnY@8yFwjY*zUIH(0s~~>Lx%)1 z!MIG(W>d*|!5AvxP(A`x?p`Mh9^H`iy&1IOha z#RO62ycQpH_I)!{n|8O&KbD$@hvEpUXK)A)%No-OT~dVV?qe?Qbmml~q&C$PYL;Ju z{*$3=3cskTa#uatQ>VAV@Y;_-GF{VFt1Wbt(jGQ+d}p47KMHV^cMKDjWEBiL+Z)m_ z+_L5&{!xWqVoOuV#SeCr)YJw^8Yd>d-iS?1lm~8M`4389ef>UQG0=iu2LTj(MT}k> zO?PF_pNoa14>7=zzKkEFStb0JJ~`Nuss8EkUwVr-1*XLv^$}MCOCsv6O!Z2(FlWon;SL&z(Ctj3g&IKCT6b9@L(afitUfFy(3{*~c*--K^>(OXAi zN7GQ>1WoI^|LkM0EY?8XIaO_~>pjZbNh-F7;%INlkfMg?eYd)CaD*1mH#5;S$$v_w zNgch!*v(AsdxS(95cXiHmF6MY(eW^;xDN8Hn%(QP-NHjpQoVVTR=h1T=Je6VN{8IOJ{7k%cvYn zG|xskRXz|55uc>z&6;u6VX9tmTcZ>A+NMPHwAqf;H@Gs?A)b=;VtIbhTB%8YSBLP^ zqQR8{=UZnonw5&gHXjDBi8jt8?rXmY-<-vQney0p>E6TnpND*IWuAIWC1LhgMD?^c z7d5p570(l|lyM3=E9C%|?2W0>YqJA9A_mc-Nsk0K7^hal#V2V_pQ39Dir;pUnvuyu z{|SvqGuy@6X02hdIn#U{e90{bq15 zr;Ia99lilbxoJ<$rb^h-pjsxq2ISDQo^M=0IOe3J+#&FJ(Eh$5(D#d=z}*070q|Qf zJ_wHuTNj>~TmlUa05r@)`hPCN!3V?u5*flM_nuo!AaeQE?PdPAoY2S=11}M1(9&Z0TE2gxmQu^wf%(_;e3;|q3hLDr z?XqVN9*G|-;yk>MDzWp~(|6yb)u1JhR-Z_`9hGu*yX_??ZGe#8VN#9YNa&1z_^<&S zMqqXm9t%xEJY3 z4*b7d0H?@97cNpozstR#M5%OSWh2UsYCkE__D;0As)7F|A4kWSgXWH)ag)D78Dpv8 zJo`&1>|CiN(#OO_*bcd{^0r{5Pied>wN-CCgV?lV@Khn(2-q=)#_o>141A;=+)9a{ zet^CQ<@xEmYBN^sG<^S~N|jW>dj`07pY$R&4}#9EmFO-k|EV8gjlMu z@Dd(x;82A9ixbWN0!rE8RfLm)mJDznSl3owXxi-^I~?u)Pzu%+d|b|9lntS7kevKI zuX=Im^^)9U6M-FK92{Gxeb)BqvZsEbk=`PZI`|}F6cHt)n*F?T-wK^LoGfecwxQPm z)3>{=O?u1 z5$VVkK5i#8G}-{8yIjt@q=6fRR}XuPI3Vm{(x(;hHgM2utHJXL;YhTIowQ5FwG0HS zoRE+~^Z|eg6o{^O9W=Ns?71_ zhlGTZl&?KyYId!f#O5?~;{jIt+SPhBZ)i@p(9)(XCrbE29f$2TAq;-a*P(!FUv&s3 zXvD>mG+I}jmg@n(YjX%8t+fejh#&#uW&;#?`}XXyEw#-`f&czo;jngvp9H4PCXWb8 zHTp}!{tOfm$Vs48dOY~oNKn-Bjkcct;Ved8WwXMEv7Q*JAE~RZe%tHiiCYp`ioZ8C zWjAp4;O%cPpmA#HY-=Nv&yM1@NZ5vGE#3~p?aI1J?Ni44+}rpM5ORoFZZScDQC?9U zd`5Hp>E%5nWZuDc=iFWx`y3^{xJmc86exF(8~K_-gEGx&o&F?YaV<86<;!zw5y$KKbk^?21F$kO@wNY!IKy+ZS3B~K>8o})OVTAioh zZv|TTWDX81*RjNh4~b8D`_`>?>sokW96R>NYofx*FfYmMr<=yHwA9p8prVFNqKr@I zbeAt5ix%^?ED^tPSjHdX02rwXWMYFks|-IMs9&BuVPZ~^aoO$1&j*7dC*?%sx2hDzX3ZF1J09P68pb_XnQ5{cRO3t3qE> zjjYUHk3vcs=AYjt#l|nj#>NuFsR34vzfe%!FTsv`!H25o8ejJV^M4!G#&tz)v18$F z3(_u^saCveEJCftTREjKk{@)(IfRA@9`mdlKVM?{a?wywSdGJyH84&$O;N_FUyZI5 zxqcT~7}9G;%vsUFG7on)&S)ycbOS zRKB+xJ>I(i?H6yY7nL=6jS(C)kiLg#l+Jn92p>w5IB?}GCHc!2^OA>Qs`~r%Yz&sv zON%3t4AfkIu&K`05;kESUuE_ijvH5bc6ErEl?m|kQ!x`3y1)>dpj^Xo9e3yY`4TxH zp}dF#zfOLt6AxihP*5o2EORU9(K{M5J3G6%xw){g(6O70kYr7Qnc5#zqiS2Cx^z$- zmvgW=Zl}qeM?zCsFY{Up$2STV*81&p%sdb&HG|wmM7o?s1`?iEGb^# zf(n=BEmd7}E&KScv8g-YRl(APiv&T&sVMHtij7Sp_cdvBc7Mn;EcJLTAG?@Cel_bQIo#P_>eEB3AI?xcekRa3|v|m;kIa$YB za1?Y1(o3=wnnfI10=trki}UVQE}^04wX-udGkzgX5aLkK+>_YN!qQ)8Cgn9=TBbc^ zJ2#LUb!ixfc(;$vh^-n?DUrmtzYrrmRLyvj8Ce|Rd|XX5VIDQrauJvN%JMRiBm`c6 zkHY6o$kCo^-GTZMPM5qeNyTojCXKQ!?*&I_%!3C%Aa|=7cO}7LZgtD7gy*Uo;+Swc zqPzwafejFPKEe1#+=-4^sUS%f66o;LM|-e3Q0b>UJfs?i)H|cAtPGHrep=Evk}E^Y z@l+=Vf$`s9#0YR2>Po>75Ea6<3s93J)lx5|N1B@IXbQ!4ryUt~t-A81|75Y+?p?dC zgJb5o*8}4C{{8X!O2dXGbx?-E;4QB7D|pd}`QzlU9YQf@UO}i`!rs<+IdUA;%+NCm zV~LG$4k7}Puj1HVTA<3BQpc14Rtp+hT9kKogkkyt=_S}Ke?WNy0k6coG9RxUS&I9L zO~<1LG(nI)0-oPf3WHE9lyPsZ`v=vM;UnPMq5M_kx`$dH)Lc?Di}ZJbg$s|yM6aV* z$6z5|Q+kLRY;^Glj2O9(_-fa-Z=!$&qb$BBd*~X6Eur$-{=~6-B>S{me zvOy?w?$6r#*@9*_cnXsD7uRK@C32kJfzOKL{Pkmf{@ek7tg$buV^6G4_{Xg(NC`|H zH+J1}dTMfA zc8HWzgpQQU4s5g(?cXSxF&}6eDR?G4hGH_k_TiR@W4t-13^J&)YK(*?X^5=Aar#TCKqKJ=+Lsm239T583jSXIi3m>Up zN8u#dtP7_z9A8EEbLdZYcQGAOKd}>erpG}Q-mbK3=gt9KS~wu6+$=KFN~hz^L>?+8 zavB2_2Os+J!686SP7aw|s43*-t8pg{%zhS}AM`gLJ!fFxiBbkU@z%3Yoq{SBeDh`z zqt?)B*=_sun@zOVMW@oAtfmYJ?n+S9(iRqe$@NWmyNqi*MbKQBF|)L~L8I6?d(Ozo z@TdTT=Sm!X<_~1Dg%k>%P0|yoPro-=`QfFdWf=0S-KQX{L@~BwA=^M3bai%S$S*WG zjt#jv@F+(WI-WI2%dMQ$?z6uk$SU0J@HV~aOW26$%JD>*imT$rf;U(rPVch!Ha6-| z=)TF?P%KW{DDh6)%Gj`LN%3{-Hi}QztaR+OR;>?Ll&uCxrb~L9xBkygC`aRaL5G#>+jd-W7``|H1#sXi*4LX0@4Z|pJn~Ial_7}vUx1u^%{@o2dURu7BA}u{iWk| z;LIB-tgW|ZC|`nyB7xfZu4P7bTl9YJ;JgCMF=3%y^gB_DMBItWn@D;(=G-c0D!8li zuIC4RK~YH)TK)N}xvCkDcL+-4=?U(lKbielvp_Wizl_gp$@>3)@&CC9`sz$En<5uT zWTuaqL$e=keQ#Bs>tg%-tSVV(o;Bd`pwAzJOD`YM6IWaDBY4d&m<^(@!d{O4=?-ex zf5?T@Kas_hscP-7%hZ?Z|Hfq6IT_UYHZc{ceS|Q7ztO*A2eA}(`A{?SW^#?ebFWmy zCuV7$LP$J6&#sg4*;H+em+Os<+G44k)(=us7v-{iNwgvJ-=G$qk(o?p_!NxQS+q_BIHE>b=N2|N8q z4yuOno(PSEV`5K7J!x)%H~D1Z%+OrIHHv=Xpp8=gk@)J9KZu+VpYphsbbfP2;|+o;X9evq=pSac?ce2aZ!Ods(pCr&zQMoMf1&ghPBDx@&H3*0FT5;v zGZsv7WKC4|GV|>L-!~3!r%6fc=v0U6BaJLF1uvrl+WrC#oz>_FpO+n&@>axXqT0T5 zx6Xj>LnZ&o8$WMAcZutkZ+m$0I*gR(G3;W7|MBIq>LnY~T<+s)sD?4VQ*Qyuhp<|( z8Gd|#SykF)NPDgjWq)XS5kLJ&dptmocc-qPU<6~*q(Nr?eiE00=TydR?ssq^F*6(4 z20Z{Fs9f=QbwFksmPAa^Tl%u|`=4bNgn&%?qK%bz2v!q zSVPPAd00JU`Yug%Zf_ysnO}C(V0!o47v9Y0=E>%dv>rmo&1WnVf&kV8&K< z-_3UZVN8l(Rb#(@h|1A3;U%?uP1*lEXsWNAF9h^5k^Ph z$wVTDDRJ*OOY;ZT7BD?NN9O_S4s^$auLWFXpf6X$s0p-V+wND>p4~}O2W4b@FayTZ z0A6%8C@iNU**(zTk1YOBk$R81(HmBiye&F%<8e6b=QL#JRa5Wf5scf?qPBA0>>mbzH(-x|$pMBvBrvznU3!oorfT%lcuw9_3thuUJ;5Pm|%P?$-@OlULlIFrO-rh`i0);6{tEF}zB zK&6NM0KB;xU`&KsMM!-D0};1|^#k}|K$cvq>hQ2*GMor=6MWF3m2d!5!}Dt_ePD^F zNIUVp%%}{62gVQh5b@cZJ^L2EF}OZA!^*tmKVw{mm&$o&53?N9Vv(ho%0k4I7zV#5 zOvW1VZjnlf{~OaS;hnZQBoFYv5J%}F2{JM2V(2mu0qhNB2=v^34p4^Y#S^E21H5X;FGUDwwo`Ip zD}0Gz>%|jN4WJaDDI8!{@L>MMrUBsrE(ZSHXJ2wsifiZcn-n`QDRcKiFi?+89J@dQ zq}!PEpohgjYl^+g(9jT!>YIcQLdoV`K~V)1rEf^Oxl+o?mW+Jxa$C;iN+5Xbpq_J* zIOSZGAlrd#QsFi92>rn5uG|EPjIqF%+P`C7y-6G1aJ@Ck)1&7_Jj(_w zV3_pb*?IKW`X}qNJ>gyA^x@q@4g9-g(0A{0vVh;vw zm_vq{p(1_kifD(X5djcw4#ZR- zbp`LqU+E#*m?Y<)=y8i8enZ~?XYC45t;#=O>B65=Q%C0y(xx4HQk@nT^OK-vfly9c z4b2fYp!@5li~K*3$`?|&5H>g&?!ihcK7HZM%hM20=LZU?6Q0`h*392uOeP({5X$Z z_w)AlMzaJo1CC5Yy#1@aSFXbc0?q7?a(Y$r^y>(xE%8p++7ywgtCIj&${|vs6+y&2 z@wspzGK^;GgPP+vjG2%`bEz$w7bt_&_D?u&M$7E)j4lS?u*TnZeYSMU<;GM!(ShPS zSx+$RjIQ+=OtC98=|HjvT+2}I!;B6ocamaaZgAnko=EzRIVnJGOrUe2V}*7Uf`NNs zVQt;rkf@x{)nz0-g9eEBOfZN#G#|Rh6{-#j+e@gJh^De~*9hTg5w9$T{tfB@jFCIJ z;j+I2wunQ;mPe9~%FDkkJZ9S^9trg2^P|A?=fQ@E4$@?J5uFXr2V^T>fpdjgOFQ-3 zVO8P{5WYh&=tmEORS7AY0*aw|O6yyba->8Vm!Ugi4Q&cnX^NnXE<-eBH}mw^PcfC#fgHj zlZ|I?eH)*yd1ij}WYcmI1prtT9%@Q?sn`P~a&1h^3=>s@MDrJKya503?|MBn>qH)o}F%JQr#`28arWQ^mMz!WEq~#Wj?x z*bTppx&FlEg7$}N=T0wcenw^hWQ$Nw+C@&!%s`lYj*j_%;voyAYfCK5#LkJ)3!ja) zb{8Z@lQ8`F&hVTN@EM*_wr6j}CCiqtVtk#t0@s9|b-eOH?L?-1`=Y`=vyRrnx-JmX zH9~u}7({~E!!k5vNDtv=#z@LX?HR0XA71+kFIgU;Si4}rkd#f3mGuLm^Z(QoLc;{-9wUV}Y5X32*~<=x+_w7x}Wnq}}74nh8ZpfxNLeQNe757tVmgrz?* zH__1G>HH{2Lb!;8l+;dULb!;L0}V!=*NGh&A1Dx~CsFkjdK<^6kMNjR*dJPZWS42hu^eQzfA*1 zmd}&e8v>VQG=wx7;{LXaU84@-BKIcqF_D7ARp^+erEW>xFu%iN17#A9CZK8~Xgn}1 zM=nApd?+!7M;5`L&kek@V?cZUJ9U>*=3rhG#`b~mB9vSWuOPR_5g#!ipn>TlT&1tV zyk%Q_u9X)#f-db{wabVy}ls*Um0r28<0tzO=zpd%BUq9{v z+lA{A7~pvC*~-WWlE}Zq7u+CxANDcZ4>T5$%Km@Q3Ivsp8w21KIL#&FyzBE=zGjM8 zVU(o1&eus0O9L>-TXMLh8jMi+}SMP_;* z{fi3RJfSF3kCO-2N;%hY0ZRSp!>KE#=l$)mca-fY2xK{pLj`qhd}5-YWi}Ip<{CQ{ znJ=S$88Y*282GY@Pe~$s{rz}HjFCeU2HjGvgwm>LXz-A^zFXcA{3O6lfKC+Y5Yqy_lrEq+s`(wJti~n?u8BA4J(G7`KtNy& zHj+7^bEZLFp4~FTuSXtchoJ)kIPJ3Ti*n9$q~Bks~1f(Fb?dGAY z*u8W9n9V#xZn%G41z+;L%AD+X8`V@I``FkVTSujis0+toPmM1e#nde8ncSniOE3ao zX5$0Z3A6cg_J5+2lQ+-}o?em#nur3_y6IgxtC|G}4Y;*(mcz_T7yfMw1CZ%gf3@f(QhuLagD}M;t_du@Z2|GF|+{$5+Gc)XhXf zSsiib4s$8CE2y+9Cc+y2Y$t$yfR^ak5%hr3rO)#Z%~S<;^{xTJdqqiBPy(>ZW|@^c zpsmGe3VR6g?I-TN%!!QY^b5F>mas8@aQjp}O4j9*n6++7#=Rtbg$^E6*V1xIo=HUw zU4uFdC^U{F0@+2qZ$G1;5Ip^c;s0_06i~t(u?19_hZbXKO#8RjU}#5lG?UYA*((`s zjR0CvqK2{W-aUjiws9nUa}*;!yeLHzrildK;BwfmlLR1&>4EUwLE=n#r1Zl@p2QwX z08CD`j*-giQ?b~3R_d~oturv@E{y7ZCW817-+OU)mOo;<5xqm$Y+UdNxFGy;zs}+v zO+~4&giiJQ7kI>2V!o~^T28HHX6!ki7Q)yREm1t8@u};>Yc58I0XpWk^AD*glPH&$ ze|(5}A7%q&}lq;zGW)M{k6Sb^`(IFKHhlyp1;s_K1yZVq*WIU#0qL_|`lS&P z>*fO$sOAV(3d&y)^De+85-os5_Xi;{u`~CEbBa0S$9QbKy(=o{;cYuO;U|z1o01}O z*%f`wht$*j4=W`dZ1@n`x0eHyo z+mYbKS9Oa$G$uZN|K599h06s`pMCVKDP*&+BBG$UpFf`8z;;#eI7M45WSz27kQ*57n)rLl#kaA&v=IqmA)Sa{P>b-klpCOyVKpH{;M#PUj z&DwY+@ma)bB+_ofK}7WQoYka2yqFXV`2*{%^@)!Ij4YH}N!0PbpJeu?42+3<`TdB8 zv@3Mna#l!WsQEvCJL!vIf`%;TX)^CORh;~NJ(-T2b=i#e>5<&;6IB5Ur=ACYxwGTz zm`B;K^%R<0bYeXGc8Vn0n-nEwk(}wx$6rZbM^C&9EL_YF@&X>B^)k?ez?yYNOdVhp zpO6qJAcZH-UB1d%%%7~^Q*g6OOG}-%1tgI%?0ijjG8a1vjyfEB^zC(27z*ZbfNBb# z@f;ZfZ2{3uD3@|ZB2y-8k@mHm7&9!^O zi#?qltZ5F-B+1Pm60b+T3B9MMdVak6&hmI}`SG7*r;PeHF?Ure6jxbzC{FY7gyrQE z!%**J&2J$3DvRY98X`_H5nkTo0sFwSFi47wGh&ZOlaR&tiH;OzJ%l^q^iOA`Q=n50 zQb^qK5z_icuxAD?&V6H5fBiMeulorJrh6n=4qXUGy|iv3YCw~tzwJQ+ZKdb|W>QSk zxtX3$$}6RNx2p*br}l{L3#Ict&}3=wAZ5#hWXno#Hg|CI*qXJB;I+;R{1j8W6IiO4 zM`}LsbnCZPjIrdhF>8-#E!cFkYa^m7lSKGa&qXm}RuMfB4$L*$_DcFMq@aved;Ayo z3ey$2oo_G6ngN%U9S`g>Z)fxL=o9GWpQ0H9@#-iz>G0#}OF*~s9jakZ5bd+69JO*PoYe;utV zVd8_79l%}CfS6b5X0l6hjQIk8loB76I^HPJCa^ub@z}YmB*f`bbWeRid&|h@Do|;h zFQf>N(AV_@l^N*x$#XsNbGP-lB@!Xn2m zPO?!t)eFRq&)H^=V*VB{uT?k=Aib%0ekPuCb&`Svf(3scsR<@87fqVPMlg1v`o^lN z;dF3d^wLufDF*=}y9ykeUsy7~M{0FEL_(sYv88_I6l} zS0zpNvC?hdL6^p4Nj^faHL>BdvZu3Zp+kscv~l#;*Y2IE>i^Xz?Y(recTz_9!A>@N z63w8o8lOwt11X<_lw<(bc>jKI!XdqPmv^mOd^*>g&b)*}G?s$)e89TB|3_LgT?25Y z(!JkMlhM%aJ2X>Bj1bsxR>g2WjQ^kYa^fe(+b6Vs!bbSZABx?3tkCq`-p-*4wb*ZX z3&F(qN8R|MFW4Xc5U`_2ia#~8GW+Td1v3Meytu6FZqhxt*4JB3`TEI^USUJ-Yj9S8 z+1azROMX43`}s17{c1)5o{*rtr;NKe0MrcyP3TOLWTbuf+3+VCYNa*^Zd#779_XJf{)1|)D zJGQaZe7tG-@d--#olV=wMO|GDU+$XyJOA|0WarO`jlju(Quq0|J)wvEJez;+u>8F! z^>g*iy}K&B|Gl~+<@xkMLHJocJ#iH1XxKbZ3j*P22FyXY8ey2=hFOw>MnWU}u|d}= z%JJn3q(R47i+NAB=J%Ntx4%dWOnLN3Y~1%ROv@*HM=xX2wObW2>2L=wkv3-FZ9(|+ zCD}d9ix@TIImG}QLnQEXF!oDAhlw&B+Nm z6R&&k-n~;TD7f25A3dLlh>D&^SB$=4H~dW!5~?udd-v8)?~{g%%j#_3`}q}g)R`mE7P{Cr zZ2!CV^j?LOcrfX?IC+FlKJIjz`A-L%NR%VQILXhKn&h;0|9l`?=Q?!61;<1aDZl;u z)A-L6^aiwvAR(et$AH#2Le9{GFvgB_ao$@9mI>yw0zTa^X$6DitbyVe0G{BQm3E@iy7bJX4)Skg4|KCzn zV4`p1v56Oz^vr}cFASHHb|rzIk+&eh+y}cMqMfh1!F=F(fpL>U6VaSMqpWOPL+i??tJ>@q3bHi+el zbB;s?H3<&HDf=wPMotxNKD)us;h&n_JnvvdW+JCXx6hncxif7UA{fodR(e1v1Fe_E5Q{fyGuM6bGF|BDw4%xWzRr`De{?4YcHum6T&-=3 zJI4X;4AJc+%fFpBc5A`928=S$d^X@bQfOYOC5nF0d4D;QC<)Re!3hS5wCvJ%5|P1x zTZtpaaKkxSf;`nb1JrT@kB;c47fCxo1_4$p4w83`khuFl8fL<4^d=31WUooZy_$ZN zVf2HHSI!T>1j zAf_bVJC2qxi07~nI|a#8gDGr!JGe>(O;^(}A}r zaYw!SGy;btrx(?;jU~xX*_cVeyr%C$UZwdMcqK5A56F|^oSF|%%jt)SxVbP}AblUf|vPzp@;?GrOs&hw==AWTr@>ONftdN?Ll?^_u|@rdX)WzR4SaJF}Rph;vGQ z|K1xq2(aYB40b&ZzAJ>w>}QG8`ufHuid6dt1uAJ+$Bi8q=GfvLJ;~Btcf%q{=f_N(q z4GzcXyG@E0x4y0(Jl#TNXj@EmlZ6=u%jwnCiDCIqs?7`D;ja+bXXw&4kp!;^++tjC z(|=Cq%{5M%(S2~2M+SJ_p zLd}0U`PHYGBhRn?{CYX9k;Z^_p!?mq6oD6pt#o=kPIf#V$1Osb&b6NUq3!B!oHASRu9EJ+-shyn z`@;uxuI)8qG0vr*UAPge$1q|$VKZT0|FWz5Th`%UR7YNQUq^Firl+T(M&Lh)J6WIc zqTtE2NQ}IDSJ^Q2}&o) z8&G)w9~qmXQ>5AdKwz(D$L>q5t&mT@5EO{YaO;@*}HM`PSwn>W9C=!CGsjX@q&(hsuS)Oy>A;37W-?jAuFpFx&x-jK zCQx0312F(S=B2>Dp9AMqwCQ=#_p);Q`+y1~q34jXbVfwzBR1BKmz|oYD4RfgA%^pW zECue0&e%q}g?2F$zXh)*or4y14P=X*J{n?swU*bD=?*Yx)%*3lqzI(}5d%IP%A38EdUS zsfshwkp-%zsU#^&s=7>EzpR}MEy^<)WT(E^c4OaBT5hFsbsFA7eC)^Bn%iTjD9Mh^ zZRZ0QhYy5$Q@WC*r&h-*mDRm3EqzH&EAZXtnb8`O!qH6Av^7Zd_6xarzc-KXg&JAM z_c*C3Iz9}1?RRc(Slg#1C5#hmi`_<_E&@;@5fTi-jZRTiLFR0B=Q(yn&@=aevWeMJ zGOQ1MzVs6`{A0%gp>n}<8vkdr@!#)8gWzOQhxbQ>%#UtcdDDOFFjy!eG0R%0JW#U% z9FU9pe0||w<%zpB-+!sak*uggRI8)*m0lYs3BKl$mVSQRz2v5}TVB9aTi(r4<_^m* z`TDdEUgsCoYeu|ouBHw5SGI6}Uh_U=s&va18D~}P|A)Ij@usqG-+=LJBU6YnPq7J^ zA{0`lZOBmODO0EnWhP453T={7DMU$y5NVRBBtx0XoFQe1&?Kppc#nNQ>-+ny_5KNO z>sibFWN5qgb)BE{JkDcKQ?Qa!DsrYQKD)v+nWxAokuSA*RB7)cfyuI!s0IB3{(am$ zRYT1KPnNEwiJj175D?`y<2kr5(d|HRz4_DiJ2Ioc`&{|>;<}hiA|ZKko65!+weX*8 zw1>498a_u>Al*1-2rU*)UHYEq@~ydfFMSG~42%^VOhDg%YGkNv+kzkp`kHQ2%^eGw zO@Iv?o;-XD*-Rqpp zQztmf;%L&uDuFD$FOQ;6xESIV!8h_74lV4lXxXkr(R0t;>?y9dhRP}Nf-14AlDb~;-M@~!&qk>)Hd{^^4^T~) zV#X{vh2H3WStj&_PmCJe5v)@{3XsN7?u^1@^6B_%uTtNgKX7Jz;Yb-!MYP_=CMIhH zRXo2>yoBz_{*;ts_NMQdh?#l?f=gpEACls+hXR)y(I(SKBadfWM<-F7eWiXa;ywEx zBdxIfz{1I`ckV4L&-^9z%s5s}HuzM^7HpKMEgd%zJ(79(x(tn!`<{7o!6e5o-Y?Pr z&Ya78o)Z48veCg+>)Jw(Uc1mx-sh8NFAm%|ctpk9-~cl>cb-gPn>cTeO2Xqr>IZ+3 z9^K~(B#FnQ?aPrR=FUAMrO7jKRhX>NLz$t~1X6!PYEsb$dgu#BkQeW$Cl z9~eGg6V}~&iQVm#|0BiOvY{WQC+3BY@!EU;A@;b{wh1^`cJ2;PU;p~r=tjE(hNcOA zMcw8RuTN>n%Y3*@5@ci;jo2q1DJah1=cRZ}A%yv6bxyO|Jl9f3VU@9CoX(fztIu|F zX7c+8nwrK_rMan+7yW+bCR!#9v7Xy`=)2cx*8AK0Tpq7F_)_V}%#|KD+0%kdA_ml) z@Gq-^;@^IYw5SsZS}{NMhB<)i!0taji)>xPf@}BstQ>P{eR@WaTe0q?=bp&U3m&Bd zITyx9+qi@U-f!6J%aG8yQF$(X=ubrQnhHsOrg$sn_XOi;o|t%^mE7_~#=3jRu5e5< z?tTFB^4+^Cvn|_sKxT#r)c*6A9tbH1um0@4n<353AjrbdRV%Aj+016ZTBqiLXex3oNKkmOUiQ#9Z)2$%DIOSA z_JmyNF_gHIZPX;}`= zZ-T*G0X1f?HUK}nfiHVD@_df04iD4N2vvKp$G51~ME6)7`n0&cKWcLaM=!bG>0jR({ptR-?pE@$ z*0zX_iCKfz>T$2#{JiW5W3DPqHST5T!h!s8d|)Rwzo+WlXxe|@e#K{DFgNk7)ToJ~KQrrdkw@{(qu2uuNrtO)LZ)6UpBb-TMO@d3QDALMxAa;L`&b{(4WqRoj zOW^ciUuvBm@%_($Bz~FQCw@%B-g)0Xws>ynBcSlxoS_GdpQzI**e)kmh*8r1v1YHn z)rYKiM$`{$IS%SSyY)gd_m$x(!irUl%9E?}Hm`n2A)b|++HzT~_0>5BLadXAmnnXM zVsI%Sn7>$obdjX^E}GT)uX%OTN#W*g)SB!8LY;vyB$ybv%u+@Aisrhu58U>1c3kjn zWEGMUYhmS-J=v3ELR;lFkRNhQCP|Vom97{OWF-`tJcDn%DRyaA90H8$mXP z9ujM~1Y^eQxYc#R)KN!iQvJ{IN_T?9%F4=o;x#E!yj`Cp<^3@_Nj>z{d33YgDK**7_C~#amld1zD~&IyaS#Gn1(6RJQ+BfAgU5#d}YJlSYyZ zQO;nLEYepWcpz19@0`^-UluuC4+^WqcS4vj37e0T*r=UBBF6U-M~=<1CKz1T6p1Fb zP1We1WJY`VAv>%Cour76ke_&6Xh?w5xUu8uxphLpXH3+;<(*;jj%PKcGH8-y2?MgU zwU1l66Kf7(;IMvsAe=Q$D2_7U6q;t11&@kFGXX8blgUjO>jgxI?0C$`^G;PMfW z`bbE;Fg86woMY7>wsw~Ny%(PU&^&oUV(V*y#nDZ4pyapO-tD%HUytww@g=vY81j5b z3o&)nGKlRRRg`|Pd^?pdm&Elon@XdkQF&SIcbP+{Hn4-(A?5QObAYq~ov2 z{i`ouxIxlOxw%LFC1=7D9}XhpV%~@T80*}dg6XnqWFeaNW)AkS25IVkg^I?$AXW*1 z_m_lpgIan;2HeVqkL_ETcw@!1KdPoJP(o4nqDFM>qjk^!j|SALa? z2%jh7zXzK_ztFK>$oUMd#ytqWzDUZTvG`Yie&%gL_Mp+sUg<`7 z6(CK)%3-qo)s_}l4i077PIK>QcHaC#g*DAH>&{8vR7k#U!cGy>3S%1bUR$^SskG0{ z2JX8XDn6EOeY(x!`j*Y5{#Vp*Mm*1Fn-g+&p8X~2aqpMUb)~$W!2<@99kEoyZMo= zg_EGL9#g}r>TqpS>mU_IzfyP?fZPJ5bJxsJR&-hvr3PRrqU2c~ z#x5s~-IG>P4m_6I*Ld@X4at!D?NUxhU#r=n`uCK3vfM3)6O*ZP+187dTI#Aj+!EFC;TzT(9C(ah@#UsVL9Lyf6lq3576%!Q@Q*cnyc*^!)_mldx|clkwj)qI%EdV7#T^hv&AOpEn>q z54i2BcU#;S7mO$!(G&_jjsVOMuuJ6rb(hF8_3kM#+x>5GW8eCTI6hF^=n1kDe3sn^ zp&j~ssi_xCT&%QbmB1?jHI9oIMtrReG%J@mA09Wo{;D>eG$^Xd<`gq%>)Md;Odo4f z>UXD#<*PCbz%cYpY0ROFl~RiJ{GEjQ=e+8f?5z#z)YQ(=m7JKj^*Pf|m#(H=wLgE^ zgS%VeWAen!;z8N7pSw0^)#$YmXVUc4SsxQJ$GldBZqqM&OsljtZXb9Qw4CbAlFC1o zt|=w*MtcJ##qZrgrwWI?zM6HbPf$a|sFEsq!o%yl{#@DVEElhNj81SK0iys}jr4Fb zB%ETo-aCIXj;=({%-l)A@)UwafokmquBMFE4_UggJ9r@2e+T3lX#yXzMH164TnIzZ znXb@nE7y^pgbYQ;YiT_92X1(2m;^iTy*?Tusb*RfA1w1I^}`McPX>Jnp3qRH1PZ16 zy{DdW9UINfrka6m6;BwEaPQub_Ew?p%5+j4lM1sim62oHW7b3N??tH`aVd=KLjN3C z_XZOZS3IpfhX-%9bGnu45oJV995XjNw?!_aU_1xG#;s4NTx^MH|6vPQ8ez9)w#(C1 z^qful2ngLSsP-%1BgyQ_e(`~M;E19W$rFebrv1$v-e9%~R4Xs=NH$>g@iuTPj~iUk z!S_F>CT-abQ33P?9M9j^jLEsMH-0b?qrLoo;SLi6iR`N=t@vh=`-@lY&4zCmNhC6( zgr2t4Mz#;WK@!17&+!Yl7}V?EC%yb9ZFT&CblrwLhx4AyQe$=cOaZJstinVdS0Xb9 zO;Aro(w7ijrLu|2Nbqn-c$zYVPcx^S*rxf0_{1bf&}b*Q;_KAkGgIH6xbeOr#?3=b z!VA<*0F-weT7YgM!0!`i|L`Bu#O52M5HMF94OjJ%^Pd!pZdQm_y@Wh8u{n4))lJ;D33KGpL!ngBIU&dMj-->0!9PSR{{J14{_Q%*HGI}O=l^Yxv~sOh?d;h+ z)0LHZyI-P}FLQ{|OMjny-NS3e-NV;-<(dGy+{{eNkx!+C@E+mJD=y59*?!KjgGpUa zV)%gO1%hO-EA)Vj+S`St<-(hX_MhigiK8wYpJTtU^KgXyg2k~v(MU)z{q1<>sI0u- zr#*JBYG>E(yc4|2=bnvxQ3lkY}3smGGy3OZt}UMUi`ZH!c4* z3djVl>~!N;WHqqn?M;@dH^GBwYHnV?EwUsxw=S?^tA9@+o$yl?PU>du(Ao{JA`s{Z zEFbi&FJi?4<-4w|EDO(#8#jmr?g&0FPAd%kVAxO>(ri-9U92Ce%5jaDE6m09E!HGj&$*pXAxwyDsmR~*h#OgBC z{+K1Pt%5rbR6J000Y*w<&eDSRCp2{PR19>hAd%BS%A-F^dj0=lQxf>lMQaKN9RB{l z9x46VMjyTzpM4;y)MYU;F7P)d6<=e9)EM_84B(UlOU?9((FEG;XGi+>QD z8LUgL6%`q4fhqy6GTm3@30SU3Y1sRWb(;oazH$9PHm%ruY^^oSISw9FBpw95bz$Pp zL}skc(Vqt?lm;q^kp_6*HZR8T3YIQg$@g6DppZK9HvXsg@3?E*en`FkGDwlHA#1sx zCa`|}Q~zFh@1$ec>%!6speN|k*x>2kh$GIOKTmEEU|0I_K%Oq%#JS@iPe+``z0GV79?(qz^B8SQe5 zZY34L9oX^78F$e&DNCTm-+?Y+6&3j@FkFGET7Z`qoLM+=!3KpMt_lKSJ(*|2$Es8s zif}Em7~n01ifd=T9rqEobAEo)*vw$FAUSEWJ*CL3-zCX|^T2*k=|OH!lGD;!#Gb@z zFKmw}Vv79cK*&?N+7b%#^I;WYNne}PO&qf{HFc^C-9W@AB%Be|g7D%y{1X6s?XtAo zj>sMOG|?A5$|l|F_V&2-xy!Mo86Aatj=A8oCbc-I-Lb4jb^!jx8+mz15dv-senf%a zAmC?Ql{dKNSr<1R0m*k7AnQ8SV(g#-I`dW$aO=?7vEvC`kI)W9R@dTKOce3NlTlTQ zmxzTOKLPM%^6S@ET4!lA?wpNZ(iRyd{*|7ey;3Z5;nOcZiKnv?$qSNvUmYkvE(U3a z8qLby=9urvY5ky$tRjfc{r!tdXORcOGb-LB-3RgnVv{>L+4t-l#$l1Ivx$;(?B%WW`sLABvDgqWBeB6SU;aK9N|DUxH zR9VN49cxs@S@|0;17A>urwd+jrxFu|AM$bj`1LCmdE$`Bd6lX;Oq5a@jL!&f2dk)l zh>gI@0_$lUI;sdakB9!fcy8J)OCMNt2A_If?(=djnVpq23Njue2C)vXZQIeDX<`Kn zH-Mfy?>L0q0T+~^4hPB^4K)yiCzsV_Od5CJPCGw)71vnCf>cUK9BTe!26KY8 zOf`FZ4U<@c+UuVK<^)_c&PXMM1)(6-msBDjyg}|@lvnc^k;70c5@1_2!)4)9$aV07 zU=h_4JhHh7#c7?OAP6546Q9|gRPxwpPOh#saOvo%?Dek&%e-w?aoyUr!{{L1!tDqK zK#AgNO@WABf-4kvCm!vfs%e>!nvgokz2x@`)%LnQjtQ#%yv0yz6%I$RbUe-e`-7xy z1bm(%x%p*+ECDVA(tW_Z!F@ZKkpt}_$TKU5&qiTIRLOeHV9%VN1(eXwp?uevoELSA zejkq8;r?6p{O%G*p}mGQQ~t1tn;91nH4HoT@3d^#2d(#3#DB}kGzIr1jc(J?an+o& zv9*1s&>xA=@|2S6^?xMuU_-Q5{28C~InVf_;}8gAk)J)k?Q4x~lFLa#y)(M>#Qnxyyv8zAy>l!8WUvO{9PH$KBGg0pM&vliSY~{9wHv;`Bjtqr? z2wW?|F_vQyi$LuCZIT^xxK)6ha5_l6A#G-fVQ-&9wl22>JIpC|`MYASyv(TUzjr^TOrmv&2)^hb4k z+tHs-5g0KUPV^6`9_^tt2B93lke=t;L$2B!pi<3?t1`VmgeTgwSgB*OShID zXFWUN@~pX87FT(^9oOYy;M+fthqSb}yFo!g=lUD!C)A*796}|vPj_~4Np_$8nR`z{ zxbW{C#x{nNG?22_?R|iW5f-PXdDx|ogME|SkG1oiCvlS+X~yR5y4zK*yxrfh>kGvv zJgf52$UbdRF1LOmZwPVqy1mS9=uvoML$z}PEA~u&zF;bEq^-6g_9E`Xt$tncVNa0! z1dy~9a5>x0BquWv zh$u83h2PW<%pFBxWf?3FWd!p|XaC?-MV>8Z1A^L-w()>89>$j~$ zjD!9BZU?)Hoc>cBT`xiQ@bHi<<~ntwt5~h6f!&-?A-m1?ilv#(Fxtp$h~=Fx$+%AT zD%YKI?S4Sl`$v>sw{R@2viG(-j*;=!dezav-^+b$&#}0%R4_v(K70VR%wB^O^JQ3z%+AKfLuyOcKDyz&u2$Ey{B54Z zT^ZYtC%#CcB2oPe&oDAq5574^<66I?WVXtQ(~qkoUN=8&%(eP$D$Q$L_QrX;G;R~C z+b`I5-!43FofQ=VT`yFngdQppT@uCgv@W4V`g~VN2h^4_I>nR48C%8F{)TR1cPKge zrb#z#Zd#c6$7>Du z2>LhHfyVe0?Iw8)r-J8i*dKu@nw$rD@d`d8zMq{Z2hAK*MLg_BqPs891Vu#^DXL0J zmcB_=icL;PMug$XIngyFZibDDZqHfyH?gl?4b{UQ;@huZbcI*#CVx3j^3R!Gb02H& zOwTjLGzA=jkwAsX>c7qlzt9rMbThJ#aAN`U5~nbKQ$|@?5bFAw=;k7Jo{HO`a^$%w zIF_nUY8aj6>CDmyAoThpD%0*!Y&ieB&XkE@O%#cSGOxOJh$rDSh9b=i%kvLlHfe9mx^-%5YMA8B=>x35mh945kYOS8I@vwVM8%i8CyGfKuYjNR=5u60w-979$mPae z!an{LT@3keN`)~4_1d*-PR*HFS%bIC1*Fa=ge^len_QR&Iq_px<D*hq2DVcUxf z_I&l$b8BNELW8O!6nM2{_>yjEO%hgSow zm1{zua=LLdB;k0`)o;H*D-4&Y*Zc27+=ynMM76T|x(+ApW7ta=S!4I|00umlTNiNJ zAp8L0X>g5Ct7O8yN71dyj_GpNt9>c-;4|{GtBqcjkOD(V>|tm3077$&D_FNuta;O% zhP}#Z7LP6u>NRB)718q&VSor8q?x&ScNc8fLeNq*I&=vros-{ERRt0uZew(EplLsV zF#wn$tiP+?{p;Uc>yiM}oKYwt8(hae2TVvdqjN4NK zm2C!o2s#Xyu6+&HHrj7zWNl~vxwp6<=ev=(-TVig7?>TYDm(~zgSTIds~P43*lE_g zaB^~rq1nZB4Oi4JhnkFxjlt9M!6RB#dYb8-@nX}57Y~Z_7G3AVW$4EsdsfU{l zz!<%5-}5hb!bQ-4Ru89}isfIo@T)dU3vn#szoE1TUp5Q2tRwI~g10QRA)7A+XY8I+ z?#{4TK+gn=50GIlXJk0JxS&0@4!POrHQitK4m7hD74`L^#5Yj3VgxL>pR zZSlmP-2Ajv=XE6$jcojgo2vF3k1urG_h0t?Ff}DBD|*wSmAU; zr_WTnUmHc;-YeyBdcN@10BVJn*A}KTX+7i59nx2gmyh2#B`>&5DywYYjxH;%`4(fN5ZkqxC@rYB6=+jp}~nnyjT>Z(68V0FK^uQ1qN z=BUQLtXT)k1)V+EPpz5t&W8FjMuI-f?l^XWjcIT3sS~8DJ)cvte|~uq`b++T}c$1 z_2)Jt;zD}u&h!_j9FiyqbSa{=+@yb$w#QRO|9DEh1+PyLved?#oPyXP1P%_+x+#*XW6D73xxs(JjeQbWgvyeLJ# zBBk7P<`EjzEQEN|T@x@?!Pfyw+zQc;T2txCAB^-V<1cTN%FCwx_w|C5c$!zRfW%mD zX^(x=4VL)#y{}*Ji^)}6+w}kM2O7W$4;AmoPY`UO0>`ND@!8C6ALW$97&t8nX|*WL zP5$c2me+RFqS%jJ#NavcD&@#S1&Q1J+}4wzIUQX=?*qdO9DkIF z?A2GMiyW57G>71h08Mf~d){u%DUfGsL0vC<6S3)}LgczL=7L}PvOIom{`3MnvF!+$ zgaaYi#+W*CHEXZNI&5$h^RqPbXY#gqTn~kD{{l`RAVsbAy zSMu1^Ue6zG5d&JN5Yd1;V10s;E308+V~CuU+dNgJEX>R!kllJ_t&>Odv2*9Z!+^_0 zMM!QvI+11q$~qu{DTRZ#+~lF?0b5ow4;oZ7(Qcn-dZcJ_n>Ll94W|hT3s+&3Eq1#x zh}1l&h4RYXpa6z`TwPWr;QnbvR$HMvWz5P6K(0u73t%acukg z!#axINp+%u@{_B}0+0ENQRu!BKo{U~DPxf#QS(JYm-+l@>)7>+8y46{Ej5m7h&s?< zlG=^w+mz!Th&OHR?HZGrZ3l!(ca8U(mDx=9wg)h`-FlXAvbx#1s`RLh5Vy1DG=rK=2Pl3=O3v1N3|%#!Hu7(>Q`@b7d#dWn&IhR$f=`c}I(wvM)77K# zvU*V+XSmc;w+aM)(F+?D=Tm_#ATLkS_ka^7alNPNk30~w*xKR$5GU9&Yy`>&4vz38 zt_~_cocTUtYwezyPXDjFF1Uu_W?PbMxcrnpV`phF5)_?~vG}`2HP7{mgcOT}SsJ-* zZ?EomawZA5R4^y!eXI)9>}&8avNn!OzslCmKZuE^Spws1rY8bFw#_#)pNo zLMEHV3jbWnRK1ea)co>sb~EEO>vbWL^OirjYs_?IM{0dU6~9ax1t{E}GA$^cI7?1h z6B`>#NRThLuFBVsg|H1h*jV;%xEZLc|9}^ddv)X?Y~L^~B4O7{x%t0LbZKQ}=#350 zj$ufG#pGuk{4hS?D(;>6%;KOTbk5hTkA8&jFCwTe6VH03BiNK+ri|IJ%*TaDNiB zh!TL-2sVM$;nSfYbpvp5NvT1{fnhE6^yzI!eiU|3AB4OT9SCsPVK_g`&o{s_0*G^x zFG6SGX3P`n`{f$CxE0t}(msCusvA1-0{&|fc^^fMtZ^vA zX>ezlfjI`fvSHMK-OqC^I;-4B?M$O(hxv_hXkL^Rm{wA_&5Z38g_0-F18!r zsc&R`95@h~IUZ|IPjKFM_&QbDFUXSNlW`U@uIzbSSsjq!$HqazdwlF~WJff*d2ki) z6crUV0mQ>S+@Tfw@%kxfi=a?g!oCBf&20(_3UF1#G7s-pGYqCC+zwcmw0`)5mx|6h zjBZ(Q?;c=W_)+|YDuRyGxw;Mm(%}h#CU9&UxpER`-|bcCL}4=AC;?9^HzzMKMD}ZAOE{LZKr;Uq?_JIyj>t0}}S^L)VJcTw0 z*RAzKsL)AolyJP|bZI#~(H)Y6%^t-!Z%*KsXQ5(>56Jon?-AjjaKBNu3$P09fD=3{ z#%0MfgAZpR^2h?AF`n`R-8CJ^42G0?3Gu_)Ft0-*FyJ!DaCjAEXVb$-U}gk>@IE|| zZid_egp0P+Kg+?!W^+KF;#bxQRBPv+ML3>868(X$m{UB@<8|=G1gbj}Bt~lVy|0nA z_Tev|LMz>me%5&YX-DjG%|I{~7}s#xU=B=4oSK*@fqEB@9}H$PbYq`XRumstURs1^ zm7fQXqmt`mD_A0=yrHL}K&)!3Q+o`sla=ad6;2*pU5|^eVYPqb5oSaT5PSqwQlWTm z#@uONT98r!M|Duf>2@M;PklaaKm+(NZ6Y2$0Sn&5-VF4j+1X2|3d`f`S73aPKa5l$ zx_eqCG?Fjw+-W(trJt!PBX~jZ|F{69^a~$YA;N?EP|2t3+aMduSD(7=EKbIzA|RjY}Va!jU}XKuys0|ZV9!#ybJ|m zb>)t+VQd8GyLN+v42+Bkhur>=h3;Ptg@+0X0>}n1K4)cT2l(<9rrpPn?;v9Ez{02) z%a48d0Gl%8BI7$zQE=PCI*IfiJn3l+9uSN+KE$Tr=$TQ!2>0{uWjLIUdYhgyjMjb0Y;KD2n%?fWn6>JJdxc;US8 zUe=q@DRSsR%*_6vR>E|=Z1#wc&qVCXUyu1`%vt%+iM<6EAGG^W+5$QmBEyhfSOSWUdV z04h7TwW%rRth1fn4`iOBKXm)eNP_g@carQ?xR)r9p8&6+{l1KVaF|m)9Qtfc zi1>!PmA-7u*7SXyivbQ9@Yl4$5V~`WE=?|80Pg`29@2yOv_#Vzyjuy@Z;+flw~TY4 z?ZTnSqu7kKXkEk5O@AFyQZLoD*a`evx?z(XWO}R|9(|VQ7V0KE>qu!fZdZ_1(bFH7ZMQ z?vOH!lGb40d6liz4YMEz%*h7_Y^2#{-;|HzR)t@mx}}J^?`z2DaI{JVweEa#2%sz- zRWdzB*6|#Bfz}D3TOl2Q_w(^a+v^-#jT8kKWoY@l;3i(QwqDtGxYRncHE>mi&Hj^) zxg{zEa5_~AX~UZ6rHY~u!;ZZHEGk=jDZumy>sB}GUL$8dvFS&@Hxza#Px zkHA;3s7QE+Pi*W8%7BPG!){dFr6!S}-+%rH@0G$Fy|Of=+c=BOP(YSUkyAfJ@vpFkLy!QOgqTjNrwwwOMTliIv)<;>Bzt7G2b>=1H$)(T432KT%n@FV+ zg;ti! zP>k(P61Ix?G;1S z-|?l!PqyC(i`QQS5R_cio}p^^u{LD4(H^>|23cNC?hVS4{XcF2fnV?mVT-;3SQhBg z>wqr<9Q0s@9rJB!S^1JV<88d3Y(gcwP?Vd+oaJ#8uH=4Eknc0j|#8;>rCdPCL2{=^zUX}5CjV3oK>A4_r z;H|ItwPvyB}GbVvS)e3^hllVIaF#flU@p=3IkKflqSI-!Nc7*w{8u%k4(cL z+e~g$w(Q=lrUoC*EW}e_RxEQW_rlwZW9-Mz5T3M*v+4q@1S&q7OWGTpa(Lo0cQ+(2 z{O1LPhYmdt!lqzS`v#Y~HSmNfp#lukOr#5CF*i3Cu>|NiXRDN(ybQe? z)UP%>U-nr(LW|Sf3Xi&)$D@fNI_$ic10avWWw0_E&2r#{P*WK#xv8ZktVHS8884DD z3Xg&RJOPbNOb*ZXwYz4G#+Hs)PtVRO4A(cw^Ufg&3T6Q0Je+zcJUUl*HN3(j@?T^D z)(E|jp@$j9^U@XC7_jS||3Qir{juESIZ3a70Y@Br|90VD^&H)>d{<`We3 zeR8bH83rkNVe!hzTcJBrQdY$ibG9`)rck?f_nl1*>~l%)F*4b&3h7~|MJ@dfvA%^tnXWS)iP(ZYK(`Xs{>uJiafd&38S!k_ zg6jxMy#UW;r9CpDFTB!I@0P|_I-kCopU)mWKR^HXLy>L!pUH$xDS-v!C`0EJyjgeYr5tr9#ZZu_e{U91g<5p*)Ff<8OvGHDMpP4%K zWVz5OUddk7;Pl!u)77OqqH!dWWH6alC?cxvB9HQa1)rp$P}VEpS8_f)p5}{AF!fs{-D1g_ zceFvxu?hAiAQ+@)#%#{MrBRc8eRsv%$RQj7j084-N8!ws;2nc#uRKY5wQ)6zZ#Cj+ zH3)Zm)z-#KGBGIDDN-5<(G;EBYE%Kf%ARiZri^?)6tSZGz_F-~#iuL(g31F=(db?b z`)TEm#?9Nm947Q|rZ5n%BixQSlm*Si1fFlnDY8ntmtyNzuBPxdV#jLp(|26&rv;8? zwM48ANq=#|bJacWigTqifeO=Y)?*#*%I(@F8;t6r8&BCS6;HPvdSA!)CS!Cs?>(A_&x_ePj&E92@b6qNzN6Q1as za65(A+ZM%&jz_PuIGvXA=HSbSmwYbV^#s8h-tHDP;&!o&+mV%^hPZ#(p}`!p)_5(! z%}7j$5^;*VOq?^#IfHNOKg)Gt)f06n^E&6xqnu@IVL1CGyZ)5!l4QN#QIWlTJ{ z9)=Wj*`4YzB8;e~!8ao2jtAXgEk>Mf(2#D-E^pb0)akK&7DH{4SDOnldS+D%_{V|!1+-mAhT!$C zs8|9l36#_FV+b&x6h=k<(~r+}Ppj9*rnn>tc!hRKt@zv1*5de)az8lu;N^=Gha~DP zK6+h&N6^y0e~rQ^=;VP>L<0*zIx1RZ?7M@nPr)My;Aq;bk2t&F197`2Z|$JNbo{N- zSuZhJp6%$7F;kCcV{`}oOyf$g2iFA(mgG%XZ|s|Xs{b~C8WB$W%8cPDV-(H z!6gVZg2@6C6O+@QRN&G^mQ1ZU90-qZa;qE4W5R9Oku_Xg{F}}L*LgPiEg3)%X3(qv zX}dSu>CDEN#|H;Cr`>8B7c{a)&Y;P~{l||pPuIRz32y5L_rF>e!xLpvIkZ0#3d2AG_h zDwBtIEs6I@L^DDzjlRMM_{dOoN;%v$%>I?14>&z|Vdq-i$9GRMiLyR!%{hEEdzU?n zGy_vkToY9)SnqiG+`q%E=UG{kxLJ95k4c_8lH~PVn5kq(NMS+py(aPySXN(~^I{QnwHr*cs-RcHu zX;#_!4L`RI`tLoEVLybr744)@Z1XL6_R)P| zFmD+D5>7xP2B-B%Jewm)!)=q>rgX?;10NR_D%Jwa;G0@qPo1nzq>TwuI5;_T)DEPc z8v+(Dhb%Vvd}if>uSI%x_wcfCTQgEIZQ+4$|EOSHfx81%I$&D&Q}?SUc8jfyC;tN?5pLI;YVr= zTn*=V$p=Exp0+bD$MuJwCvoKl6HEdpiZ=KBu<;Bz5O$#Bbn7Hn>514+aT^Vf=<0_2 zUT64sQXu_N3fpSNyNrYrk?}%Budpv-jfB_nJPYo zKHY+%m)Mu|TNsn#e)JCTz(m-^5`Y}^Q*BoQt2P23y?ZwppQ0mU;6rImp^rgE{KdV0 zXotZ9=DghcFm`1b->x&)unu!AxGso1TBHX`1-_N<=|B1jbigyIsk;!9TIou+mjM!9 z?B}AVM=c+Qck>cmPYm)ET44E6QuQVS(>JL0g11pl{Ysz&Dsd3=U4) zTZ#b`!~peb2_CWgQgF|Q!N&b;N=nHMzj|~~Uy&8nuYL&D-8d(~F7pTJG~7)Wlw>re zHR#iIKX%KID~A5z+|?$tb#eI4_93PL(%Zs<4&IW`mo>q3X%)jpB5*+|A+h)P+61Co_+Q5WhNb%a=ss5 zBa}D}B$wiDJ5O5&q6j9*y#b|Q@B*7_8nMZy4_kOxZ{E5Ut#B*Y08G*5`u7>z4lC0g zJ?MBRj*-#Sp1z%rqG9uoMe&!FY^kCLRsz^Vo4Jp8!Z1oz;tK~TC~R7Bgx zH@h8=FFcFsq=?g(^5t64(L4e~4`Uo9c-r9}_u6!4MyUWPOkTJdz&OBRPXF<+G@g~q zE&y(i+v&SPSq%e$+Z6x1qUy~~0Q_lbYx@yt+JjgoaOhu~lC_WhQ70twx~JQ~3mXl> zeW7l0%p)vAwg^)y7)PXBG^++1Gy8YkcD$;+c*HTI-F4H(z5BQl)ZdFXWJ?oLR?6lZ zIqfLMRSepXU61&wQjhNq=|49W#~tp;$|_G~lu+J&c(6c0O6n47Jyc zlYC))fH7ywPY3n0mG!Z`*aTpk{qMu3Se`foyYj1R323G0>H#_;KQT7PWpq1;HiHPS zM<_lis#n^#V4+mRW!yjq^00t0j{3G?dxuC|Gl28sI3p2=;x9gp6E{zez9t4P1)7Zg zzc;d_3SSn0d@J0W`TC~Z&_}6o(zSDh4t3+$pgizy=1MzNm}vP}x*`7^K=LFW38fK% zxCT12{Kfq70d6?jz6OQ(bL3H67%c1DL)d_k6AJ)*xtw6e-_ctn6xz#0j@K5>@1?-Z@7*0l7!VZ01hMiWI&-{M$Pq@FgcRwFuE z!m2KLB%PbyLHbGa_2jo*C4auross+87iKlFxxwAZl~{R1MZjz>oPe2hK3Y|A5Q^d3>8RG=lPQe{WN$kZx)H-D2^vR+U{~`w6;$F0v-Dtc(@OD4$JtLGCWc1<oXe#W6}dbjt&4_L-onyVv19jM4!TPJ%*zhW0otFX+@;{K`V*@IESc z!um#lo7?K&$w9u~%)JRx1|Kt0v#wm$VrvaccYI(t;ZSV{+3j1_VpB>T!CL1wNgNg$ zxENp}yooAR#B@t^ZO8uhrB72FwUQNoLgc#?c5v`=nHlB(2oNUQ*gyQ=ug=s>{&M>2$Em5U0LieZ$lQHSA{UK}H#Q5Cn34<}ggA!6ySV*H4%{|U49>llc zdWodYwN{rRj0x8d6H5En$%hFl7qzBf*oni}%f!OlN%z>F2XCSj6csJn>ic}`Rms8| z7s?_&)^o(#i`inioRRS{w36HUQ_W;(B|-`?4&oy3`6o5V7d2dR8Ir&@loU^xJ(z?c1@2)8dPE{3i9NNKm)m`fN$1D=bjBsB(vaplAOOgL|EA@J6 zCFjNSQ$b(mgI64;jy}w)@@&uVF)&z(m@?bi;oP@{{Mh{d`_V)7#uY()gs||oV%Ju| zW$i+8@h*+m39~ZSnFqbIy|VZ10JF@y0LDfN&1XUp^o*7;sStE!Y@#0bgeo&qc-LLP z=1HJ7Z*+R|U{v&lGV!oCj%`PtoagSOyPiQ6qIyx}+Pb~_=mB~$=a|=sE2e(!yEBAs zhg0nhcV5^VMVEC42+XBy*iu&dgJ^mKWs}jl`%ay!2{7SdJqRW*_85Tp3TM<#(!jxq z*FN4?qLDfAG-!#5pz0lQ7GfNu)-Mm$ej0 zoQ2E@J&ZcMqC#=IbT*VJ-ZJfsIZ^4J$4y>bb(ya=IN;hTmIs_N+h3o!_V4MHcY87) z2iuwnIh)(BFlsGP_7`pH5AuH}{LEi|Vep8-V`82-vUB|(^X;t%CGEq^G*o#mZ|^%z zQpY9~)|osj?^gcCI_k-2O$9yu_vpn+FluD7=OzCvt3A&o(r3mNZl)36uu-PxIl~+9 zE%2i5>xL}jJjpaPbk!*K7V0aY6>>Kpp6Pk{#;;U(tFdLu-2jI1%2C{~y$>8SIxzA< zRTdW)N4&<0>H0;h@>>@6QzKYjMg03@AF%nQ#mea%rSO98`Ur{*o92lo!L>!g^6w`d z+(wTqN{=VBkK3y|97s0c?-8s}F>o6dC8;wp7gnqDvU$dq>D8WOR2ieXve+|P#W9}a zN;YF>Si^pobSZ7pz~`ol{bAi;+gB`qAF@pZGT6N6GL3T#`h3*3*Ql7bouZz+g6QdeB!+RUXu1Mw{$g+3z6*#`uKP%T}!bC*YAFSng@9q zqaLPNdRv$kKhfirUV5_pi7*zyw2xT$0zos!8WY{y%=q{PxLo2417=A(ejEzE_!Gie z*2k{7u4ix#qi-is)c^&1=h#?TS)uqHMowmEWoh6-?oY$0loidxSGVsA*+!~+z2gkY z`{pIBmabPmVnZHJMD8EXk>8kY^x%tEz_3%rVz3yWyER$Wpi6Pb4o+{Ha|(NpVMH*a zGvzy@4^tq8Go0xzOf33O+rNvwGh8mQ?DIs)$~N3yVOFr|Nz|vlM>~>F>|=M*H*q7$ zPRE<2p8l#8^7BV!o5S@uDeYlT6Rrm8b*`HPHI*Qe-S>|6KR=MDyD@x}i)PoXurqtt z0lGZO1AD4(K-}>w%vFUScUSsvpOy_8_TIgcuA0sg`2%R5=Ku^jTpRhR-IUQ&*fcHg zs)((I*Y1P!mDtHb(%k#Iuzgp5)$EYVg_p@T(Y>2cwHgb3P>ef(CUWsE6W+bdySv5~sw)p&eCbhrWyXAgg zNcCHwWqZJ6!6Fq~9{S)3$&BvjC^r+@Q5SHNV-~;D!!PhT1PTCyM<+<91Y@!nfh*FZ zS0&v?-GXk9%5LhsvitwI0C$QOm$RBlJ<}#teLgl@qZe6OBi@Gmyl5ZBaNd?w+@ToQ zV^>RwU`kIHjanI})_b$3%gSu&CoG=|-s$VK;q59N?`dW~#u($e<#fKke5HM0FO{)M zX~&Z@UQ}mdrrctNusAHapR}iP+vkc}*)`W%H687`Bs2@n!%ueR2%nkhOC*&H$Bv~p zh74RXouIZw+umHM*|^ojMeF9qpM?;f6TpxrSc3`vB z^u3ik82&)+NT&U!00E(Iga5WxaIH4eN&ok0N@VlnI~M!&OY8d+A6?Zhx||#+FcA5H zzs=?Gm|p(S%O#~co^Sz4Z_j?wxAnp`OLa8m(d8Xf))Jjh>E_;`th#EXu-pR$&^|1s zs0hqvjFOX?N6BD1cc8r=DQva?=05wUyzEgHl)?oOMZ_AmAC9ao zCzB$Gha(Y*6p-Nc`24gn!`b%E?72<@*C4)9m_J%y{~6%&;x(gtQ{H0X7m@S2v`@;7 zL};F4$Svd-ds(DLNJuk?@#UXxeAt$EBbw{`VZVv=7HNXmobRt(Q6w)K(ZQ;Pw}&-( zf$Z#3s?FrYap|%^nQo&}XJqr=HN!g6GHvqO`Fn=om zaxVBYQ-5aV*N;2Plm;4AwQxGJ0Zighd(RYvRs*0oG_)nHg^xf&NW5_3{NVUR-&55CYsy-=SdH9q+|hy8By8^`>x4o#=bE7d$yRzX$i z#FW?BZ6)r)yR_?)YhSW-i!F1!i~W>;MVO_?YrQ2GlVH)~z*M$JIy&EycK3hUG38Rb zZJ*b#{5V&}9$p{$!;+_Z|Jqi3&|+j8iT<6x_l)6ExGc4W&z^KQZbwP<@9H&YkA#1j zsTE2n{+T~AbSG1U%J^IVSSa?MpQ>6@P^Z#}!kGdJCBBj5^Pm_GX&Sno&4NT6yJ0KAif}(qyr7q&PD@N}$mu z+dIo!JS2FG_YVe+7uk+3ZXjgqs*WAVb`X7A+L_zLvn8$iETbHiiT|#=-aoJUQxE(e zf3b=jes=`+scFY3&CG^bl;0$hEr}M#oJ+{bKYQP_mvQK! z(=q9=g}CmDhyZb>K!)P%D`0>-@;v8}|0!9>OZG?nfPn!6k6#wX=q&{IwwEeRZCBr% zcE6Qc_)xzmJ3pNu2|1M{w;QJrX;IN_`xbxvKkWVYKh^*L2aZ3^agyD!GUFh!_a=#x z5QUIUR>>$cib8hE-djepsqFI1$R?Yt%FHM`5}(_7y}zFyzL(4QPx$uB^LZ)boX5G3 z+wFS0j&IxyE=u8CG!M=(p;X)KiHc7#Ht2Azj`47?&OLZkbj60qRPC?Z1ENF}GDIS& zOqGpNCi15)WkLU*Uh&CCf|#?P`@8C-lU%)?b<>mCTa}jHp9uXX=%!Fq2DHTosi^C=n`bb#4Dybv=4JBejt&v-VP>^p zHw=BV8YbIahlKI;oIjcJj}9Hmx9KXOr<&DRNn!*eP0f&$`~!J+>2R`2L~Nk9A4NeD zrXOVZg@a$NYMWfnze5vmaD4~3^cqddC2^bE+pA#A-)2qCvd8rG*>I?V6y3VIs#_*<# zaaRPCy_QcDnJ=c_4k7yS`mWYH3eL7kUG`qglZdhn#dKpbB!ViU;8kB3 z?cF?g4@WBhKU_X}0_SYdl9=aE>}$|D3Ax zJn7D-lhX_&Jh|l`f3nYgS6y>9)CLqC2Q0J9ar7@p?V2%Qwe7LABh?!^_*DPH;PVHN3QFNh(>Ra3PMBJJJIYHVZUZ$LP_6wbve&^8&r! zztY>^@=UU4Hrkzjl2PJV<}&w_?)MAb%lmzzWmyRmvKYO-L#fgqG?=Ms4ei zqOAUWT>3@I|J43VK>Q>7Ti41c@%B=jV8H;waG3t2766+SG{8Y2A*t*q-#(r9U9>De zjE|i+$z!wEDB`wuv-bUky+ohFqI1g1?Q+=spcst_?SiQ8!cAL<|;$c!*r z;QS6C<>CIXGoBw$!sqK-6+{T8Mx)x_Po4auB0}RU#uHwvm8Cmv2)=jIMrBZQq4ABl z9?Zpzs>oKGQ`BzPdwTWl_iI9~A~%&wvXA_V>@WWKFMH2W6Sc1w404b zMu$IDm)7drN^{lnmD2a0=O+F7Q0Z>uA55E&7T~4RT74RAB7bH4>~)s?o)c%eREzq> zfr*jNRg!2-2*PhfeA?mZ)4OI#1IpwpNOt1=Mq_gM6%YA4y{V_@yS-mf+R$GirJw*V z)=z*B2*N|(RN3szs3`kag~z`1t7mfW05Aq*c{CC!W?)ciSUj|HK9?{bW%S!_=NgQU zHxjc`q&6)W4@% zvid@jlE$U#E^LU#=p*uY;(xO{V#N>HEviqP^who_1m>!t?uFhxp`MmMvpOhE(wyWm zC(j$AIby%WxM{CKF1G~K#s1#ji$E&#@Yn(R2rDZq1+Z#UosJ&U%Yg$23@XCzLSXpW z=UV#nqy$s;60^G4FkfHajr!Fxm}c-rRd4fX#SpUGx1_GrDZe)Q&#J?IIplQAZ z5})~4C?$*6yH!2*Z|Q}3c_d$5fx-y$GrAksDRCcXUKMBSQ!~VwT%YP`kfdFin&gD~bK8wjVVanP5&>dMMnu; zyAja2NLnEekxxHpt$UocGb>JjBQ7vv4$SGZ#TN_5|FQUYuY1YYHbi=u4@s7}W30$- z>eJJ;SyvWGD;Sj`h*l}5`toG+rz^MIm1WS&uhHVSH#q#!BtgxWk8Uio)?w(`>`T=5 zKWAGP@kTf+q4?bdU+cIEqeU3LO)i(-v)=+ny%+GHuIPem0sv3H&h6ZdtXM44{!#u( zPpn@fxrR>KQa8wwxjcBm#Gxa9Utw`jaZjM^XN9K1e`czpSArff4yYM4t=g33>Bbws zzv$((XAz|vC*CWfxDj|D7`4dwlxE^n*#A$fp(E3vf;CwZk{-0Om4Tok3(nu?uLefB zB>4@#C2*&U0~s@7y@1BK=`cPxRXd)vz3)Fu_QQySy7tx4lf=4(oxj~~{Q5BnrC+Hi zcR24}Vrf@me9%a;SDUGD#1Ru_a0q6?_g-827}Y|Mdx z*)UAL2$2s!9f1g2PFP3#fHncjq?R51;1HO#=U}W%4Q}?_ZeGa(?*V>^jimv8BK2eN zBkF`H8xcWsMR(;}`scPbAoAcFA$&W$W5vS-=nG$4>5^za!G@gzo17nhIl^$$Kz25O zuR{F%z#j(k0t|3zDJ9*3I16DJeFxy?0r50UEPqcX6n_1A9kCveCE;F^7nh0T>~l8l zZ|NvsuE;Uc$@)hccDNJ1-gM#T;~Q-uywZZam&iFaMfCDX^i(NPD%!u&dN-QfpFGua zt6SsYu%bct?LTwf-Y>_``yie75qYU7ms_5BzAc5bU)AZyN!}`vFj)lv*^& z^YL{qxPb;n`ra^bs4s)`VyDRBln)p)Oq>wdZgqet18otqRM5)$`uf0E0vMNtnwo<> z<=`OxJyp}gi?%4_$alQs8I10^-+0On(47Qg){U~6GC1vCkUh=M`U)&*b=2X7OeFs$^C zV3Z=MEwK%e)8-$1<*0!&DPml8UP9vLJyE?IAlH+F(Jsgi@7>G7$H&H2`5w3-pr8u& zz*P~(?l2l{ZzUv515FLg4YpSQ#nv<@y?O?N+d2q@#CQZ!_PMSZRYMFi(hAm%v8>F}00y)oCyJh$ezxU8OE?3f?3 zo_G87>(+XbJ6zc8w^uUV|JJ39c`+(y^GanWV(>TwA(B0fpos_9x~Ne?x~RT&>G=1Q_Zh*PL?zIT3pA!sk^Nb1~a#=7&yN7l(Im{?+Wq<1k1vr0E-N)xPY|*iwfdq zWW~f>LC{3dEHa1p{3l9*Cq%q}<0KHoKvq`dFbSF>5dO@$)j?x63T6g%NR0ysJR2JYOvygXp&s}@amF~)gTOgI65L)yLCJ~KTX+zvg>qrkf3`K~8C1YmJv z`~gY>#xBs1A>sv+ik0BA0CfO?ZV!|KDR%QQDQ$Rir#N8}Bq=XH`C~J=fp0#&`NyW9 zQ3Xy=umsL>7{vnfmoUSsb1y7g1!ht?3<*H#${$y_mJj47_#jLUOQ@J73BJL|0~jL0 z{E`mr%u^&juXwx%Hy%l%onN!FqLEOX!IDBNLn#BQxL_>M7pf!{B&wdE;-?KismOql3oxGYv+zp~`6M^K8h%n3~{UaU+?44yQL3Y9>h= zifsK5)0`+`36H>uu^mjk@)|QXA%mEMv!Qf3MEi#XlhQvtdANt|5Ko*+4BL{3Dz|&^+>7dK4{t~ zC@A6`AmrpPNF><63J7NQ0FS?njFfZ~U;z^kUyyx4-_~q$NAQJtco;An8o;I@{~awN z@A>mBlsOPn1VN>+N#O;KPtJ>P$5V>%^QX$(XQK`d3L>NsiTl6A=E4*fesmgm#eZGT z1Ys*-);dUpSb=*9QUNKq?)_j{Rr2mqZ=3GHtxoke`Wr8p&FQ7Cyz60yni696V0bg~ zmk0r21hCnmlz#f&8_&@S#N#{@_a850x^&0iw!VZ_1j`BeP2i5Y_ev?9g23fk5H}ZW zCW?)M$OJSgFoxutxu^UrMw`HG4TQ~0!T~?@+{3Bc(|6!>B`zPkh6f3l4G>iLqL^$5 zaf(1;4!;X$28c7>Y2D|kg$i$;|J6s2B%WDtMVX0@iG|N@1FZ3(_J9Kg>Z!1Zh%Mdh z?w)NJbAunusH8KLig523W@XgY*1}f-(|sSfDsX8*5?(r9M;m&<;$buxM*CG z8Rw=gEF>g!>C)_HcQ=qPM^?i31qES-j4p)!T3){E&z~4#J#OmDT)O_GlYCbMcq(WuaM6sB#y%$3WQa`i$buz zXsryUr`=L2Pj8ESUFG899(+cMfPi$_tgGkdsLqgN;m`ye09D>iU7c59t-^x~!VWhd8FlQFIQsRD_(^r*IB0D6NWdr3S6ooy24`%@O*!oV_kB+U z2tfql4kVicxIf~VrsA5P)0&rCg~4FJckYjw)Bhbrz<#I((>u_#P67B?fJyl73>;wi zrGg?ilDdPR{N2kIyukeW_!UtJnwT2y9p34?Amb}*+A|rVS3mBBZrqN*{Cf zCYqcaS{XEycEnAEcdSV|I{(`S>nw4KXJUqh*U!CmDv*@G;}2rKuJ1i+;&;hX(dFq{ ze)H#*ndgWBe6Uy*r|J5U)!iL$h`F4wdp{bl1V6I_dvOXYF_c^;?{6% zMeve04C42`Z4G!fz4^%_G;?<%Lr<*gkqSv#uVDe1uz_z%-Pmer>?3}LoG$C3&uWXW~_)TE0rACKB%;SEwC_cYIAnD%ZkjiMU)^MY;07-H|~y_f;=pH z$Di(Ixu-1amggzTAiWeagB(nQnXp5xw9M`;(H2}T2>195AvL2*Fhh*zp%L1DWyPd8 zGfYL7MaNoi^J-;TM2a5XWsgB+x!wX^!q$cq_sw2|8sDBS5!3gAa&k=6rzsc*ASe6e z3LX#Lblh`ai*gxo%M0ZBga6SW8-#_%rHZ-gzCfRjQcPG_7`W(wxy;sCf2bg}@qpJ4 z2GnWUl&frBxXNTspYSQ3*6?{2-+W%nn*xVL5|H0R1A7e*m6HU8>PE<0`8%K3<6^cf z3WvhOw&pHW$Lcdw3sBvh2DBQs*bI{{SfID{_h317IS%^k_ zTVispl1<+J-9jdV;PmO0A>A<0$X-4$ba`3An4$^?bU4A)5e{B^iC#rmnc%m{Xd}Mp zPZLdcC52b4=rx_;!<+7}6NpxUQWTq-8W9FYDl*OwkpXaG?ehVdF#P@ZR|VKFAT9@M zlskNvMO=Jns9RcE@^v7T13Zp21>47=BLzhU*Oix1NHkjSUVQ;%l7jh^A1DJ5ES%(j z*K7VV+|y*nuZ@^UI_+L6hs89{K>!FTkS?x zR#w0d>h=7*B_0!11QdrVbGXSR$E57QWeJQTk!DefEX12E_)~;peh7ACEN0*w%fUH0<2ZcqM{_!3tOkVZ>=~hQTof`XB7IfE8*jPQPBH>U1?$?Te zBxvTMK69<7kFwg1Lo61Mm0|O0-MC@d=v|4mo@)*ej|Nd4*0m}?+`=4kxU{679{V;- z&wz}!jwU58ZX0wY9iFWq2i8c^fAibyhVeSI%kMx++g?vjN!bKxdUlb(B0IqGQ=%M)-pL66osK1eI{oY3pFt^9YXq?X9gu zcmQyJ!p|0Kzn-ZG#-W{ZQJ@1Bx9zy1u!YO*wI`?szqdVaviJohFJa#FKo*2iK#Kr7 zU7ZsahzA4Nyk6e{(1<|;wXo^Ub0gEK<|`0xLGLa+Z40BzLZED3Thd!?imW7r*%Ob0 z!1W;*WB_jut_PsSHNr~(a^8bJJ1bXkSmThsc}%2y}4DM*}b3%YfQKm z+^Q0;YGlc%@?Tv38N%WM;#gT=iNY03Xud0MtNzbgY-hFTR{0Ew>GeA!_`oYTK3QKs z$n#)e=RwY3^miZ;fITYIJuBe4Xl0e{IhR;&V{Hv#x?iA>Ajp*0y}CPIr@Lzp!OsOQ zm^#zx5tH;N=TUx}5}HXZX((cWG7SfL_p92x_smR8a-jBr?ZPha(P8=G`HL6lM6H_P zTPa$iQ6GAbdcSwLy~*9RSast;ASDzs?j4{UhMNmU)u+#}#aoZqSHt@vu=Zh;Yh}Er z@Jl}bXa_ze6L7-?N6f0~>IKhsBY67v>lV(slZWn}JyGoC9+{h)JI&^81@>f6eSrfi zA>W+OS=tl=m*J4Gr>wr?T(BE%dTqzJ_gz7N`z;@X+FNZ2m+dZ+(>+zudXcAfTHU=! z@563X^}T=hAh8ilPdk%|K-~w6ot%`kGSPa`{^D}7U|IEpvh+Q<5xsUI(Dqe)U56SK zh+K)qc_FvsA3k!|)QpI%prxlD0l$Odydq^?2??hfXK>yP@qx#gNUx<{}}gg)Wo z#SF2tkU$C4Es#nUKZ})WTD#4(+e{FbS`;0_{$-J!&_FNxh$Il_`;a$zfgQriU@RW{PGtlXS;ZdF%JxzF2$8QZ_9+ zP0Qf#;#c65UvRV7wn{1Q?)mRdq%^J32%UT5Ud?9U?4J!w+7y~=m1j*X9sHa-JpX5SO8#2K*>6KX#U*5Jsg@R)RDW71TBDzLC^bDbvb~Ze?-?At>z+Bzs$ zvN#(dK+MfUPw)C=R3eLG=HyXI=e%X{dET3hw?u1iedwdtFA03?rzBo%V(&sg1Yv(Y44mEIwVHK6sL$w4@9H0=d=d6MMfZ-GQ&UT;kFZcN57)Lhk@C zrS|g=AD}|RP)p!``NFJ08~0U*xCqVv7~={=-AiOBxI{#LLCH?=27qEf30$54B*4b7 z-WKun6Hu8@6lh*c5vXzpQ{Y$7G!vX7pbOMgD4Z~w{}IfPy^jx;7EEt~?-E!t5)|A3hyeik5&RNB zp{y;${?J#_Cb;#d3;!6oh_l}t*8ho8m}Okd^z|j}dl(%&Pwz_Uq(@CEEZiFA&yyPQ zb!0BVRsymHB8M_oTPR6WL04KPVf;&6J7e*y_%*(2kJU_+qsxPhxefQE+l)#JU5CjA zu_9el`nXH63x{+Z5}P7@JA*qD>^?K=WTWxN{}zYjTSR>ajV{HOa*;2ZKHb;NT6LS@ z5<(5Sxp-x#%%s-6BPvb#tKU57>u88SzgE)T`7N>>~`get^F#O1OJ9bO4Nj{ z&N!ZpwHX{6jddRU;@Cw@!!)quxhw&V#2(11d5(=2&fSH51|6{cXuh( z0!|G0AfXW`USL`N1&$oJ0<-{bvsw=f=p9%(1_J?Mpb4vLZN(#upda-1{sXle1K?Ir zGsD6LpD_@&5YjgRB1q!85294q7Pm@_Tpf#_~JBMxX^ax!DP5omsZKCgub zs0`jDKorOFAk~0ZOU^;C*O}0Ae`L$p4#x^Sm5;#lhV}{eFDM-;*_Do9di5jyWP4vU`rrg|`Y$A7hf* zhBg2#LSam!G6kelVC@o8Pyk+h0<|x6YVa@sP@M%C00B~Ynql{zEouo~r$v=*CoBjs z*8wga!P|tCmIn@JTSy^@ILxR{tqK-=Jp0Ac$J>Jq~wVRK)yfAD01 z3iT`u4wG`Idi5hqNI@p%4xOC&*wq)z^lWUIv9VjA2=cd+34=%zCBOQTpP*|mwWgsa zm>QMVGS6Qdb>*T2L z1sQ=g7r;a}@X$>hfiMUj=J`|MYoP=+mdU%H;5+jd)J9G4Y(wF&1B(*9>!R%d&Oj$x zW%Xa+|3~gF)(uyOS-nS5*(%hwuupdPl?mcq+YEvm73`-E+kFa)JWPC%+G5}<5)Ika z6FuOQ6zBeA#Nd{XVkSh;f6;FMET$ylFU(fJcLfP)S^#bdaX z%fJc()&lZiwX}v`%qK-}-8vQ-@U;sZefWK9g-Jjg_xik0c7LIcTX+e(c?=dILm<;# z-9Oc-(n0c5?1xW3YqI_pJJPg>681s)*z+)0u1VH|ZznZ~7?ynDRe^Q2UNSsD4_Oe0vP8M8t?_JD7)$7ihDzh{6_w^wIZq(;G<^k3eqs z2Rf^Y(W^6$0U3qA(I86$C-oxJTa6m+_wv&v9rR!`nVFe^lLTygXa~VOEh#RJ2m!I7 z0a*JJe7lm880hJxteX8{Q^UzaXt#mx+)RKf;e&~Nr&$o|52cP-m(7}IOj8!jJ9YyaC5Jctr^pVREX33`my34-oeQIMn!Qd-82YaNZzTY%R3G>oOk;?+~Rh z)-*AhgaQf<5IaUSNH!-(GwJC)Vg3q^8H>=|@bJt)f&>KGR|sPh5B!My3EC;p?kuJ& zu)YL*)^lx~x=Rd~Hd{lm!GwdH)wXJG-Tk zQ7s(0fOufPz%dHb06;6oO#fEAe!UFdKpNMtCo%xaJoUyv0D(-K$FL`>`<0-9j{maj zRyf&qK5?^a>72k4QHUY+bET|^!H2gNx9)Y)p|05zL-;ikfF+q*(@I5a9eF#y$Zf4; z(QhetGAqBXZq@+S~+q7C^>;x`N&H6#GpVT;Q;3z zIG&@Hge^F~12_xnQBOjFhkKX8eCE9-1lV zX`y&UAq3wexY#;az^0mki76!^!4qbs;6~!LF*U(o8U@!BtnZ0-KuN^f&k7ph59 zr)w38-dCQLy@y)n!b#ZgV9|6Au0jYqF+O`0Wd%P0+-;Ow6*g*ZKqhv*@aE!nEA0(} z>kG^VZ)j?!LID8fr^SEUoVf(ODIhWN#37(9C5W}Z&(Nw6-aO0|6>TEjkKrVSwc7G= zi=HszfxERQT~30ZUo&xa(;H0Hs=PO?@r0Qxlm?~Npo*AL@cko79UK}87@=RYKWcH9 zAPR>0k5`>p0^cp&v?3G}m*xp7;aH%~$wvf<9DKSZrOv0G&*v9sxk-YSL$V~U;1Kj} ze=JVZp6t%wx6GGY;ZuhDDG!z~Y?6e_p8(YW@V6EWu9xd)VBQLLOdlXy{x~&m_$w6i z9{}TGI>eha+jhZ;e@Q{Xl*AR_xsR|Y-1dZA%)>_Q?(Poa4i@%VY|-_LP`^X*5)mFAW7AaNkT7dW zaOweR>D^TO$>?X#o>{`N$LpzB_w!A$#gl^t0BVPv&*N}&V0o0}+XS}i=3{mVFb;t+ z=)!K+(_^MS&s)Zz))sVy`9->WF^}_y3MI`GsXBr~ThzrpBM*^QMQV#=IFapAdk4c@p7|DTG>Qp_VnpQq z8U9mlQO*buRS2E;$WdGCvnBmXxperPMps#xPy@;?*>w@gJs?ytz;n>ym$D0i$s@EF zFVg=KRo)-7^*4Ns3I>n5^gt7@X2?7i7Dmc9L+*2+( zUkjJ89rsv?vCZyLeAiVcr!^Qa+1-1sL@z4>VsfF3RWONyvJHlQ(f1)umqzV$q+wOIr>)Ll)dKZ0^~dOdOx;b zQV-sO$cR`^NKlE5-(QqSa6ag^JXXPjlock4yyfhMB}SmyzM;MeHOE_UwaA{Gtft4C zK|um?*n(AgPO$bmVTI9bU7yerDq-aWnqwd*q;3=OgZka*eX?w~=oPO2~v-;?j9E4es3qyfUxGXd0TjJy#RXKW`I=Fvgc@8h*^fw}KTaL9|rgn!ox8#_YgtfCga~Bu#`2 z6F`$)#>z52zIYxs|0qvN6#;|Ip8o!;tpqNy<7m$#uEdQyFr2&sgalYM1St>HQE*Y2 zd(is6v2AjB!hcQ}u>u{ropc_oI=Ecr<-9<)WBx0(5Tc z3RK;G;_+=GV8M9tqPeD~L+WBWlqxy!WPsBM2>b*9$~n(gl0Y;jHw-oQ{vMnBZ}#v;vk0C;p$F>+`cfYY;Z7KoG*j9i)(ss(HMJ&=CVZ zs_RhlWHIHA?|kKr7F4rnB*5XCL*NWmRG0~!ZF;{@0tGoeGxI$^-$iqH7gg?gX7LPg zvOY9!1ue91#P)$6pRnT_ORd3)%Z+%Y*dw%0OqfUcm;v5Ggt`9Ph{iHP!So~bg=8Nq zRqr<|=@;05HEuavG#{?qiJeU@k~RmYOx27E$7&!%#GjCm{Cjrq9zKhAhXeFI)$^_T zvQ-Jt1}XVLT4f(>m>RIhkSoyV>4*A9L%R}ep%;;LiCPj4c}NEU{XIgk&+@s>YJlbh zy9aec2N7+j{c3-@&_RIv6OW%sraU%Xx8_9>Z~=R8ax&c+1RSC!pSX7UEP@7+_gQR)U4F^F%Eh9 z!BIs&tX;sM0`7k&F@L?nmH`cN3uY{y{5X8?$!P=+M%1T}7SVXS-L8Ve1-Qu?Cp ztY20qoVo8s-La@{rylVX?;)QdO`cldkji5TIhu9h`R$V7?w^N?Qo4`NJ?$(r5; z489j6e>dTTR6uP5rAK^C-r;AlB>}66NZUhufim@bRF<4QXbC6WeHE zn*yd@n)g6(mvYb@1AgT_h#UZvF}TFD6>tf6J7f_%F7Pf z;y++OM<}{jLN=k^@*r>^$}JX{SZobpZVV>wbKr4^3-X2@20Wz)VN17&9tCf*e>P(l zZ8-&n=AR14eE0$w@*;8Dn26ANXhetNIa%lD~&WZ#j9MbEY@;GGSQR#5!ND74C%4gkdlQtLTU>mRy}6y=^Gpif8FtNet!9Vu$|!IW z;IiiX$r9RONnd`;fGr?QHaJb7&?#GmW;agr2rlj7#u8|f5Bwk&)5u3$PtTs$t9=~) zo`4QRn#Vk_XX0mz3BDb+wttqal)|8TZGC@mR9=&^5tWjz3r8bRVDev*_gJ4a zcys}HkQy(ikG~UaRN%_eV|%zV*rsJwJz;wpj0-$Y#2mRTJKC;f{V87Ha3^qzd;t`Z zmmqr|?je!0eNIoen1qTd@v$g4+l)dT3&gdwULbwf{Q2W8JLY!$*#RPABJX&Bdk2)j z@kP2C6NZa{&=@-1ykjwgvT_;k`<5_z0t5jR9jv5RqE! z?Bzs=eWWR)>$h5bQ&!5w(Kw{Vdq{Qu1dZv9hBmo9z zfdiDxF225s)Xp=F#v#8;l?e0k$M<1icYG*pBdDOzp!K-q`&RB@Tb`(*+=MQjS>OwPmgmmgnrG^s^ML~=Q1{Dr$(sTz8G|Szkx@hQ&J9~BZYW)b_9$Eool8I z511tX=ff*L*9?Uv|BWMDw?+hzbP`{tA`jr0c-EFa^4{6>z#6>5^jfO7x&+GO8QiD7 zJy2iCgcRAPOU>3q;G*6JmrHoZy4NKbCxC;bQVm220kt6t=vzP(9PIE#{qyF-<-!3B zB@zs~H6wPsV!nR+W;Sb%03|^0Biu&2-ii!wXsEwHV<)f;$e#$HOLx7>Q#t|aQUhqr zu8ZzO*~M_zoP-)&*||!In*Yz-mok6w%i%B_$(LJUfDP#8HCLz%ew7(l6Kf_B3m5$A zx@=el&;pEq|7USUfW_7XjB8*e5YV0lw|~^ZdNU2ckO6dN1we%%&*`;JSUb=Fu5b5W zl6flr?xffVHTwDI$=;w|udpIsM}JwUqngMSj|-w1OMBkcbEH8#PSSYbBb`PRsQRtS z0tBSd8!~8F+Q0?$S6|Nc`|MA%7T?jzwj`|`t!7`p*-i!j*^oPRN_+J)UX+AbRjo?k zpRxYOO;)iB7w!Y`C0d?ie;3Vw2xH^Nh8i@F1GSkMc>lqD3-7oZbE zaMm%{nZYm8)wDdR7q?4S`B_rE7}AeI!WUk#t$0sMl_Nh(Is=BM|I*T%s> z2oMl(<(vAksg^sI~2# zJ4DFR5rDTuNYrO{z#j_Ww9yHJYcsUlhi}*n+@0jaC`n0k^d5$$?k8{O)VJmRUbGz# z3Q!MJR+A`ver|{~8HGlXA{q}kcBY(!co{1uGZY<((L$k>BNrsHj;+3tMhw5hU2Yuz z=5uFv!zt&IQkCg*Q3awFQj!o#)KAPvJQu3_#DD+p#2QvHcE zDv*~-=zzGn+bs1ox#qz>Ebo&T@Sr(U!7KnudAnoa2NMP}&X@Xrlk zL75;(LY?Ka$duGA1zDc$<4{Nw9G|p@@@9*seo>!2OE5G8L!b;9nZ5%kO)7xf0ZC=R zmM0i4XXYDJE&}p(1`Ox@07eFs4!RdY)J$<_Mj?RJz;&I1Eqn%nj21Y-s_U%D5a456 z)7IXVQP*c{H@ba0Cq4%60on~j8DH}?BQsNYnvlkIZ};%ST7|GaY$j-=Ns&i;Ykb_? z+X@%nEfytaJzS~gj4Iago~EF{qNC9aBK`j_7l2lLV(R(b%Esr|Q{tFD5?Qhw1|PHF zd88b2D35`hQ!FWe`8f;!)yQJRlx1Eyy(0y^hf)FwheC>NgX2G z(#ka)^v&RjrS`0f22i%(7(GqNyaNu0M~v-oAlXL3x*zzw{~Rm7fd}STTBNFeCyyC+Yv31P?EN0miDUU(n}BX| z{b;n5#Dh`b9$aM#8GSIxm;uPWDh!(LkN?*1UoWhNV-W^|g*Ql!yUDjNWTmAwI1KTW zkFD-Lf^_Tnv^2*j9uAI1BeYnwvgo{K+V9$%Hyk(6=mE@We{1dWpgCf)MfZ;RgKy~Q z=2tNRg3T>Rv!etCk$aXQw%NKyoJ}m1ZTRmpNi%|0=4tukgOVhQ&JG*;8RrnEWaO6q z>3Q9`Y!YLfcPCZ-X%rLrrtR!>`2;Q3qhZuH+`K>f@`X9&bWDZ7<@`NwMF4&kfxrsZ zz%Y$~@eokEMQUE$Qiky)ys`gfMm_>5B7B({%*|gv7>>6713=>}Fy*niu!#xNj+lvF zjNT5Q@@rFd5^{1A>t0}j2_x@%0)qLFylM*;O>c_CRgz7(y(3v<`pi~KH@;|J$4$9B z1p6YOjS(#24YtX;=v4@uSs2Wzgh5D(=dYQ6-(S)tUbwk2UdbgPF$8(!fF*;c8f1kq z%qE+JteLKe!#(gGz6Hh|1Q8UJs%*B?Fb84sz7NRZmeC5qM)7;7u;<+tQ9{!W6SOyr z%r~9WYnsB60le|qwja8MQ2!s%N<~G5KlAlSv&ic%3Tng6Ah(*vF#$!F>)BjbvhzNS z66kE@_Sf_2uQdaBv2rAsIs8_hHEs^EC_mk3;W6&m;4c`S=8vRfN&PG4J49c^JfBOt zIdR6X@sDINmn6&fS@fB}@85YlK04agIRA~6KG!YqsadZ0%sGWrzmpe!3hkKFt7_RH zUUYc%4oCMP0aB2#kAn6Bfbc*&+I^q<(5o>+DlfnUcLRj^wZ`Cl?wklX*QdOlO2^wafF*1qW5TZPo2HxXh7(!>fG-Pi9T^%aO)$m zs^Qu>snk^ z)<;;@QJRe%uhtv--*>=HI($}=mE8XW23JV(I%!Oy`Z4;NWq&Yp< z>_xoXRRyFQsdfJR>FA?9l z`-2zVsd2ex$)HJu^Wd@z?W39q zG}otsrpG7m?h8&HwT0ki&UkM7o2P92xU(l;c{^*bcP-l?=}VW>fzjy!^20&PWphKy zqCivr-{G~(l@?UlAoqfyvK%Lv^gS&_iSY$_!%=B_6iSdq$XDwR1if)1=DR6up&?F# z5GXyjARGgxgM<+p6i<%VbHbJhqyacF?Y4i_uL`FScKM=)JAJYk|EnPLv&kUOKTV}S zU{NIh)#*LdOrYOJE5qTCmlrh1W!Kd}fU8C>?!OTi3x85hdB4*9$(`&SBRpC~W$WIf zmGG_v7nal06|NkNr!mU0U$;<90b{040+l<7Y9Xa>3=NntcRPj(uATFa4!ib{2Twvp zsXP$;z;)gqnPa-t_&1};R4w*;Go^ohx_|GgRPiCV$Jc?N z(vbEy9;UKTULL}u_EBW8eptfx1~TXM@uY~FGH=k|d9j8uN`Ew^iYhHDW?x=?`|Qup zFEivCX>(c=%4j^TDti?sSS0C8ME7xG<)P%lvJC0biqeJE7DU#m#sT~ex+9#LR5RyA z8SI!4v2!t`_fgeNZy)^lyh_#Z77XMErllwM+AKqB+}$w3vN$@<@d(O>q!tH}qbEB% z&|#V4N12ApjI$-bS@1Pjmsu@O(PE2IAI8>B6E51c`>Vr6+kmWt_uoc@tRARFE0W||~;uJts3i29)$&+@+om4m~fmZ=_BOI&>GBPrtJA;N0?p5&r_JzK1 zelum{u*6X);iw>?X2#OzlK;Jbnzu5N1vlp<+wy9b=fy%T*6d!BS|hx*0X_M3DvEV~ zXM6=jfAMmi)10>ME;B3I9aiq8A9=g`EQ;=Hi}NGIM&%AM7MsiSdVIrU)LGq3wVCD} z<-M$L2?%xynLw`9a0|`4+WtyOk2@%P{ov}~+U0NSv}G$(tSE*x#e@Fxr2GlhaCAm) z#eJgBrVpd;+Ac{GML(m8Cav)Po5S7ka!ny4D`h2uoZQLclDAc1yUfG8)aYzu!G27E zV`Z-4+>ds>f>oW7M7w{UX>8DNyo$=T)?&9RE>1f5G$s6f`>E zBotxxaL+&BWC=NOzS>>y9ntJ?n5!V9cI262pTb*){)P5cwPD(}fzHEwC z7r5U5kDpw=Xb56E-RYqZ{Ac4ZD##CI<@)^{(hb)DEc$l|Aa=JAbc9=9EnlI0tFy9y zpS-ksbBjj13;V-$LYKHx45*I9HFjJ!9^v zm;FLQ0kP%M`e*GLan>Y@L91b@9!2^k0AVKia(-`dL+HuH%;VOztTPQQnSbvs?8m8{ zPVflj4KFH#clZuMoS-!KR)iKUF~O;7f6E5q?{mEYau`AVeM;$@6js0P=d0gV;TWPc ziEJ#>X~EqA4unqSG~_2N@X9YxuHyX>{i<}IJqo^aU%q*uN6rT5SZ};DGAGSuv>Msq z3Y)Z?9R6i_sU0=H&_kR}jWD8dNX1i?ow>!`q+i!eW0D_vjx9z;iw))^4?GYv`tX-G z=f_V&(rMc7XcR(9iJT`2K!{Mpqfzw*S9S*fZL~cWSMv(w#bQVXF{OkxhaST6_jB;l za=tOGAyoC?iMR`j3?hF;jSw;WcdeN<@%M5k+jZKN-9;OoF+Qsf`Jp$aglJcYpKv7n z>yPy;B4@X0m3=6C+1+1JHn*O6`>Gg1b7O%ed)JALT`LgrM9}9t5FU>?v?#_(z1UlM z+(*2H9EYG#|0!eTn>XS;=tST8yru0xZi`}g&Lztdy=)B(IIN*MO%-0ag+Z8!iQ0vK z+%%ylYs(6TaWW8F)SCu=Y<3{a>DvT4RJLMLB0p`MVSWvs$o?)l*$63y!|gi0q?ObF zA^C4d{5YpCd+jL+^nZ2JK_qevLGl4Yujh^Iizm&CRcbnk3+?I2f`Z1?ujir0FXOz! zU5T6;J+9fcBtPJoatN+-i{qw7PdK&ub+FFP@^Y#?*~^8QIgqH}Hs)F@>2a~sLv zP_+bON1KNJV?KJaD?(Q{;z-HJ2vTu~lsmnb^7+|th`FQz4^Ka-;VowCo8%PpRE4}u z6<_quAM!reIws=jI=GO;mXC>NB1L+?&7-(kRyU8x(~~HO2XIrJ_-~s%9?x8P-^q6W z@tndT4^V8H-RQKlY-h1ZHcAY4zVDPfE@&(qm*%ym#ZPk&y^d&Qidd82+=xj0ZB{1m zSO&Ic#DCKo`KR}^iJ&{Mo-g7{--aT5=jX_C_ddB_a9tW1CHo-V`K7lLSj>gmZ`d|V zv6*~+E=?0j&51*i&0*3JlJMxDBXSI0y)~Q>+dL7a{>Yh=Oc`T^!-g>q#cW5*H%sv; zf6)uLMv{8c5-Y>nOf}3Y97t~V^tXpJl6}3^gSxr%+IdVg?g6Sq;ei_Cd#}x1RC5`5 z=!4VYX4>A=*?wjZ{0krK=DhVqL{L~UWg70!FUFB6eDRqA=Jc9UmiEgo*(j%~f#_sjT_n6bf@_~UA%GQmNGj)my z2Tp4lgC`PbwdQOO?yB=I>l*R)51250P3Uv~aR`dO-Ya@(2ftt?8cDTTDccd^W0L1U zyqUapx6wG6Ms=2`{PJl}L4U=T$+n=tFEz&h!imzfSqI);-NsOf*50&Hs}SAXc|&_= zid6@edOA91 zo$Qal*iPR4ih|A+24W$!0W0~8B>tkn}So5i|%gkH8%`Yy9v;Fn<^s1Z@ z9z1>;6;*l-jn(|b!6#N!`@q&#igW8x6R-(^CsAJpNc4sC=kaQ4CWR*tT(C;3U$1@0 z%d2HA3XY*GHrJxT(p+|}lu_vA6}Gl|a5)lV<)DnH+>WS^eNd8*jt!c+aQ|lvTK8%n zD{8O9sA58doQY2iee{y05O+@-h$ezqeei4XLc`yi;Y!u4V4}yY_NP zuy*6$j^$#`XV1PMoH*g}g`vw4CeN7E-GLqs;wnB~-e^M+A2G#Ky-< z0z!&s1wb4M%bdghOS)z;e?GUBQo=Ma-h$tX1UvJ+M6#)NfQRDx`N!xS+dHp+v-C41 zq^Am)+o1+<_M*0Z%2)ApZBJS-#^km%+wRSg{&c4t%*?aozRy z3EOPV`fd($0lU&8Cls|RzxD9Y_gS#h@!6N`CQlc`X8>#FQ41zR<~})dB5MU z_j5cR_ebz7ipAkn1yQ=!poOO(qcxM_A6)#ujDVBnIdz49*_lbE@o8CS4PKAE3_PCr z$`rlxQ6RI8M<+Lth+u=*97()dL)e^^>+i8~%*(O%RjS%$>c@57qw^$To?FBoII8x_ zho4_%Wsnst5ufoPaP+!>Wf#iln>}m&J44-sClodzK=Gf2b#_3&G0cP^)qond}Hgp;Gq-)^0fHh|zh?p;)f39Qx z=|gC$fNo9#s`gV)ftaeIt5f9?uaa%NKE;xw>C^r7$|reedfA+Okku)>_2ib+=QibU>+<5MULpr1+Gy*afH!`~A?+vAPH z&;!9Z9R!*KtE8Vy1vm&S;Vy+~lkjO9C`D3Q1nW`kz0fkvIZ2{G8jTk=yM-6pn?hV| zKtP)nV`jO_t4M`Y;%t{Rv>Ry2iE+6#U5iM@^`^c35W=2Yr-^Z=vB>6&LQY~uLNS`A zx#uJ#HC;5Lj*4yy6iF2$+<3LP)WtkMjtKuALUb(ug6Xq(T?E z2(aX&G(wyNEgnQFuP(4SB3^{&t74@#)OZ+Gosf7H@{qIqUw@Yopn!g3R%d5buLsUl zKiE-&3^NddLO~RJhDB-=HiUe9)l9hjgR@_k`dj zf>eE5fzRS(%r+8lS!uPit(_AzeB><|!*{sY$r0(#=~=E+54}oyBD`b~LN_FycHeO% zg{m~zHr#k|ZScatDt|cn{kXlwBH3#w>`-F0*e7FBM8}#7bM%vjz!Cd0qyFic>biI3 zn#nILxt!q}2Dij=CO0>CBd9hOHpf`3Dl{_IO7w0CyEj}4cUQf;8ED_}Ed4>6V9H;k z7tKeRM&`F)ipah)ykwJL)V%T9N49n*6G1q!7!3Yl{ zZ^zpy@44jVAHl{3WT6A^-g(Pp0rm+R_NP>JkSdW-Sjfqaf#nnos#<{J47;D{uLt*Y z9!)}M5If#b!75LQz~o0BK0I)R5}!Ub7c_!pN`cD1ck>yqjSMUv%|7_*z7v05gqw!k zCN<$aC&~FTLPBbuqDa$6b@bcc59=mwOmw_6djPJJM_)PPrt=NxUg?oiSI}XC~^ENQQ(}IxlN~6KX;GL&~&pxRUPriI8B*NxkvNk1B-G29|kmcopCQVIc zrM9E5jpJ3PkHXP4kaz{_om6+hyZxIUuq6E7hF~9%BS47kYEnuHL~T6A!DbwM2=Jl8 zNI1THfbS6xx-x4RuD%*kT3C1&t{xt6rGrwui%$XuVh3ClkU((f!{5X0Qb9cP-n)na zhx!p%0qW9$u^xoJJ=)@pW8`<3z)BAyp0_IVFkAz2LNwO_^^=0?>S_oh2BOrNtB1K` ziU6ABqg_1}cFymci&WQr4+wsff%SuJE%0Z+kk!{@cl~+~Jo@0dKX>K!7Y5a7l{6&| z>2=U8fadA((5eWN9t3XRz73N0c^J;e!p=kLsW7C$XlsqrDWeo1+&adS4NT+zdmD}s zgG9a_ceVuc1&<6(raM>WcbWQb+(6Sl3jY-SbfJ&+)Vdr$zsAo(P<0Od{3R|c`v)R*2(TYNegqulB~DIwd_l6p0JCiv+QFv)X@E#dfqi;e89Ygt zUeIRa|45G2ikR7k5UvRfbb;OU^XE_Sx0Q?^LGCjw!}Z7Oz<_v(o4cgAxS>STHO0-{ zT>+waP_=l!87%q<@su{$j|1!suX%2q0i2+169EWC^S1rqtFX7{MPS7wC?}Wcj@RTk z75k{HDIlBP$^U(~FQfPs1-IBD_t`scK0?V~W{&l+KjC~ObaM{;O0Zah%+!LWCgZ7n z@b5*soUx%TFD|wPwNqq1I8LrYLZ4Tw1)T_}yP!FN)$CVzy}<)>>5@u-AWK=dkRPbD z@LTSy01wyz)jf#Pwa_xa956La*CpHg$xJ-JY%b$0i3hPgCMD=nz4(apt{X(ruGLdP zq&A3qs+)`Q+2IPncZm4_!U*`5&Ko^{{v5CXr%+tn-2XOI6)35w=wIW*=7F6AViyw= z6Fgl=Q!`rEY^)26aA4{xh1K?&Ou-dzF7(0X_&V4o=;yT&Mlh@z8cu>s*3;tMzyMgE z!kP!K4bf4-xdx%UZ215_0rQtR=jc-A89?g6dmTTb9$Ga8{W4gpfWO%^3Hk-Z5{CNy z>1kk-+dC1vq}q2ASh3sLPKoMgixDFvzQN`u7Z7_u$F6HI!E!=?7?$tH%I?4c67E{) zvIF0mYN|NYw5ay#r3ed{kHIh)IR@E42jE~%1jPUy36Zi_UQ-vE`mb&36rX7QS1kYG-ZFB8{ zkflBWo7|RDDc@k<>tt8RCNF;i2LU`Z@MBg^v zS$It04)_By)=d{FY8I*Z?CkmHziy$My*B$3*d83Ib_Attd@kxlOIv@n4W(j7n+zu; zT5DH=EwrX)?26kiDEy*p03!ym-#sSWE#H9Lw$#fnC>V}4p8;&3=*w`*?I$=$F$(o1 zws^_*irfHiR%2#nmR<*EWaIaykaueGZY$&nXvm5R3sZ@7P{P3f00IaCRTj~u8U5Kx z*r!BFcPrLh)01NQA1wgG*Zpy|V4Ni(w!2%YB`Nn_BvV0IX+h z%!v2+G!}fz!ML4_KAbEZyOQqLhBZ;MH#%U%*#X`J|0K&wZ*Ok{CY#srVJ88O z6nqG!QwtnA4Go66NgqDwTcxEg!`L(4nmj7+@Uj>`JSuRC~dUPMnRy!8HIOGW^&ciFNzMxT-8R_v;rmr{Pl5)A>TH z%gWwVb>-fpO>oJKw4yX`sU$-54Zwry92gh~UEk|DiHH@yfBlnE)4b%w<~OI=t`e;NTCsZ7u|QrpF54Fw5#0iXk$ zzc$Q7>#+T(&C9#iHZnM9-x|Coo}i-fR^db_W6ri$dk1rOR?Ph&(GbXoTPj8SK_?Pc z+?S*hjN=Jhb!%?BT{S$zoj|NCBg$nogoljnZ@-pSfc@>%HoZj~3>`I+S;-OHy)Scf zA?JoCjUPS)qS*s{TXO257)?Mr9CW>o6{aF`-g^D?*J^o`5&;X?dRhg8!AC40%CFCB@zzsMuZ?w zNLhDlYYUv=q6(s6I54Y%z0D>s*GNJ>0tc(XbNR-4dgwX~&p8{Yl;B5GEj9-s52kxo zX+<2xKt)9beaBS7MY( zK;K}>6R&WDwd1|$u<&ra0xs3tv4ZADYO!_}-}F9+*%*P+L^E5vjRy5(3mu}wa@i0K;5Z)kfsvEy)Os62hj} z8$lWw;Q3YmhU-Ttiz*DasU&CFY|-0WTl0^8SqR4+W_=4#4K38m_~RGp0Cfc%38pJ8 zQPI(D@O)!p0lbu$mgc)MfWiQ}?uA+oXM$P?`kvlxn*dErw}xaqZ70OnwSp1| zYWOZXBI_DsR-eUSFjR|+i8;n+Q29&V; z-FHEu@e)+_D07FaDZ&RJr->l;KNqzMeRdF#3YnOh@qmfsWI!?k3?F0d8r6pu`E=U} zxJzYa9q^%{90R5RFi5$$xX__e($Xf5TqiLkPn`?OTX$N@A@Taf3rFxd0Kn+WLZ<>c zTiRWvcOQQHb&x?i-S6Xk>ddWoiR+>F?8Xcw*c&PDHLq=IB5F5o_(5tC%!yJn zGG+k3sH4Mncfz_x6R;~FvIDP@W%|LNe%~8EW`L-)2>LsG3TH(HeU4~9NUl2oab-jC zZOZ+zdwPu$U>uYPR|l_c?_zi0?*)X!)KMOiTSVDrsp&rMF!S3uh}fV zH@Tg*W8n5^bbf$vu#u(*O~HiNYZ^p02KySm4Kjsy3>0tZbw!wTz?aDaJ#I&&Zn|%Q zjY!x?cahB3#`2;8GANbr)|*1(3Q#&WTXjzh=ZC-607sl)#VsM>1s+t}BKsa4QA*+LtVw6y~XadCL78W!^<$TmDdPN&gn z6@ziB;DB47PbG*!Y@*48i100{mfh}WVPKGBg^5t0?A71TLuy3xk4)D%s8)qCOoBxy zihC>iHy28i!b1-?TTC`*1PjH3p2rV@DvKTOkmlcg{X6C~W+lP*%uvIS*n;Hsmf$XA{1!lY5jxRSm#ls;T#ea}}#pzxs+*P|a&i2o~`6>t#Y|NGPsUM%omTJ>_ZGzdF^f7hT; z6CyeyFz@-`$^8H7pRpiDMdU1#ATA@V^(q(6tecBPx;xc1w(@f&nA7w8!*M;WT!Mom z03^^->(Rm(&U{r?-<9U|?CZTdm8Pm9ptKDMA!L7+pN7}f*aAUhZvUxTC5^q-4NmyC z+4FU%UwK}NCsfvj!{s``k3r8*nuCpG^{xDD2?1s_?*|w&pk;-64Y35+@tnnT>Tn#O z3~pOkP$3{)ffZHKPb2k8Pq+ZnAvowzp*gOd=S+V%E7mC;DztvdU(R+JaR$*m2!zvuJdY25gq>oDB_yvhkFvjZqGxv?s;NUFZ*h-hZuIqIeli+#!;_n`T3UK)a9L6An z|M|U>IAVo4LEA{Z)iU~gtrl3Dr###9^4Jv={^8}dRsVih!RZQ$^0NsWf~~C`562Q8 zWn(_f&bGnf?JTFKe-#AY;Mi`dBztz2Cw_6fyX*el(@e)Jdwza9w1dg)73%7(qn`32 zBD?T{AWgjR*+mNYjvdFl8U_Ye%$vT0G#fw81=Y2MW08g6#KZ*TX0zvmBnFOnZ*NaQ z0+hJcA8_m(55Ju>Zgje^ZmvtGpbYjNJO+v=?jOu2;x5RNB2aW>3t5FM6iKf`eIya& zBQNhAvGNB01T#eDV^mc;!5_6;F=j*91z$L;|Cbu*{9xa3=VZ$W4>(*h49f}oP{4*( z)&&cP8#p+@lw9W(UFz%Zz6WF5#rLn-)zs94Sf4eg;bq1!)giEalf=t!RQZ!%bhNu8 zJG&L*c5fgrr_h4DD*cC{2hLis3_2jVC^^bgzAy^j+H$b5&pi_8nXqr8@=MM&l03JR z7WdQa8G}!yi*kAu|CWuudK-C5+q<^Ici(99~*tF!9H&yHox5c zKifkO@B1FymMF@gSg%rzqpk3LXV4uc@;Iqy3F zF@iF%7K$IlrPG7qc5_byFHUAmB8R)h%D}6l>YA$f?;?bZl0LcT9*n3fu z1Xw%-0oFk(y?8{$<~SMZ_!lynT=0O7a;&6A!Vkcu8z24C@% zKqBrC43)=glAT~i4*$KjwubkVz#cP@Oq*@jrJ=<{lhtLO{L&#xrd~ zK6Li|Pf5|>Q2zJ(iNY}f!e|3X>T$>mhff1OgA<^@w3t`o4*;;&d(U~MG+$kwh4Jve zEu&6aLf}sFI=(o#^6a~h9DD>wg}4hmen7~BcvBo$pj~83UacTx=CS1hm?@t3ojsl>D%I6ATbbQPS_ zLcmeUbI6Z}_6hIx+TP!4`3pd%t3cI&aJN-c98kzBZ|D_O(1-RCC{_ds(({hD)BbH0u zVep*^rj&~>d@*oKerJJhrvC4!#reRb8OE=XJI2NuDvhPvzrmmb8sB7SX7bLhx%VZ& z?GHpQ2(A~)cf+v5hbpV6;L101YF>rtZxLDTuU>y3+FVap_Xo&9rzR)y1nADgzAKA- z=iC@2D)V@PMRK=)I|uGDyAFQ~`sbgQ(7y>!^*TVX@LsIRsVRW+@4z5a{0VP_c`@GS zzPma^H3uolCxn-}yGB zzK>by(^NWwXhr3hvjMRxY0h-z1_o{`yaJhyOK6ldK=Ad&<3%z6jw}lllzYl#!JEV; znm2N{Hs|EDpv5A4K}q7rx^Q1ntFIfiiRMfhY<-me8Ub- zQ7m>*mSi-ZYEo}xA4i>wwX4!r>@px^>nC6&2=Q@aK?vf>$z^?eALHORg!7?uOqmc^ z=3s3HQU#1Q{(+UM22a{r2=s>$aPWvEnszT?jgWv1=7cphD3mI^N}t zcp#FKk2;HfO@U3E`RpmJwii;lnf}C`x{EF>&)TXi#4y95R@8u`AE6^;PbJ4Hc~vbq zbModA7<&*SB+|l)nB|pwb|lMKHR6xfik1ws6nXA96;A3l1mB{>(u?uQ^1k3?g!utf zl?gDvQdL&2uN{WNfK%f75 ziTHATS=mkb{SQ$0fbVU5cXt;L;{z+(*PjH2Mn-{63@+m%z-orkz$L$acisdpsanTl zoY9x4zbZ1D%^I9jG+BQowJvKLd@mCCykh5OkooI_es00w8*%2%bCu7{8)=Lp1jr59 zPPv}iEZeDnI+FQze(Q#6ABlq#@yH`V=^s_MShVC3;$m7X5PIUR-`7HNY#Gt65W5g@6Z}CFBOQ8};7G!+UjEE}HlaPlPGMugxHVjt zK06T(1g@7a>BCIVpsnGPV|Vnn;;O`Q2F?zULe>&^#|8|f%S?f2U;IvPyN!uiGnt+-5SRN|vcK-jNH}BA_N#oijNx1QJqB;hS-vlkCeyjrmZ1@m zh4-RspsT3s9EORR)e9WSZZH{npM#wp%^MVeS~}{+?vPo`-0vN4s1;GMEECFuB8gV) z$mjmI$5xte!o$NM#(V=Rf~xZJq2b{+@YMpyZyVy&&`iLBfH&x|K$~{O&CxSehyPt4 zi(HY@!9<&jo-O5XYVmv#PN(tU#C3NEm zp5un}lsmd~BS)}G%uQ|&Ay@?A$g)xXEeXMg)Ou7~74%HAfy{KMK5JEs+@s%O#2OKT z#I1XG2hG#qoQinx2nrRX8fsU-$8CT->T3vP-7ABu&?zwGKe!vN=-$lF-!*1OWWhBe zWgW%Q(05@7)ikP%iS4T48c{vwSPUJA7f@Sq^e{*Xo*JyYbBh+iVzFWfpf`Ho-v?SA0G{Rt(Ks4n+xo%{3a z8^g&ig!Dxd37m^+iif3ecTlux1cS9(P1g1ihQi+7ez^|@T+#Y#q|w6c`WB1pocT~Y z!6i0j@}PxkomH7C{B=7Jb8V&QIRlvwhKu%%m?HX=gV7Snzk zot83iKPrtv2}2NEuDIgb~z2^(;dORvMw2+gjY``H8TeDu&9;4Z_Sb6;g( zCO@kS##O10ZK=J0;erV3I~=dp7qcXtvvpqMO@GPBM}rN<7dQ9)hK7b_7KZt+m+nJp z_J~~#4qA_U_qfn-C-Q8Jm{<({pR4rw?$h2gf?wDO$Ow0SOisoR6>VWL*j&+3Mi*l0 zfzIbaopvU)1nelnV5R6*In(PGT(UwnJ^X#2 z?56&QE5{?HT#2{x>)n^ytPAT8RxbuLN^+oMh;Eq{r__%))k-gH_EDZCQ1tZN2OcaO z+Y|^0u>LIk3;dJij5~xT78gp?dGyw~H}+z9g$f2Xg-~C9>w6V<^>?YOZp{b1HgL8- zuKqp@kTw2_RTe-o{-m7wQKn-xZ87$j+N2V`OJZv`4g|+Kjl^FDbb(vr{Chtq zo7|9va$yTEq-joP>Uk(Z9@kc!c-jRCBVzNR(NE4Rmc@P*s0g&37`d^t!Wa7OaBC?o zG01QI%WJ1})2R){BwFTEmSjjZB9vf-)%p5G7K?+iB{aV;efa+dfW8CnHz+-zYYIK{ z{l3B<69)$e*(~Sf2SP!AkD)_{ANVt{@%(`WZ}aLiGe4N>kB-JCB>aGSJg>Mo-2E#2 z%?#&`PpQ##Imf@JI>BfPrM5o|ZSfsC^m&alj{uO5@7lPzRZE?7@v8|~2W2S)&42!J z{39-?4gPu18sKn3&rVK$1^xR4aXPwYW`Cf#$M@_LsGy4*qyHJ!8uqIK~E8=0d02HHE}GG=y+LR@Mjm8bxx`^3l< zY$Z?YI|KRsN$w)L3bP)9ftbVo5%b2^`-x#^$Me5nZ_64>1*nsDquMf9wQkaQ?u^!4qw7zO4Gmx90v4+-4u?T|)A%4cInb3H)Dc*e-u>tGu zY*@VWC~$GDI>SJWBTh~aBm?H=$A_QeIP%F!M3%$P37Q`Qm(9HG>xZfu@{hhSS;OE7 zd=3I}n}jA?(NKG(vM*5bARyiR2;onNW|oWrPmy%iUGFhzUpBM9pZ(lL-DT@6z{M#TX zzz+Q+jl))hRe%tpKl=Q#QDE$?rQLR@>i}No0oHw-BKvMd2{AS)DGA?ORka_a z5A=KQ*uM4q#oHHUcE zx(s{8<)+^XG92m=Cr@J3>KTqzW1SE(dBY+84#@SS$i9lyg4|_)c;(!wshrT?DbzK zd)&u>^_;KuG-&wP_%e!8qb#S19NyU05@26{2CE>nSs;^z0f+PFpMs1| zUN_FMT7BHS-AIW+zoI4(?Y>jRL&3}8nt$l)diRyol)O`2*r8mz%JHZ;5uq4S+hJVA zom)w^HP{`}a2dm2dg(!a+euHHSjpSi_fFbp#eIPPEf`^8LL1@Jo)?dD5jlW$Y@l_BCN#{W9O)T3H#3 zgN3RjYz~JmmvCRlK!0EL;sqxfwL#nw)5y7?-J1(^+|v3nTAMF0BeSM|)@Pk2W>1R% zh@lJ0>-KrTfJ2khaNv0zum8Hn9VafUdFiMxCI>-i)Vy2I(~!{}2TMKhysY-1 zM_WmUoIK%=NUhb-MAMF!DcVn0mU5HekXK5CFv~}`qP0|aOv%r^-Gm)Ox&75Cz-uY# zN%5I&Ex$sNh2uE0f1NiE+8av-v<#mx{WYLhu(-|gX5UQZv~qR&jrIl=@)XO-zM@Qssw;s(+YG}!%yHbm!?6SPNGF%ax~H33`_n^$Ib^&Vs=wEgKBEScHG#f&buT_ z5mgG0qIB;^_7jMOwbIQMZ3u?7zWv8Y^6$0ob;V*~#V|&p#nV#k_mSCxJT+uSFF0Gu zYZXysQJf-}MPytqc$`7FNeJ@Mj##Jna^%r;%B%BaPHBgWbxR569PjC4A2GJoxoow@ z&AI(q3}tMm&igc^$e~Yzh~H@wAlN`MiRw7-w-)=A2p-5c+~>CXx%6vkB2_iqF`&Tx z&=fP}$x*CMY@iOJiicpc@+_Z_@!h>!bBhKaho+S&HRe$7wdF+iwXop2PxL=2@8s+n z9}8r3nTKSQQAnk1X7g%LW&Wb^kkfFmC#9ZL)J2)e$;K+DQ%f>@vqorPrS}5t?1Q|$ z0xBeax$3JVFL5hm1LSQurVVmSA{U~mj1Qy#ekBHVw*T3dF)J+IC|hHtANu>ZC#*+_ zj%1F~_?oH(0mEkKjbwm~Soj?%9WAB~NOL^-m_Q_KSxUr<X`{67N6tWvgz&(PL( zR1sglBy-M%)bvF7hVTAl@FBx%pD~{2H$E=jWFN$ zPSyPFRrh_cuu@jz-A6G=#Ft*rQ^(`G>EJzARWY%E{gTdb@l3se#>5=nHZrXT%F$)6 z`WXe|;Z=vax9%2BJgoQAy`^}oE1!eiN8jJhjgrR@o6Votfw@*lG>)BRX+M2|<;7_w z52stk@?8twnLZEy&@MFuddj>c7C(h^2(UcDZnjDT<3k@^d|MghaH_KuE>-MdECMW z%cvSMVl^hh>%}zrmF}E6jBFw`+tpXj%=IY=7Pp8V+#ywZk0J=(4rRg8A+%Mdw7mz% z-|}L+n#gJhzb9V5ub{B&IUn}&RLt9UKh+qmEpca$oQ&5+^55rBH5BYuD(6h>)m>~h z^8BWPIudHOmdwt+7zn1(CGsb!zN{MAaPjhGnLD}$W(?!94#s-DZ8az7Vwh2s!=$(M z9YecbMIvfhS)Ph+zT~Z2GG@fq{cR2Pt=s6$!0EH$(+9){k}7SD+^GkAn?eutIypn1 zV7K2E-T#uFQBW+b?_oQke*yPGW>3s-!y zsIU9oMDG3lVNVcr^|NXt!5G-;zJJKLW(n3`=GUcVOaK*UWp&DK=FTrJE{4HMSB&2< zkm~Dd84Nw^#_17+oLt>IIi+mG7ReG-9HGMp9|55NC!q9IR7r7smU(7Z@SD5F>=D+*$ zYV3s8nzEd!W1#Brxcog(d5vew0|WjXZVb4D<#MGOlj-=+zKvJoT%eAwuLapb#Y0*) zIyfI9g^YTTimECILWI|weL+A!K5%#X6UA4V;Y|k#Q>l2u+|qLw2FpYdHcr9|^u)&Su=Znk?9d_0W|7JYXiE<;E`05b_ zf5}=}r9Wjm|JIj$MQ>UA6YwCCRBA2W*3-dN{6WWY7m5V;i+;Vl8sKLog%UoD5B&pQp;*DwTYO;(y=OhJAMCf z`iygPk#iy6!Ptr-RycBfGHL$VllK?@bPiu{6+t(! z%`9vo@AG|el6o-{Hb*%9O7vH1gz*mqog4rSuO1~Z=c}GfB_k60S`E$SXgk65{&ZQs zom%0RW}zn+IvmR~_7VLui;b2xfqDR|$i;@P%5ODz&~%wjE%bDH!_0szj3Utr z!i4-JOcfP7kG&{!XB%PrfXEX^KVDXn@;wMCyY)LhkPjgv)~C2EQ!2xziiFZ&&tLcZ z^h(#IQi+Hz&BMRn=NkTA0=bSgbfn#aKYirT1TFbIpw%u=do$kdk*bMC8orFMi%|v( zYxoqB_FhvZ!Fnj}%)I&>x!a>N6ozedR4P7e(#=8esc2-HHD&iP@@9)A)YQ%Tw5kq) zBL!5xr2)-;@BHZGuYkKN2Ie;I-XX3$GVOL7wp7JCd9_3ha%-x zE?;@^;=v+<=10}bjPZk_M4qrFlS4qj5jbtV{gXL+y5#>7=-qVu_Yws9I<)hfu2p6~$Q*`VO=ny^>p6P@y(mbPdjM2IaMedQvSF^%TOc(SLH zjZBOOywF=S?nEx~VsC1d&O_B|zZj&Rr zL%O5Uz(Gl6)WadiQd;!fXm6FB6Tw(UGNdxGM&zLacW3p^Zkgb4``eRzszX*}ko0mIQPt699Q~xwo zV}E~wLojPAbM2;~9g>VJ1U=jf-s-8;+kSMHtE>UhiM zXT5?66BRW^&5y?IKwaPK@Fz%v(Lu5?PPhsBiCtTD~>QWZB+qd<{)=BPXnpP1=>{ncTcb?&b zgK`wZD?;tjFIaNQUvBU6_}-la%^r$}VxIc%jIN~hJpIkt&tRzk?JfUYhJW#GDh(>v zlLeb7Zq+Bx2imZFNa86a&Xl+W8&Q~kiHg=fe?F)-eggyQcQAmsKt#b2p7{ejnG2|I zO`l^SgbKCN&-RqRTW)1b`o<@#?MpwN{uAuRA}9ebu9~=#vZt--FU)T#-tWaBs4Enr z*Mdp}ID}uYDUC$tOE6?E z_$`N6H2N5P7(jI&)nWXsK6$_HRWX**jXG~frW`F#Gb$o8e=vVG>hE4p`_cM^H0~RX z=k)D}$xjpLR}bUf2mW}hY-5AITu6kSez|v_Y@-lNBh7AHhtGa6MTFBUM?^LigfT>7ebrwqcv1=6f;X`!SEu9}?7;-1}qF#J!B^6nQqg zJatYdZw+MF^kz_8UdfyS6NHgbj+85!mv=Mo3G3EeE4)qmhg?bFoF< zbq-n$)Aw^QV(-y6{zb_yJeu_0T zrKkOboBo8qigISuB+ra$BS#P~gx>)nj!tk9VlHvlqbJ2sD$$Pay7r{RUfdxWB^My) z3l7OecnaC4kghseHP!iCkgVbdx}yqO_T1E=ilLNz1A_{$S*{Zk7TvSat|M%Gx}zaexKT=jucl{%~6 zd(8u-4T#|{$b9&6`aF!VVBG{MiU#K90U*jbK0by$jK{#L41LdhGC|rkt7wqUMk*dd zJ^1*_-8=njY8_b~VVaZyujEf7iuVLGYo7cfjGNkabF0emuoxGsbSJi?yroZ1*JoYx zTte2k0zot%a;a@hcXjtwtYL2xkLr@#?Vi1kkkF6^KR(PEmUg2EV72{O^v7n=j4#qU z<0f(^mV`cF9sT_rY6sUk(XTlV5^|U`6!F|}b^QSYH^RrPF|3nv_4dbx#(Zni6nhdC zYy6SlH9pr@n?DXWCk?a~yzFx3W9}1@c8WEo5MK0$cUXxsCgY3lTrv5>FCSKB-!XW6 zH7xgD`kh00l0B2ax59y3FT+p?->aD3Y3slL*jW0+e~RlVb>6AFFIUY%i@!*blMfRr zS{eu#GP7ZQ|ELoy{?szax4K5ULW6xE5POz@Mw;hz6QTK+Az$IesrxMaoP^#EO+EZ$Nk6#F@G|ArR{WY(XH>f7E2P4dxxM!v@7D_0iU{k@@U0Gx7CGp+@ zwC|~6)=c+65R}1O4(S3l6ut7`n>Ud5U~FJOkA`i5R;rK{NcB{5<6VA3Zd;}tBv|9! z5N=+n{0-o|1PK(_R#5&&U;zeMIVIjLRlq~J9smiJhUN4qyqJ3Ka%p5jPve?M@2Azlj@no+8@$HxF64jVU+OUlg5gx>}MF^HzMb^g@$ zT(dj3%?sy+4p=0<-{(7$81^Vt~j5HAMHMBt{=hp8m&(jj9*=pS_$(lO17gm28wgyYvj)dACf~N#rAh6=W&@nKiq@|fySX>rh zv?_T9S4G<07R-y``|21lAHbR!!GhQ-5VF z5-NgWh35`W-KhVQ9)ha{dvM=CuPTvonUnJ_pcngI4WvsaT|F z@N?FB|6dR`&~E#d2?9+WKMM6w3EA|YwwQu^N3cE-;Pu)dV9VTtPytubK2ec>1Zth- zE5D0RoeL%I5SjmTr2%s*G`91)xN%TWjNRQ+e;-j0nrE6g9-%gRDD5~%|1l0nDPn{# zR3)rEmj%i;%!Pr%4L3EUasp}vwPU4e4f_%RjhpBufu$4Nva{gUQ&Nd$tO55Q=wpEQ zQ&>_0NojRrSYfMimywH)evU!dAMPFC7-THFu=s>T90;hMK6_^S;g#G8DZ#F-zDmH= z|5rW1XhHOluM#7f=nLI*)}kEJ9ZJV ztT)A1wjgpfZ<9l>2Y~gc`vpD~Y$J^7<`;m`!nh1sTma%cOp>;|^T5yuS$!W}=0*CU z=0J0BfEhKc%`=#UBOQx_Llw4yOPOXBlOQF{m@=`we;<+*@ZlTkYHGN=*U>egug{kA zeTt)ImGc2KIPP-{5v)9UY|j%-STqFy7~JbR2MKsm;dr^ihyQ`?d_i%}l^~EBrgU`J@NJR)fLBuHBvkQmrT!iW}2m)$AC~C}}sn z;X%kx6qAtnt*i|$)Fk0&si~}(bwZ;H9&Vwy zT#a*_jCp1F;07BTj`d?uKb~VX`hNa7$nFTRg@uK%KX*s?(EYFRKSX%ld)M8J$eD>o z=R3lxj64VV?SV|ZG$>6($Jkgcg|<#p_qu@r=r2KxGC;S_M|u(H``~8#oCz2|ub(Gi zWVu)<4tjcwn9E`pfP&MqGJz-n&T>#af9wnjf?!#YM}R#+S#l2CR^X22xdCv)>fzyS zNld0Tyco2!s7PKcBZ)3gosWWj^6X7VKg&KJgu@;{3g~>Gp}4Q`p(Vt`K1@wpY@otkXo(#@-Uv4AuY>Betvz z_Ni~*y}JUXb<@nH1aoIl#Mh+&eht2m&NILYkq0dzXbMWe`YI~A2dRz1bov7`P_==a z!IXb|$;Gm(MV|Z|hA6Ak6Ki5ggU>Yx9V;#@ob{S{sKOI}_-S;M?Ka*PJg2yqu2xXA zmJ^uz)=AWw+Z!$7l-}GBd`~7z+hXdx4In_2#;t*Sg`w+oYc{`KWwiD+5p085l~2p> zf$IUZe4jHf%K40L??(#GZ}0yux^adW=@mKHak_TO_Q8vW22i5aBbJRbeMyDVHVB%j zm5=hD%LBXgNnOvgtj=N;+V;%4@*~ZC))WpQr_SD?+u8S%EzHJ?kn1jS8HH^G0JIn$ z8x0J2E5M)fxx>|R#ITTr`4 zDQaK8qmf2|`MYfFF|dI_aEyl6^SGzQi*PZr``r`vg;G;svdRB=I9SSgM7vBL5}Elu zDU$DeQ3M05t!L{WFd!f=3xYFB<@+h~GEFq^h4nVL@;-)djf9qc~<%l7t{S5G@n*H$Y1(W=ykS7=I!}Quf38iH$@LO#x0Vkb*zSV6v##Fvf@V z?(Vu_K=h4UY9+$LCYdi%&&cQ(Bs?6Y@nZB%O@IG*eeo1Y`^!yr%$K>=Mw&jb`{i*d z)iBz?;{!iRZpXxmHa2EE16NlmHB00hsT2BqSgC~jR2%8Lmp*+o6UnT+-p2sWXym_R_K4U z0F|Hz?@gVe^Nz-We~3VF%CnQ$hc==&+N0&hGzm&NYXpL4!0FNiw-!8A5QnBir>vU< z+LehH=U{O?2M3L>?-AHNaCT6pb%&38%1cUm|0iNUg+O+v3yD|s#t8&{-iH`!!6~Su z;OI)r%oK?31b0s47tvrQ3IuGL))($WHv@{`7&XXi1VE0m{utDYa;$OR-P1K_>}Ps^ zz{qcCaPawy7m$p>zkXbt;Y1q|iYON1&Qj4GZB0KAxu~ zwD`|f1HE8aiW3`GApN;>ULb~nW+RxFwAZ>u?t2#zXxl+@`4N=*_(_xlpUn$!d$qKf z>L!7`32as7u54$};2?#v<>6e*#P&WuxEUT6iHr&`mVka52@N65mVzV|@JRATz68=e zSfzaBvH`d=?NE=endSWrLkGd&at}l*=%x<=f;L~k-gcJaK;YaE=$ZaOQaQX=zy~eP z&tDF&kH~yn?=p;U@4jrGB^Bw{H+TdeT0}$y2G8Kkg1BQ0xvQJo%a1pHGoRvIP%#2m zgb`?5O-w$4#sOZ6_){Ju=e`z=+Xhq7I|#p<8;Mkh4L{!E)-CtQ4$~BWrN{^FOR&&D z_jyS>6O_shJ57DtV0Poq_OS*+29*Eq&E{y)vV5MhGDyd;hbInwrtHWqu*0ObKb3x3 z$`Q&b?UFrkFVL8|Qalj9bjcPHK=862xJ7f%!%;5ezBUXM6xh&d1DIEtQ#i&eBiG#D zj;A4T$o~ZG6TU_S6*@2{;0Ycm`%iH29$afMFzXWg`YUEHX}R$XTqS5DvU76on3?fk zy&nxB!+pO;|;#YbgK+%M1|v}GvN8{0BOdPo`js7E3tLP0&usr!mQ%0{lV~C zF$LE>`V(9Cx39a3YTHESpp49IPXNy}d$=;K81g=~jLw;8??w8*Ixu&@>r{Cp_+L)D zXk1+Wo8Nz>8_QY~yoei>zrOJ$9~%m%c*;x`=9ZR;j7z)Y?m zo(0jd@NLXiioIoYFbuXdV@k(?ybpvt2LFMcW@l$ZLqlDjRCqpfp zONP7?b>I*hb#K6is>j3A_)=P_@rgyR{%0>La!J)M;kgru;Q^oWO@!F_uZEX~)QjYw zm>qa5fj+nYNceS6F{OFI8I&m&g7nAjG%w!hZyPU^YwO|G^A6b_`EIcrgX+zHe{Kr=An(;ls1ap+$X9%?XFV*olIxDT z>1?5J(WAfN441;sBR_7Y_o7*+nDfH6bPmSL|<*Vbi0?JX?Y++17&1L8A>E6+=7xp1zS#_SN%Ad+-Wv>3E^Zb!?S(0o-IacYm7ovrUNoi|EpdX5CiQwh}CMMD|a}W^twJkk4ZI7&_sDEiT0rRa8{Kp_ZHL0EZQt zZUypU6)0513M#HHuBIHm^rH2fj$>2o{_nJQ*p6@}nwqaP4xP!Dbc*hLJFs4Ti6|7` z+cz~3=n%>fu=pcx;dDAVBRw5BuUyy-UE=4U78v+}TJDPNILLwFE;XQHO##g~cQ=U@ zXDACGVD&T;m?ELt*(aqVDFh-|iv_QHA~^M3G->$swJL?LC0ukX=QE-~UIc1S1%tKw zT0Bi~CQml?)>2D8(2pIGm--)EZ+cv+fGP|O`v9Ww1(Z%DXVvJ|_6S5T%=DDY^FM1LGHT43#;z+b&HTP@#1)r+0ru0 zs=DE0dOFpt;BFPm$~zA>$|&2Pijq{uWIz9OPdR-jcHv;hIcZM?)5m$%B! zeY<-tH^k`1sg2`F!5$uhkFH4^AqT?q+{RvV94e`vfdLwXh-S!SvUBkCv3%IPbIW;E zf^XuXR=`||-SA@2N)}nYpT6qFz0txF(b5hQ?TeV_y6FD+(4eGxF<|4ORI$$LrBf)~E#|=iz?-Y5tXT6kWsG8AQSG;(*=*7*l2POXMJ`!1q zj<2T9r?}cu)j1w667~^4gbDn2>V&>XWLCwS&9m#qVB-fVSfTB_mlAg2X3c@xwIEsb z-+~eJIshJI0MVKEOmG1Bq=MO3b=v%|Yao)Zfiegbz3)ax!8pSS9wjt2_{@#^kp=T% zuUROgXZIn9wICUumVwu=tG123q9GT%9K3Z#Y5~7(g9;ar8SCZ%G2~jSokH;9uWP7U zSmI9O*Gw;^Cr>#K8zAT#Yu+%Hc@jv)HTung_fh*AMM-FTP0eqR1W(j@n1RPs_q-0&rJxZ2fn+d{+SP^v zp5|ndz|CbAv~cJs5LO_R;J{|4q+v)`y!`PdN7JDBY(d>^c`)5 zP8$qkA5ZbX#|NFpO7FLz7F6AdUNf))A(Mqc;Rsba%=w|l($Uq0$Tih%EFWN@paH79 z^}9y|;E4G6cvvIw0AzusIlQk$|0aA~Bmm-~c$Rcij7{ zr`($!I5=u4d>UG+s*H=xH8rDx&DM8N;0tD#7z7%F8=wRO`vQx~kmU)P8+0}eKK~#| z1~dSUdXxaSfR#%lD8WHz`~yv&M(?o>dJ&#IJ5789nWy0LD7EwI6R4#71_w_vH~}pe zszgaKu`Lj59IT(jgM1FOt0g7y;j8)tQFY1p>TaI3oqws@KL9)eFc(zj+o-J?B#TWC zj!jJbg<=xATP6X9&TF6^lDc7S@X_l-uKAba_HDapf^tcT>Dniz>im50`4m7!wlKVe zq?Z&hHsc>eKO3aM8CzSIRGdJ-lL)?ifRPX7EV>Jx`u6Bb%{s$nq=+%0d+qZBBkQN` z`XiF!;=v%BPq{RExtkj>nBhR%m7KJ+tFT5TW@^S4!Sp8tG}(I~ceo1iWpnIvpcF=R zqMV$Zl9bzkk;3(dOE&|KHv0*;cIRtH49x;>0Lvf77NED~Is^<|=myRpK&6g{Gs1T2 zf-oIYE_I-`JxOE&D;S}y?K#CR{n^YtIChy=##_I!i7aif27&=SJdV%>ShrY4?diZ0 z_7V9NaGkhfYn#RV6uP(RkjJnewt>{coDNVj&{7-e>XsB0S%Gl@>}?(%3t+kQ6Ba`} zuj&Wp7m3&XQ?(}`gU&Mtx&zoFC34V5IYmtl5Zkf0-LGC*hi4N1pr4@f);|dZ%LgF3 zlJjfah&-x6gbxR{W~j>lZbCx)r@Io%>n7a*-2H^D_`z@zi2hw{PV!QelmLML3FYN4 zT4#lBF916mWVMRHf4)OB}DUg?CW1a=XB%G z_sg(lK#j|zaCIreWwIj>ppYsb>;?yeNE`qt=tzfOSXdZh&O|NU zf`gmaXRO*o|3T}Kd}t$1Zos%s3CQzc--a3pg7YU}qVUS74euiBV*b&SKD%R;p#^PH zp#3}oHdCE*ity_ItHI+A;5E8B0lX~L$^f*gRQU$TOaX1+BL)Wt`}@lxqTf_IYlTP8 z-zN_5@w3YP?XKlB?FHOiTp9hk0or?6(st3~$BwWB8)kxRe0+SM5P>}kTE8-GR@6u# zC@2VBqdq@iGlG@u&JmS7EN@_;4UIgkZve;u3g8X?&i94M0y<&XfLfJi25t!Z$_V@P>4Ddi?{21Gf9+;xMhH+L;4}iOG zzyQs))i?7ih=r5^`i2pf+<>A6wM4NIp!F6<<>chxg|n}HiWwd@4K&B&p!k&Up9K#C zPuwO9?G6=t0zQz)r(s^sCS@(?4gOrqHGRj>W30a))%;l~jK1Vm4!Q)N7d~10-um%C z3yY&OKIk7;W`bVn=F&N46TQVFD-Uz`W?ph$l?gsZNvN3Te#h@qs(?JHMrLM7C@8g~50y>HFwE!d@47iT zJxTrzehBU0CcE-W0Zd7cKSM>_5y`*d&x2ZK7N5WYnU6P&2XMQfZ&WaN2!hA=O;^eZM-4{6{}-g!Oo1a1 z=PPi7<1H9(jDGip6^*Lt>J`aQNdo4eez+!R#abe@n|^{Wfz>JfUBN$oTa7ap`M(>! zBwKQ7Gv?C>BJzC?Vb)li`8?10TCx67H4#m|srj=pqW^1Pskt;`D^PX<3o|pEb0q}W zt>F_M;FC8%z25lB%rv`bd9D#W7QqmM1xsaEzPI6LpjSB8h1Fl*gZo~0b=3~x0G|y& z`V8t5P3kU#=*ZNMYM_~dn6JqQrn?Xr8!JN7tNwJD2TKK!iczHIiv8Vhs?i_K*-Ep) zwi4AXF>T#!gCxb$*!QsVz$n5W4ztboR}M|)`HfwNwUM$19X$|BN;yj?jOw#DVm0dV z&U)YQMaibU(6Xbkli-tRzGW-*+^i%uBcqsnALeYJ(#9HoE-EcGy90xQ>DFH*1t3z$ zP`$MWyH-WhSI)lVTAxRU{YpKDGuEu7ZIgrO=c=hoC~2Y()8$vG2vQ^wKn9D&8thUagT$+Fr2E_I-)D7ufc+pwvd~0?)03HEP zCxEY*j)Ld^2-J6Q?56y+DZW>r0X(U>R|aeKV2yWUH1Z$RB9I!3A!t9khx-J;7^ERE zFs=WXvB?qzpa`Ua&PVUp#mTM9^6@<`D`P-zs^r)X^1m~iOoJ!R9=v9*fsFC`SKu5- zs@3qMs8=N5E&_OpzDLVf$8M0^=^h7vGPwgTv@n_nQ5O^*Wfz)$M;tg-HIT>p3pNJ~ za^%eq=a7@hPHaYxpnWdao9+{&lXyec;ni8!#R&2X;QVfg;s_(slr%b&gj`(a54=WpLi^?f_y5&t{kHIc6r)G-+QS!^jp@|ACM-{Z+qiQiX|v$ zXJ7D?gNJC-jHHcR-iT3utpLA5cdmuahE&NZm@w#uLaUJHk?F|66q3{{xpwXJ?Cibb zq9SAJlBPnY=ME*9K<=_W^xTDjP)|sY7QZ{;V=TU35|>mbLRI&$bB+X%R(s{ZVHTE= z-(QQSYvI;_GY>qJHmTLMLPA&%cH}+Ob)oJ1;OWEqe?g`w#{}8}PzuSykP_A^xT;Pz zU7?m!HJ`E*{?)aw$Dc55V^(_oXaG`(;8|oiGH(-mzvzZJ>r&8SWf>RNw;r{@6C!hh zEt1d|JAKEXhlOG8nDgOi_b4G|%L|uZLR(8BLE+ave^1%kDw&y&|1QLkH3&#aK2T?z zs$(h<7FRT5OrszG0k3h`>Re@J+^;i(UK~-L4ssHuMPH}KkEEK00U4VjvdokyS z$`k+_g@xeR01X7N+G_pYG6<|iJS6RiUQ5wW&n zjPf`;<@iBRfl-b*>_}qWSopR$yG{%hl9YMTs#-^QC{yCdZjK<5OdhdW+8kS=)3M$Z z1Z~xd&V3dwEh~KSt%T;2u|{Sear94AIh4JeZ27Zpl&<>y2*)MFJDd?dGkKMt9Z}d<&-a7 ziKy*(%EB|S`G;wKZ%4@A%&KGXvluNeG(n=4Im2` zc{V)TVloP@IfQ{$vlZ?VFb2pTYu^Sf>-cSU$HBZwN<=O)J^-v)Kcd$H+;4UO&Xz)R zS!;x#zWTPt4YWG6NrNt`P(R}$@A~QDW|=p|Zx8Y?T%sSOyVjcWCA(Ri&j3H7a*|5@ z>)i=E>s4$br?#(cT|)^2gQ^wJR0Tyi1~E89fyHB~uy{-+lV2t#eiy%s!LtbBk#vp? zS_kH$0aIKq#vx(|xt+nGly|=V=EIVmR1;YrEkPimoT7mUWrq8|gG(!Z0vFJPcTyx_ zx(ap_2cki2-@k&dB8jizo%P9LufDBh=TE^x3 zL0VcQ@eCA*7V%JAllWbt8!FsN&1>!yX-n`o0}45&%<-nn`A*@~rsdc)90NcMKp1#c zE;P-S&%pMQee-t@3?0EF=@xW@Fl_62^Ct4a$ty>`CVNC{TOuPr%~PIKnuwU;FnBtQ z$kviVxIBfRSuVq0eWSx&8;gvYPY=+!nC09hPZ9C%W#wCgM^h?W4Q5%D^^emZ8AABN z+?iiSs#4+akNo`0HRIg={zTRF?k}JUplE_VR2j@3&6&Gl`dPmJxHvus9h9vFmz<1{zmXw&@ep`d_aNPv~ zKG6zL6VE}w03^*qdyKlvfseh2bD80Ye9oXH46cBNm)cN!1iBV5Q8hL;_5=XnPBlOr8TLVD&(1c{f=2EQFR{uG9EceLuB>TlmvIqdd%J@`5K{#}^sLi4e+ zR}u`R^LMC5BXNVU+`wbN?fS*BK-l&BdwRA2%26^QxEVHDAdTe1-DLzH*+fXTu!@33={YsMxzlyC4XAME6rlJeb)KMbmp9L zDreE+QpI3N>0t4dO%Fe}v2o{di|AdK6QlXtyQkK(-?Y+-^MpJL{c`JKh)RF2^=PWL zO`F-00ljNoHq>r#Zeu#W_@iO6QmdIvzv`!b%;;#rS3#!k6YV4$H;OWRiV+zd0BUYa zped0{DWGV|3b-)W?$$xxfV}m<<+Q$n(N;1U0yt?OwqFGT3k-FI_$$YE`t0{=V7l|! zuX6kb_~k&&;RS=e-kzJx<1);|0I*!fgyBA+=tyN{!=Xl9F*Ma4&{lq+%yB{!ZF;|~ zO!A9qDA*@yymI&Q^bGwKYd;D_hi_r;ixHKiNiZm0KV!1>gYP?$I&5lc3hX8U;VRxN zzYY}qY=8Zc-SPIlg1o$CpKrh~hWZenNA?iEUN$xiO#gx|7?i);@t)BnOI3w7G1&)6 zdS5bN4lk4$GX6$2j0?v}Cy7+wxpV$2Mfh#JvHdr{M!X7BbH@`QCY&x3&a7?L|8tb* z*8GbZ7P-HtfIawT>MiU*RqJP6(gQ4ug&VG7G^RE5>aRFk!LniwL2oZ6`>`~fILpYWK4u05Q!`tTa|r|*ENm_61ECwxTRL4dsp&k&l80n{NV zs*QVDFaNC{Yl2Q4OjC~)+=S=%U+6xxZleHB!LI_SXCWc+NNY@YCJXSvUn!P$Vp387 z{1}13!W0VVxIMb82~;THxB`UXlyHX3{|tuYF8vwRr#}<(m$uRF8xnP(*Z{++FDWj~ zxOa)F{3Wy_5J(~M{&A-prmjxEmtb&5Ba>0k0(Kp+VW<}OP$Kw(n^0Oo zmIGwP0MPj#OO^MgF_q<{MT%0JYs z?Y~P(yWromzdN{Jls3EORz`md{uap3EzlJ~j}3$l3*BN@dh5`6lxhd)YegUv?0_d= z5j1=VK7gDnx32RX6YqOu$|tQ(`fo08XJw@b@h2OX&9fn+5mq)@h$9YAFvHlZey0^W zIuZ$}W5i@wOc%SbU%Kwa#@?8_!|nr?o51Qrefu)KDyr@MU_!%iMD}wyEl<_0U&u#T zXJGmbX%J}jbgX^XAK`%WBj~7UTXD^I^aUX2~3UDsUMuIVGeR&oF*3BK67Mi zCSLaAVTXw1FwV!u{b%m5l|I3m@mAwK5<(w`tt`Fw}i{ac$uM}{l5?6q}%$p z#$Ah6OAmi8CdJ0WnU)D2A*h`S1eXM5`vrLJ8vHXYJ-w*c#2Xl68Q^gYmlre}Ma;9GzkI2CxUZ|b1)fK! z6QB64N*5t;Hig;?u>TeSt4%F$06T&;&eBx85o%zGibi!8ntJ=6(7dB?#;v9DhO)Qr zSQRHBK|#q2jndAU#>(;!JkFNo+=N&Q$O5Mfhs|@MInc}7+gs#venZePBE+9!=8nTqb_ncCPBwQWKJHl?m(?1 zB1|RkuT6zvejlA+fBn4r-Um)g{rCGf$lZTVee_F`X40qNHh7!NsX|48A7SO${H>6^ z;~P`B{rmC!Q@ia1rdb0mt#97fV}I@Zg9~B4Gy@<4BcRNn!8zD{^;$Ae8tQ##?aa-c zIW%F5(~Rdt-Syq;yUlAL<;Z>+`e31D6!cu2Eay)C1LG(77GxMqEUg2f@DTvg&{v3k%Rm}iB3Rnn2m%YyW&{ZJv%f4`(M`pulVYX__gBb~5NB3}I7VFEGoqFdAU#2aro93ilC( zBuqTE!vl{{$*YHj{fIMIp{($sd3-PHf^Z0DqdsD=9n$jq-ur7VyzQIrgUjy=Bt(U5 z=o7;@sh$xG{X9*01Q`f)=m)W|sBk@>=lG6br$5~bD zD^nSXHb>Og6lxEtHW1I1&)~1MCqGqdy#lUFV7m+J9me|NX$m9RwV#zG3m}dL7HH}0 zsumlM0d&nbOA$*RjzS&v+7GNZ7KTgMz~z!a1U`<(1V#2DJD;8qOsq@8pX_o zkevCo6^=(DRD7JnKZE5hC8sn+m7knr#U3uQ!rH1}s@ zf%KMvFD8=2O3e*GGs}nQrNmA+?;qrSU9D;rIr>?7 z$#wRz&=~=$)#MpIBO)B?_pIa0sBP-<<;n5JDjUG-aTm_OJ)9Ju_0&2&-9*O#sKZ+(ar$vNpO^*;E3qN$S06Qc;6S;15#f683`~Tk zw%=BbIWu?G_lFG@hYA>oxl`eA%%+5s5fb7JjozFbWQ_-=?qP^FfdWCO)$kw9=KWKZ zf()DRLxN$hoZ+7+Bu{aO2!)^D`R<#ryCq}$Bpc~S<9)7^yTI+EbCY+f>8lh|lDqBNX0a!UUh|oYh)wA0J#P0?cdS0o@rJmK zScjVb)#8Yot9f7buGvZQK^7aotii}a0Gd0OnLGyQsf^Prv~K9#nX0bcm=M%P)Ci&l zo3HuYIu3*}8>pM5acWC_$*nttQd|TlRn|`a+Ru~Aen|D1FIbtrmarz z2&Y8j=CM$L>UwR;CwCa-)Mnc&9lDOQX`e9+JD5*bSH_&Ane_Vf6`cehY($`=Ncmx7+@_8y9#r*3x_*uVyNDlR%Sr8<<+N&CW z(WcN4VPu%WFDhN%3GFIsDjc#qbvg9WP|TXwmg!aeMp1)%t#f1ZR_EjRoJebh)5Aah zn7FeDa?NcDHVM%YiI%A`NpmAu+;S zH-~(Gz}IMI!I@djQxG9&8|XSNs<;O<#T{YaXxmlN){lx?4&OX;A!z=6WY|#>l10D} z=-yExcf&e@-Il}b_jxo8h}^*!CZGO^H45=k@uTh#GY}kU1|yV+7&e6lOR{9Ki7LM`|QthzUZQ*gNc~rq~)v= zn7k)8nua%LLrr2yf9t;*7o0qwD2`yYnLRT#q?RO=pQ_`LL>g^D!Mvf!MAM~77OsO7 zhykhA&QzPgkHPPXTHN$C+pQuBj_z`*pF8fq=s-Vu;YX<0cZwK8UQH{*NJkEGr-?=a zNYCD~(HY;hGB?kEaY2f*7Fhj&==4Yb#PZQFaQyry=cr(`bjtSE^gauM%jyuYNfgdSaDo@hRbyn{*%M-SweZI77&AsUU8K0g2g&WE=JS#ZQtu%U@OT0e9k|8I8U{G(M1+hJR?@$FfMZNTwWHaTVO0HkhJ>$I z&G@R|urvE>&pu!T-s9BU*!c&jm090B&IDlIVeL8w!|r!gDdj!Bb^Vx)L{3KT-5@ZVKBm$x1SH%-YPVo~!S!iYcz?zd(kyRlBT?0$cS4L~^Xd7PvnN(h>m7aT zdIhNIGeVD!F;lb|Y#!!nq#7z9{f%bb8ax*uT^NNElMcGovM9B$ zI^Imzk@#0bgYa3E;hq-#VM3Wl(h%M+z{@&y(kJMu`u-F8(-eu3rin!6NJNlJg*K7L z4~sY8;0*sdLY0oqXIW(CX1)-4Or4g_HcumuvO`BSfKD49ID(`29$j@B+bON8J{p$= z6)5O(pU?Rf>6pFyjn8saF`5=EJ@Be^>GI_oKF#1W)_$$p2o7PE7A!1X&&!y+#m}K# zN}q%@OK8T%-y#3pex0%)DPNwISJTYTjH95JH<5_HI(kYWjW1G6klrluN0PW4}+d4nZj%$#@2s&naMLll?Fo+fj9 z40_F))NW7Y_ix2e$lK~I&#fJ4mRIoPyd*G$#dVmooip9$2_=G1>SILnZ_B5J<1q&Pk^mH1)jjrASCyv32P7)F zhm&7XJ`xS33X43L%u^(x8t^Nh;i=(YCixKpZTP+C^-mpSy|rpjdGbBzY6%E-B%i6) zbAVwwj9}Sr{Zl*tz=R-mY+$VX;d%Sxk($n;PP(El480CN_w#f(MLE9Y-OVNEFFY^a zz-F=~e!pvkyFAmJD2jFY5x^_z?HZjlm}7iw`gadRl~zic(%HdCr1rXqWksdeG9_rIIH5_O;X} z$MDh4e{xPxQW<4AThlTe^U)`U6LLXo{Os9#={T78x;i@xiikv)I=I=eXD;M@)T!Vf zSE=2Sy_E0Yw0$#tQFi1TlY?gxl}?9)I->58p>9y>L`|KPe|Vb$p(^@x=8nX>j7KyF zMFM1w_EHg4v3&oU3n?Oz5e#RT9374_LOwADF0*aK#LDs?l;r);}{_(y`qN;f=! zCo18X5d}k;^L3sUFz*a`Id9h*JOknE5Ph(r0SW*VmH7{+fu0)sOqBMDu2n^>;g?$9 zHKA3Z+$ik9v?rFn=PlTNDo~^o)NSdg)CdvIP^>mEo$kF!#8H{@Q{?KrAhOC_9>}-T z6;Sr|Y?c-bLomoeMC{R!6gQSGoItORKRhoO9$C}$WXo0vPl08mv_2Fq$AmG-b5%Es zGe7!qJs1!NHLT%$-MO|gc_|s0YG8*!=pGl~vv-Y_80J@IW9k}B-8eSBM;1vuh0JRq zy;LL&r#>E7I0BB%F-4&MIjuQcBV4Z6%bt3QTKzZARc2t4J)9D!%I!>f6t!-#x2

`Q66o;q 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 L6Yn8(J9*(B2#sVy literal 0 HcmV?d00001 From 4ebd3788006a634dbc19a3f4071890d26c2fc1b5 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 9 Feb 2024 12:31:49 +0100 Subject: [PATCH 120/173] Revert "added docker doc with command samples" This reverts commit e2895714715995d4780131611e5b7123ddba77b8. --- docs/developers/docker_guide.md | 125 --------------------- docs/users/osmand.md | 26 ----- docs/users/osmand/brouter-osmand-4.7.1.png | Bin 237157 -> 0 bytes 3 files changed, 151 deletions(-) delete mode 100644 docs/developers/docker_guide.md delete mode 100644 docs/users/osmand/brouter-osmand-4.7.1.png diff --git a/docs/developers/docker_guide.md b/docs/developers/docker_guide.md deleted file mode 100644 index 990d8e9..0000000 --- a/docs/developers/docker_guide.md +++ /dev/null @@ -1,125 +0,0 @@ ---- -parent: Developers ---- - -# Docker help - -In addition to the intro in readme.md about Docker, here are a few commands for daily work with the system. - -Build the Docker with a version based name -``` -$ docker build -t brouter-1.7.2 . -``` - -Start Docker with name additional to the Docker image name. -Please note: -The path for segments are on a Windows system. -Here the port used in server.sh is published. -``` -$ docker run --rm -v "I:/Data/test/segment4":/segments4 --publish 17777:17777 --name brouter-1.7.2 brouter-1.7.2 -``` - -and with a mount for profiles as well -``` -$ docker run --rm -v "I:/Data/test/segment4":/segments4 -v "I:/Data/test/profiles2":/profiles2 --name brouter-1.7.2 brouter-1.7.2 -``` - -Show the running Docker processes -``` -$ docker ps - -output: -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -b23518e8791d brouter-1.7.2 "/bin/sh -c /bin/ser…" 5 minutes ago Up 5 minutes 0.0.0.0:17777->17777/tcp brouter-1.7.2 -``` - -Fire some curl or wget commands to test if is realy useful running. - -Stop a running Docker image - please note, this only works when starts docker image with name, see above -``` -$ docker stop brouter-1.7.2 -``` - -Docker available images - -``` -$ docker images - -output: -REPOSITORY TAG IMAGE ID CREATED SIZE -brouter-1.7.2 latest e39703dec2fa 2 hours ago 410MB -brouter latest 728f122c7388 3 hours ago 410MB -``` - -Control -## Docker with docker-compose - -Use a git clone to build a local folder with last version. -Make a Docker container with version number inside your repository folder. -``` -$ docker build -t brouter:1.7.2 . - -$ docker images - -REPOSITORY TAG IMAGE ID CREATED SIZE -brouter-1.7.2 latest e39703dec2fa 3 hours ago 410MB -brouter 1.7.2 e39703dec2fa 3 hours ago 410MB -``` - -Start a container with composer -This needs a docker config file docker-compose.yml -Something like this: -``` -version: '2' -services: - brouter: - image: brouter:1.7.2 - restart: unless-stopped - ports: - - 17777:17777 - volumes: - - type: bind - source: "I:/Data/test/segment4" - target: /segments4 -# - type: bind -# source: "I:/Data/test/profiles2" -# target: /profiles2 -``` - -Start it -``` -$ docker-compose up -d -``` - -Have a look what is running -``` -$ docker-compose ps -or -$ docker-compose ls -or -$ docker ps -``` - - -Now update your repository (git pull) and build your Docker container with the new version tag -``` -$ docker build -t brouter:1.7.3 . - -$ docker images - -REPOSITORY TAG IMAGE ID CREATED SIZE -brouter 1.7.3 5edc998cb5ae 3 hours ago 410MB -brouter-1.7.2 latest e39703dec2fa 6 hours ago 410MB -``` - -Replace the version in Docker config file docker-compose.yml -``` - image: brouter:1.7.3 -``` - -Stop old running container and start the new one -``` -$ docker-compose down - -$ docker-compose up -d -``` diff --git a/docs/users/osmand.md b/docs/users/osmand.md index a1c2f03..db751f7 100644 --- a/docs/users/osmand.md +++ b/docs/users/osmand.md @@ -67,29 +67,3 @@ application profiles"/> The BRouter app should be launched before OsmAnd for this specific entry to appear in OsmAnd. Therefore, if you cannot find "BRouter (offline)" navigation option, you should force quit OsmAnd and restart it. - - -## OsmAnd version 4.7.1 - -From version 4.7.1 upwards Osmand supports the profile parameter for mapping: -Since Osmand version 3, many profiles can be defined in Osmand and the user can easily switch between these profiles. -This allow now when using the service-interface to address different brouter-profiles in a more flexible and better comprehensive way. - -- If in Osmand a profile has "BRouter" defined as navigation service -- AND the profile-name looks like "Brouter[mysting] - -==> then the profile "mystring" will be used in the Brouter-app! -(this new mapping replaces in that case the basic mapping defined above and based on the file "serviceconfig.dat) - -### Examples: Osmand-profile name Brouter-app -``` -[Brouter[trekking] "trekking" profile will be used (file trekking.brf) -[Brouter[racebike] "racebike" profile will be used (file racebike.brf) -.... -``` -Remark: -Currently Osmand do not check the defined name (case sensitiv) for the Brouter-profile (mystring). -If no profile is found, the routing will fail with "Could not calculate route.."! - -BRouter configuration in OsmAnd
-application profiles diff --git a/docs/users/osmand/brouter-osmand-4.7.1.png b/docs/users/osmand/brouter-osmand-4.7.1.png deleted file mode 100644 index 42dfe802c55cfa176bdd4f7f32fb0f8ae26c2d21..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 237157 zcmd3M^K&Lq*X&9~4| zMZ4)evwa<>!-s|Rn+0-TyL9}mMt^}%t?MjT$Mt&6VstR6FNT26>)~XsXq1-Zy9si( zNFwf^;bV7RCF%d#tNwquymMDe0*7jRfoI@*+5$c1l??#qru_H~^|#~tDRPwSS#ZnX zY2F0eQsz(Y`}LZ@#qeOSZ9YK%?Jf=0C+^N?O+h0a;Bf_=`##!X;eOTh)L7JQt5|m@ z8%H1@+`X@NlQ>?S?p92zy&30mss{^}MnR|KC1UKjjE<36Py6JKdniLG!gI{vcS(yq@gIUkZcbaVF&NK9F_foMkS~F&!k#b1pZ|~~J z4bgc?oQmSk{VQ^zA}T3xX{5xhXSQE%j2-(kw=i?LD*2;vY{4Cv4ltlHnSxNlmcb^) z_x);UFlA_@(ZyWM>7F4RAlVuLreVJ0t1b5>avAXA;m}Aqap=MOA$ld=xkcb=P-sa7 zfiRfVLPJyiL_lUpR8%UVdlVT}5G05Wlj0&JI1JQG$pWzyRS8rqK^UrpLN-Bs5XG)0 z6=K|R$+M^tSg-}jTVQ5+iLtPZQT`kW#1lI=JHU2{1%3#il;w<>wHA{i(|At?*YB9EByFE)tia8s9tDyPQK!@HYbOCIR2qibTvXJ(0P*p(qh@p}S-yj-rg6oKlg3)5P8V^ftLp#;%PDHrjDYu|8S z`fn&Ka2In?Dj+^J_>>{i@2NsC!E>?>m3w4;WHC}zsh}ZIQV{2Wa`y>QD`aLODOpq{ zVRYbp1T-i%I@vB7H5?PfjJ0$aph-~Nav&QgMG2or+$*x8oC-QKf`091FtpqvlnN@3 zMQ=-qfodm;lVFgbCA1Ep5Vi8(|AdMwh_!YbfCI&u;b0v2s9%nTMy14M%bCB*`Py;yft3O1(>LgAt|G+qz(Hh5~eRB{Fn0mj)k@?F0+tQ~_mL=}DhX^KfSFW&X*=oZX7Z>O> zJ6Axs{5~VxnDYj%&LdTyce9g>+!Io5HL46zQe{EaM&m0~)Xd*7S;$Oa21W(;NMbN-Jo`Z^VXZ3| zWE%D7-F~l_Qt8NNP!T9a>8vv7;;bbk#ImrclD>gMCq)O+=$UONH7_$E;X4w8d)+ls z=yWw}crk2XMS{?8hU&9N|6v zB}p9Bc(7UF1-CRkVE(zfIbC=A4!MJPAgHTe8l&(EzH|w-RsGPE*Z|eyP&|(-j770B z6cY?Ooo>q(PlqN2l2ATH0EE`vhG-`GxfCp)cjIUr)rGiPJ`$^meK3}quM5k$M(VO| z)9XZAnubAbiIEkbzpn0;YBaF!Cc=EXP|4XyM-kq7-IN5%F)_c}XcGVX?Z#JRq3a6g zw^bDLJVdz3)rF7k_|;h_RId`f67*3ZGRk~#4ggrO{I1~@0ox?O3mPFE4AK!$?9x&J zG|FPHu*ciH3GJ_0nVf}DJϖM|n6S3smJ7fwfWAn_TQZUh;;YS^dSiz$Q))2eLpkg)bz-u3(%iBmFo57#XOz zT*6A+{rmj$2d@CT7AKZec!s5XJR7SL6KPhjY_a??ib#G`D$JeW<%_j>MB7k)Mv#is zo-?Iu)>8p`4s{``GfYU7NdVb3?id&IQFeWWb~WZWTbW?t=w+8)day#NH4Ht=tW?aNBGN9*49^1whEle; z-Eb_cEh>g!;(_Uog2t#4+kRs074_Bui0z`$I0~^{$Oo98iCO6=q1G6Xo2yH>b>0Sc zhb4yoGnoq+5f<03mWfJmFe=grjEg^#AOu8hG???!l?#3^eo>n+^Qv?c8Pe)(%Ke*Bc| z+R20p<6wo%Pr3&O-ER3fH@VZaADRbvZ>$*9%m(VA5e`G4v$MjWC>Dy10!FCV!n0$b z`_M`veo@S=!xAsKftHJcoyd}Ly!)-g9am9c6W2loL!+H21{PyWH+0wb?iHrs zF30XXmRXGnv6uH3E~XjW{ylu;VDgfjLIoLyFta82J|c9!; z$jkIRn>>urBe*2VSJWKR^Q2~c>(@wDAV0vk`*O|NzMGs=lNWgHxY+fbaWaFerZ8El z>39QMJQ=y(`hMw|Wc1p|w9}BdH)j`wu72o%qfrF-c zD_xI{9yiI+{5Hb@@d$gmn^+Qxq4G7mmzeb2;dQv0+5fHoPbmW`dN_0Tw)68SL-%V= zP}UB=%TnBA3c`1xaNt%hXFFUy|as4Qe0{9Q2o!x^L05Dx}UKFXY#z#Ew-?u3Wq{^d9OIXx&fs zMo{*x^LJD+<~^rk)BoPw`K;s)TS3aYIPwuMJe-j0>+U?7SJSDx`p0`x*y(!R?@8?c zG7eMFNjCZ#&mIF$SaPBnk7EF$4QaO1Ocb`LDn32dG+Ed%a7F)S*`UIv|GDu7?)lyhCHp2hYy4t41&hSfq<>ysks*@VqA-G+kTdFS$OZ zx!#M{oy6~bmg(99h#Hhlh8RvnM_J&s=bx<8#4W+XZeK&aDZZBtd%a=O|gc1IjV z7YY-N=$G2W5!!Wk7$$Z(Ezr}}^f_%TU6)sP4(U$27`Z*KAo6!O51#v1IygGo?*#pP zf?@-QEEBuxLHX@JX$Ic(I<1(h_tlA~Lhe*|vH5M~bTn~Dbi zmvRvsu?jrF$@6L~LnzVou=snA{I}}m(p+uXd*ZcP=W#!ZMo7^FD z?Ym{>b|D;kjyiBT&HSTfD@7u#@n; z%+GNl@?5}ie}<2LW`G1b#&O0X70%Aj?DcTPfA=8Gjj|yHD*%lu3Yx7|Z@t!`V|Up= zq%-6)2(s_#{c%_X*>x$oHgqHcv-fqbQZD^jeWU)U(p|=Na6OB^{(6kmwiRy)jjgq$ z_o)!q=!fsYYvhclXbE^}2U)NuJ;_jKa`Mf-mS10O3IU%VfU5SgsUw-H@;rGdr1@&3 zcHL$AeUdb-pLc(8YTNq{uTxo;fF?nYvFW9Ha76~C!1b1kNTE`^yE7+h$ik{Vr{Bvq zV|33eX$!1Fw};`@=0jixl5C?U_0QK?Dy!t`jA{2F)FrYJrwdbit5_}zM~hK)viEFX z(N3mZ=Q>S2c zzq>usIRBQPP8!fgj+S<=_m8KMu%?cS<*IWdvKGeI`xSckN$;bCK1Oe^-s%?~8onFT znYC56{?|YX%InuU=G>`rP|8X&60+jM^;zlm^f-Y`&Gy}z@89CJ)4m3U*e2jP+zQSZ zI3x=a7b;O@2q~^}S}v&z2Zz!ThQ6@a$DA6J2pgHltTid;`!$v%3)QTC8c1_C3vSc^+IT@Qx( z87U8YQ#AZ$PbP^`!O1&2Wj2utMD;>46t6Mm4Jv^WgHpHxi|dgO0s z<|tU(F!W-}XN?l*dpyySu3=Bc=K0yKowa?LyFQk9niX$l*4Fg?j?)dNYC`;Q<^J{< zpFa%~XmL7U!dh3~_Bh{vVO5*T5`t3#sBpGpAsG+Q1wqrH%!norAoW!BULm|RbA9DJ zu^L=Nsr>Ntd7Dc(Z2Qid=sXuOqsOp-qCtXouqN@v>=V^bJE%IZjZHCYt=~nv322F6 z%Xg^d?S8cDOOCde$QE#UCLB9x>R*J05UQb14GDU#%C&x#)#OQ`ukC%z)A1E^b%`S2 zvBwAsh@Y4Zb#YPo;hN9t>Y>HNDxREW-w_@A__(B7#_oL<0Gnhsjtn-1uBIG<|AA8% z`){k)=MV5GW#Z7$ES7LB;4R&)%AMEwYPc>9LlHFm^xENTQuwAXMM!Q=Y3kj-%c8rz z3{q&Nj8)&AxG=p~Ui4N&R}mV`i8nGAt zA@KZgOiA!1nm0|USa7fpzrM7z1)hV+th@()mM>-6gnB|wwYM#g>Q;f4FEkA zL)hWF@SUY1PBX-tv=&Y%qkFai$k}WRU2O zn$V2b#NzivO-zTQu%T4I_TVX+i;FWo?YlGk6<$!D5ti%?sJQFtAi=p{f5!+pCzj8I%I*vO^^dhy;+T zX7@Jtaddp2t(=CZM$f1gOA%9|im+FUb0aiIW-7&A)U`9kH&hvY_Ov5oGwWsY^k8$C zOM^_SgAa4#Qb7i?s;NmIn%%{WlNNbKN!b~`hT86hXMjoPn_EZu4X2*_HTo~9LFFXH zeRy)ZDDX9tVCiA#A=!}r=xLc4o4Ug?DeC}?W1Z?1&Y_U}ToyF&U66}^d#HVXr)Skv z;a%2eb-R~HG&?t<*R&Ho{=@qi!W!q}h1Ddg$V@EjE!{YZ2!Di(G>uJ#k}+d!W_6FN zq4)UoHb#+LnaXbNzT^IO%ec0g>o-NA#q(_YT!-Irqpf*9G}HA~OpIYEHf_xDr%7@- zm{|yRG>`ixV@Kz^G%i_Pmwu+m;cu(^r@HSY&cTAj)w@H^@f99alreR>RRUCyy`1Y@ zZ<$VnrAz6|RQbA`ibTAN5^afE8a%xYSKBeIG>$S81X>8SdU~9>1-$t=PCgS+%TtAf zKpr5+{CJi1`JHB@_WS)1)^BRU)e-?h7?nG%ZDU(QQ<}aa-vL%;Lp`?F{^q1-^!okb zq4(XSs^_i|7lHRqqvgVdokRj7Eq0~FtE(eeTQ#qV%7EO z+SRehXzVC2Vj3)#KG5FJA)AkimQm`5E=$|-B>9miosdsqziQ&FQXHEo@%jzIt$TcEIDR;FxW%|PvoDee_>0_v&ECR5;}WTg^{N=ZghJj$t( zR|W5y)l58g;mUjfGi#9OC_G2O8UkWCuIc4BcTnsCej%d5)@?NLqs$+Aud;0nO zT(>^o=Qn!t*mt76?z5jnjZuyuKK9(?S1u9v-JyttW>>-BRNsC2h^hNp>oQz&^EpQO zhxfT!|hHWUaIcxU36x-t^SpWAOFNd+V4t4YRlO8opU@S%cOkUW^_yqL{p+5+S-& za54hQY%{q##(s_4eei?K&+TKanpvqB_!YNarNx|qT(FFj*YW*VPR<65R(!6 z{ob6K3}1VpWzs_B_2h>S*T%$^xAp!L3hlk^7F1929#GKvhKbu` zpWd23S*}!GC%%+0TsJjsrLO13yi$j^oA#3Jv`_|ZEan%KJTiFhp{*M;c7oH+3Ahch zUdz?U^W?;1(;oqr=Yz6|6E2_C$unElq0eCRq^av`O4S4J!J|siv}) zKmLTysy*K`$G1D*#)9$m&YQv+Zk=B1<-}>(*YX;#9LUZ>JfEK>Q$4EG&wjN~M*odF zTz<4f_l{cmAV~2*g`qJpYCGEPUTRM;&wD!%fxJ` z5amelwmgxCtW8N0p&SO-1Q*nQu1EjUlnztDJ=Sy!F$mG9m-oNs;zE!>2XM~(@V`-VFe<%*u8)dFfc6{`- z3584SGpuZSJrCwoysxYYd`+*1=4w{DtD1c-Gjb4%iz9_)(Wd5(eMagZ)^Jpz15nhc zYd7L&z>B4nm>L&ncyL~6d|mY6!32MTrlZIm!wX9@q%x=0R1()GLrM^(yhW~`Mg3li z=aJTJOl6KdTxl(p zE6f^5P3JYOEv{&kQ&^+Q-;pLodAZa26^ba}ekN(-WkDVpA>N1OTc6nIX}g2@rAME~ z;uZ*psDEGEFZWO0+W15GH9(y0DP9O{GxoT5wPeSP#mAbp3>6i=zacd```NcY5p(?t zpY{HGF=HzRlfZ^>Q)^Ut&c>uZRiuWI})sx5w(lsOUK3x!gvYgq3w(5pXG#=XM zr@_?;vzUJBr1k8Hls#?l?5u7{16vKg?JbXoW*DE=*o;Dh$6&v$8xqN;=HQlL_*1J` zSsHI^`ZfjJvZU1lTjKbe{I59-+}{N%Yden$^FA~fH(v=|?<$-uk;bS`o-k=ivWAm| z!PvATi0kU~+_w}oT6J4JE+SCfzx=R&tvi+0pU{KDZnf&k;5QoV+5Uop6fL`{hk~j$ z$KT=g+AsJ{lum71Z#m>q-}yk?JgZtnNhk3@fO-@0b&>I1h&`p`cQ^ktNZI#D$$)Pe zmXS>wG|6y6M}QCoi;iqohUJ;iE`(vnCpH8PN)JZLb`5Mu^$IOy3N$*-5Zig|0&!&P zsvUTQQ>9jhQP1ads0ek-(mN(+Peq4<9M{Dci9oqINu#K zyt0h?*=>?!te%V?zy+cNAWKTCkYdfmPM+B)G=bF(R-kM0c`9y^pRJR9e_P51DiI2C z(&MaxHiQ(T?JeqNh!H-*>Ia)!@XTRc zS`_F9tcG5z$W5b&sHQukrwhsoQntECGr+VBKegQ9a!TRdHS z-Ce6z+HZ7ykx$@`Zolk_7;vYaB{#K3RzR8Hz8Z5rRj+2~X2$wLUgxgeI;Z7#_OiLz zSy&T_+ML%3xxP+qOUxfZBR|3{oak6Q`F^=W)_NRI)U0%U?Iql0N&MKZw0Mf%`GoX3 z8XsC8MgTeX6?-_j1%ne?S2`u1TP|-Ic%b&(&j@&^C)ZMT^|fLAh}aSR*0efbg6*Pw zZ{4vThiOIpbJ<}51)rHx$Su+!BQz&l9ZuProHL;~Shw!;UiTEEhX@P1AOQ(X5d zy}tO$QrXykRM6US>W};0`1F5g^xOw=U@p>7s#}Jab<-%>?q*eKP~3JBIV{|WMwf|& ztSHCE${w+Im*q53erY&BN2LaU5(q^R7>Xmaih#~?&u7LmD3#D;%jL zbux{AH4^HYEy>~ax~<`!MMXKUB}l9@$LfBiCdlkWHT&G@^sRX9Z3wX%H19Igw)!)r zE9L`(nh>f7yFnOh&p%V_LuBTPuE@LSyC;0!xBh4=ZKfVyejSaY=Do@-$m4fVfwTm7 zC>cY~NcCbVT%j5Ib?1ipT-x=iuidpypynNCl%eEc@4W6*HA$@K-h`SOdYW7v6zFwv zY~2xS)U;6`7IR9_*pGr3GD`-kL;_7W zlfQZWp2kjIz>=v36bFHv7IEGt6|n&UPfVm;nd65pA+C8u3|hCA?GL0gOG7WZvfo?Y z;x?x?wMRpngAlVpNI~yqSxkr=NmZu#B+U;yeBI9mlN84~rK?QWcFsL?J{VRz`)5BY z;1W?9=m%fsy4}=k>B!0MKVZ$n?zq6TwsFAf`G_6)n|HF%Is{_^16nE{of)AK5p^L; zdaXIE5vK76=VX$Wv6~=q>cYR?e4zu}K~ZLj=J4oXAjtp6=S>;VdXmP*K)~nkd;L?R zn#0%bo&q5r99Q{PciBJ5M{{fmMM4OG4#TKeKIDdP8$3K?>wGnf3Rb71fvwFEhMvXP z5hNKa;HN+>ASTXC!Hh+Z`DRQ?=|Kc5dZi22BD3>uRNW3!!}@3YnOUH{;JfnKoE&nT zsmJ*`D6i+Hrmt}PY$>h#?_x#SWIDMpwVQ9ev?zt0x^+-h*or-8?{f#1P6RId1FEvB ztmw~)WVApaP0s*sFp=EaV+rzBW{7d0fXw1lx=bNaJZ@C04|P#WfmYWV*QaoWznq)X z_lrCK27e3XkrJw^L~HHV?XQW|!&G)*o6vZ*y6>^yu{zqY)2s?x$MX1fVTOhHKMQYd zfeA&;nNdN_>$^@lTmByr-YdFq>Y~chOaNh=lV1pICc3)Puf(0y2|IEtHZmAIpE$nS zxZrFy(^WQUuKJO2!ohPE9d!uw3u;Gm$N zb0w1#ZZaX67Prp!I-@)vU0URmmQnHVX2d7-RKC6x5qJ9-8F!JyyqnSxnR`G#b$&fr z%n^n>uNL8Ix6l1~?mAcVfHpSJrpi44-{puK6_U0TRzI3OkDM9<{BkW~O9V_uKe?@c zJrmy$a&7bD;FnkLtg>)9(~gzLfw3sDyAeRAc z2B(a9M|Ja^bn>}q6_;x#Mx~b3+U^^zi(jp$iv>icxKbFzGDqJlrs?o-5@as#@M7_TbLZ1bLBEobhtyegCZ(Yy9cMESCE{*kz0F4{Di`|l3suXaiK@#zWc z+VJIY_+LR`(e`i1gQ>W>&~3D?6u#GGgywgg%CdY z0NaRhkiG*&Xsr9|qL5|Nb!HxR-?2Mx+~@Ejseb~Ltd(H{YprHmOhlU-%#kt#TllbZ zXJ&SdT(OpQ13fxRsxUugRhr%ts^FBuHMP~G4IMZ-N-&y{r{ST_7qju{!OFL}-%gHM zwsoN{wmu?~uI-O3ekl_{zH`&sIoX^zeq4Qlr%*(em)B23j^pPQF`2o{+Sadi_dJ}P z!^t9*(Ky15`_;p?-aoI(j>modECDOKE-h(lb0|R8c_J=jjI>s%P$gLlH-Xi*$4eHU zwYRp9reO<0S=tuZx{kqp=~g#-Jim5Eo0j+GYX4&gvQHZXzk8Ft*_FwYDc-&&|4F+N zGtBCaSJDN@T*$cjIn{T5S6rBw2{@II%?8YFEOBkNp6+5>Am+&tgURa)uHp0kmG5~;M}0aYt%bn zez7-)Klg{W>&rhz{g4>jc)$9n0gt-x>Sb?!Oii0qxbhxo6hp?8ou)1isnTOBp6)Pt z{#sgguM;u*TyOiB|5Awao<6e{w6Fc)HSCQ=G5u=xc`nY_sU-C)^!m%;@bzM=BP zDAPstq~htTvTiS5j&JL8^gL%TV+Y%132oDXL^w;9!wkzItrX2ViY9t8_joOa7XA`1 zq9+LvjCJid(;}GLXvYX_&%BBa9_sOIlu+RdNvOWCDk)$1wEt>Lu}?Y{dz#w4hMz6h zVb5!~pE(t%{H?@Nu!rqOiJVS{cT1S?F_(h#m=w9km|<;O zv+lPsRUGyrCXJi+I4It7xVAl|o#Saj=~gl>I6ovVT?9x-=4QDb9!{@)yk6O_30A!h ze~-`_Q4i_3J)H1;w{*AnB|=%-B+Xq)LRwFbHj-N5aNkQ1X8GP9qxQInhv>+c!n zgEF_&CipK4DB6BGV z7w7D~)K*Xmuy_BBy-pOkKcSphE=^C$enry@TV{ccr7A$B$j$b8NE+nuD`)F=n|5{J zx!cvi!mY ztE;l}`n$~W$#%h~h?r;Te7*NBpciB3z`%W3>iB$eD0Og8~E#YHwg_WeQ9ospQbbfn{xsAoG57qu?n^WxN+XY!66cTac#0`qS%qE^aWK9}b_#2=KuF-?NmP*qy0WeK{^5!-g&&hggU z_v8p?vB;u@+4KC4##RKjF^^G1*g=p8oh|>5ScL|&(Z$xDlHwpW|L@0(zdM# zo1eGZw)Fho^D<4ka zz7H>6P{B+zmLz_dF<;7FnXs|FXt~whOX0SYspDygYzI=?A z{R<^dakY`PoooKJ#p!z(Qe`<-_RlrZJ1Y>r4fNI8_j)=$y4~CRU77w3kvJGy087ot z@MIT8n+Xn;r7QWJC}}ZYlM;C$&M0F(R!T|u@v{F?dscdG<6Fhku*Lc1;(XmB67yGD z0XB6RAo4>evOxjsaq2*X&<4-{sOun&Jr=SKLu5UJ`}W2nP}SooM09bA$aDIrEbmP> zHp9(q$laWVoGp&er<%P;nVAgub6x&UOTgdQ?E}Kx8SG+8WBDOY-?U62Oa&6V0J@Np zyVlla?e@0rTi$GN5bV5;ZbhP*kHh(GcJo)54J)H+3+hR;WyiPgYv8|L-Cv> zyWyGME=BBy3zzLoDrVTfaqgAm+Lmzw2dl9)9X?m!dWmNDGfoP3YAmpD7`k(+bP@Gr z@C>aAyl;mMXPqvCuFIT`FE;+F)^eO>QKB0qExaMa6+sGgy#6c23d_B>Wj2GjIwM12 zy3-)!>>(;Ll4R3CkOJTSDWR>4X)#1nq?SmTsLlY{JLi~TXSV&0qpP*B=rK4mA|u=2 zwSN@)3081PH{v`5ohi~$hn8rxK97e?LTxs<%#1^v7wf-_=fFoCv&Ful}BiDh$`?^uL zr&f2v|LZi{;D`?4PzBm}+qF<7fGV{pvuODVPIhv=awFMF$<$gJ^Aw&01K+8LD=2eP z>#Utjc_>}H=ES&AC?d8fk~>H!aB0!H11T}ZrJ*<@9lL@ph%0Ddt~jOhZiGj@=M__7 z`@QUS`;X@&I*Fp2`yve1ZL3UX&0KQN_2EkH;%KCMM9g6mhT2{^u2&6(E+m0(k% zh{P~=V(O0`*TW5eSOo8H|EF!r(Azod)nXATXEY8Hr%|Uv2o$BqA^YBCG*jni(-^h zR4XT{H(CAr42Ha)X9#a6tE(%g=8e(ONcjC#)y2iTpZCRvx~{h_eUJRd5H|VI-mOu@ zq@_R@fZAY=Y4bz)`D(pkSIuMrmXXs>!`EAHBL<=r@4a_ApYMH~1JqzLin#k(AI2(F(!=U)vbz67BMDKBkW`6qGi{OR=E+e@v~(9} zq+|s~KmlgWP;X6SJ37+Ij1m*WU4C(Z^KscDvVdAE)4+~P6aGAi%ykN(LN`l6RD?&+ zT5b@KLhJgv}Ue*j(_(`@cb;G{ygcxVV%v;B@Lh>z9*Oq@~EaDj}^AuN5BkA=Ukb> zWoNI`90G|P3>;1=yS}{qw)Qt#M&Lv0mMxA_J+%cIfSRPl*u?XuECtl;CuX3Jt9m)d z`Bvb`v%crefg#3@`G6oJdPWX_|IQCJq(j*vj!DKDzhk3pP9!mAIvq;nx5yPkPCVnc zcUI%ivCgWN?_d*+I+GF<NLG5+FbE97LWtw!vcR->|;O zVoB>avQWp-SjT#~364~n5O(8Rx)bZIX1tnx+9N`vd~)H#V!b|??~AoxhLDx9APhw= zPA^iT1p(}jLW(jOmPaCL9KJ}?RM2PkNWB)60!{L7Ao7vK{`oRx8&9PaE*~Z!d_XkP+gx6N266DrQ{u}8@4)xbnh#9L z{g;@s?uq#supAm+ikDbW8q|6btrAT@9Ga?XEe@o_usO~T$ljgo7@wweY7G^Sg1`>Z zj8-KYh`b*lgC<1`3Ke0@#_m4A-VV_=@t2M{paeu5M|5|;){z?ztsOZ5&4M%onGao? zG#}Cn6uP_%xg;K;A)oFv8t24J!?ieGxF~(T#tU(ZHm$`{F-?_g(0T%*M2bb2zO1aa z-O9Pz0Ocq1U(4p&@Cbb8UQ}XYB^2WF`C~{|#9S0BMKQ9307E8Y@&kCWqS7LF(uB(!EY6%!HJuc|^>?ZewjxDqlplt8xCS{O++Cj!hh4?{{K7m5a| zRBRk2)EdHgv62yGz@HYEaphBS@SpU>m$|yUP5MU-qEJ*hOTZ_pbX+YvA;R%WfuV-X zf*IP1rX!H1vj?Vn1^aY5wScK^MmC`%0u+)KG!SF+pu|`=W*4NCxwP`9yZm$%W$O&` z1N8{;^1*O@K~|%!WpXSJR4%v}vJjjQb(r;#DUSS9NPp^?Z^pEAyoY6G6Fx|lg#HB5 zb60^v#Z^b?M*glr=2$5S674v&UEa5sH^je^K&V>t*R|iyo{j_g^GkQJC*YNIX#7;& zG!}#Rkp7kSgc2-ggn4y^%HocCq{~~2M5kg)O6S)>Hhfx#9ojm->|G?-*#(HI;diTo zjv5Sx-r{VK{KDn>QkN1nsRiPw`}>#3YJXez^m4bq?{*1T^XkHT=-Qply^X++grT13 z#F_8FQ|E#rn!hxE=`JpZiFaN(IZ!ll73r&RyZxUEH_$>)*)S1kP|@VJvGjU+vx> zP-`>(W%hVpY5Re%G9!LWSYb@Re;Ul5yITi6y1CZSYdR{&YHjTo;JF938~S%}!HvO-=(naIO(*M9* z#hO{`zh0Vg2EANr>gBl?9NCEfEArGpP=L0z8VqKgMT zoWjf@;gBHU9g)acK^CPw08FcTDUNXRyR@pxoWp`hj+gkGSBTS77})|8g@3fG zh{E@g&55MPp}-#$+$gK%8)>ZPH19t}|BYz=BO}#;gj5fBo}~h$)S~y=+Jn~A3zn?7 zV)_k0agme_<~$9k+r~f5S27{WIV<`73)~$^5U?+WpOE9&-mW$&HY>^bua%EebSnsV zw_MvLlK$A>N9D={$N)AhgO@Z&3z-lny2Mt6vX4LORn>4dk2wBV#4fj4y`Odo#LIXD2?zk7 zIU_$CrU)+s8xufP?*lY;l~Dj>zlVn-MB|KHwNBB36sdA|6={&)W#)2azaCMgoP^4FE9; zw2qWkNEDNJ3g9K zBG&eKsNQv1Rk^xTrKmG{A|+{w%wP_wl)RKg*VKdh z5{DOC+p^S4R1;~Wl@Xf{)&pq>^2&7Kp{vjmEhcR*V6+UwGz3qn6^8oHwtP8Qj1;E` zs-^#TtrNs401PA{UdN@>oa%g+#pGH%0FN``Zx%>#>IZlY?!_quTaf`!z(I4TlT{#y z3|vNBkjO+gK~zuhu?Hm8;(78YA*$}OdVs_?le^(y?~)3XNDL)fMst*d>#rS#}WL%yPwb7(Mw8L03GL81^M$>9l=!`)zNYY-S!JiyCJS$1mS0uoG-s%BL0S!?m? zf9P2UX~d;~F1t4knV=LSOieN%X<7*VMJ~;z1K;kf>?0Q&ULqw>;0Dqeh;s9KH#xq^ zFjslr9&s#seh))&k{+Wr=C@wG?Z7-HB z6C6N*xJNI-_EvrdIRr6q+CwWbIFzftuMS~lO579y03LVFX6;wNg)kaHCzY{4<1}Qa zJ2Ij~bA?E*q+{UWxoN2D332iDgZn_AyY|=TE9ZIQYpalzbx&=5IfH9AHz?A@A|R$h zBl0s5&Tt4(BK1Nnpp`M%R^utFXjfTn#HzOAMafPVfP;wD;~JExikKjaBj<)aKRbNd zg;uUCJ@A+Dq0bKcyumDknphHr!6XG$^Z~#Gbk8Iv$zpYOz_IJ(%=QGfaFG%Vyf78*X*~xjPKmO1<$CLCYg9V zk&MJk{4UqRRLHf)Z&rSyqmafz=QZ2j@ynRJ2pl$Zk%4QB!@+7KO|S$s89{rpu9QH#%M20qKRY;WzwZIRAZMOOQr(UN%!M_rLv> z%$GDpCTgH3Lm14Do$6?c_fVGN4hcZH$;<#%?8}iVS0C^TrFsg!$#dDD&KDq7YhDq2 z7nH7&TOnRnBZ+zfkf1KAo(8K55KsqX;I;3N!B0w+(Pi_`<9me+;nLzuh{+yWQU4N=kp;vD-+5F#H&Xu}DcEob)+u>hRr zaD})r=^$Cp3uE0XYV}tDNBcJ&HId)E{U@yBxSn0F#gp}_zZW{Zt~p^HS>df!w>p{F z2F=%UuO7e|NKuD4xm)NeSEco3u5ph^*}rRQ0_c6~peC`%Csa>zNj%GW|Q zH^SVhU5pN$^X8sx%>YGA`K#pY*Y+O&TBBS909Sa785d+ylaj+-8KQGE9%!Hx8hq11 zz+bF;n9BwdjsWS$Jr^AJLhvYELd`3jlPxCQrR>Qtku_77Ram#ViXU{sm5uSLGZL0mD-4hqSIm%Wz z1D(F;&wb<2?SG2>cQktk0Pi=q%eITL{oKi2AJ<)3#?{?)2c$AzZ6z>gyT5@Rz@VZ-24r zE1ou&O#^X%;wi!j8O)>7kxAvIbqNLvcGgRr^W&Pe_uu}R(~76^*aaykKP>%4;~3~stNFG>lM;j>8w@+% zE2r=0(mtY0S%%aQHwsdtts^^-O@gD)7EDdrzWut%xBk-nXMb&?{~fmk-Ds+Nq+T4g z(()4y3>$p#l@Q*S-L!vlH8xU+i??#P7gS7%S&q z2P?bm+J!HslLrEFy)E{{m#fiT<81NGKh3Wif;1ovEbRA(|1S8F0|?G!NGWNj)N^y) z4K~OIkSA_IY6B6(W(!KL@AH>tIeg)LPFvN@o?3JFnv=xI6_X_!ooheZyz=MVRoI%k z^>;h=Ttf>OXaZ!Q0HvkGz^;eou(fslM;n*@6v!QJ{agQiF5Q8%`YE>M7crQ}R|X!Z zfhAyq0Ive`=BDrdG#>bT8{MuVOAII@W7K9D#@lCKJ8|uYF?ty!m((}O`Kq^M7v0sp z?~^T?bKl&7V(?+|h;Pn`vv(h*E%J3GY=)NFE+HM!+30xt)%eIs6s&h<#^j`DcipxvuSUxGnsP3ye={`KfTy(nbRARjD0s;qt0 z6zRohR9~k!b*`$(*Nyw@>}0D)t4Hgb^xK9la8wq{fdNuLTg_!p97(+Ug5}HV?XS~N zY^)M+0`ws)E#CHfiwB;>#4fkop7?ra_EACVDKao50}w3I0+<2HK?Sa!;E}RP`D=%{ zu4C5jY2%h}=wW_*`Rk{n>wUIrljIK&-v!$wkqTzJ8drR%rTP3VzqO^O~Vb zp?8rTY@D<0+MjM;{(cMFq&%dlFO{db0=D_;>U)Sg2AOIiUDO4x29<$X$}*c9_UT43 zH=MhaHW*40yuZJAX#aPf*?Z;(KIX%h5v4FMIXZmhj~;s9bFE?)wABc}qZpW?KtnJA zoZ>EnEU6otEF&A$!lCk(qE|Usu~~n{Xfa4WvIQ~(7jeM4D^pJodFh@cWEPVQygvp` zRZb;Iq+qM5;9g4HK36@W#84T2=zw6&gVgE1>Sys1)|XbjN$W3kNMay>s%K!UH?_Hx zTdmrw?jJN;e04x4tCV=o=Qk5@-Te|?IX(??1QK2;NRMgDs_zU3uL9EX{RTk2*sR8? z*(4K!5Q(a;gByYkDn0sUH!cZu=%5o;yDWIILGL*TM1)}F7-xD<{G~k=62ee9D-Kgr z5%vksT>B|ZS7p~zC6u#Z~4#dau&fI1v~%-gcaSj?V5ko z-t&{-sHB6--~4p%_Wy4(_Dq>Xv(3ErzlT&`j7zos1YKwWi2?%>AP@r$RzTn`G31JJ z;N;?#rTK~F2WD>lor$;oON3E#A&HTl?QLh<{wGXCOq^0O_rcIcB4u8}rIfs2!>Dis z6s@$Rp18U*)L`XBjmQ$SuC@&Upt=!JJ!W16FBN^b51bT;>P8C55*wwqvd~Ew5@s}q z6A}GvMBI>~un;c^n&f@9N>Kp8V`gP$5kByuJP};KsO9RMUlTPki_9Z4hZx0OGW6AB z=KbI$5rN9-Fd!N>;47eRK2E%ne_rn^+GGn-cfs1t}WrJA8O1awPd3T zad8M8f~a0*=m+nUaU&Do+LA>RLu3}2MK%ly&_P9{#|%YgtIfc=9PLgLQL>0e_2LOk zQHXnD)|Xa9?w}wJrTGlyV5&u8-FR-4D7jI4Y`cfVph#+ojJbD%_Xf^6zUI=r{_4T6 z2_Z^eqM4W*#7F|_v1!ZdrFu!EwZ$C(n2BZpx-vqTyVLtgbEclShtkQsz-Uj?V(7Fb zHIQHw0?vfal5CBhCoMX@tb97_(CH5_uJrSb;0+!CLgyXAKx+np;u1R2k- z{&2$Z!p*-kk`AaB5b4R#l1{d-`}y{jA988Z=9lmORKsW5> znb)_^+cUoNd<^Y|45L^6xL_hd1F}S5Isub{1ayHPV z+Q9jf02y7CK~mAr8;iPK&%CaA!5c<*oQ=*1WXKD};(>*I_pUthmF&Mx=p0Um2Qy zO1!tQ^ZfEvZ#PUxHsq6MI+P3mpv~qvuU{D6S+dmhT=wXfW%k*o7hr)zkRHrE@1orw zy-;6$W9Qu0>F7>mEm%Ttt~~t2?BjRECvP2H`gYq_zz~yUmcT@}B5yBV`H|SzCS_Np zC*6bDx4sx%eaFR(3*SC^##QJ{A_SCuKm5$xckU=3_|n+ow{t8R-6@*<6}w?+%{smd zO4rzmhn8;ooy>cUD?Z@iRjsRj2AUnd_1{hQ52zQ*VSMPikG3xVAUMW!c;((tP2cf{ z+v0(0O(rQP4PKnWuj87j2S5VL$}0(QXLKp6(0CIU1CYW%O9ld|-^I$jH1aAs@`%n^%`KO=Ui~(odf@Oa|0zCoTT_=C$?0(P7P678ulq3a(;))1 z-MvaBM&?`2zTVGzedUL22fsS~(5;PPrYnA}-F*$v0^5*wBGr>O7PFz#$97+hE8n+r_a83a^ZUc;u$d?dz`;f# zL$>9LZLj?(xDx<4`;>0suKug8{|6Q(fhJNDtqHWY7AApYTAEhefeR@W9cyv#-NCBClr*WY_h-ey74!DM0Z^3 zoCFCLj)r$KcFyeVdTr;@A1@yM%Z0D~-tgjslKK!bl@^eoz0euG_(q=gCU68W*zC;V z{qgV|q+f2iV#{?u4{QfR)GR`>!9yM$Ip-~%b26bL?pr$an9Lu7l~!fikLZP=89A$S z)qgXw=Y43LX4TzV3#2zwHhj^xGv4Y~KQVXn@A~7PA4;>KTJ}l?$3ht2`r7xQc@~^t z>9bEg{`gZnulbSACGSqzPDitf2lbrRI%|CA<+$jEslWI)jf1xjW0@({Ysw!GPO_LJ z3bMhNr6(3{`M48}J^v_%9j(iL(hY}h`#nK-HuK1vb?TiPz_x^tO*5BHZrW;av z-~gAh{S_W@Jl##ZQf1^WRzH?7lYgl$ZdmGTjs~ zkeSOi?znc#J5O7@`40|1^y%$sK@&pLN-DUc;C6un=CA-E1q|*(tfKSH1g%g+@qU(i zkX8y45l{>TUG7VsOoN{wJpy403-AJ@iKxW|4yDW9zwL&Pc)rtQd~J)%3`N+BN`ZXCkz0f?tbCeO8A_e48nyzE5%ib+R zt?4iQ#`fNmmA2IfnFJ7}xfsUmf_H9xE1ej0RCD;HLE6Bv?9rHC;M z2sSA~L?D=tNj{ui`k~>;SIvCke;-|WSktl!Bnd@h%AKRsiG-o*Z3sk&%gz{F!H%IL zOu-bAUdTdoeW4RSrC}TY<#+P&dwWuF`RWHFbP+tE;L)!Gt)3CI*PH$ z@wZK`w1*bI^(Sr2(h|)?#7q!|pGLBeM+9gruVdMw^r4 zTi?n3pZWFH-2Is?dXSU6<4w&e!ATaA&Ok9s2pB03F5dL<1RuKcXOWGz_xwyRj5yJ{ zYQUjdq!JV-S?*R@ShO{Dt$U}b4@SFZ|EEeNKpOVl`5p4zHfxHT)(^1F>+(2La*mm?a_Fv3HAuO=k4nu}9JVtn)=HZ8dT$zIn zu%*lsVA$R`>xVm6?Je&3?M6B6#F|UXiVnv!-n{*-|9zRCD=rGaS71wGo}mYKgmIN& zOJcG`w%^!(s~`TtuWTuvrbR%4$0BQT*EJJ2{8QxT2wGY#s(l_ShrPeTtev;ELPy~t zI8C;Pi+@(9rqf*?59k6ASU?P(;61UTAwZUo?7lEDlLS_#L@}{GQ5ayXlJZq^4-$ZN z^48ga(H0Slpa!f!hh4K04Wd!%NI36D@~a0OdGOQUF#6Vi?)m9t z!(CmH_#(>^QbGkB6RIOGIGXK#)7Xs*hraN!t-jZ3AsQWuj8GVq3Frge7@9E)4g*$D z9+1odb50s^6)_+}aya4wJR$X@>?;aYx*0T^3A?tx^%ugc-db`e;L5&uLh8b&0Ee`w z;{f3j+vm04@Xx1b=CY^%Dz^niLYbNZL_?!8h%jq*#!i0&V6YXU7c2=Wl0)(wO_LmG zV)Qj*m%pob(-RpMOJ_#D0AH<|?{7|edI2dT6i9H!5j!|@^KVnwzV!XbwvAo!0fE5V z5)XI3`KkWRA0O>6g9cQ(qCo;~2okBM{H)FW7;)|{C?SEQ5-?Cp2;72uax6D@G|qV= zvhg*aMc7K5-5VHz7QGN7JO?rX;X(pzWVQ%fMmdY+1!P^UJUL{Gp)3k*_9xHVe$5Aw z?W!6h-t*Qq<&;RutSeT6fe@g9q=N;|<`6FI zgH>t;C-a5oIa}ZK3*I`nKql~jWjp-f!kvG_eRuTA9=D#=IsffL7r&>}?XWzfw$8kI z^sLvVM-OD!$fNQEHA^5YzyUQDzWA-Vd;Tmu_i))=k=Cfb>gwUkemphLG^j`hZW+Gf zCk{XOw>sa=e0dNBDtWJU`i?h$6!~sKoiOxx_@4Q1{88_~w0O2rTx((Af1Ku-4faD2{t+{?6%JKi=DWd&?8N?c1-Kc+-co z?bi@3NpRUdzUO_19{l2D_bK&0oMOt9AvM7q01yMB`!Y45SisUjSOG7yavoxUy8Eg# z4(LuNQ2NT=*?T|7XYMVExzHTeGhaV?**nlU2L^{F8E;+n?xjcmDjhzoDZ+t_C0gB~ zv&Y}^Ps;q941_~$ALE`(fB{%yd+5sdYtP?m$>I-v&>K5 za{UMA|M(lN-eIV?NDne9xg&7ko=y+O`TG|i{6hbkhiqxC*=WVnE*rh-J=!`)P=(NJ z?ObroNf|GBX6oF?z;c5{!nGPQO>L+`;W#aPpix0yHEX~~ix$>4z zZZ8j}EKgOUS6_=Xbo4Id%uJ`PnBR=n3p>~>a*0fck zGkVo~&^Z^DCF#3Jn1ATm&;D9__O8*ml6i`~hvp95{oE6`?Rw|0+VGVm2u0`Y?6n{4 z-SljS`x;!5@BB^1u%K-}?}mZU_XPCg^aHJ@{-(|OP&fl<(Gh?E%REB@6r|9^VFm|X zHd6r56W~tJv4r-}6+eNoOMzy?0bhz!cOCfrZ%oYG)3W)FuW8FACj~ znM1b?UGd(ylwxN{?F0b`(;vf9J0b?Z0o^kNwZy zJlBvp$;N5f#XsD?{RxeWPQtk&!^3;th0)i*v%us?K7H47fA(t=^Y;vUbTcga{B!rj zmFbb|ewN%qD6~0#**j(){bH^w@TM}v@e5_go+NGc1nZ*v#Np5WUn4WOZ}Vj)Mf9Tg z+|-w+cm34A_Qv`2Ky!3<=#Go+$!FC2fIHoUmHD!Noo z-TK6+O@n}0p~Kmp5BTWiV1TF6N}0QR>T|!^S@>on6*R`>N0y(t4~zSo*Zp#mF<69d z>+Jc7g>T<9UhaoL#RuDF(Nksbl>p1!f-;l212Q)wA@pSom%bCo!H@{`6`y?od;h#4 zE2ThsQ8GsqXWkt-Z|fEBG2IcNL4aa$?zTVb-}hg)mQRZHO!Af4C#Ls3xb40FQYJ2i z_rV5sUW~J@E+5}3Ea7xXSo6qrfRPLLJo~x-ZEWi69qTI~F)cs)^|`6XC*SpNkTn2_ zkkC3UY(2;KJ)(4+^3(U4zdt8^pWLWgvSFfPP)CG{Hqht*In3b($cvY6001BWNkl@$^4F%cV#Ej@=h zA}YzT>h-&M7X?Q~O9ox)oY}tU%`PJVk^|+`%-?-t>-^V;Y)U+alv>5&cz=I+&!^?^ zy_pvf>B^|=xYDGo!^y6^C zGRLS+ToT65jgpC*dDdvOaOeR&cw3tOy3c+MPkla{y|0-T0C90>k#n&z^Kkjhw+zXJ zP(m|ywmP-ik;Yg;O0e%tj(*@`7N2?MEC1HN{vUSC-94VB+Hq#AoEl0CIU?Xbi0-W( zOoFUT|J8q-oV|OvJS?6}#4V2bbaUTdFMadV2#Y>QD?Hvg`%OJgB<2tZb)~hVbJ>rX z3}-M$%zWzbO`n*Y`PNXcE9OqkV|Oy{UAgm9Jp9doKG-PO(cPD)$qNz{PMWAeEW!&# zB6>uZDo&3s1cUV(w$wzAl>!Qdy2DLkYA5EsVyIsTESbZ+$IE?geDh~zi!yE<(t3Yca9?1rN%2TCnbF$>lop1dWyz%Fe z0Tg&Z>H_XrC$U9Ha?yc@5@Ee!?_Irz|8BVK0z#G{G502H7(NUUAW3;>@t)5#Y%24v zi6goIx%A;yDCJdqkZ%*ZRel_^50_8;9g`6`tN^0}qy#UQ9=&bLHQ(g;^{|X8$cGwd zUl70bP=@4Uj!;@V2#v|1)fz<5GHRp*cXI*EQ7U?1Dc%qWkR#>vfd{H{Gi4MFnWLna zNkX>!5)AEhHxiJp&D>`P?$WpfI9TnZ7NV%d-t^=5Y?-_QI;;?o*m;3XeO+HfHhfP= zia-O4h4R2RMs0yAw4CTg6(^dQn|bJOI#>NP!dAct!N~5`*d%85CI^`1yDviLOdyni z!Z7t9_TNn;%bHsDof3nFRJ1WQ{rESwZMhuC45HbVxbxM;>|KqxBtqMF%hVr#vzY`) zksxB*3%D{x4^}FWGEZrFW_IkX2#E<)s^!B`JU53!K_;zwyP=9?ao+>k<6jx~8Hh)Q zAPJ=l0Y!3m5r75~pc{&rbnx3lRsaf58Y5I-%N>+c4}Edyb?+}Rnrn1xaAJ3Bc%+yQ zE$f#I`Dqs;9}}m;z=Wv>R`%aNZlE{ROZ5aWiYbce$L`)Txd+HH8XVklcK7gIZC^;x zD3y7EgFRWHm9P|bM1vrSfN*z%3JD|-rG_#DB_YlPQNXmT6QdWr!PpTuC1BH2kN!pO z`w%=qJ(!rGbeCfJsoTek>Db&}xiEEhT{xXuune%ur~y>-(df1?_Uuia-jsN5WMru{ z(cW;J?oZ#}o;Vj6f<&P2*>I8^EOUOJOyFz&9-RD*v_t|DJ-Cfc4Q(5K$1k_eeJ^Yj zn4108m*{Ei(zijkH?RB|IG1kutx-FGP_RgF7dJp*0dnf)bln((lt6BPg3vK!BMGim z_MOTVeGvEzVImM6AszH*{%-a!e{-V$EZ76#B&lQ%SOHjRH6dA6Oy{ZVa4~?DKYDZq zLo46$8e|=~f}Yv@%v1L?t*cTXMZire+ly%IZqoyEk9~dOb+1FU2Nbtr>t!)~B}*%G z11U`Is+BSo00USMsmjJKGF@pTqofy6rTuNvO+k+=z07R+m{8S*RmtrMYZMs>h z4V@Ns>448Xoo(F>&nbX+u;px-|CS*u*};yBpe;f`aV#I&`@oQQSEN}sTDdI;vyyEh z_JiB}ex%Y>9dI4N#FGCPss#Fe6h7{^u}Lmp7$Q- z2;+43o>bU!(ker z!U##AJ44mg4ymFMQ354WH+1(eJ@nc6yZ&&z_e?+pGf2U~28IsU|Igl=N7+?e_rKp= zRp;D$d+yfIY6+nk354bWp$U+%g8=~^urY=Z$02co^LuM$@Neb)Ub2$))_VRW?iL2)%XEuiFWMezC(PcdAnCAvkx|{tQSQTE^uQ1HGBbDIJg&kyktB zN{AuSfMxQjab~pSvsunsxKUCu`J}WB0)Nyzu&)&R&?J?a+9^TYb>*A~9S&r`!H^{@ zDFF(iZLZjJDm6L;(f~+lWE!lHW+{@X8Z$`&N&*oAJU#LnyWsvlDn>@-nHd>E5n`>X zruwU)s{*9~XpZWt$5|*qv_NsM5{5Q;swgx}knxMtla|zsn1WO_Qk9}pBnA?(84*yG z28;k;ZR1GyZp*F2yLJ0Rke8ar8TI>5{V2H_Vh?ws!&pr zt_LX#j>y!G8V^eKZ}~MmYCbRceNn|w`4Q(ygvlpihVe(vhDwoGVuA9i8ph_xq;ZZm zigSUtPVN-p(zE&l=9Kp#Qq?0Gp)&m;oG4WFX7=5An^&TNf`Vm5U|&fwqZ&dz!iORh zMSQ3#T{~9t`%wzM9QOrRm;EQDI!_2Gk2*;G_1SM)_;neR65bu}`D{|GF~*+p8*4uN z#ngf_P*#ErOb8rlwQ8pqfJM8vi5K@$` zcgJ{<2q-f7PAx?UpfrS4X=o%;tfff#4oC#Id0r`9Q5lgI(j@PFtRoTtOk_!W>8g?- z5Yn6(b>#|7T+YfU3Bd(GgVK>^!+k&nr~=H21fq?pExMhIcw0cUcYu%!6G}wlsi2VW zNFgi@*dlDIS&=U)(ne<*0TC)%R54YJoV-#^S`H=FwY4ac$SEHn59IsKPX(Uujq71a zYoUk;9HdiSW{SV~_ZqIf8R_w$s{SJ*J%WhzI&mKHhUr zS5{todR)zi|Eyx#oq~D_+j6fxnSbe@N7*(gVnwy~WcRwSK{&nO9(W`DMZaIIO<~>F z(%~r(D*^b|$1({5Jh+{9(%Z4+@#xq_4|zlpnUKH=mTVAYJEL5OkwS_KHMRiDq;&rY z@_;CTFX|#BmVjU~00|-jfmnrEL+%7@(kKlGOwdG1tg!{KxB(At8I6<>%3^rFcet&B zilJeAkdo3SSqtS0AtE1?@l_>6St1Jb4I~C^fjlIH^dYi_d?>55Pas7GBI<&osK09n22rutC*qV+2Elr1 zhGvLp1(gwUF+LbVN(od&|LNpThm8PI$`|)ZtWg9GKw81ORIe~Ilr}7Y_JsORT@@%| zkdIV~K?;=-5Fwv3X^RX1f@lv#7!RZnDO}_koe96)ytiV~l0Kbos`=1gR7|@=sFI@g z;M+v&W^G$?|uwQbq;Un8;J;?GBBTxRYc3jEW*`saFk+T#Fl7Pt2_r3#Ug>x}-SUeH?txfa+I0mIsSpst($q}=1tB-{F#U6_;G&(7IfCTvYEksB`fP&T;=_cFZvtjZ8Ktw}pFxV>P z6MLYLB!KjiJxhp6>y0kB?ztCw&z%jtb;^hFQl*7Vf)Es27gvwjg`6W0g&GQ0OBbSk z1&%$KGY}2&!RVh|8HmzhE26_56`71O=+t8yZ}?L)c4efh8OB3E;nARF^Vv6dczvzu z>=|ussO#+9yX&Xj4pRXr5XpPtkp6bfLWEez^HZf2egZ_4jTDqf|G6Wj<$nPL+=o(p zA0a3Z5h8#gOwbrqXHY!Tf4?!0nNRjPQ?tV(xeo15^Dpm zbSSL&Mn@!qs2vpk+9=)$mjUmsn2gA;U-e*m(jAZ{lGehGM{~dW*V^b5d+xwrrNkOU zAX^ch>RA8XYRXhB_!M+g#r#hbPKB>L?1@;VK*6s|tU3aK2ry8ZsF;!-!&wlFoB23* z11Jaq1|*@!T0{v19BB){V++QxacqWuC`-Pb5CCbABKEkV-jdNAP<+G_1k7Yqgmjo5 z4H$qFm?RAIy+$HM{Jv~VRRCPOOIV=Nar2z9U7hQ{V~)M-BNr;4p|pS?Le5`bF)0NI zTMbLat`rx_MY>#Eg~gC!=PCpt9Y{KrZh(0G23f0YPO$|^3RZ$HB7ta+0qQup`FnJ1 zQw8?|;#p;N01(6g?!YpT5a5NdUU(+WJuIHpo^D!@nzRyBVZ;J?6pyw4?C){pg(~zB z2N{G+=%vwAya5JQDRBp?QS@HxFQiNa&r8>oe6Lu5NJV`lffn+FrPxIj5DY+&LdUNj z<(BOgw!37-BodH9V3pOZMA9Hul(!^)uCpdMPKM>3oIyGeafc0XWNgtHg8@KTw>KYq z@tj1f`RXK1J%k6GX|i9$&16;dFpAYtX1ZA!x7yv!032JqgBNrDvHj-@AM!L7O7 z-zu#CO0~3$$C?45qDuQ*R5r4jv_?a8H22Cs=cun@?q{F^o9*z~tzjwe*F;95WVwkL zKEW3vDY>hNd25*KKk*8s1AWQumLbt(`<-FvqO@-%Hrsx%!UB*5b&^Ba5vdX62KxW=Z)v56oy7zv6! z9k8KN7EnS&RmOJg^!xT3L`oP?Agh>|QlU~tB4srjYs3_l58Ig-CKE24_cpwMCxvMD z_j+Ib$Na8;?Oy+ImF+el9TG(zv81@5*aA@6u(civHGHb;)&HH_^}U`~zRKHwTRTA%l`PB}9QWL_=b4QXvs9B4Pzm5U2<`0@#T2_vv?Q z>_4EHXs9WZ3+I78#ySp9`-bg z^haBL%Qv#@A%uPrT6X+l|3uXxbk2280;sfi1k9d|vaN5z76iI zn>SL>$fv?=I?AP@g5i(}q(Y4>NzVD0ye;j4&l}KZ`ZG)vDeo=cTu}fRV##>{J(MmC-Jvj4PO3pxACdK5|4&h>MKnIMXC<`K1M%;N`Z?ED0<8)>L$5<9}OB1BxugIl_x0;(s$kWxMP&3j6BXCM4{iURSZbP8!HDr5PHqg0mZvTxoh47q(r{l2>b z0tx53&Y;jm#DGLl;*Y5tca4&iwjhcKfnpFV%~>rukx;}MSQGW7ZLfN3#hEZ{pMUF$ zL0#+OwAh4ScivkuNh0;d?)1(_^($W<9klu!5L+UFia;Q-Ab|o!p#eMBzQ*9BdFg*< zwtc(Wc7Y-wgvfiG&38hi2*O~%I`JYQX^@5xRGJ`=tg?VjLpkdkgZlp~hunq^3so8k zL6#7Za6~>ppn{SJ&>#abz-la@tN1y-yGuwW%t z_e!0fwu&0Af-odx_K-jA9p7N0-jXPFPMhvFKXG+o&{ndC=^*4kfdz^N4J367(y(XeHwLb`s$l{sG8xCn3DO@qlk#1 zx=kY%-IHsW86w4G`s1g&O)gBrF1L}}x6S~>LWu%MyP?)?GJ#hUOSzpb?rxTrrNW@il9Oe6Du%< zzG+L3!lIA&HOwhc21tq0B+R0qQzVL15D`I?$66x;5NRl4VTdt(W<+K6Se7)9A||l4 znn^r}wu(b9fY^SEY6LMNzK||gV%+KAOo~+$nF82=32q~-S3~3H7gUXfAr>$MDnztCl$d)hWn^xBceP6kdTFR!_?G_-{~_W6-0f$uX%!~}K+ut$IvQVa$HcQ7~!;F_Dol0;fFTZoIrrC=dkTY!cVQOc4h z01>BcE_Y-TYz|@pW~!=P_?h<1xX|+;1agrs0udNiIn~fJWu219h|1H`Fx5Cbuk_yPUa@Se*b67jg;b6&ep<|PL7ueZL}68fC; zI8VYuU3s}+gh6iShZbl88!3WW6awfF3Z(lt^hv*D9H_4LGvIsoClewlB}jv`1qAF# zai~{Q6e7Rt&EnB5u-zJgC!zJ4>Tmo)&xl2RY6O!<#1W~!bW_i$`D2#fD{2%|Wk9~- z&ccCTNcMy%C`Q~Lm^^`B65A3e>u~Yd7HxY%r30mf#x|^Ys4(@;)2XqcQ4GagZKWdA6>XVm-s-lj@c~m|FUm^>=-_bIh_Vs)UIYv;njBw0pMKM{tapLs>ndW=*V^+ zE*@D2DOmDCN(E}nh#Ma4n{?Y*-6W#?sGw{yRO$B2sMhpVRX07PSN=uiC;lm0vmiya zV$)z!BF@h0VpIy!Kn4PGUEN7oXc5>j5B5A^4@G!kR9|24>mtrrTS1~hN}wv`Z0?Px z!DrmI5KEpKwP3{R|Km(*dR|qFFd#zdg09Nyy0B?U?Op$%Zu)z*^1ruv6U3>ruo@vG zHgx!cDlWYORK8>)B&>Xf=8`vaNU$ZvH{rK{_u*LP-%u=|V6_4p7gEdl?h=X`MqmKN zB%*9dv9yO$;!J#ZY?#NVtys&TM+Laq?VvbHJiB&@gB8-G6^}`!m^N z>!|0jKcW$nZWy=lW2l}^kq1qd1i3xg?cdKJC(hy~M56oMjhvw~0xT#XVF*K!i#!cs z4GStoB@l#w)mD?$AckUMAr%0YO8W&Eq6k7_2$2g zZuzVH-nrSm&)V)2y#=bSzfz5zGG^f&RC6^{HL6FB{rLare*C|2SFaj!H=YO-tRiJS zB}^{ZAyJ4TRLT=TtZA4w`cr?`@!C&{N1Hi!+Emxe_(kIvd{R;qJP{%*iYRxoxbGJu zqb?AEA_7%&F8AvH_OF~R71J#=NeUG+n(p|2diK1~yZ43g%%MQ?sk%nfG<)2fTWI8B zMvbcJY+CdA?32goY`3tNQpMimTWY4>B2ptjM0J%@ZfLr>tMJ;7yE*`ky+sKhf8^2YnrWrp8>kXw-r=1%I*;xo=|PWoLhQ&+OlW&`My$}htYjlfi(JX zdP8pi&nm8YKuNtK5i=@1?GxihOzYb5M6Pv5(B0#CHR-1DH0|aQGd>Je3+30${a7k! z3)X)%BYBV|xJdnilEGW6(j*ej#Oz!MK;m|Dc}_Y?1XzIx$*+qCe!{yU5tHu-0hmOK zCGlM76GEsA$O9tn6jC7&a~!ziVxK@ti3Pi`J1#Pr2`Ozz4K*VrC7kJgtz+Fc8gBe+ z@fr!5#i-hAM%*|N{5c?m(x3{*ADJ^0&vx~H6IF~uQtNZ0| z*RFcV_>&-j_MyjS=6*Uedkt(b0tV2~UZm^6%5ae=;=T^Q*fF$>1f1?KVB&}ftPv;; zN*O5Y+@&M{6RM`yEt^0~StL?Sph~D33#G^sB8sxvZBM1oyx~y<<-6^qnse#)*Sgk! zyY7ZRGX5lx4^#wxMfIF}XvS)gKm{Nlk_J^+sbRn~5f^*ILYAPwVkpQSe6fDv9jICW z_(}|;E`9aK(pUXXjc8GtAgt=dE$F%3fMAGZ8k&k!3F-qyBq7|AI6|;MrNJrUlDREX z&I4iv4TZL2l>{WKszvwMTL|x)w3Et^fy2r=lN>hrTR$t-&i<|@USwP>mU z)wr0ZBBV4~fGE(}H~Kby*S_&gMgmy41=UDfUx>AVB7g#T8qlm5DGSdnAsYlBfK*7n zb<=4Opxwv}Wue$PD_P1IGJp=Cwe#B~!=U&8EF!70ojuJz$$3(-;t$|Y7O7%^C~b9x zr#)qfDNu?8o>kBi$jM)}Kl}gI70wKEsu}^>QbaBsplBf#k^zLoGqB(w-+nArVN~@v z<{IU^LzKIi$!$BaorS||bK4&qY5M>~8it94?d=9|001BWNklwf0T)rB^T5DF1=gfz&5 zXpjOyv=xLFo*~6x1tKo`2z!ri!DA%w~Ei;TT>RF+-zEs6+& zfKnnzmxMGT-6pG8GAl29A&r-)+F zJ;lIwJuT?CV@1?yzSX1esYc~nA(z4IZ6V2EjXWp#G)3GZ_*{RI|50F6l_)Wq3ZCmS zr%z_tmHE}aPj3VWk z23qHy(`^31qM@tO-|}S-z8j_-S-$X00@;oWi;X9~Jt&cq!GazkMN@ndF-u1(<%bVC zx!4`e$*&KY>L(jrzb%@#{OMnBH%WRX9!n#Enx22Kbz~K<&Yd`URn}mAqY=YLZP@Mk z2<3%bqdIkvNV^^BAGBi$%LS`}pKGhz70Nc}#E=bEz+F!P16De^Lpe-++5%fC?b znrF5DttnmiDJ1}2W+gYrI-EFGLDGZYG8dmR5&-{F3f}m5F?Bhem#1-$Us`-zAZdfR|N&0N;(D6BWEv zn{a(7SLS{jAuIh;2bw-z+b?SIX!XA-K35;THm=oQ?NOI~kLKeL!@uMIdepunacdS`KJACz zwZ^m?-fP(!nPz{~FBPN|krL@lwHt~02PbcT;VMRXrD=y^_1=+4DK-+&FyKdZAA^Y*A zBKQXl8zI77gW|I1nkDJ!HWrg3CD*mwH4UFV2wtQ0Y%<(Sd!b~}NyVC_EQ?M2S-a(2 z#|AGT6JeDt;}esereh^qNcQRF{l>M(%YIB=^u}+f8U?8N`KX~-5?%d$XDSJ#ERUkh zqZ+zeSX5Kp#ZMnA(V7v1LK=2}No#&j(E5gA#L?v*L;Qha${(U!|4 zbCne-`BuCu^@Y@^dg_dr3bjG7zCdKw-K_l(L7hWM`i+pjyyD7t-9A}7-MlIos7Hx` zb`xPrTol6flzG)HeETv>i2|Cz2uOsR@}~NVycsp}MezP;*;l&S&yO{;I+}_QeMKv` z_@f0*aY(djy{zfC53Iubu&w1(vf}(y)e5^tMGMhRe%&DHPM)8TYFf zT}Z>v2C7NxgOH!rIs4`H=PvRuf}RD?SZ8R99Ynm^X%1?Y%+){4Ux>w1Gl|W5^iFPc z{>R<^=m!;%=vGJt+T{11(r{4KtoVpJk(6}A9%$)h$9RnW{hnu2Shw7w+aiAYBuK(m zecRaaIBca3m*{O~8710;vi2i7#V7}tx5r;a6@Oqld(F`81dk`?b~AMJN-n)L$D|DM zD_*qQNypzxPOdx7toXyA?dy=y=BI>RH5elt-ptnAELGSX@?;GB5>5!>Ib=Zl@{q`MH!8_mGnhs6Ro;AYi}e3_cVE}6zuzW*l{%sbU2(poTUZTy$z9}+d{U+E4pD#v7~xa z>E2n8`&h-m#^CKxihl|$zvS4Oqp2xT1(RT<)eDy(=*u9dMzDsyWe>3BFLG&AXO{oV%&&?_7I-Be`p%zZmOt<;Aiz||#iDQw)Wivi{rF1}gKOATF@*34nQ(H29hnzRz3psl`rsiA zRO~nj!NjUaUA`F@42{2&O6k{}C0m4Buv8SqJu*HN;KY6U&G5{SotpG(!oEeHx(sy> z7Cv>tono5cAo24{O~0_$&BlMG3dX)=p%x3j4;VX}eV5^sCLi-6__fNHdC?>>S;NzX znT-HFDIB&l8@Fz!c|p`_BQuRTM$tcH7KbU!eX-$xGp>dD?3;AHBwjbppe-%--FrrY zq2e$@M49rw&E9N?62tAqypYq2>RXqNcuf3e)XCu0^)mrbJZP7Cs=w`rC%0wU3M6|PVW z9*llljhHE%Lu|lICc}Ov(J}WYk(FH~{^jWdYV4T%|9x5<&OTI1zr6iaS-osE7QOxa zDyBAMr?~wXyuo2hjJ*A@GJZ;s`|o=MpT*^9sBb?QvXnHqIJcj`r~l`G(LtR7|9uRk z|NkBCF)o=v%zx*IWJS0uO%wY+S0-j8eC*#R_CFV75*%gR`{I8ti2$SI`s=~}{DNJg zA;SOt!`FZR*RPc93a;}1ccIkU-x5e){9lJd!;`nd!WJe+l$p}~?+&~}=*z^SmEg_c ziD<&qC9nVI24h9YBPkkj5GWG=*Bv*#A~Je!US712Oa0X0zag5hOxUkbrNs^NtHZne z5}iHz-@PLtJWYJb>B&$hd}hU7qW0g>E;$#CDcRy*u?kg9{m)f-&c!NVrO0uRlkTtl z&zQ}cev!*Ew*Oyu${qbY@&99d*>)r{C~1khaq)ZpONQOv_w9#=hla$&+Y^F= zgS)fjcaLc{wzs_xdb!X)`0ZR6wTC`=ZrU>^_(Vd{)wObjH%=ydXw6kGvg)I8**Ggk zK?Qu;wFj!^*>`40EDJ`G662KYZ|ZtF-eU$=eYV@9&|6^pSzD_m9?CX6Jj~o2QggNM zj?9cQgUj~my0SY=5U#UF8y={qr^iC6xzSzsAXZ6U2}kjfgw1O=GHMxD*OT~$!>0&& zY6Ue%BU=3)UhZkkkI+a+NDK(h8)gRv<}L)WA9!t*jmVM9sE*Uw#*0HRthsTA` zWs49()!A~gZS~S;xHZF?nwsm4#%s%+nR@$5q}Qsd74MGL@fg%%_1ypcj`F!V@iANG z+paQN>WW(>bDZ7FG;)5s*dEp|c#*HPj;?q5D^6Y4o#IHD?vZ>dKab56H*ImEhIlxc zkglGdo}yyZ@86CCLMCLJ~1#ng@st()S#S!(ilk!-xI zuIJ_468IoTAuW8i+=*Af=lXIuPaQt-{SM4r;;j$3#GMwyxMm!>ncVdLGOVp2rY+5U z>`msfovMPDtmBSoo(N(M!vDmx=n0zjec5kScB?n+<5AXJ9Z%X;%?F0m*OybJ1_k9; z;Cp!ROIM8%`S`mlv>HoEb!0{ezT5vNJ*u*|E`o)$F;XyD8BeJOlgaWXoanLhs2#)j;q zmNm7tWZtLqeSPxTf2-^lKeOn0FU6@78p)>#);q5BSx^2%7TKNN@m9C=+Nv0RSWW3f z@wse%WT3wwEmhsax$|*In@+7uwe33(4@_ciR#<`aN+AWlOZ4$8H+7tW&C8RH3g!#O zH;Y2oi@jX4)EUw-G}BH)uW)JQ550uqyXzt1Ek_Fl-1q1#-BFTEh8^aR=lwB`n-0#hJn*}cyv_Hd06#ex!2=xP|Ba&ZL_4K z;k1oKO`KdClct9d132(XWtf1u@RkBd6+1B8M;8-m=n{v|WUg1<7qf57aX6<-EQ& zr<^Fah*5gG|F5(AGn3J>fX6YEjb?W`vi-oaI~e0$Mki0|s3eB7tIhgJ!ldKTrB#p{mYJ5+JcA=2|_+H zh)#)p?_pG*aldzx)?C@aL&i^OH1J*J8uhRrHKZ*ghRp_>vB5+*Q~TwMhmS?KUsY2t zl0VQ?A=qub!^AGEW-t)KqyMI3lELivp9e2Js~qaf-c{`$_V<3sT68ckAlX&k^rW>t zVQnIpa6$NsIa9darb?2t^M_{#vB#Du{tu`06^#G_+1YQYE3q|f}3+q(1qnV?lqlZUr#m}0WcbBJoqk2A9zoV$S$;x4+)O(!Z3?{v>MEme7 z@Bz_765hYl(_0tUVLQq-aMw#H zn9^+C?fPux;H=K$B;jmzFjMGq9`lY|3a|6==J?VY8LyN1#qm~-d`j2KL9WW%!_`5^ zfAz~rmN1RCe>wTn+Ws>OEjm zj$k+*p?&!A<41{62eW3m`LBTcd#A|yC)?8~n52x4Lzs;|Vv>2U=jH@`Pfu^IcD9-_do0K2*^!5;X1jwob0DwcAct zN28eA@(My%?SbYOiWBg4d-uVWn~EhArAp)tGYhlKp`4q{y{n;axUgjvgYLE zbh)nHh!ZtlW)`m{sm+iE=Z546zgk~kpR4ktq-1ZxvM2>sY2?Dl{kp}D2-}JBwbQ*t z5v;$%!@UV?nVuM)01aI?$L0mAa`;cC9P#k*Ml`vGrKy*my3CI^`ZRjP&_cDWaxi*o zGLo+WBh2s!4Nd?vkbR*l&dbZ&aMo*^3dMFr4qhJZb-Fu$J2`!PJK8_3H1c&wg}g6^ z)%EbA?&H$%pZ$vw!D@9V%+5A}^pSS?k9MUu`Q&QH=cZy*H{`=%;&Zd(?de+k#rA%m z>w~-es0*Xca7JtPj(B*B}1# z)#taM{X#1gO1Rj?%l*asNWK7oJXiQ;*ETjB@c|Va!vcj^g4Y~VnAP~_{`ldbY7cO6 zVq#){n#A7yX4&?dW&{g{#2#kBE!++MeY7!(8@1vNwZOpw%6SK=N6)y4(Z@=_D>mb$ zOY3<>4Lc1N?1?=Wua6yG{d~jdeRDAj3(^JIUiJ{)k~Rx4soY|S)NSt0?1>Rs_Q=Qx zz(rMhRXx3%p6C5lZ=IeC#qaMec96OMi_&Js#~j!jLQx3R)YKd)(!oc|7*o z@E0kv>+8L4uCKIyTAj53xtN&H#I_f^)AbqYr|qn~_{aoshed~(=-mc;dh3{!loXo? zm+GfhzZqagBd+nA=m*6g1+v8&wQ{5V;H5kG25m+v6kp2A0PvmNG$NG>wXRnVd56<{~0h`0Jr;3vSnr9863uEC}v|T;Rd-AR)>BR;R zB8m*Mt5qo~$pH-wo`;NNTYI+<2aC8L!|2_$VWfI&SLb1!SNbvrigWr$Qo@0LfAWMEmLt=yvn2he(~UsI ze|^HG)wYN7ipx?`s9n83IeYEm=y(9@9ai+(vCOL^4vVf>`f;m~UZM6dqRO|!Kjt0W zZerY%?Vd24Iy7PESvxcZEqc&Q7)IWP9(8>gnHug*7$ zq!jy@`y%C5e0BBlRIod->1ttTdYjx_F&j7tgOlBADi!On&2te5MThrTM$D$EBd*Yya9BiSe?>fE3E0wB*Ttm*2UmZN#@ zxGab54)y34D{-l~xKlS%B{$D|@2wrUaaWJ}>1{Uk3@grB5C^cI@VVqqhD%?ieuhc` z$L5-`5hB~R-!81(Y4A8&AC@oQoT%8?(7QPYm1$NGkR9Ya$?E(tp;mMgncV#&W?FTa za#eMm7f`w(RxNykU@~%RZ_Ba2SxkK0ru_y!+6RChAfyQBMu*XM!A#u63Y_CqWjxGYUx$qSOm2 zsLe(J(Nj>J&W|=Ej3HV4x0u)vG&dA{&WD&^&aimzh3uz&@4;KGJ0G&k*J~6^?tPXm zc)b^a85B@q+Dn)t4ajJCxE=XhHB97fJrw^tHg<~Qvx}0e(p6E_eE^3}p@6(_Q#JI7 zM4ZEU{V$PiT~v^!mey^GO#QZjQ90!x%SbiN1XRTCi6ibF!KfA?A>nrIj^OOc;s(RB zXUqL*JM50TjKE=FgSd0)L7y#ulgshL`+UJNW)_tqe68T!g#W!*`D zVQFVQX4-PhuJ$xBi9$XNLkW`;6AL?oI~ zNp%g*DdQ&rf_8m*wx=MI>j4N3&=|>nHAuL3Nn_=67#J81&ODWshkSD}eaGh8UNLq7 zT%N3TW$}fI^EvtaF|&3RkUeeYN{^H6TP9;kzH}m=$o|wj@}l$P3bbZOsb|hj1Aq1Fd-;x7xX^2oqYwIo0h9F^X zx~Jw1J0xO9Oxu@ILSl%9LQpt0$lvf4UV&02`)+!26S6?rMSD~59g^(m>=G+ z`j=W*Xqr;C5t+yJ#}-js5CH z@T{Zy;%7z0k^@hr=)mBhJw`d}|nVvVhIMD`v_AmVpd~?*3glpY3ua zv$=KSsr+-?C{_#>1A*2xJ^d38E|`;&H%?E8{%nbfLk<20GQZy3KbDcWn9c=c2l_VS zXK@bxK?jB%uYXFP*lsST%I9Ryo|LAi&!2ejoRE5KmH=`BtYbU*vzK6zAODud*+}uT z*dOSfz7Z1#Zfd;zMlZ%YFM8zNOr86!Cing>3NY<2h;Hx43BJZwU&T{8ybmYf==nLg z-@lEVS6Haq;CTi}2n5cbHq+-_3`K`WM-}Deo>!2e4}@+G4$e)gJT)yr3)%$@#?{58 zU1FENA6cs^5~HK8yR?H#QnB2iB@o#51FYetSDqASol6ePy-+*Q8JY)Lbu0R0Ut0?N z!>A=z#{_Lb{ySzQ!>X-G&LS?fk+_&mN4Tl&D*zimk^mJ|$Zc2O-T6}xTYG0JD~3P0 zUCiA4#6-o~EgELUNZ}@SKwO_nkT8bC#!nX;UwfpO(s+s@lpI#aELzn_fMenBi9ObxGWAW^I^EXb`wXkDf(Xzk?$uB zI6o#&X3l)vuKCQWYBi}Nk=jpAM5JnJT(_V1^BGgI%BZ!-o%7 z{Wq5i^78Tq1_sK?G2>Q1VG46|s{s@*w1ub_YV-V{LX&!01tepx$busY)*JyF4Vn=K zUfpYyT8E`B?4K6XUS;E6g|+QvNp@8uX}s@$u^7;sLd_e8#_q-yT6`kQ}p|__o>~b&}$T<*+*^ zGDePmQD0YIZ;&ogpw_bYh>FdJkWxw3d1z_?uX3!kTz4ipu&n zKSMforPe_G84rH{=JiLc(>}}u_#gfVq%6Yi|W;slpMAuDt`X_S!UJ` z-yJT2YXciBEh|$m z&~#3dexA&lDA(53=0}cn?_O|ta(eV%5IhtW)7A1UEiK10;;<2GmbbPZB-K|`4D+7d z^SIvJusl_Fc0PiFfhC%!UKIG^ev}m4OHNKsNJ#g<09JBgc{TYRug>mnF;sUqw|vbC zv_iWc_pHqI&8rgC=7Pyxx>}4C-Y~YF!>G#U_l%8gQx*}qWM!Er580@d-^*iC7hDY$TBqp)~2(wvq+~D=v9z7q{yE% z-?E8j6wvJ3U71<=D?Vg-`?AX}Y;L*yFHVA!1gR63{G~0&K zSvU@n0_k%rKR^FmjI>oEUajp4DeFXo^2ht7C&g&LB`dKWDRuMW5CSNsi0R}GXJBG7 z={~~}^&?M^qv6|n1CJFRexBSbA|m3_j;`h6 zrO4C9cNgR(w%<4E{&@c$s|)De{QX=U9B&jPMStI_e2q63jkmhq&Vf$s{gd%Ns=yMC z7=p#QIel)zl5gMcaVC^;HT9eU;h=aEu%*mO4RfJc06^dc3kxQv)E_gp_N%Ka@`e#2 z{_PljH${2*hZN%JuY?Hln}wYH%Vjl#>Uq66l?Ou{*R8Z{4S zCWDSZ8342qXs`Py_%+!r{$v#wv!<>XOzSu)*w`?8ny`ZDMouZp%J9K)f**VKL$se? z3k$c06ofv|n+oxE5R`d;`uMTqGm{o~_|Te@R5)>ZUgMk%jwq4{x5qJKZ2LeC#=X15 zcFp$M4i5WQCx?Y!1qc}g<)~N7KZMEM^PgWI&ZC?FDJD211ftO6_?LjM_u++Lw=0=I zXkubk)Hozk2qa^64Nc97#+$31jEoEb9+(dvyx`#Qyx6L|xjtzW5wV@Ad-5m8>ALAG zZF+k8R+RwIsedLadOJCqgdO(R*VhdV4dueVZ|_(dVK~@2lB5d0y!y7iZ8N5VPeSr) z6hS|X?Pnd8B9ysX&YYA)2oL3bW3r<$LrKH+a8E-;ALG(`~hS^iyA7#7`?qN1W*!v>nKS z+|h7=G%R+XYfo-}Q>g5afNv{8Z}003bkAjOW=6*RZ{OOjILnA6#yC5E?x>8iXq09_ zG~M>ka@WA`lEKt%TnYu1`%PYDWxTT&(sCv)qcm|x?-!nfk`B^ZP&b>JnxHiiX~M_v zc~X#`E>ip@l6k%Fwwp3l6_uL0y}tgIm0;CCE-EUjxT|UX#WT@Zk8%<+vNYfTWJ2EB znS;c9E*STI>$CdaY%eb#hv;4UrP_8lMDNGE@e=uT&rT)PnkJ(Xu#C@*_n6=f6eMmQ z9>C`{j#QMCexwdeGNwUp=$|Ynq>5cYB7oalTU!InG&nf8JEyE50ERGSn8Vk%9|UKC zL&l)iR`Jum&X?^EHI_>&LdWaUUhuR!2s2D^J`)W6>BWH|Ir(tu?qSkGqFL(MN#hM9 z1uvkopc~S~$&`&_D1H=82N|aW?(zFyMWIg3oMy`UBa8>==>AKSJv}`@VeE)cJ_=nn z!*9xudeab;nMbQ)>FVm*HeMY=Z>GMkZj7EbrW4A?0qBR1*oCY zdp6EQxx07o(!@yTs`RA_K&F}v%zBc{lrGP_@%&HK$nt1Zu?bJ`GU>eAGq98m47|Qr zC{%eiKUQ~s{*KcgrKXccVQj{r`&$qcW9TjYnl~oy0N)>AYS1R?_PZD!K77bYqJZ}6 zi`3@I$_ex<`Jt11cxcVrtgWLH&!lyHdtD<&Sc{5<&{^t=ee)rl&Be)Ss=`topu&R) z*7g{hSMQF;&Gg*nkrUz>vq?11ifu`1 z7&;0UV=+YYd+FDcM>5$CFsjhyeKw6LZ1U-Lw54CYa&~Y4K^;ApCT_;?vp3Xll_Sy;x3eeUUO$Sh>PWK403|*pV)YaK} z?dToklZ_48anbuk-eNGq#VAAQ1B(+dR$jIS9yL;-+>Z)(p z6R4HsIsxNRhzs_RbVBf1CJ@v_mr7v6#+s)Uq zxU@Ey$@9y~9L>#NGRLDJA#wBamU0x^Bl2App!`k(ynQ+s-)X$RyX*UWENcV9_-Cvqvf60gj$~5qeRsiH! z@PrtK^2p<;zyo28I$Qt#9YV2wB8M)DYGoiL^^qJE)(3kcMj^n$qmvVqd-vqCR#*Q0 zyTykAk+YY}T;Ovc?W8K+y(h#IZuDyTbiT;vDsRv{G$O(W{6b`$!@g|WTU(%GKsSE} zXnig%+nu6iZuz27aHRaGs!CYoeuL!&?pHrQzwLQT3aG9=7n8F+rUyn50>`I|K>Rv| ztWo`TK^`Z>2uw&wz}Iy%@S6>1<0K4&?ltITGTBNpnlFn#r9~(HCib&R;H{k^?U1Kix*MgFoEJ}PzN#ybH|1avE*ID$6_ z_0z_u5;o!vRW-Gd;o&`gGGgM(&9eS?Zf?G`ccPYl-<4UXe*Yx0L5qj@dm~?SMZn)E zYfDRi)!p=TiVva`$gw#2Y}=+dkBF~6ZEJrdVp=WRT=XNSrlp0nMiKCtQ3LS??ZE?` zDmw!Og?^9@H;3-!$`49hM2uA@e)E(#DNYn~quN5H_y-nZ)N~jgWE7N$Sp1e4vsc4* zDTdM}xuvrWy^{GwWiBQGocBIc4-E~mwQr0TRUU6|QR_?GWcUQP$Kz1FMX6EK(RpCs z4yYN((mW-RzeuxE3p>$r}qT)3#@b z*kiI2xA-WGK{GS5;!M7}yc{qOz|#0scx5KVaFEfUJc&@R-w|&8uh1oFMtHKMVHP&` zHMV_oyceM1@r-kejiFw}VH1lbNsV*-A;&Jig6f?(Yd3W(D=Tg7FTJB-X{1ISA4p`5 z18ZIMegzm1KgYJ!KC^X)P`hE>c zj_Ktear1GEjKtRedWh72@Kp&IHtAC0J<8>&xE0qp_o&@s-lX8*iq4#fmVvQ!k$jX7^SOgohT8W)8)? z!kKdZ_An>!&Y3bIncpt-iT_BVyJ)GuE)mzIv8mAB?p1GtWEFQvQc}{EUb7VW zTfhv9Z*(;0HQnXaRZL8bSxAp!aofW09U6t^(AuPiPu4$cMXj3%aYauOsvUicecEEF}!yG#H6fkX+I zv}?&hNT{)@s_Of9I>oA8z`aSm%Roju+S;MtNrzM(D>68LWW6e^mxeF+_huqH*Tcfv?EO z1e2q_tG|Y%P22$u1N~4F_Hpp0Ay0$C{SXzkxZ_;_mZ+GR7`T3*2{!T{oFYGPiJe9@ zAvMB?MM6R8Xl+#i+6pR(v-6WM$v7FHyeYA<)bZW_NjZSy;Kre9L5~OYl!KN2G_cC( z=)`~onj}X9=my496NL?^8FZSPBwbqWQDlKkQCwUM0-Zi948`fi1vISEQf&Srrtmlh zVj({9gFFK_rJOkkMku>xKV(J$;tnvHnwgq{xGIX0nUR5mi)+9Y{(^(>m5YlDG=+V9 z8vXtKi;8x@a!W-;CE;-KA7h7zBZcctOw`cT6=Y>CHtxj1YJ;Qa4Zo$O&4KY!yjaZX zfEIP{fhTO3l9Q8z3=OGBRyL&5*umcZQ*f}1jEsYWL+N6HOoD!Bb|MmONkhYR)BC%c z1&;$Y3e-RyGd4B`%i>c30iC?zXOGZoa34|xuraf+2-z=+Jv3lgo|(ZB(rQvqw>qK(BnT`6~!nl|A3@cLbphu??awq`}IZ)_Cl*4IE<2ain< zrvs|r0Q4H5qJcZdn2i-A7-VE*7zVv6JBsi7zkdA!smHJr=j8CPsq~A#e~Wo9H3ibx z_tA|tJ2)(SpuY%yqv0A6*cc+h?eOoVq9S$}Etq1V)6LG#uIqE<4C_xZKv7rM8^QpJ z)M^nqDv)syTY;y7q!5~!NfmbcT{)T|Sobh%*49iMh*QNwAO8GN68#No2xuv561;XG ze8JX@rKKfu3)XPBQ&?5$>FKc1z*v?Q)IXrz@3Az&YhW%$+Rn|uAe%QF_~8T7n_^!q zLskOz=g%!p=}<*YO=;=`07iu;29#Kun{(UE`ATM{BMuA6P(Xl$ z!kB{)9UWa(M#dbUmya*+zgC6o*2M7NzfNGygj?Z#12+O!k(--4!cpugj0!jnF3+Ag zJ|SUseZ4DiE+Dcyo~5#)LWBqJskgcLDzImWNvCiay!Q5XNKr5oBJ+3;j=00SySmsD z<@WaW=31JaUEVg;LhIdbsSEGir9x8&wi6{Lo`E$2^mZ(oX+#VVDYGOq-z&y+8pEUk z!J40+?-#nM6HWi>1rYI$Vh`k_!otAH*#WU*GM1vFWL){Fq@JQq=YyInkM~m6fh=h_AWLH=u^X1;fF4 z;M~1d*T87O+>T^&f{xF8cyK^OOw346p9{vK@83^hvlh5@4W-odqc^8(D?v)uSYU;Q zlG<&%XZY<4xoE(-$`6=hI$>V=Q}{aUZ*8d100SkhT+Zrt_HzJWLFVvqiy(lw+8OaC8Ba* zp;cgG318elI7ol<4qU#%{*8?{c9?$rz$I^)Mc%MsDD1|}$jlVshcDeKgc~{Aua;p} zutl-4u_21kK8O~mtt>4;3lsdxV6Aw;$%!KhX%#jAJVOMD&KuP5iA&0 zsE}f))$RwThK7`UVxX)}kBkJer9vYG?{4Y}STOHWy)bIWAP9malVAf0#^RkB|3ockfLVn4FxfEh=Jxd<;QVTYCX6637=AL~Bs_ zaS2uz7NVo0Z#NJ@`CeF90GnJ_R~I-^hX^f<*^HoSk_Yd)U}9oAnN9VCueGk0L`UPK z$ggZ~r$$E3t*woZj>ab^PtML3spUbV=r+@?`L=&VGBTwRnW7*OLDc|Ta#tL~no=uN zNujfUxZG#R+aG;kT)`gTUW&4HhRf zG&Hak*-qDh4Uh46<1J#>*GDj_gF}HgN7O{d)AJHW&$2IV*W%ZioeGPu|&1FC9jwrA=m zhlX&uabYh2F)?^51ZYQ(uC6;jM)QJrmQ`&JM~EmnJDM503zNW>M22BwGOu1CC!)NlfpU9mYqUb}N@KqPi|2nmyVMovH}3*>wba*#KFmX- zOK|Q71_U6);4^C{#KauH658cYOG~rukQ3#F=6hLVV`Fi#9oVMwT@jn>$x*=ql|6L| zEEQ%1TNJi1YBqWcyl{rZ#}6eBDuA@KG~_HRE5^K~_v#Qg6Qw30F)_FW$^P6pMphhz z08GHG%0q|>dr($yO_c-$boBIa$LLiB0ayiU7fbPafQndHihxbmR#%Jg(bLm|(dCx# z_x9?Pn=1+nBa}S4(=?e&r}TrIg2M23z#3bMyp)lix!K-A8*D?c0WYVtbSG%3Tk7O8 z39O+e;zgqy=xAu%;mNQ{fa<-e_^y--K9R0 z@*EZ%ghhId;cUkT2U@i*OchZCM%CKT3w9~?1CvAgfkw^{s0Bu$0H!DuUNZEm6=))t zL!keORq^%~6s3Fi>=^~cduIy)0Rh+!e4hfcB_PDV_r$)!9wD@DY88CpLOdn5F~svn z9roqPR9}b=Ib%Bi&Cf&lmO%Xt8ly`A$$ai72!Cb$eyRI=d*k3|d=+)a9rWb@z0%Us zIZ9DF09BcTcEpwTi;@s;K?(DE09!te?C#N7XTB7pE5E76f}goIK*s^{(oYx}dfx6otGxY*HH zhNw@BjSXP3FbH3S;Ur8v9fdv)?QZCUm zX?+B6i{wgwRcPiv+}%aR#LO8LF@(E>9`oS9fN+F($=M8O9iU!eX#yg2#wam)KLBbK zB;drvL~zW#t(X`c#W#XuuR^PrGZ{9s-C8l;lY{&*bK4KbRz}xVRW;!t#1@4*DHeM6 zo`_sq3{}KC@Q4ixkvK<-VmS!@kzb88P#!!~S9Me8=H|ZD>Bk(t-d7c9lKcH>&58FI z+btd{f%6Rg0d$Gs`&ycs06xFdm`I^bTkHZy{3f3=0Ty;;XZ?{Plg-|xP!>%Ol0zHa~Y8P4gP_xtsJy`Im!h)_ex$mIdomJU$zS+P`>Dp!Sh1uDyPxC^9YWPB_Rd7{U=jnp}fva|` z8XBM8GIevJ2F{vu4L*$PsS7e~)%{3^5 z#~>v={gH98b=~8q_r>kEp1pE@kUkoCn`$BBa15GS-eZmot8BI;v_L{6Lim!)WxM6R5 z^Xiq;<;wy8Hh;z5yNB8U`}mCc^eo8IyL4@Az(3+Iaph;=umtlqO{=01v&H81xL4|1 z8Y2d-7iibfXQM%Hu-qpozTPZV&nvHke*0|FFL1FQ#>?9MXH(rSG4 zC%kH8tjOAj4aV8XpK>Ojvm^^kN{;9B5QCeR72{NTP9=$2MJgyLj0^jl1cCoDd8v4v zdg(6Li=g6tKGc6J4?Bx$;Y6XtN-#IS9T9;_;BH>();2+D>4YaE@7{$9#~**kY5NK5 z@qSDU#UT9$y^q@;ET|I2Qr-YBCGrBXztwyolEY>dlOEIt?+AIqZ0CnoS z=*Z5VJDRpCDnVu?M}1enp@j#ohfW%F4krssXp+M{`3VTeoI52CcF33T$9?lnX;=l( z54aHy4m1Hm+k<>RY@r&Y7@?axLgXzmMvlW#S=kP^R7lQH^A~yzcp>tQM2|I-m$`w)zbIWuei@{?#W%x4Qr2 zo7HG-8k!>`W-`3g#tzgfff~fSXSQq!LnU#@v|IpvNoA#BPS47(UqGI}eEkY-u{{<( zBu*X72bel_3*M|cW_t9CL?1*E4%g;I@nkM8u9nu;0=;BPM|l+z4BBDfU6?e@x@qKt zn{?u1jr- zf`mn7hJ{v~=UR zSwe?84Gn{7wT|<3+wFCP;sVJVG#83(BJsS#hii45FkizRe`!xB$!y_*crucp{dM&9 z!)`R8NbEH`w1$%=IUzyt-i}qb^>-g7s8uo*KB0k1Ik@}8?soOBFGwqgAR9m(4rRvP zy?c@Uf(v%O0=_CSdHMT$@45Jf$(^|P>47lz@-lYYrR&F?tJIGt*5Ts*`x2{PUO_v0 z=Qn?INUyChMfN!XMb}9Cyn}IGOsYe}!;WH{C2AGBn80KMy-T0sy?FW1;EII4$I~*% zdez$cdV5>`xUKB7&>{j;c&vN6j7vRG?B-}}X~XIebTCbP!9)Lpw`&Dz4^cjLCJ7|G z+5U^`c)8==RXz^4?|CQ*8OBi`WzII&6^>_bOX;_ckNNrmHL5aeQ#pw~eQIX9Z0p6! zCN?N0sKOi@XgBE9A7o|{Zc=%Kn8dz$wVjt*l_Eu@%fyt&d4F~@o2R?`N$X6d7dl^G zkYjL&SvW(nJJQFZNBAbUJ(MInn?ULv8F`(*?Li~Wa3Yf`TG|Pf+>{i~r=9nMl60

QR2PdTKl$5RJ}hK-?& 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

`Q66o;q zDT9Uv+-h{WXKO_R!G4b7<0vK!D@0GlhvD*`p^^}KGORA<0@4&0moG5S9vK&l2Svk05%u_w%zgL z$3;a!(amkE73HBir6K(AjT@^V*Fbh_fMr=_{kuOmjMMY=)&HJoxVNH+$1$t%GkJc# z8>6=@cF_q#9j-~BP#Rf#byXd~VhD`+h&(}&3YV_*9ihpM%4Nb5sp#@C6lX8x(1pRa zd@`BYSb$FkpW7ep0elSr&^#7$964a%hZ4sj=P`ln)NgdKXW71;LKc19 z*|r(^exN=E2G}YInD)Y@cTooD_zL*5F4F0YuLFVv^2rf-<R3wTp#QVTm=8^z!;fSwsZ(9OcF4N^IhZ#p`I+SB6Y+sfo zYg2$J_{L_Bo5*uNTQLidwWe+C)ksJ zm>-mh^6-LXgCUMwO7NY1+rby9aaH(_VEgfpm;1ta^+#X=C4|WpmY)%m=jI;Zuw+om z62HiXBZgEQi~MnhagB;ZfV*_)8^w5Id@%G)FXYlYY!ssX~e+(xX*x80`-_M zQVVYjX0j9P7whiT#Wo(I;iEg5 zuv9Q7P~SEfD}XSb)374JfVaN>gs?Cf#_QlTp*kNY*YWPQo*0@Sk8+71k~fL$0VKMK zIxow!XQ}zgg@y35UKO9mo9E@M&RWO%)0#G{^SM!rqOk+E|?Gj5oC3?9Z1m32x#RMU`Pt^C%|sS zHm?76U_2pSSlTQG8*EH$EM*utu>AQkqm08sb8m=!3!?*YkgZ-y*^(JDk)4$1jKLGl zm@yclK@3eM&!yWzlt_S7->+^een8@ignY|hc1tO1VyhiHqfmI#R98rjC!r8x6CeT& z`u}HO@*H5>(0l5CoXr?fAt9Hs5(}6<_C}ZldFtr&z@+v+iqy7X9(ZfRr>E$EWp9Dp z&w^+uxDtX23V~325kYdyXVF&B594VWR5}R(JeDK!?X&IRQP5{t*YK9!o)b9UDCJJm z&d%-wP(ID|SrFh@!i*mlE;?)kRbve&76GrElA8rXx~nrjC#m8GZqptD@m22^|Cg^` z9kXwHujB!lq38y+B8SkNa=vLp2L^f1M^sWR(T*~-Sk zA_p-zeT>pDg&C2D86E8M6bPKPaR~`&rr`Qn5?lP^{=Wkqp_X5x4WfJU{|3>#Zcrk5 zhHC$uaUdFvLy#*H;mKhs6`$O_|Q1BG_9rD6~6}%^0_DnJm+HQcJ zQbxw$vL@`k*I_&e>!1NJUi29f5h>kF(Xcq8iP|xF6lRe>0O#Mq&~O0e5dP^XB=pTFh{2B0Ag;b^KUq!>Ic1HuF(P%?`0_qMlngr_h9qRw7xXH z`r^Rx+5uC*lOb)Le-YH7$6=@o5$1J+KpjKZ9!zP0x9SMddcey)=WRg=!KUb`?c(Y> zwN$2JaTI6{t&la6(!hcMMA-z7B!qTkkee>PybDnS(+|0-^~d}`w&UExjH-{UfKjBI zD^TzTIK;3ILWU@qsn3GLJB(6>76MAH*aCo!RKeKUYe3)HI`dy})SEcxc<0ccV5vA& zMcqh&OyA2&fpOJ)7SOg~XYIu-)#;H?66t$M?JpcN#;;!j-x8ZTk5KV1!ORa2;` zJ&KUAU+`0V9Wwwa;lKd28nEH(%2m4!__~*uSIV=x+tjz8M|uMr{69c{cp=~eME`7D zPX%`TK`II~0(p62!b&gU4h|i>!4;1A4sTx>rm|yu?+(KEVmMtGEU$j~-3rrD$#`>p zaHW8C2oFAd!L$`@1?XjR1ya#mTws;~&F$@3c2GA>I0XR?76M~5NHmyO34+5T1Rfag z;4??aOT*zt5qS#<5w}FX&&TjGsa6L`_eL4tm)v-*qabVvjAwg$UglFU$uZ<3LM*ME ztgNJ4)?%BPscAAxB}n#P40E};x{BxUQHl8COh6_qn~-ZOENJz(r1IHsU4ROb{h=c1 zou^k52!vw?NzBg92KSvqgtJ{h#0A4M1SnLviA!dIO~8FFe1#Pd)=Dvra?e*kD~%yI zI`U;{cgb2X2HGaX#2f(+`pG=IvCeLof3yTH2^YKv^Cd5P)u^qxA;)+5W5=h0pH-P? z*Gnif62SpR+3THT%Y&!DP=?JHxQVl%x&}K6uvLOZW^ocCl&v6c*~1!Kd1~G)ubni1 zWC(0T@Q1q7{|R~MXX>{dJ|)c{@M+I`^fUT!9X`zT7O4L#_e)7ukgIEB6H5YS_Z%(% zMX;GfjemN3mw$WQS+^cMGS@xD(mpwuo|RP|Sp{`jeg_Gn=lvg*bh9d6wxflp><@4# z!QzLFu=azCQ#vmvH}8wifWsG^oZuR(e}IDfFT87ZDQk1^lJXoYfjPynsj(2s9Wq-g z(}d_0r-^50NUTIk?{?<pg6t0J-xPdYs+QBC6Hu;iK&^^Ypv{P)PsDAr+Bf&*Fcx zxMkM{?z$vprt|FmcGEv)&+sI8WU7;(M9B4)EwI4zmn1y1^Tp#nJ_})BVG44UolS$I z6yOtGF_aQcz~?sqg+W}B_!%z5cw}Ph0GNg%ctnS&X0F^!Z7@+@4U;ACs9`}MDR?w7 z{|uN@WRg7?>omRa{G3s5tpm+?d~5p+J$UFV_P+Zm@@IbR?XtW>OA&sqSfq3J=XVCa z)B5b1fo*`40I}MaR0Kp0=s=K<{!Qk`Ps4ly_N=qY^)si7q@FVej>ykmn~w*>4&pPA zTfs?-ZzSAx83zZzx*2^+zM|qGPR*+6m2>yJyi9M&8F8QnQ-#iB^S_9vTX5?ARaA(? zaGlu_>#aQhl#Ciw41zcZD%vwQ zaZF5LB3VJRMG;N_orRnMw&&GVPi--XtN~p?op&1{Pz&Br34|r}_U17nAT)?0!8gp2 zD@3>Box86c3nfPQC9|q->`LA@!<05x*qmwX`vP0!Atu(p1*NT|8H&=~_&|(0kq$blNZXk^Tu31_GbpaB$uFfjEE$HIocZMmPVw)#7udXM!BW&tPbi zU^--WG@IB3t+u!c=&MYu^H}w;Cf4XziJ%A(NhXNvjmt)iX+kXm>bZjmXluZ!GUTt0 zRDoI-5cNwW>BU*f0@(sz!8wSAxr)ng=5vA!H3LE)4MwOCIBmf&5X%R(kH5cv+2wBc zJ4Ms-jxVUjDh{dq?cf|Yw~QkJ^MD#5dqfF#IB~o>sBD|TrbdMNt*A)5)tuW0nnNI# zfihRY0F+876T9>!nA<>jDOx83VU^`ADv!9NI3f=evo(mt1Z5x62uZO<&+nPE-g0$y z{f}PK-`96kDZm}ZyUD+RDIS^kf!I!u@@Qz2*3@3A^#u`wULv}53<0`zh+XAH22 zV2*D&s`v@C0_Eza#gl<&Eb5?H1R+R;|BR5Z@E$z%CZ}A1ECUKAG#`!xM3^YuoGxj7 zBB&N3~FvHRv~nM@F8rQGW7M1f{_aM8gB0svLj`u+yBJoKz>l zT>yESXJ%?rk}N!uW(Pm35n-$e*0Jg z7_kGE8599K|MuXe`W6DfoB|Hwlj8cWK=OxAXYssX!|4tV)h6p!ee+ZFdG_lS2pZbj z+CqU6f@*=@ycv}@5Ml$THt`25JtvH@KurLROEVn)Aen}wUVwWKtG_Qa;;l>mbCpf| z`JIZ2#!uiTa2IM>IH%DhN6?s&u0frBczgto0I=Aot-8ptHV=wwkApiuA0M7mf1dma z$jYFDh1v*8LVYk%ZJj?i4H}j9-7m)>>$2W!{8;69sq7QaO!Cq;96mrqxtKyb7TS!P zEwgo6ruSZfqLu-w5U^FLV@<0$2bxp2{>(b?=zt-hpWHDisg*}p1|}OXKk>*Ef8Jox z7N}HHUgEC$PUR6D0;)LZvAzu#=)r=6#ECbM>*iiOSb$aby5yGo@IXP{lRaTY3n)7_eF1A`SiG)dfXIv0`B-?FlZ>*%a z80tCmQ?$Vw%cnqv1th7R^JT-!2e=8E+kq=S*YYjMbQ+Ffb1vp!GkB7ItC+g z^}$y_=GP7yahLQoSWLik3ld;-|8pgL`0!z3BJM|3-2n43K4L8x z8(EZ|TWAg)JhKT{9i)T7mJO*mAo~MDMscSz&=jV5`1%4l{6FnL6OJF8C7>P!UB?C@ zw6nKJUrMB%JNd#c-mjYBdYNN-#lb#7mcym(vFV=gD=>3kNi(?G;NisMHu49AonLn2 zEUM0F*5>rJ6&7#>$w^!gexBMmrnIY^GMto%vu%(FS-V4AOB2ty&>F13{{g6R; z6=3s;kD9WrJ9p1^%l=bBEkVOdd|Q(iUc z;?8V8AYvgCwZQz7L%B0R+PN>fCMy*(+73VG#`X!0h#3!) zNU8FB((=Ef-_l2I$#ly01h5ThYK<3+RYm5kCpk8Y!r59#=;^V&I+Fs+|G|T&kUiM9 zK2>lXS_t4H4?%y`&2yvd$Ozot>(jitf9CPY-xQ3_r7w2hA-Q8TQ_e)io!H z;;?^jw()l03xwjy9>0C#9h=cI;fm->(;(-YH_^1{%K9fOaU@W&jDC(MF?1S#IU4o2 zEyLDUtemY{Usj*}?B>=@Zm`Z?%X2pM))O0RC=k+!O6`27(%s2fMENlKU0Zu{5-C49 zoIs$96X(0=vEh``>c!}0{mFBagXKQi!!vP=`Nq;jqSEw3kF-U1?}kh=z4<@Rk0oYE_>Ht?qKc`gS0V07y3=o~-Q&+}IxQkp>n>&VW$6O)`;!=IiIUUw;FY2gGwE>qy+GUGyz-R^V_t80LVhY#Mk*qR}UfR78AdK z3UnWGoI$f@3nJ;nM87GILbNRf!Rv`fMRp*g2kB<2DKNp%Qhp0mqi6#QEvdE`@3oJZ zy8S4|qW~q4+#wGexOgb23cVB5nT8-ghX2SrX$HN02r{pNV-jRvyr=w$x@hDWU}mV0 z3MA>7P>=x#Y#^FLRq%Nk6AQhd>BAnAj54uW`;@Xjq?}lO~rS92Rzr2QbJ`y%TQiF7G zk>IKLpG^#}@Bja8WWGidYZ=N4(=$?FBWgydgv03Y7(vYa?dMliT)%cRVsN;y+J78^ zVLJHogt_Tkab`RHev4P47Ius75fDBHv$WjvInr+u*eE(a?vI|B`rtE_j8Gs(*LIv8 zGMv+og~N<`Bj?oQ9>-U$*zLU~ghO6jhcfxOmL5hJ!F*p7TVoxvsjh*Yk#CXR5VwrM z5wv+uI)-*p8ED}N>!zY>_X&6=thqWJbE4p<;(ylYj>;I*NcvO!__S6Sb3QMDYG{s9 zUTU0KO(HAWv6(>_p`vCMRAiz`yeB9~?}*21v!hcSK_dl@LIsy3(r#$`>?%CnhhtsR zkHuFkHCMD;I7U3}GM4Y#dNzshcj)|)u$R2tcIzoXj8lW@gK5oX%9crZ6pktqPaSUf znfOF2;>vJ(vX{uq!mhjnapW9E{;<<7-Fl_*2D)N1J|M|NhO83?8AirThP8>!6(IlY z+^joJN7yb<wAvqVYLUlwH`s4) zpJIrP$@O5?*}b*#;dF1!lK9$gTgX7`rh$wrap0+Jfj(pL_22mGiE$!TG81GD{kaHh z^AUMl+p;`I1<4Xivw)N4W|E>MqM0;u$(W zy}EzdKHt>JHN`tz^4m`Nh5IreZhY!ezM9iI_+eHva?5h-QjKI^jpW(wBZ|EtK1jo zau(+(iZEJTvu(JqNnve4Vzm5_7<)*Fk%GWrS`cDJktAfeyYj(&E-qB){bHNS(HP4i z{ez636Z!W4GMt-yb3uf2=4XJ@>zDm~sTxO${N&bV#Hs7)`$Gkuy>RuvCJur*%lVWk z-E%tS{Ic&e4<7klllb}WkB*ZALl5D7)L|_G8^z$me#tGl|9e=xxN=s#^`dLdZ-MAn z{;v~n1)K}&=^Q)Z{JPHpU+noYcAIjR)fIjKqY#FzcTVoY(ZH{Ee%-*oVWIjrlOwZd z&HTaZ11~RIe*St2ci?6W7l-ACUh0v*hq%~&Mey=zC+>gTyk&Md^jwtsw8kD&Zg|vM zOrq=vlv5AnN z&1+>``rLG=!{K(2lsz3IkHgr~%Z~yae7ejjaSDixqDJ80trzzHR^>PnOycR!aeF=r z*Ob$gu3+F`e)wQ@o$bgNTAOI4zOJdD zoqyX!dWZJi@%|$$a^jQ{PsQ2JSN(oJaOp}#qAjN{til#*6 zVk50KH zzFFkNggIv%-NaB+=}Af3h9^^bzeY9#-R4Vp7Iil-w!VHz#T5VCyrF#LR_M1>vg19) z;Ke`Bn@Eo{b_*}M3x|t;?tJl)F3iPY*kwD-UUBW`W6f^Qd8-R@bpg{~1d6HoDF$+d zOE?dte0&NH!>MCcp>uM0-U~HK{|{;B6dhR;HtN`RGO=wtosMl|l8J5GHYc_-(PUyw zY-eKIcJlZ4uXAqB?YZgQtGCv!db{@CRc}4dVKqBI#!QSJT)dcJ?=Gh-jJU)7yBi)n z5J2Cv1QS)S@uP`7|HhYVWlZg_C8fI7>PU^g1*yg3uC(Khrmu4T@7tu>g-U#RB#;~; ziI&Xno57HFP;*R{KHILUB^MG^v6y9J@_;~O-`oI<$9wb*(&o_^=>*J4S4b{+P+B8C z9+W!Lt}{$injzocyBP;)`fqe#WR%HhKeazUJ?eV=ZIZ>-9OYbSz7B8f$+#$>H7ZOK zXH7$}MeAx((rkvT&mMP=VDXzn)`L#(WcOa$8R|MYjoy>)+!h$lhF~BMx7V;eZ+so8 zaOCy8?&g&KYU04M@TPb*NV_`jr1^kVDB2(=sYbde8dvDmd?Rq>M0G5kyB`#7M#fRAIQjnM$2MQCj7;FkA&Ry!^vi_lk0Rm$J36En+@gee0n&mcD!V6$Ts;r`Gn zn#mFYC zJ?jx0Xb6B@s-)!d^voJ-@@6L&Q4p6xS&+vDC%&Hme^{RsZE2M_yY;=CQx~^JqLtOR3?C)+C~!PL^XpFKhTw`U_(1aF7eW0#WA!& zL1{)Bp_7D$$eIt%^a|t*8S8ySxjhqDXhd_1pCmjse7M8_O#jdavX^@sAb-O-Q=1c|x(t<9;fe>;SfCa7ZK+3@kLmz{kBhwO#NkSb-Qj9D;+q)RC48U_w%ktyDw$HU# z1`99s&|Ch}2SUiI3;eIkaBB92-z8{zIa+bebxK9@&MtlxPUFNr^09b(f`)9%Okgfc2)a6M#c z%pqO0pkpI(QDH2Kiu%rkXa+QcDjpt~)SU@fCjn_&CQufl8;4_%M@kN8KAkCGaK6SC2UnWCc;RT&}W^k`)}Z&!u^9j z3lHB>@Jasgz(vTAqdUcb##0BX4;Y)Lsxp#gN(&&@NYppP3Sa2xvfsdDC#Y(2%OOQC z1k#_g!5yFuVaUCnHkyEq!&#uEf!(QWBiA{%hZ3k{ z{>BT0gYqK@5r;*W%@9xJoH>yN`QE^twW^K7n)G& zFF^S@pqHUv0DKd@Lm{`p>Ssg8Xi3G=`fDU%%9$my8p;bvW@)##3(;>UQ`DaodO?p_ z%JL8~a2DjS)5k6rDRUOc6WyPy_m^E>#&^GZqV@6ga1+vF{hsr@_2ceJ>MqJh>oP2{*9Ej>|d|ny#E_*`Ve$j zYP4Hhvd_2vL92S^%`_p%7~~CG(c+E5kuCV}zT1{X#b9S!;y(JjGR|}|{jm8rDIU@G zV3lby76CvWH%Zu~D&YFO^WY%-aDi$~N-LpEM_Z9oQpT7IH$;4T;)q#DBu|iq;KB85 zsYP#jVc6LwG&!191_;;RMu|=&Cxp3gK6;)Uu)Ce*a7kZ%sVcF zU|q1v2e#!7XBwXRsrgLT)sxMTo9#vi7s_8K*ev5&?i^uk&1UT^aX=N`qeWdOx|mWt z?h$eokjsWz^fCAE{UI0 zrNlDg1b+2~?cTOOjp*kyGvuG%wYkP$@?D6Y+=n zu}rkEZJG+{9wOo7O7(nqnKH4`Jf%OvagQAo>TvDM8!me3kM%zhb>NFXyf9k{0p|H|<%#3@(zgwI zPm4zVj0jZ5uhx!3!_3-huC9f^Img|qhv!X?x|J^P61tcn8q@l1q!)4 zr-72gnuLD}p0~FbI}ts0cT=P3^HfeKi|EB+$#p-s$1S`AZld-g4omDPxGI;&N;SRr zKRoPxk6I|;k2D4rI`NOw&n^o?jQH|WI+n*HHo5#3Q>SY}_vt@AXf~9-rZ5fa>~{CR zUPU(EuOW|wpFDD@@R|6?8#7mCH*8)+IzfZ*vyuw9$ja?8YzH@bSQzx0i1HhAM9WLo zfrXxjykGBmDLe+mDkAA=-%J|ATb6}Q*jZEqy}cj8xD zLR>+i>7vN&;vr+A10G_eQ0}>AFfuOfJUJWfr2UjK+5MY$&VoKI=p>U^18PB0G@RY-~vbYcFy zm5L|HvvUoj2AG+!GdGD`PaXOgwwmW02B+oO?KSLCC<%S)+n706vhryOhyLGOfCxNB zuAtVJr}QAote#t zTp#uM{4UJ$zkfY<>v`=x9yY;dC^G#ln*DWP;}N$|SZ0>?y)0fTI-l8}$p z<6K|0h?t|f^t`RITS3U9MxbN8+kZK9S#+rTM?gotV9UPTQ9IGc?Pnvd0nkeLq-k;R zSzqO=qkQ^eg^zpIwUX_RWBbJC>yC4ugYDaSR>}t?M=kU+l1%z3NNLB-e>m1JDg5~F z9Rwax8-CLhAZ#i@I=^2zce+@i0u4d7Z3yV@QJ}>cFD$SO4Sju-Pj7W)Ali#>l_&K9 z5%EvHKUAVTUI;b6%~hUcgEgeyZ(7{q9^wr=X%PFHc-dPqva86fNw4}>Ri`M4xR@*- zu4YGt#{Q^`6%x6nNtpaP)QT;i=4u-MoGni<_-*Q{Ar~f;JiLY((9qgVY17{QvT(DR zf$Hj_byYlFvXn3>JbctH=zc$NR&S^#l`u3iz$|~|qo7i~*j<=kPEZ2o|K)P$7vHs1 z?F3UfRiuMo_LJu8=xSZ?@*nKi{$`d>KE1;q@5R+JPtxb>ZOoX`SXheKFc*C9tM1CS zVlxHlYWz*l@z+HK09i0nb~b0|8uv1Y@pzSwPEjjxm$$5>fQQkCIV=Ecp-LJR6*t%< z@_C7z!C8ZzwQ!d2?^>3sOBPjdH?;BLuqMP0!HDmFgjtT`HdkZm^!av~`QG56J z_>pBA%gr0VZ|=;!)*jk#BMqdAs}Q+%4d8ngmZ5~Pa10DG*YvL99YtIL^-gbpiy`sJ z@ZF}?JB#o4?)vfWGQFQbbcv&i${7;bN7jlrPF`zs6sLtF@_Mlnv9;dg^>-ptATPz@ zR)OTDI?HS+Efp;pj)a|LaByfPta)k^hPbQ7-`b@;Q^HE`vD9=2^=b@vPm_F$+kf}3 zhwJC$o8+d4i*PRC+1tKr+vnHE{*4*YjV=5yyaPpXIzKDgmrww9+%(dN6yrUqv;|& zPx@0ZT*cw+l_H*$pM3@&8Q-R7Rd=JD=N-NVEAzN5;Z4=zR1hp?P0j|KM1)MPdRm@& zt}6VlUk5MZ_>>q^_oj13ABk)-YWmpOXjo`+1opWi(5c7}@(1)->Xker3oS15%J(3a zHtwF~#dvpz#)q`xn*ER{f#Bh!;GE!ttbD7|p+>yxpVI+%{q>&*v#<%NLUV=M2iFFz z^;5R^>ABfYQb!8#uw5oUrvW^#fVMwYt9IE%g-RBBJd`%NBaF4x@!D6>wZh<7yuSP* zM2(`$sbOo(aU|YqHV{?YNs5np9=Xp4A_5PyQi}N+Qt971Klk6<>(cE_G^`{!!Deab z=S?ZfJD!VSY2g)8iYTbGClX$rX%rWkkFNQkm>Xo$N2jyJ@em|Yp z`@M#VyuVkbjAL|#FlbqG`!)~M*h)Vw$?c(bn%Sm3EbNaszpi%^I|Mz3tQHF;3_$-S^$MaG(J2G3emm4Q) zj0wb9Oz6e8==`_MS~Zg>UH`%3i;T0LuiO=v_TLs19zve4?H6lj>4_!;giPpifUn^# zo|~a@f?B-T%L|?{2jAP{E~QqNg(ng>Sj9ukYN8}O<=0G_s%Ea^uco#4Tps;7X(lF~ zl4GXV^QX(~lYf#lT?!Ny0hi)yzZU_J-%pYos$1PZH}dnXhMC@`Z=%HS95w_G4Sv@= zC<%KuVzw^*>v7Y2q7nIJrxBR3TbmJgsoV5k9k6&pxu=vb(0XP0HG~Xbt6Kh8HrIH7 z+GBdZ_Qg&)|i--T@tLsL;sq`*} z#Ae!gve3o;DkyCE!dBMJ3hVILB;d*QGlgWevv5-m(wnfuKjU*k${f&MXj<#XlGE}1 zE_?w3oVB}T({;oK6LNbZ1IGR8kCTC9rpeaqW`o(G|M}){{QI~76C{nQevifd0A$ep zX|c>s2xTeneVokJx|E2^p~uzFi&2> zTL$yGMs4HpFU9}ViB{a)3>i4uNDX^iJxortM1`EhtFX`ORzB`a zPhEY7n{S6vamWfkR%(_3Z)J9ILe;ravraZ7t46Mv3^kvn<%&~!hm7X>&)uyKL>4>i z$-}#mlRb9R|EQ{Q&Up7!hy|U=$(h1TW(%(;TI&vTwU&mPQ?jDm%a}GjD=D*Mv}qKX zSSZMGYJ_--BP^P)U)lFoHvd>7pPHSYWwQ|4AMF+x79imEs=sZy`S{sjd^4M9CY*mf zID5~=!ILA^(VilQhhgQBi(uJUCRSJOi^O&cAi&Ugo{Eo}ay#R&I0n!6G8vjdavV;d zqe%#_sOY=wG_TEQ5|->OFQis>BcMbkFF(53>i+X5Qm&nQ)u{#AzVj(+g;2A`wD?kQCKr*bi* zF&EhLz=lQMS&jX;Yi~RAlCUG~)FYTVm&w{fMQgV2+WJ*K zW?eje^u`nmA5nnv_ks}XYRRD41!Go)Lbc*{CqMB5mG>x)I(3dpglpu2reFT;dC^`l z7(=RqLS0`!Mu8ep2A(dpb|q15(9hh(S; z0xL+((u9*-(&3<#x#k$d8J%5nASte@!IouNTZ=~rf^n0OhH6;oaD%O?J{_OvNtO-?Lz&h?M2P0Cyz@u-LWb?Ai%mydZ{KIl)HZ`o$n_^+iU zFBl5PXFb4mmK5K0TJkp%C)U7UpQGFX0t+cywt4P}Jl<OhXMwcdAM;% z?5I9;#zhEUmU;p>?Zd9byx(@F$+KV@5fOPgG3*!<_!4m{z9QwF$WGi}>trV$<- zGR(aQH)nzdDTbXrK-tc@S~fbNmlTtbJjKL3%%#SVmQKw&uU*MMzYWHg{QaS-KiAxt zpXW%!7%3bJOjOy59tIFsgap2lwbG1bfhDKqcX|~cXFZZjcS4RpGd<0XReQXC@=^-M zfvd#g6$v}mqBfOA#)illMw4lE#ndreREJ=m#ZqWix?QZX!VcCe9|l0r?%j@6$7F+4 zXUxvcRgg8OX5q-28|`b8j8buHnMZViiBg4fDsokXk@aN>gObs}#Fy0R;qn|^5(Llw z{a$v~DsZUsvG-=-*0y0Ri+R;wtdw`zs$qXkt8rj}WPYdD{J z)Ur%kMpkIr0eU+1w6M0B&$`&u7W8KAv5z1@2YU>ov7nYZ?0GvJAL7t&rRaDve!7iY zG->vWNMEz;6^s*DR=_|AO1qi**`#MH>_T1&ePm6*oJ$}_R{7lk+3Fal?je~BJWW%Y zi}5I@&XPOEg-w!O@ppT$3t;0-D;oh^bO@hbTk7n>3vgp;F%Ak0E7GBZ?u6RF z@?-(-j!;4>3Y)>xlD}-P!Nd7Pp_rF2NswEV{dB~%h>%1t&`{;x% z4@UUNDa$a_N}D>=u9DQ*X%XSO&E*$$#9|apdIdtfG(0Rp21qUiMovu6m_R&(dLvcJ z?g|I{cnx$GE6&IYUL)?gISK|kFO?X{S`2WoY$W<9hCACxq@?3})tNh>r&a31T!pPff|3)I$jgyQU$a2$@gnc-h1Tq{=T+AJjy6YE)1^Mv|1`~ zYox_`(MTPzMJ)6(lZ!l_A3872dtwxzMC|C)j~O0P1oFXZ(TR!Y#to>+&;{Q0mF^+7 zJ~^LCj0&r-a5Vl_poLBU^ zZ{yH%*h<5gz5dsfTh|Pyg7qNTG(FSontJosVbcyshqfOLrJ_4ECY!;WV!vzTT*agP zOA=sLpy<_?!GOxbJ*O&)N5QsLZ!%N_Ik+EmXj2+&qdw$s&A%zlP@N7ha{rs5>T=Rh zh3krP8=D}BDq!nUqkrUaPSW#tBf4K)S<=@Y8)ph6!fxNN#hQIlpaovQdF5lVl8>pq zYvO^oj$Zl#6arscrl1pfz{b#k6)|UIqWWU6a^10e{xX(Uh-T54^;FNF1Zwy+dhZHr zEEJpsR$wMRG9?hEZGxeFC&c2f=u;MisBtJv)f{kvb8exUN)th!A#*v5FxR|Woq?^Am6@bC&Jfje^=q8OOCSPg9mRFRe zRoK*NZqfKBMqZi%vA5=RklAu#(0Yr_bD$H?wLi>7aGxvGU|UudUasN(@Y|_Eh!lZ| z|DM^Ik%twQoQ9|+3U?Y@L7L6a(aDUZOK)bDlJB}FM)-v#nW2ad&O7Gga4Ip$smT{U zn-78QD^o2kTqo~bYb+K6H@}5^bQ_y88CZ3OnKJ4>i>C#q*Zx_0xtb zN+%RrS#}Al?w9-|JDomUYL(cX7IHrxIGj9~izG)px16q$@0Zyy=0LD$TIIQ=rAj2G zw(FPZ@~=BYuaQ>b?6}qzHb7C)Hw5BZI!O{fmJDbvBzF0c=&pq;-st zV9k;Bn=#2{Y1^Nldmk&rLudAaf8Xr4I5iT$FT2YX?@O>JL|lzOFMOz%5_UIu90+b$ z{~{vtdNU8i=ABNokAF-^!sVR5=D(+PjkAl%Oj-wb=3}uAZ>s?%mgb{Ym#u9Em|W-S zO?HF0D7>F-wRllLSOyLzv(ia8FgGvPjTIDR%)p7q8;7sIn08qsey59~Oa_il>VRQF z@46%x+nJby;EJai!cPzO|Ela7#Ou#&WdjnmggtHQ%Ewv8LJP^e^iRmnd|vjh?+t4~ z%aQo~KJJ{P5q>D>McdKGn3SL#3pQUEs1Ul{9(`F?O**b=wc5bZl|hRn4I#*dMQ0-T zj#6qJbv_c6S-n7MB#`Sf4>bzi<)g{}kck%fn)>^D3zgDoV_b8_R3YYa9NHXuOr9{D zTsgRc3mZcsnG)5;pY{wwTi0R%r?O))X9%=uW#u$e4?nNVFc%A=y}XCdbOW2%*c$ck zM%`_>bEz8f2~lKhHAZ%Z<9RjSFE^2gbGI-Bu4J&MManLZjXRp#!7USrpCr_{olCr1 zXcrKKV?6o?hVw;fAh5Lfw2MqbCp#p@TiUKd-aXQO_Y?_ z%buFDn6jt{X!=zd)@mqx3e^Jh!}q?eX%A!GvIWbGCraje(z2}VdP?fo^Hu&g$l#k} zc~v*QX{3x=(3ICpsThPKxViNL21UYiS*~#duWirre?m#%wMe!tDAP+J@sYA6KwQ6T zE-frqAw^zbgfo>!%UtS^7Iz}aZKJ-cd2)eDH5>xCXdKe+rH^yvWF;vIdZ^93JK4px;e{ zmz<{f-+N1bHb8kp32ssax_m;uq3&^P3xQ6vJ~V6V)ygAaZCE`ATcCBhqR4$Szsk8$ z=OB*&u9)?_&DZauY1l`_dEs+6W|+I%M=m9u6s!AuTwCmGn*yY)Khv_yrZt`S>ajRl zRoYyGF(}U$e4SX=VEv8wH=V9q6(RrO?#B?dEd`S~u&ftaCFd7s4 zWnC(sU>Y|#q@hW>M=BI)SylefdEhD#OZfDwDsRerxdgG zTK9F^!$frlR>dVF>C$BT+%Pk+WRLYUkA)QxP_nMP*&Y!sD4yW>rET5kGW@~p4h>S zX&stz{Uc%}T848pcct-s)6*vsV}lxK0Of^m<8#nW3t^OGh*|=_&+~b8q4Kr${XgvS z#ie%}mklWx%U3DhbQpgYHd!>;S~H6Jt?^v?OS*(DD+g1O`>5yzIOo4FnqF||+R&`r zIPWiuxIJbyF~tebKkC&>4^bkF7tx=3jDtEMRC z$i4sSusB@4I{nxkHu8M_=L@VRe)Hk}GrqLH>+QE7;_PwsS^VLZ!Mrqt;*s6+Ia)kC zHi}W4wS6k|lTybWof*P0X@VTjAPVn9Ra6Jqi#i|)aK&=xI;qc^X+2k+9;_#vZnJ+C zc*=Qg>A1%g=6?FOV;>Yw3szod^xS<1GP*p&%@hF%1{h_LG(5Vhd~h*ztg3nTTY`h> zp(k^KnSrE>j)SfqPp?RG#jAcwQ@`DsXEY3QbssV9c#`#+7-~_ z-w=xVRgcK?1Vd5uRHV&V`FzJN1!gYLP#zC+C-XnHpVMcXOuajqX;_H(J{-DD?~Qu= z&Ot+!s{$-n<>&PJW${&!1fp!%Y6O13otzlm+zz!J#wynA1WW(hn-n{UUqq(wtt2_C z-24J#YHMDX!<+g(UkZCs7-^qq#%MT~*YZ!FkX^((gZ4c&H}hLf{>vjP)F@mx@1+z+ z{G=iE+7va|*SjbfjWq#6X5(JkugR=64#$uUVW z<3F3LY*2GJyg9xT<9QQ$ZAZjvDkEFRj|A|g1b$3{#+3$v4o zV6rhQ2wgz%R{3@RK9GrXYJv0Zkh@|#q@u-Bs?}i?4Rgt%asFoBRAUj9vLJab-2HZ6B{cU#)@}u>L$a|i~wEw>6 zvJx@NkpE6X#&TRi-m4m-Ho`obRd~wQM|vd_d^R)$dZCJH2!ZTXn2Jyknx7D+ZeeA# zni;2)rrg*}^g+bs`s008@o>hjR;)!~b;~hLiQt0KB_+M66+1mA$th!d^8l@Ai3FfC zxsaL6t)vOU5e7UJ5<8Eq9zRD+K^;z<)lF0(W6RS(2%6N2Wh_MLPzWCx&ba2j77h`B zmtlG#H$7F`W8OLR$7ar;L730AMt&kEJ#_%R84RW$4ik$t4o1E7zDW6KA*;7&f_ZgT zCpWuHx#RS5YnhIh`P7&(=j(mGsa5}XHyy2fo`?>kM~EC{4bbVe{jl}x{yd&!t=(mS z3wg``;8?GYAU8jRKQ^JTkG#QudC_`rvLD;rue35!0?yd_hjIg0e^zBxe*s#Qd5LI^ z5x210^u$_S!=a@u&~LbOlILCBuKQ{j^M|myKV`G?1*ekpUrPb|mr0N}>2q(bwdI!j zxvrXev)yOc75DU4Sv)#eq@Ppm4OE@03eg~#T15tJ+bf}NDH0|tGTW@*Bc^0z(#8Z8aT)Yj9M1G6R{9!!0bdgm~7n=bcL9Ugp2vh4k#ZoVg5$ zk58i+w011IZ9XIkYi(3k7aB?{KR8xQD!#Glb5XJ9FgOey2W2 zw!R0%NRUq>dkoa!3FB`NmtEiU}QxrIAS=7>X~58Utopj!5e`9{I}24IOJjoO z9>rav#-~V$jX`#Zz)idwV4pY2p3H%sKpgMKjlLuAT)E*vIu#Nln&J6;vX>-0aJR}a z;pgmF`PkIsc07Kx+c*okx3s|>bg{=T%SCBndhzt>y;JA2CN_)>mq%2f&>VC;DNSLb z3d68dqU5@R6A0*(3~SGP&2oPDdPv%nYVQ_3h14S3N;-j3&h9zeQ9~4+?m#Ih=pBqC zo;zAg5qZ*sB^F>Z_>s4C|4%qPS0NT0o%h?m#})tGs1~IH%^$ zV$fP;PNiUMh}Sn)1HR8W*U55g!_zA4D8S$F3%K-hEWR$h(|z^3ByxOD=k@UC&*j=Z z3ljdf33#2$G4+^bBa<{EAf3LD4a3*5<8 zh(W)0z_=-TN4UGuV0>+{P)Kb1Ct_}%&BzHc@3vpS{2%=`do70aiD8DnD;tM2#7+jH zYp=&s#>6l(15V4R7HYfI%`Ur^1kbBf?mscd4RR+-4@hroD~$*(iDqa6_g;pQ@} zQ+}essJ*m}n{PjxB!x9%1e*y7Xwv>{)P=GbqH`YDW zWzk@)YFn=gX+8~$lhd9O9u!7Ijh&%__Lh|GgZY(dn{V%9;sQQCY`E+i=c*el_(`67 z1h%{n)8JKjylX7NeMoB%s9-PrPPU)Fye*Bw04Ys8s86toVDajrrS)H*ZQ^l<{t3MN zIsyb4&tjyFaDw}(e8!|4IK z53?@qubqjvBWwTKo~}cbVtZWwaEG~QXBvE+nGSzGQM|b%!8{+#T2~s-??-kW^`pKY zHi&;Lj>dX^t-0+8V?b%iTlfX|6@P~0ceOv`R~O;1!7K6dop2_f(YKIG zjH;``xDlhPs|P#qW{+{QvWsz9Q&Uk$suPyM(b74nwmObTI9nlTCuZ!+X_|dclTV6j zj3yVYmZw6KI4uoH{85$m?}zlLA+0x)OyRZ8{+!BuTW}|B6NXJYsZ)5C{(UtJ~v$!-LLR5?nF0N4U_ zNU;vCqJPm;oy;^9OaMz$IJGK56m?Yw;3yCOSq|^TUDzmL+^A&UCz-UOFbnPGy`4!| zLJ_{kWK{qm>PVhn;h zrDHFcLEb7$DsFBhFI6_Eh#Ty6kWhAm@-)Mq;KebVWScCH03fPV+Q?aL{~LB$D+PLQT;|6 zlR&(hF{_DoP}hfzW17qf#c2$Z*eW05%h7FNV{?BAAPlls5f1?XRUir_^@!v~4va(t zz*7fNq$<*v5ax)=m7`pvWzb3CkWoFzO1P@X0Gsg5VV(FDNHtV{1Y%4lHU>lCgDp|R z1N}&Gb>Ma>d9iW8tw`Z)r~$H}XgjICr(|SQWN`zOK>7=91OO$d(mX~JOUm3|0dmBvr%9iz-DgHe5u#{mCD&QAp=r(2-`M~qQPcdo-8xFB%= z_kaKqN*9b4#py;b8_#)xhHf>+M$3*B3B_{G8y8`gNH4me9$ZCZCI!J8nXoVBs0bGn zHm3{?&VVK)yU2N$B2ARJodzC>CKVspNLx&~0HxlZ%n4m69~mm9TrbufyfDY8xF|E` zN_CBX0?|jxLVrpjUIIg!j3!Z@Efwxe38lF5R-{boM%X2XIr0a~8Q3QlDh*$R56l8H zgWy9i#TyO!IVQ$6pUJWqKjP8Q+)pV@A%QbI%O-n33JkiHu34XeVrK7^M*xCj#Zwh) zYXM5=+@Sxas6*)2^+RZ)I_*kSK*2{KG4qp^BT37<5Ej-DysHCSt0+>5q2w8| zu;zLh_f?}^0F;ZOd223I6oP2E!PrUQVH75D(mEp=_+lGt%JmHh>dk3P%p1xWQs${i z07^{^jL88$dTeDAkh?jNmd@%vxOaZGlD=T7k`{XBpJ)aEKGljc4q!Mc0YN$=jh2WY zQ3?iw9<bg!qU6$0e#zinJRVl4vRGA0eHs+t%iI%85bQPZw!H^P-y^ayg}1 zLrV6Y$$-*{a-R%8I3`OOE!aB!b{Sm);}+B_-x5SURpiqdajT0sI#j9fe>bow`0{Ib{KwPzEr1LV|a+%etx^LAACU zDwDD}RS*dNBN544&@RvH*WYBs6j>zL3g<{LT`j}!cBODhOKv1|N%Tno-gas*7Nk)P z89IKTRf{vnpFmGV4#&zLVF1%?v(#=(O2;ZLm;!L9MOj-5c-na^7KL$GAnC>mM5^;8 zFPNe@1G17e9mE_ov~*LA%r+TH?F2lPD$c^ad)V)}0d|VgF|iYVO#0^B+QrhStt7BT z#^18UeDotx;sQO*iNU(amWuQ04DqC;$@61SoW-(~iyjQ9;Iz3AVQP_OIcJHSeH2u_ zGpVkTq5!n=z{cQAID&bH2>zHPizIb7)4)(T6j>xNLRv`L(hVi;q@jP%Xpr>X51W~g zwD1T}Cb(@WfAqHXeX>a$1$F2dM1_e_oluZyISMKYFyABBM)5f8xmKG2!M){8A)E*F z*SR%GNTNiARaX7NL|>)K5S_p0Ai+iDD?yZ-{u_(AZ;>-owN=|wHHcJ}b)ux?qzq@N z!~(SRB^QL#bRkM_^K&oZuAj8!IU@^-bk(Ai{A8C&BHyp+cXG3m)16(W#`OCOHr91T=N zSyN_0L>*J)u3(CJ7Bf>=^KAnjzfVdu1=u)qbIKJhgihZVb3FTJqLMD2wI(*@*GhA{ z`f4BwTo`;CF#~4LZ)aqtJ557$j8>SHbYV(2LYoOa3d$B&Im|Z3s39Xh~H}IF=KY%`gs=qcRpPGNx1HwGA;x_sc|; z{}d{9R*yF%yD(D=Lq0TwyB9@%O!faLRh@0l31>6Mv94C9FaM&ewM76b zIn&ihX^?{0=L=_6--}s32IQ09lyn0t7+er{s*0$nxb(e&5ra$ zX)rIyNeM3vxqiZxubatvSTkeDq@>opr70Dwy(!!j+LE&U`xfjs$E~bv?6*E7YHU%_ z_3qjC?JbkSZVzlaFo^DcdljO zbHp)}`CP9*x_#Hjfl=g5C<~YZ-b<9G79yUJVUE&{CE5c2PgR%2JR~$<_wA7{j7Kd3X^5q zgl_|D8yuU<>1EaOS?*B#)1N#HbPgVZIK1LW*D5Y|_&mMb%vKP(6(s*%;e*`R{^lpF+o-vaS3{Xp*Kr~7-5I=)e}`eL(d4P^$v2f8YU zu7{=nczCLn1wsT2EeMRRvlE@aKoyY-0?M_nJI7SzNya&X7{DO05J)cbv%Hast5WP+ zpo=}o&f2=+u2-r`H+~y0aOnvG1DZcUfzAP<@$T%Ad_99|7ZwHv_`}DR^ABcteS^@)xm=cr8)y@nc zdHXlae}^o5>=R`Pf(~-?IiQpZjZT2T$fmWLE>jx;Umu`gmt|pdad5c8?Rg&j53t?Q zA@E=Xk(rmb34&~ffXI#@e|0&-EeNZ92{HcvRfhtS-iwT|}o^Id_~$YTnUmyw~kZ%vFo zAR*t^^RP(S86!G~&AS28AVNYyHaRFnR%lj(qTKB7-@i|rK35=SP9UrOHsJPeX%1FNGJxp~#4wZ;Q=F?cqGz=2SGT zmYPaY3V5AgwB{Om2ALj9@G7f7!0G4Br*0lr*2M6j63JUvmlWcs$Hz_Yv)cb~!be#k z8fZd7LiMSI@Lg8er$S6l75`BkNP123c^v^#xJ#s)o#3s2?UfGo@9!>TLA{7=@d~i4 z`eNC_t1g0Kc4z0i1`$5~3J6v0%|6};;*X=CpcDxqQ5yJ4lr(**VRSh7o#{Pz3u(}T z4Bed;)5PY6-M%j%c`wd=1`{B+`-iz92qg&8g?_BrcQ7Ga_wAw>=KdO|5p%1?ZW+_Vj7U(7&-t*BTL~(9f2QQWgKpnO`YO1w~B zFmJRpE4*Mzn3}gt8D4^|&FHo~Y7wYNq-eT;8S09qw9+ol%kgfhs6`+{L-TH4_M@{` zd%fp1JO}tc&-eTLpNI4LL_7sSY!y({s!SzPXe%#Qat3_=5FA!(T(Mj*J#1l zcCfS0k)+5-i)vBnHwSZp<6gJoJO1LuAs;`3+wd0x|A7L)EN1WdmMmQwOVpyWYe9f6 zHVv~2K%Rv%WqYZ>x~}u`yymDpcA`XOr3{r^-eP99q&iH;pwUe9`~FucCZvL91qd!H0fv^s zKGp@u T2i-zX(#k25C_IL~fJKY(GU1-ZLO8>hY#h7+!KgeomM6?J0+${{#Mo5{y zTJL;fer?@3Wy`!x@A^(hU4TqOQbL)UZ{1qRW~*$2&<+mMAjHVf=;`TAI%`gCDa_A* z4H{hd^z%@Ce7m4jDs>LH$1^fezG?&YK3g+sH#Uqbo){Ik)=fM!g)>3|TgQ|1W9-tI z#FIv^rF1%hnO0O5VaXASW~2Sbc4T^A)#3j61ewg)jUe^c#P^MlC)E)@IVe9VL+-t{*q6RM* zwgD9WHz0@uzLy4eZ!N%%_NBVy#MC|X!?pf?ezu@sh>pH17LH~dcfyomjK&6~EBmDL z{u12~e{uq#KjB50Z#HnpW#QD}ERakW7Z-C}2BT3p98L?2UHUk?&6&E}e`ciq1&_zm z&}RSGX+O#Zr0F1Nh7p#nB`IGw-tPwNHlJdM`u(37cj8dRy(B!zT&S_EB?cGm%h)`lLFdKwxN4jJ=05hWE}v9JT7A-a6jW=t}BO?Jtcz#%3f)oN4RtNZWHgntb7 zxk3SWzcn{A8TjaymX;j5z@LZ^t3Y5(hM^3|(^l0CO>gB}7#m_xCStwk$X*S>2ctZy zbPnhOT53GhD%f>(is;Mp+TTe(7`HbZ257Mk5`4)a2@m;FDYgM`wg@1)!^6W+J$;LX4UI5w>;J&cNKY4nWBXU3 zqykRZ{L=SKR75vweNa5Qpw)S@NxM>=IWg6X1~*&Q^`O=!@UD^+SrCJW13r;O7v8Og zIs+iR07%+I(P_e(+cvPJ`c!j7Y6OhDu=$oCq$_bM#kxaY^-O+f|EKQ|HJi%EL^8<( z0|Nx6qw>lfR$*Zr%7n91uROkc;Tkp$BY5*^5Apwu`u{l#%TvCxD$w*Cht))_)q%s? L6Yn8(J9*(B2#sVy From 3baf7a08fa06a1d52a52efce6fb7eca3ba87fbaa Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 9 Feb 2024 12:34:20 +0100 Subject: [PATCH 121/173] added doc new osmand function --- docs/users/osmand.md | 26 +++++++++++++++++++++ docs/users/osmand/brouter-osmand-4.7.1.png | Bin 0 -> 237157 bytes 2 files changed, 26 insertions(+) create mode 100644 docs/users/osmand/brouter-osmand-4.7.1.png diff --git a/docs/users/osmand.md b/docs/users/osmand.md index db751f7..a1c2f03 100644 --- a/docs/users/osmand.md +++ b/docs/users/osmand.md @@ -67,3 +67,29 @@ application profiles"/> The BRouter app should be launched before OsmAnd for this specific entry to appear in OsmAnd. Therefore, if you cannot find "BRouter (offline)" navigation option, you should force quit OsmAnd and restart it. + + +## OsmAnd version 4.7.1 + +From version 4.7.1 upwards Osmand supports the profile parameter for mapping: +Since Osmand version 3, many profiles can be defined in Osmand and the user can easily switch between these profiles. +This allow now when using the service-interface to address different brouter-profiles in a more flexible and better comprehensive way. + +- If in Osmand a profile has "BRouter" defined as navigation service +- AND the profile-name looks like "Brouter[mysting] + +==> then the profile "mystring" will be used in the Brouter-app! +(this new mapping replaces in that case the basic mapping defined above and based on the file "serviceconfig.dat) + +### Examples: Osmand-profile name Brouter-app +``` +[Brouter[trekking] "trekking" profile will be used (file trekking.brf) +[Brouter[racebike] "racebike" profile will be used (file racebike.brf) +.... +``` +Remark: +Currently Osmand do not check the defined name (case sensitiv) for the Brouter-profile (mystring). +If no profile is found, the routing will fail with "Could not calculate route.."! + +BRouter configuration in OsmAnd
+application profiles diff --git a/docs/users/osmand/brouter-osmand-4.7.1.png b/docs/users/osmand/brouter-osmand-4.7.1.png new file mode 100644 index 0000000000000000000000000000000000000000..42dfe802c55cfa176bdd4f7f32fb0f8ae26c2d21 GIT binary patch literal 237157 zcmd3M^K&Lq*X&9~4| zMZ4)evwa<>!-s|Rn+0-TyL9}mMt^}%t?MjT$Mt&6VstR6FNT26>)~XsXq1-Zy9si( zNFwf^;bV7RCF%d#tNwquymMDe0*7jRfoI@*+5$c1l??#qru_H~^|#~tDRPwSS#ZnX zY2F0eQsz(Y`}LZ@#qeOSZ9YK%?Jf=0C+^N?O+h0a;Bf_=`##!X;eOTh)L7JQt5|m@ z8%H1@+`X@NlQ>?S?p92zy&30mss{^}MnR|KC1UKjjE<36Py6JKdniLG!gI{vcS(yq@gIUkZcbaVF&NK9F_foMkS~F&!k#b1pZ|~~J z4bgc?oQmSk{VQ^zA}T3xX{5xhXSQE%j2-(kw=i?LD*2;vY{4Cv4ltlHnSxNlmcb^) z_x);UFlA_@(ZyWM>7F4RAlVuLreVJ0t1b5>avAXA;m}Aqap=MOA$ld=xkcb=P-sa7 zfiRfVLPJyiL_lUpR8%UVdlVT}5G05Wlj0&JI1JQG$pWzyRS8rqK^UrpLN-Bs5XG)0 z6=K|R$+M^tSg-}jTVQ5+iLtPZQT`kW#1lI=JHU2{1%3#il;w<>wHA{i(|At?*YB9EByFE)tia8s9tDyPQK!@HYbOCIR2qibTvXJ(0P*p(qh@p}S-yj-rg6oKlg3)5P8V^ftLp#;%PDHrjDYu|8S z`fn&Ka2In?Dj+^J_>>{i@2NsC!E>?>m3w4;WHC}zsh}ZIQV{2Wa`y>QD`aLODOpq{ zVRYbp1T-i%I@vB7H5?PfjJ0$aph-~Nav&QgMG2or+$*x8oC-QKf`091FtpqvlnN@3 zMQ=-qfodm;lVFgbCA1Ep5Vi8(|AdMwh_!YbfCI&u;b0v2s9%nTMy14M%bCB*`Py;yft3O1(>LgAt|G+qz(Hh5~eRB{Fn0mj)k@?F0+tQ~_mL=}DhX^KfSFW&X*=oZX7Z>O> zJ6Axs{5~VxnDYj%&LdTyce9g>+!Io5HL46zQe{EaM&m0~)Xd*7S;$Oa21W(;NMbN-Jo`Z^VXZ3| zWE%D7-F~l_Qt8NNP!T9a>8vv7;;bbk#ImrclD>gMCq)O+=$UONH7_$E;X4w8d)+ls z=yWw}crk2XMS{?8hU&9N|6v zB}p9Bc(7UF1-CRkVE(zfIbC=A4!MJPAgHTe8l&(EzH|w-RsGPE*Z|eyP&|(-j770B z6cY?Ooo>q(PlqN2l2ATH0EE`vhG-`GxfCp)cjIUr)rGiPJ`$^meK3}quM5k$M(VO| z)9XZAnubAbiIEkbzpn0;YBaF!Cc=EXP|4XyM-kq7-IN5%F)_c}XcGVX?Z#JRq3a6g zw^bDLJVdz3)rF7k_|;h_RId`f67*3ZGRk~#4ggrO{I1~@0ox?O3mPFE4AK!$?9x&J zG|FPHu*ciH3GJ_0nVf}DJϖ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%0Ddt~jOhZiGj@=M__7 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

QR2PdTKl$5RJ}hK-?& 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

`Q66o;q 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 L6Yn8(J9*(B2#sVy literal 0 HcmV?d00001 From 503ceb625bbd971fb3bf62b51ef2d8c1e343abf4 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 9 Feb 2024 12:34:56 +0100 Subject: [PATCH 122/173] added docker doc with command samples --- docs/developers/docker_guide.md | 125 ++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 docs/developers/docker_guide.md diff --git a/docs/developers/docker_guide.md b/docs/developers/docker_guide.md new file mode 100644 index 0000000..990d8e9 --- /dev/null +++ b/docs/developers/docker_guide.md @@ -0,0 +1,125 @@ +--- +parent: Developers +--- + +# Docker help + +In addition to the intro in readme.md about Docker, here are a few commands for daily work with the system. + +Build the Docker with a version based name +``` +$ docker build -t brouter-1.7.2 . +``` + +Start Docker with name additional to the Docker image name. +Please note: +The path for segments are on a Windows system. +Here the port used in server.sh is published. +``` +$ docker run --rm -v "I:/Data/test/segment4":/segments4 --publish 17777:17777 --name brouter-1.7.2 brouter-1.7.2 +``` + +and with a mount for profiles as well +``` +$ docker run --rm -v "I:/Data/test/segment4":/segments4 -v "I:/Data/test/profiles2":/profiles2 --name brouter-1.7.2 brouter-1.7.2 +``` + +Show the running Docker processes +``` +$ docker ps + +output: +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +b23518e8791d brouter-1.7.2 "/bin/sh -c /bin/ser…" 5 minutes ago Up 5 minutes 0.0.0.0:17777->17777/tcp brouter-1.7.2 +``` + +Fire some curl or wget commands to test if is realy useful running. + +Stop a running Docker image - please note, this only works when starts docker image with name, see above +``` +$ docker stop brouter-1.7.2 +``` + +Docker available images + +``` +$ docker images + +output: +REPOSITORY TAG IMAGE ID CREATED SIZE +brouter-1.7.2 latest e39703dec2fa 2 hours ago 410MB +brouter latest 728f122c7388 3 hours ago 410MB +``` + +Control +## Docker with docker-compose + +Use a git clone to build a local folder with last version. +Make a Docker container with version number inside your repository folder. +``` +$ docker build -t brouter:1.7.2 . + +$ docker images + +REPOSITORY TAG IMAGE ID CREATED SIZE +brouter-1.7.2 latest e39703dec2fa 3 hours ago 410MB +brouter 1.7.2 e39703dec2fa 3 hours ago 410MB +``` + +Start a container with composer +This needs a docker config file docker-compose.yml +Something like this: +``` +version: '2' +services: + brouter: + image: brouter:1.7.2 + restart: unless-stopped + ports: + - 17777:17777 + volumes: + - type: bind + source: "I:/Data/test/segment4" + target: /segments4 +# - type: bind +# source: "I:/Data/test/profiles2" +# target: /profiles2 +``` + +Start it +``` +$ docker-compose up -d +``` + +Have a look what is running +``` +$ docker-compose ps +or +$ docker-compose ls +or +$ docker ps +``` + + +Now update your repository (git pull) and build your Docker container with the new version tag +``` +$ docker build -t brouter:1.7.3 . + +$ docker images + +REPOSITORY TAG IMAGE ID CREATED SIZE +brouter 1.7.3 5edc998cb5ae 3 hours ago 410MB +brouter-1.7.2 latest e39703dec2fa 6 hours ago 410MB +``` + +Replace the version in Docker config file docker-compose.yml +``` + image: brouter:1.7.3 +``` + +Stop old running container and start the new one +``` +$ docker-compose down + +$ docker-compose up -d +``` From 6427dab483359c0f2745cb4c4fdb71509eec8ec3 Mon Sep 17 00:00:00 2001 From: Emux Date: Mon, 12 Feb 2024 15:18:55 +0200 Subject: [PATCH 123/173] Error reading rawTrack: do not throw exception, fix #671 --- brouter-core/src/main/java/btools/router/OsmTrack.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index 9ccff98..a6c9817 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -285,7 +285,9 @@ public final class OsmTrack { } dis.close(); } catch (Exception e) { - throw new RuntimeException("Exception reading rawTrack: " + e); + if (debugInfo != null) { + debugInfo.append("Error reading rawTrack: " + e); + } } } } From f1e5732dc2d678a921b380d34175bca72cc59ec9 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 17 Feb 2024 10:45:42 +0100 Subject: [PATCH 124/173] removed throw exception #671 --- brouter-core/src/main/java/btools/router/OsmTrack.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index 9ccff98..087caf5 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -285,7 +285,10 @@ public final class OsmTrack { } dis.close(); } catch (Exception e) { - throw new RuntimeException("Exception reading rawTrack: " + e); + t = null; + if (debugInfo != null) { + debugInfo.append("Error reading rawTrack: " + e); + } } } } From b2009cf7e84fd9fafb2186e4d5f602bf64f75337 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 20 Feb 2024 12:00:59 +0100 Subject: [PATCH 125/173] more on roundabout #664 --- .../btools/router/VoiceHintProcessor.java | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java index 33b607a..cb60390 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java +++ b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java @@ -83,10 +83,21 @@ public final class VoiceHintProcessor { if (explicitRoundabouts && input.oldWay.isRoundabout()) { if (roundaboudStartIdx == -1) roundaboudStartIdx = hintIdx; roundAboutTurnAngle += sumNonConsumedWithinCatchingRange(inputs, hintIdx); + if (roundaboudStartIdx == hintIdx) { + if (input.badWays != null) { + // remove goodWay + roundAboutTurnAngle -= input.goodWay.turnangle; + // add a badWay + for (MessageData badWay : input.badWays) { + if (!badWay.isBadOneway()) roundAboutTurnAngle += badWay.turnangle; + } + } + } boolean isExit = roundaboutExit == 0; // exit point is always exit if (input.badWays != null) { for (MessageData badWay : input.badWays) { - if (!badWay.isBadOneway() && badWay.isGoodForCars() && Math.abs(badWay.turnangle) < 120.) { + if (!badWay.isBadOneway() && + badWay.isGoodForCars()) { isExit = true; } } @@ -97,12 +108,35 @@ public final class VoiceHintProcessor { continue; } if (roundaboutExit > 0) { - roundAboutTurnAngle += sumNonConsumedWithinCatchingRange(inputs, hintIdx); - double startTurn = (roundaboudStartIdx != -1 ? inputs.get(roundaboudStartIdx + 1).goodWay.turnangle : turnAngle); + //roundAboutTurnAngle += sumNonConsumedWithinCatchingRange(inputs, hintIdx); + //double startTurn = (roundaboudStartIdx != -1 ? inputs.get(roundaboudStartIdx + 1).goodWay.turnangle : turnAngle); input.angle = roundAboutTurnAngle; + input.goodWay.turnangle = roundAboutTurnAngle; input.distanceToNext = distance; - input.roundaboutExit = startTurn < 0 ? roundaboutExit : -roundaboutExit; + //input.roundaboutExit = startTurn < 0 ? roundaboutExit : -roundaboutExit; + input.roundaboutExit = roundAboutTurnAngle < 0 ? roundaboutExit : -roundaboutExit; + float tmpangle = 0; + VoiceHint tmpRndAbt = new VoiceHint(); + tmpRndAbt.badWays = new ArrayList<>(); + for (int i = hintIdx-1; i > roundaboudStartIdx; i--) { + VoiceHint vh = inputs.get(i); + tmpangle += inputs.get(i).goodWay.turnangle; + if (vh.badWays != null) { + for (MessageData badWay : vh.badWays) { + if (!badWay.isBadOneway()) { + MessageData md = new MessageData(); + md.linkdist = vh.goodWay.linkdist; + md.priorityclassifier = vh.goodWay.priorityclassifier; + md.turnangle = tmpangle; + tmpRndAbt.badWays.add(md); + } + } + } + } distance = 0.; + + input.badWays = tmpRndAbt.badWays; + results.add(input); roundAboutTurnAngle = 0.f; roundaboutExit = 0; From 260e960baf410be23ab2a82538df3a0801f041aa Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 26 Feb 2024 18:51:31 +0100 Subject: [PATCH 126/173] Update gradle.yml switch actions to v4 git actions do not generate complete zip file with v3 --- .github/workflows/gradle.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 39fdb4e..4fd8bde 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -15,9 +15,9 @@ jobs: runs-on: ubuntu-latest environment: BRouter steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '11' distribution: 'temurin' @@ -37,7 +37,7 @@ jobs: ORG_GRADLE_PROJECT_RELEASE_STORE_PASSWORD: ${{ secrets.BROUTER_STORE_PASSWORD }} run: ./gradlew build - name: Upload ZIP - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ZIP path: brouter-server/build/distributions/brouter-*.zip From 86e62e116392c1d648c74075fdbf1dcef1c6efb0 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 1 Mar 2024 12:01:27 +0100 Subject: [PATCH 127/173] Update gradle-publish.yml action version Update actions from v3 to v4 --- .github/workflows/gradle-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 1d23647..57cd7a4 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -18,10 +18,10 @@ jobs: packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '11' distribution: 'temurin' From 526bb53b703a3a187825dbcc2994e6fca74f2a4d Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 2 Mar 2024 10:44:38 +0100 Subject: [PATCH 128/173] added create elev image --- .../CreateElevationRasterImage.java | 266 ++++++++++++++ .../java/btools/mapcreator/HgtReader.java | 342 ++++++++++++++++++ 2 files changed, 608 insertions(+) create mode 100644 brouter-map-creator/src/main/java/btools/mapcreator/CreateElevationRasterImage.java create mode 100644 brouter-map-creator/src/main/java/btools/mapcreator/HgtReader.java diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/CreateElevationRasterImage.java b/brouter-map-creator/src/main/java/btools/mapcreator/CreateElevationRasterImage.java new file mode 100644 index 0000000..50b8d81 --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/CreateElevationRasterImage.java @@ -0,0 +1,266 @@ +package btools.mapcreator; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +import javax.imageio.ImageIO; + + +public class CreateElevationRasterImage { + + final static boolean DEBUG = false; + + int[] data; + ElevationRaster lastSrtmRaster; + Map srtmmap; + int lastSrtmLonIdx; + int lastSrtmLatIdx; + String srtmdir; + boolean missingData; + Map colorMap; + + private void createImage(double lon, double lat, String dir, String imageName, int maxX, int maxY, int downscale, String format) throws Exception { + srtmdir = dir; + if (format.equals("hgt")) { + createImageFromHgt(lon, lat, dir, imageName, maxX, maxY); + return; + } + srtmmap = new HashMap<>(); + lastSrtmLonIdx = -1; + lastSrtmLatIdx = -1; + lastSrtmRaster = null; + NodeData n = new NodeData(1, lon, lat); + ElevationRaster srtm = srtmForNode(n.ilon, n.ilat); + System.out.println("srtm " + srtm.toString()); + //System.out.println("srtm elev " + srtm.getElevation(n.ilon, n.ilat)); + double[] pos = getElevationPos(srtm, n.ilon, n.ilat); + System.out.println("srtm pos " + pos[0] + " " + pos[1]); // Arrays.toString(pos)); + short[] raster = srtm.eval_array; + int rasterX = srtm.ncols; + int rasterY = srtm.nrows; + + int tileSize = 1000 / downscale; + int sizeX = (maxX); + int sizeY = (maxY); + int[] imgraster = new int[sizeX * sizeY]; + System.out.println("srtm target " + sizeX + " " + sizeY + " (" + rasterX + " " + rasterY + ")"); + for (int y = 0; y < sizeY; y++) { + for (int x = 0; x < sizeX; x++) { + //short e = getElevationXY(srtm, /*pos[0] + */(sizeY - y) * downscale, /*pos[1] +*/ (x * downscale)); + short e = get(srtm, sizeY - y, x); + + if (e == Short.MIN_VALUE) { + imgraster[sizeY * y + x] = 0xffff; + } else { + //imgraster[sizeY * y + x] = getColorForHeight((short)(e/4)); //(int)(e/4.); + imgraster[sizeY * y + x] = getColorForHeight(e); + } + } + } + if (DEBUG) { + String out = "short "; + for (int i = 0; i < 100; i++) { + out += " " + get(srtm, sizeY - 0, i); + } + System.out.println(out); + } + + BufferedImage argbImage = new BufferedImage(sizeX, sizeY, BufferedImage.TYPE_INT_ARGB); + data = ((DataBufferInt) argbImage.getRaster().getDataBuffer()).getData(); + + for (int y = 0; y < sizeY; y++) { + for (int x = 0; x < sizeX; x++) { + int v0 = imgraster[sizeX * y + x]; + + int rgb; + if (v0 != 0xffff) + rgb = 0xff000000 | v0; //(v0 << 8); + else + rgb = 0xff000000; + data[y * sizeX + x] = rgb; + } + } + + ImageIO.write(argbImage, "png", new FileOutputStream(imageName)); + } + + private void createImageFromHgt(double lon, double lat, String dir, String imageName, int maxX, int maxY) throws Exception { + HgtReader rdr = new HgtReader(dir); + short[] data = rdr.getElevationDataFromHgt(lat, lon); + int size = (data != null ? data.length : 0); + int rowlen = (int) Math.sqrt(size); + System.out.println("hgt size " + rowlen); + int sizeX = (maxX); + int sizeY = (maxY); + int[] imgraster = new int[sizeX * sizeY]; + + for (int y = 0; y < sizeY; y++) { + for (int x = 0; x < sizeX; x++) { + short e = data[(rowlen * y) + x]; + if (e == HgtReader.HGT_VOID) { + imgraster[sizeY * y + x] = 0xffff; + } else if (e == 0) { + imgraster[sizeY * y + x] = 0xffff; + } else { + imgraster[sizeY * y + x] = getColorForHeight((short) (e)); //(int)(e/4.); + } + } + } + if (DEBUG) { + String out = "short "; + for (int i = 0; i < 100; i++) { + out += " " + data[i]; + } + System.out.println(out); + } + BufferedImage argbImage = new BufferedImage(sizeX, sizeY, BufferedImage.TYPE_INT_ARGB); + int[] idata = ((DataBufferInt) argbImage.getRaster().getDataBuffer()).getData(); + + for (int y = 0; y < sizeY; y++) { + for (int x = 0; x < sizeX; x++) { + int v0 = imgraster[sizeX * y + x]; + + int rgb; + if (v0 != 0xffff) + rgb = 0xff000000 | v0; //(v0 << 8); + else + rgb = 0xff000000; + idata[y * sizeX + x] = rgb; + } + } + + ImageIO.write(argbImage, "png", new FileOutputStream(imageName)); + + } + + public double[] getElevationPos(ElevationRaster srtm, int ilon, int ilat) { + double lon = ilon / 1000000. - 180.; + double lat = ilat / 1000000. - 90.; + + double dcol = (lon - srtm.xllcorner) / srtm.cellsize - 0.5; + double drow = (lat - srtm.yllcorner) / srtm.cellsize - 0.5; + int row = (int) drow; + int col = (int) dcol; + if (col < 0) col = 0; + if (row < 0) row = 0; + + return new double[]{drow, dcol}; + } + + private short get(ElevationRaster srtm, int r, int c) { + short e = srtm.eval_array[(srtm.nrows - 1 - r) * srtm.ncols + c]; + if (e == Short.MIN_VALUE) missingData = true; + return e; + } + + public short getElevationXY(ElevationRaster srtm, double drow, double dcol) { + int row = (int) drow; + int col = (int) dcol; + if (col < 0) col = 0; + if (col >= srtm.ncols - 1) col = srtm.ncols - 2; + if (row < 0) row = 0; + if (row >= srtm.nrows - 1) row = srtm.nrows - 2; + double wrow = drow - row; + double wcol = dcol - col; + missingData = false; + + double eval = (1. - wrow) * (1. - wcol) * get(srtm, row, col) + + (wrow) * (1. - wcol) * get(srtm, row + 1, col) + + (1. - wrow) * (wcol) * get(srtm, row, col + 1) + + (wrow) * (wcol) * get(srtm, row + 1, col + 1); + + return missingData ? Short.MIN_VALUE : (short) (eval * 4); + } + + + int getColorForHeight(short h) { + if (colorMap == null) { + colorMap = new TreeMap<>(); + colorMap.put((short) 0, new Color(102, 153, 153)); + colorMap.put((short) 1, new Color(0, 102, 0)); + colorMap.put((short) 500, new Color(251, 255, 128)); + colorMap.put((short) 1200, new Color(224, 108, 31)); + colorMap.put((short) 2500, new Color(200, 55, 55)); + colorMap.put((short) 4000, new Color(215, 244, 244)); + colorMap.put((short) 8000, new Color(255, 244, 244)); + } + Color lastColor = null; + short lastKey = 0; + for (Entry entry : colorMap.entrySet()) { + short key = entry.getKey(); + Color value = entry.getValue(); + if (key == h) return value.getRGB(); + if (lastColor != null && lastKey < h && key > h) { + double between = (double) (h - lastKey) / (key - lastKey); + return mixColors(value, lastColor, between); + } + lastColor = value; + lastKey = key; + } + return 0; + } + + public int mixColors(Color color1, Color color2, double percent) { + double inverse_percent = 1.0 - percent; + int redPart = (int) (color1.getRed() * percent + color2.getRed() * inverse_percent); + int greenPart = (int) (color1.getGreen() * percent + color2.getGreen() * inverse_percent); + int bluePart = (int) (color1.getBlue() * percent + color2.getBlue() * inverse_percent); + return new Color(redPart, greenPart, bluePart).getRGB(); + } + + private ElevationRaster srtmForNode(int ilon, int ilat) throws Exception { + int srtmLonIdx = (ilon + 5000000) / 5000000; + int srtmLatIdx = (654999999 - ilat) / 5000000 - 100; // ugly negative rounding... + + if (srtmLonIdx == lastSrtmLonIdx && srtmLatIdx == lastSrtmLatIdx) { + return lastSrtmRaster; + } + lastSrtmLonIdx = srtmLonIdx; + lastSrtmLatIdx = srtmLatIdx; + + String slonidx = "0" + srtmLonIdx; + String slatidx = "0" + srtmLatIdx; + String filename = "srtm_" + slonidx.substring(slonidx.length() - 2) + "_" + slatidx.substring(slatidx.length() - 2); + + lastSrtmRaster = srtmmap.get(filename); + if (lastSrtmRaster == null && !srtmmap.containsKey(filename)) { + File f = new File(new File(srtmdir), filename + ".bef"); + if (f.exists()) { + System.out.println("*** reading: " + f); + try { + InputStream isc = new BufferedInputStream(new FileInputStream(f)); + lastSrtmRaster = new ElevationRasterCoder().decodeRaster(isc); + isc.close(); + } catch (Exception e) { + System.out.println("**** ERROR reading " + f + " ****"); + } + srtmmap.put(filename, lastSrtmRaster); + return lastSrtmRaster; + } + + srtmmap.put(filename, lastSrtmRaster); + } + return lastSrtmRaster; + } + + public static void main(String[] args) throws Exception { + if (args.length < 6) { + System.out.println("usage: java CreateLidarImage [type]"); + System.out.println("\nwhere: type = [bef|hgt] downscale = [1|2|4|..]"); + return; + } + String format = args.length == 8 ? args[7] : "bef"; + new CreateElevationRasterImage().createImage(Double.parseDouble(args[0]), Double.parseDouble(args[1]), args[2], args[3], + Integer.parseInt(args[4]), Integer.parseInt(args[5]), Integer.parseInt(args[6]), format); + } +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/HgtReader.java b/brouter-map-creator/src/main/java/btools/mapcreator/HgtReader.java new file mode 100644 index 0000000..f161d7d --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/HgtReader.java @@ -0,0 +1,342 @@ +// License: GPL. For details, see LICENSE file. +package btools.mapcreator; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; +import java.nio.channels.FileChannel; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * adapted from https://github.com/JOSM/josm-plugins/blob/master/ElevationProfile/src/org/openstreetmap/josm/plugins/elevation/HgtReader.java + *

+ * Class HgtReader reads data from SRTM HGT files. Currently this class is restricted to a resolution of 3 arc seconds. + *

+ * SRTM data files are available at the NASA SRTM site + * + * @author Oliver Wieland <oliver.wieland@online.de> + */ +public class HgtReader { + final static boolean DEBUG = false; + + private static final int SECONDS_PER_MINUTE = 60; + + public static final String HGT_EXT = ".hgt"; + public static final String ZIP_EXT = ".zip"; + + // alter these values for different SRTM resolutions + public static final int HGT3_RES = 3; // resolution in arc seconds + public static final int HGT3_ROW_LENGTH = 1201; // number of elevation values per line + public static final int HGT_VOID = -32768; // magic number which indicates 'void data' in HGT file + public static final int HGT1_RES = 1; // <<- The new SRTM is 1-ARCSEC + public static final int HGT1_ROW_LENGTH = 3601; //-- New file resolution is 3601x3601 + /** + * The 'no elevation' data magic. + */ + public static double NO_ELEVATION = Double.NaN; + + private static String srtmFolder = ""; + + private static final Map cache = new HashMap<>(); + + public HgtReader(String folder) { + srtmFolder = folder; + } + + public static double getElevationFromHgt(double lat, double lon) { + try { + String file = getHgtFileName(lat, lon); + if (DEBUG) System.out.println("HGT buffer " + file + " for " + lat + " " + lon); + + // given area in cache? + if (!cache.containsKey(file)) { + + // fill initial cache value. If no file is found, then + // we use it as a marker to indicate 'file has been searched + // but is not there' + cache.put(file, null); + // Try all resource directories + //for (String location : Main.pref.getAllPossiblePreferenceDirs()) + { + String fullPath = new File(srtmFolder, file + HGT_EXT).getPath(); + File f = new File(fullPath); + if (f.exists()) { + // found something: read HGT file... + ShortBuffer data = readHgtFile(fullPath); + // ... and store result in cache + cache.put(file, data); + //break; + } else { + fullPath = new File(srtmFolder, file + ZIP_EXT).getPath(); + f = new File(fullPath); + if (f.exists()) { + ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(f))); + try { + for (; ; ) { + ZipEntry ze = zis.getNextEntry(); + if (ze == null) break; + if (ze.getName().toLowerCase().endsWith(HGT_EXT)) { + // System.out.println("read zip " + ze.getName()); + ShortBuffer data = readHgtStream(zis); + // ... and store result in cache + cache.put(file, data); + break; + } + zis.closeEntry(); + } + } finally { + zis.close(); + } + } + } + System.out.println("*** reading: " + f.getName() + " " + cache.get(file)); + } + } + + // read elevation value + return readElevation(lat, lon); + } catch (FileNotFoundException e) { + System.err.println("HGT Get elevation " + lat + ", " + lon + " failed: => " + e.getMessage()); + // no problem... file not there + return NO_ELEVATION; + } catch (Exception ioe) { + // oops... + ioe.printStackTrace(System.err); + // fallback + return NO_ELEVATION; + } + } + + public static short[] getElevationDataFromHgt(double lat, double lon) { + try { + if (lon < 0) lon += 1; + if (lat < 0) lat += 1; + String file = getHgtFileName(lat, lon); + if (DEBUG) System.out.println("HGT buffer " + file + " for " + lat + " " + lon); + + ShortBuffer data = null; + + // Try all resource directories + //for (String location : Main.pref.getAllPossiblePreferenceDirs()) + + String fullPath = new File(srtmFolder, file + HGT_EXT).getPath(); + File f = new File(fullPath); + if (f.exists()) { + // found something: read HGT file... + data = readHgtFile(fullPath); + } else { + fullPath = new File(srtmFolder, file + ZIP_EXT).getPath(); + f = new File(fullPath); + if (f.exists()) { + ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(f))); + try { + for (; ; ) { + ZipEntry ze = zis.getNextEntry(); + if (ze == null) break; + if (ze.getName().toLowerCase().endsWith(HGT_EXT)) { + // System.out.println("read zip " + ze.getName()); + data = readHgtStream(zis); + break; + } + zis.closeEntry(); + } + } finally { + zis.close(); + } + } + } + + + System.out.println("*** reading: " + f.getName() + " " + (data != null ? data.limit() : -1)); + if (data != null) { + short[] array = new short[data.limit()]; + data.get(array); + return array; + } + return null; + } catch (FileNotFoundException e) { + System.err.println("HGT Get elevation " + lat + ", " + lon + " failed: => " + e.getMessage()); + // no problem... file not there + return null; + } catch (Exception ioe) { + // oops... + ioe.printStackTrace(System.err); + // fallback + return null; + } + } + + @SuppressWarnings("resource") + private static ShortBuffer readHgtFile(String file) throws Exception { + if (file == null) throw new Exception("no hgt file " + file); + + FileChannel fc = null; + ShortBuffer sb = null; + try { + // Eclipse complains here about resource leak on 'fc' - even with 'finally' clause??? + fc = new FileInputStream(file).getChannel(); + // choose the right endianness + + ByteBuffer bb = ByteBuffer.allocateDirect((int) fc.size()); + while (bb.remaining() > 0) fc.read(bb); + + bb.flip(); + //sb = bb.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer(); + sb = bb.order(ByteOrder.BIG_ENDIAN).asShortBuffer(); + } finally { + if (fc != null) fc.close(); + } + + return sb; + } + + // @SuppressWarnings("resource") + private static ShortBuffer readHgtStream(InputStream zis) throws Exception { + if (zis == null) throw new Exception("no hgt stream "); + + ShortBuffer sb = null; + try { + // choose the right endianness + + byte[] bytes = zis.readAllBytes(); + ByteBuffer bb = ByteBuffer.allocate(bytes.length); + bb.put(bytes, 0, bytes.length); + //while (bb.remaining() > 0) zis.read(bb, 0, size); + + //ByteBuffer bb = ByteBuffer.allocate(zis.available()); + //Channels.newChannel(zis).read(bb); + bb.flip(); + //sb = bb.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer(); + sb = bb.order(ByteOrder.BIG_ENDIAN).asShortBuffer(); + } finally { + + } + + return sb; + } + + /** + * Reads the elevation value for the given coordinate. + *

+ * See also stackexchange.com + * + * @param lat, lon the coordinate to get the elevation data for + * @return the elevation value or Double.NaN, if no value is present + */ + public static double readElevation(double lat, double lon) { + String tag = getHgtFileName(lat, lon); + + ShortBuffer sb = cache.get(tag); + + if (sb == null) { + return NO_ELEVATION; + } + + if (DEBUG) System.out.println("HGT buffer size " + sb.capacity() + " limit " + sb.limit()); + try { + int rowLength = HGT3_ROW_LENGTH; + int resolution = HGT3_RES; + if (sb.capacity() > (HGT3_ROW_LENGTH * HGT3_ROW_LENGTH)) { + rowLength = HGT1_ROW_LENGTH; + resolution = HGT1_RES; + } + // see http://gis.stackexchange.com/questions/43743/how-to-extract-elevation-from-hgt-file + double fLat = frac(lat) * SECONDS_PER_MINUTE; + double fLon = frac(lon) * SECONDS_PER_MINUTE; + + // compute offset within HGT file + int row = (int) Math.round((fLat) * SECONDS_PER_MINUTE / resolution); + int col = (int) Math.round((fLon) * SECONDS_PER_MINUTE / resolution); + if (lon < 0) col = rowLength - col - 1; + if (lat > 0) row = rowLength - row - 1; + + + //row = rowLength - row; + int cell = (rowLength * (row)) + col; + //int cell = ((rowLength * (latitude)) + longitude); + + if (DEBUG) + System.out.println("Read HGT elevation data from row/col/cell " + row + "," + col + ", " + cell + ", " + sb.limit()); + + // valid position in buffer? + if (cell < sb.limit()) { + short ele = sb.get(cell); + // check for data voids + if (ele == HGT_VOID) { + return NO_ELEVATION; + } else { + return ele; + } + } else { + return NO_ELEVATION; + } + } catch (Exception e) { + System.err.println("error at " + lon + " " + lat + " "); + e.printStackTrace(); + } + return NO_ELEVATION; + } + + /** + * Gets the associated HGT file name for the given way point. Usually the + * format is [N|S]nn[W|E]mmm.hgt where nn is the integral latitude + * without decimals and mmm is the longitude. + * + * @param llat,llon the coordinate to get the filename for + * @return the file name of the HGT file + */ + public static String getHgtFileName(double llat, double llon) { + int lat = (int) llat; + int lon = (int) llon; + + String latPref = "N"; + if (lat < 0) { + latPref = "S"; + lat = -lat + 1; + } + String lonPref = "E"; + if (lon < 0) { + lonPref = "W"; + lon = -lon + 1; + } + + return String.format("%s%02d%s%03d", latPref, lat, lonPref, lon); + } + + public static double frac(double d) { + long iPart; + double fPart; + + // Get user input + iPart = (long) d; + fPart = d - iPart; + return Math.abs(fPart); + } + + public static void clear() { + if (cache != null) { + cache.clear(); + } + } + + public static void main(String[] args) throws Exception { + System.out.println("*** HGT position values and enhance elevation"); + if (args.length == 3) { + HgtReader elevReader = new HgtReader(args[0]); + double lon = Double.parseDouble(args[1]); + double lat = Double.parseDouble(args[2]); + // check hgt direct + double elev = elevReader.getElevationFromHgt(lat, lon); + System.out.println("-----> elv for hgt " + lat + ", " + lon + " = " + elev); + + } + } +} From 6e858b6c91aeeed0210fd34c628497d8c7b28b97 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 2 Mar 2024 15:41:32 +0100 Subject: [PATCH 129/173] added app certificate fallback --- .../btools/routingapp/DownloadWorker.java | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java b/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java index c5d5eaa..1aa7416 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java @@ -73,6 +73,7 @@ public class DownloadWorker extends Worker { int version = -1; int appversion = -1; String errorCode = null; + private boolean bHttpDownloadProblem; public DownloadWorker( @NonNull Context context, @@ -255,6 +256,7 @@ public class DownloadWorker extends Worker { newappversion = meta.minAppVersion; } else { String lookupLocation = mServerConfig.getLookupUrl() + fileName; + if (bHttpDownloadProblem) lookupLocation = lookupLocation.replace("https://", "http://"); URL lookupUrl = new URL(lookupLocation); downloadProgressListener.onDownloadStart(fileName, DownloadType.LOOKUP); changed = downloadFile(lookupUrl, tmplookupFile, size, false, DownloadType.LOOKUP); @@ -305,6 +307,7 @@ public class DownloadWorker extends Worker { //if (profileFile.exists()) { String profileLocation = mServerConfig.getProfilesUrl() + fileName; + if (bHttpDownloadProblem) profileLocation = profileLocation.replace("https://", "http://"); URL profileUrl = new URL(profileLocation); int size = (int) (profileFile.exists() ? profileFile.length() : 0); @@ -326,6 +329,8 @@ public class DownloadWorker extends Worker { private void downloadSegment(String segmentBaseUrl, String segmentName) throws IOException, InterruptedException { File segmentFile = new File(baseDir, SEGMENTS_DIR + segmentName); File segmentFileTemp = new File(segmentFile.getAbsolutePath() + "_tmp"); + if (bHttpDownloadProblem) segmentBaseUrl = segmentBaseUrl.replace("https://", "http://"); + if (DEBUG) Log.d(LOG_TAG, "Download " + segmentName + " " + version + " " + versionChanged); try { if (segmentFile.exists()) { @@ -333,6 +338,7 @@ public class DownloadWorker extends Worker { String md5 = Rd5DiffManager.getMD5(segmentFile); if (DEBUG) Log.d(LOG_TAG, "Calculating local checksum " + md5); String segmentDeltaLocation = segmentBaseUrl + "diff/" + segmentName.replace(SEGMENT_SUFFIX, "/" + md5 + SEGMENT_DIFF_SUFFIX); + if (bHttpDownloadProblem) segmentDeltaLocation = segmentDeltaLocation.replace("https://", "http://"); URL segmentDeltaUrl = new URL(segmentDeltaLocation); if (httpFileExists(segmentDeltaUrl)) { File segmentDeltaFile = new File(segmentFile.getAbsolutePath() + "_diff"); @@ -376,13 +382,28 @@ public class DownloadWorker extends Worker { } private boolean httpFileExists(URL downloadUrl) throws IOException { - HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection(); - connection.setConnectTimeout(5000); - connection.setRequestMethod("HEAD"); - connection.setDoInput(false); + HttpURLConnection connection = null; try { + connection = (HttpURLConnection) downloadUrl.openConnection(); + connection.setConnectTimeout(5000); + connection.setRequestMethod("HEAD"); + connection.setDoInput(false); connection.connect(); return connection.getResponseCode() == HttpURLConnection.HTTP_OK; + } catch (javax.net.ssl.SSLHandshakeException e) { + String url = downloadUrl.toString().replace("https://", "http://"); + downloadUrl = new URL(url); + try { + connection = (HttpURLConnection) downloadUrl.openConnection(); + connection.setConnectTimeout(5000); + connection.setRequestMethod("HEAD"); + connection.setDoInput(false); + connection.connect(); + bHttpDownloadProblem = true; + return connection.getResponseCode() == HttpURLConnection.HTTP_OK; + } finally { + connection.disconnect(); + } } finally { connection.disconnect(); } @@ -391,14 +412,24 @@ public class DownloadWorker extends Worker { private boolean downloadFile(URL downloadUrl, File outputFile, int fileSize, boolean limitDownloadSpeed, DownloadType type) throws IOException, InterruptedException { if (DEBUG) Log.d(LOG_TAG, "download " + outputFile.getAbsolutePath()); - HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection(); - connection.setConnectTimeout(5000); - connection.setDefaultUseCaches(false); - + HttpURLConnection connection = null; InputStream input = null; OutputStream output = null; try { - connection.connect(); + try { + connection = (HttpURLConnection) downloadUrl.openConnection(); + connection.setConnectTimeout(5000); + connection.setDefaultUseCaches(false); + connection.connect(); + } catch (javax.net.ssl.SSLHandshakeException e) { + String url = downloadUrl.toString().replace("https://", "http://"); + downloadUrl = new URL(url); + connection = (HttpURLConnection) downloadUrl.openConnection(); + connection.setConnectTimeout(5000); + connection.setDefaultUseCaches(false); + connection.connect(); + bHttpDownloadProblem = true; + } if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { throw new IOException("HTTP Request failed: " + downloadUrl + " returned " + connection.getResponseCode()); From dcc9719ba61136019799e0d49dc18dc374d62944 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sun, 3 Mar 2024 17:52:23 +0100 Subject: [PATCH 130/173] added create elev image with diff colors --- .../CreateElevationRasterImage.java | 89 ++++++++++++++++--- 1 file changed, 79 insertions(+), 10 deletions(-) diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/CreateElevationRasterImage.java b/brouter-map-creator/src/main/java/btools/mapcreator/CreateElevationRasterImage.java index 50b8d81..935965f 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/CreateElevationRasterImage.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/CreateElevationRasterImage.java @@ -4,9 +4,11 @@ import java.awt.Color; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FileReader; import java.io.InputStream; import java.util.HashMap; import java.util.Map; @@ -25,26 +27,40 @@ public class CreateElevationRasterImage { Map srtmmap; int lastSrtmLonIdx; int lastSrtmLatIdx; + short maxElev = Short.MIN_VALUE; + short minElev = Short.MAX_VALUE; String srtmdir; boolean missingData; Map colorMap; - private void createImage(double lon, double lat, String dir, String imageName, int maxX, int maxY, int downscale, String format) throws Exception { + + private void createImage(double lon, double lat, String dir, String imageName, int maxX, int maxY, int downscale, String format, String colors) throws Exception { srtmdir = dir; + if (colors != null) { + loadColors(colors); + } if (format.equals("hgt")) { createImageFromHgt(lon, lat, dir, imageName, maxX, maxY); return; } + if (!format.equals("bef")) { + System.out.println("wrong format (bef|hgt)"); + return; + } srtmmap = new HashMap<>(); lastSrtmLonIdx = -1; lastSrtmLatIdx = -1; lastSrtmRaster = null; NodeData n = new NodeData(1, lon, lat); ElevationRaster srtm = srtmForNode(n.ilon, n.ilat); + if (srtm == null) { + System.out.println("no data"); + return; + } System.out.println("srtm " + srtm.toString()); //System.out.println("srtm elev " + srtm.getElevation(n.ilon, n.ilat)); double[] pos = getElevationPos(srtm, n.ilon, n.ilat); - System.out.println("srtm pos " + pos[0] + " " + pos[1]); // Arrays.toString(pos)); + //System.out.println("srtm pos " + Math.round(pos[0]) + " " + Math.round(pos[1])); short[] raster = srtm.eval_array; int rasterX = srtm.ncols; int rasterY = srtm.nrows; @@ -53,11 +69,12 @@ public class CreateElevationRasterImage { int sizeX = (maxX); int sizeY = (maxY); int[] imgraster = new int[sizeX * sizeY]; - System.out.println("srtm target " + sizeX + " " + sizeY + " (" + rasterX + " " + rasterY + ")"); for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { - //short e = getElevationXY(srtm, /*pos[0] + */(sizeY - y) * downscale, /*pos[1] +*/ (x * downscale)); - short e = get(srtm, sizeY - y, x); + //short e = getElevationXY(srtm, pos[0] + (sizeY - y) * downscale, pos[1] + (x * downscale)); + short e = get(srtm, (int) Math.round(pos[0]) + (sizeY - y), x + (int) Math.round(pos[1])); + if (e != Short.MIN_VALUE && e < minElev) minElev = e; + if (e != Short.MIN_VALUE && e > maxElev) maxElev = e; if (e == Short.MIN_VALUE) { imgraster[sizeY * y + x] = 0xffff; @@ -67,6 +84,8 @@ public class CreateElevationRasterImage { } } } + System.out.println("srtm target " + sizeX + " " + sizeY + " (" + rasterX + " " + rasterY + ")" + " min " + minElev + " max " + maxElev); + if (DEBUG) { String out = "short "; for (int i = 0; i < 100; i++) { @@ -97,9 +116,13 @@ public class CreateElevationRasterImage { private void createImageFromHgt(double lon, double lat, String dir, String imageName, int maxX, int maxY) throws Exception { HgtReader rdr = new HgtReader(dir); short[] data = rdr.getElevationDataFromHgt(lat, lon); + if (data == null) { + System.out.println("no data"); + return; + } + int size = (data != null ? data.length : 0); int rowlen = (int) Math.sqrt(size); - System.out.println("hgt size " + rowlen); int sizeX = (maxX); int sizeY = (maxY); int[] imgraster = new int[sizeX * sizeY]; @@ -107,15 +130,19 @@ public class CreateElevationRasterImage { for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { short e = data[(rowlen * y) + x]; + if (e != HgtReader.HGT_VOID && e < minElev) minElev = e; + if (e != HgtReader.HGT_VOID && e > maxElev) maxElev = e; + if (e == HgtReader.HGT_VOID) { imgraster[sizeY * y + x] = 0xffff; } else if (e == 0) { imgraster[sizeY * y + x] = 0xffff; } else { - imgraster[sizeY * y + x] = getColorForHeight((short) (e)); //(int)(e/4.); + imgraster[sizeY * y + x] = getColorForHeight((short) (e)); } } } + System.out.println("hgt size " + rowlen + " x " + rowlen + " min " + minElev + " max " + maxElev); if (DEBUG) { String out = "short "; for (int i = 0; i < 100; i++) { @@ -143,6 +170,47 @@ public class CreateElevationRasterImage { } + private void loadColors(String colors) { + if (DEBUG) System.out.println("colors=" + colors); + File colFile = new File(colors); + if (colFile.exists()) { + BufferedReader reader = null; + colorMap = new TreeMap<>(); + try { + reader = new BufferedReader(new FileReader(colors)); + String line = reader.readLine(); + + while (line != null) { + if (DEBUG) System.out.println(line); + String[] sa = line.split(","); + if (!line.startsWith("#") && sa.length == 4) { + short e = Short.valueOf(sa[0].trim()); + short r = Short.valueOf(sa[1].trim()); + short g = Short.valueOf(sa[2].trim()); + short b = Short.valueOf(sa[3].trim()); + colorMap.put(e, new Color(r, g, b)); + } + // read next line + line = reader.readLine(); + } + + } catch (Exception e) { + e.printStackTrace(); + colorMap = null; + } finally { + if (reader != null) { + try { + reader.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } else { + System.out.println("color file " + colors + " not found"); + } + } + public double[] getElevationPos(ElevationRaster srtm, int ilon, int ilat) { double lon = ilon / 1000000. - 180.; double lat = ilat / 1000000. - 90.; @@ -255,12 +323,13 @@ public class CreateElevationRasterImage { public static void main(String[] args) throws Exception { if (args.length < 6) { - System.out.println("usage: java CreateLidarImage [type]"); + System.out.println("usage: java CreateLidarImage [type] [color_file]"); System.out.println("\nwhere: type = [bef|hgt] downscale = [1|2|4|..]"); return; } - String format = args.length == 8 ? args[7] : "bef"; + String format = args.length >= 8 ? args[7] : "bef"; + String colors = args.length == 9 ? args[8] : null; new CreateElevationRasterImage().createImage(Double.parseDouble(args[0]), Double.parseDouble(args[1]), args[2], args[3], - Integer.parseInt(args[4]), Integer.parseInt(args[5]), Integer.parseInt(args[6]), format); + Integer.parseInt(args[4]), Integer.parseInt(args[5]), Integer.parseInt(args[6]), format, colors); } } From e75adfb555278d4876df9238cc5d010bc5c4eb73 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Wed, 6 Mar 2024 19:54:48 +0100 Subject: [PATCH 131/173] enabled raw track for test only --- brouter-server/src/main/java/btools/server/BRouter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/brouter-server/src/main/java/btools/server/BRouter.java b/brouter-server/src/main/java/btools/server/BRouter.java index 1600c7e..eedc8b2 100644 --- a/brouter-server/src/main/java/btools/server/BRouter.java +++ b/brouter-server/src/main/java/btools/server/BRouter.java @@ -105,7 +105,8 @@ public class BRouter { if (engineMode == RoutingEngine.BROUTER_ENGINEMODE_GETELEV) { re = new RoutingEngine("testinfo", null, new File(args[0]), wplist, rc, engineMode); } else { - rc.rawTrackPath = "testtrack.raw"; + // use this to generate a raw track for CLI + // rc.rawTrackPath = "testtrack.raw"; re = new RoutingEngine("testtrack", null, new File(args[0]), wplist, rc, engineMode); } re.doRun(0); @@ -114,7 +115,7 @@ public class BRouter { engineMode == RoutingEngine.BROUTER_ENGINEMODE_PREPARE_REROUTE) { // store new reference track if any // (can exist for timed-out search) - if (re.getFoundRawTrack() != null) { + if (rc.rawTrackPath != null && re.getFoundRawTrack() != null) { try { re.getFoundRawTrack().writeBinary(rc.rawTrackPath); } catch (Exception e) { From 28e6523ab94c2f5de010b4acd41bc60933e95807 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Wed, 6 Mar 2024 19:57:12 +0100 Subject: [PATCH 132/173] wrong last message in track #657 --- brouter-core/src/main/java/btools/router/OsmTrack.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index ac278d1..6c64cf7 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -346,6 +346,12 @@ public final class OsmTrack { if (i > 0 || ourSize == 0) { e.setTime(e.getTime() + t0); e.setEnergy(e.getEnergy() + e0); + if (e.message != null){ + if (!(e.message.lon == e.getILon() && e.message.lat == e.getILat())) { + e.message.lon = e.getILon(); + e.message.lat = e.getILat(); + } + } nodes.add(e); } } From b9b629185a20a6de9a4c4864e223e47e98554b78 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 8 Mar 2024 12:16:13 +0100 Subject: [PATCH 133/173] Revert "App: Rerouting" --- .../src/main/java/btools/router/OsmTrack.java | 1 - .../java/btools/router/RoutingEngine.java | 44 +------------------ .../src/main/java/btools/server/BRouter.java | 17 +------ .../src/main/java/btools/util/CheapRuler.java | 17 ------- 4 files changed, 3 insertions(+), 76 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index ac278d1..98d5e93 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -285,7 +285,6 @@ public final class OsmTrack { } dis.close(); } catch (Exception e) { - t = null; if (debugInfo != null) { debugInfo.append("Error reading rawTrack: " + e); } diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index f940f2f..105ed09 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -20,8 +20,6 @@ import btools.mapaccess.OsmLink; import btools.mapaccess.OsmLinkHolder; import btools.mapaccess.OsmNode; import btools.mapaccess.OsmNodePairSet; -import btools.util.CheapAngleMeter; -import btools.util.CheapRuler; import btools.util.CompactLongMap; import btools.util.SortedHeap; import btools.util.StackSampler; @@ -32,8 +30,6 @@ public class RoutingEngine extends Thread { public final static int BROUTER_ENGINEMODE_SEED = 1; public final static int BROUTER_ENGINEMODE_GETELEV = 2; - public final static int BROUTER_ENGINEMODE_PREPARE_REROUTE = 6; - private NodesCache nodesCache; private SortedHeap openSet = new SortedHeap<>(); private boolean finished = false; @@ -162,7 +158,6 @@ public class RoutingEngine extends Thread { switch (engineMode) { case BROUTER_ENGINEMODE_ROUTING: - case BROUTER_ENGINEMODE_PREPARE_REROUTE: if (waypoints.size() < 2) { throw new IllegalArgumentException("we need two lat/lon points at least!"); } @@ -194,9 +189,6 @@ public class RoutingEngine extends Thread { ArrayList messageList = new ArrayList<>(); for (int i = 0; ; i++) { track = findTrack(refTracks, lastTracks); - - if (engineMode==BROUTER_ENGINEMODE_PREPARE_REROUTE) break; // no output for rerouting prepare - track.message = "track-length = " + track.distance + " filtered ascend = " + track.ascend + " plain-ascend = " + track.plainAscend + " cost=" + track.cost; if (track.energy != 0) { @@ -583,10 +575,6 @@ public class RoutingEngine extends Thread { boolean dirty = found && nearbyTrack.isDirty; logInfo("read referenceTrack, found=" + found + " dirty=" + dirty + " " + debugInfo); } - if (nearbyTrack != null && - engineMode==BROUTER_ENGINEMODE_PREPARE_REROUTE) { - return null; // already rerouting prepared - } } if (matchedWaypoints == null) { // could exist from the previous alternative level @@ -599,22 +587,6 @@ public class RoutingEngine extends Thread { matchedWaypoints.add(mwp); } matchWaypointsToNodes(matchedWaypoints); - if (routingContext.startDirection != null) { - // add a nogo not to turn back - double angle = CheapAngleMeter.normalize(180 + routingContext.startDirection); - int[] np = CheapRuler.destination(matchedWaypoints.get(0).crosspoint.ilon, matchedWaypoints.get(0).crosspoint.ilat, 10, angle); - OsmNodeNamed n = new OsmNodeNamed(); - n.name = "nogo8"; - n.ilon = np[0]; - n.ilat = np[1]; - n.isNogo = true; - n.radius = 8; - n.nogoWeight = 9999; - if (routingContext.nogopoints == null) { - routingContext.nogopoints = new ArrayList<>(); - } - routingContext.nogopoints.add(n); - } routingContext.checkMatchedWaypointAgainstNogos(matchedWaypoints); @@ -659,8 +631,7 @@ public class RoutingEngine extends Thread { routingContext.inverseDirection = false; wptIndex = i + 1; } else { - //seg = searchTrack(matchedWaypoints.get(i), matchedWaypoints.get(i + 1), i == matchedWaypoints.size() - 2 ? nearbyTrack : null, refTracks[i]); - seg = searchTrack(matchedWaypoints.get(i), matchedWaypoints.get(i + 1), nearbyTrack, refTracks[i]); + seg = searchTrack(matchedWaypoints.get(i), matchedWaypoints.get(i + 1), i == matchedWaypoints.size() - 2 ? nearbyTrack : null, refTracks[i]); wptIndex = i; } if (seg == null) @@ -1077,18 +1048,7 @@ public class RoutingEngine extends Thread { track.nogoChecksums = routingContext.getNogoChecksums(); track.profileTimestamp = routingContext.profileTimestamp; track.isDirty = isDirty; - if (foundRawTrack == null) { - foundRawTrack = track; - } else { - for (OsmPathElement n : track.nodes) { - foundRawTrack.nodes.add(n); - } - foundRawTrack.endPoint = endWp; - } - if (engineMode==BROUTER_ENGINEMODE_PREPARE_REROUTE) { - return null; // rerouting prepared - } - + foundRawTrack = track; } if (!wasClean && isDirty) { diff --git a/brouter-server/src/main/java/btools/server/BRouter.java b/brouter-server/src/main/java/btools/server/BRouter.java index 1600c7e..a6dfe10 100644 --- a/brouter-server/src/main/java/btools/server/BRouter.java +++ b/brouter-server/src/main/java/btools/server/BRouter.java @@ -105,28 +105,13 @@ public class BRouter { if (engineMode == RoutingEngine.BROUTER_ENGINEMODE_GETELEV) { re = new RoutingEngine("testinfo", null, new File(args[0]), wplist, rc, engineMode); } else { - rc.rawTrackPath = "testtrack.raw"; re = new RoutingEngine("testtrack", null, new File(args[0]), wplist, rc, engineMode); } re.doRun(0); - - if (engineMode == RoutingEngine.BROUTER_ENGINEMODE_ROUTING || - engineMode == RoutingEngine.BROUTER_ENGINEMODE_PREPARE_REROUTE) { - // store new reference track if any - // (can exist for timed-out search) - if (re.getFoundRawTrack() != null) { - try { - re.getFoundRawTrack().writeBinary(rc.rawTrackPath); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - } catch (Exception e) { System.out.println(e.getMessage()); } - + } diff --git a/brouter-util/src/main/java/btools/util/CheapRuler.java b/brouter-util/src/main/java/btools/util/CheapRuler.java index bdd9360..7a7dc8f 100644 --- a/brouter-util/src/main/java/btools/util/CheapRuler.java +++ b/brouter-util/src/main/java/btools/util/CheapRuler.java @@ -77,21 +77,4 @@ public final class CheapRuler { double dlat = (ilat1 - ilat2) * kxky[1]; return Math.sqrt(dlat * dlat + dlon * dlon); // in m } - - public static int[] destination(int lon1, int lat1, double distance, double angle) { - - double[] lonlat2m = CheapRuler.getLonLatToMeterScales(lat1); - double lon2m = lonlat2m[0]; - double lat2m = lonlat2m[1]; - angle = 90. - angle; - double st = Math.sin(angle * Math.PI / 180.); - double ct = Math.cos(angle * Math.PI / 180.); - - int lon2 = (int) (0.5 + lon1 + ct * distance / lon2m); - int lat2 = (int) (0.5 + lat1 + st * distance / lat2m); - int[] ret = new int[2]; - ret[0] = lon2; - ret[1] = lat2; - return ret; - } } From eebf22de84d295094e01e04cc6d45c3a3f79dd5c Mon Sep 17 00:00:00 2001 From: afischerdev Date: Wed, 3 Apr 2024 14:38:57 +0200 Subject: [PATCH 134/173] updated for next version --- docs/revisions.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/revisions.md b/docs/revisions.md index 4169388..c2647e6 100644 --- a/docs/revisions.md +++ b/docs/revisions.md @@ -2,6 +2,29 @@ (ZIP-Archives including APK, readme + profiles) + +### Changes since last version + +Library + +- new "DIVIDE" command for profile calculation +- new "maxslope" and "maxslopecost" parameters +- new parameter collector +- new output logic +- rework on voice hints and roundabouts +- enabled elevation raster files with 1 asec + + +Android + +- BRouter translations +- fallback on certificate problems + + +- Minor bug fixes + +[Solved issues](https://github.com/abrensch/brouter/issues?q=is%3Aissue+milestone%3A%22Version+1.7.4%22+is%3Aclosed) + ### [brouter-1.7.3.zip](../brouter_bin/brouter-1.7.3.zip) (current revision, 19.08.2023) - Minor bug fixes From a4388ce5c9d766706505c811ba953f487196c678 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Wed, 3 Apr 2024 21:14:17 +0200 Subject: [PATCH 135/173] `bundle update` ruby dependencies --- docs/Gemfile.lock | 110 +++++++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 068424c..e51aaf0 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,23 +1,31 @@ GEM remote: https://rubygems.org/ specs: - activesupport (7.0.8) + activesupport (7.1.3.2) + base64 + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) minitest (>= 5.1) + mutex_m tzinfo (~> 2.0) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) - base64 (0.1.1) + base64 (0.2.0) + bigdecimal (3.1.7) coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.11.1) + coffee-script-source (1.12.2) colorator (1.1.0) commonmarker (0.23.10) - concurrent-ruby (1.2.2) - dnsruby (1.70.0) + concurrent-ruby (1.2.3) + connection_pool (2.4.1) + dnsruby (1.72.0) simpleidn (~> 0.2.1) + drb (2.2.1) em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) @@ -25,24 +33,23 @@ GEM ffi (>= 1.15.0) eventmachine (1.2.7) execjs (2.9.1) - faraday (2.7.11) - base64 - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.2) - ffi (1.15.5) + faraday (2.9.0) + faraday-net_http (>= 2.0, < 3.2) + faraday-net_http (3.1.0) + net-http + ffi (1.16.3) forwardable-extended (2.6.0) - gemoji (3.0.1) - github-pages (228) - github-pages-health-check (= 1.17.9) - jekyll (= 3.9.3) - jekyll-avatar (= 0.7.0) - jekyll-coffeescript (= 1.1.1) + gemoji (4.1.0) + github-pages (231) + github-pages-health-check (= 1.18.2) + jekyll (= 3.9.5) + jekyll-avatar (= 0.8.0) + jekyll-coffeescript (= 1.2.2) jekyll-commonmark-ghpages (= 0.4.0) - jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.15.1) + jekyll-default-layout (= 0.1.5) + jekyll-feed (= 0.17.0) jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.13.0) + jekyll-github-metadata (= 2.16.1) jekyll-include-cache (= 0.2.1) jekyll-mentions (= 1.6.0) jekyll-optional-front-matter (= 0.3.2) @@ -69,28 +76,28 @@ GEM jekyll-theme-tactile (= 0.2.0) jekyll-theme-time-machine (= 0.2.0) jekyll-titles-from-headings (= 0.5.3) - jemoji (= 0.12.0) - kramdown (= 2.3.2) + jemoji (= 0.13.0) + kramdown (= 2.4.0) kramdown-parser-gfm (= 1.1.0) liquid (= 4.0.4) mercenary (~> 0.3) minima (= 2.5.1) nokogiri (>= 1.13.6, < 2.0) - rouge (= 3.26.0) + rouge (= 3.30.0) terminal-table (~> 1.4) - github-pages-health-check (1.17.9) + github-pages-health-check (1.18.2) addressable (~> 2.3) dnsruby (~> 1.60) - octokit (~> 4.0) - public_suffix (>= 3.0, < 5.0) + octokit (>= 4, < 8) + public_suffix (>= 3.0, < 6.0) typhoeus (~> 1.3) html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.8.0) - i18n (1.14.1) + i18n (1.14.4) concurrent-ruby (~> 1.0) - jekyll (3.9.3) + jekyll (3.9.5) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) @@ -103,11 +110,11 @@ GEM pathutil (~> 0.9) rouge (>= 1.7, < 4) safe_yaml (~> 1.0) - jekyll-avatar (0.7.0) + jekyll-avatar (0.8.0) jekyll (>= 3.0, < 5.0) - jekyll-coffeescript (1.1.1) + jekyll-coffeescript (1.2.2) coffee-script (~> 2.2) - coffee-script-source (~> 1.11.1) + coffee-script-source (~> 1.12) jekyll-commonmark (1.4.0) commonmarker (~> 0.22) jekyll-commonmark-ghpages (0.4.0) @@ -115,15 +122,15 @@ GEM jekyll (~> 3.9.0) jekyll-commonmark (~> 1.4.0) rouge (>= 2.0, < 5.0) - jekyll-default-layout (0.1.4) - jekyll (~> 3.0) - jekyll-feed (0.15.1) + jekyll-default-layout (0.1.5) + jekyll (>= 3.0, < 5.0) + jekyll-feed (0.17.0) jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) - jekyll-github-metadata (2.13.0) + jekyll-github-metadata (2.16.1) jekyll (>= 3.4, < 5.0) - octokit (~> 4.0, != 4.4.0) + octokit (>= 4, < 7, != 4.4.0) jekyll-include-cache (0.2.1) jekyll (>= 3.7, < 5.0) jekyll-mentions (1.6.0) @@ -194,16 +201,16 @@ GEM jekyll (>= 3.3, < 5.0) jekyll-watch (2.2.1) listen (~> 3.0) - jemoji (0.12.0) - gemoji (~> 3.0) + jemoji (0.13.0) + gemoji (>= 3, < 5) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) - kramdown (2.3.2) + kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) liquid (4.0.4) - listen (3.8.0) + listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) @@ -211,22 +218,24 @@ GEM jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.20.0) - nokogiri (1.15.4-x86_64-linux) + minitest (5.22.3) + mutex_m (0.2.0) + net-http (0.4.1) + uri + nokogiri (1.16.3-x86_64-linux) racc (~> 1.4) octokit (4.25.1) faraday (>= 1, < 3) sawyer (~> 0.9) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (4.0.7) - racc (1.7.1) + public_suffix (5.0.5) + racc (1.7.3) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) rexml (3.2.6) - rouge (3.26.0) - ruby2_keywords (0.0.5) + rouge (3.30.0) rubyzip (2.3.2) safe_yaml (1.0.5) sass (3.7.4) @@ -241,14 +250,15 @@ GEM unf (~> 0.1.4) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - typhoeus (1.4.0) + typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unf (0.1.4) unf_ext - unf_ext (0.0.8.2) + unf_ext (0.0.9.1) unicode-display_width (1.8.0) + uri (0.13.0) webrick (1.8.1) PLATFORMS @@ -262,4 +272,4 @@ DEPENDENCIES webrick (~> 1.8) BUNDLED WITH - 2.4.20 + 2.5.4 From e2752c78bbce549a4202b4ec8c73470859507735 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Wed, 3 Apr 2024 13:18:54 +0200 Subject: [PATCH 136/173] Remove AndroidManifests for pure java libraries AGP 8.0 upgrade assistant transforms those to build.gradle instructions which aren't supported for pure java libs --- brouter-codec/src/main/AndroidManifest.xml | 3 --- brouter-core/src/main/AndroidManifest.xml | 3 --- brouter-expressions/src/main/AndroidManifest.xml | 3 --- brouter-mapaccess/src/main/AndroidManifest.xml | 3 --- brouter-util/src/main/AndroidManifest.xml | 3 --- 5 files changed, 15 deletions(-) delete mode 100644 brouter-codec/src/main/AndroidManifest.xml delete mode 100644 brouter-core/src/main/AndroidManifest.xml delete mode 100644 brouter-expressions/src/main/AndroidManifest.xml delete mode 100644 brouter-mapaccess/src/main/AndroidManifest.xml delete mode 100644 brouter-util/src/main/AndroidManifest.xml diff --git a/brouter-codec/src/main/AndroidManifest.xml b/brouter-codec/src/main/AndroidManifest.xml deleted file mode 100644 index 152508c..0000000 --- a/brouter-codec/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/brouter-core/src/main/AndroidManifest.xml b/brouter-core/src/main/AndroidManifest.xml deleted file mode 100644 index 04180cf..0000000 --- a/brouter-core/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/brouter-expressions/src/main/AndroidManifest.xml b/brouter-expressions/src/main/AndroidManifest.xml deleted file mode 100644 index 9110ced..0000000 --- a/brouter-expressions/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/brouter-mapaccess/src/main/AndroidManifest.xml b/brouter-mapaccess/src/main/AndroidManifest.xml deleted file mode 100644 index 46f6fa6..0000000 --- a/brouter-mapaccess/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/brouter-util/src/main/AndroidManifest.xml b/brouter-util/src/main/AndroidManifest.xml deleted file mode 100644 index d28b2cc..0000000 --- a/brouter-util/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - From 1573aa52e03d6ca7567df0fd23f3bd6433aaa11e Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Wed, 3 Apr 2024 13:20:20 +0200 Subject: [PATCH 137/173] Upgrade to AGP 8.0 and Gradle 8.4 --- brouter-routing-app/build.gradle | 6 +++++- brouter-server/build.gradle | 1 + build.gradle | 2 +- gradle.properties | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index 2300600..c69c192 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -51,7 +51,7 @@ android { buildTypes { release { - minifyEnabled false + minifyEnabled true debuggable false if (project.hasProperty("RELEASE_STORE_FILE") && RELEASE_STORE_FILE.length() > 0) { signingConfig signingConfigs.release @@ -74,6 +74,10 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + buildFeatures { + aidl true + buildConfig true + } applicationVariants.all { variant -> diff --git a/brouter-server/build.gradle b/brouter-server/build.gradle index 9dac2d4..4af5535 100644 --- a/brouter-server/build.gradle +++ b/brouter-server/build.gradle @@ -35,6 +35,7 @@ application { distZip { dependsOn fatJar + dependsOn ':brouter-routing-app:packageRelease' archiveFileName = 'brouter-' + project.version + '.zip' } diff --git a/build.gradle b/build.gradle index 6f0c33d..f2e698c 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:8.3.1' // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle.properties b/gradle.properties index 147c25a..b94ae53 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,5 +17,5 @@ org.gradle.jvmargs=-Xmx1536m android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=false - - +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cdbfc5f..11a5bd0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip From 2f7ce424809bae96dade5d963f5f8ed775fed237 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Wed, 3 Apr 2024 14:29:49 +0200 Subject: [PATCH 138/173] Upgrade to PMD 7.0.0 and disable violated rules --- build.gradle | 2 +- config/pmd/pmd-ruleset.xml | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index f2e698c..f1348b5 100644 --- a/build.gradle +++ b/build.gradle @@ -52,7 +52,7 @@ allprojects { pmd { consoleOutput = true - toolVersion = "6.51.0" + toolVersion = "7.0.0" rulesMinimumPriority = 5 ruleSetFiles = files("${rootProject.rootDir}/config/pmd/pmd-ruleset.xml") ruleSets = [] diff --git a/config/pmd/pmd-ruleset.xml b/config/pmd/pmd-ruleset.xml index 787e663..dc7ae3e 100644 --- a/config/pmd/pmd-ruleset.xml +++ b/config/pmd/pmd-ruleset.xml @@ -45,11 +45,18 @@ + + + + + + + - + From dd896347a2c3d477f4fe88566ac53116872c8df3 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Wed, 3 Apr 2024 14:41:06 +0200 Subject: [PATCH 139/173] Fix newly detected violations from PMD 7 --- .../src/main/java/btools/codec/MicroCache2.java | 3 ++- .../src/main/java/btools/codec/TagValueCoder.java | 3 ++- .../src/main/java/btools/router/OsmNogoPolygon.java | 4 ++-- brouter-core/src/main/java/btools/router/OsmTrack.java | 4 ++-- .../src/main/java/btools/router/RoutingEngine.java | 10 +++++----- .../main/java/btools/router/RoutingParamCollector.java | 2 +- .../src/main/java/btools/expressions/BExpression.java | 6 +++--- .../java/btools/expressions/BExpressionContext.java | 5 +++-- .../btools/mapcreator/CreateElevationRasterImage.java | 8 ++++---- .../mapcreator/ElevationRasterTileConverter.java | 1 - .../src/main/java/btools/mapcreator/OsmNodeP.java | 10 ++++++---- .../java/btools/mapcreator/RelationStatistics.java | 3 ++- .../src/main/java/btools/mapcreator/WayLinker.java | 3 ++- .../src/main/java/btools/mapaccess/OsmFile.java | 5 ++--- .../src/main/java/btools/mapaccess/OsmNodePairSet.java | 2 +- .../java/btools/mapaccess/WaypointMatcherImpl.java | 2 +- .../src/main/java/btools/server/IpAccessMonitor.java | 2 +- .../src/main/java/btools/server/Polygon.java | 3 ++- .../src/main/java/btools/server/RouteServer.java | 7 ++++--- .../src/main/java/btools/server/SuspectManager.java | 3 ++- .../btools/server/request/ProfileUploadHandler.java | 2 +- .../test/java/btools/server/RequestHandlerTest.java | 3 ++- .../src/main/java/btools/util/LazyArrayOfLists.java | 1 + .../src/main/java/btools/util/StackSampler.java | 8 ++++---- .../src/test/java/btools/util/CompactMapTest.java | 3 ++- .../src/test/java/btools/util/CompactSetTest.java | 3 ++- .../src/test/java/btools/util/DenseLongMapTest.java | 6 ++++-- config/pmd/pmd-ruleset.xml | 10 ++-------- 28 files changed, 65 insertions(+), 57 deletions(-) diff --git a/brouter-codec/src/main/java/btools/codec/MicroCache2.java b/brouter-codec/src/main/java/btools/codec/MicroCache2.java index c7b6878..657aaa9 100644 --- a/brouter-codec/src/main/java/btools/codec/MicroCache2.java +++ b/brouter-codec/src/main/java/btools/codec/MicroCache2.java @@ -1,6 +1,7 @@ package btools.codec; import java.util.HashMap; +import java.util.Map; import btools.util.ByteDataReader; import btools.util.IByteArrayUnifier; @@ -287,7 +288,7 @@ public final class MicroCache2 extends MicroCache { @Override public int encodeMicroCache(byte[] buffer) { - HashMap idMap = new HashMap<>(); + Map idMap = new HashMap<>(); for (int n = 0; n < size; n++) { // loop over nodes idMap.put(expandId(faid[n]), n); } diff --git a/brouter-codec/src/main/java/btools/codec/TagValueCoder.java b/brouter-codec/src/main/java/btools/codec/TagValueCoder.java index 098ea06..f0a3a0f 100644 --- a/brouter-codec/src/main/java/btools/codec/TagValueCoder.java +++ b/brouter-codec/src/main/java/btools/codec/TagValueCoder.java @@ -4,6 +4,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.PriorityQueue; +import java.util.Queue; import btools.util.BitCoderContext; @@ -58,7 +59,7 @@ public final class TagValueCoder { TagValueSet dummy = new TagValueSet(nextTagValueSetId++); identityMap.put(dummy, dummy); } - PriorityQueue queue = new PriorityQueue<>(2 * identityMap.size(), new TagValueSet.FrequencyComparator()); + Queue queue = new PriorityQueue<>(2 * identityMap.size(), new TagValueSet.FrequencyComparator()); queue.addAll(identityMap.values()); while (queue.size() > 1) { TagValueSet node = new TagValueSet(nextTagValueSetId++); diff --git a/brouter-core/src/main/java/btools/router/OsmNogoPolygon.java b/brouter-core/src/main/java/btools/router/OsmNogoPolygon.java index ea828fe..2809f27 100644 --- a/brouter-core/src/main/java/btools/router/OsmNogoPolygon.java +++ b/brouter-core/src/main/java/btools/router/OsmNogoPolygon.java @@ -168,7 +168,7 @@ public class OsmNogoPolygon extends OsmNodeNamed { Point p1 = points.get(0); for (int i = 1; i <= i_last; i++) { final Point p2 = points.get(i); - if (OsmNogoPolygon.isOnLine(px, py, p1.x, p1.y, p2.x, p2.y)) { + if (isOnLine(px, py, p1.x, p1.y, p2.x, p2.y)) { return true; } p1 = p2; @@ -234,7 +234,7 @@ public class OsmNogoPolygon extends OsmNodeNamed { final long p1x = p1.x; final long p1y = p1.y; - if (OsmNogoPolygon.isOnLine(px, py, p0x, p0y, p1x, p1y)) { + if (isOnLine(px, py, p0x, p0y, p1x, p1y)) { return true; } diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index 1c95880..c153f8f 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -166,7 +166,7 @@ public final class OsmTrack { } public List aggregateMessages() { - ArrayList res = new ArrayList<>(); + List res = new ArrayList<>(); MessageData current = null; for (OsmPathElement n : nodes) { if (n.message != null && n.message.wayKeyValues != null) { @@ -188,7 +188,7 @@ public final class OsmTrack { } public List aggregateSpeedProfile() { - ArrayList res = new ArrayList<>(); + List res = new ArrayList<>(); int vmax = -1; int vmaxe = -1; int vmin = -1; diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index 4d706e3..2909e5a 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -187,7 +187,7 @@ public class RoutingEngine extends Thread { OsmTrack[] refTracks = new OsmTrack[nsections]; // used ways for alternatives OsmTrack[] lastTracks = new OsmTrack[nsections]; OsmTrack track = null; - ArrayList messageList = new ArrayList<>(); + List messageList = new ArrayList<>(); for (int i = 0; ; i++) { track = findTrack(refTracks, lastTracks); track.message = "track-length = " + track.distance + " filtered ascend = " + track.ascend @@ -690,9 +690,9 @@ public class RoutingEngine extends Thread { return false; } } - ArrayList removeBackList = new ArrayList<>(); - ArrayList removeForeList = new ArrayList<>(); - ArrayList removeVoiceHintList = new ArrayList<>(); + List removeBackList = new ArrayList<>(); + List removeForeList = new ArrayList<>(); + List removeVoiceHintList = new ArrayList<>(); OsmPathElement last = null; OsmPathElement lastJunction = null; CompactLongMap lastJunctions = new CompactLongMap<>(); @@ -1246,7 +1246,7 @@ public class RoutingEngine extends Thread { addToOpenset(startPath1); addToOpenset(startPath2); } - ArrayList openBorderList = new ArrayList<>(4096); + List openBorderList = new ArrayList<>(4096); boolean memoryPanicMode = false; boolean needNonPanicProcessing = false; diff --git a/brouter-core/src/main/java/btools/router/RoutingParamCollector.java b/brouter-core/src/main/java/btools/router/RoutingParamCollector.java index 99f5b3b..b8d7f6b 100644 --- a/brouter-core/src/main/java/btools/router/RoutingParamCollector.java +++ b/brouter-core/src/main/java/btools/router/RoutingParamCollector.java @@ -101,7 +101,7 @@ public class RoutingParamCollector { * @throws UnsupportedEncodingException */ public Map getUrlParams(String url) throws UnsupportedEncodingException { - HashMap params = new HashMap<>(); + Map params = new HashMap<>(); String decoded = URLDecoder.decode(url, "UTF-8"); StringTokenizer tk = new StringTokenizer(decoded, "?&"); while (tk.hasMoreTokens()) { diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpression.java b/brouter-expressions/src/main/java/btools/expressions/BExpression.java index b083f58..d266780 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpression.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpression.java @@ -242,15 +242,15 @@ final class BExpression { } // parse operands if (nops > 0) { - exp.op1 = BExpression.parse(ctx, level + 1, exp.typ == ASSIGN_EXP ? "=" : null); + exp.op1 = parse(ctx, level + 1, exp.typ == ASSIGN_EXP ? "=" : null); } if (nops > 1) { if (ifThenElse) checkExpectedToken(ctx, "then"); - exp.op2 = BExpression.parse(ctx, level + 1, null); + exp.op2 = parse(ctx, level + 1, null); } if (nops > 2) { if (ifThenElse) checkExpectedToken(ctx, "else"); - exp.op3 = BExpression.parse(ctx, level + 1, null); + exp.op3 = parse(ctx, level + 1, null); } if (brackets) { checkExpectedToken(ctx, ")"); diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java index 786439a..349baa5 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.NavigableMap; import java.util.Random; import java.util.StringTokenizer; import java.util.TreeMap; @@ -227,7 +228,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { } public List getKeyValueList(boolean inverseDirection, byte[] ab) { - ArrayList res = new ArrayList<>(); + List res = new ArrayList<>(); decode(lookupData, inverseDirection, ab); for (int inum = 0; inum < lookupValues.size(); inum++) { // loop over lookup names BExpressionLookupValue[] va = lookupValues.get(inum); @@ -433,7 +434,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { public void dumpStatistics() { - TreeMap counts = new TreeMap<>(); + NavigableMap counts = new TreeMap<>(); // first count for (String name : lookupNumbers.keySet()) { int cnt = 0; diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/CreateElevationRasterImage.java b/brouter-map-creator/src/main/java/btools/mapcreator/CreateElevationRasterImage.java index 935965f..4cbd3c5 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/CreateElevationRasterImage.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/CreateElevationRasterImage.java @@ -184,10 +184,10 @@ public class CreateElevationRasterImage { if (DEBUG) System.out.println(line); String[] sa = line.split(","); if (!line.startsWith("#") && sa.length == 4) { - short e = Short.valueOf(sa[0].trim()); - short r = Short.valueOf(sa[1].trim()); - short g = Short.valueOf(sa[2].trim()); - short b = Short.valueOf(sa[3].trim()); + short e = Short.parseShort(sa[0].trim()); + short r = Short.parseShort(sa[1].trim()); + short g = Short.parseShort(sa[2].trim()); + short b = Short.parseShort(sa[3].trim()); colorMap.put(e, new Color(r, g, b)); } // read next line diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/ElevationRasterTileConverter.java b/brouter-map-creator/src/main/java/btools/mapcreator/ElevationRasterTileConverter.java index 9b251fc..229dcfd 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/ElevationRasterTileConverter.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/ElevationRasterTileConverter.java @@ -90,7 +90,6 @@ public class ElevationRasterTileConverter { } else { System.out.println("usage: java [arc seconds (1 or 3,default=3)] [hgt-fallback-data-dir]"); System.out.println("or java all [arc seconds (1 or 3, default=3)] [hgt-fallback-data-dir]"); - return; } } diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java b/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java index 572554e..b945582 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java @@ -8,6 +8,8 @@ package btools.mapcreator; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; import btools.codec.MicroCache; import btools.codec.MicroCache2; @@ -105,7 +107,7 @@ public class OsmNodeP extends OsmLinkP { } public void checkDuplicateTargets() { - HashMap targets = new HashMap<>(); + Map targets = new HashMap<>(); for (OsmLinkP link0 = getFirstLink(); link0 != null; link0 = link0.getNext(this)) { OsmLinkP link = link0; @@ -165,14 +167,14 @@ public class OsmNodeP extends OsmLinkP { mc.writeVarBytes(getNodeDecsription()); // buffer internal reverse links - ArrayList internalReverse = new ArrayList<>(); + List internalReverse = new ArrayList<>(); for (OsmLinkP link0 = getFirstLink(); link0 != null; link0 = link0.getNext(this)) { OsmLinkP link = link0; OsmNodeP origin = this; OsmNodeP target = null; - ArrayList linkNodes = new ArrayList<>(); + List linkNodes = new ArrayList<>(); linkNodes.add(this); // first pass just to see if that link is consistent @@ -226,7 +228,7 @@ public class OsmNodeP extends OsmLinkP { origin = this; for (int i = 1; i < linkNodes.size() - 1; i++) { OsmNodeP tranferNode = linkNodes.get(i); - if ((tranferNode.bits & OsmNodeP.DP_SURVIVOR_BIT) != 0) { + if ((tranferNode.bits & DP_SURVIVOR_BIT) != 0) { mc.writeVarLengthSigned(tranferNode.ilon - origin.ilon); mc.writeVarLengthSigned(tranferNode.ilat - origin.ilat); mc.writeVarLengthSigned(tranferNode.getSElev() - origin.getSElev()); diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/RelationStatistics.java b/brouter-map-creator/src/main/java/btools/mapcreator/RelationStatistics.java index a6d5e04..e239d89 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/RelationStatistics.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/RelationStatistics.java @@ -4,6 +4,7 @@ import java.io.DataInputStream; import java.io.EOFException; import java.io.File; import java.util.HashMap; +import java.util.Map; /** * WayCutter does 2 step in map-processing: @@ -25,7 +26,7 @@ public class RelationStatistics extends MapCreatorBase { } public void process(File relationFileIn) throws Exception { - HashMap relstats = new HashMap<>(); + Map relstats = new HashMap<>(); DataInputStream dis = createInStream(relationFileIn); try { diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java b/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java index 0a01b93..e0a3c31 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java @@ -9,6 +9,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.RandomAccessFile; import java.util.List; +import java.util.Map; import java.util.TreeMap; import btools.codec.DataBuffers; @@ -481,7 +482,7 @@ public class WayLinker extends MapCreatorBase implements Runnable { MicroCache mc = new MicroCache2(size, abBuf2, lonIdxDiv, latIdxDiv, divisor); // sort via treemap - TreeMap sortedList = new TreeMap<>(); + Map sortedList = new TreeMap<>(); for (OsmNodeP n : subList) { long longId = n.getIdFromPos(); int shrinkid = mc.shrinkId(longId); diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java index 67eff29..9b5724b 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java @@ -31,7 +31,6 @@ final class OsmFile { private int divisor; private int cellsize; - private int ncaches; private int indexsize; protected byte elevationType = 3; @@ -47,7 +46,7 @@ final class OsmFile { elevationType = rafile.elevationType; cellsize = 1000000 / divisor; - ncaches = divisor * divisor; + int ncaches = divisor * divisor; indexsize = ncaches * 4; byte[] iobuffer = dataBuffers.iobuffer; @@ -143,7 +142,7 @@ final class OsmFile { new DirectWeaver(bc, dataBuffers, lonIdx, latIdx, divisor, wayValidator, waypointMatcher, hollowNodes); return MicroCache.emptyNonVirgin; } finally { - // crc check only if the buffer has not been fully read + // crc check only if the buffer has not been fully read int readBytes = (bc.getReadingBitPosition() + 7) >> 3; if (readBytes != asize - 4) { int crcData = Crc32.crc(ab, 0, asize - 4); diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNodePairSet.java b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNodePairSet.java index 3fe874e..19aec72 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNodePairSet.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNodePairSet.java @@ -21,7 +21,7 @@ public class OsmNodePairSet { n2a = new long[maxTempNodes]; } - private static class OsmNodePair { + private static final class OsmNodePair { public long node2; public OsmNodePair next; } diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/WaypointMatcherImpl.java b/brouter-mapaccess/src/main/java/btools/mapaccess/WaypointMatcherImpl.java index 85ab9b5..e70d3be 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/WaypointMatcherImpl.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/WaypointMatcherImpl.java @@ -51,7 +51,7 @@ public final class WaypointMatcherImpl implements WaypointMatcher { } // sort result list - comparator = new Comparator() { + comparator = new Comparator<>() { @Override public int compare(MatchedWaypoint mw1, MatchedWaypoint mw2) { int cmpDist = Double.compare(mw1.radius, mw2.radius); diff --git a/brouter-server/src/main/java/btools/server/IpAccessMonitor.java b/brouter-server/src/main/java/btools/server/IpAccessMonitor.java index a9413e2..3484b0e 100644 --- a/brouter-server/src/main/java/btools/server/IpAccessMonitor.java +++ b/brouter-server/src/main/java/btools/server/IpAccessMonitor.java @@ -31,7 +31,7 @@ public class IpAccessMonitor { } private static void cleanup(long t) { - HashMap newMap = new HashMap<>(ipAccess.size()); + Map newMap = new HashMap<>(ipAccess.size()); for (Map.Entry e : ipAccess.entrySet()) { if (t - e.getValue().longValue() <= MAX_IDLE) { newMap.put(e.getKey(), e.getValue()); diff --git a/brouter-server/src/main/java/btools/server/Polygon.java b/brouter-server/src/main/java/btools/server/Polygon.java index 9115ef1..a711de5 100644 --- a/brouter-server/src/main/java/btools/server/Polygon.java +++ b/brouter-server/src/main/java/btools/server/Polygon.java @@ -3,6 +3,7 @@ package btools.server; import java.io.BufferedReader; import java.io.IOException; import java.util.ArrayList; +import java.util.List; import java.util.StringTokenizer; public class Polygon { @@ -15,7 +16,7 @@ public class Polygon { private int maxy = Integer.MIN_VALUE; public Polygon(BufferedReader br) throws IOException { - ArrayList lines = new ArrayList<>(); + List lines = new ArrayList<>(); for (; ; ) { String line = br.readLine(); diff --git a/brouter-server/src/main/java/btools/server/RouteServer.java b/brouter-server/src/main/java/btools/server/RouteServer.java index 0685eac..b1f9cc8 100644 --- a/brouter-server/src/main/java/btools/server/RouteServer.java +++ b/brouter-server/src/main/java/btools/server/RouteServer.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.PriorityQueue; +import java.util.Queue; import java.util.StringTokenizer; import java.util.zip.GZIPOutputStream; @@ -299,7 +300,7 @@ public class RouteServer extends Thread implements Comparable { ProfileCache.setSize(2 * maxthreads); - PriorityQueue threadQueue = new PriorityQueue<>(); + Queue threadQueue = new PriorityQueue<>(); ServerSocket serverSocket = args.length > 5 ? new ServerSocket(Integer.parseInt(args[3]), 100, InetAddress.getByName(args[5])) : new ServerSocket(Integer.parseInt(args[3])); @@ -364,7 +365,7 @@ public class RouteServer extends Thread implements Comparable { private static Map getUrlParams(String url) throws UnsupportedEncodingException { - HashMap params = new HashMap<>(); + Map params = new HashMap<>(); String decoded = URLDecoder.decode(url, "UTF-8"); StringTokenizer tk = new StringTokenizer(decoded, "?&"); while (tk.hasMoreTokens()) { @@ -417,7 +418,7 @@ public class RouteServer extends Thread implements Comparable { bw.write("\n"); } - private static void cleanupThreadQueue(PriorityQueue threadQueue) { + private static void cleanupThreadQueue(Queue threadQueue) { for (; ; ) { boolean removedItem = false; for (RouteServer t : threadQueue) { diff --git a/brouter-server/src/main/java/btools/server/SuspectManager.java b/brouter-server/src/main/java/btools/server/SuspectManager.java index 303c699..8e67933 100644 --- a/brouter-server/src/main/java/btools/server/SuspectManager.java +++ b/brouter-server/src/main/java/btools/server/SuspectManager.java @@ -9,6 +9,7 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.StringTokenizer; import java.util.TreeSet; @@ -244,7 +245,7 @@ public class SuspectManager extends Thread { bw.write("\n"); File countryParent = new File("worldpolys" + country); File[] files = countryParent.listFiles(); - TreeSet names = new TreeSet<>(); + Set names = new TreeSet<>(); for (File f : files) { String name = f.getName(); if (name.endsWith(".poly")) { diff --git a/brouter-server/src/main/java/btools/server/request/ProfileUploadHandler.java b/brouter-server/src/main/java/btools/server/request/ProfileUploadHandler.java index 78a39cf..81d52b0 100644 --- a/brouter-server/src/main/java/btools/server/request/ProfileUploadHandler.java +++ b/brouter-server/src/main/java/btools/server/request/ProfileUploadHandler.java @@ -37,7 +37,7 @@ public class ProfileUploadHandler { String id; if (profileId != null) { // update existing file when id appended - id = profileId.substring(ProfileUploadHandler.CUSTOM_PREFIX.length()); + id = profileId.substring(CUSTOM_PREFIX.length()); } else { id = "" + System.currentTimeMillis(); } diff --git a/brouter-server/src/test/java/btools/server/RequestHandlerTest.java b/brouter-server/src/test/java/btools/server/RequestHandlerTest.java index 2d5f698..81d03cb 100644 --- a/brouter-server/src/test/java/btools/server/RequestHandlerTest.java +++ b/brouter-server/src/test/java/btools/server/RequestHandlerTest.java @@ -5,6 +5,7 @@ import org.junit.Ignore; import org.junit.Test; import java.util.HashMap; +import java.util.Map; import btools.router.RoutingContext; import btools.server.request.ServerHandler; @@ -13,7 +14,7 @@ public class RequestHandlerTest { @Test @Ignore("Parameters are currently handled by RouteServer, not RequestHandler") public void parseParameters() { - HashMap params = new HashMap<>(); + Map params = new HashMap<>(); params.put("lonlats", "8.799297,49.565883|8.811764,49.563606"); params.put("profile", "trekking"); params.put("alternativeidx", "0"); diff --git a/brouter-util/src/main/java/btools/util/LazyArrayOfLists.java b/brouter-util/src/main/java/btools/util/LazyArrayOfLists.java index d8b05a5..b9f6d0c 100644 --- a/brouter-util/src/main/java/btools/util/LazyArrayOfLists.java +++ b/brouter-util/src/main/java/btools/util/LazyArrayOfLists.java @@ -9,6 +9,7 @@ import java.util.List; * * @author ab */ +@SuppressWarnings("PMD.LooseCoupling") public class LazyArrayOfLists { private List> lists; diff --git a/brouter-util/src/main/java/btools/util/StackSampler.java b/brouter-util/src/main/java/btools/util/StackSampler.java index 6b2e304..753fec4 100644 --- a/brouter-util/src/main/java/btools/util/StackSampler.java +++ b/brouter-util/src/main/java/btools/util/StackSampler.java @@ -51,12 +51,12 @@ public class StackSampler extends Thread { try { int wait1 = rand.nextInt(interval); int wait2 = interval - wait1; - Thread.sleep(wait1); + sleep(wait1); StringBuilder sb = new StringBuilder(df.format(new Date()) + " THREADDUMP\n"); - Map allThreads = Thread.getAllStackTraces(); + Map allThreads = getAllStackTraces(); for (Map.Entry e : allThreads.entrySet()) { Thread t = e.getKey(); - if (t == Thread.currentThread()) { + if (t == currentThread()) { continue; // not me } @@ -76,7 +76,7 @@ public class StackSampler extends Thread { flushCnt = 0; bw.flush(); } - Thread.sleep(wait2); + sleep(wait2); } catch (Exception e) { // ignore } diff --git a/brouter-util/src/test/java/btools/util/CompactMapTest.java b/brouter-util/src/test/java/btools/util/CompactMapTest.java index 1217497..b2dc0af 100644 --- a/brouter-util/src/test/java/btools/util/CompactMapTest.java +++ b/brouter-util/src/test/java/btools/util/CompactMapTest.java @@ -4,6 +4,7 @@ import org.junit.Assert; import org.junit.Test; import java.util.HashMap; +import java.util.Map; import java.util.Random; public class CompactMapTest { @@ -22,7 +23,7 @@ public class CompactMapTest { private void hashMapComparison(int mapsize, int trycount) { Random rand = new Random(12345); - HashMap hmap = new HashMap<>(); + Map hmap = new HashMap<>(); CompactLongMap cmap_slow = new CompactLongMap<>(); CompactLongMap cmap_fast = new CompactLongMap<>(); diff --git a/brouter-util/src/test/java/btools/util/CompactSetTest.java b/brouter-util/src/test/java/btools/util/CompactSetTest.java index e0f5340..1a4f130 100644 --- a/brouter-util/src/test/java/btools/util/CompactSetTest.java +++ b/brouter-util/src/test/java/btools/util/CompactSetTest.java @@ -5,6 +5,7 @@ import org.junit.Test; import java.util.HashSet; import java.util.Random; +import java.util.Set; public class CompactSetTest { @Test @@ -22,7 +23,7 @@ public class CompactSetTest { private void hashSetComparison(int setsize, int trycount) { Random rand = new Random(12345); - HashSet hset = new HashSet<>(); + Set hset = new HashSet<>(); CompactLongSet cset_slow = new CompactLongSet(); CompactLongSet cset_fast = new CompactLongSet(); diff --git a/brouter-util/src/test/java/btools/util/DenseLongMapTest.java b/brouter-util/src/test/java/btools/util/DenseLongMapTest.java index 57e867a..a40ba83 100644 --- a/brouter-util/src/test/java/btools/util/DenseLongMapTest.java +++ b/brouter-util/src/test/java/btools/util/DenseLongMapTest.java @@ -5,7 +5,9 @@ import org.junit.Test; import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Random; +import java.util.Set; public class DenseLongMapTest { @Test @@ -16,7 +18,7 @@ public class DenseLongMapTest { private void hashMapComparison(int mapsize, int trycount, long keyrange) { Random rand = new Random(12345); - HashMap hmap = new HashMap<>(); + Map hmap = new HashMap<>(); DenseLongMap dmap = new DenseLongMap(512); for (int i = 0; i < mapsize; i++) { @@ -48,7 +50,7 @@ public class DenseLongMapTest { int trycount = 100000; Random rand = new Random(12345); - HashSet hset = new HashSet<>(); + Set hset = new HashSet<>(); DenseLongMap dmap = new DenseLongMap(512); for (int i = 0; i < mapputs; i++) { diff --git a/config/pmd/pmd-ruleset.xml b/config/pmd/pmd-ruleset.xml index dc7ae3e..dc25d1b 100644 --- a/config/pmd/pmd-ruleset.xml +++ b/config/pmd/pmd-ruleset.xml @@ -29,6 +29,7 @@ + @@ -45,18 +46,11 @@ - - - - - - - - + From c73a8cebb82363b604d1a897c94716d8bf255076 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Wed, 3 Apr 2024 14:50:05 +0200 Subject: [PATCH 140/173] Enable PMD rule UnnecessaryBoxing and fix violations --- .../main/java/btools/codec/MicroCache2.java | 2 +- .../src/main/java/btools/router/OsmPath.java | 2 +- .../expressions/BExpressionContext.java | 51 +++++++++---------- .../java/btools/server/IpAccessMonitor.java | 4 +- .../java/btools/util/DenseLongMapTest.java | 2 +- config/pmd/pmd-ruleset.xml | 3 +- 6 files changed, 30 insertions(+), 34 deletions(-) diff --git a/brouter-codec/src/main/java/btools/codec/MicroCache2.java b/brouter-codec/src/main/java/btools/codec/MicroCache2.java index 657aaa9..a09d19e 100644 --- a/brouter-codec/src/main/java/btools/codec/MicroCache2.java +++ b/brouter-codec/src/main/java/btools/codec/MicroCache2.java @@ -419,7 +419,7 @@ public final class MicroCache2 extends MicroCache { nlinks++; if (isInternal) { - int nodeIdx = idx.intValue(); + int nodeIdx = idx; if (dodebug) System.out.println("*** target nodeIdx=" + nodeIdx); if (nodeIdx == n) throw new RuntimeException("ups: self ref?"); nodeIdxDiff.encodeSignedValue(nodeIdx - n); diff --git a/brouter-core/src/main/java/btools/router/OsmPath.java b/brouter-core/src/main/java/btools/router/OsmPath.java index 1bf318b..ce99dce 100644 --- a/brouter-core/src/main/java/btools/router/OsmPath.java +++ b/brouter-core/src/main/java/btools/router/OsmPath.java @@ -296,7 +296,7 @@ abstract class OsmPath implements OsmLinkHolder { // apply a start-direction if appropriate (by faking the origin position) if (isStartpoint) { if (rc.startDirectionValid) { - double dir = rc.startDirection.intValue() * CheapRuler.DEG_TO_RAD; + double dir = rc.startDirection * CheapRuler.DEG_TO_RAD; double[] lonlat2m = CheapRuler.getLonLatToMeterScales((lon0 + lat1) >> 1); lon0 = lon1 - (int) (1000. * Math.sin(dir) / lonlat2m[0]); lat0 = lat1 - (int) (1000. * Math.cos(dir) / lonlat2m[1]); diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java index 349baa5..a459f38 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -246,7 +246,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { public int getLookupKey(String name) { int res = -1; try { - res = lookupNumbers.get(name).intValue(); + res = lookupNumbers.get(name); } catch (Exception e) { } return res; @@ -438,7 +438,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { // first count for (String name : lookupNumbers.keySet()) { int cnt = 0; - int inum = lookupNumbers.get(name).intValue(); + int inum = lookupNumbers.get(name); int[] histo = lookupHistograms.get(inum); // if ( histo.length == 500 ) continue; for (int i = 2; i < histo.length; i++) { @@ -451,7 +451,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { String key = counts.lastEntry().getKey(); String name = counts.get(key); counts.remove(key); - int inum = lookupNumbers.get(name).intValue(); + int inum = lookupNumbers.get(name); BExpressionLookupValue[] values = lookupValues.get(inum); int[] histo = lookupHistograms.get(inum); if (values.length == 1000) continue; @@ -508,7 +508,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { public String variableName(int idx) { for (Map.Entry e : variableNumbers.entrySet()) { - if (e.getValue().intValue() == idx) { + if (e.getValue() == idx) { return e.getKey(); } } @@ -545,9 +545,8 @@ public abstract class BExpressionContext implements IByteArrayUnifier { } // look for that value - int inum = num.intValue(); - BExpressionLookupValue[] values = lookupValues.get(inum); - int[] histo = lookupHistograms.get(inum); + BExpressionLookupValue[] values = lookupValues.get(num); + int[] histo = lookupHistograms.get(num); int i = 0; boolean bFoundAsterix = false; for (; i < values.length; i++) { @@ -559,7 +558,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { if (lookupData2 != null) { // do not create unknown value for external data array, // record as 'unknown' instead - lookupData2[inum] = 1; // 1 == unknown + lookupData2[num] = 1; // 1 == unknown if (bFoundAsterix) { // found value for lookup * //System.out.println( "add unknown " + name + " " + value ); @@ -653,11 +652,11 @@ public abstract class BExpressionContext implements IByteArrayUnifier { // found negative maxdraft values // no negative values // values are float with 2 decimals - lookupData2[inum] = 1000 + (int) (Math.abs(Float.parseFloat(value)) * 100f); + lookupData2[num] = 1000 + (int) (Math.abs(Float.parseFloat(value)) * 100f); } catch (Exception e) { // ignore errors System.err.println("error for " + name + " " + org + " trans " + value + " " + e.getMessage()); - lookupData2[inum] = 0; + lookupData2[num] = 0; } } return newValue; @@ -678,15 +677,15 @@ public abstract class BExpressionContext implements IByteArrayUnifier { histo = nhisto; newValue = new BExpressionLookupValue(value); values[i] = newValue; - lookupHistograms.set(inum, histo); - lookupValues.set(inum, values); + lookupHistograms.set(num, histo); + lookupValues.set(num, values); } histo[i]++; // finally remember the actual data - if (lookupData2 != null) lookupData2[inum] = i; - else lookupData[inum] = i; + if (lookupData2 != null) lookupData2[num] = i; + else lookupData[num] = i; return newValue; } @@ -701,11 +700,10 @@ public abstract class BExpressionContext implements IByteArrayUnifier { } // look for that value - int inum = num.intValue(); - int nvalues = lookupValues.get(inum).length; + int nvalues = lookupValues.get(num).length; if (valueIndex < 0 || valueIndex >= nvalues) throw new IllegalArgumentException("value index out of range for name " + name + ": " + valueIndex); - lookupData[inum] = valueIndex; + lookupData[num] = valueIndex; } @@ -722,9 +720,8 @@ public abstract class BExpressionContext implements IByteArrayUnifier { } // look for that value - int inum = num.intValue(); - int nvalues = lookupValues.get(inum).length; - int oldValueIndex = lookupData[inum]; + int nvalues = lookupValues.get(num).length; + int oldValueIndex = lookupData[num]; if (oldValueIndex > 1 && oldValueIndex < valueIndex) { return; } @@ -733,12 +730,12 @@ public abstract class BExpressionContext implements IByteArrayUnifier { } if (valueIndex < 0) throw new IllegalArgumentException("value index out of range for name " + name + ": " + valueIndex); - lookupData[inum] = valueIndex; + lookupData[num] = valueIndex; } public boolean getBooleanLookupValue(String name) { Integer num = lookupNumbers.get(name); - return num != null && lookupData[num.intValue()] == 2; + return num != null && lookupData[num] == 2; } public int getOutputVariableIndex(String name, boolean mustExist) { @@ -850,7 +847,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { public void setVariableValue(String name, float value, boolean create) { Integer num = variableNumbers.get(name); if (num != null) { - variableData[num.intValue()] = value; + variableData[num] = value; } else if (create) { num = getVariableIdx(name, create); float[] readOnlyData = variableData; @@ -859,13 +856,13 @@ public abstract class BExpressionContext implements IByteArrayUnifier { for (int i = 0; i < minWriteIdx; i++) { variableData[i] = readOnlyData[i]; } - variableData[num.intValue()] = value; + variableData[num] = value; } } public float getVariableValue(String name, float defaultValue) { Integer num = variableNumbers.get(name); - return num == null ? defaultValue : getVariableValue(num.intValue()); + return num == null ? defaultValue : getVariableValue(num); } float getVariableValue(int variableIdx) { @@ -883,7 +880,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { return -1; } } - return num.intValue(); + return num; } int getMinWriteIdx() { @@ -901,7 +898,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { public int getLookupNameIdx(String name) { Integer num = lookupNumbers.get(name); - return num == null ? -1 : num.intValue(); + return num == null ? -1 : num; } public final void markLookupIdxUsed(int idx) { diff --git a/brouter-server/src/main/java/btools/server/IpAccessMonitor.java b/brouter-server/src/main/java/btools/server/IpAccessMonitor.java index 3484b0e..6545db7 100644 --- a/brouter-server/src/main/java/btools/server/IpAccessMonitor.java +++ b/brouter-server/src/main/java/btools/server/IpAccessMonitor.java @@ -15,7 +15,7 @@ public class IpAccessMonitor { synchronized (sync) { Long lastTime = ipAccess.get(ip); ipAccess.put(ip, t); - return lastTime == null || t - lastTime.longValue() > MAX_IDLE; + return lastTime == null || t - lastTime > MAX_IDLE; } } @@ -33,7 +33,7 @@ public class IpAccessMonitor { private static void cleanup(long t) { Map newMap = new HashMap<>(ipAccess.size()); for (Map.Entry e : ipAccess.entrySet()) { - if (t - e.getValue().longValue() <= MAX_IDLE) { + if (t - e.getValue() <= MAX_IDLE) { newMap.put(e.getKey(), e.getValue()); } } diff --git a/brouter-util/src/test/java/btools/util/DenseLongMapTest.java b/brouter-util/src/test/java/btools/util/DenseLongMapTest.java index a40ba83..85936d7 100644 --- a/brouter-util/src/test/java/btools/util/DenseLongMapTest.java +++ b/brouter-util/src/test/java/btools/util/DenseLongMapTest.java @@ -34,7 +34,7 @@ public class DenseLongMapTest { long k = (long) (rand.nextDouble() * keyrange); Long KK = k; Integer VV = hmap.get(KK); - int hvalue = VV == null ? -1 : VV.intValue(); + int hvalue = VV == null ? -1 : VV; int dvalue = dmap.getInt(k); if (hvalue != dvalue) { diff --git a/config/pmd/pmd-ruleset.xml b/config/pmd/pmd-ruleset.xml index dc25d1b..1837468 100644 --- a/config/pmd/pmd-ruleset.xml +++ b/config/pmd/pmd-ruleset.xml @@ -52,7 +52,6 @@ - - + From 6c22d7d01263f21cf50ac3c0bf7241e53c8ac278 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Wed, 3 Apr 2024 22:31:09 +0200 Subject: [PATCH 141/173] Update workflows to java 17 --- .github/workflows/gradle-publish.yml | 4 ++-- .github/workflows/gradle.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 57cd7a4..789adfd 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -20,10 +20,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: - java-version: '11' + java-version: '17' distribution: 'temurin' server-id: github # Value of the distributionManagement/repository/id field of the pom.xml settings-path: ${{ github.workspace }} # location for the settings.xml file diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 4fd8bde..952ae9d 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -16,10 +16,10 @@ jobs: environment: BRouter steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: - java-version: '11' + java-version: '17' distribution: 'temurin' cache: gradle - name: Create local.properties From 616266084c54783935cafe204018f36910a86eea Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 5 Apr 2024 13:29:52 +0200 Subject: [PATCH 142/173] changed version numbers --- brouter-core/src/main/java/btools/router/OsmTrack.java | 4 ++-- brouter-routing-app/build.gradle | 2 +- build.gradle | 2 +- docs/revisions.md | 5 +++-- 4 files changed, 7 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 a6c9817..f18d4da 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.3"; - final public static String versionDate = "19082023"; + final public static String version = "1.7.4"; + final public static String versionDate = "09042024"; // 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 2300600..4a160af 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -11,7 +11,7 @@ android { namespace 'btools.routingapp' applicationId "btools.routingapp" - versionCode 50 + versionCode 51 versionName project.version resValue('string', 'app_version', defaultConfig.versionName) diff --git a/build.gradle b/build.gradle index 6f0c33d..b8dd9aa 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ allprojects { // app: build.gradle (versionCode only) // OsmTrack (version and versionDate) // docs revisions.md (version and versionDate) - project.version "1.7.3" + project.version "1.7.4" group 'org.btools' repositories { diff --git a/docs/revisions.md b/docs/revisions.md index c2647e6..97a53c3 100644 --- a/docs/revisions.md +++ b/docs/revisions.md @@ -3,7 +3,7 @@ (ZIP-Archives including APK, readme + profiles) -### Changes since last version +### [brouter-1.7.4.zip](../brouter_bin/brouter-1.7.4.zip) (current revision, 09.04.2024) Library @@ -25,7 +25,8 @@ Android [Solved issues](https://github.com/abrensch/brouter/issues?q=is%3Aissue+milestone%3A%22Version+1.7.4%22+is%3Aclosed) -### [brouter-1.7.3.zip](../brouter_bin/brouter-1.7.3.zip) (current revision, 19.08.2023) + +### [brouter-1.7.3.zip](../brouter_bin/brouter-1.7.3.zip) (19.08.2023) - Minor bug fixes From b8929ab414bd07ef2e409f3ea505bba9e412fefa Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Fri, 5 Apr 2024 23:09:00 +0200 Subject: [PATCH 143/173] Global build ignore --- .gitignore | 2 +- brouter-codec/.gitignore | 1 - brouter-core/.gitignore | 1 - brouter-expressions/.gitignore | 1 - brouter-map-creator/.gitignore | 1 - brouter-mapaccess/.gitignore | 1 - brouter-routing-app/.gitignore | 1 - brouter-server/.gitignore | 1 - brouter-util/.gitignore | 1 - 9 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 brouter-codec/.gitignore delete mode 100644 brouter-core/.gitignore delete mode 100644 brouter-expressions/.gitignore delete mode 100644 brouter-map-creator/.gitignore delete mode 100644 brouter-mapaccess/.gitignore delete mode 100644 brouter-routing-app/.gitignore delete mode 100644 brouter-server/.gitignore delete mode 100644 brouter-util/.gitignore diff --git a/.gitignore b/.gitignore index ddd96e8..4a88787 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.iml .gradle .idea/ +build /local.properties /.idea/caches /.idea/gradle.xml @@ -10,7 +11,6 @@ /.idea/navEditor.xml /.idea/assetWizardSettings.xml .DS_Store -/build /captures .externalNativeBuild .cxx diff --git a/brouter-codec/.gitignore b/brouter-codec/.gitignore deleted file mode 100644 index 84c048a..0000000 --- a/brouter-codec/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build/ diff --git a/brouter-core/.gitignore b/brouter-core/.gitignore deleted file mode 100644 index 84c048a..0000000 --- a/brouter-core/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build/ diff --git a/brouter-expressions/.gitignore b/brouter-expressions/.gitignore deleted file mode 100644 index 84c048a..0000000 --- a/brouter-expressions/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build/ diff --git a/brouter-map-creator/.gitignore b/brouter-map-creator/.gitignore deleted file mode 100644 index 84c048a..0000000 --- a/brouter-map-creator/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build/ diff --git a/brouter-mapaccess/.gitignore b/brouter-mapaccess/.gitignore deleted file mode 100644 index 84c048a..0000000 --- a/brouter-mapaccess/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build/ diff --git a/brouter-routing-app/.gitignore b/brouter-routing-app/.gitignore deleted file mode 100644 index 84c048a..0000000 --- a/brouter-routing-app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build/ diff --git a/brouter-server/.gitignore b/brouter-server/.gitignore deleted file mode 100644 index 84c048a..0000000 --- a/brouter-server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build/ diff --git a/brouter-util/.gitignore b/brouter-util/.gitignore deleted file mode 100644 index 84c048a..0000000 --- a/brouter-util/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build/ From 5d4065d141eda7133d31c5e8ce9b5954a0b861ed Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Fri, 5 Apr 2024 23:11:58 +0200 Subject: [PATCH 144/173] Use conventions instead of cross-project configuration gradle userguide suggests to avoid allprojects/subprojects and use conventions instead https://docs.gradle.org/current/userguide/sharing_build_logic_between_subprojects.html#sec:convention_plugins_vs_cross_configuration --- brouter-codec/build.gradle | 3 +- brouter-core/build.gradle | 3 +- brouter-expressions/build.gradle | 3 +- brouter-map-creator/build.gradle | 4 +- brouter-mapaccess/build.gradle | 3 +- brouter-routing-app/build.gradle | 7 +++ brouter-server/build.gradle | 6 +-- brouter-util/build.gradle | 6 +-- build.gradle | 47 ------------------- buildSrc/build.gradle | 3 ++ .../brouter.application-conventions.gradle | 8 ++++ .../groovy/brouter.java-conventions.gradle | 25 ++++++++++ .../groovy/brouter.library-conventions.gradle | 21 +++++++++ .../groovy/brouter.version-conventions.gradle | 7 +++ settings.gradle | 4 +- 15 files changed, 80 insertions(+), 70 deletions(-) create mode 100644 buildSrc/build.gradle create mode 100644 buildSrc/src/main/groovy/brouter.application-conventions.gradle create mode 100644 buildSrc/src/main/groovy/brouter.java-conventions.gradle create mode 100644 buildSrc/src/main/groovy/brouter.library-conventions.gradle create mode 100644 buildSrc/src/main/groovy/brouter.version-conventions.gradle diff --git a/brouter-codec/build.gradle b/brouter-codec/build.gradle index 22faffe..7224590 100644 --- a/brouter-codec/build.gradle +++ b/brouter-codec/build.gradle @@ -1,8 +1,7 @@ plugins { - id 'java-library' + id 'brouter.library-conventions' } dependencies { implementation project(':brouter-util') - testImplementation 'junit:junit:4.13.1' } diff --git a/brouter-core/build.gradle b/brouter-core/build.gradle index cb7dde5..e4c865f 100644 --- a/brouter-core/build.gradle +++ b/brouter-core/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'java-library' + id 'brouter.library-conventions' } dependencies { @@ -7,7 +7,6 @@ dependencies { implementation project(':brouter-util') implementation project(':brouter-expressions') implementation project(':brouter-codec') - testImplementation 'junit:junit:4.13.2' } // MapcreatorTest generates segments which are used in tests diff --git a/brouter-expressions/build.gradle b/brouter-expressions/build.gradle index ff146d6..fd71fbf 100644 --- a/brouter-expressions/build.gradle +++ b/brouter-expressions/build.gradle @@ -1,9 +1,8 @@ plugins { - id 'java-library' + id 'brouter.library-conventions' } dependencies { implementation project(':brouter-util') implementation project(':brouter-codec') - testImplementation 'junit:junit:4.13.1' } diff --git a/brouter-map-creator/build.gradle b/brouter-map-creator/build.gradle index 7c48259..3f8c263 100644 --- a/brouter-map-creator/build.gradle +++ b/brouter-map-creator/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'java-library' + id 'brouter.application-conventions' } dependencies { @@ -8,6 +8,4 @@ dependencies { implementation project(':brouter-expressions') implementation group: 'org.openstreetmap.osmosis', name: 'osmosis-osm-binary', version: '0.48.3' - - testImplementation('junit:junit:4.13.1') } diff --git a/brouter-mapaccess/build.gradle b/brouter-mapaccess/build.gradle index 40189cb..b17e589 100644 --- a/brouter-mapaccess/build.gradle +++ b/brouter-mapaccess/build.gradle @@ -1,12 +1,11 @@ plugins { - id 'java-library' + id 'brouter.library-conventions' } dependencies { implementation project(':brouter-util') implementation project(':brouter-codec') implementation project(':brouter-expressions') - testImplementation 'junit:junit:4.13.2' } // MapcreatorTest generates segments which are used in tests diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index c69c192..73b1a63 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -2,6 +2,8 @@ import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform plugins { id 'com.android.application' + id 'checkstyle' + id 'brouter.version-conventions' } android { @@ -88,6 +90,11 @@ android { } } +repositories { + mavenCentral() + google() +} + dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation "androidx.constraintlayout:constraintlayout:2.1.4" diff --git a/brouter-server/build.gradle b/brouter-server/build.gradle index 4af5535..9206872 100644 --- a/brouter-server/build.gradle +++ b/brouter-server/build.gradle @@ -1,13 +1,10 @@ plugins { - id 'application' + id 'brouter.application-conventions' } - application { mainClass.set('btools.server.BRouter') - distTar.enabled = false - jar { manifest { attributes "Main-Class": getMainClass(), "Implementation-Version": project.version @@ -78,6 +75,5 @@ dependencies { implementation project(':brouter-mapaccess') implementation project(':brouter-util') - testImplementation 'junit:junit:4.13.2' testImplementation 'org.json:json:20180813' } diff --git a/brouter-util/build.gradle b/brouter-util/build.gradle index 3dc5565..80c7d8c 100644 --- a/brouter-util/build.gradle +++ b/brouter-util/build.gradle @@ -1,7 +1,3 @@ plugins { - id 'java-library' -} - -dependencies { - testImplementation('junit:junit:4.13.1') + id 'brouter.library-conventions' } diff --git a/build.gradle b/build.gradle index f1348b5..c316d1c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - repositories { mavenCentral() google() @@ -9,57 +8,11 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:8.3.1' - // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } -allprojects { - // NOTE: - // there are four places to change the version number - // this file - // app: build.gradle (versionCode only) - // OsmTrack (version and versionDate) - // docs revisions.md (version and versionDate) - project.version "1.7.3" - group 'org.btools' - - repositories { - mavenCentral() - google() - } - - apply plugin: "maven-publish" - publishing { - repositories { - maven { - name = "GitHubPackages" - url = uri("https://maven.pkg.github.com/$System.env.REPO") - credentials { - username = project.findProperty("gpr.user") ?: System.getenv("USERNAME") - password = project.findProperty("gpr.key") ?: System.getenv("TOKEN") - } - } - } - publications { - gpr(MavenPublication) - } - } - - apply plugin: "checkstyle" - apply plugin: "pmd" - - pmd { - consoleOutput = true - toolVersion = "7.0.0" - rulesMinimumPriority = 5 - ruleSetFiles = files("${rootProject.rootDir}/config/pmd/pmd-ruleset.xml") - ruleSets = [] - // ignoreFailures = true - } -} - task clean(type: Delete) { delete rootProject.buildDir } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 0000000..6784052 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,3 @@ +plugins { + id 'groovy-gradle-plugin' +} diff --git a/buildSrc/src/main/groovy/brouter.application-conventions.gradle b/buildSrc/src/main/groovy/brouter.application-conventions.gradle new file mode 100644 index 0000000..da0466c --- /dev/null +++ b/buildSrc/src/main/groovy/brouter.application-conventions.gradle @@ -0,0 +1,8 @@ +plugins { + id 'application' + id 'brouter.java-conventions' +} + +application { + distTar.enabled = false +} diff --git a/buildSrc/src/main/groovy/brouter.java-conventions.gradle b/buildSrc/src/main/groovy/brouter.java-conventions.gradle new file mode 100644 index 0000000..390704a --- /dev/null +++ b/buildSrc/src/main/groovy/brouter.java-conventions.gradle @@ -0,0 +1,25 @@ +plugins { + id 'java' + id 'checkstyle' + id 'pmd' + id 'brouter.version-conventions' +} + +group 'org.btools' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'junit:junit:4.13.2' +} + +pmd { + consoleOutput = true + toolVersion = '7.0.0' + rulesMinimumPriority = 5 + ruleSetFiles = files("${rootProject.rootDir}/config/pmd/pmd-ruleset.xml") + ruleSets = [] + // ignoreFailures = true +} diff --git a/buildSrc/src/main/groovy/brouter.library-conventions.gradle b/buildSrc/src/main/groovy/brouter.library-conventions.gradle new file mode 100644 index 0000000..3fd5d46 --- /dev/null +++ b/buildSrc/src/main/groovy/brouter.library-conventions.gradle @@ -0,0 +1,21 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'brouter.java-conventions' +} + +publishing { + repositories { + maven { + name = 'GitHubPackages' + url = uri("https://maven.pkg.github.com/$System.env.REPO") + credentials { + username = project.findProperty('gpr.user') ?: System.getenv('USERNAME') + password = project.findProperty('gpr.key') ?: System.getenv('TOKEN') + } + } + } + publications { + gpr(MavenPublication) + } +} diff --git a/buildSrc/src/main/groovy/brouter.version-conventions.gradle b/buildSrc/src/main/groovy/brouter.version-conventions.gradle new file mode 100644 index 0000000..bfc1cc1 --- /dev/null +++ b/buildSrc/src/main/groovy/brouter.version-conventions.gradle @@ -0,0 +1,7 @@ +// NOTE: +// there are four places to change the version number +// this file +// app: build.gradle (versionCode only) +// OsmTrack (version and versionDate) +// docs revisions.md (version and versionDate) +version '1.7.3' diff --git a/settings.gradle b/settings.gradle index eaa63a4..9bded48 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,7 @@ rootProject.name='brouter' if (file('local.properties').exists()) { - include ':brouter-routing-app' + include ':brouter-routing-app' } else { println "Note: To include Android app add 'local.properties' with 'sdk.dir=...' " } -include ':brouter-mapaccess', ':brouter-core', ':brouter-util', ':brouter-expressions', ':brouter-codec', ':brouter-map-creator', ':brouter-server' \ No newline at end of file +include ':brouter-mapaccess', ':brouter-core', ':brouter-util', ':brouter-expressions', ':brouter-codec', ':brouter-map-creator', ':brouter-server' From 258a0c107d9a11e04e841d96d6c66380372ac177 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Fri, 5 Apr 2024 23:42:24 +0200 Subject: [PATCH 145/173] Remove leftover maven file --- brouter-util/pom.xml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 brouter-util/pom.xml diff --git a/brouter-util/pom.xml b/brouter-util/pom.xml deleted file mode 100644 index 23b3925..0000000 --- a/brouter-util/pom.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - 4.0.0 - - org.btools - brouter - 1.6.1 - ../pom.xml - - brouter-util - jar - - - - junit - junit - test - - - From 8e3c9a95123dec603ff205eb52c61687608ff676 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Sat, 6 Apr 2024 00:26:50 +0200 Subject: [PATCH 146/173] Target Java 11 --- buildSrc/src/main/groovy/brouter.java-conventions.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/buildSrc/src/main/groovy/brouter.java-conventions.gradle b/buildSrc/src/main/groovy/brouter.java-conventions.gradle index 390704a..4609537 100644 --- a/buildSrc/src/main/groovy/brouter.java-conventions.gradle +++ b/buildSrc/src/main/groovy/brouter.java-conventions.gradle @@ -15,6 +15,10 @@ dependencies { testImplementation 'junit:junit:4.13.2' } +compileJava { + options.release = 11 +} + pmd { consoleOutput = true toolVersion = '7.0.0' From 13781fb1fce122d8129e14ee54b4e1c9c7285d4a Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Sat, 6 Apr 2024 00:28:57 +0200 Subject: [PATCH 147/173] fixup! Upgrade to AGP 8.0 and Gradle 8.4 --- brouter-server/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/brouter-server/build.gradle b/brouter-server/build.gradle index 9206872..b581310 100644 --- a/brouter-server/build.gradle +++ b/brouter-server/build.gradle @@ -32,7 +32,6 @@ application { distZip { dependsOn fatJar - dependsOn ':brouter-routing-app:packageRelease' archiveFileName = 'brouter-' + project.version + '.zip' } From d969ac11cbba0b65399d17c1a7b21e747884fe4d Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Sat, 6 Apr 2024 00:35:04 +0200 Subject: [PATCH 148/173] Downgrade AGP version for IntelliJ IDEA compatibility --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c316d1c..6c64c85 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.3.1' + classpath 'com.android.tools.build:gradle:8.2.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From a148ba70ebaf0cf585e81d8b6241e9d10e445547 Mon Sep 17 00:00:00 2001 From: zod Date: Tue, 9 Apr 2024 22:31:18 +0200 Subject: [PATCH 149/173] Add docker publish workflow based on workflow template --- .github/workflows/docker-publish.yml | 98 ++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 .github/workflows/docker-publish.yml diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..e8af57c --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,98 @@ +name: Docker + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +on: + schedule: + - cron: '21 9 * * *' + push: + branches: [ "master" ] + # Publish semver tags as releases. + tags: [ 'v*.*.*' ] + pull_request: + branches: [ "master" ] + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Install the cosign tool except on PR + # https://github.com/sigstore/cosign-installer + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.1.1 + with: + cosign-release: 'v2.1.1' + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # Sign the resulting Docker image digest except on PRs. + # This will only write to the public Rekor transparency log when the Docker + # repository is public to avoid leaking data. If you would like to publish + # transparency data even for private images, pass --force to cosign below. + # https://github.com/sigstore/cosign + - name: Sign the published Docker image + if: ${{ github.event_name != 'pull_request' }} + env: + # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable + TAGS: ${{ steps.meta.outputs.tags }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + # This step uses the identity token to provision an ephemeral certificate + # against the sigstore community Fulcio instance. + run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} From 51ebfd346bc20d39531fa582e00a32a8d36d1bd1 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Tue, 9 Apr 2024 23:00:08 +0200 Subject: [PATCH 150/173] Disable image signing --- .github/workflows/docker-publish.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index e8af57c..0d05009 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -87,12 +87,12 @@ jobs: # repository is public to avoid leaking data. If you would like to publish # transparency data even for private images, pass --force to cosign below. # https://github.com/sigstore/cosign - - name: Sign the published Docker image - if: ${{ github.event_name != 'pull_request' }} - env: - # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable - TAGS: ${{ steps.meta.outputs.tags }} - DIGEST: ${{ steps.build-and-push.outputs.digest }} - # This step uses the identity token to provision an ephemeral certificate - # against the sigstore community Fulcio instance. - run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} + # - name: Sign the published Docker image + # if: ${{ github.event_name != 'pull_request' }} + # env: + # # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable + # TAGS: ${{ steps.meta.outputs.tags }} + # DIGEST: ${{ steps.build-and-push.outputs.digest }} + # # This step uses the identity token to provision an ephemeral certificate + # # against the sigstore community Fulcio instance. + # run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} From 47f58126e71cff70e1b5338ff9a31b546d08d701 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Mon, 15 Apr 2024 20:49:25 +0200 Subject: [PATCH 151/173] Add dependency on brouter-routing-app for distZip --- brouter-server/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/brouter-server/build.gradle b/brouter-server/build.gradle index b581310..49cfd98 100644 --- a/brouter-server/build.gradle +++ b/brouter-server/build.gradle @@ -32,6 +32,7 @@ application { distZip { dependsOn fatJar + if (file('../local.properties').exists()) dependsOn (':brouter-routing-app:assemble') archiveFileName = 'brouter-' + project.version + '.zip' } From 584a2a82d66f1c8f24aa8b5e2393bff0968d907a Mon Sep 17 00:00:00 2001 From: quaelnix <122357328+quaelnix@users.noreply.github.com> Date: Sun, 5 May 2024 14:47:41 +0200 Subject: [PATCH 152/173] Update gravel.brf - Fix flaws in 'vehicle=' and 'bicycle=use_sidepath' logic - Use more realistic drag coefficient - Fix typo in the downhillcost logic - Improve maxspeed penalty - Improve noise penalty --- misc/profiles2/gravel.brf | 50 ++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/misc/profiles2/gravel.brf b/misc/profiles2/gravel.brf index 9afd822..3468ae0 100644 --- a/misc/profiles2/gravel.brf +++ b/misc/profiles2/gravel.brf @@ -1,4 +1,4 @@ -# "gravel.brf" -- Version 09.09.2023 +# "gravel.brf" -- Version 28.04.2024 # This customizeable profile, developed by quaelnix, is designed for gravel cyclists who want to avoid traffic as much # as possible, but still get to their destination efficiently - taking into account the capabilities of a gravel bike. @@ -15,7 +15,7 @@ assign validForBikes true # +++ Kinematic model parameters (travel time computation) assign totalMass = 90 # %totalMass% | Mass (kg) of the bike + biker | number assign maxSpeed = 35 # %maxSpeed% | Absolute maximum speed (km/h) | number -assign S_C_x = 0.370 # %S_C_x% | Drag coefficient times reference area (m^2) times half air density (kg/m^3) +assign S_C_x = 0.300 # %S_C_x% | Drag coefficient times reference area (m^2) times half air density (kg/m^3) assign C_r = 0.005 # %C_r% | Rolling resistance coefficient (dimensionless) assign bikerPower = 150 # %bikerPower% | Average power (W) provided by the biker | number @@ -40,10 +40,10 @@ assign uphillcutoff switch and not consider_elevation avoid_steep_inclines assign has_decent_surface surface=asphalt|concrete|paved|paving_stones|fine_gravel|compacted assign bad_when_steep ( and highway=track|path not ( or tracktype=grade1|grade2 has_decent_surface ) ) -assign downhillcost switch bad_when_steep ( max 80 downhillcost ) downhillcost -assign downhillcutoff switch bad_when_steep ( min 6 downhillcutoff ) downhillcutoff -assign uphillcost switch bad_when_steep ( max 160 uphillcost ) uphillcost -assign uphillcutoff switch bad_when_steep ( min 6 uphillcutoff ) uphillcutoff +assign downhillcost switch bad_when_steep ( max 160 downhillcost ) downhillcost +assign downhillcutoff switch bad_when_steep ( min 1.5 downhillcutoff ) downhillcutoff +assign uphillcost switch bad_when_steep ( max 80 uphillcost ) uphillcost +assign uphillcutoff switch bad_when_steep ( min 1.5 uphillcutoff ) uphillcutoff assign any_cycleway or cycleway=track|lane|shared_lane|shared or and cycleway:right=track|lane|shared_lane reversedirection= @@ -55,10 +55,10 @@ assign turncost switch junction=roundabout 15 65 assign is_main_road highway=primary|primary_link|secondary|secondary_link|tertiary|tertiary_link assign initialclassifier switch route=ferry 4 switch is_main_road 3 switch footway=crossing 2 1 -assign initialcost switch route=ferry 20000 switch is_main_road 800 switch assume_wet_conditions 100 20 +assign initialcost switch route=ferry 20000 switch is_main_road 400 switch assume_wet_conditions 100 20 -assign nobikeaccess not switch bicycle= ( not access=no|private ) ( not bicycle=no|private|dismount|use_sidepath ) -assign nofootaccess not switch foot= ( not access=no|private ) ( not foot=no|private|use_sidepath ) +assign nobikeaccess switch bicycle= switch vehicle= access=no|private vehicle=no|private bicycle=no|private|dismount +assign nofootaccess switch foot= ( and access=no|private ( not bicycle=dismount ) ) foot=no|private assign badoneway switch not reversedirection=yes oneway=-1 @@ -92,9 +92,10 @@ assign smoothnesspenalty switch surface=asphalt switch not assume_wet_conditions 1.1 1.1 switch concrete=plates|lanes switch not assume_wet_conditions 1.2 1.2 switch surface=fine_gravel|compacted switch not assume_wet_conditions 1.2 1.2 - switch surface=concrete|paving_stones|wood|metal switch not assume_wet_conditions 1.3 1.3 + switch surface=concrete|paving_stones switch not assume_wet_conditions 1.3 1.3 switch surface=paved switch not assume_wet_conditions 1.4 1.4 switch surface=gravel switch not assume_wet_conditions 1.5 1.5 + switch surface=wood|metal switch not assume_wet_conditions 1.6 2.0 switch surface=cobblestone|sett switch not assume_wet_conditions 1.7 2.5 switch surface=grass_paver switch not assume_wet_conditions 1.8 2.2 switch surface=pebblestone switch not assume_wet_conditions 1.9 2.8 @@ -187,8 +188,8 @@ assign maxspeedpenalty switch maxspeed=60 1.1 switch maxspeed=70 1.3 switch maxspeed=80 1.4 - switch maxspeed=90 1.5 - switch maxspeed=100 1.6 1 + switch maxspeed=90 1.6 + switch maxspeed=100 1.8 switch highway=primary|secondary 1.8 1.0 assign trafficpenalty switch consider_traffic_estimate @@ -201,13 +202,13 @@ assign trafficpenalty assign noisepenalty switch avoid_noise - switch estimated_noise_class= 1.0 - switch estimated_noise_class=1 1.2 - switch estimated_noise_class=2 1.3 - switch estimated_noise_class=3 1.5 - switch estimated_noise_class=4 1.8 - switch estimated_noise_class=5 2.0 - switch estimated_noise_class=6 2.2 1 1 + switch estimated_noise_class= 1.00 + switch estimated_noise_class=1 1.10 + switch estimated_noise_class=2 1.15 + switch estimated_noise_class=3 1.20 + switch estimated_noise_class=4 1.60 + switch estimated_noise_class=5 1.80 + switch estimated_noise_class=6 2.20 1 1 assign noriverpenalty switch prefer_rivers @@ -238,7 +239,7 @@ assign townpenalty switch estimated_town_class=4 1.8 switch estimated_town_class=5 2.0 switch estimated_town_class=6 2.2 1 1 - + assign costfactor multiply notcycleroutepenalty multiply noriverpenalty multiply notcyclewaypenalty multiply maxspeedpenalty @@ -246,6 +247,8 @@ assign costfactor multiply noforestpenalty multiply noisepenalty multiply trafficpenalty multiply townpenalty multiply hikingpenalty multiply mtbpenalty + multiply switch bicycle=use_sidepath 1.6 1 + multiply switch nobikeaccess 6 1 multiply tracktypepenalty switch or highway=motorway|motorway_link motorroad=yes 10000 switch and nobikeaccess nofootaccess 10000 @@ -253,7 +256,6 @@ assign costfactor switch highway=trunk|trunk_link switch any_cycleroute 20 80 switch highway=bridleway|raceway switch not bicycle=yes 20 5 switch highway=pedestrian switch not bicycle=yes 8 3 - switch or vehicle=no|private nobikeaccess 6.0 switch highway=primary|primary_link 4.8 switch highway=secondary|secondary_link 4.2 switch highway=tertiary|tertiary_link 3.6 @@ -261,7 +263,7 @@ assign costfactor switch highway=road 2.8 switch highway=unclassified switch not any_cycleroute 2.6 1.4 switch highway=living_street 2.5 - switch highway=service switch not service=parking_aisle 2.4 3.0 + switch highway=service switch service= 2.4 3.0 switch highway=residential switch not maxspeed=30 2.2 1.8 switch highway=track 1.0 switch highway=path pathpenalty @@ -317,8 +319,8 @@ assign classifiermask ---context:node -assign nobikeaccess not switch bicycle= ( not access=no|private ) ( not bicycle=no|private|dismount ) -assign nofootaccess not switch foot= ( not access=no|private ) ( not foot=no|private ) +assign nobikeaccess switch bicycle= switch vehicle= access=no|private vehicle=no|private bicycle=no|private|dismount +assign nofootaccess switch foot= ( and access=no|private ( not bicycle=dismount ) ) foot=no|private assign barrierpenalty switch barrier= 0 From 2a94b7f300569adc28a3f089640e9470d8b4da6b Mon Sep 17 00:00:00 2001 From: ulteq Date: Sat, 11 May 2024 19:52:26 +0200 Subject: [PATCH 153/173] Remove unused traffic simulation code --- .../main/java/btools/router/FormatGpx.java | 2 +- .../src/main/java/btools/router/OsmPath.java | 42 +-- .../java/btools/router/OsmPathElement.java | 11 +- .../router/OsmPathElementWithTraffic.java | 68 ----- .../java/btools/router/RoutingContext.java | 19 -- .../java/btools/router/RoutingEngine.java | 22 +- .../main/java/btools/mapcreator/OsmNodeP.java | 10 +- .../java/btools/mapcreator/OsmTrafficMap.java | 245 ------------------ .../java/btools/mapcreator/WayLinker.java | 16 +- .../scripts/mapcreation/process_pbf_planet.sh | 16 -- 10 files changed, 19 insertions(+), 432 deletions(-) delete mode 100644 brouter-core/src/main/java/btools/router/OsmPathElementWithTraffic.java delete mode 100644 brouter-map-creator/src/main/java/btools/mapcreator/OsmTrafficMap.java diff --git a/brouter-core/src/main/java/btools/router/FormatGpx.java b/brouter-core/src/main/java/btools/router/FormatGpx.java index 8a6f761..06f8564 100644 --- a/brouter-core/src/main/java/btools/router/FormatGpx.java +++ b/brouter-core/src/main/java/btools/router/FormatGpx.java @@ -524,7 +524,7 @@ public class FormatGpx extends Formatter { idx2 += 6; int idx3 = line.indexOf('"', idx2); int ilat = (int) ((Double.parseDouble(line.substring(idx2, idx3)) + 90.) * 1000000. + 0.5); - track.nodes.add(OsmPathElement.create(ilon, ilat, (short) 0, null, false)); + track.nodes.add(OsmPathElement.create(ilon, ilat, (short) 0, null)); } } br.close(); diff --git a/brouter-core/src/main/java/btools/router/OsmPath.java b/brouter-core/src/main/java/btools/router/OsmPath.java index ce99dce..94549c9 100644 --- a/brouter-core/src/main/java/btools/router/OsmPath.java +++ b/brouter-core/src/main/java/btools/router/OsmPath.java @@ -5,8 +5,6 @@ */ package btools.router; -import java.io.IOException; - import btools.mapaccess.OsmLink; import btools.mapaccess.OsmLinkHolder; import btools.mapaccess.OsmNode; @@ -33,8 +31,6 @@ abstract class OsmPath implements OsmLinkHolder { public OsmPathElement originElement; public OsmPathElement myElement; - protected float traffic; - private OsmLinkHolder nextForLink = null; public int treedepth = 0; @@ -72,25 +68,6 @@ abstract class OsmPath implements OsmLinkHolder { public MessageData message; - public void unregisterUpTree(RoutingContext rc) { - try { - OsmPathElement pe = originElement; - while (pe instanceof OsmPathElementWithTraffic && ((OsmPathElementWithTraffic) pe).unregister(rc)) { - pe = pe.origin; - } - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - } - - public void registerUpTree() { - if (originElement instanceof OsmPathElementWithTraffic) { - OsmPathElementWithTraffic ot = (OsmPathElementWithTraffic) originElement; - ot.register(); - ot.addTraffic(traffic); - } - } - public void init(OsmLink link) { this.link = link; targetNode = link.getTarget(null); @@ -102,7 +79,7 @@ abstract class OsmPath implements OsmLinkHolder { public void init(OsmPath origin, OsmLink link, OsmTrack refTrack, boolean detailMode, RoutingContext rc) { if (origin.myElement == null) { - origin.myElement = OsmPathElement.create(origin, rc.countTraffic); + origin.myElement = OsmPathElement.create(origin); } this.originElement = origin.myElement; this.link = link; @@ -143,7 +120,7 @@ abstract class OsmPath implements OsmLinkHolder { return; } - boolean recordTransferNodes = detailMode || rc.countTraffic; + boolean recordTransferNodes = detailMode; rc.nogoCost = 0.; @@ -272,7 +249,7 @@ abstract class OsmPath implements OsmLinkHolder { if (recordTransferNodes) { if (rc.wayfraction > 0.) { ele1 = interpolateEle(ele1, ele2, 1. - rc.wayfraction); - originElement = OsmPathElement.create(rc.ilonshortest, rc.ilatshortest, ele1, null, rc.countTraffic); + originElement = OsmPathElement.create(rc.ilonshortest, rc.ilatshortest, ele1, null); } else { originElement = null; // prevent duplicate point } @@ -333,13 +310,6 @@ abstract class OsmPath implements OsmLinkHolder { cost += (int) sectionCost; - // calculate traffic - if (rc.countTraffic) { - int minDist = (int) rc.trafficSourceMinDist; - int cost2 = cost < minDist ? minDist : cost; - traffic += dist * rc.expctxWay.getTrafficSourceDensity() * Math.pow(cost2 / 10000.f, rc.trafficSourceExponent); - } - // compute kinematic computeKinematic(rc, dist, delta_h, detailMode); @@ -357,7 +327,7 @@ abstract class OsmPath implements OsmLinkHolder { if (stopAtEndpoint) { if (recordTransferNodes) { - originElement = OsmPathElement.create(rc.ilonshortest, rc.ilatshortest, originEle2, originElement, rc.countTraffic); + originElement = OsmPathElement.create(rc.ilonshortest, rc.ilatshortest, originEle2, originElement); originElement.cost = cost; if (message != null) { originElement.message = message; @@ -383,10 +353,8 @@ abstract class OsmPath implements OsmLinkHolder { transferNode = transferNode.next; if (recordTransferNodes) { - originElement = OsmPathElement.create(lon2, lat2, originEle2, originElement, rc.countTraffic); + originElement = OsmPathElement.create(lon2, lat2, originEle2, originElement); originElement.cost = cost; - originElement.addTraffic(traffic); - traffic = 0; } lon0 = lon1; lat0 = lat1; diff --git a/brouter-core/src/main/java/btools/router/OsmPathElement.java b/brouter-core/src/main/java/btools/router/OsmPathElement.java index d721ad8..08a8500 100644 --- a/brouter-core/src/main/java/btools/router/OsmPathElement.java +++ b/brouter-core/src/main/java/btools/router/OsmPathElement.java @@ -81,16 +81,16 @@ public class OsmPathElement implements OsmPos { public OsmPathElement origin; // construct a path element from a path - public static final OsmPathElement create(OsmPath path, boolean countTraffic) { + public static final OsmPathElement create(OsmPath path) { OsmNode n = path.getTargetNode(); - OsmPathElement pe = create(n.getILon(), n.getILat(), n.getSElev(), path.originElement, countTraffic); + OsmPathElement pe = create(n.getILon(), n.getILat(), n.getSElev(), path.originElement); pe.cost = path.cost; pe.message = path.message; return pe; } - public static final OsmPathElement create(int ilon, int ilat, short selev, OsmPathElement origin, boolean countTraffic) { - OsmPathElement pe = countTraffic ? new OsmPathElementWithTraffic() : new OsmPathElement(); + public static final OsmPathElement create(int ilon, int ilat, short selev, OsmPathElement origin) { + OsmPathElement pe = new OsmPathElement(); pe.ilon = ilon; pe.ilat = ilat; pe.selev = selev; @@ -101,9 +101,6 @@ public class OsmPathElement implements OsmPos { protected OsmPathElement() { } - public void addTraffic(float traffic) { - } - public String toString() { return ilon + "_" + ilat; } diff --git a/brouter-core/src/main/java/btools/router/OsmPathElementWithTraffic.java b/brouter-core/src/main/java/btools/router/OsmPathElementWithTraffic.java deleted file mode 100644 index 18496f4..0000000 --- a/brouter-core/src/main/java/btools/router/OsmPathElementWithTraffic.java +++ /dev/null @@ -1,68 +0,0 @@ -package btools.router; - -import java.io.IOException; - - -/** - * Extension to OsmPathElement to count traffic load - * - * @author ab - */ - -public final class OsmPathElementWithTraffic extends OsmPathElement { - private int registerCount; - private float farTraffic; - private float nearTraffic; - - public void register() { - if (registerCount++ == 0) { - if (origin instanceof OsmPathElementWithTraffic) { - OsmPathElementWithTraffic ot = (OsmPathElementWithTraffic) origin; - ot.register(); - ot.farTraffic += farTraffic; - ot.nearTraffic += nearTraffic; - farTraffic = 0; - nearTraffic = 0; - } - } - } - - @Override - public void addTraffic(float traffic) { - this.farTraffic += traffic; - this.nearTraffic += traffic; - } - - // unregister from origin if our registercount is 0, else do nothing - - public static double maxtraffic = 0.; - - public boolean unregister(RoutingContext rc) throws IOException { - if (--registerCount == 0) { - if (origin instanceof OsmPathElementWithTraffic) { - OsmPathElementWithTraffic ot = (OsmPathElementWithTraffic) origin; - - int costdelta = cost - ot.cost; - ot.farTraffic += farTraffic * Math.exp(-costdelta / rc.farTrafficDecayLength); - ot.nearTraffic += nearTraffic * Math.exp(-costdelta / rc.nearTrafficDecayLength); - - if (costdelta > 0 && farTraffic > maxtraffic) maxtraffic = farTraffic; - - int t2 = cost == ot.cost ? -1 : (int) (rc.farTrafficWeight * farTraffic + rc.nearTrafficWeight * nearTraffic); - - if (t2 > 4000 || t2 == -1) { - // System.out.println( "unregistered: " + this + " origin=" + ot + " farTraffic =" + farTraffic + " nearTraffic =" + nearTraffic + " cost=" + cost ); - if (rc.trafficOutputStream != null) { - rc.trafficOutputStream.writeLong(getIdFromPos()); - rc.trafficOutputStream.writeLong(ot.getIdFromPos()); - rc.trafficOutputStream.writeInt(t2); - } - } - farTraffic = 0; - nearTraffic = 0; - } - return true; - } - return false; - } -} diff --git a/brouter-core/src/main/java/btools/router/RoutingContext.java b/brouter-core/src/main/java/btools/router/RoutingContext.java index 0cf8adf..474c1c9 100644 --- a/brouter-core/src/main/java/btools/router/RoutingContext.java +++ b/brouter-core/src/main/java/btools/router/RoutingContext.java @@ -5,7 +5,6 @@ */ package btools.router; -import java.io.DataOutput; import java.io.File; import java.util.ArrayList; import java.util.List; @@ -141,14 +140,6 @@ public final class RoutingContext { starttimeoffset = expctxGlobal.getVariableValue("starttimeoffset", 0.f); transitonly = expctxGlobal.getVariableValue("transitonly", 0.f) != 0.f; - farTrafficWeight = expctxGlobal.getVariableValue("farTrafficWeight", 2.f); - nearTrafficWeight = expctxGlobal.getVariableValue("nearTrafficWeight", 2.f); - farTrafficDecayLength = expctxGlobal.getVariableValue("farTrafficDecayLength", 30000.f); - nearTrafficDecayLength = expctxGlobal.getVariableValue("nearTrafficDecayLength", 3000.f); - trafficDirectionFactor = expctxGlobal.getVariableValue("trafficDirectionFactor", 0.9f); - trafficSourceExponent = expctxGlobal.getVariableValue("trafficSourceExponent", -0.7f); - trafficSourceMinDist = expctxGlobal.getVariableValue("trafficSourceMinDist", 3000.f); - showspeed = 0.f != expctxGlobal.getVariableValue("showspeed", 0.f); showSpeedProfile = 0.f != expctxGlobal.getVariableValue("showSpeedProfile", 0.f); inverseRouting = 0.f != expctxGlobal.getVariableValue("inverseRouting", 0.f); @@ -199,17 +190,7 @@ public final class RoutingContext { public int ilatshortest; public int ilonshortest; - public boolean countTraffic; public boolean inverseDirection; - public DataOutput trafficOutputStream; - - public double farTrafficWeight; - public double nearTrafficWeight; - public double farTrafficDecayLength; - public double nearTrafficDecayLength; - public double trafficDirectionFactor; - public double trafficSourceExponent; - public double trafficSourceMinDist; public boolean showspeed; public boolean showSpeedProfile; diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index 2909e5a..09113a8 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -1279,7 +1279,6 @@ public class RoutingEngine extends Thread { } if (path.airdistance == -1) { - path.unregisterUpTree(routingContext); continue; } @@ -1347,7 +1346,6 @@ public class RoutingEngine extends Thread { OsmNode currentNode = path.getTargetNode(); if (currentLink.isLinkUnused()) { - path.unregisterUpTree(routingContext); continue; } @@ -1390,7 +1388,7 @@ public class RoutingEngine extends Thread { + path.elevationCorrection() + (costCuttingTrack.cost - pe.cost); if (costEstimate <= maxTotalCost) { - matchPath = OsmPathElement.create(path, routingContext.countTraffic); + matchPath = OsmPathElement.create(path); } if (costEstimate < maxTotalCost) { logInfo("maxcost " + maxTotalCost + " -> " + costEstimate); @@ -1400,7 +1398,6 @@ public class RoutingEngine extends Thread { } } - int keepPathAirdistance = path.airdistance; OsmLinkHolder firstLinkHolder = currentLink.getFirstLinkHolder(sourceNode); for (OsmLinkHolder linkHolder = firstLinkHolder; linkHolder != null; linkHolder = linkHolder.getNextForLink()) { ((OsmPath) linkHolder).airdistance = -1; // invalidate the entry in the open set; @@ -1419,7 +1416,6 @@ public class RoutingEngine extends Thread { // recheck cutoff before doing expensive stuff int addDiff = 100; if (path.cost + path.airdistance > maxTotalCost + addDiff) { - path.unregisterUpTree(routingContext); continue; } @@ -1474,7 +1470,7 @@ public class RoutingEngine extends Thread { if (routingContext.turnInstructionMode > 0) { OsmPath detour = routingContext.createPath(path, link, refTrack, true); if (detour.cost >= 0. && nextId != startNodeId1 && nextId != startNodeId2) { - guideTrack.registerDetourForId(currentNode.getIdFromPos(), OsmPathElement.create(detour, false)); + guideTrack.registerDetourForId(currentNode.getIdFromPos(), OsmPathElement.create(detour)); } } continue; @@ -1510,16 +1506,14 @@ public class RoutingEngine extends Thread { } } if (bestPath != null) { - boolean trafficSim = endPos == null; - - bestPath.airdistance = trafficSim ? keepPathAirdistance : (isFinalLink ? 0 : nextNode.calcDistance(endPos)); + bestPath.airdistance = isFinalLink ? 0 : nextNode.calcDistance(endPos); boolean inRadius = boundary == null || boundary.isInBoundary(nextNode, bestPath.cost); if (inRadius && (isFinalLink || bestPath.cost + bestPath.airdistance <= (lastAirDistanceCostFactor != 0. ? maxTotalCost * lastAirDistanceCostFactor : maxTotalCost) + addDiff)) { // add only if this may beat an existing path for that link OsmLinkHolder dominator = link.getFirstLinkHolder(currentNode); - while (!trafficSim && dominator != null) { + while (dominator != null) { OsmPath dp = (OsmPath) dominator; if (dp.airdistance != -1 && bestPath.definitlyWorseThan(dp)) { break; @@ -1528,9 +1522,6 @@ public class RoutingEngine extends Thread { } if (dominator == null) { - if (trafficSim && boundary != null && path.cost == 0 && bestPath.cost > 0) { - bestPath.airdistance += boundary.getBoundaryDistance(nextNode); - } bestPath.treedepth = path.treedepth + 1; link.addLinkHolder(bestPath, currentNode); addToOpenset(bestPath); @@ -1538,8 +1529,6 @@ public class RoutingEngine extends Thread { } } } - - path.unregisterUpTree(routingContext); } } @@ -1553,12 +1542,11 @@ public class RoutingEngine extends Thread { private void addToOpenset(OsmPath path) { if (path.cost >= 0) { openSet.add(path.cost + (int) (path.airdistance * airDistanceCostFactor), path); - path.registerUpTree(); } } private OsmTrack compileTrack(OsmPath path, boolean verbose) { - OsmPathElement element = OsmPathElement.create(path, false); + OsmPathElement element = OsmPathElement.create(path); // for final track, cut endnode if (guideTrack != null && element.origin != null) { diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java b/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java index b945582..3c796ee 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java @@ -93,10 +93,10 @@ public class OsmNodeP extends OsmLinkP { return null; } - public void writeNodeData(MicroCache mc, OsmTrafficMap trafficMap) throws IOException { + public void writeNodeData(MicroCache mc) throws IOException { boolean valid = true; if (mc instanceof MicroCache2) { - valid = writeNodeData2((MicroCache2) mc, trafficMap); + valid = writeNodeData2((MicroCache2) mc); } else throw new IllegalArgumentException("unknown cache version: " + mc.getClass()); if (valid) { @@ -144,7 +144,7 @@ public class OsmNodeP extends OsmLinkP { } } - public boolean writeNodeData2(MicroCache2 mc, OsmTrafficMap trafficMap) throws IOException { + public boolean writeNodeData2(MicroCache2 mc) throws IOException { boolean hasLinks = false; // write turn restrictions @@ -212,11 +212,7 @@ public class OsmNodeP extends OsmLinkP { } } - // add traffic simulation, if present byte[] description = link0.descriptionBitmap; - if (trafficMap != null) { - description = trafficMap.addTrafficClass(linkNodes, description); - } // write link data int sizeoffset = mc.writeSizePlaceHolder(); diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/OsmTrafficMap.java b/brouter-map-creator/src/main/java/btools/mapcreator/OsmTrafficMap.java deleted file mode 100644 index ca6d119..0000000 --- a/brouter-map-creator/src/main/java/btools/mapcreator/OsmTrafficMap.java +++ /dev/null @@ -1,245 +0,0 @@ -/** - * Container for link between two Osm nodes (pre-pocessor version) - * - * @author ab - */ -package btools.mapcreator; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.EOFException; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.List; - -import btools.expressions.BExpressionContextWay; -import btools.util.CheapRuler; -import btools.util.CompactLongMap; -import btools.util.FrozenLongMap; - - -public class OsmTrafficMap { - int minLon; - int minLat; - int maxLon; - int maxLat; - - private BExpressionContextWay expctxWay; - - private OsmTrafficMap oldTrafficClasses; - private DataOutputStream newTrafficDos; - private File oldTrafficFile; - private File newTrafficFile; - - private int totalChanges = 0; - private int supressedChanges = 0; - - private boolean doNotAdd = false; - private boolean debug = false; - - public OsmTrafficMap(BExpressionContextWay expctxWay) { - this.expctxWay = expctxWay; - debug = Boolean.getBoolean("debugTrafficMap"); - } - - public static class OsmTrafficElement { - public long node2; - public int traffic; - public OsmTrafficElement next; - } - - private CompactLongMap map = new CompactLongMap<>(); - - public void loadAll(File file, int minLon, int minLat, int maxLon, int maxLat, boolean includeMotorways) throws Exception { - load(file, minLon, minLat, maxLon, maxLat, includeMotorways); - - // check for old traffic data - oldTrafficFile = new File(file.getParentFile(), file.getName() + "_old"); - if (oldTrafficFile.exists()) { - oldTrafficClasses = new OsmTrafficMap(null); - oldTrafficClasses.doNotAdd = true; - oldTrafficClasses.load(oldTrafficFile, minLon, minLat, maxLon, maxLat, false); - } - - // check for old traffic data - newTrafficFile = new File(file.getParentFile(), file.getName() + "_new"); - newTrafficDos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(newTrafficFile))); - } - - public void finish() throws Exception { - if (newTrafficDos != null) { - newTrafficDos.close(); - newTrafficDos = null; - oldTrafficFile.delete(); - newTrafficFile.renameTo(oldTrafficFile); - System.out.println("TrafficMap: changes total=" + totalChanges + " supressed=" + supressedChanges); - } - } - - public void load(File file, int minLon, int minLat, int maxLon, int maxLat, boolean includeMotorways) throws Exception { - this.minLon = minLon; - this.minLat = minLat; - this.maxLon = maxLon; - this.maxLat = maxLat; - - int trafficElements = 0; - DataInputStream is = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); - try { - for (; ; ) { - long n1 = is.readLong(); - long n2 = is.readLong(); - int traffic = is.readInt(); - if (traffic == -1 && !includeMotorways) { - continue; - } - if (isInsideBounds(n1) || isInsideBounds(n2)) { - if (addElement(n1, n2, traffic)) { - trafficElements++; - } - } - } - } catch (EOFException eof) { - } finally { - is.close(); - } - - map = new FrozenLongMap<>(map); - System.out.println("read traffic-elements: " + trafficElements); - } - - - public boolean addElement(long n1, long n2, int traffic) { - OsmTrafficElement e = getElement(n1, n2); - if (e == null) { - e = new OsmTrafficElement(); - e.node2 = n2; - e.traffic = traffic; - - OsmTrafficElement e0 = map.get(n1); - if (e0 != null) { - while (e0.next != null) { - e0 = e0.next; - } - e0.next = e; - } else { - map.fastPut(n1, e); - } - return true; - } - if (doNotAdd) { - e.traffic = Math.max(e.traffic, traffic); - } else { - e.traffic = e.traffic == -1 || traffic == -1 ? -1 : e.traffic + traffic; - } - return false; - } - - private boolean isInsideBounds(long id) { - int ilon = (int) (id >> 32); - int ilat = (int) (id & 0xffffffff); - - return ilon >= minLon && ilon < maxLon && ilat >= minLat && ilat < maxLat; - } - - public int getTrafficClass(long n1, long n2) { - // used for the old data, where we stpre traffic-classes, not volumes - OsmTrafficElement e = getElement(n1, n2); - return e == null ? 0 : e.traffic; - } - - public int getTrafficClassForTraffic(int traffic) { - if (traffic < 0) return -1; - if (traffic < 40000) return 0; - if (traffic < 80000) return 2; - if (traffic < 160000) return 3; - if (traffic < 320000) return 4; - if (traffic < 640000) return 5; - if (traffic < 1280000) return 6; - return 7; - } - - private int getTraffic(long n1, long n2) { - OsmTrafficElement e1 = getElement(n1, n2); - int traffic1 = e1 == null ? 0 : e1.traffic; - OsmTrafficElement e2 = getElement(n2, n1); - int traffic2 = e2 == null ? 0 : e2.traffic; - return traffic1 == -1 || traffic2 == -1 ? -1 : traffic1 > traffic2 ? traffic1 : traffic2; - } - - public void freeze() { - } - - private OsmTrafficElement getElement(long n1, long n2) { - OsmTrafficElement e = map.get(n1); - while (e != null) { - if (e.node2 == n2) { - return e; - } - e = e.next; - } - return null; - } - - public OsmTrafficElement getElement(long n) { - return map.get(n); - } - - public byte[] addTrafficClass(List linkNodes, byte[] description) throws IOException { - double distance = 0.; - double sum = 0.; - - for (int i = 0; i < linkNodes.size() - 1; i++) { - OsmNodeP n1 = linkNodes.get(i); - OsmNodeP n2 = linkNodes.get(i + 1); - int traffic = getTraffic(n1.getIdFromPos(), n2.getIdFromPos()); - double dist = CheapRuler.distance(n1.ilon, n1.ilat, n2.ilon, n2.ilat); - distance += dist; - sum += dist * traffic; - } - - if (distance == 0.) { - return description; - } - int traffic = (int) (sum / distance + 0.5); - - long id0 = linkNodes.get(0).getIdFromPos(); - long id1 = linkNodes.get(linkNodes.size() - 1).getIdFromPos(); - - int trafficClass = getTrafficClassForTraffic(traffic); - - // delta suppression: keep old traffic classes within some buffer range - if (oldTrafficClasses != null) { - int oldTrafficClass = oldTrafficClasses.getTrafficClass(id0, id1); - if (oldTrafficClass != trafficClass) { - totalChanges++; - boolean supressChange = - oldTrafficClass == getTrafficClassForTraffic((int) (traffic * 1.3)) - || oldTrafficClass == getTrafficClassForTraffic((int) (traffic * 0.77)); - - if (debug) { - System.out.println("traffic class change " + oldTrafficClass + "->" + trafficClass + " supress=" + supressChange); - } - if (supressChange) { - trafficClass = oldTrafficClass; - supressedChanges++; - } - } - } - - if (trafficClass > 0) { - newTrafficDos.writeLong(id0); - newTrafficDos.writeLong(id1); - newTrafficDos.writeInt(trafficClass); - - expctxWay.decode(description); - expctxWay.addLookupValue("estimated_traffic_class", trafficClass + 1); - return expctxWay.encode(); - } - return description; - } - -} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java b/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java index e0a3c31..2592143 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java @@ -36,7 +36,6 @@ import btools.util.LazyArrayOfLists; public class WayLinker extends MapCreatorBase implements Runnable { private File nodeTilesIn; private File wayTilesIn; - private File trafficTilesIn; private File dataTilesOut; private File borderFileIn; @@ -45,7 +44,6 @@ public class WayLinker extends MapCreatorBase implements Runnable { private boolean readingBorder; private CompactLongMap nodesMap; - private OsmTrafficMap trafficMap; private List nodesList; private CompactLongSet borderSet; private short lookupVersion; @@ -155,7 +153,6 @@ public class WayLinker extends MapCreatorBase implements Runnable { String dataTilesSuffix) throws Exception { this.nodeTilesIn = nodeTilesIn; this.wayTilesIn = wayTilesIn; - this.trafficTilesIn = new File("../traffic"); this.dataTilesOut = dataTilesOut; this.borderFileIn = borderFileIn; this.dataTilesSuffix = dataTilesSuffix; @@ -216,8 +213,6 @@ public class WayLinker extends MapCreatorBase implements Runnable { } - File trafficFile = fileFromTemplate(wayfile, trafficTilesIn, "trf"); - // process corresponding node-file, if any elevationType = 3; File nodeFile = fileFromTemplate(wayfile, nodeTilesIn, "u5d_1"); @@ -274,11 +269,6 @@ public class WayLinker extends MapCreatorBase implements Runnable { nodesList = nodesMapFrozen.getValueList(); } - // read a traffic-file, if any - if (trafficFile.exists()) { - trafficMap = new OsmTrafficMap(expctxWay); - trafficMap.loadAll(trafficFile, minLon, minLat, minLon + 5000000, minLat + 5000000, false); - } return true; } @@ -493,7 +483,7 @@ public class WayLinker extends MapCreatorBase implements Runnable { } for (OsmNodeP n : sortedList.values()) { - n.writeNodeData(mc, trafficMap); + n.writeNodeData(mc); } if (mc.getSize() > 0) { byte[] subBytes; @@ -557,10 +547,6 @@ public class WayLinker extends MapCreatorBase implements Runnable { ra.write(abFileIndex, 0, abFileIndex.length); ra.close(); } - if (trafficMap != null) { - trafficMap.finish(); - trafficMap = null; - } System.out.println("**** codec stats: *******\n" + StatCoderContext.getBitReport()); } diff --git a/misc/scripts/mapcreation/process_pbf_planet.sh b/misc/scripts/mapcreation/process_pbf_planet.sh index d3b190e..99021b1 100755 --- a/misc/scripts/mapcreation/process_pbf_planet.sh +++ b/misc/scripts/mapcreation/process_pbf_planet.sh @@ -62,22 +62,6 @@ ${JAVA} -cp ${BROUTER_JAR} -Ddeletetmpfiles=true -DuseDenseMaps=true btools.mapc mkdir segments ${JAVA} -cp ${BROUTER_JAR} -DuseDenseMaps=true btools.mapcreator.WayLinker unodes55 waytiles55 bordernodes.dat restrictions.dat ${BROUTER_PROFILES}/lookups.dat ${BROUTER_PROFILES}/all.brf segments rd5 -mkdir traffic - -${JAVA} -jar ${BROUTER_JAR} segments 8.593025 49.724868 seed 0 ${BROUTER_PROFILES}/car-traffic_analysis.brf - -${JAVA} -jar ${BROUTER_JAR} segments 8.609011 50.527861 seed 0 ${BROUTER_PROFILES}/car-traffic_analysis.brf - -${JAVA} -jar ${BROUTER_JAR} segments 12.867994 51.239889 seed 0 ${BROUTER_PROFILES}/car-traffic_analysis.brf - -${JAVA} -jar ${BROUTER_JAR} segments 11.128099 49.501845 seed 0 ${BROUTER_PROFILES}/car-traffic_analysis.brf - -${JAVA} -jar ${BROUTER_JAR} segments 16.532815 49.169541 seed 0 ${BROUTER_PROFILES}/car-traffic_analysis.brf - -${JAVA} -jar ${BROUTER_JAR} segments 16.917636 51.040949 seed 0 ${BROUTER_PROFILES}/car-traffic_analysis.brf - -${JAVA} -cp ${BROUTER_JAR} -DuseDenseMaps=true btools.mapcreator.WayLinker unodes55 waytiles55 bordernodes.dat restrictions.dat ${BROUTER_PROFILES}/lookups.dat ${BROUTER_PROFILES}/all.brf segments rd5 - cd .. rm -rf segments mv tmp/segments segments From 6d7b8f0d777809d09df51b5f9acd4935ef8aa34b Mon Sep 17 00:00:00 2001 From: Marcus Jaschen Date: Tue, 14 May 2024 21:03:47 +0200 Subject: [PATCH 154/173] Update MIME type for GeoJSON responses The MIME type for GeoJSON registered with IANA is application/geo+json, replacing the old value application/vnd.geo+json. The change was published with RFC 7946 in 2016. Example request: `GET /brouter?lonlats=13.377485,52.516247%7C13.351221,52.515004&profile=trekking&alternativeidx=0&format=geojson HTTP/1.1` Exampe response headers: ``` HTTP/1.1 200 OK Content-Encoding: gzip Content-Disposition: attachment; filename="brouter.geojson" Access-Control-Allow-Origin: * Connection: close Content-Type: application/geo+json; charset=utf-8 ``` References: - https://www.iana.org/assignments/media-types/application/vnd.geo+json - https://www.iana.org/assignments/media-types/application/geo+json - https://datatracker.ietf.org/doc/html/rfc7946#section-12 --- .../src/main/java/btools/server/request/ServerHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 505fd2b..c459322 100644 --- a/brouter-server/src/main/java/btools/server/request/ServerHandler.java +++ b/brouter-server/src/main/java/btools/server/request/ServerHandler.java @@ -107,7 +107,7 @@ public class ServerHandler extends RequestHandler { } else if ("kml".equals(format)) { result = "application/vnd.google-earth.kml+xml"; } else if ("geojson".equals(format)) { - result = "application/vnd.geo+json"; + result = "application/geo+json"; } else if ("csv".equals(format)) { result = "text/tab-separated-values"; } From 4e858f5e49b309b4a4198f6531c842a45180f63c Mon Sep 17 00:00:00 2001 From: Marcus Jaschen Date: Wed, 15 May 2024 07:59:18 +0200 Subject: [PATCH 155/173] ISO8601 compatible timestamps in log output This fixes #699. **Warning:** this change breaks with backward compatibility, e.g. for log parsing tool chains. --- README.md | 21 +++++++++++++++++++ .../main/java/btools/server/RouteServer.java | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a223a7..7f6d496 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,27 @@ The API endpoints exposed by this HTTP server are documented in the [`brouter-server/src/main/java/btools/server/request/ServerHandler.java`](brouter-server/src/main/java/btools/server/request/ServerHandler.java) file. +The server emits log data for each routing request on stdout. For each routing +request a line with the following eight fields is printed. The fields are +separated by whitespace. + +- timestamp, in ISO8601 format, e.g. `2024-05-14T21:11:26.499+02:00` +- current server session count (integer number 1-999) or "new" when a new + IP address is detected +- IP address (IPv4 or IPv6), prefixed by `ip=` +- duration of routing request in ms, prefixed by `ms=` +- divider `->` +- HTTP request method +- HTTP request URL +- HTTP request version + +Example log output: + +``` +2024-05-14T21:11:26.499+02:00 new ip=127.0.0.1 ms=189 -> GET /brouter?lonlats=13.377485,52.516247%7C13.351221,52.515004&profile=trekking&alternativeidx=0&format=geojson HTTP/1.1 +2024-05-14T21:11:33.229+02:00 1 ip=127.0.0.1 ms=65 -> GET /brouter?lonlats=13.377485,52.516247%7C13.351221,52.515004&profile=trekking&alternativeidx=0&format=geojson HTTP/1.1 +``` + ## BRouter with Docker To build the Docker image run (in the project's top level directory): diff --git a/brouter-server/src/main/java/btools/server/RouteServer.java b/brouter-server/src/main/java/btools/server/RouteServer.java index b1f9cc8..36145dc 100644 --- a/brouter-server/src/main/java/btools/server/RouteServer.java +++ b/brouter-server/src/main/java/btools/server/RouteServer.java @@ -59,7 +59,7 @@ public class RouteServer extends Thread implements Comparable { if (e != null) e.terminate(); } - private static DateFormat tsFormat = new SimpleDateFormat("dd.MM.yy HH:mm", new Locale("en", "US")); + private static DateFormat tsFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", new Locale("en", "US")); private static String formattedTimeStamp(long t) { synchronized (tsFormat) { From 7001c4cbc787cdf26cca9c55e4f7125bc379f226 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Wed, 22 May 2024 17:54:44 +0200 Subject: [PATCH 156/173] check for nogolist --- .../src/main/java/btools/routingapp/BRouterView.java | 4 +++- .../main/java/btools/routingapp/BRouterWorker.java | 12 ++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) 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 a14dfcf..674eaf9 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -224,7 +224,9 @@ public class BRouterView extends View { // add a "last timeout" dummy profile File lastTimeoutFile = new File(modesDir + "/timeoutdata.txt"); long lastTimeoutTime = lastTimeoutFile.lastModified(); - if (lastTimeoutTime > 0 && System.currentTimeMillis() - lastTimeoutTime < 1800000) { + if (lastTimeoutTime > 0 && + lastTimeoutFile.length() > 0 && + System.currentTimeMillis() - lastTimeoutTime < 1800000) { BufferedReader br = new BufferedReader(new FileReader(lastTimeoutFile)); String repeatProfile = br.readLine(); br.close(); 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 f7a74ff..1c733ba 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java @@ -214,10 +214,14 @@ public class BRouterWorker { } private void writeWPList(BufferedWriter bw, List wps) throws Exception { - bw.write(wps.size() + "\n"); - for (OsmNodeNamed wp : wps) { - bw.write(wp.toString()); - bw.write("\n"); + if (wps == null) { + bw.write("0\n"); + } else { + bw.write(wps.size() + "\n"); + for (OsmNodeNamed wp : wps) { + bw.write(wp.toString()); + bw.write("\n"); + } } } } From cbf172656be825df0c26118aa6f60c7902059f01 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Tue, 28 May 2024 10:13:35 +0200 Subject: [PATCH 157/173] updated doc for publishing --- docs/revisions.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/revisions.md b/docs/revisions.md index 97a53c3..a520dd0 100644 --- a/docs/revisions.md +++ b/docs/revisions.md @@ -2,6 +2,23 @@ (ZIP-Archives including APK, readme + profiles) +### next version + +Android + +- bug fix for repeat last route + +Library + +- ISO8601 compatible timestamps in log +- Update MIME type for GeoJSON responses + +Profiles + +- update gravel profile + +[Solved issues](https://github.com/abrensch/brouter/issues?q=is%3Aissue+milestone%3A%22Version+1.7.5%22+is%3Aclosed) + ### [brouter-1.7.4.zip](../brouter_bin/brouter-1.7.4.zip) (current revision, 09.04.2024) From 646f805b9998bc9c7dc4e413fa2f7d91d52503b3 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Wed, 29 May 2024 18:36:51 +0200 Subject: [PATCH 158/173] protect exception --- .../src/main/java/btools/routingapp/BImportActivity.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java index 2550bf9..0cd981e 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java @@ -73,13 +73,19 @@ public class BImportActivity extends AppCompatActivity { // URI example ==> dat=content://me.bluemail.mail.attachmentprovider/a2939069-76b5-44e4-8cbd-94485d0fd4ff/cc32b61d-97a6-4871-b67f-945d1d1d43c8/VIEW String filename = null; long filesize = 0L; + try (Cursor cursor = this.getContentResolver().query(intent.getData(), new String[]{ OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { filename = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)); filesize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE)); } + } catch (Exception e) { + resultMessage.append("ERROR: File not accessible\n"); + displayMessage(resultMessage.toString()); + return; } + // is the file extention ".brf" in the file name if (filename == null || isInvalidProfileFilename(filename)) { resultMessage.append("ERROR: File extention must be \".brf\"\n"); From 2a77f71c85ab88d3a9ebe3dba99d812a869afa3c Mon Sep 17 00:00:00 2001 From: afischerdev Date: Wed, 29 May 2024 18:38:31 +0200 Subject: [PATCH 159/173] updated doc --- docs/revisions.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/revisions.md b/docs/revisions.md index a520dd0..95b53d6 100644 --- a/docs/revisions.md +++ b/docs/revisions.md @@ -17,6 +17,9 @@ Profiles - update gravel profile + +- Minor bug fixes + [Solved issues](https://github.com/abrensch/brouter/issues?q=is%3Aissue+milestone%3A%22Version+1.7.5%22+is%3Aclosed) From 8f50671b98cb47d3163572096ec8ef1e7d1b414a Mon Sep 17 00:00:00 2001 From: afischerdev Date: Mon, 3 Jun 2024 20:04:34 +0200 Subject: [PATCH 160/173] Preparing for version 1.7.5 --- 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 7283b39..658c75f 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.4"; - final public static String versionDate = "09042024"; + final public static String version = "1.7.5"; + final public static String versionDate = "05062024"; // 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 dcc0e03..cc690a7 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -13,7 +13,7 @@ android { namespace 'btools.routingapp' applicationId "btools.routingapp" - versionCode 51 + versionCode 52 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 96f7f0a..02c9953 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.4' +version '1.7.5' diff --git a/docs/revisions.md b/docs/revisions.md index 95b53d6..db66bb6 100644 --- a/docs/revisions.md +++ b/docs/revisions.md @@ -2,7 +2,7 @@ (ZIP-Archives including APK, readme + profiles) -### next version +### [brouter-1.7.5.zip](../brouter_bin/brouter-1.7.5.zip) (current revision, 05.06.2024) Android @@ -23,7 +23,7 @@ Profiles [Solved issues](https://github.com/abrensch/brouter/issues?q=is%3Aissue+milestone%3A%22Version+1.7.5%22+is%3Aclosed) -### [brouter-1.7.4.zip](../brouter_bin/brouter-1.7.4.zip) (current revision, 09.04.2024) +### [brouter-1.7.4.zip](../brouter_bin/brouter-1.7.4.zip) (09.04.2024) Library From 2b3bbca4481e79be2d97329cc5f786cfaa06ab26 Mon Sep 17 00:00:00 2001 From: Alex <37914724+jmizv@users.noreply.github.com> Date: Mon, 17 Jun 2024 22:14:14 +0200 Subject: [PATCH 161/173] Added port mapping for docker run command It probably makes it easier for others using the instruction to have the port mapping ready. Also, use an explicit name for the container instead of a randomly by docker generated one. --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7f6d496..cecb125 100644 --- a/README.md +++ b/README.md @@ -170,15 +170,24 @@ Download the segment files as described in the previous chapter. The folder cont segment files can be mounted into the container. Run BRouter as follows: ``` -docker run --rm -v ./misc/scripts/segments4:/segments4 brouter +docker run --rm \ + -v ./misc/scripts/segments4:/segments4 \ + -p 17777:17777 \ + --name brouter \ + brouter ``` -This will start brouter with a set of default routing profiles. +This will start brouter with a set of default routing profiles. It will be accessible on port 17777. If you want to provide your own routing profiles, you can also mount the folder containing the custom profiles: ``` -docker run --rm -v ./misc/scripts/segments4:/segments4 -v /path/to/custom/profiles:/profiles2 brouter +docker run --rm \ + -v ./misc/scripts/segments4:/segments4 \ + -v /path/to/custom/profiles:/profiles2 \ + -p 17777:17777 \ + --name brouter \ + brouter ``` ## Documentation From 77e9bd316ba1a053898fbbc733a1b8d5b11622e3 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 20 Jun 2024 11:08:08 +0200 Subject: [PATCH 162/173] keep btools classes in proguard --- brouter-routing-app/proguard-rules.pro | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/brouter-routing-app/proguard-rules.pro b/brouter-routing-app/proguard-rules.pro index f1b4245..95f0278 100644 --- a/brouter-routing-app/proguard-rules.pro +++ b/brouter-routing-app/proguard-rules.pro @@ -19,3 +19,11 @@ # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile + +-keep class btools.codec.** { *; } +-keep class btools.router.** { *; } +-keep class btools.expressions.** { *; } +-keep class btools.mapaccess.** { *; } +-keep class btools.server.** { *; } +-keep class btools.util.** { *; } +-keep class btools.routingapp.** { *; } From 928bd0e28f43d981568387cfa0ce8b622763e574 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Thu, 20 Jun 2024 11:09:53 +0200 Subject: [PATCH 163/173] preparing for version 1.7.6 --- .../src/main/java/btools/router/OsmTrack.java | 4 ++-- brouter-routing-app/build.gradle | 2 +- .../main/groovy/brouter.version-conventions.gradle | 2 +- docs/revisions.md | 11 ++++++++++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index 658c75f..66c645f 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.5"; - final public static String versionDate = "05062024"; + final public static String version = "1.7.6"; + final public static String versionDate = "20062024"; // 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 cc690a7..e7daacc 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -13,7 +13,7 @@ android { namespace 'btools.routingapp' applicationId "btools.routingapp" - versionCode 52 + versionCode 53 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 02c9953..8e94c81 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.5' +version '1.7.6' diff --git a/docs/revisions.md b/docs/revisions.md index db66bb6..6268d6a 100644 --- a/docs/revisions.md +++ b/docs/revisions.md @@ -2,7 +2,16 @@ (ZIP-Archives including APK, readme + profiles) -### [brouter-1.7.5.zip](../brouter_bin/brouter-1.7.5.zip) (current revision, 05.06.2024) +### [brouter-1.7.6.zip](../brouter_bin/brouter-1.7.6.zip) (current revision, 20.06.2024) + +Android + +- bug fix for car profiles + +[Solved issues](https://github.com/abrensch/brouter/issues?q=is%3Aissue+milestone%3A%22Version+1.7.6%22+is%3Aclosed) + + +### [brouter-1.7.5.zip](../brouter_bin/brouter-1.7.5.zip) (05.06.2024) Android From c631714c1f268abefcf876d0228e89dfb8605979 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 12 Jul 2024 09:51:35 +0200 Subject: [PATCH 164/173] changed Android version --- 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 e7daacc..7328ca9 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -7,7 +7,7 @@ plugins { } android { - compileSdkVersion 33 + compileSdk 34 defaultConfig { namespace 'btools.routingapp' @@ -19,8 +19,8 @@ android { resValue('string', 'app_version', defaultConfig.versionName) setProperty("archivesBaseName", "BRouterApp." + defaultConfig.versionName) - minSdkVersion 14 - targetSdkVersion 33 + minSdkVersion 21 + targetSdkVersion 35 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } From dec6cc8ba0bc44120e7ab6ee343c7b8c0c074aa6 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 12 Jul 2024 09:53:54 +0200 Subject: [PATCH 165/173] changed gradle lib versions --- brouter-routing-app/build.gradle | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index 7328ca9..a853fdf 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -96,22 +96,22 @@ repositories { } dependencies { - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation "androidx.constraintlayout:constraintlayout:2.1.4" - implementation 'androidx.work:work-runtime:2.8.1' - implementation 'com.google.android.material:material:1.8.0' + implementation 'androidx.work:work-runtime:2.9.0' + implementation 'com.google.android.material:material:1.12.0' implementation project(':brouter-mapaccess') implementation project(':brouter-core') implementation project(':brouter-expressions') implementation project(':brouter-util') - implementation 'androidx.preference:preference:1.2.0' + implementation 'androidx.preference:preference:1.2.1' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - androidTestImplementation 'androidx.work:work-testing:2.8.1' + 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' } gradle.projectsEvaluated { From b1e9208be664e064d9a8e12455dbaff082cf7567 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 12 Jul 2024 09:54:57 +0200 Subject: [PATCH 166/173] added compiler params --- brouter-routing-app/build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index a853fdf..7dc0085 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -159,3 +159,8 @@ task generateReadmesZip(type: Zip) { } destinationDirectory = layout.buildDirectory.dir("assets") } + +tasks.withType(JavaCompile).configureEach { + options.compilerArgs += ['-Xlint:unchecked'] + options.compilerArgs += ['-Xlint:deprecation'] +} From 8d22a2d0eb10f9ef6fe4cf093ac7478d3bba32b1 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 12 Jul 2024 09:56:14 +0200 Subject: [PATCH 167/173] added new gradle app name --- brouter-routing-app/build.gradle | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index 7dc0085..56038cf 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -1,6 +1,7 @@ import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform plugins { + id 'base' id 'com.android.application' id 'checkstyle' id 'brouter.version-conventions' @@ -9,6 +10,10 @@ plugins { android { compileSdk 34 + base { + archivesName = "BRouterApp." + project.version + } + defaultConfig { namespace 'btools.routingapp' applicationId "btools.routingapp" @@ -17,7 +22,6 @@ android { versionName project.version resValue('string', 'app_version', defaultConfig.versionName) - setProperty("archivesBaseName", "BRouterApp." + defaultConfig.versionName) minSdkVersion 21 targetSdkVersion 35 From f289b0cd83153abd71a257d40480ef81e0bb468f Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 12 Jul 2024 10:25:52 +0200 Subject: [PATCH 168/173] suppressed "deprecation" --- .../java/btools/routingapp/RoutingParameterDialog.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 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 356e2d3..04c2d17 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java @@ -240,6 +240,7 @@ public class RoutingParameterDialog extends AppCompatActivity { } @Override + @SuppressWarnings("deprecation") public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -262,10 +263,10 @@ public class RoutingParameterDialog extends AppCompatActivity { if (i.hasExtra("PARAMS")) { List result; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - result = (List) i.getExtras().getSerializable("PARAMS", ArrayList.class); - } else { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { result = (List) i.getExtras().getSerializable("PARAMS"); + } else { + result = (List) i.getExtras().getSerializable("PARAMS", ArrayList.class); } if (result instanceof ArrayList) { for (Object o : result) { From 1f2f655863e4cc5bac8a9fd2c1cf520bbd0657e1 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 12 Jul 2024 10:34:47 +0200 Subject: [PATCH 169/173] changed printStackTrace to log --- .../java/btools/routingapp/BInstallerActivity.java | 13 +++++++------ .../main/java/btools/routingapp/BRouterView.java | 14 ++++++++------ .../btools/routingapp/RoutingParameterDialog.java | 5 +++-- .../main/java/btools/routingapp/ServerConfig.java | 6 +++++- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java index 5d375d6..6dcc259 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java @@ -46,6 +46,8 @@ import btools.router.RoutingHelper; public class BInstallerActivity extends AppCompatActivity { + private static final String TAG = "BInstallerActivity"; + private static final int DIALOG_CONFIRM_DELETE_ID = 1; private static final int DIALOG_CONFIRM_NEXTSTEPS_ID = 2; private static final int DIALOG_CONFIRM_GETDIFFS_ID = 3; @@ -216,7 +218,7 @@ public class BInstallerActivity extends AppCompatActivity { Object data; Toast.makeText(this, R.string.msg_too_much_data, Toast.LENGTH_LONG).show(); - e.printStackTrace(); + Log.e(TAG, Log.getStackTraceString(e)); return; } @@ -242,10 +244,9 @@ public class BInstallerActivity extends AppCompatActivity { //WorkManager.getInstance(getApplicationContext()).cancelWorkById(downloadWorkRequest.getId()); } } catch (ExecutionException e) { - e.printStackTrace(); + Log.e(TAG, Log.getStackTraceString(e)); } catch (InterruptedException e) { - Log.d("worker", "canceled " + e.getMessage()); - //e.printStackTrace(); + Log.d(TAG, "canceled " + e.getMessage()); } workManager @@ -516,10 +517,10 @@ public class BInstallerActivity extends AppCompatActivity { } return running; } catch (ExecutionException e) { - e.printStackTrace(); + Log.e(TAG, Log.getStackTraceString(e)); return false; } catch (InterruptedException e) { - e.printStackTrace(); + Log.e(TAG, Log.getStackTraceString(e)); return false; } } 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 674eaf9..efdbdfb 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -48,6 +48,8 @@ import btools.util.CheapRuler; public class BRouterView extends View { + private static final String TAG = "BRouterView"; + private final int memoryClass; RoutingEngine cr; private int imgw; @@ -148,8 +150,8 @@ public class BRouterView extends View { try { td.mkdirs(); } catch (Exception e) { - Log.d("BRouterView", "Error creating base directory: " + e.getMessage()); - e.printStackTrace(); + Log.d(TAG, "Error creating base directory: " + e.getMessage()); + Log.e(TAG, Log.getStackTraceString(e)); } if (!td.isDirectory()) { @@ -173,7 +175,7 @@ public class BRouterView extends View { // new init is done move old files if (waitingForMigration) { - Log.d("BR", "path " + oldMigrationPath + " " + basedir); + Log.d(TAG, "path " + oldMigrationPath + " " + basedir); Thread t = new Thread(new Runnable() { @Override public void run() { @@ -184,7 +186,7 @@ public class BRouterView extends View { try { t.join(500); } catch (InterruptedException e) { - e.printStackTrace(); + Log.e(TAG, Log.getStackTraceString(e)); } waitingForMigration = false; } @@ -333,9 +335,9 @@ public class BRouterView extends View { out.close(); } catch (FileNotFoundException fileNotFoundException) { - Log.e("tag", fileNotFoundException.getMessage()); + Log.e(TAG, fileNotFoundException.getMessage()); } catch (Exception e) { - Log.e("tag", e.getMessage()); + Log.e(TAG, e.getMessage()); } } 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 04c2d17..d69a037 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java @@ -7,6 +7,7 @@ import android.content.SharedPreferences; import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; +import android.util.Log; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; @@ -146,7 +147,7 @@ public class RoutingParameterDialog extends AppCompatActivity { list.add(p); } } catch (Exception e) { - e.printStackTrace(); + Log.e(TAG, Log.getStackTraceString(e)); } } } while (line != null); @@ -278,7 +279,7 @@ public class RoutingParameterDialog extends AppCompatActivity { sparams = i.getExtras().getString("PARAMS_VALUES", ""); } } catch (Exception e) { - e.printStackTrace(); + Log.e(TAG, Log.getStackTraceString(e)); } getPreferenceManager().setSharedPreferencesName("prefs_profile_" + profile_hash); diff --git a/brouter-routing-app/src/main/java/btools/routingapp/ServerConfig.java b/brouter-routing-app/src/main/java/btools/routingapp/ServerConfig.java index 0d83d49..a059a19 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/ServerConfig.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/ServerConfig.java @@ -2,6 +2,7 @@ package btools.routingapp; import android.content.Context; import android.content.res.AssetManager; +import android.util.Log; import java.io.BufferedReader; import java.io.File; @@ -13,6 +14,9 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; public class ServerConfig { + + private static final String TAG = "ServerConfig"; + private static String mServerConfigName = "serverconfig.txt"; private String mSegmentUrl = "https://brouter.de/brouter/segments4/"; @@ -52,7 +56,7 @@ public class ServerConfig { } } } catch (IOException e) { - e.printStackTrace(); + Log.e(TAG, Log.getStackTraceString(e)); } finally { try { if (br != null) br.close(); From e379b7abb0f3fa5be7e974749dc50ccdf42d8956 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Fri, 12 Jul 2024 18:37:03 +0200 Subject: [PATCH 170/173] changed android tests for "deprecation" --- .../androidTest/java/btools/routingapp/BRouterActivityTest.java | 2 +- .../androidTest/java/btools/routingapp/DownloadWorkerTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/brouter-routing-app/src/androidTest/java/btools/routingapp/BRouterActivityTest.java b/brouter-routing-app/src/androidTest/java/btools/routingapp/BRouterActivityTest.java index 6a6a9cd..43b89f7 100644 --- a/brouter-routing-app/src/androidTest/java/btools/routingapp/BRouterActivityTest.java +++ b/brouter-routing-app/src/androidTest/java/btools/routingapp/BRouterActivityTest.java @@ -1,9 +1,9 @@ package btools.routingapp; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertThat; import android.os.Build; import android.os.Environment; diff --git a/brouter-routing-app/src/androidTest/java/btools/routingapp/DownloadWorkerTest.java b/brouter-routing-app/src/androidTest/java/btools/routingapp/DownloadWorkerTest.java index a9004c0..c0ce44d 100644 --- a/brouter-routing-app/src/androidTest/java/btools/routingapp/DownloadWorkerTest.java +++ b/brouter-routing-app/src/androidTest/java/btools/routingapp/DownloadWorkerTest.java @@ -1,7 +1,7 @@ package btools.routingapp; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; import android.content.Context; From b234d48c00a80f3ba7c63a4e773c4fe97abe4884 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 13 Jul 2024 09:43:49 +0200 Subject: [PATCH 171/173] changed to android 34 --- brouter-routing-app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index 56038cf..a2a58e6 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 35 + targetSdkVersion 34 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } From e46444cf57a50d9e636d417a64c8375cb43d020e Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sat, 13 Jul 2024 09:46:58 +0200 Subject: [PATCH 172/173] updated revision doc --- docs/revisions.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/revisions.md b/docs/revisions.md index 6268d6a..5cc9dc3 100644 --- a/docs/revisions.md +++ b/docs/revisions.md @@ -2,6 +2,11 @@ (ZIP-Archives including APK, readme + profiles) +### since last version + +- new Android API 34 + + ### [brouter-1.7.6.zip](../brouter_bin/brouter-1.7.6.zip) (current revision, 20.06.2024) Android From 15bf08aaef9fb4bc7afc1382147883da4a480f67 Mon Sep 17 00:00:00 2001 From: afischerdev Date: Sun, 21 Jul 2024 11:31:14 +0200 Subject: [PATCH 173/173] prepared the version change --- 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 66c645f..859fdc8 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.6"; - final public static String versionDate = "20062024"; + final public static String version = "1.7.7"; + final public static String versionDate = "23072024"; // 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 a2a58e6..ebcc34b 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 53 + versionCode 54 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 8e94c81..b99c641 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.6' +version '1.7.7' diff --git a/docs/revisions.md b/docs/revisions.md index 5cc9dc3..e58e2a9 100644 --- a/docs/revisions.md +++ b/docs/revisions.md @@ -2,12 +2,12 @@ (ZIP-Archives including APK, readme + profiles) -### since last version +### [brouter-1.7.7.zip](../brouter_bin/brouter-1.7.7.zip) (current revision, 23.07.2024) - new Android API 34 -### [brouter-1.7.6.zip](../brouter_bin/brouter-1.7.6.zip) (current revision, 20.06.2024) +### [brouter-1.7.6.zip](../brouter_bin/brouter-1.7.6.zip) (20.06.2024) Android