diff --git a/docs/functions/reproj.md b/docs/functions/reproj.md index 42677be..b7e734f 100644 --- a/docs/functions/reproj.md +++ b/docs/functions/reproj.md @@ -43,4 +43,13 @@ extent.reproj(4326, { // which is passed to https://github.com/danieljdufour/bbox-fns#densepolygon density: [100, 125] }); +``` + +## shrinking +Sometimes reprojecting a bounding box extends pass the valid bounds of the new projection, leading to NaN values. You can effectively shrink the bounding box, so it fits into the new projection's bounds, by pasing `shrink: true` and a `shrink_density` number. This works by essentially densifying the edges and disregarding points on the edges reprojected as NaN values. +```js +const northPole = new GeoExtent([-180, 85, 180, 90], { srs: 4326 }); + +northPole.reproj(3857, { shrink: true, shrink_density: 100 }).bbox; +[-20037508.342789244, 19971868.880408563, 20037508.342789244, 49411788.9015311] ``` \ No newline at end of file diff --git a/package.json b/package.json index 9c82b52..adecadf 100644 --- a/package.json +++ b/package.json @@ -54,22 +54,22 @@ "homepage": "https://github.com/DanielJDufour/geo-extent#readme", "devDependencies": { "@babel/cli": "^7.23.4", - "@babel/core": "^7.23.6", + "@babel/core": "^7.23.7", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "esbuild": "^0.19.10", - "flug": "^2.7.1", + "esbuild": "^0.19.11", + "flug": "^2.7.2", "global-jsdom": "^9.2.0", - "jsdom": "^23.0.1", + "jsdom": "^23.2.0", "leaflet": "^1.9.4" }, "dependencies": { - "bbox-fns": "^0.19.0", + "bbox-fns": "^0.20.2", "geography-markup-language": "^0.2.0", "get-epsg-code": "1.2.0", - "preciso": "^0.12.0", - "reproject-bbox": "^0.12.0", + "preciso": "^0.12.2", + "reproject-bbox": "^0.13.1", "reproject-geojson": "^0.5.0" } } diff --git a/src/geo-extent.js b/src/geo-extent.js index ca788ba..c17a114 100644 --- a/src/geo-extent.js +++ b/src/geo-extent.js @@ -458,10 +458,20 @@ export class GeoExtent { reproj( to, - { allow_infinity = false, debug_level = 0, density = "high", quiet = false } = { + { + allow_infinity = false, + debug_level = 0, + density = "high", + shrink = false, + shrink_density = 100, + split = true, + quiet = false + } = { allow_infinity: false, debug_level: 0, density: "high", + shrink: false, + split: true, quiet: false } ) { @@ -497,12 +507,14 @@ export class GeoExtent { let reprojected; try { - reprojected = reprojectBoundingBox({ + const options = { bbox: this.bbox, density, from: this.srs, + split, to - }); + }; + reprojected = reprojectBoundingBox(options); } catch (error) { if (debug_level) console.error(error); } @@ -510,6 +522,7 @@ export class GeoExtent { if (reprojected?.every(isFinite)) { return new GeoExtent(reprojected, { srs: to }); } + // as a fallback, try reprojecting to EPSG:4326 then to the desired srs if (to !== 4326) { let bbox_4326; @@ -518,6 +531,7 @@ export class GeoExtent { bbox: this.bbox, density, from: this.srs, + split, to: 4326 }); } catch (error) { @@ -530,6 +544,7 @@ export class GeoExtent { bbox: bbox_4326, density, from: 4326, + split, to }); } catch (err) { @@ -538,7 +553,36 @@ export class GeoExtent { } } - if (allow_infinity || reprojected?.every(isFinite)) { + if (reprojected && (allow_infinity || reprojected?.every(isFinite))) { + return new GeoExtent(reprojected, { srs: to }); + } + + // if really haven't gotten a solution yet, + // such as when reprojecting globe into Web Mercator + // reproject with shrinking and highest density + if (shrink) { + try { + if (shrink_density === "lowest") shrink_density = 1; + else if (shrink_density === "low") shrink_density = 2; + else if (shrink_density === "medium") shrink_density = 10; + else if (shrink_density === "high") shrink_density = 100; + else if (shrink_density === "higher") shrink_density = 1000; + else if (shrink_density === "highest") shrink_density = 10000; + + reprojected = reprojectBoundingBox({ + bbox: this.bbox, + density: shrink_density, + from: this.srs, + nan_strategy: "skip", + split: true, + to + }); + } catch (err) { + if (debug_level) console.error(`failed to reproject from bbox ${this.bbox} with shrinking to ${to}`); + } + } + + if (reprojected && (allow_infinity || reprojected?.every(isFinite))) { return new GeoExtent(reprojected, { srs: to }); } else if (quiet) { return; diff --git a/test/test.reproj.js b/test/test.reproj.js index af9aa19..bf698dd 100644 --- a/test/test.reproj.js +++ b/test/test.reproj.js @@ -13,15 +13,15 @@ test("reproj error", ({ eq }) => { test("north pole", ({ eq }) => { const northPole = new GeoExtent([-180, 85, 180, 90], { srs: 4326 }); - let msg; - try { - northPole.reproj(3857); - } catch (error) { - msg = error.message; - } - eq(msg.includes("failed to reproject"), true); - - eq(northPole.reproj(3857, { quiet: true }), undefined); + eq( + northPole.reproj(3857, { shrink: true }).bbox, + [-20037508.342789244, 19971868.880408563, 20037508.342789244, 49411788.9015311] + ); + eq( + northPole.reproj(3857, { shrink: true, shrink_density: 100 }).bbox, + [-20037508.342789244, 19971868.880408563, 20037508.342789244, 49411788.9015311] + ); + eq(northPole.reproj(3857, { split: false, quiet: true })?.js, undefined); }); test("reproject extent that crosses 180th meridian", ({ eq }) => { @@ -29,7 +29,7 @@ test("reproject extent that crosses 180th meridian", ({ eq }) => { const lyr = new GeoExtent([-180.00092731781535, 15.563268747733936, 179.99907268220255, 74.71076874773686], { srs: 4326 }); - const result = lyr.reproj(3857); + const result = lyr.reproj(3857, { debug_level: 0 }); eq(result.bbox, [-20037508.342789244, 1754201.542789432, 20037508.342789244, 12808999.953599941]); }); @@ -73,29 +73,33 @@ test("reproject extent that bends out", ({ eq }) => { const srs = 6623; const extent = new GeoExtent(bbox, { srs }); eq( - extent.reproj(4326, { density: "lowest" }).bbox, + extent.reproj(4326, { density: "lowest", split: false }).bbox, [-104.15783650020958, 22.33428366410961, -51.769705847928805, 56.48158793780131] ); eq( - extent.reproj(4326, { density: "low" }).bbox, + extent.reproj(4326, { density: "low", split: false }).bbox, [-104.15783650020958, 22.33428366410961, -51.769705847928805, 57.099578714450445] ); eq( - extent.reproj(4326, { density: "medium" }).bbox, + extent.reproj(4326, { density: "medium", split: false }).bbox, [-104.15783650020958, 22.33428366410961, -51.769705847928805, 57.52407399197629] ); eq( - extent.reproj(4326, { density: "high" }).bbox, + extent.reproj(4326, { density: "high", split: false }).bbox, [-104.15783650020958, 22.33428366410961, -51.769705847928805, 57.53583071204875] ); eq( - extent.reproj(4326, { density: "higher" }).bbox, + extent.reproj(4326, { density: "higher", split: false }).bbox, [-104.15783650020958, 22.33428366410961, -51.769705847928805, 57.53588499736936] ); eq( - extent.reproj(4326, { density: "highest" }).bbox, + extent.reproj(4326, { density: "highest", split: false }).bbox, [-104.15783650020958, 22.33428366410961, -51.769705847928805, 57.535885041786784] ); + eq( + extent.reproj(4326, { density: "highest", split: true }).bbox, + [-104.15783650020958, 22.33428366410961, -51.769705847928805, 57.53588504321043] + ); }); test("reproj to inf", ({ eq }) => { @@ -103,8 +107,9 @@ test("reproj to inf", ({ eq }) => { const srs = "EPSG:3857"; let msg; + let result; try { - new GeoExtent(bbox, { srs }).reproj(26916, { debug: false, density: 100 }); + result = new GeoExtent(bbox, { srs }).reproj(26916, { debug: false, density: 100 }); } catch (error) { msg = error.message; } @@ -120,3 +125,23 @@ test("reproj to inf", ({ eq }) => { Infinity ]); }); + +test("south pole", ({ eq }) => { + const ext = new GeoExtent([-3950000, -3943750, 3950000, 4350000], { srs: "EPSG:3031" }); + eq( + ext.reproj(4326, { debug_level: 10, density: "highest", split: true }).bbox, + [-179.9942619157211, -90, 180, -39.36335491661697] + ); +}); + +test("split", ({ eq }) => { + const ext = new GeoExtent([-180, -85.0511287798066, 179.99856, 85.0511287798066], { srs: "EPSG:4326" }); + const reprojected = ext.reproj(3857).bbox; + eq(reprojected, [-20037508.342789244, -20037508.342789255, 20037348.0427225, 20037508.342789244]); +}); + +test("split again", ({ eq }) => { + const ext = new GeoExtent([-180, -89.99928, 179.99856, 90], { srs: "EPSG:4326" }); + const reprojected = ext.reproj("EPSG:3857", { debug_level: 0, shrink: true }); + eq(reprojected.bbox, [-20037508.342789244, -76394987.3448674, 20037348.0427225, 30976473.682611432]); +});