Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions helper-img/heal-map-client-helper-geojson-import.sh
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
#!/bin/bash
# ORS heal-map-client-helper-geojson-import
########################################################################################################################
rclone config create heal s3 --non-interactive --quiet \
rclone config create heal s3 --non-interactive --quiet \
provider=Minio \
access_key_id="$HEAL_KEY_ID" \
secret_access_key="$HEAL_ACCESS_KEY" \
endpoint="$HEAL_URL" \
acl=private

echo "Downloading index.json..."
rclone copyto heal:/"$HEAL_BUCKET"/"$HEAL_PREFIX"/output/index.json ./aois/index.json --quiet
echo "Downloading countries.json..."
rclone copyto heal:/"$HEAL_BUCKET"/"$HEAL_PREFIX"/output/countries.json ./aois/countries.json --quiet

########################################################################################################################
echo "Downloading corresponding GeoJSON files..."
mkdir -p aois

while IFS= read -r state; do
while IFS= read -r city; do
echo " Downloading germany/aois/${state}/${city}.geojson..."
rclone copyto \
heal:/"$HEAL_BUCKET"/"$HEAL_PREFIX"/output/aois/germany/"${state}"/"${city}".geojson \
./aois/"${state}"/"${city}".geojson --quiet
done < <(jq -r --arg s "$state" '.[$s][]' ./aois/index.json)
done < <(jq -r 'keys[]' ./aois/index.json)
while IFS= read -r country; do
while IFS= read -r state; do
while IFS= read -r city; do
echo " Downloading /aois/${country}/${state}/${city}.geojson..."
rclone copyto \
heal:/"$HEAL_BUCKET"/"$HEAL_PREFIX"/output/aois/"${country}"/"${state}"/"${city}".geojson \
./aois/"${country}"/"${state}"/"${city}".geojson --quiet
done < <(jq -r --arg c "$country" --arg s "$state" '.[$c][$s][]' ./aois/countries.json)
done < <(jq -r --arg c "$country" '.[$c] | keys[]' ./aois/countries.json)
done < <(jq -r 'keys[]' ./aois/countries.json)

find ./aois

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ors-maps-client",
"version": "3.0.6",
"version": "3.0.7",
"private": false,
"description": "Openrouteservice maps client",
"repository": "https://github.com/GIScience/ors-map-client",
Expand Down
2 changes: 1 addition & 1 deletion src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Info from '@/fragments/dialogs/info/Info'
import MainMenu from '@/common/main-menu'
import utils from '@/support/utils'
import {EventBus} from '@/common/event-bus'
import CitySelector from '@/fragments/city-selector/CitySelector.vue';
import CitySelector from '@/fragments/city-selector/CitySelector.vue'


export default {
Expand Down
1 change: 1 addition & 0 deletions src/config-examples/app-config-example.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const appConfig = {
setCustomMenuIcons: true, // If the icons of the menu loaded must be customized via (only necessary if useORSMenu is true)
baseMenuExternalUrl: 'https://openrouteservice.org' // The base url to retrieve the menu items
},
defaultCountry: 'germany',
defaultState: 'baden-wuerttemberg',
defaultCity: 'heidelberg',
defaultLocale: 'de-de', // only set as default a locale that is present in the app. By default, they are: 'en-us', 'de-de' and 'pt-br'
Expand Down
8 changes: 8 additions & 0 deletions src/fragments/city-selector/CitySelector.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
<template>
<div class="container">
<v-select
:label="$t('citySelector.country')"
:items="countryOptions"
item-text="text"
item-value="value"
v-model="selectedCountry"
/>
<v-select
:label="$t('citySelector.state')"
:items="states"
item-text="text"
item-value="value"
v-model="selectedState"
:disabled="!selectedCountry"
/>
<v-select
:label="$t('citySelector.city')"
Expand Down
67 changes: 49 additions & 18 deletions src/fragments/city-selector/city-selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,79 @@ export default {
}

return {
selectedCountry: urlParams.get('country') || appConfig.defaultCountry,
selectedState: urlParams.get('state') || appConfig.defaultState,
selectedCity: urlParams.get('city') || appConfig.defaultCity,
aois: {}
countries: {}
}
},

async created() {
try {
const res = await fetch(`/aois/index.json`)
this.aois = await res.json()
const res = await fetch('/aois/countries.json')
this.countries = await res.json()
} catch (e) {
console.error('Failed to load AOIs:', e)
console.error('Failed to load countries:', e)
}
},

computed: {
stateCityMap() {
countryStateCityMap() {
const map = {}

Object.entries(this.aois).forEach(([rawState, cities]) => {
map[rawState] = {
text: this.prepareString(rawState),
value: rawState,
cities: cities.map(rawCity => ({
text: this.prepareString(rawCity),
value: rawCity
}))
Object.entries(this.countries).forEach(([rawCountry, states]) => {
const stateMap = {}

Object.entries(states || {}).forEach(([rawState, cities]) => {
stateMap[rawState] = {
text: this.localizedName("states", rawState),
value: rawState,
cities: (cities || []).map((rawCity) => ({
text: this.localizedName("cities", rawCity),
value: rawCity
}))
}
})

map[rawCountry] = {
text: this.localizedName("countries", rawCountry),
value: rawCountry,
states: stateMap
}
})

return map
},

countryOptions() {
return Object.values(this.countryStateCityMap)
.sort((a, b) => a.text.localeCompare(b.text))
},

states() {
return Object.values(this.stateCityMap)
if (!this.selectedCountry) return []
return Object.values(this.countryStateCityMap[this.selectedCountry]?.states || {})
.sort((a, b) => a.text.localeCompare(b.text))
},

cities() {
if (!this.selectedState) return []
return (this.stateCityMap[this.selectedState]?.cities || [])
if (!this.selectedCountry || !this.selectedState) return []
return (this.countryStateCityMap[this.selectedCountry]?.states[this.selectedState]?.cities || [])
.sort((a, b) => a.text.localeCompare(b.text))
}
},
watch: {
selectedCountry(newCountry) {
const states = Object.values(this.countryStateCityMap[newCountry]?.states || {})
if (states.length === 1) {
this.selectedState = states[0].value
} else {
this.selectedState = null
this.selectedCity = null
}
},
selectedState(newState) {
const cities = this.stateCityMap[newState]?.cities || []
const cities = this.countryStateCityMap[this.selectedCountry]?.states[newState]?.cities || []
if (cities.length === 1) {
this.selectedCity = cities[0].value
this.changeCity()
Expand All @@ -68,6 +94,10 @@ export default {
}
},
methods: {
localizedName(category, slug) {
const key = `citySelector.places.${category}.${slug}`
return this.$te(key) ? this.$t(key) : this.prepareString(slug)
},
prepareString(s) {
return (String(s[0]).toUpperCase() + String(s).slice(1))
.replace('ae', 'ä')
Expand All @@ -79,14 +109,15 @@ export default {
this.$router.push({
name: 'MapLocation',
query: {
country: this.selectedCountry,
state: this.selectedState,
city: this.selectedCity,
}
})

EventBus.$emit(
'city-change',
this.selectedState + '/' + this.selectedCity
this.selectedCountry + '/' + this.selectedState + '/' + this.selectedCity
)
}
}
Expand Down
41 changes: 40 additions & 1 deletion src/fragments/city-selector/i18n/city-selector.i18n.de-de.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@

export default {
citySelector: {
country: 'Land',
state: 'Bundesland',
city: 'Stadt'
city: 'Stadt',
places: {
countries: {
germany: 'Deutschland',
austria: 'Österreich'
},
states: {
'baden-wuerttemberg': 'Baden-Württemberg',
bayern: 'Bayern',
berlin: 'Berlin',
bremen: 'Bremen',
brandenburg: 'Brandenburg',
hamburg: 'Hamburg',
hessen: 'Hessen',
'mecklenburg-vorpommern': 'Mecklenburg-Vorpommern',
niedersachsen: 'Niedersachsen',
'nordrhein-westfalen': 'Nordrhein-Westfalen',
'rheinland-pfalz': 'Rheinland-Pfalz',
saarland: 'Saarland',
sachsen: 'Sachsen',
'sachsen-anhalt': 'Sachsen-Anhalt',
'schleswig-holstein': 'Schleswig-Holstein',
thueringen: 'Thüringen',
wien: 'Wien',
oberoesterreich: 'Oberösterreich',
salzburg: 'Salzburg',
steiermark: 'Steiermark',
tirol: 'Tirol'
},
// Only cities whose German name differs from the slug-derived
// fallback (prepareString) need an entry here.
cities: {
muenchen: 'München',
koeln: 'Köln',
nuernberg: 'Nürnberg',
hannover: 'Hannover',
wien: 'Wien'
}
}
}
}
41 changes: 40 additions & 1 deletion src/fragments/city-selector/i18n/city-selector.i18n.en-us.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@

export default {
citySelector: {
country: 'Country',
state: 'State',
city: 'City'
city: 'City',
places: {
countries: {
germany: 'Germany',
austria: 'Austria'
},
states: {
'baden-wuerttemberg': 'Baden-Württemberg',
bayern: 'Bavaria',
berlin: 'Berlin',
bremen: 'Bremen',
brandenburg: 'Brandenburg',
hamburg: 'Hamburg',
hessen: 'Hesse',
'mecklenburg-vorpommern': 'Mecklenburg-Vorpommern',
niedersachsen: 'Lower Saxony',
'nordrhein-westfalen': 'North Rhine-Westphalia',
'rheinland-pfalz': 'Rhineland-Palatinate',
saarland: 'Saarland',
sachsen: 'Saxony',
'sachsen-anhalt': 'Saxony-Anhalt',
'schleswig-holstein': 'Schleswig-Holstein',
thueringen: 'Thuringia',
wien: 'Vienna',
oberoesterreich: 'Upper Austria',
salzburg: 'Salzburg',
steiermark: 'Styria',
tirol: 'Tyrol'
},
// Only cities whose anglicized name differs from the slug-derived
// fallback (prepareString) need an entry here.
cities: {
muenchen: 'Munich',
koeln: 'Cologne',
nuernberg: 'Nuremberg',
hannover: 'Hanover',
wien: 'Vienna'
}
}
}
}
17 changes: 9 additions & 8 deletions src/fragments/map-view/map-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -1981,16 +1981,16 @@
})
}
},
loadRegion(state, city) {
loadRegion(country, state, city) {
try {

Check warning on line 1985 in src/fragments/map-view/map-view.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using 'await' for the promise inside this 'try' or replace it with 'Promise.prototype.catch(...)' usage.

See more on https://sonarcloud.io/project/issues?id=GIScience_ors-map-client&issues=AZ7sCsQB07zXu9atrbXA&open=AZ7sCsQB07zXu9atrbXA&pullRequest=492
fetch(`/aois/${state}/${city}.geojson`)
fetch(`/aois/${country}/${state}/${city}.geojson`)
.then(result => result.json())
.then(json => {
this.region = json
this.addRegionOfInterest()
})
} catch (e) {
console.error(`Error loading region for ${state}/${city}:`, e)
console.error(`Error loading region for ${country}/${state}/${city}:`, e)
}
},
/**
Expand Down Expand Up @@ -2032,10 +2032,10 @@
}
})

EventBus.$on('city-change', (stateCityPath) => {
const [state, city] = stateCityPath.split('/')
if (state && city) {
context.loadRegion(state, city)
EventBus.$on('city-change', (countryStateCityPath) => {
const [country, state, city] = countryStateCityPath.split('/')
if (country && state && city) {
context.loadRegion(country, state, city)
}
})
},
Expand Down Expand Up @@ -2105,10 +2105,11 @@
if (url.length > 1) {
urlParams = new URLSearchParams(url[1])
}
const initialCountry = urlParams.get('country') || appConfig.defaultCountry
const initialState = urlParams.get('state') || appConfig.defaultState
const initialCity = urlParams.get('city') || appConfig.defaultCity

this.loadRegion(initialState, initialCity)
this.loadRegion(initialCountry, initialState, initialCity)
} catch (e) {
console.error('Error loading region config:', e)
}
Expand Down
17 changes: 16 additions & 1 deletion src/support/map-data-services/ors-params-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,18 @@
adjustCSVColumn()

let region_bbox = []

// aois/ path segments are slugs like 'baden-wuerttemberg' or 'heidelberg' - reject
// anything else (e.g. '..', '/', encoded path traversal) before it reaches fetch().
const AOI_SEGMENT_PATTERN = /^[a-z0-9-]+$/
const isValidAoiSegment = (segment) => typeof segment === 'string' && AOI_SEGMENT_PATTERN.test(segment)

EventBus.$on('city-change', (path) => {
const segments = path.split('/')
if (segments.length !== 3 || !segments.every(isValidAoiSegment)) {
console.error('Invalid city-change path, ignoring:', path)
return
}
fetch(`/aois/${path}.geojson`)
.then(result => result.json())
.then(json => {
Expand All @@ -47,9 +58,13 @@
if (url.length > 1) {
urlParams = new URLSearchParams(url[1])
}
const country = urlParams.get('country') || appConfig.defaultCountry
const state = urlParams.get('state') || appConfig.defaultState
const city = urlParams.get('city') || appConfig.defaultCity
fetch(`/aois/${state}/${city}.geojson`)
if (![country, state, city].every(isValidAoiSegment)) {
throw new Error(`Invalid country/state/city in URL: ${country}/${state}/${city}`)
}
fetch(`/aois/${country}/${state}/${city}.geojson`)

Check failure on line 67 in src/support/map-data-services/ors-params-parser.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Ensure that tainted data is validated before being used to construct a client-side request URL.

See more on https://sonarcloud.io/project/issues?id=GIScience_ors-map-client&issues=AZ7dRLu9L49KgC-o0IKJ&open=AZ7dRLu9L49KgC-o0IKJ&pullRequest=492
.then(result => result.json())
.then(json => {
region_bbox = Leaflet.geoJSON(json).getBounds()
Expand Down
Loading