Skip to content

Instantly share code, notes, and snippets.

@marinho
Last active January 19, 2018 08:17
Show Gist options
  • Save marinho/8a7e71b53c7da97f9579146742bb54de to your computer and use it in GitHub Desktop.
Save marinho/8a7e71b53c7da97f9579146742bb54de to your computer and use it in GitHub Desktop.
Projection of a plain polygon (with paths) into a geographic area (including rotation)
/** This code was written as a solution for the following Stack Overflow entry:
* https://stackoverflow.com/questions/48307362/draw-a-multi-polygon-with-metric-dimensions-over-part-of-a-sphere-using-d3-geo/48336677
*
* Even though it seems to work fine, this code has at least 2 needs for improvements:
* - some of the types and functions here actually exist in existing libraries and could be used from them instead
* - d3-geo's mercator projection inverted should be able to do the same job more elegantly, in combination with
* other d3-geo features
*/
import * as turf from '@turf/turf'; // https://npmjs.com/package/@turf/turf
import * as LatLon from 'geodesy/latlon-spherical'; // // https://npmjs.com/package/geodesy
// Example polygon - looks like an arrow pointing up
export const plainPolygon = {
rect: {"x":30,"y":40,"width":20,"height":10},
paths: [[[0,10],[0,0],[20,0],[20,10]],[[10,10],[5,0],[10,3],[15,0]]]
};
// Example geographic areas to fit polygons into
export const about20Degrees = {
leftTop: [13.377948, 52.521293],
leftBottom: [13.377962, 52.521250],
rightBottom: [13.378174, 52.521275],
rightTop: [13.378161, 52.521318]
};
// Types
export type Position = number[];
export interface GeoReferences {
leftTop: Position, // lng / x, lat / y
leftBottom: Position,
rightBottom: Position,
rightTop?: Position
}
export interface Rect {
x: number;
y: number;
width: number;
height: number:
}
export interface PlainInfo {
rect: Rect;
paths: number[][];
}
// THIS IS WHAT MATTERS
export function projectPlainOverGeoArea(plain: PlainInfo, geoReferences: GeoReferences): FeatureCollection<MultiPolygon> {
// preparation
const geoRect = getRect(geoReferences);
const vertBearing = haversineBearing(
geoReferences.leftBottom,
geoReferences.leftTop
);
// Convert to geo using left/bottom as pivot
const paths = plain.paths.map(path => pathToCoords(path));
// Pack path into features
const features = paths
.map(p => ({
type: 'MultiPolygon',
coordinates: [[p]]
}))
.map(p => ({
type: 'Feature',
properties: {},
geometry: p
}));
// Rotation
const collection = { type: 'FeatureCollection', features };
const rotationOptions = { pivot: geoReferences.leftBottom };
const rotated = turf.transformRotate(collection, vertBearing, rotationOptions);
return {
type: 'FeatureCollection',
features: rotated.features
};
// Nested functions
function pathToCoords(path) {
return path
.map(point => projectToGeo(point));
}
function projectToGeo(coords: Position): Position {
const horzDistance = coords[0] / plain.rect.width * geoRect.width;
const vertDistance = coords[1] / plain.rect.height * geoRect.height;
const horzDestination = haversineDestination(geoReferences.leftBottom, horzDistance, 90);
const vertDestination = haversineDestination(geoReferences.leftBottom, vertDistance, 0);
return [horzDestination[0], vertDestination[1]];
}
function getRect(geoReferences: GeoReferences): Rect {
const width = haversineDistance(
geoReferences.leftBottom,
geoReferences.rightBottom
);
const height = haversineDistance(
geoReferences.leftBottom,
geoReferences.leftTop
);
return {
x: 0,
y: 0,
width: width,
height: height
};
}
function haversineDistance(point1: number[], point2: number[]): number {
return latLongFromPair(point1).distanceTo(latLongFromPair(point2));
}
function haversineBearing(point1: number[], point2: number[]): number {
return latLongFromPair(point1).bearingTo(latLongFromPair(point2));
}
function haversineDestination(origin: number[], distance: number, bearing: number): Position {
const destination = latLongFromPair(origin).destinationPoint(distance, bearing);
return [
+destination.lon.toFixed(6),
+destination.lat.toFixed(6)
];
}
function latLongFromPair(pair: number[]): any {
return LatLon(pair[1], pair[0]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment