Init commit
Generate a world/countries map, add simple pins, svg with circles
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
*.svg
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "always",
|
||||
"bracketSpacing": true,
|
||||
"printWidth": 120
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# dotted-map-generator
|
||||
|
||||
## Specs
|
||||
|
||||
```js
|
||||
const map = new DottedMap({
|
||||
height,
|
||||
width, // (one of both if enough)
|
||||
countries: ['FRA', 'DEU'] // if not present, whole world is used
|
||||
region: { lat: {min, max}, lng: {min, max} }, // if not present, it fits the countries or the world
|
||||
})
|
||||
|
||||
// → it will cache the points array
|
||||
DottedMap.clearCache()
|
||||
|
||||
map.addPin({
|
||||
lat,
|
||||
lng,
|
||||
svgOptions: { color, radius },
|
||||
data, // whatever you want
|
||||
})
|
||||
|
||||
map.getPoints(): [{ x, y, data }]
|
||||
|
||||
map.getSVG({
|
||||
shape: 'circle' | 'hexagon' | 'square', // custom path is possible
|
||||
color,
|
||||
backgroundColor, // transparent is possible
|
||||
radius: 0.5,
|
||||
})
|
||||
```
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,117 @@
|
||||
const fs = require('fs');
|
||||
const proj4 = require('proj4');
|
||||
const inside = require('point-in-geopolygon');
|
||||
|
||||
const geojsonWorld = JSON.parse(fs.readFileSync('./countries.geo.json', { encoding: 'utf8' }));
|
||||
const geojsonByCountry = geojsonWorld.features.reduce((countries, feature) => {
|
||||
countries[feature.id] = feature;
|
||||
return countries;
|
||||
}, {});
|
||||
|
||||
const DEFAULT_WORLD_REGION = {
|
||||
lat: { min: -65, max: 78 },
|
||||
lng: { min: -179, max: 179 },
|
||||
};
|
||||
|
||||
const computeGeojsonBox = (geojson) => {
|
||||
const { type, features, geometry, coordinates } = geojson;
|
||||
if (type === 'FeatureCollection') {
|
||||
const boxes = features.map(computeGeojsonBox);
|
||||
return {
|
||||
lat: {
|
||||
min: Math.min(...boxes.map((box) => box.lat.min)),
|
||||
max: Math.max(...boxes.map((box) => box.lat.max)),
|
||||
},
|
||||
lng: {
|
||||
min: Math.min(...boxes.map((box) => box.lng.min)),
|
||||
max: Math.max(...boxes.map((box) => box.lng.max)),
|
||||
},
|
||||
};
|
||||
} else if (type == 'Feature') {
|
||||
return computeGeojsonBox(geometry);
|
||||
} else if (type === 'MultiPolygon') {
|
||||
return computeGeojsonBox({ type: 'Polygon', coordinates: coordinates.flat() });
|
||||
} else if (type == 'Polygon') {
|
||||
const coords = coordinates.flat();
|
||||
const latitudes = coords.map(([_lng, lat]) => lat);
|
||||
const longitudes = coords.map(([lng, _lat]) => lng);
|
||||
|
||||
return {
|
||||
lat: {
|
||||
min: Math.min(...latitudes),
|
||||
max: Math.max(...latitudes),
|
||||
},
|
||||
lng: {
|
||||
min: Math.min(...longitudes),
|
||||
max: Math.max(...longitudes),
|
||||
},
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Unknown geojson type ${type}`);
|
||||
}
|
||||
};
|
||||
|
||||
function DottedMap({ height = 0, width = 0, countries = [], region }) {
|
||||
if (height <= 0 && width <= 0) {
|
||||
throw new Error('height or width is required');
|
||||
}
|
||||
|
||||
let geojson = geojsonWorld;
|
||||
if (countries.length > 0) {
|
||||
geojson = {
|
||||
type: 'FeatureCollection',
|
||||
features: countries.map((country) => geojsonByCountry[country]),
|
||||
};
|
||||
if (!region) {
|
||||
region = computeGeojsonBox(geojson);
|
||||
}
|
||||
} else if (!region) {
|
||||
region = DEFAULT_WORLD_REGION;
|
||||
}
|
||||
|
||||
const [X_MIN, Y_MIN] = proj4(proj4.defs('GOOGLE'), [region.lng.min, region.lat.min]);
|
||||
const [X_MAX, Y_MAX] = proj4(proj4.defs('GOOGLE'), [region.lng.max, region.lat.max]);
|
||||
const X_RANGE = X_MAX - X_MIN;
|
||||
const Y_RANGE = Y_MAX - Y_MIN;
|
||||
|
||||
if (width <= 0) {
|
||||
width = Math.round((height * X_RANGE) / Y_RANGE);
|
||||
} else if (height <= 0) {
|
||||
height = Math.round((width * Y_RANGE) / X_RANGE);
|
||||
}
|
||||
|
||||
const points = {};
|
||||
|
||||
for (let x = 0; x < width; x += 1) {
|
||||
for (let y = 0; y < height; y += 1) {
|
||||
const pointGoogle = [(x / width) * X_RANGE + X_MIN, Y_MAX - (y / height) * Y_RANGE];
|
||||
const wgs84Point = proj4(proj4.defs('GOOGLE'), proj4.defs('WGS84'), pointGoogle);
|
||||
if (inside.feature(geojson, wgs84Point) !== -1) {
|
||||
points[[x, y].join(';')] = { x, y };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
addPin({ lat, lng, data, svgOptions }) {
|
||||
const [rawX, rawY] = proj4(proj4.defs('GOOGLE'), [lng, lat]);
|
||||
const [x, y] = [Math.round((width * (rawX - X_MIN)) / X_RANGE), Math.round((height * (Y_MAX - rawY)) / Y_RANGE)];
|
||||
points[[x, y].join(';')] = { x, y, data, svgOptions };
|
||||
},
|
||||
getPoints() {
|
||||
return Object.values(points);
|
||||
},
|
||||
getSVG({ shape, color = 'current', backgroundColor, radius = 0.5 }) {
|
||||
return `<svg viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">
|
||||
${Object.values(points)
|
||||
.map(
|
||||
({ x, y, svgOptions = {} }) =>
|
||||
`<circle cx="${x}" cy="${y}" r="${svgOptions.radius || radius}" fill="${svgOptions.color || color}" />`,
|
||||
)
|
||||
.join('\n')}
|
||||
</svg>`;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = DottedMap;
|
||||
Generated
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "dotted-map-generator",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"mgrs": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mgrs/-/mgrs-1.0.0.tgz",
|
||||
"integrity": "sha1-+5FYjnjJACVnI5XLQLJffNatGCk="
|
||||
},
|
||||
"point-in-geopolygon": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/point-in-geopolygon/-/point-in-geopolygon-1.0.1.tgz",
|
||||
"integrity": "sha1-InEbCCna4qpXyTk/Mw2//jEfb90="
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz",
|
||||
"integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==",
|
||||
"dev": true
|
||||
},
|
||||
"proj4": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/proj4/-/proj4-2.6.1.tgz",
|
||||
"integrity": "sha512-RP5EcrfrLcARy+Zjjz1wIeqZzZdPtQNl685asHcwdU/MQ/dvydmf1XWM4mgok6wPaNsXZ8IFrM4qadO3g46PiQ==",
|
||||
"requires": {
|
||||
"mgrs": "1.0.0",
|
||||
"wkt-parser": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"wkt-parser": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.2.4.tgz",
|
||||
"integrity": "sha512-ZzKnc7ml/91fOPh5bANBL4vUlWPIYYv11waCtWTkl2TRN+LEmBg60Q1MA8gqV4hEp4MGfSj9JiHz91zw/gTDXg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "dotted-map-generator",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"point-in-geopolygon": "^1.0.1",
|
||||
"proj4": "^2.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^2.0.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
const DottedMap = require('./index.js');
|
||||
|
||||
const map = new DottedMap({ height: 100, countries: ['FRA'] });
|
||||
|
||||
map.addPin({ lat: 48.85, lng: 2.35, svgOptions: { color: 'red', radius: 0.45 } });
|
||||
// map.addPin({ lat: 47.65, lng: -2.76, svgOptions: { color: 'red', radius: 0.5 } });
|
||||
|
||||
console.log(map.getSVG({ radius: 0.3, color: 'grey' }));
|
||||
Reference in New Issue
Block a user