Skip to content

Commit

Permalink
DOC now with elevation and math
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanlurie committed Dec 6, 2023
1 parent f32a455 commit b2b5134
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 149 deletions.
20 changes: 10 additions & 10 deletions examples/elevation.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,19 @@


// a long multilinestrin
// const resGeojson = await fetch("long.geojson");
// const geojson = await resGeojson.json();
// console.log(geojson);
const resGeojson = await fetch("long.geojson");
const geojson = await resGeojson.json();
console.log(geojson);

// const allMLS = maptilerClient.extractLineStrings(geojson);
const allMLS = maptilerClient.tools.extractLineStrings(geojson);

// const mls = allMLS[0]
// console.log(mls)
const mls = allMLS[0]
console.log(mls)

// console.time("1")
// const elevatedMls = await maptilerClient.elevation.fromMultiLineString(mls);
// console.timeEnd("1")
// console.log(elevatedMls)
console.time("1")
const elevatedMls = await maptilerClient.elevation.fromMultiLineString(mls);
console.timeEnd("1")
console.log(elevatedMls)



Expand Down
132 changes: 132 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The **MapTiler Client JS** exposes a number of handy functions that wrap API cal
- [Coordidinate systems search and tranform](#-coordinates)
- [User data fetching as GeoJSON](#-data)
- [Static maps of all sorts](#%EF%B8%8F-static-maps)
- [Elevation lookup with batch features](#-elevation)

## Why?

Expand Down Expand Up @@ -58,6 +59,8 @@ import {
coordinates,
data,
staticMaps,
elevation,
math,
} from '@maptiler/client';
```

Expand Down Expand Up @@ -327,6 +330,135 @@ And voila!
Read more about bounded static maps on our official [API documentation](https://docs.maptiler.com/cloud/api/static-maps/#auto-fitted-image).

## 🏔️ Elevation
With the elevation API, it's possible to get the elevation in metter from any location. It's possible lookup and compute elevation for a single location, to provide a batch of points, from a GeoJSON LineString or from a GeoJSON MultiLineString!

> ℹ️ Under the hood, the elevation API is fueled by MapTiler Cloud's **RGB Terrain** raster tileset, which is a composite of many high-resolution DEMs from all over the world, currated and processed by our geodata team! The same dataset is also fueling our SDK's elevation (3D terrain) and the hillshading we use in many of our styles.
> 📣 Note for **TypeScript** users: internaly, the elevation feature relies on some *GeoJSON* types definitions that can be found in this NPM package: `@types/geojson`. Namely `LineString`, `MultiLineString` and `Position`. It may improve your developer experience to also use these types.
Let's see how to use it:

### At a single location
```ts
// Not mandatory, but it's to explain where the type comes from:
import { Position } from "geojson";

const montBlancPeak: Position = [6.864884, 45.832743];
const elevatedPosition = await maptilerClient.elevation.at(montBlancPeak);
```
The returned value is also a *GeoJSON* `Position` array, but with three elements: `[lng, lat, elevation]`.

Read more about elevation lookup for a single location in our [official documentation](https://docs.maptiler.com/client-js/elevation/#at).

### Batch mode
```ts
// Not mandatory, but it's to explain where the type comes from:
import { Position } from "geojson";

const peaks: Position[] = [
[6.864884, 45.832743], // Mont Blanc, Alps
[86.9250, 27.9881], // Mount Everest, Himalayas
[-70.0109, -32.6532], // Aconcagua, Andes
[-151.0064, 63.0695], // Denali, Alaska
[37.3556, -3.0674], // Mount Kilimanjaro
[42.4453, 43.3499], // Mount Elbrus, Caucasus
[137.1595, -4.0784], // Puncak Jaya, Sudirman Range
[-140.4055, 60.5672], // Mount Logan, Saint Elias Mountains
[138.73111, 35.358055], // Mount Fuji
];

const elevatedPeaks = await maptilerClient.elevation.batch(peaks);
```

Read more about elevation lookup for a batch of locations in our [official documentation](https://docs.maptiler.com/client-js/elevation/#batch).

### From a GeoJSON LineString
In the *GeoJSON* LineString case, it clones the entire structure and the positions arrays of the clone will contain three element: `[lng, lat, elevation]`. The original LineString is not mutated nor pointed at.

```ts
// Not mandatory, but it's to explain where the type comes from:
import { LineString } from "geojson";


const someLineString: LineString = {
type: "LineString",
coordinates: [[6.864884, 45.832743], [86.9250, 27.9881], [-70.0109, -32.6532]]
};

const someElevatedLineString = await maptilerClient.elevation.fromLineString(someLineString);
// someElevatedLineString is also of type LineString
```

Read more about elevation lookup for a `LineString` in our [official documentation](https://docs.maptiler.com/client-js/elevation/#linestring).

### From a GeoJSON MultiLineString
In the *GeoJSON* MultiLineString case, it clones the entire structure and the positions arrays of the clone will contain three element: `[lng, lat, elevation]`. The original MultiLineString is not mutated nor pointed at.

```ts
// Not mandatory, but it's to explain where the type comes from:
import { MultiLineString } from "geojson";


const someMultiLineString: MultiLineString = {
type: "LineString",
coordinates: [
[[6.864884, 45.832743], [86.9250, 27.9881], [-70.0109, -32.6532]],
[[-151.0064, 63.0695], [37.3556, -3.0674], [42.4453, 43.3499]],
[[137.1595, -4.0784], [-140.4055, 60.5672], [138.73111, 35.358055]],
]
};

const someElevatedMultiLineString = await maptilerClient.elevation.fromMultiLineString(someMultiLineString);
// someElevatedMultiLineString is also of type MultiLineString
```

Read more about elevation lookup for a `MultiLineString` in our [official documentation](https://docs.maptiler.com/client-js/elevation/#multilinestring).

### Caching
In order to increase performance while reducing unnecessary elevation data fetching, the elevation tiles are cached. This is particularly important for the LineString and MultiLineString lookups because GeoJSON data are likely to come from a recorded or planned route, where position points are very close to one another.

## 🧮 Math
Some operations can be fairly repetitive: WGS84 to Mercator, WGS84 to *zxy* tile index, distance between two points with Haversine formula, etc. As a result, we have decided to expose a `math` package providing the most recurent feature, so that, just like us at MapTiler, you no longer need to copy-paste the same function from your previous project!

The `math` package differs from the others in the sense that it does not call the MapTiler Cloud API, instead it operates fully on the machine it's running on.

Here are some examples:

```ts
// Not mandatory, but it's to explain where the type comes from:
import { Position } from "geojson";

// Some constants
const earthRadius = maptilerClient.math.EARTH_RADIUS;
const earthCircumference = maptilerClient.math.EARTH_CIRCUMFERENCE;

const montBlancPeakWgs84: Position = [6.864884, 45.832743];

// From WGS84 to Mercator
const montBlancPeakMerc = maptilerClient.math.wgs84ToMercator(montBlancPeakWgs84); // also of type Position

// From Mercator to WGS84
const montBlancPeakWgs84Again = maptilerClient.math.mercatorToWgs84(montBlancPeakMerc);

// A great-circle distance in meter:
const from: Position = /* ... */;
const to: Position = /* ... */;
const distance = maptilerClient.math.haversineDistanceWgs84(from, to);

// Full distance of a route made of many positions
const route: Position[] = /* ... */;
const totalDistance = maptilerClient.math.haversineCumulatedDistanceWgs84(route);

// Lon lat to tile index, given a zoom level. An [x, y] array is returned
const tileXY = maptilerClient.math.wgs84ToTileIndex(montBlancPeakWgs84, 14);
// Possible to have floating point tile indices with a third argument to `false`

// and many more!
```

Please find out more about the math package in our [official documentation](https://docs.maptiler.com/client-js/math):

# From NodeJS
NodeJS includes a stable `fetch()` function only from its version *18*, and this client does not contain a polyfill. If the `fetch()` function exists (browser or Node >= 18) then it is going to be resolved automatically, Yet, a custom `fetch()` function can be provided to the `config` object for Node < 18.

Expand Down
47 changes: 0 additions & 47 deletions src/geojsontools.ts

This file was deleted.

6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BBox, Position } from "geojson";
export type { BBox, Position };
import { BBox, Position, LineString, MultiLineString } from "geojson";
export type { BBox, Position, LineString, MultiLineString };
export * from "./config";
export * from "./language";
export * from "./services/geocoding";
Expand All @@ -12,4 +12,4 @@ export * from "./mapstyle";
export * from "./services/math";
export * from "./services/elevation";
export * from "./tiledecoding";
export * from "./geojsontools";
export * from "./misc";
138 changes: 138 additions & 0 deletions src/misc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import {
GeoJsonObject,
GeometryObject,
LineString,
MultiLineString,
Feature,
FeatureCollection,
Position,
} from "geojson";

/**
* From a generic GeoJSON object extract thepossibly nested LineString and MultiLineString features
* it contains. The result is a flat array made of LineString and MultiLineString.
*/
function extractLineStrings(
geoJson: GeoJsonObject,
): Array<LineString | MultiLineString> {
const lineStrings: Array<LineString | MultiLineString> = [];

function extractFromGeometry(geometry: GeometryObject) {
if (geometry.type === "LineString" || geometry.type === "MultiLineString") {
lineStrings.push(geometry as LineString | MultiLineString);
}
}

function extractFromFeature(feature: Feature) {
if (feature.geometry) {
extractFromGeometry(feature.geometry);
}
}

function extractFromFeatureCollection(collection: FeatureCollection) {
for (const feature of collection.features) {
if (feature.type === "Feature") {
extractFromFeature(feature);
} else if (feature.type === "FeatureCollection") {
extractFromFeatureCollection(feature as unknown as FeatureCollection); // had to add unknown
}
}
}

if (geoJson.type === "Feature") {
extractFromFeature(geoJson as Feature);
} else if (geoJson.type === "FeatureCollection") {
extractFromFeatureCollection(geoJson as FeatureCollection);
} else {
// It's a single geometry
extractFromGeometry(geoJson as GeometryObject);
}

return lineStrings;
}

// square distance from a point to a segment
function getSqSegDist(p: Position, p1: Position, p2: Position): number {
let x = p1[0],
y = p1[1],
dx = p2[0] - x,
dy = p2[1] - y;

if (dx !== 0 || dy !== 0) {
const t = ((p[0] - x) * dx + (p[1] - y) * dy) / (dx * dx + dy * dy);

if (t > 1) {
x = p2[0];
y = p2[1];
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}

dx = p[0] - x;
dy = p[1] - y;

return dx * dx + dy * dy;
}

function simplifyDPStep(
points: Array<Position>,
first: number,
last: number,
sqTolerance: number,
simplified: Array<Position>,
) {
let maxSqDist = sqTolerance,
index;

for (let i = first + 1; i < last; i++) {
const sqDist = getSqSegDist(points[i], points[first], points[last]);

if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}

if (maxSqDist > sqTolerance) {
if (index - first > 1) {
simplifyDPStep(points, first, index, sqTolerance, simplified);
}
simplified.push(points[index]);

if (last - index > 1) {
simplifyDPStep(points, index, last, sqTolerance, simplified);
}
}
}

// simplification using Ramer-Douglas-Peucker algorithm
function simplifyDouglasPeucker(
points: Array<Position>,
sqTolerance: number,
): Array<Position> {
const last = points.length - 1;
const simplified = [points[0]];
simplifyDPStep(points, 0, last, sqTolerance, simplified);
simplified.push(points[last]);
return simplified;
}

/**
* Simplify a path made of a list of GeoJSON Positions, with a tolerance.
*/
function simplify(points: Array<Position>, tolerance: number): Array<Position> {
if (points.length <= 2) {
return points;
}

const sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
const simplePoints = simplifyDouglasPeucker(points, sqTolerance);
return simplePoints;
}

export const misc = {
extractLineStrings,
simplify,
};
Loading

0 comments on commit b2b5134

Please sign in to comment.