Skip to content

Commit

Permalink
increased precision
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielJDufour committed Dec 28, 2021
1 parent 6e28e39 commit 211e981
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 53 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
],
"scripts": {
"build": "cp ./src/geo-extent.js ./dist/geo-extent.mjs && npx babel ./src/geo-extent.js --out-file ./dist/geo-extent.cjs --plugins=@babel/plugin-transform-modules-commonjs",
"format": "npx prettier --arrow-parens=avoid --print-width=120 --trailing-comma=none --write src/*.js test/*.js",
"format": "npx prettier --arrow-parens=avoid --print-width=120 --trailing-comma=none --write src/*.js test/*.js test/*.mjs",
"prepublishOnly": "npm run format",
"test": "for f in ./test/*; do node $f; done",
"test:html": "snowpack dev --polyfill-node"
Expand Down Expand Up @@ -53,6 +53,7 @@
"snowpack": "^3.8.8"
},
"dependencies": {
"big.js": "^6.1.1",
"get-epsg-code": "^0.0.7",
"reproject-bbox": "^0.2.0"
}
Expand Down
45 changes: 38 additions & 7 deletions src/geo-extent.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
* TO DO:
* add support for GeoJSON and need to check projection of GeoJSON
*/
import Big from "big.js";
import getEPSGCode from "get-epsg-code";
import reprojectBoundingBox from "reproject-bbox";

const avg = (a, b) => (a + b) / 2;
const avg = (a, b) => Big(a).plus(Big(b)).div(Big(2));
const isAry = o => Array.isArray(o);
const isDef = o => o !== undefined && o !== null && o !== "";
const isFunc = o => typeof o === "function";
Expand All @@ -32,6 +33,7 @@ const hasObjs = (o, ks) => ks.every(k => hasObj(o, k));
const hasKey = (o, k) => isObj(o) && o[k] !== undefined && o[k] !== null;
const hasKeys = (o, ks) => ks.every(k => hasKey(o, k));
const allNums = ary => isAry(ary) && ary.every(isNum);
const allStrs = ary => isAry(ary) && ary.every(isStr);
const getConstructor = o => (typeof obj === "object" && typeof obj.constructor === "function") || undefined;
const normalize = srs => {
if (!srs) return srs;
Expand Down Expand Up @@ -74,6 +76,7 @@ export class GeoExtent {
this.srs = normalize(srs);

let xmin, xmax, ymin, ymax;
let xmin_str, xmax_str, ymin_str, ymax_str;
if (getConstructor(o) === this.constructor) {
({ xmin, xmax, ymin, ymax } = o);
if (isDef(o.srs)) {
Expand All @@ -82,6 +85,9 @@ export class GeoExtent {
}
if (isAry(o) && o.length === 4 && allNums(o)) {
[xmin, ymin, xmax, ymax] = o;
} else if (isAry(o) && o.length === 4 && allStrs(o)) {
[xmin_str, ymin_str, xmax_str, ymax_str] = o;
[xmin, ymin, xmax, ymax] = o.map(str => Number(str));
} else if (isAry(o) && o.length === 2 && o.every(isAry) && o.every(o => o.length === 2 && allNums(o))) {
[[ymin, xmin], [ymax, xmax]] = o;
} else if (isLeafletLatLngBounds(o)) {
Expand Down Expand Up @@ -139,11 +145,21 @@ export class GeoExtent {
}

this.xmin = xmin;
this.xmin_str = xmin_str || xmin.toString();
this.ymin = ymin;
this.ymin_str = ymin_str || ymin.toString();
this.xmax = xmax;
this.xmax_str = xmax_str || xmax.toString();
this.ymax = ymax;
this.width = xmax - xmin;
this.height = ymax - ymin;
this.ymax_str = ymax_str || ymax.toString();

const width = Big(this.xmax_str).minus(this.xmin_str);
this.width = width.toNumber();
this.width_str = width.toString();

const height = Big(this.ymax_str).minus(this.ymin_str);
this.height = height.toNumber();
this.height_str = height.toString();

// corners
this.bottomLeft = { x: xmin, y: ymin };
Expand All @@ -156,11 +172,26 @@ export class GeoExtent {
[this.ymax, this.xmax]
];

this.area = this.width * this.height;
this.perimeter = 2 * this.width + 2 * this.height;
const area = Big(width).times(height);
this.area = area.toNumber();
this.area_str = area.toString();

const perimeter = width.times(2).plus(height.times(2));
this.perimeter = perimeter.toNumber();
this.perimeter_str = perimeter.toString();

this.bbox = [xmin, ymin, xmax, ymax];
this.center = { x: avg(xmin, xmax), y: avg(ymin, ymax) };
this.str = this.bbox.join(",");
this.bbox_str = [this.xmin_str, this.ymin_str, this.xmax_str, this.ymax_str];

const center = {
x: avg(xmin_str || xmin, xmax_str || xmax),
y: avg(ymin_str || ymin, ymax_str || ymax)
};

this.center = { x: center.x.toNumber(), y: center.y.toNumber() };
this.center_str = { x: center.x.toString(), y: center.y.toString() };

this.str = this.bbox_str.join(",");
}

_pre(_this, _other) {
Expand Down
2 changes: 1 addition & 1 deletion test/test.combine.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ test("combine southern and northern hemisphers", ({ eq }) => {
const combined = new GeoExtent({ xmin: -180, ymin: -90, xmax: 180, ymax: 90, srs: 4326 });
eq(ne.combine(sw), combined);
eq(sw.combine(ne), combined);
});
});
117 changes: 73 additions & 44 deletions test/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,49 @@ const CENTER = { x: (XMIN + XMAX) / 2, y: (YMIN + YMAX) / 2 };
const WIDTH = XMAX - XMIN;
const HEIGHT = YMAX - YMIN;
const AREA = WIDTH * HEIGHT;
const TWO_CORNER_BOUNDS = [ [YMIN, XMIN], [YMAX, XMAX] ];
const PERIMETER = 2 * WIDTH + 2 * HEIGHT;
const TWO_CORNER_BOUNDS = [
[YMIN, XMIN],
[YMAX, XMAX]
];
const NORTHERN_HEMISPHERE = [-180, 0, 180, 90];
const SOUTHERN_HEMISPHERE = [-180, -90, 180, 0];
const WESTERN_HEMISPHERE = [-180, -90, 0, 90];
const EASTERN_HEMISPHERE = [0, -90, 180, 90];
const NORTH_WEST_QUARTER_SPHERE = [-180, 0, 0, 90];
const str = n => n.toString();

const AS_OBJ_RESULT = {
srs: 'EPSG:4326',
area: AREA,
perimeter: 2 * WIDTH + 2 * HEIGHT,
bbox: BBOX,
center: CENTER,
xmin: XMIN,
ymin: YMIN,
xmax: XMAX,
ymax: YMAX,
width: WIDTH,
height: HEIGHT,
bottomLeft: { x: XMIN, y: YMIN },
bottomRight: { x: XMAX, y: YMIN },
topLeft: { x: XMIN, y: YMAX },
topRight: { x : XMAX, y: YMAX },
str: BBOX.join(","),
srs: "EPSG:4326",
xmin: -72,
xmin_str: "-72",
ymin: -47,
ymin_str: "-47",
xmax: 21,
xmax_str: "21",
ymax: 74,
ymax_str: "74",
width: 93,
width_str: "93",
height: 121,
height_str: "121",
bottomLeft: { x: -72, y: -47 },
bottomRight: { x: 21, y: -47 },
topLeft: { x: -72, y: 74 },
topRight: { x: 21, y: 74 },
leafletBounds: [
[YMIN, XMIN],
[YMAX, XMAX]
]
[-47, -72],
[74, 21]
],
area: 11253,
area_str: "11253",
perimeter: 428,
perimeter_str: "428",
bbox: [-72, -47, 21, 74],
bbox_str: ["-72", "-47", "21", "74"],
center: { x: -25.5, y: 13.5 },
center_str: { x: "-25.5", y: "13.5" },
str: "-72,-47,21,74"
};
// console.log(AS_OBJ_RESULT);

Expand All @@ -60,41 +75,46 @@ test("create from bbox in 4326", ({ eq }) => {

test("create from points", ({ eq }) => {
const EXPECTED_EXTENT_FOR_PT = {
srs: 'EPSG:4326',
srs: "EPSG:4326",
xmin: 147,
xmin_str: "147",
ymin: -18,
ymin_str: "-18",
xmax: 147,
xmax_str: "147",
ymax: -18,
ymax_str: "-18",
width: 0,
width_str: "0",
height: 0,
height_str: "0",
bottomLeft: { x: 147, y: -18 },
bottomRight: { x: 147, y: -18 },
topLeft: { x: 147, y: -18 },
topRight: { x: 147, y: -18 },
leafletBounds: [
[-18, 147],
[-18, 147]
],
area: 0,
area_str: "0",
perimeter: 0,
bbox: [ 147, -18, 147, -18 ],
perimeter_str: "0",
bbox: [147, -18, 147, -18],
bbox_str: ["147", "-18", "147", "-18"],
center: { x: 147, y: -18 },
str: '147,-18,147,-18',
leafletBounds: [
[ -18, 147 ],
[ -18, 147 ]
]
center_str: { x: "147", y: "-18" },
str: "147,-18,147,-18"
};
eq(new GeoExtent({ x: 147, y: -18 }, { srs: 4326 }).asObj(), EXPECTED_EXTENT_FOR_PT);
eq(new GeoExtent([ 147, -18 ], { srs: 4326 }).asObj(), EXPECTED_EXTENT_FOR_PT);
eq(new GeoExtent([147, -18], { srs: 4326 }).asObj(), EXPECTED_EXTENT_FOR_PT);
});

test("reproject from 4326 to 3857", ({ eq }) => {
const bbox = new GeoExtent(BBOX, { srs: 4326 });
const extentIn3857 = bbox.reproj(3857);
eq(extentIn3857.srs, 'EPSG:3857');
eq(extentIn3857.bbox, [
-8015003.337115697,
-5942074.072431109,
2337709.3066587453,
12515545.2124676
]);
eq(extentIn3857.srs, "EPSG:3857");
eq(extentIn3857.bbox, [-8015003.337115697, -5942074.072431109, 2337709.3066587453, 12515545.2124676]);
});

test("intake Bounds Array", ({ eq }) => {
Expand All @@ -103,9 +123,15 @@ test("intake Bounds Array", ({ eq }) => {
});

test("intake two xy points", ({ eq }) => {
const bbox = new GeoExtent([{ x: XMIN, y: YMIN }, { x: XMAX, y: YMAX }], { srs: 4326 });
const bbox = new GeoExtent(
[
{ x: XMIN, y: YMIN },
{ x: XMAX, y: YMAX }
],
{ srs: 4326 }
);
eq(bbox.asObj(), AS_OBJ_RESULT);
})
});

test("overlaps", ({ eq }) => {
const northernHemisphere = new GeoExtent(NORTHERN_HEMISPHERE);
Expand All @@ -117,14 +143,16 @@ test("overlaps", ({ eq }) => {
eq(northPole.overlaps(southPole), false);
});


test("crop", ({ eq }) => {
const northernHemisphere = new GeoExtent(NORTHERN_HEMISPHERE, { srs: 4326 });
const northWestQuarterSphere = new GeoExtent(NORTH_WEST_QUARTER_SPHERE, { srs: 4326 });
eq(northernHemisphere.crop(WESTERN_HEMISPHERE).bbox, NORTH_WEST_QUARTER_SPHERE);

// northern part
const result = new GeoExtent([-180, -85, 180, 85], { srs: 4326 }).reproj(3857).crop(northWestQuarterSphere).reproj(4326);
const result = new GeoExtent([-180, -85, 180, 85], { srs: 4326 })
.reproj(3857)
.crop(northWestQuarterSphere)
.reproj(4326);
eq(result.bbox[0] - -180 < 0.000001, true);
eq(result.bbox[1] - 0 < 0.000001, true);
eq(result.bbox[2] - 0 < 0.000001, true);
Expand All @@ -139,15 +167,19 @@ test("cropping area in web mercator by globe", ({ eq }) => {
});

test("cropping global by web mercator globe", ({ eq }) => {
const a = new GeoExtent([-20037508.342789244, -20037508.342789255, 20037508.342789244, 20037508.342789244], { srs: 3857 });
const a = new GeoExtent([-20037508.342789244, -20037508.342789255, 20037508.342789244, 20037508.342789244], {
srs: 3857
});
const b = new GeoExtent([-180, -89.99928, 179.99856, 90], { srs: 4326 });
const res = a.crop(b);
eq(res.bbox, [-20037508.342789244, -20037508.342789255, 20037348.0427225, 20037508.342789244]);
});

test("cropping web mercator tile", ({ eq }) => {
// web mercator tile x: 964, y: 1704, z: 12
const tile = new GeoExtent([-10605790.548624776, 3355891.2898323783, -10596006.609004272, 3365675.2294528796], { srs: 3857 });
const tile = new GeoExtent([-10605790.548624776, 3355891.2898323783, -10596006.609004272, 3365675.2294528796], {
srs: 3857
});

// convert web mercator tile to Latitude/Longitude
eq(tile.reproj(4326).bbox, [-95.27343750000001, 28.84467368077178, -95.185546875, 28.921631282421277]);
Expand All @@ -160,6 +192,3 @@ test("cropping web mercator tile", ({ eq }) => {

eq(partial.bbox, [-10605790.548624776, 3358990.12945602, -10601914.152717294, 3365675.2294528796]);
});



12 changes: 12 additions & 0 deletions test/test.precision.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import test from "flug";
import { GeoExtent } from "../src/geo-extent.js";

test("precision", ({ eq }) => {
const bbox = ["7698736.8577886725053", "160793.853073251455", "10066450.2459496622445", "1322636.683007621705"];
const ext = new GeoExtent(bbox);
eq(ext.width, 2367713.3881609896);
eq(ext.width_str, "2367713.3881609897392");
eq(ext.xmin_str, bbox[0]);
eq(ext.bbox_str, bbox);
eq(ext.str, bbox.join(","));
});

0 comments on commit 211e981

Please sign in to comment.