Browse Source

Fix Mercator projection

Honor Mercator's growing bounding box.
master
TheoryOfNekomata 2 years ago
parent
commit
93388048a5
4 changed files with 138 additions and 13 deletions
  1. +73
    -0
      src/assets/options/maps.json
  2. +30
    -13
      src/backend/services/Projection.service.ts
  3. +33
    -0
      src/pages/api/test.ts
  4. +2
    -0
      src/utils/types.ts

+ 73
- 0
src/assets/options/maps.json View File

@@ -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"
}
]
}
]
}
}

+ 30
- 13
src/backend/services/Projection.service.ts View File

@@ -2,11 +2,31 @@ import {PNG} from 'pngjs';
import {Projection} from '../../utils/types'; import {Projection} from '../../utils/types';
import * as d3geo from 'd3-geo'; import * as d3geo from 'd3-geo';


type ProjectionData = {
fn: Function,
bounds: (a: [number, number]) => [number, number],
}

export default interface ProjectionService {} export default interface ProjectionService {}
export class ProjectionServiceImpl implements ProjectionService { export class ProjectionServiceImpl implements ProjectionService {
private readonly projections: Record<Projection, Function> = {
[Projection.EQUIRECTANGULAR]: d3geo.geoEquirectangular,
[Projection.MERCATOR]: d3geo.geoMercator,
private readonly projections: Record<Projection, ProjectionData> = {
[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 } as const


private readonly referenceWidth = 960 as const; private readonly referenceWidth = 960 as const;
@@ -16,17 +36,14 @@ export class ProjectionServiceImpl implements ProjectionService {
return equiImage; return equiImage;
} }


const projectionFunction = this.projections[projection]();
if (!projectionFunction.invert) {
const { [projection]: currentProjectionData } = this.projections
const { invert } = currentProjectionData.fn();
if (!invert) {
return undefined; 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({ const target = new PNG({
width: tx, width: tx,
height: ty, height: ty,
@@ -34,9 +51,9 @@ export class ProjectionServiceImpl implements ProjectionService {
const targetData = target.data; const targetData = target.data;


let i = 0; 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) { 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) { if (projected) {
const [lambda, phi] = projected; const [lambda, phi] = projected;
if (!(lambda > 180 || lambda < -180 || phi > 90 || phi < -90)) { if (!(lambda > 180 || lambda < -180 || phi > 90 || phi < -90)) {


+ 33
- 0
src/pages/api/test.ts View File

@@ -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;

+ 2
- 0
src/utils/types.ts View File

@@ -1,6 +1,8 @@
export enum Projection { export enum Projection {
EQUIRECTANGULAR = 'equirectangular', EQUIRECTANGULAR = 'equirectangular',
MERCATOR = 'mercator', MERCATOR = 'mercator',
AZIMUTHAL_EQUIDISTANT = 'azimuthalEquidistant',
CONIC_CONFORMAL = 'conicConformal',
} }


export enum MapType { export enum MapType {


Loading…
Cancel
Save