Skip to content

feat: Switch geodata providers #7393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions devtools/test_dashboard/devtools.js
Original file line number Diff line number Diff line change
@@ -17,8 +17,7 @@ var Tabs = {
Plotly.setPlotConfig({

// use local topojson files
topojsonURL: '../../node_modules/sane-topojson/dist/',

topojsonURL: "../../dist/topojson/",
// register mapbox access token
// run `npm run preset` if you haven't yet
mapboxAccessToken: credentials.MAPBOX_ACCESS_TOKEN,
2 changes: 1 addition & 1 deletion dist/topojson/africa_110m.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/topojson/africa_50m.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/topojson/antarctica_110m.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/topojson/antarctica_50m.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/topojson/asia_110m.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/topojson/asia_50m.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/topojson/europe_110m.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/topojson/europe_50m.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/topojson/north-america_110m.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/topojson/north-america_50m.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/topojson/oceania_110m.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/topojson/oceania_50m.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/topojson/south-america_110m.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/topojson/south-america_50m.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/topojson/usa_110m.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/topojson/usa_50m.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/topojson/world_110m.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/topojson/world_50m.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions draftlogs/7393_change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Switch to United Nations/Natural Earth geodata for building topojson used in geo plot
801 changes: 743 additions & 58 deletions package-lock.json

Large diffs are not rendered by default.

20 changes: 12 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -35,9 +35,9 @@
"use-draftlogs": "node tasks/use_draftlogs.js",
"empty-draftlogs": "node tasks/empty_draftlogs.js",
"empty-dist": "node tasks/empty_dist.js",
"build": "npm run empty-dist && npm run preprocess && npm run find-strings && npm run bundle && npm run extra-bundles && npm run locales && npm run schema dist && npm run stats",
"build": "npm run empty-dist && npm run preprocess && npm run build_topojson && npm run find-strings && npm run bundle && npm run extra-bundles && npm run locales && npm run schema dist && npm run stats",
"regl-codegen": "node devtools/regl_codegen/server.mjs",
"cibuild": "npm run empty-dist && npm run preprocess && node tasks/cibundle.mjs",
"cibuild": "npm run empty-dist && npm run preprocess && npm run build_topojson && node tasks/cibundle.mjs",
"lint": "npx @biomejs/biome lint",
"lint-fix": "npx @biomejs/biome format ./test/image/mocks --write; npx @biomejs/biome lint --write || true",
"pretest": "node tasks/pretest.js",
@@ -60,7 +60,10 @@
"version": "npm run build && git add -A lib dist build src/version.js",
"postversion": "node -e \"console.log('Version bumped and committed. If ok, run: git push && git push --tags')\"",
"postpublish": "node tasks/sync_packages.js",
"postshrinkwrap": "chttps ."
"postshrinkwrap": "chttps .",
"get_geodata": "node tasks/topojson/get_geodata.mjs",
"process_geodata": "node tasks/topojson/process_geodata.mjs",
"build_topojson": "npm run get_geodata && npm run process_geodata"
},
"browserify": {
"transform": [
@@ -72,6 +75,7 @@
"@plotly/d3-sankey": "0.7.2",
"@plotly/d3-sankey-circular": "0.33.1",
"@plotly/mapbox-gl": "1.13.4",
"@plotly/regl": "^2.1.2",
"@turf/area": "^7.1.0",
"@turf/bbox": "^7.1.0",
"@turf/centroid": "^7.1.0",
@@ -85,7 +89,6 @@
"css-loader": "^7.1.2",
"d3-force": "^1.2.1",
"d3-format": "^1.4.5",
"d3-geo": "^1.12.1",
"d3-geo-projection": "^2.9.0",
"d3-hierarchy": "^1.1.9",
"d3-interpolate": "^3.0.1",
@@ -107,7 +110,6 @@
"point-in-polygon": "^1.1.0",
"polybooljs": "^1.2.2",
"probe-image-size": "^7.2.3",
"@plotly/regl": "^2.1.2",
"regl-error2d": "^2.0.12",
"regl-line2d": "^3.1.3",
"regl-scatter2d": "^3.3.1",
@@ -124,6 +126,9 @@
},
"devDependencies": {
"@biomejs/biome": "1.8.3",
"@plotly/mathjax-v2": "npm:mathjax@2.7.5",
"@plotly/mathjax-v3": "npm:mathjax@^3.2.2",
"@turf/simplify": "^7.2.0",
"amdefine": "^1.0.1",
"assert": "^2.1.0",
"browserify-transform-tools": "^1.7.0",
@@ -132,6 +137,7 @@
"canvas": "^2.11.2",
"check-node-version": "^4.2.1",
"chttps": "^1.0.6",
"d3-geo": "^3.1.1",
"deep-equal": "^2.2.3",
"ecstatic": "^4.1.4",
"esbuild": "^0.23.1",
@@ -161,8 +167,7 @@
"karma-viewport": "1.0.2",
"lodash": "^4.17.21",
"madge": "^8.0.0",
"@plotly/mathjax-v2": "npm:mathjax@2.7.5",
"@plotly/mathjax-v3": "npm:mathjax@^3.2.2",
"mapshaper": "^0.6.102",
"minify-stream": "^2.1.0",
"npm-link-check": "^5.0.1",
"open": "^8.4.2",
@@ -172,7 +177,6 @@
"raw-loader": "^4.0.2",
"read-last-lines": "^1.8.0",
"run-series": "^1.1.9",
"sane-topojson": "^4.0.0",
"sass": "^1.78.0",
"stream-browserify": "^3.0.0",
"through2": "^4.0.2",
29 changes: 24 additions & 5 deletions src/assets/geo_assets.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
'use strict';

var saneTopojson = require('sane-topojson');
const topojson = {
africa_110m: require('../../dist/topojson/africa_110m.json'),
antarctica_110m: require('../../dist/topojson/antarctica_110m.json'),
asia_110m: require('../../dist/topojson/asia_110m.json'),
europe_110m: require('../../dist/topojson/europe_110m.json'),
'north-america_110m': require('../../dist/topojson/north-america_110m.json'),
oceania_110m: require('../../dist/topojson/oceania_110m.json'),
'south-america_110m': require('../../dist/topojson/south-america_110m.json'),
usa_110m: require('../../dist/topojson/usa_110m.json'),
world_110m: require('../../dist/topojson/world_110m.json'),
africa_50m: require('../../dist/topojson/africa_50m.json'),
antarctica_50m: require('../../dist/topojson/antarctica_50m.json'),
asia_50m: require('../../dist/topojson/asia_50m.json'),
europe_50m: require('../../dist/topojson/europe_50m.json'),
'north-america_50m': require('../../dist/topojson/north-america_50m.json'),
oceania_50m: require('../../dist/topojson/oceania_50m.json'),
'south-america_50m': require('../../dist/topojson/south-america_50m.json'),
usa_50m: require('../../dist/topojson/usa_50m.json'),
world_50m: require('../../dist/topojson/world_50m.json'),
}


exports.version = require('../version').version;

exports.topojson = saneTopojson;
module.exports = {
topojson,
version: require('../version').version
}
11 changes: 0 additions & 11 deletions tasks/preprocess.js
Original file line number Diff line number Diff line change
@@ -11,7 +11,6 @@ var updateVersion = require('./util/update_version');
// main
makeBuildCSS();
exposePartsInLib();
copyTopojsonFiles();
updateVersion(constants.pathToPlotlyVersion);

// convert scss to css to js and static css file
@@ -72,13 +71,3 @@ function writeLibFiles(obj) {
);
}
}

// copy topojson files from sane-topojson to dist/
function copyTopojsonFiles() {
fs.copy(
constants.pathToTopojsonSrc,
constants.pathToTopojsonDist,
{ clobber: true },
common.throwOnError
);
}
119 changes: 119 additions & 0 deletions tasks/topojson/config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
const config = {
resolutions: [50, 110],
scopes: [
{
name: 'africa',
specs: {
filter: 'georeg === "AFR"',
bounds: [-30, -50, 60, 50]
}
},
{
name: 'antarctica',
specs: {
filter: 'georeg === "ANT"',
bounds: [-180, -90, 180, -50]
}
},
{
name: 'asia',
specs: {
filter: 'georeg === "ASI"',
bounds: [15, -90, 180, 85]
}
},
{
name: 'europe',
specs: {
filter: 'georeg === "EUR"',
bounds: [-30, 0, 60, 90]
}
},
{
name: 'north-america',
specs: {
filter: 'subreg === "Northern America" || ["Central America", "Caribbean"].includes(intreg)',
bounds: [-180, 0, -45, 85]
}
},
{
name: 'oceania',
specs: {
filter: 'georeg === "OCE"',
bounds: [-180, -50, 180, 25]
}
},
{
name: 'south-america',
specs: {
filter: 'intreg === "South America"',
bounds: [-100, -70, -30, 25]
}
},
{
name: 'usa',
specs: {
filter: 'iso3cd === "USA" && ![4, undefined].includes(stscod)',
bounds: [-180, 0, -45, 85]
}
},
{
name: 'world',
specs: {
filter: '',
bounds: []
}
}
],
outputDirGeojson: './build/geodata/geojson',
outputDirTopojson: './dist/topojson',
inputDir: './build/geodata',
vectors: {
// 'coastlines', 'countries', 'land', and 'ocean' are derived from UN geodata
lakes: {
source: 'lakes',
type: 'physical'
},
rivers: {
source: 'rivers_lake_centerlines',
type: 'physical'
},
subunits: {
source: 'admin_1_states_provinces_lakes',
type: 'cultural'
}
},
layers: {
coastlines: 'land',
countries: 'countries',
ocean: 'land',
lakes: 'lakes',
land: 'land',
rivers: 'rivers_lake_centerlines',
subunits: 'admin_1_states_provinces_lakes'
},
unFilename: 'un_geodata_simplified',
unDownloadUrl: 'https://geoportal.un.org/arcgis/sharing/rest/content/items/d7caaff3ef4b4f7c82689b7c4694ad92/data',
filters: {
countries: 'stscod !== undefined',
land: [
'{839C9589-44D9-4BD5-A681-13E10ED03C5E}', // AME
'{2EE1B4A5-9C3F-445C-A1AB-399715463785}', // ANT
'{3D11547B-94D9-42C9-B849-14B389FE5F7F}', // OCE
'{32DB79BE-0D53-46BD-995F-EBE7C30ED6B6}', // AFR
'{3F3547E7-C7FB-4347-9D80-575C6485FD2E}', // EUR
'{4351AA38-B383-44BF-8341-720DD74872B4}' // ASI
]
.map((id) => `globalid === "${id}"`)
.join(' || '),
subunits: ['AUS', 'BRA', 'CAN', 'USA'].map((id) => `adm0_a3 === "${id}"`).join(' || ')
}
};

export const getNEFilename = ({ resolution, source }) => `ne_${resolution}m_${source}`;

export function getNEDownloadUrl({ resolution, vector: { source, type } }) {
return `https://naciscdn.org/naturalearth/${resolution}m/${type}/${getNEFilename({ resolution, source })}.zip`;
}

export default config;
62 changes: 62 additions & 0 deletions tasks/topojson/get_geodata.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { exec } from 'child_process';
import fs from 'fs';
import { Readable } from 'stream';
import { pipeline } from 'stream/promises';
import config, { getNEDownloadUrl, getNEFilename } from './config.mjs';

const { resolutions, unDownloadUrl, unFilename, vectors } = config;

const tasksPath = './tasks/topojson';
const outputPath = './build/geodata';

// Download Natural Earth vector maps
for (const vector of Object.values(vectors)) {
for (const resolution of resolutions) {
const url = getNEDownloadUrl({ resolution, vector });
const filename = getNEFilename({ resolution, source: vector.source });
const archivePath = `${outputPath}/${filename}.zip`;

if (!fs.existsSync(archivePath)) {
try {
const response = await fetch(url);
if (!response.ok || !response.body) throw new Error(`Bad response: ${response.status}`);

if (!fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true });
const file = fs.createWriteStream(archivePath);
await pipeline(Readable.fromWeb(response.body), file);

// Use the shell to handle decompressing
if (!fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true });
exec(`unzip -o ${archivePath} -d ${outputPath}`);

} catch (error) {
console.error(`Error when downloading file '${archivePath}': ${error}`);
continue;
}
}
}
}

// Download UN GeoJSON file
const url = unDownloadUrl;
const archivePath = `${tasksPath}/${unFilename}.zip`;
const geojsonPath = `${outputPath}`;
const geojsonFilePath = `${geojsonPath}/${unFilename}.geojson`;

if (fs.existsSync(archivePath)) {
if (!fs.existsSync(geojsonFilePath)) exec(`unzip -o ${archivePath} -d ${geojsonPath}`);
} else {
try {
const response = await fetch(url);
if (!response.ok || !response.body) throw new Error(`Bad response: ${response.status}`);

const file = fs.createWriteStream(geojsonFilePath);
await pipeline(Readable.fromWeb(response.body), file);

// Use the shell to handle compression
exec(`zip -j ${archivePath} ${geojsonFilePath}`);

} catch (error) {
console.error(`Error when downloading file '${geojsonFilePath}': ${error}`);
}
}
268 changes: 268 additions & 0 deletions tasks/topojson/process_geodata.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
import simplify from '@turf/simplify';
import { geoIdentity, geoPath } from 'd3-geo';
import fs from 'fs';
import mapshaper from 'mapshaper';
import path from 'path';
import config, { getNEFilename } from './config.mjs';

const { filters, inputDir, layers, resolutions, scopes, unFilename, vectors } = config;

// Create output directories
const outputDirGeojson = path.resolve(config.outputDirGeojson);
if (!fs.existsSync(outputDirGeojson)) fs.mkdirSync(outputDirGeojson, { recursive: true });
const outputDirTopojson = path.resolve(config.outputDirTopojson);
if (!fs.existsSync(outputDirTopojson)) fs.mkdirSync(outputDirTopojson, { recursive: true });

async function convertShpToGeo(filename) {
const inputFilePath = `${inputDir}/${filename}.shp`;
const outputFilePath = `${outputDirGeojson}/${filename}.geojson`;
const commands = [inputFilePath, `-proj wgs84`, `-o format=geojson ${outputFilePath}`].join(' ');
await mapshaper.runCommands(commands);
}

function getJsonFile(filename) {
try {
return JSON.parse(fs.readFileSync(filename, 'utf8'));
} catch (err) {
console.error(`❌ Failed to load JSON input file '${filename}':`, err.message);
process.exit(1);
}
}

async function createCountriesLayer({ bounds, filter, name, resolution, source }) {
const inputFilePath = `${outputDirGeojson}/${unFilename}_${resolution}m/${source}.geojson`;
const outputFilePath = `${outputDirGeojson}/${name}_${resolution}m/countries.geojson`;
const commands = [
inputFilePath,
bounds.length ? `-clip bbox=${bounds.join(',')}` : '',
filter ? `-filter '${filter}'` : '',
`-o ${outputFilePath}`
].join(' ');
await mapshaper.runCommands(commands);
addCentroidsToGeojson(outputFilePath);
}

function addCentroidsToGeojson(geojsonPath) {
const geojson = getJsonFile(geojsonPath);
if (!geojson.features) return;

const features = geojson.features.map((feature) => {
const centroid = getCentroid(feature);
feature.properties.ct = centroid;

return feature;
});

fs.writeFileSync(geojsonPath, JSON.stringify({ ...geojson, features }));
}

async function createLandLayer({ bounds, name, resolution, source }) {
// TODO: Figure out way to only include North and Central America via filter, dissolve
const inputFilePath = `${outputDirGeojson}/${unFilename}_${resolution}m/${source}.geojson`;
const outputFilePath = `${outputDirGeojson}/${name}_${resolution}m/land.geojson`;
const commands = [
inputFilePath,
'-dissolve',
bounds.length ? `-clip bbox=${bounds.join(',')}` : '',
`-o ${outputFilePath}`
].join(' ');
await mapshaper.runCommands(commands);
}

async function createCoastlinesLayer({ bounds, name, resolution, source }) {
// TODO: Update source to be a path?
const inputFilePath = `${outputDirGeojson}/${unFilename}_${resolution}m/${source}.geojson`;
const outputFilePath = `${outputDirGeojson}/${name}_${resolution}m/coastlines.geojson`;
const commands = [
inputFilePath,
'-dissolve',
'-lines',
bounds.length ? `-clip bbox=${bounds.join(',')}` : '',
`-o ${outputFilePath}`
].join(' ');
await mapshaper.runCommands(commands);
}

async function createOceanLayer({ bounds, name, resolution, source }) {
const inputFilePath = `./tasks/topojson/world_rectangle.geojson`;
const outputFilePath = `${outputDirGeojson}/${name}_${resolution}m/ocean.geojson`;
const eraseFilePath = `${outputDirGeojson}/${unFilename}_${resolution}m/${source}.geojson`;
const commands = [
inputFilePath,
bounds.length ? `-clip bbox=${bounds.join(',')}` : '',
`-erase ${eraseFilePath}`,
`-o ${outputFilePath}`
].join(' ');
await mapshaper.runCommands(commands);
}

async function createRiversLayer({ name, resolution, source }) {
const inputFilePath = `${outputDirGeojson}/${getNEFilename({ resolution, source })}.geojson`;
const outputFilePath = `${outputDirGeojson}/${name}_${resolution}m/rivers.geojson`;
const commands = [
inputFilePath,
`-clip ${outputDirGeojson}/${name}_${resolution}m/countries.geojson`, // Clip to the continent
`-o ${outputFilePath}`
].join(' ');
await mapshaper.runCommands(commands);
}

async function createLakesLayer({ name, resolution, source }) {
const inputFilePath = `${outputDirGeojson}/${getNEFilename({ resolution, source })}.geojson`;
const outputFilePath = `${outputDirGeojson}/${name}_${resolution}m/lakes.geojson`;
const commands = [
inputFilePath,
`-clip ${outputDirGeojson}/${name}_${resolution}m/countries.geojson`, // Clip to the continent
`-o ${outputFilePath}`
].join(' ');
await mapshaper.runCommands(commands);
}

async function createSubunitsLayer({ name, resolution, source }) {
const filter = ['AUS', 'BRA', 'CAN', 'USA'].map((id) => `adm0_a3 === "${id}"`).join(' || ');
const inputFilePath = `${outputDirGeojson}/${getNEFilename({ resolution, source })}.geojson`;
const outputFilePath = `${outputDirGeojson}/${name}_${resolution}m/subunits.geojson`;
const commands = [
inputFilePath,
`-filter "${filter}"`,
`-clip ${outputDirGeojson}/${name}_${resolution}m/countries.geojson`, // Clip to the continent
`-o ${outputFilePath}`
].join(' ');
await mapshaper.runCommands(commands);
addCentroidsToGeojson(outputFilePath);
}

function pruneProperties(topojson) {
for (const layer in topojson.objects) {
switch (layer) {
case 'countries':
topojson.objects[layer].geometries = topojson.objects[layer].geometries.map((geometry) => {
const { properties } = geometry;
if (properties) {
geometry.id = properties.iso3cd;
geometry.properties = {
ct: properties.ct
};
}

return geometry;
});
break;
case 'subunits':
topojson.objects[layer].geometries = topojson.objects[layer].geometries.map((geometry) => {
const { properties } = geometry;
if (properties) {
geometry.id = properties.postal;
geometry.properties = {
ct: properties.ct,
gu: properties.gu_a3
};
}

return geometry;
});

break;
default:
topojson.objects[layer].geometries = topojson.objects[layer].geometries.map((geometry) => {
delete geometry.id;
delete geometry.properties;

return geometry;
});

break;
}
}

return topojson;
}

function getCentroid(feature) {
const { type } = feature.geometry;
const projection = geoIdentity();
const path = geoPath(projection);

if (type === 'MultiPolygon') {
let maxArea = -Infinity;

for (const coordinates of feature.geometry.coordinates) {
const polygon = { type: 'Polygon', coordinates };
const area = path.area(polygon);
if (area > maxArea) {
maxArea = area;
feature = polygon;
}
}
}

return path.centroid(feature).map((coord) => +coord.toFixed(2));
}

async function convertLayersToTopojson({ name, resolution }) {
const regionDir = path.join(outputDirGeojson, `${name}_${resolution}m`);
if (!fs.existsSync(regionDir)) return;

const outputFile = `${outputDirTopojson}/${name}_${resolution}m.json`;
// Layer names default to file names
const commands = [`${regionDir}/*.geojson combine-files`, `-o format=topojson ${outputFile}`].join(' ');
await mapshaper.runCommands(commands);

// Remove extra information from features
const topojson = getJsonFile(outputFile);
const prunedTopojson = pruneProperties(topojson);
fs.writeFileSync(outputFile, JSON.stringify(prunedTopojson));
}

// Get polygon features from UN GeoJSON
const inputFilePath = `${inputDir}/${unFilename}.geojson`;
const outputFilePath50m = `${outputDirGeojson}/${unFilename}_50m/all_features.geojson`;
const outputPath110m = `${outputDirGeojson}/${unFilename}_110m`;
const commandsAllFeatures = [inputFilePath, `-o target=1 ${outputFilePath50m}`].join(' ');
await mapshaper.runCommands(commandsAllFeatures);

const geojson = getJsonFile(outputFilePath50m);
const simplifiedGeojson = {
...geojson,
features: geojson.features.map((f) => simplify(f, { tolerance: 0.01, highQuality: true }))
};
if (!fs.existsSync(outputPath110m)) fs.mkdirSync(outputPath110m, { recursive: true });
fs.writeFileSync(`${outputPath110m}/all_features.geojson`, JSON.stringify(simplifiedGeojson));

for (const resolution of resolutions) {
for (const { source } of Object.values(vectors)) {
await convertShpToGeo(getNEFilename({ resolution, source }));
}

// Get countries from all polygon features
const inputFilePathCountries = `${outputDirGeojson}/${unFilename}_${resolution}m/all_features.geojson`;
const outputFilePathCountries = `${outputDirGeojson}/${unFilename}_${resolution}m/countries.geojson`;
const commandsCountries = [
inputFilePathCountries,
`-filter '${filters.countries}'`,
`-o ${outputFilePathCountries}`
].join(' ');
await mapshaper.runCommands(commandsCountries);

// Get land from all polygon features
const inputFilePathLand = `${outputDirGeojson}/${unFilename}_${resolution}m/all_features.geojson`;
const outputFilePathLand = `${outputDirGeojson}/${unFilename}_${resolution}m/land.geojson`;
const commandsLand = [inputFilePathLand, `-filter '${filters.land}'`, `-clean -o ${outputFilePathLand}`].join(' ');
await mapshaper.runCommands(commandsLand);
}

for (const resolution of resolutions) {
for (const {
name,
specs: { bounds, filter }
} of scopes) {
await createCountriesLayer({ bounds, filter, name, resolution, source: layers.countries });
await createLandLayer({ bounds, name, resolution, source: layers.land });
await createCoastlinesLayer({ bounds, name, resolution, source: layers.coastlines });
await createOceanLayer({ bounds, name, resolution, source: layers.ocean });
await createRiversLayer({ bounds, name, resolution, source: layers.rivers });
await createLakesLayer({ bounds, name, resolution, source: layers.lakes });
await createSubunitsLayer({ bounds, name, resolution, source: layers.subunits });
await convertLayersToTopojson({ name, resolution });
}
}
21 changes: 21 additions & 0 deletions tasks/topojson/world_rectangle.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-180, -90],
[180, -90],
[180, 90],
[-180, 90],
[-180,-90]
]
]
}
}
]
}
15 changes: 1 addition & 14 deletions tasks/util/constants.js
Original file line number Diff line number Diff line change
@@ -24,20 +24,7 @@ var strictIndex = fs.readFileSync(pathToPlotlyStrict, 'utf-8');
var allTraces = fs.readdirSync(path.join(pathToSrc, 'traces'))
.filter(startsWithLowerCase);

var pathToTopojsonSrc;
try {
pathToTopojsonSrc = path.join(path.dirname(require.resolve('sane-topojson')), 'dist/');
} catch(e) {
console.log([
'',
'WARN: Cannot resolve path to *sane-topojson* package.',
' This can happen when one `npm link sane-topojson`',
' and runs a command in a Docker container.',
' There is nothing to worry, if you see this warning while running',
' `npm run test-image`, `npm run test-export` or `npm run baseline` ;)',
''
].join('\n'));
}
var pathToTopojsonSrc = path.join(pathToDist, 'topojson/');

var partialBundleNames = [
'basic', 'cartesian', 'geo', 'gl3d', 'gl2d', 'mapbox', 'finance', 'strict'
5 changes: 2 additions & 3 deletions test/jasmine/karma.conf.js
Original file line number Diff line number Diff line change
@@ -118,7 +118,7 @@ if(isFullSuite) {
}

var pathToCustomMatchers = path.join(__dirname, 'assets', 'custom_matchers.js');
var pathToSaneTopojsonDist = path.join(__dirname, '..', '..', 'node_modules', 'sane-topojson', 'dist');
var pathToTopojsonDist = path.join(__dirname, '..', '..', 'dist', 'topojson');
var pathToMathJax2 = path.join(__dirname, '..', '..', 'node_modules', '@plotly/mathjax-v2');
var pathToMathJax3 = path.join(__dirname, '..', '..', 'node_modules', '@plotly/mathjax-v3');
var pathToVirtualWebgl = path.join(__dirname, '..', '..', 'node_modules', 'virtual-webgl', 'src', 'virtual-webgl.js');
@@ -192,8 +192,7 @@ func.defaultConfig = {
// more info: http://karma-runner.github.io/3.0/config/files.html
{pattern: pathToMathJax2 + '/**', included: false, watched: false, served: true},
{pattern: pathToMathJax3 + '/**', included: false, watched: false, served: true},
// available to fetch from /base/node_modules/sane-topojson/dist/
{pattern: pathToSaneTopojsonDist + '/**', included: false, watched: false, served: true}
{pattern: pathToTopojsonDist + '/**', included: false, watched: false, served: true}
],

// list of files / pattern to exclude
30 changes: 15 additions & 15 deletions test/jasmine/tests/geo_test.js
Original file line number Diff line number Diff line change
@@ -23,9 +23,7 @@ var DBLCLICKDELAY = require('../../../src/plot_api/plot_config').dfltConfig.doub
var HOVERMINTIME = require('../../../src/components/fx').constants.HOVERMINTIME;

// use local topojson files
Plotly.setPlotConfig({
topojsonURL: '/base/node_modules/sane-topojson/dist/'
});
Plotly.setPlotConfig({ topojsonURL: '/base/dist/topojson/' });

function move(fromX, fromY, toX, toY, delay) {
return new Promise(function(resolve) {
@@ -1484,7 +1482,7 @@ describe('Test geo interactions', function() {
})
.then(function() {
check([179, -16.6], 1, 'spot on Fiji island that cross antimeridian west of antimeridian');
check([-179.9, -16.2], 1, 'spot on Fiji island that cross antimeridian east of antimeridian');
check([-179.9, -16.8], 1, 'spot on Fiji island that cross antimeridian east of antimeridian');

return Plotly.relayout(gd, {
'geo.center.lat': null,
@@ -1798,8 +1796,8 @@ describe('Test event property of interactions on a geo plot:', function() {
expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber');
expect(typeof pt.data).toEqual(typeof {}, 'points[0].data');
expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData');
expect(pt.lat).toEqual(57.75, 'points[0].lat');
expect(pt.lon).toEqual(-101.57, 'points[0].lon');
expect(pt.lat).toEqual(57.72, 'points[0].lat');
expect(pt.lon).toEqual(-101.68, 'points[0].lon');
expect(pt.location).toEqual('CAN', 'points[0].location');
expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber');
expect(pt.text).toEqual(20, 'points[0].text');
@@ -1902,8 +1900,8 @@ describe('Test event property of interactions on a geo plot:', function() {
expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber');
expect(typeof pt.data).toEqual(typeof {}, 'points[0].data');
expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData');
expect(pt.lat).toEqual(57.75, 'points[0].lat');
expect(pt.lon).toEqual(-101.57, 'points[0].lon');
expect(pt.lat).toEqual(57.72, 'points[0].lat');
expect(pt.lon).toEqual(-101.68, 'points[0].lon');
expect(pt.location).toEqual('CAN', 'points[0].location');
expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber');
expect(pt.text).toEqual(20, 'points[0].text');
@@ -1943,8 +1941,8 @@ describe('Test event property of interactions on a geo plot:', function() {
expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber');
expect(typeof pt.data).toEqual(typeof {}, 'points[0].data');
expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData');
expect(pt.lat).toEqual(57.75, 'points[0].lat');
expect(pt.lon).toEqual(-101.57, 'points[0].lon');
expect(pt.lat).toEqual(57.72, 'points[0].lat');
expect(pt.lon).toEqual(-101.68, 'points[0].lon');
expect(pt.location).toEqual('CAN', 'points[0].location');
expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber');
expect(pt.text).toEqual(20, 'points[0].text');
@@ -2626,7 +2624,8 @@ describe('Test geo zoom/pan/drag interactions:', function() {
_assert('after scroll', [
[-94.5, 35.0], 1.3
], [
[387.1, 245.9], 974.4
// TODO: Verify that this change is acceptable
[380.5, 245.9], 974.4
], [
'geo.center.lon', 'geo.center.lon', 'geo.projection.scale'
]);
@@ -2636,8 +2635,9 @@ describe('Test geo zoom/pan/drag interactions:', function() {
_assert('after some relayout call that causes a replot', [
[-94.5, 35.0], 1.3
], [
// TODO: Verify that this change is acceptable
// new center values are reflected in translate()
[387.1, 245.9], 974.4
[380.5, 245.9], 974.4
], [
'geo.showlakes'
]);
@@ -2759,11 +2759,11 @@ describe('Test geo interactions update marker angles:', function() {
})
.then(function() {
newPath = getPath();
expect(newPath).toEqual('M0,0L18.238949470790537,8.206139299448276L19.579067739888885,-4.081679364776507Z');
expect(newPath).toEqual('M0,0L18.22327727600463,8.240883770679762L19.586810955756498,-4.044358612123453Z');

expect(newPath).not.toEqual(initialPath);
expect(newPath).toEqual('M0,0L18.238949470790537,8.206139299448276L19.579067739888885,-4.081679364776507Z');
expect(initialPath).toEqual('M0,0L-1.5033314641545745,19.94341982983066L10.506227353572104,17.01820163222463Z');
expect(newPath).toEqual('M0,0L18.22327727600463,8.240883770679762L19.586810955756498,-4.044358612123453Z');
expect(initialPath).toEqual('M0,0L-1.5100144203478312,19.942914943667123L10.500523963798084,17.021721313830223Z');
})
.then(done, done.fail);
});