Skip to content

Commit

Permalink
Use Open Elevation instead of MapZen to retrieve point elevation (#250)
Browse files Browse the repository at this point in the history
  • Loading branch information
cdauth committed Mar 16, 2024
1 parent db8d7d6 commit bc4d97d
Show file tree
Hide file tree
Showing 10 changed files with 50 additions and 59 deletions.
4 changes: 0 additions & 4 deletions config.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@
# Get an API key on https://www.mapbox.com/signup/
#MAPBOX_TOKEN=

# MapZen is used for getting elevation information
# Get an API key on https://mapzen.com/developers/sign_up
#MAPZEN_TOKEN=

# Maxmind configuration. If specified, the maxmind GeoLite2 database will be downloaded
# for Geo IP lookup (to show the initial map state) and kept in memory.
# Sign up here: https://www.maxmind.com/en/geolite2/signup
Expand Down
6 changes: 3 additions & 3 deletions docs/src/developers/server/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ The config of the FacilMap server can be set either by using environment variabl
| `DB_PASSWORD` | | `facilmap` | The password to connect to the database with. |
| `ORS_TOKEN` | * | | [OpenRouteService API key](https://openrouteservice.org/). |
| `MAPBOX_TOKEN` | * | | [Mapbox API key](https://www.mapbox.com/signup/). |
| `MAPZEN_TOKEN` | | | [Mapzen API key](https://mapzen.com/developers/sign_up). |
| `MAXMIND_USER_ID` | | | [MaxMind user ID](https://www.maxmind.com/en/geolite2/signup). |
| `MAXMIND_LICENSE_KEY` | | | MaxMind license key. |
| `LIMA_LABS_TOKEN` | | | [Lima Labs](https://maps.lima-labs.com/) API key |
| `HIDE_COMMERCIAL_MAP_LINKS` | | | Set to `1` to hide the links to Google/Bing Maps in the “Map style” menu. |
| `CUSTOM_CSS_FILE` | | | The path of a CSS file that should be included ([see more details below](#custom-css-file)).
| `CUSTOM_CSS_FILE` | | | The path of a CSS file that should be included ([see more details below](#custom-css-file)). |
| `NOMINATIM_URL` | | `https://nominatim.openstreetmap.org` | The URL to the Nominatim server (used to search for places). |
| `OPEN_ELEVATION_URL` | | `https://api.open-elevation.com` | The URL to the Open Elevation server (used to look up the elevation for markers). |

FacilMap makes use of several third-party services that require you to register (for free) and generate an API key:
* Mapbox and OpenRouteService are used for calculating routes. Mapbox is used for basic routes, OpenRouteService is used when custom route mode settings are made. If these API keys are not defined, calculating routes will fail.
* Maxmind provides a free database that maps IP addresses to approximate locations. FacilMap downloads this database to decide the initial map view for users (IP addresses are looked up in FacilMap’s copy of the database, on IP addresses are sent to Maxmind). This API key is optional, if it is not set, the default view will be the whole world.
* Mapzen is used to look up the elevation info for search results. The API key is optional, if it is not set, no elevation info will be available for search results.
* Lima Labs is used for nicer and higher resolution map tiles than Mapnik. The API key is optional, if it is not set, Mapnik will be the default map style instead.

## Custom CSS file
Expand Down
4 changes: 1 addition & 3 deletions docs/src/developers/server/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ services:
DB_PASSWORD: password
ORS_TOKEN: # Get an API key on https://go.openrouteservice.org/ (needed for routing)
MAPBOX_TOKEN: # Get an API key on https://www.mapbox.com/signup/ (needed for routing)
MAPZEN_TOKEN: # Get an API key on https://mapzen.com/developers/sign_up (needed for elevation info)
MAXMIND_USER_ID: # Sign up here https://www.maxmind.com/en/geolite2/signup (needed for geoip lookup to show initial map state)
MAXMIND_LICENSE_KEY:
LIMA_LABS_TOKEN: # Get an API key on https://maps.lima-labs.com/ (optional, needed for double-resolution tiles)
Expand Down Expand Up @@ -76,7 +75,6 @@ services:
DB_PASSWORD: password
ORS_TOKEN: # Get an API key on https://go.openrouteservice.org/ (needed for routing)
MAPBOX_TOKEN: # Get an API key on https://www.mapbox.com/signup/ (needed for routing)
MAPZEN_TOKEN: # Get an API key on https://mapzen.com/developers/sign_up (needed for elevation info)
MAXMIND_USER_ID: # Sign up here https://www.maxmind.com/en/geolite2/signup (needed for geoip lookup to show initial map state)
MAXMIND_LICENSE_KEY:
LIMA_LABS_TOKEN: # Get an API key on https://maps.lima-labs.com/ (optional, needed for double-resolution tiles)
Expand All @@ -100,5 +98,5 @@ To manually create the necessary docker containers, use these commands:

```bash
docker create --name=facilmap_db -e MYSQL_DATABASE=facilmap -e MYSQL_USER=facilmap -e MYSQL_PASSWORD=password -e MYSQL_RANDOM_ROOT_PASSWORD=true --restart=unless-stopped mariadb --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
docker create --link=facilmap_db -p 8080 --name=facilmap -e "USER_AGENT=My FacilMap (https://facilmap.example.org/, [email protected])" -e TRUST_PROXY=true -e DB_TYPE=mysql -e DB_HOST=facilmap_db -e DB_NAME=facilmap -e DB_USER=facilmap -e DB_PASSWORD=facilmap -e ORS_TOKEN= -e MAPBOX_TOKEN= -e MAPZEN_TOKEN= -e MAXMIND_USER_ID= -e MAXMIND_LICENSE_KEY= -e LIMA_LABS_TOKEN= --restart=unless-stopped facilmap/facilmap
docker create --link=facilmap_db -p 8080 --name=facilmap -e "USER_AGENT=My FacilMap (https://facilmap.example.org/, [email protected])" -e TRUST_PROXY=true -e DB_TYPE=mysql -e DB_HOST=facilmap_db -e DB_NAME=facilmap -e DB_USER=facilmap -e DB_PASSWORD=facilmap -e ORS_TOKEN= -e MAPBOX_TOKEN= -e MAXMIND_USER_ID= -e MAXMIND_LICENSE_KEY= -e LIMA_LABS_TOKEN= --restart=unless-stopped facilmap/facilmap
```
2 changes: 1 addition & 1 deletion frontend/src/lib/components/marker-info/marker-info.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@

<template v-if="marker.ele != null">
<dt class="elevation">Elevation</dt>
<dd class="elevation">{{marker.ele}}^m</dd>
<dd class="elevation">{{marker.ele}}&#x202F;m</dd>
</template>

<template v-for="field in client.types[marker.typeId].fields" :key="field.name">
Expand Down
7 changes: 5 additions & 2 deletions server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ export interface Config {
db: DbConfig;
orsToken?: string;
mapboxToken?: string;
mapzenToken?: string;
maxmindUserId?: string;
maxmindLicenseKey?: string;
limaLabsToken?: string;
/** Hide the "Open this on Google/Bing Maps" links in the map style menu */
hideCommercialMapLinks?: boolean;
customCssFile?: string;
nominatimUrl: string;
openElevationApiUrl: string;
}

const config: Config = {
Expand All @@ -51,7 +52,6 @@ const config: Config = {
},
orsToken: process.env.ORS_TOKEN || "", // Get a token on https://go.openrouteservice.org/
mapboxToken: process.env.MAPBOX_TOKEN || "", // Get an API key on https://www.mapbox.com/signup/
mapzenToken: process.env.MAPZEN_TOKEN || "", // Get an API key on https://mapzen.com/developers/sign_up

// Maxmind configuration. If specified, the maxmind GeoLite2 database will be downloaded for Geo IP lookup (to show the initial map state) and kept it in memory
// Sign up here: https://www.maxmind.com/en/geolite2/signup
Expand All @@ -63,6 +63,9 @@ const config: Config = {
hideCommercialMapLinks: process.env.HIDE_COMMERCIAL_MAP_LINKS === "1",

customCssFile: process.env.CUSTOM_CSS_FILE || undefined,

nominatimUrl: process.env.NOMINATIM_URL || "https://nominatim.openstreetmap.org",
openElevationApiUrl: process.env.OPEN_ELEVATION_URL || "https://api.open-elevation.com",
};

export default config;
1 change: 0 additions & 1 deletion server/src/database/line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,6 @@ export default class DatabaseLines {
}

async _setLinePoints(padId: PadId, lineId: ID, trackPoints: Point[], _noEvent?: boolean): Promise<void> {
// First get elevation, so that if that fails, we don't update anything
await this.LinePointModel.destroy({ where: { lineId: lineId } });

const create = [ ];
Expand Down
2 changes: 1 addition & 1 deletion server/src/database/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface MetaModel extends Model<InferAttributes<MetaModel>, InferCreationAttri

export interface MetaProperties {
dropdownKeysMigrated: "1";
hasElevation: "1";
hasElevation: "1" | "2";
hasLegendOption: "1";
hasBboxes: "1";
untitledMigrationCompleted: "1";
Expand Down
70 changes: 33 additions & 37 deletions server/src/elevation.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,44 @@
import type { Point } from "facilmap-types";
import config from "./config";

// const API_URL = "https://elevation.mapzen.com/height";
// const LIMIT = 500;
// const MIN_TIME_BETWEEN_REQUESTS = 600;

// const throttle = highland<() => void>();
// throttle.ratelimit(1, MIN_TIME_BETWEEN_REQUESTS).each((func) => {
// func();
// });

export async function _getThrottledSlot(): Promise<void> {
// return new Promise<void>((resolve) => {
// throttle.write(resolve);
// });
}

export async function getElevationForPoint(point: Point): Promise<number | undefined> {
const points = await getElevationForPoints([point]);
export async function getElevationForPoint(point: Point, failOnError = false): Promise<number | undefined> {
const points = await getElevationForPoints([point], failOnError);
return points[0];
}

export async function getElevationForPoints(points: Array<{ lat: string | number; lon: string | number }>): Promise<Array<number | undefined>> {
return points.map(() => undefined);

/*if(points.length == 0)
return Promise.resolve([ ]);
let ret = Promise.resolve([ ]);
for(let i=0; i<points.length; i+=LIMIT) {
ret = ret.then((heights) => {
return elevation._getThrottledSlot().then(() => (heights));
}).then((heights) => {
let json = {
encoded_polyline: polyline.encode(points.slice(i, i+LIMIT).map((point) => ([point.lat, point.lon])), 6),
range: false
};
export async function getElevationForPoints(points: Point[], failOnError = false): Promise<Array<number | undefined>> {
if(points.length == 0) {
return [];
}

return request.get({
url: `${API_URL}?json=${encodeURI(JSON.stringify(json))}&api_key=${config.mapzenToken}`,
json: true
}).then((res) => (heights.concat(res.height)));
try {
const res = await fetch(`${config.openElevationApiUrl}/api/v1/lookup`, {
method: "post",
headers: {
"Content-type": "application/json"
},
body: JSON.stringify({
locations: points.map((point) => ({ latitude: point.lat, longitude: point.lon }))
})
});
if (!res.ok) {
throw new Error(`Looking up elevations failed with status ${res.status}.`);
}
const json: { results: Array<{ latitude: number; longitude: number; elevation: number }> } = await res.json();

return json.results.map((result: any) => {
if (result.elevation !== 0) {
return result.elevation;
}
});
} catch (err: any) {
if (failOnError) {
throw err;
} else {
console.warn("Error lookup up elevation", err);
return points.map(() => undefined);
}
}
return ret;*/
}

interface AscentDescent {
Expand Down
11 changes: 5 additions & 6 deletions server/src/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ interface NominatimError {
error: { code?: number; message: string } | string;
}

const nameFinderUrl = "https://nominatim.openstreetmap.org";
const limit = 25;
const stateAbbr: Record<string, Record<string, string>> = {
"us" : {
Expand Down Expand Up @@ -115,7 +114,7 @@ export async function find(query: string, loadUrls = false, loadElevation = fals

async function _findQuery(query: string, loadElevation = false): Promise<Array<SearchResult>> {
const body: Array<NominatimResult> | NominatimError = await throttledFetch(
nameFinderUrl + "/search?format=jsonv2&polygon_geojson=1&addressdetails=1&namedetails=1&limit=" + encodeURIComponent(limit) + "&extratags=1&q=" + encodeURIComponent(query),
config.nominatimUrl + "/search?format=jsonv2&polygon_geojson=1&addressdetails=1&namedetails=1&limit=" + encodeURIComponent(limit) + "&extratags=1&q=" + encodeURIComponent(query),
{
headers: {
"User-Agent": config.userAgent
Expand All @@ -131,7 +130,7 @@ async function _findQuery(query: string, loadElevation = false): Promise<Array<S

const points = body.filter((res) => (res.lon && res.lat));
if(loadElevation && points.length > 0) {
const elevations = await getElevationForPoints(points);
const elevations = await getElevationForPoints(points.map((point) => ({ lat: Number(point.lat), lon: Number(point.lon) })));
elevations.forEach((elevation, i) => {
points[i].elevation = elevation;
});
Expand All @@ -142,7 +141,7 @@ async function _findQuery(query: string, loadElevation = false): Promise<Array<S

async function _findOsmObject(type: string, id: string, loadElevation = false): Promise<Array<SearchResult>> {
const body: Array<NominatimResult> | NominatimError = await throttledFetch(
`${nameFinderUrl}/lookup?format=jsonv2&addressdetails=1&polygon_geojson=1&extratags=1&namedetails=1&osm_ids=${encodeURI(type.toUpperCase())}${encodeURI(id)}`,
`${config.nominatimUrl}/lookup?format=jsonv2&addressdetails=1&polygon_geojson=1&extratags=1&namedetails=1&osm_ids=${encodeURI(type.toUpperCase())}${encodeURI(id)}`,
{
headers: {
"User-Agent": config.userAgent
Expand All @@ -158,7 +157,7 @@ async function _findOsmObject(type: string, id: string, loadElevation = false):

const points = body.filter((res) => (res.lon && res.lat));
if(loadElevation && points.length > 0) {
const elevations = await getElevationForPoints(points);
const elevations = await getElevationForPoints(points.map((point) => ({ lat: Number(point.lat), lon: Number(point.lon) })));
elevations.forEach((elevation, i) => {
points[i].elevation = elevation;
});
Expand All @@ -170,7 +169,7 @@ async function _findOsmObject(type: string, id: string, loadElevation = false):
async function _findLonLat(lonlatWithZoom: PointWithZoom, loadElevation = false): Promise<Array<SearchResult>> {
const [body, elevation] = await Promise.all([
throttledFetch(
`${nameFinderUrl}/reverse?format=jsonv2&addressdetails=1&polygon_geojson=0&extratags=1&namedetails=1&lat=${encodeURIComponent(lonlatWithZoom.lat)}&lon=${encodeURIComponent(lonlatWithZoom.lon)}&zoom=${encodeURIComponent(lonlatWithZoom.zoom != null ? (lonlatWithZoom.zoom >= 12 ? lonlatWithZoom.zoom+2 : lonlatWithZoom.zoom) : 17)}`,
`${config.nominatimUrl}/reverse?format=jsonv2&addressdetails=1&polygon_geojson=0&extratags=1&namedetails=1&lat=${encodeURIComponent(lonlatWithZoom.lat)}&lon=${encodeURIComponent(lonlatWithZoom.lon)}&zoom=${encodeURIComponent(lonlatWithZoom.zoom != null ? (lonlatWithZoom.zoom >= 12 ? lonlatWithZoom.zoom+2 : lonlatWithZoom.zoom) : 17)}`,
{
headers: {
"User-Agent": config.userAgent
Expand Down
2 changes: 1 addition & 1 deletion types/src/marker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ export const markerValidator = cruValidator({
colour: optionalCreate(colourValidator), // defaults to type.defaultColour
size: optionalCreate(sizeValidator), // defaults to type.defaultSize
data: optionalCreate(z.record(z.string())),
ele: optionalCreate(z.number().or(z.null()), null)
ele: optionalCreate(z.number().or(z.null()))
});
export type Marker<Mode extends CRU = CRU.READ> = CRUType<Mode, typeof markerValidator>;

0 comments on commit bc4d97d

Please sign in to comment.