diff --git a/.gitignore b/.gitignore
index ee3a868..180354f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
node_modules/
test.js
*.svg
-dist/
\ No newline at end of file
+/index.js
+/without-countries.js
diff --git a/.prettierrc.json b/.prettierrc.json
index 28920ea..2db7aff 100644
--- a/.prettierrc.json
+++ b/.prettierrc.json
@@ -5,5 +5,5 @@
"singleQuote": true,
"arrowParens": "always",
"bracketSpacing": true,
- "printWidth": 120
+ "printWidth": 80
}
diff --git a/index.d.ts b/index.d.ts
index 0a9d28c..6a8558f 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -4,12 +4,15 @@ namespace DottedMapLib {
lng: { min: number; max: number };
}
- interface Settings {
+ interface MapSettings {
height?: number;
width?: number;
countries?: string[];
region?: Region;
grid?: 'vertical' | 'diagonal';
+ }
+
+ interface Settings extends MapSettings {
avoidOuterPins?: false | true;
}
@@ -40,6 +43,8 @@ namespace DottedMapLib {
};
}
+export const getMapJSON: (settings: DottedMapLib.MapSettings) => string;
+
export default class DottedMap {
constructor(settings: DottedMapLib.Settings);
diff --git a/package-lock.json b/package-lock.json
index 505e62f..093fc5e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,11 +1,11 @@
{
"name": "dotted-map",
- "version": "2.0.0",
+ "version": "2.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "version": "2.0.0",
+ "version": "2.1.0",
"license": "MIT",
"dependencies": {
"@turf/boolean-point-in-polygon": "^6.0.1",
@@ -69,9 +69,9 @@
}
},
"node_modules/@types/estree": {
- "version": "0.0.46",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz",
- "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==",
+ "version": "0.0.47",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.47.tgz",
+ "integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==",
"dev": true
},
"node_modules/@types/json-schema": {
@@ -327,14 +327,14 @@
}
},
"node_modules/browserslist": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.4.tgz",
- "integrity": "sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==",
+ "version": "4.16.5",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.5.tgz",
+ "integrity": "sha512-C2HAjrM1AI/djrpAUU/tr4pml1DqLIzJKSLDBXBrNErl9ZCCTXdhwxdJjYc16953+mBWf7Lw+uUJgpgb8cN71A==",
"dev": true,
"dependencies": {
- "caniuse-lite": "^1.0.30001208",
+ "caniuse-lite": "^1.0.30001214",
"colorette": "^1.2.2",
- "electron-to-chromium": "^1.3.712",
+ "electron-to-chromium": "^1.3.719",
"escalade": "^3.1.1",
"node-releases": "^1.1.71"
},
@@ -356,9 +356,9 @@
"dev": true
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001209",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001209.tgz",
- "integrity": "sha512-2Ktt4OeRM7EM/JaOZjuLzPYAIqmbwQMNnYbgooT+icoRGrKOyAxA1xhlnotBD1KArRSPsuJp3TdYcZYrL7qNxA==",
+ "version": "1.0.30001214",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001214.tgz",
+ "integrity": "sha512-O2/SCpuaU3eASWVaesQirZv1MSjUNOvmugaD8zNSJqw6Vv5SGwoOpA9LJs3pNPfM745nxqPvfZY3MQKY4AKHYg==",
"dev": true
},
"node_modules/chrome-trace-event": {
@@ -411,15 +411,15 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.3.717",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.717.tgz",
- "integrity": "sha512-OfzVPIqD1MkJ7fX+yTl2nKyOE4FReeVfMCzzxQS+Kp43hZYwHwThlGP+EGIZRXJsxCM7dqo8Y65NOX/HP12iXQ==",
+ "version": "1.3.720",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.720.tgz",
+ "integrity": "sha512-B6zLTxxaOFP4WZm6DrvgRk8kLFYWNhQ5TrHMC0l5WtkMXhU5UbnvWoTfeEwqOruUSlNMhVLfYak7REX6oC5Yfw==",
"dev": true
},
"node_modules/enhanced-resolve": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz",
- "integrity": "sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw==",
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.0.tgz",
+ "integrity": "sha512-Sl3KRpJA8OpprrtaIswVki3cWPiPKxXuFxJXBp+zNb6s6VwNWwFRUdtmzd2ReUut8n+sCPx7QCtQ7w5wfJhSgQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.4",
@@ -1235,20 +1235,20 @@
}
},
"node_modules/webpack": {
- "version": "5.33.2",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.33.2.tgz",
- "integrity": "sha512-X4b7F1sYBmJx8mlh2B7mV5szEkE0jYNJ2y3akgAP0ERi0vLCG1VvdsIxt8lFd4st6SUy0lf7W0CCQS566MBpJg==",
+ "version": "5.35.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.35.0.tgz",
+ "integrity": "sha512-au3gu55yYF/h6NXFr0KZPZAYxS6Nlc595BzYPke8n0CSff5WXcoixtjh5LC/8mXunkRKxhymhXmBY0+kEbR6jg==",
"dev": true,
"dependencies": {
"@types/eslint-scope": "^3.7.0",
- "@types/estree": "^0.0.46",
+ "@types/estree": "^0.0.47",
"@webassemblyjs/ast": "1.11.0",
"@webassemblyjs/wasm-edit": "1.11.0",
"@webassemblyjs/wasm-parser": "1.11.0",
"acorn": "^8.0.4",
"browserslist": "^4.14.5",
"chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^5.7.0",
+ "enhanced-resolve": "^5.8.0",
"es-module-lexer": "^0.4.0",
"eslint-scope": "^5.1.1",
"events": "^3.2.0",
@@ -1449,9 +1449,9 @@
}
},
"@types/estree": {
- "version": "0.0.46",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz",
- "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==",
+ "version": "0.0.47",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.47.tgz",
+ "integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==",
"dev": true
},
"@types/json-schema": {
@@ -1679,14 +1679,14 @@
"dev": true
},
"browserslist": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.4.tgz",
- "integrity": "sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==",
+ "version": "4.16.5",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.5.tgz",
+ "integrity": "sha512-C2HAjrM1AI/djrpAUU/tr4pml1DqLIzJKSLDBXBrNErl9ZCCTXdhwxdJjYc16953+mBWf7Lw+uUJgpgb8cN71A==",
"dev": true,
"requires": {
- "caniuse-lite": "^1.0.30001208",
+ "caniuse-lite": "^1.0.30001214",
"colorette": "^1.2.2",
- "electron-to-chromium": "^1.3.712",
+ "electron-to-chromium": "^1.3.719",
"escalade": "^3.1.1",
"node-releases": "^1.1.71"
}
@@ -1698,9 +1698,9 @@
"dev": true
},
"caniuse-lite": {
- "version": "1.0.30001209",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001209.tgz",
- "integrity": "sha512-2Ktt4OeRM7EM/JaOZjuLzPYAIqmbwQMNnYbgooT+icoRGrKOyAxA1xhlnotBD1KArRSPsuJp3TdYcZYrL7qNxA==",
+ "version": "1.0.30001214",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001214.tgz",
+ "integrity": "sha512-O2/SCpuaU3eASWVaesQirZv1MSjUNOvmugaD8zNSJqw6Vv5SGwoOpA9LJs3pNPfM745nxqPvfZY3MQKY4AKHYg==",
"dev": true
},
"chrome-trace-event": {
@@ -1744,15 +1744,15 @@
}
},
"electron-to-chromium": {
- "version": "1.3.717",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.717.tgz",
- "integrity": "sha512-OfzVPIqD1MkJ7fX+yTl2nKyOE4FReeVfMCzzxQS+Kp43hZYwHwThlGP+EGIZRXJsxCM7dqo8Y65NOX/HP12iXQ==",
+ "version": "1.3.720",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.720.tgz",
+ "integrity": "sha512-B6zLTxxaOFP4WZm6DrvgRk8kLFYWNhQ5TrHMC0l5WtkMXhU5UbnvWoTfeEwqOruUSlNMhVLfYak7REX6oC5Yfw==",
"dev": true
},
"enhanced-resolve": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz",
- "integrity": "sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw==",
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.0.tgz",
+ "integrity": "sha512-Sl3KRpJA8OpprrtaIswVki3cWPiPKxXuFxJXBp+zNb6s6VwNWwFRUdtmzd2ReUut8n+sCPx7QCtQ7w5wfJhSgQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.4",
@@ -2357,20 +2357,20 @@
}
},
"webpack": {
- "version": "5.33.2",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.33.2.tgz",
- "integrity": "sha512-X4b7F1sYBmJx8mlh2B7mV5szEkE0jYNJ2y3akgAP0ERi0vLCG1VvdsIxt8lFd4st6SUy0lf7W0CCQS566MBpJg==",
+ "version": "5.35.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.35.0.tgz",
+ "integrity": "sha512-au3gu55yYF/h6NXFr0KZPZAYxS6Nlc595BzYPke8n0CSff5WXcoixtjh5LC/8mXunkRKxhymhXmBY0+kEbR6jg==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.0",
- "@types/estree": "^0.0.46",
+ "@types/estree": "^0.0.47",
"@webassemblyjs/ast": "1.11.0",
"@webassemblyjs/wasm-edit": "1.11.0",
"@webassemblyjs/wasm-parser": "1.11.0",
"acorn": "^8.0.4",
"browserslist": "^4.14.5",
"chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^5.7.0",
+ "enhanced-resolve": "^5.8.0",
"es-module-lexer": "^0.4.0",
"eslint-scope": "^5.1.1",
"events": "^3.2.0",
diff --git a/package.json b/package.json
index 5d4e478..182ef67 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,13 @@
{
"name": "dotted-map",
- "version": "2.0.0",
+ "version": "2.1.1",
"description": "Create a SVG map filled with dots for the world or countries",
- "main": "dist/dotted-map.js",
- "module": "src/index.js",
+ "main": "./index.js",
+ "exports": {
+ ".": "./index.js",
+ "./without-countries": "./without-countries.js"
+ },
+ "module": "./src/with-countries.js",
"scripts": {
"build": "webpack"
},
diff --git a/src/index-with-countries.js b/src/index-with-countries.js
deleted file mode 100644
index e69de29..0000000
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index 6263f55..0000000
--- a/src/index.js
+++ /dev/null
@@ -1,186 +0,0 @@
-import proj4 from 'proj4';
-import inside from '@turf/boolean-point-in-polygon';
-import geojsonWorld from './countries.geo.json';
-
-// TODO
-// [ ] Handle prefill cache →
-// [ ] Method to export a cache
-// [ ] Method to import a cache
-// [ ] Make two exports: one with countries, one without (reduce bundle size)
-
-const geojsonByCountry = geojsonWorld.features.reduce((countries, feature) => {
- countries[feature.id] = feature;
- return countries;
-}, {});
-
-const geojsonToMultiPolygons = (geojson) => {
- const coordinates = geojson.features.reduce(
- (poly, feature) =>
- poly.concat(feature.geometry.type === 'Polygon' ? [feature.geometry.coordinates] : feature.geometry.coordinates),
- [],
- );
- return { type: 'Feature', geometry: { type: 'MultiPolygon', coordinates } };
-};
-
-const CACHE = {};
-
-const DEFAULT_WORLD_REGION = {
- lat: { min: -56, max: 71 },
- 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, grid = 'vertical', avoidOuterPins = false }) {
- 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 poly = geojsonToMultiPolygons(geojson);
-
- const cacheKey = [JSON.stringify(region), grid, height, width, JSON.stringify(countries)].join(' ');
-
- 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 = { ...CACHE[cacheKey] } || {};
- const ystep = grid === 'diagonal' ? Math.sqrt(3) / 2 : 1;
-
- if (!CACHE[cacheKey]) {
- for (let y = 0; y * ystep < height; y += 1) {
- for (let x = 0; x < width; x += 1) {
- const localx = y % 2 === 0 && grid === 'diagonal' ? x + 0.5 : x;
- const localy = y * ystep;
-
- const pointGoogle = [(localx / width) * X_RANGE + X_MIN, Y_MAX - (localy / height) * Y_RANGE];
- const wgs84Point = proj4(proj4.defs('GOOGLE'), proj4.defs('WGS84'), pointGoogle);
-
- if (inside(wgs84Point, poly)) {
- points[[x, y].join(';')] = { x: localx, y: localy };
- }
- }
- }
-
- CACHE[cacheKey] = { ...points };
- }
-
- return {
- addPin({ lat, lng, data, svgOptions }) {
- const [googleX, googleY] = proj4(proj4.defs('GOOGLE'), [lng, lat]);
- if (avoidOuterPins) {
- const wgs84Point = proj4(proj4.defs('GOOGLE'), proj4.defs('WGS84'), [googleX, googleY]);
- if (!inside(wgs84Point, poly)) return;
- }
- let [rawX, rawY] = [(width * (googleX - X_MIN)) / X_RANGE, (height * (Y_MAX - googleY)) / Y_RANGE];
- const y = Math.round(rawY / ystep);
- if (y % 2 === 0 && grid === 'diagonal') {
- rawX -= 0.5;
- }
- const x = Math.round(rawX);
- let [localx, localy] = [x, Math.round(y) * ystep];
- if (y % 2 === 0 && grid === 'diagonal') {
- localx += 0.5;
- }
-
- const point = { x: localx, y: localy, data, svgOptions };
-
- points[[x, y].join(';')] = point;
-
- return point;
- },
- getPoints() {
- return Object.values(points);
- },
- getSVG({ shape = 'circle', color = 'current', backgroundColor = 'transparent', radius = 0.5 }) {
- const getPoint = ({ x, y, svgOptions = {} }) => {
- const pointRadius = svgOptions.radius || radius;
- if (shape === 'circle') {
- return ``;
- } else if (shape === 'hexagon') {
- const sqrt3radius = Math.sqrt(3) * pointRadius;
-
- const polyPoints = [
- [x + sqrt3radius, y - pointRadius],
- [x + sqrt3radius, y + pointRadius],
- [x, y + 2 * pointRadius],
- [x - sqrt3radius, y + pointRadius],
- [x - sqrt3radius, y - pointRadius],
- [x, y - 2 * pointRadius],
- ];
-
- return ``;
- }
- };
-
- return ``;
- },
- image: {
- region,
- width,
- height,
- },
- };
-}
-
-export default DottedMap;
diff --git a/src/with-countries.js b/src/with-countries.js
new file mode 100644
index 0000000..c0a5032
--- /dev/null
+++ b/src/with-countries.js
@@ -0,0 +1,180 @@
+import proj4 from 'proj4';
+import inside from '@turf/boolean-point-in-polygon';
+import geojsonWorld from './countries.geo.json';
+import DottedMapWithoutCountries from './without-countries';
+
+const geojsonByCountry = geojsonWorld.features.reduce((countries, feature) => {
+ countries[feature.id] = feature;
+ return countries;
+}, {});
+
+const geojsonToMultiPolygons = (geojson) => {
+ const coordinates = geojson.features.reduce(
+ (poly, feature) =>
+ poly.concat(
+ feature.geometry.type === 'Polygon'
+ ? [feature.geometry.coordinates]
+ : feature.geometry.coordinates,
+ ),
+ [],
+ );
+ return { type: 'Feature', geometry: { type: 'MultiPolygon', coordinates } };
+};
+
+const CACHE = {};
+
+const DEFAULT_WORLD_REGION = {
+ lat: { min: -56, max: 71 },
+ 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}`);
+ }
+};
+
+const getMap = ({
+ height = 0,
+ width = 0,
+ countries = [],
+ region,
+ grid = 'vertical',
+}) => {
+ 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 poly = geojsonToMultiPolygons(geojson);
+
+ 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 = {};
+ const ystep = grid === 'diagonal' ? Math.sqrt(3) / 2 : 1;
+
+ for (let y = 0; y * ystep < height; y += 1) {
+ for (let x = 0; x < width; x += 1) {
+ const localx = y % 2 === 0 && grid === 'diagonal' ? x + 0.5 : x;
+ const localy = y * ystep;
+
+ const pointGoogle = [
+ (localx / width) * X_RANGE + X_MIN,
+ Y_MAX - (localy / height) * Y_RANGE,
+ ];
+ const wgs84Point = proj4(
+ proj4.defs('GOOGLE'),
+ proj4.defs('WGS84'),
+ pointGoogle,
+ );
+
+ if (inside(wgs84Point, poly)) {
+ points[[x, y].join(';')] = { x: localx, y: localy };
+ }
+ }
+ }
+
+ return {
+ points,
+ X_MIN,
+ Y_MIN,
+ X_MAX,
+ Y_MAX,
+ X_RANGE,
+ Y_RANGE,
+ region,
+ grid,
+ height,
+ width,
+ ystep,
+ };
+};
+
+export const getMapJSON = (props) => JSON.stringify(getMap(props));
+
+const getCacheKey = ({
+ height = 0,
+ width = 0,
+ countries = [],
+ region,
+ grid = 'vertical',
+}) => {
+ return [
+ JSON.stringify(region),
+ grid,
+ height,
+ width,
+ JSON.stringify(countries),
+ ].join(' ');
+};
+
+function DottedMap({ avoidOuterPins = false, ...args }) {
+ const cacheKey = getCacheKey(args);
+ const map = CACHE[cacheKey] || getMap(args);
+
+ return new DottedMapWithoutCountries({ avoidOuterPins, map });
+}
+
+export default DottedMap;
diff --git a/src/without-countries.js b/src/without-countries.js
new file mode 100644
index 0000000..55b1351
--- /dev/null
+++ b/src/without-countries.js
@@ -0,0 +1,93 @@
+import proj4 from 'proj4';
+import inside from '@turf/boolean-point-in-polygon';
+
+function DottedMapWithoutCountries({ map, avoidOuterPins = false }) {
+ const {
+ points,
+ X_MIN,
+ Y_MAX,
+ X_RANGE,
+ Y_RANGE,
+ region,
+ grid,
+ width,
+ height,
+ ystep,
+ } = map;
+
+ return {
+ addPin({ lat, lng, data, svgOptions }) {
+ const [googleX, googleY] = proj4(proj4.defs('GOOGLE'), [lng, lat]);
+ if (avoidOuterPins) {
+ const wgs84Point = proj4(proj4.defs('GOOGLE'), proj4.defs('WGS84'), [
+ googleX,
+ googleY,
+ ]);
+ if (!inside(wgs84Point, poly)) return;
+ }
+ let [rawX, rawY] = [
+ (width * (googleX - X_MIN)) / X_RANGE,
+ (height * (Y_MAX - googleY)) / Y_RANGE,
+ ];
+ const y = Math.round(rawY / ystep);
+ if (y % 2 === 0 && grid === 'diagonal') {
+ rawX -= 0.5;
+ }
+ const x = Math.round(rawX);
+ let [localx, localy] = [x, Math.round(y) * ystep];
+ if (y % 2 === 0 && grid === 'diagonal') {
+ localx += 0.5;
+ }
+
+ const point = { x: localx, y: localy, data, svgOptions };
+
+ points[[x, y].join(';')] = point;
+
+ return point;
+ },
+ getPoints() {
+ return Object.values(points);
+ },
+ getSVG({
+ shape = 'circle',
+ color = 'current',
+ backgroundColor = 'transparent',
+ radius = 0.5,
+ }) {
+ const getPoint = ({ x, y, svgOptions = {} }) => {
+ const pointRadius = svgOptions.radius || radius;
+ if (shape === 'circle') {
+ return ``;
+ } else if (shape === 'hexagon') {
+ const sqrt3radius = Math.sqrt(3) * pointRadius;
+
+ const polyPoints = [
+ [x + sqrt3radius, y - pointRadius],
+ [x + sqrt3radius, y + pointRadius],
+ [x, y + 2 * pointRadius],
+ [x - sqrt3radius, y + pointRadius],
+ [x - sqrt3radius, y - pointRadius],
+ [x, y - 2 * pointRadius],
+ ];
+
+ return ``;
+ }
+ };
+
+ return ``;
+ },
+ image: {
+ region,
+ width,
+ height,
+ },
+ };
+}
+
+export default DottedMapWithoutCountries;
diff --git a/webpack.config.js b/webpack.config.js
index 18fe99d..cdfaa5f 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,16 +1,37 @@
const path = require('path');
+const fs = require('fs');
+
+class CleanOutput {
+ apply(compiler) {
+ compiler.hooks.thisCompilation.tap('CleanOutput', () => {
+ if (fs.existsSync('./index.js')) {
+ fs.rmSync('./index.js');
+ }
+ if (fs.existsSync('./without-countries.js')) {
+ fs.rmSync('./without-countries.js');
+ }
+ });
+ }
+}
module.exports = {
- entry: './src/index.js',
+ entry: {
+ index: './src/with-countries.js',
+ 'without-countries': './src/without-countries.js',
+ },
mode: 'production',
output: {
- path: path.resolve(__dirname, 'dist'),
- filename: 'dotted-map.js',
+ path: path.resolve(__dirname),
+ filename: '[name].js',
library: {
name: 'dotted-map',
type: 'umd',
},
globalObject: 'this',
- clean: true,
},
+ externals: {
+ '@turf/boolean-point-in-polygon': '@turf/boolean-point-in-polygon',
+ proj4: 'proj4',
+ },
+ plugins: [new CleanOutput()],
};
diff --git a/without-countries.d.ts b/without-countries.d.ts
new file mode 100644
index 0000000..6fdb6ce
--- /dev/null
+++ b/without-countries.d.ts
@@ -0,0 +1,65 @@
+namespace DottedMapWithoutCountriesLib {
+ interface Region {
+ lat: { min: number; max: number };
+ lng: { min: number; max: number };
+ }
+
+ interface Map {
+ points: Point[];
+ X_MIN: number;
+ Y_MAX: number;
+ X_RANGE: number;
+ Y_RANGE: number;
+ region: Region;
+ grid: 'vertical' | 'diagonal';
+ width: number;
+ height: number;
+ ystep: number;
+ }
+
+ interface Settings extends MapSettings {
+ map: Map;
+ avoidOuterPins?: false | true;
+ }
+
+ interface SvgOptions {
+ color?: string;
+ radius?: number;
+ }
+
+ interface Pin {
+ lat: number;
+ lng: number;
+ data?: any;
+ svgOptions?: SvgOptions;
+ }
+
+ interface SvgSettings {
+ shape?: 'circle' | 'hexagon';
+ color?: string;
+ backgroundColor?: string;
+ radius?: number;
+ }
+
+ type Point = {
+ x: number;
+ y: number;
+ data?: any;
+ svgOptions?: SvgOptions;
+ };
+}
+
+export default class DottedMapWithoutCountries {
+ constructor(settings: DottedMapWithoutCountriesLib.Settings);
+
+ addPin(
+ pin: DottedMapWithoutCountriesLib.Pin,
+ ): DottedMapWithoutCountriesLib.Point;
+ getPoints(): DottedMapWithoutCountriesLib.Point[];
+ getSVG(settings: DottedMapWithoutCountriesLib.SvgSettings): string;
+ image: {
+ region: DottedMap.Region;
+ width: number;
+ height: number;
+ };
+}