Skip to content

Commit

Permalink
feat: projections available internaly
Browse files Browse the repository at this point in the history
  • Loading branch information
neocarto committed Jul 1, 2022
1 parent 3fe91fd commit fdc8db2
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 67 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ bertin.draw({

#### Parameters

- **projection**: a d3 function or string defining the map projection. Refer [d3-geo-projection](https://github.com/d3/d3-geo-projection) and [spatialreference.org](https://spatialreference.org/) for more detailed explanation. (default: d3.geoEquirectangular() except if you use tiles. in this case, the projection is automatically set to d3.geoMercator()). Moreover, if you define projection as "none", you can display a basemap already projected. [Example](https://observablehq.com/@neocartocnrs/bertin-js-projections).
- **projection**: a d3 function or string defining the map projection. Refer [d3-geo-projection](https://github.com/d3/d3-geo-projection) and [spatialreference.org](https://spatialreference.org/) for more detailed explanation. (default: d3.geoEquirectangular() except if you use tiles. in this case, the projection is automatically set to d3.geoMercator()). Moreover, if you define projection as "user", you can display a basemap already projected. [Example](https://observablehq.com/@neocartocnrs/bertin-js-projections).
- **width**: width of the map (default:1000);
- **extent**: a feature or a bbox array defining the extent e.g. a country or [[112, -43],[153, -9]] (default: null)
- **margin**: margin around features to be displayed. This option can be useful if the stroke is very heavy (default: 1)
Expand Down
28 changes: 7 additions & 21 deletions src/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ const d3 = Object.assign({}, d3selection, d3geo, d3geoprojection);
import { getheight } from "./helpers/height.js";
import { figuration } from "./helpers/figuration.js";
import { getcenters } from "./helpers/centroids.js";
import { proj4d3 } from "./helpers/proj4d3.js";
import { bbox } from "./bbox.js";
import { getproj } from "./projections/projections.js";

// Layers
import { graticule } from "./layers/graticule.js";
Expand All @@ -35,28 +35,14 @@ import { logo } from "./layers/logo.js";

// Main
export function draw({ params = {}, layers = {} } = {}) {
// default global paramaters

// projection
let projection = params.projection
? params.projection
: d3.geoEquirectangular(); // default

if (typeof projection === "string" && projection !== "none") {
projection = proj4d3(projection);
}

// Use projected geometries

if (typeof projection === "string" && projection === "none") {
projection = d3.geoIdentity().reflectY(true);
}

// if tiles used, the projection is setted to d3.geoMercator()

// projections
let projection = params.projection;
const types = layers.map((d) => d.type);
if (types.includes("tiles")) {
projection = d3.geoMercator();
} else {
projection = getproj(projection);
console.log(projection);
}

// extent
Expand Down Expand Up @@ -160,7 +146,7 @@ export function draw({ params = {}, layers = {} } = {}) {
}

// ----------------------------------------
layers.reverse().forEach((layer) => {
[...layers].reverse().forEach((layer) => {
// Graticule
if (layer.type == "graticule") {
graticule(
Expand Down
45 changes: 0 additions & 45 deletions src/helpers/proj4d3.js

This file was deleted.

16 changes: 16 additions & 0 deletions src/projections/hoaxiaoguang.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as d3geoprojection from "d3-geo-projection";
const d3 = Object.assign({}, d3geoprojection);

// Approximative projection of HoaXiaoguang (thanks to Fil/@recifs)

export function HoaXiaoguang() {
const projection = d3
.geoHufnagel()
.a(0.8)
.b(0.35)
.psiMax(50)
.ratio(1.6)
.angle(90)
.rotate([110, -200, 90]);
return projection;
}
12 changes: 12 additions & 0 deletions src/projections/polar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as d3geo from "d3-geo";
const d3 = Object.assign({}, d3geo);

export function Polar() {
const projection = d3
.geoAzimuthalEquidistant()
.scale(190)
.rotate([0, -90])
.clipAngle(150);

return projection;
}
48 changes: 48 additions & 0 deletions src/projections/proj4d3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// proj4d3() is a function developped by @fil. See https://observablehq.com/@fil/proj4js-d3
import proj4 from "proj4";
//const proj4 = Object.assign({}, proj4);

import * as d3geo from "d3-geo";
const d3 = Object.assign({}, d3geo);

let epsg2154 =
"+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs";
let epsg3035 =
"+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +units=m +no_defs";
let epsg3857 =
"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs";
let epsg27700 =
"+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs";
let epsg42304 =
"+proj=lcc +lat_1=49 +lat_2=77 +lat_0=49 +lon_0=-95 +x_0=0 +y_0=0 +datum=NAD83 +units=m +no_defs ";

proj4.defs([["epsg:2154", `+title=France Lambert93 ${epsg2154}`]]);
proj4.defs([["epsg:3035", `+title=Europa (ETRS89/LAEA) ${epsg3035}`]]);
proj4.defs([["epsg:3857", `+title=WGS 84 / Pseudo-Mercator ${epsg3857}`]]);
proj4.defs([
["epsg:27700", `+title=British National Grid -- United Kingdom ${epsg27700}`],
]);
proj4.defs([["epsg:42304", `+title=NAD83 / NRCan LCC Canada ${epsg42304}`]]);

export function proj4d3(proj4string) {
// from Philippe Rivière : https://observablehq.com/@fil/proj4js-d3
let raw;

if (+proj4string == proj4string) raw = proj4("epsg:" + proj4string);

if (!raw) raw = proj4(proj4string);

const degrees = 180 / Math.PI,
radians = 1 / degrees,
p = function (lambda, phi) {
return raw.forward([lambda * degrees, phi * degrees]);
};
p.invert = function (x, y) {
return raw.inverse([x, y]).map(function (d) {
return d * radians;
});
};
const projection = d3.geoProjection(p).scale(1);
projection.raw = raw;
return projection;
}
80 changes: 80 additions & 0 deletions src/projections/projections.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { stringtod3proj } from "./stringtod3proj.js";
import { Polar } from "./polar.js";
import { HoaXiaoguang } from "./hoaxiaoguang.js";
//import { Spilhaus } from "./spilhaus.js";

import { proj4d3 } from "./proj4d3.js";
import * as d3geo from "d3-geo";
const d3 = Object.assign({}, d3geo);

// This function define the rules concerning projections

export function getproj(projection) {
/* DEFAULT - the projection is not defined.
The default projection is d3.geoEquirectangular().
*/

if (projection === null || projection === undefined || projection === "") {
return d3.geoEquirectangular();
}

/* FUNCTION - if the projection is a d3.js function (outside bertin.js).
then, the function is used directly.*/

if (typeof projection === "function") {
return projection;
}

// STRINGS
if (typeof projection === "string") {
projection = projection.replace(/\s/g, "");

/* CUSTOM projections*/
if (projection == "Polar") {
return Polar();
}
if (projection == "HoaXiaoguang") {
return HoaXiaoguang();
}
// if (projection == "Spilhaus") {
// return Spilhaus()();
// }

/* USER projection - if he geometries use a projection system,
they are displayed in this projection. Then it is impossible
to reproject them on the fly. And you ca't use outline layer.*/

if (projection === "user") {
return d3.geoIdentity().reflectY(true);
}

/* +PROJ & EPSG - in this case, proj4 and proj4d3 is used. */

if (
projection.substring(0, 5) === "+proj" ||
projection.substring(0, 5) === "epsg:"
) {
return proj4d3(projection);
}

/* D3.GEO - a d3.js function within a string,
then, it is interpreted roughly as a d3.js function. See projections/d3proj.js.
In this case, projections are managed inside bertin.js. See projection/d3proj.js*/

if (
projection !== "user" &&
projection.substring(0, 5) !== "+proj" &&
projection.substring(0, 5) !== "epsg:"
) {
return stringtod3proj(projection);
}
}

/* TILES - if a tiles layer is used,
the projection is automatically set to d3.geoMercator().
The geometries must be in geographic coordinates for this.
If the geometries are already projected, tiles are not used.
This part is managed in draw.js. */

// Custom cases
}
72 changes: 72 additions & 0 deletions src/projections/spilhaus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// import * as d3geoprojection from "d3-geo-projection";
// const d3 = Object.assign({}, d3geoprojection);

// function ellipticF(phi, m) {
// const { abs, atan, ln, PI: pi, sin, sqrt } = Math;
// const C1 = 10e-4,
// C2 = 10e-10,
// TOL = 10e-6;
// const sp = sin(phi);

// let k = sqrt(1 - m),
// h = sp * sp;

// // "complete" elliptic integral
// if (h >= 1 || abs(phi) === pi / 2) {
// if (k <= TOL) return sp < 0 ? -Infinity : Infinity;
// (m = 1), (h = m), (m += k);
// while (abs(h - k) > C1 * m) {
// k = sqrt(h * k);
// (m /= 2), (h = m), (m += k);
// }
// return sp < 0 ? -pi / m : pi / m;
// }
// // "incomplete" elliptic integral
// else {
// if (k <= TOL) return ln((1 + sp) / (1 - sp)) / 2;
// let g, n, p, r, y;
// (m = 1), (n = 0), (g = m), (p = m * k), (m += k);
// y = sqrt((1 - h) / h);
// if (abs((y -= p / y)) <= 0) y = C2 * sqrt(p);
// while (abs(g - k) > C1 * g) {
// (k = 2 * sqrt(p)), (n += n);
// if (y < 0) n += 1;
// (p = m * k), (g = m), (m += k);
// if (abs((y -= p / y)) <= 0) y = C2 * sqrt(p);
// }
// if (y < 0) n += 1;
// r = (atan(m / y) + pi * n) / m;
// return sp < 0 ? -r : r;
// }
// }

// function ellipticFactory(a, b, sm, sn) {
// let m = Math.asin(Math.sqrt(1 + Math.min(0, Math.cos(a + b))));
// if (sm) m = -m;

// let n = Math.asin(Math.sqrt(Math.abs(1 - Math.max(0, Math.cos(a - b)))));
// if (sn) n = -n;

// return [ellipticF(m, 0.5), ellipticF(n, 0.5)];
// }

// export function Spilhaus() {
// const { abs, max, min, sin, cos, asin, acos, tan } = Math;

// const spilhausSquareRaw = function (lambda, phi) {
// let a, b, sm, sn, xy;
// const sp = tan(0.5 * phi);
// a = cos(asin(sp)) * sin(0.5 * lambda);
// sm = sp + a < 0;
// sn = sp - a < 0;
// b = acos(sp);
// a = acos(a);

// return ellipticFactory(a, b, sm, sn);
// };

// return () =>
// d3
// .geoProjection(spilhausSquareRaw)
// .rotate([-66.94970198, 49.56371678, 40.17823482]);
// }
20 changes: 20 additions & 0 deletions src/projections/stringtod3proj.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as d3geo from "d3-geo";
import * as d3geoprojection from "d3-geo-projection";
const d3 = Object.assign({}, d3geo, d3geoprojection);

export function stringtod3proj(string) {
let str = string;
str = str.replace(/\s/g, "");

if (str.substring(0, 6) !== "d3.geo") {
if (str.indexOf(".") === -1) {
str += "()";
} else {
str = str.replace(".", "().");
}
str = "d3.geo" + str;
}

const createProjection = new Function("d3", `return (${str})`);
return createProjection(d3);
}

0 comments on commit fdc8db2

Please sign in to comment.