Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 67 additions & 15 deletions packages/turf-projection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,35 @@ import { Position } from "geojson";
import { coordEach } from "@turf/meta";
import { AllGeoJSON, isNumber } from "@turf/helpers";
import { clone } from "@turf/clone";
import proj4 from "proj4";

/**
* Converts a WGS84 GeoJSON object into Mercator (EPSG:900913) projection
* Converts from any Proj4 projection to any Proj4 projection.
* * @function
* @param {GeoJSON|Position} geojson GeoJSON object with coordinates in inProjection
* @param {string} inProjection defines the proj4 projection of the input coordinates, e.g. "WGS84"
* @param {string} outProjection defines the proj4 projection of the output coordinates, e.g. "EPSG:900913"
* @param {Object} [options] Optional parameters
* @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated (significant performance increase if true)
* @returns {GeoJSON} Projected GeoJSON
* @example
* var pt = turf.point([-71,41]);
* var converted = turf.toProj4(pt, "EPSG:3857");
*
* //addToMap
* var addToMap = [pt, converted];
*/
function proj4ToProj4<G = AllGeoJSON | Position>(
geojson: G,
inProjection: string,
outProjection: string,
options: { mutate?: boolean } = {}
): G {
return convert(geojson, inProjection, outProjection, options);
}

/**
* Converts a GeoJSON object into Mercator (EPSG:900913) projection
*
* @function
* @param {GeoJSON|Position} geojson WGS84 GeoJSON object
Expand All @@ -22,7 +48,7 @@ function toMercator<G = AllGeoJSON | Position>(
geojson: G,
options: { mutate?: boolean } = {}
): G {
return convert(geojson, "mercator", options);
return convert(geojson, "wgs84", "mercator", options);
}

/**
Expand All @@ -44,22 +70,24 @@ function toWgs84<G = AllGeoJSON | Position>(
geojson: G,
options: { mutate?: boolean } = {}
): G {
return convert(geojson, "wgs84", options);
return convert(geojson, "mercator", "wgs84", options);
}

/**
* Converts a GeoJSON coordinates to the defined `projection`
*
* @private
* @param {GeoJSON} geojson GeoJSON Feature or Geometry
* @param {string} projection defines the projection system to convert the coordinates to
* @param {string} inProjection defines the projection of the input coordinates, e.g. "WGS84"
* @param {string} outProjection defines the projection of the output coordinates, e.g. "EPSG:3857"
* @param {Object} [options] Optional parameters
* @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated (significant performance increase if true)
* @returns {GeoJSON} Converted GeoJSON
*/
function convert(
geojson: any,
projection: string,
inProjection: string,
outProjection: string,
options: { mutate?: boolean } = {}
): any {
// Optional parameters
Expand All @@ -68,23 +96,34 @@ function convert(

// Validation
if (!geojson) throw new Error("geojson is required");
if (inProjection == undefined) throw new Error("inProjection is required");
if (outProjection == undefined) throw new Error("outProjection is required");

// Handle Position
if (Array.isArray(geojson) && isNumber(geojson[0]))
geojson =
projection === "mercator"
? convertToMercator(geojson)
: convertToWgs84(geojson);
const isPosition = Array.isArray(geojson) && isNumber(geojson[0]);
if (isPosition) {
if (inProjection === "wgs84" && outProjection === "mercator") {
geojson = convertToMercator(geojson);
} else if (inProjection === "mercator" && outProjection === "wgs84") {
geojson = convertToWgs84(geojson);
} else {
geojson = convertToProj4(geojson, inProjection, outProjection);
}
}
// Handle GeoJSON
else {
// Handle possible data mutation
if (mutate !== true) geojson = clone(geojson);

coordEach(geojson, function (coord) {
var newCoord =
projection === "mercator"
? convertToMercator(coord)
: convertToWgs84(coord);
let newCoord = [];
if (inProjection === "wgs84" && outProjection === "mercator") {
newCoord = convertToMercator(coord);
} else if (inProjection === "mercator" && outProjection === "wgs84") {
newCoord = convertToWgs84(coord);
} else {
newCoord = convertToProj4(coord, inProjection, outProjection);
}
coord[0] = newCoord[0];
coord[1] = newCoord[1];
});
Expand Down Expand Up @@ -143,6 +182,19 @@ function convertToWgs84(xy: number[]) {
];
}

/**
* Convert lon/lat values to any Proj4 projection.
*
* @private
* @param {Array<number>} lonLat WGS84 point
* @param {string} inProjection defines the proj4 projection of the input coordinates, e.g. "WGS84"
* @param {string} outProjection defines the proj4 projection to convert the coordinates to, e.g. "EPSG:3857"
* @returns {Array<number>} Projected [x, y] point
*/
function convertToProj4(lonLat: number[], inProjection: string, outProjection: string) {
return proj4(inProjection, outProjection, lonLat);
}

/**
* Returns the sign of the input, or zero
*
Expand All @@ -154,4 +206,4 @@ function sign(x: number) {
return x < 0 ? -1 : x > 0 ? 1 : 0;
}

export { toMercator, toWgs84 };
export { toMercator, toWgs84, proj4ToProj4 };
5 changes: 3 additions & 2 deletions packages/turf-projection/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"description": "Provides tools for conversion between geographic coordinates and projected coordinates.",
"author": "Turf Authors",
"contributors": [
"Stefano Borghi <@stebogit>"
"Stefano Borghi <@stebogit>",
"Benjamin W. Portner"
],
"license": "MIT",
"bugs": {
Expand Down Expand Up @@ -68,7 +69,6 @@
"@types/tape": "^5.8.1",
"benchmark": "^2.1.4",
"load-json-file": "^7.0.1",
"proj4": "^2.9.2",
"tape": "^5.9.0",
"tsup": "^8.4.0",
"tsx": "^4.19.4",
Expand All @@ -80,6 +80,7 @@
"@turf/helpers": "workspace:*",
"@turf/meta": "workspace:*",
"@types/geojson": "^7946.0.10",
"proj4": "^2.20.4",
"tslib": "^2.8.1"
}
}
57 changes: 53 additions & 4 deletions packages/turf-projection/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { clone } from "@turf/clone";
import { point } from "@turf/helpers";
import { truncate } from "@turf/truncate";
import { coordEach } from "@turf/meta";
import { toMercator, toWgs84 } from "./index.js";
import { toMercator, toWgs84, proj4ToProj4 } from "./index.js";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

Expand Down Expand Up @@ -77,6 +77,24 @@ test("to-wgs84", (t) => {
t.end();
});

test("proj4-to-proj4", (t) => {
// test wgs84 to mercator
// compare against saved toMercator results
for (const { filename, name, geojson } of fromWgs84) {
const expected = loadJsonFileSync(directories.out + "mercator-" + filename);
const results = truncate(proj4ToProj4(geojson, "WGS84", "EPSG:900913"));
t.deepEqual(results, expected);
}
// test mercator to wgs84
// compare against saved toWgs84 results
for (const { filename, name, geojson } of fromMercator) {
const expected = loadJsonFileSync(directories.out + "wgs84-" + filename);
const results = truncate(proj4ToProj4(geojson, "EPSG:900913", "WGS84"));
t.deepEqual(results, expected);
}
t.end();
});

test("projection -- throws", (t) => {
t.throws(
() => toMercator(null),
Expand All @@ -88,44 +106,75 @@ test("projection -- throws", (t) => {
/geojson is required/,
"throws missing geojson"
);
t.throws(
() => proj4ToProj4(null, "WGS84", "EPSG:900913"),
/geojson is required/,
"throws missing geojson"
);
t.throws(
() => proj4ToProj4(point([0, 0]), null, "EPSG:900913"),
/inProjection is required/,
"throws missing inProjection"
);
t.throws(
() => proj4ToProj4(point([0, 0]), "WGS84", null),
/outProjection is required/,
"throws missing outProjection"
);
t.end();
});

test("projection -- verify mutation", (t) => {
const pt1 = point([10, 10]);
const pt2 = point([15, 15]);
const pt3 = point([20, 20]);
const pt1Before = clone(pt1);
const pt2Before = clone(pt2);
const pt3Before = clone(pt3);

toMercator(pt1);
toMercator(pt1, { mutate: false });
t.deepEqual(
pt1,
pt1Before,
"mutate = undefined - input should NOT be mutated"
);
toMercator(pt1, { mutate: false });
t.deepEqual(pt1, pt1Before, "mutate = false - input should NOT be mutated");
toMercator(pt1, { mutate: true });
t.notEqual(pt1, pt1Before, "input should be mutated");

toWgs84(pt2);
toWgs84(pt2, { mutate: false });
t.deepEqual(
pt2,
pt2Before,
"mutate = undefined - input should NOT be mutated"
);
toWgs84(pt2, { mutate: false });
t.deepEqual(pt2, pt2Before, "mutate = false - input should NOT be mutated");
toWgs84(pt2, { mutate: true });
t.notEqual(pt2, pt2Before, "input should be mutated");

proj4ToProj4(pt3, "WGS84", "EPSG:900913");
t.deepEqual(
pt3,
pt3Before,
"mutate = undefined - input should NOT be mutated"
);
proj4ToProj4(pt3, "WGS84", "EPSG:900913", { mutate: false });
t.deepEqual(pt3, pt3Before, "mutate = false - input should NOT be mutated");
proj4ToProj4(pt3, "WGS84", "EPSG:900913", { mutate: true });
t.notEqual(pt3, pt3Before, "input should be mutated");

t.end();
});

test("projection -- handle Position", (t) => {
const coord = [10, 40];
const mercator = toMercator(coord);
const wgs84 = toWgs84(mercator);
t.deepEqual(coord, wgs84, "coord equal same as wgs84");
t.deepEqual(coord, wgs84, "coord equal to wgs84");
const proj4Mercator = proj4ToProj4(coord, "WGS84", "EPSG:900913");
const proj4Wgs84 = proj4ToProj4(proj4Mercator, "EPSG:900913", "WGS84");
t.deepEqual(coord, proj4Wgs84,"coord equal to proj4 wgs84");
t.end();
});
25 changes: 15 additions & 10 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.