@panoramax/web-viewer
Version:
Panoramax web viewer for geolocated pictures
207 lines (189 loc) • 6.05 kB
JavaScript
// DO NOT REMOVE THE "!": bundled builds breaks otherwise !!!
import maplibregl from "!maplibre-gl";
import { NominatimBaseUrl, AdresseDataGouvBaseURL } from "./services";
const PLACETYPE_ZOOM = {
"house": 20,
"housenumber": 20,
"street": 18,
"locality": 15,
"district": 13,
"municipality": 12,
"city": 12,
"county": 8,
"region": 7,
"state": 7,
"country": 5
};
/**
* Transforms a set of parameters into an URL-ready string
* It also removes null/undefined values
*
* @param {object} params The parameters object
* @return {string} The URL query part
* @private
*/
function geocoderParamsToURLString(params) {
let p = {};
Object.entries(params)
.filter(e => e[1] !== undefined && e[1] !== null)
.forEach(e => p[e[0]] = e[1]);
return new URLSearchParams(p).toString();
}
/**
* Nominatim (OSM) geocoder, ready to use for our Map
* @private
*/
export function forwardGeocodingNominatim(config) {
// Transform parameters into Nominatim format
const params = {
q: config.query,
countrycodes: config.countries,
limit: config.limit,
viewbox: config.bbox,
};
return fetch(`${NominatimBaseUrl()}/search?${geocoderParamsToURLString(params)}&format=geocodejson&addressdetails=1`)
.then(res => res.json())
.then(res => {
const finalRes = { features: [] };
const listedNames = [];
(res.features || []).forEach(f => {
const plname = geocodeJsonToPlaceName(f.properties?.geocoding) || f.properties?.geocoding?.label;
if(!listedNames.includes(plname)) {
finalRes.features.push({
place_type: ["place"],
place_name: plname,
center: new maplibregl.LngLat(...f.geometry.coordinates),
zoom: PLACETYPE_ZOOM[f.properties?.geocoding?.type],
});
listedNames.push(plname);
}
});
return finalRes;
});
}
export function reverseGeocodingNominatim(lat, lon) {
return fetch(`${NominatimBaseUrl()}/reverse?lat=${lat}&lon=${lon}&zoom=18&format=geocodejson`)
.then(res => res.json())
.then(res => geocodeJsonToPlaceName(res?.features?.shift()?.properties?.geocoding));
}
/**
* Base adresse nationale (FR) geocoder, ready to use for our Map
* @param {object} config Configuration sent by MapLibre GL Geocoder, following the geocoderApi format ( https://maplibre.org/maplibre-gl-geocoder/types/MaplibreGeocoderApiConfig.html )
* @returns {object} GeoJSON Feature collection in Carmen GeoJSON format ( https://maplibre.org/maplibre-gl-geocoder/types/CarmenGeojsonFeature.html )
* @private
*/
export function forwardGeocodingBAN(config) {
return forwardGeocodingStandard(config, AdresseDataGouvBaseURL());
}
/**
* Transforms GeocodeJSON search result into a nice-to-display address.
* @param {object} props The GecodeJSON API feature properties
* @returns {string} The clean-up string for display
* @private
*/
function geocodeJsonToPlaceName(props) {
// API format @ https://github.com/geocoders/geocodejson-spec/blob/master/draft/README.md
if(!props || typeof props != "object") { return ""; }
// P1 = main name, P2=locality-like, P3=country+high-level admin
let p1 = props.name;
let p2 = [], p3 = [];
switch(props.type) {
case "hamlet":
case "croft":
case "isolated_dwelling":
case "neighbourhood":
case "allotments":
case "quarter":
case "farm":
case "farmyard":
case "industrial":
case "commercial":
case "retail":
case "city_block":
case "residential":
case "locality":
case "district":
p3.push(props.city);
p3.push(props.county);
p3.push(props.state);
p3.push(props.country);
break;
case "city":
p3.push(props.county);
p3.push(props.state);
p3.push(props.country);
break;
case "region":
p3.push(props.county);
p3.push(props.state);
p3.push(props.country);
break;
case "country":
break;
case "house":
case "housenumber":
p2.push(props.housenumber);
p2.push(props.street);
p2.push(props.locality);
p2.push(props.district);
p3.push(props.city);
p3.push(props.county);
p3.push(props.state);
p3.push(props.country);
break;
case "street":
case "road":
default:
p2.push(props.street);
p2.push(props.locality);
p2.push(props.district);
p3.push(props.city);
p3.push(props.county);
p3.push(props.state);
p3.push(props.country);
break;
}
p2 = p2.filter(v => v);
p2 = p2.filter((v,i) => v != p1 && (i === 0 || p2[i-1] !== v));
p2 = p2.length > 0 ? (props.housenumber ? p2.slice(0,2).join(" ") : p2.shift()) : null;
if(p2 === p1) { p2 = null; }
p3 = p3.filter(v => v);
p3 = p3.filter((v,i) => v != p1 && (!p2 || !p2.includes(v)) && (i === 0 || p3[i-1] !== v));
let res = [p1, p2, p3.shift()].filter(v => v);
return res.join(", ");
}
/**
* Standard forward geocoder
* @param {object} config Configuration sent by MapLibre GL Geocoder, following the geocoderApi format ( https://maplibre.org/maplibre-gl-geocoder/types/MaplibreGeocoderApiConfig.html )
* @param {string} endpoint The URL endpoint (everything before the /?q=...)
* @returns {object} GeoJSON Feature collection in Carmen GeoJSON format ( https://maplibre.org/maplibre-gl-geocoder/types/CarmenGeojsonFeature.html )
* @private
*/
export function forwardGeocodingStandard(config, endpoint) {
// Transform parameters into BAN format
const params = { q: config.query, limit: config.limit };
if(typeof config.proximity === "string") {
const [lat, lon] = config.proximity.split(",").map(v => parseFloat(v.trim()));
params.lat = lat;
params.lon = lon;
}
return fetch(`${endpoint}/?${geocoderParamsToURLString(params)}`)
.then(res => res.json())
.then(res => {
const finalRes = { features: [] };
const listedNames = [];
(res.features || []).forEach(f => {
const plname = geocodeJsonToPlaceName(f.properties);
if(!listedNames.includes(plname) && f.properties.type != "other") {
finalRes.features.push({
place_type: ["place"],
place_name: plname,
center: new maplibregl.LngLat(...f.geometry.coordinates),
zoom: PLACETYPE_ZOOM[f.properties.type],
});
listedNames.push(plname);
}
});
return finalRes;
});
}