From 93388048a5089a902ed6080d3b6cd107674626c8 Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Sun, 13 Mar 2022 21:47:28 +0800 Subject: [PATCH] Fix Mercator projection Honor Mercator's growing bounding box. --- src/assets/options/maps.json | 73 ++++++++++++++++++++++ src/backend/services/Projection.service.ts | 43 +++++++++---- src/pages/api/test.ts | 33 ++++++++++ src/utils/types.ts | 2 + 4 files changed, 138 insertions(+), 13 deletions(-) create mode 100644 src/assets/options/maps.json create mode 100644 src/pages/api/test.ts diff --git a/src/assets/options/maps.json b/src/assets/options/maps.json new file mode 100644 index 0000000..bb85628 --- /dev/null +++ b/src/assets/options/maps.json @@ -0,0 +1,73 @@ +{ + "options": { + "map": [ + { + "label": "World", + "value": "world", + "projections": [ + "equirectangular", + "mercator", + "airocean" + ] + }, + { + "label": "Multiple Continents", + "value": "multiple-continents", + "children": [ + { + "label": "Afro-Eurasia", + "value": "multiple-continents.afro-eurasia" + }, + { + "label": "Americas", + "value": "multiple-continents.americas" + }, + { + "label": "Eurasia", + "value": "multiple-continents.eurasia", + "projections": [ + "twoPointEquidistant" + ] + } + ] + }, + { + "label": "Single Continents", + "value": "single-continents", + "children": [ + { + "label": "Africa", + "value": "single-continents.africa" + }, + { + "label": "Antarctica", + "value": "single-continents.antarctica" + }, + { + "label": "Asia", + "value": "single-continents.asia" + }, + { + "label": "Australia & Oceania", + "value": "single-continents.australia-oceania" + }, + { + "label": "Europe", + "value": "single-continents.europe", + "projections": [ + "conicConformal" + ] + }, + { + "label": "North America", + "value": "single-continents.north-america" + }, + { + "label": "South America", + "value": "single-continents.south-america" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/src/backend/services/Projection.service.ts b/src/backend/services/Projection.service.ts index 70f7cf6..f2461b2 100644 --- a/src/backend/services/Projection.service.ts +++ b/src/backend/services/Projection.service.ts @@ -2,11 +2,31 @@ import {PNG} from 'pngjs'; import {Projection} from '../../utils/types'; import * as d3geo from 'd3-geo'; +type ProjectionData = { + fn: Function, + bounds: (a: [number, number]) => [number, number], +} + export default interface ProjectionService {} export class ProjectionServiceImpl implements ProjectionService { - private readonly projections: Record = { - [Projection.EQUIRECTANGULAR]: d3geo.geoEquirectangular, - [Projection.MERCATOR]: d3geo.geoMercator, + private readonly projections: Record = { + [Projection.EQUIRECTANGULAR]: { + fn: d3geo.geoEquirectangular, + bounds: (a) => a, + }, + [Projection.MERCATOR]: { + fn: d3geo.geoMercator, + bounds: ([width, height]) => [width, height * 2], + }, + [Projection.AZIMUTHAL_EQUIDISTANT]: { + fn: d3geo.geoAzimuthalEquidistant, + bounds: (a) => a, + }, + [Projection.CONIC_CONFORMAL]: { + fn: d3geo.geoConicConformal, + bounds: (a) => a, + }, + } as const private readonly referenceWidth = 960 as const; @@ -16,17 +36,14 @@ export class ProjectionServiceImpl implements ProjectionService { return equiImage; } - const projectionFunction = this.projections[projection](); - if (!projectionFunction.invert) { + const { [projection]: currentProjectionData } = this.projections + const { invert } = currentProjectionData.fn(); + if (!invert) { return undefined; } - const sx = equiImage.width; - const sy = equiImage.height; - const sourceData = equiImage.data; - const tx = sx; - // const py = projection === Projection.MERCATOR ? dy * 2 : dy; - const ty = sy; + const { width: sx, height: sy, data: sourceData } = equiImage; + const [tx, ty] = currentProjectionData.bounds([sx, sy]); const target = new PNG({ width: tx, height: ty, @@ -34,9 +51,9 @@ export class ProjectionServiceImpl implements ProjectionService { const targetData = target.data; let i = 0; - for (let y = 0; y < sy; y += 1) { + for (let y = (sy - ty) / 2; y < (ty + ((sy - ty) / 2)); y += 1) { for (let x = 0; x < sx; x += 1) { - const projected = projectionFunction.invert([x / sx * this.referenceWidth, y / sx * this.referenceWidth]) as [number, number]; + const projected = invert([x / sx * this.referenceWidth, y / sx * this.referenceWidth]) as [number, number]; if (projected) { const [lambda, phi] = projected; if (!(lambda > 180 || lambda < -180 || phi > 90 || phi < -90)) { diff --git a/src/pages/api/test.ts b/src/pages/api/test.ts new file mode 100644 index 0000000..07be21b --- /dev/null +++ b/src/pages/api/test.ts @@ -0,0 +1,33 @@ +import * as d3Geo from 'd3-geo'; +import {NextApiHandler} from 'next'; + +const testHandler: NextApiHandler = async (req, res) => { + const mercator = d3Geo.geoMercator() + + let lambda, phi; + let sx; + let sy; + let q; + + [lambda, phi] = mercator.invert!([0, -128])! + sx = 64 + sy = 32 + q = ((90 - phi) / 180 * sy | 0) * sx + ((180 + lambda) / 360 * sx | 0) << 2; + console.log(q); + + // [lambda, phi] = mercator.invert!([63, 0])! + // sx = 64 + // sy = 32 + // q = ((90 - phi) / 180 * sy | 0) * sx + ((180 + lambda) / 360 * sx | 0) << 2; + // console.log(q); + // + // [lambda, phi] = mercator.invert!([0, 16])! + // sx = 64 + // sy = 32 + // q = ((90 - phi) / 180 * sy | 0) * sx + ((180 + lambda) / 360 * sx | 0) << 2; + // console.log(q); + + res.json(0) +} + +export default testHandler; diff --git a/src/utils/types.ts b/src/utils/types.ts index 7e6b91b..cc0f716 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,6 +1,8 @@ export enum Projection { EQUIRECTANGULAR = 'equirectangular', MERCATOR = 'mercator', + AZIMUTHAL_EQUIDISTANT = 'azimuthalEquidistant', + CONIC_CONFORMAL = 'conicConformal', } export enum MapType {