Skip to content

Commit

Permalink
feat(location): repace Nominatim with Photon (#517)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphodn committed Apr 10, 2024
1 parent c9d4587 commit 7ebb44c
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 43 deletions.
52 changes: 28 additions & 24 deletions src/components/LocationSelectorDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</v-text-field>
</v-form>

<p class="text-caption text-warning mt-2">
<p v-if="searchProvider === 'nominatim'" class="text-caption text-warning mt-2">
<i18n-t keypath="LocationSelector.Warning" tag="i">
<template #newline><br /></template>
</i18n-t>
Expand All @@ -48,20 +48,20 @@
elevation="1"
@click="selectLocation(location)">
<v-card-text>
<h4>{{ getNominatimLocationTitle(location, true, false, false) }}</h4>
{{ getNominatimLocationTitle(location, false, true, true) }}<br />
<v-chip label size="small" density="comfortable">{{ location.type }}</v-chip>
<h4>{{ getLocationTitle(location, true, false, false) }}</h4>
{{ getLocationTitle(location, false, true, true) }}<br />
<v-chip label size="small" density="comfortable">{{ getLocationCategory(location) }}</v-chip>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" sm="6" style="min-height:200px">
<l-map ref="map" v-model:zoom="mapZoom" :center="mapCenter" :use-global-leaflet="false" @ready="initMap">
<l-tile-layer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" layer-type="base" name="OpenStreetMap"></l-tile-layer>
<l-marker v-for="location in results" :lat-lng="[location.lat, location.lon]">
<l-marker v-for="location in results" :lat-lng="getLocationLatLng(location)">
<l-popup>
<h4>{{ getNominatimLocationTitle(location, true, false, false) }}</h4>
{{ getNominatimLocationTitle(location, false, true, true) }}<br />
<v-chip label size="small" density="comfortable">{{ location.type }}</v-chip>
<h4>{{ getLocationTitle(location, true, false, false) }}</h4>
{{ getLocationTitle(location, false, true, true) }}<br />
<v-chip label size="small" density="comfortable">{{ getLocationCategory(location) }}</v-chip>
</l-popup>
</l-marker>
</l-map>
Expand All @@ -85,12 +85,12 @@
class="mb-2"
closable
v-for="location in recentLocations"
:key="location.display_name"
:key="getLocationUniqueID(location)"
prepend-icon="mdi-history"
close-icon="mdi-delete"
@click="selectLocation(location)"
@click:close="removeRecentLocation(location)">
{{ getNominatimLocationTitle(location, true, true, true) }}
{{ getLocationTitle(location, true, true, true) }}
</v-chip>
<br />
<v-btn size="small" @click="clearRecentLocations">
Expand All @@ -102,9 +102,10 @@

<v-card-actions class="justify-end">
<div>
<i18n-t keypath="LocationSelector.OSM.text" tag="span">
<i18n-t keypath="LocationSelector.PoweredBy.text" tag="span">
<template #url>
<a href="https://nominatim.openstreetmap.org" target="_blank">OpenStreetMap Nominatim</a>
<a v-if="searchProvider === 'nominatim'" href="https://nominatim.openstreetmap.org" target="_blank">Nominatim (OpenStreetMap)</a>
<a v-if="searchProvider === 'photon'" href="https://photon.komoot.io" target="_blank">Komoot Photon (OpenStreetMap)</a>
</template>
</i18n-t>
</div>
Expand Down Expand Up @@ -140,7 +141,9 @@ export default {
map: null,
mapZoom: 5,
mapCenter: [45, 5],
mapBounds: null
mapBounds: null,
// search
searchProvider: 'photon', // 'nominatim', 'photon'
}
},
computed: {
Expand Down Expand Up @@ -168,15 +171,15 @@ export default {
search() {
this.results = null
this.loading = true
api.openstreetmapNominatimSearch(this.locationSearchForm.q)
api.openstreetmapSearch(this.locationSearchForm.q, this.searchProvider)
.then((data) => {
this.loading = false
if (data.length) {
this.results = data
if (this.results.length > 1) {
this.mapBounds = this.results.map(l => [l.lat, l.lon])
this.mapBounds = utils.getMapBounds(this.results, this.searchProvider)
} else {
this.mapCenter = this.results.map(l => [l.lat, l.lon])[0]
this.mapCenter = utils.getMapCenter(this.results, this.searchProvider)
this.mapZoom = 12
this.mapBounds = null
}
Expand All @@ -185,16 +188,17 @@ export default {
}
})
},
getNominatimLocationName(location) {
return location.name
getLocationTitle(location, withName=true, withRoad=false, withCity=true) {
return utils.getLocationTitle(location, withName, withRoad, withCity)
},
getNominatimLocationCity(location) {
if (location.address) {
return location.address.village || location.address.town || location.address.city || location.address.municipality
}
getLocationUniqueID(location) {
return utils.getLocationUniqueID(location)
},
getNominatimLocationTitle(location, withName=true, withRoad=false, withCity=true) {
return utils.getLocationTitle(location, withName, withRoad, withCity)
getLocationCategory(location) {
return utils.getLocationCategory(location)
},
getLocationLatLng(location) {
return utils.getLocationLatLng(location)
},
selectLocation(location) {
this.$emit('location', location)
Expand Down
2 changes: 2 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export default {
],
OSM_NAME: 'OpenStreetMap',
OSM_URL: 'https://www.openstreetmap.org',
OSM_NOMINATIM_SEARCH_URL: 'https://nominatim.openstreetmap.org/search',
OSM_PHOTON_SEARCH_URL: 'https://photon.komoot.io/api/',
// https://wiki.openstreetmap.org/wiki/Key:place
// https://wiki.openstreetmap.org/wiki/Key:highway
// https://wiki.openstreetmap.org/wiki/Buildings
Expand Down
2 changes: 1 addition & 1 deletion src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
"LocationSelector": {
"Clear": "Clear",
"NoResult": "No results found",
"OSM": {
"PoweredBy": {
"text": "powered by {url}"
},
"RecentLocations": "No recent locations | Recent location {recentLocationNumber}| Recent locations {recentLocationNumber}",
Expand Down
19 changes: 17 additions & 2 deletions src/services/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useAppStore } from '../store'
import constants from '../constants'

const OPENFOODFACTS_PRODUCT_URL = 'https://world.openfoodfacts.org/api/v2/product'
const NOMINATIM_SEARCH_URL = 'https://nominatim.openstreetmap.org/search'


export default {
Expand Down Expand Up @@ -212,10 +211,26 @@ export default {
},

openstreetmapNominatimSearch(q) {
return fetch(`${NOMINATIM_SEARCH_URL}?q=${q}&addressdetails=1&format=json&limit=10`, {
return fetch(`${constants.OSM_NOMINATIM_SEARCH_URL}?q=${q}&addressdetails=1&format=json&limit=10`, {
method: 'GET',
})
.then((response) => response.json())
.then((data) => data.filter(l => !constants.NOMINATIM_RESULT_TYPE_EXCLUDE_LIST.includes(l.type)))
},
openstreetmapPhotonSearch(q) {
return fetch(`${constants.OSM_PHOTON_SEARCH_URL}?q=${q}&limit=10`, {
method: 'GET',
})
.then((response) => response.json())
.then(data => data.features)
.then((data) => data.filter(l => !constants.NOMINATIM_RESULT_TYPE_EXCLUDE_LIST.includes(l.properties.osm_key)))
},
openstreetmapSearch(q, source='nominatim') {
if (source === 'photon') {
return this.openstreetmapPhotonSearch(q)
} else {
// default to nominatim
return this.openstreetmapNominatimSearch(q)
}
}
}
81 changes: 79 additions & 2 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,12 @@ function getCountryEmojiFromName(countryString) {
}

function getLocationName(locationObject) {
// Photon
if (locationObject.properties) {
return locationObject.properties.name
}
// Nominatim or OP
return locationObject.osm_name || locationObject.name
return locationObject.name || locationObject.osm_name
}

function getLocationRoad(locationObject) {
Expand All @@ -112,6 +116,10 @@ function getLocationRoad(locationObject) {
locationRoad += locationObject.address.road || ''
return locationRoad
}
// Photon
else if (locationObject.properties) {
return locationObject.properties.street
}
// OP
return ''
}
Expand All @@ -121,6 +129,10 @@ function getLocationCity(locationObject) {
if (locationObject.address) {
return locationObject.address.village || locationObject.address.town || locationObject.address.city || locationObject.address.municipality
}
// Photon
else if (locationObject.properties) {
return locationObject.properties.village || locationObject.properties.town || locationObject.properties.city || locationObject.properties.municipality
}
// OP
return locationObject.osm_address_city || ''
}
Expand All @@ -130,7 +142,7 @@ function getLocationTitle(locationObject, withName=true, withRoad=false, withCit
if (withName) {
locationTitle += `${getLocationName(locationObject)}`
}
if (withRoad && locationObject.address) {
if (withRoad && (locationObject.address || locationObject.properties)) {
locationTitle += locationTitle ? ', ' : ''
locationTitle += getLocationRoad(locationObject)
}
Expand All @@ -144,6 +156,64 @@ function getLocationTitle(locationObject, withName=true, withRoad=false, withCit
return locationTitle
}

function getLocationID(locationObject) {
// Photon
if (locationObject.properties) {
return locationObject.properties.osm_id
}
// Nominatim or OP
return locationObject.osm_id
}

function getLocationType(locationObject) {
if (locationObject.properties) {
const OSM_TYPE_MAPPING = {"N": "Node", "W": "Way", "R": "Relation"}
return OSM_TYPE_MAPPING[locationObject.properties.osm_type].toUpperCase()
}
// Nominatim or OP
return locationObject.osm_type.toUpperCase()
}

function getLocationUniqueID(locationObject) {
// examples: N12345
return `${getLocationType(locationObject)[0]}${getLocationID(locationObject).toString()}`
}

function getLocationCategory(locationObject) {
// examples: shop, amenity, building
// Photon
if (locationObject.properties) {
return locationObject.properties.osm_key
}
// Nominatim or OP
return locationObject.type || locationObject.osm_type
}

function getLocationLatLng(locationObject) {
// Nominatim
if (locationObject.lat && locationObject.lon) {
return [locationObject.lat, locationObject.lon]
}
// Photon
else if (locationObject.geometry && locationObject.geometry.coordinates) {
return [locationObject.geometry.coordinates[1], locationObject.geometry.coordinates[0]]
}
return [locationObject.osm_lat, locationObject.osm_lon]
}

function getMapBounds(results, source='nominatim') {
if (source === 'photon') {
return results.map(l => [l.geometry.coordinates[1], l.geometry.coordinates[0]])
}
return results.map(l => [l.lat, l.lon])
}
function getMapCenter(results, source='nominatim') {
if (source === 'photon') {
return [results[0].geometry.coordinates[1], results[0].geometry.coordinates[0]]
}
return [results[0].lat, results[0][lon]]
}


export default {
addObjectToArray,
Expand All @@ -158,4 +228,11 @@ export default {
getLocaleOriginTags,
getCountryEmojiFromName,
getLocationTitle,
getLocationID,
getLocationType,
getLocationUniqueID,
getLocationCategory,
getLocationLatLng,
getMapBounds,
getMapCenter,
}
16 changes: 9 additions & 7 deletions src/views/AddPriceMultiple.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@
class="mb-2"
:style="isSelectedLocation(location) ? 'border: 1px solid #4CAF50' : 'border: 1px solid transparent'"
v-for="location in recentLocations"
:key="getLocationUniqueID(location)"
@click="setLocationData(location)">
<v-icon start :icon="isSelectedLocation(location) ? 'mdi-check-circle-outline' : 'mdi-history'" :color="isSelectedLocation(location) ? 'green' : ''"></v-icon>
{{ getNominatimLocationTitle(location, true, true, true) }}
{{ getLocationTitle(location, true, true, true) }}
</v-chip>
<br v-if="recentLocations.length" />
<v-btn class="mb-2" size="small" prepend-icon="mdi-magnify" @click="showLocationSelectorDialog">{{ $t('AddPriceSingle.WhereWhen.Find') }}</v-btn>
Expand Down Expand Up @@ -361,7 +362,6 @@ export default {
proofisSelected: false,
// location data
locationSelectorDialog: false,
locationSelectedDisplayName: '',
// product price data
productPriceUploadedList: [],
productPriceNew: {
Expand Down Expand Up @@ -538,17 +538,19 @@ export default {
showLocationSelectorDialog() {
this.locationSelectorDialog = true
},
getNominatimLocationTitle(location, withName=true, withRoad=false, withCity=true) {
getLocationTitle(location, withName=true, withRoad=false, withCity=true) {
return utils.getLocationTitle(location, withName, withRoad, withCity)
},
getLocationUniqueID(location) {
return utils.getLocationUniqueID(location)
},
setLocationData(location) {
this.appStore.addRecentLocation(location)
this.locationSelectedDisplayName = location.display_name
this.addPriceMultipleForm.location_osm_id = location.osm_id
this.addPriceMultipleForm.location_osm_type = location.osm_type.toUpperCase()
this.addPriceMultipleForm.location_osm_id = utils.getLocationID(location)
this.addPriceMultipleForm.location_osm_type = utils.getLocationType(location)
},
isSelectedLocation(location) {
return this.locationSelectedDisplayName && this.locationSelectedDisplayName === location.display_name
return (this.addPriceMultipleForm.location_osm_id === utils.getLocationID(location)) && (this.addPriceMultipleForm.location_osm_type === utils.getLocationType(location))
},
showBarcodeScannerDialog() {
this.barcodeScannerDialog = true
Expand Down
16 changes: 9 additions & 7 deletions src/views/AddPriceSingle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,10 @@
class="mb-2"
:style="isSelectedLocation(location) ? 'border: 1px solid #4CAF50' : 'border: 1px solid transparent'"
v-for="location in recentLocations"
:key="getLocationUniqueID(location)"
@click="setLocationData(location)">
<v-icon start :icon="isSelectedLocation(location) ? 'mdi-check-circle-outline' : 'mdi-history'" :color="isSelectedLocation(location) ? 'green' : ''"></v-icon>
{{ getNominatimLocationTitle(location, true, true, true) }}
{{ getLocationTitle(location, true, true, true) }}
</v-chip>
<br v-if="recentLocations.length" />
<v-btn class="mb-2" size="small" prepend-icon="mdi-magnify" @click="showLocationSelectorDialog">{{ $t('AddPriceSingle.WhereWhen.Find') }}</v-btn>
Expand Down Expand Up @@ -318,7 +319,6 @@ export default {
barcodeManualInputDialog: false,
// location data
locationSelectorDialog: false,
locationSelectedDisplayName: '',
// proof data
userRecentProofsDialog: false,
proofImage: null,
Expand Down Expand Up @@ -497,17 +497,19 @@ export default {
showLocationSelectorDialog() {
this.locationSelectorDialog = true
},
getNominatimLocationTitle(location, withName=true, withRoad=false, withCity=true) {
getLocationTitle(location, withName=true, withRoad=false, withCity=true) {
return utils.getLocationTitle(location, withName, withRoad, withCity)
},
getLocationUniqueID(location) {
return utils.getLocationUniqueID(location)
},
setLocationData(location) {
this.appStore.addRecentLocation(location)
this.locationSelectedDisplayName = location.display_name
this.addPriceSingleForm.location_osm_id = location.osm_id
this.addPriceSingleForm.location_osm_type = location.osm_type.toUpperCase()
this.addPriceSingleForm.location_osm_id = utils.getLocationID(location)
this.addPriceSingleForm.location_osm_type = utils.getLocationType(location)
},
isSelectedLocation(location) {
return this.locationSelectedDisplayName && this.locationSelectedDisplayName === location.display_name
return (this.addPriceSingleForm.location_osm_id === utils.getLocationID(location)) && (this.addPriceSingleForm.location_osm_type === utils.getLocationType(location))
},
createPrice() {
this.createPriceLoading = true
Expand Down

0 comments on commit 7ebb44c

Please sign in to comment.