I am using SVG elements to render this world map in my web app. SVG structure is classic :
<svg id="all_countries">
<path
id="india"
className="st0" // st0 is the default class when exporting svg from Adobe Illustrator
d="M666.8556,380.5002l0.518,0.3002l-0.0375,-0.0375-0.0891l-0.201-0.1823l0" // country svg drawing
/>
<path
id="china"
className="st0"
d="M680.55626,380.402l0.518,0.3002l-0.0375,-0.075-0.0591l-0.301-0.3484l2"
/>
// more countries like Nepal, Kazakhstan, Mongolia, Pakistan etc ...
</svg>
For every country : I am looking for a way to find the border countries. In the following example, all countries are stored in database and have properties :
svgID: "india",
prettyName: "Republic Of India",
population: "1393000000",
currency: "₹",
school: {
forKids: "free",
forAdults: "free",
},
I need to add a borderCountries property :
borderCountries: [
"new ObjectId("2482842848451")", // represents Pakistan
"new ObjectId("5848564841232")", // represents Nepal
"new ObjectId("9645485455612")", // represents China
]
I thought about adding them manually to database but (later) I'm adding regions inside countries, and that would be too much manual work.
Any thoughts on how to detect border countries using the SVG elements ?
When dealing with map data we can make some assumptions:
Therefore we can take a shortcut by:
getPathData({normalize:true})
that returns all absolute command coordinates – natively supported by Firefox, for Chromium you still need a polyfill (see example)M
command values and add each commands final values pair (the final on-path-point)set
– we round the coordinates before stringifying them this way we can quickly check if a polygon compared against another shares the same point. The rounding replaces more expensive distance checks to compensate rounding errors produced by path data minifications (conversions to relative or shorthand commands and rounding).Overall, the above method creates the neighbor data more than 2 times faster than using point in fill methods (see below).
function getNeigbourPaths(paths) {
let polys = [];
let l = paths.length;
// init poly object – convert paths
for (let i = 0; i < l; i++) {
let path = paths[i];
let pathData = path.getPathData({ normalize: true });
let pts = getPathDataVertices(pathData);
let bb = getPolyBBox(pts);
//add id if not existent
let id = path.id ? path.id : "path_" + i;
// lower precision for tolerance
let precision = 0;
let scale = 5;
let pts_set = new Set(
pts.map((pt) => {
return `${+(pt.x*scale).toFixed(precision)}_${+(pt.y*scale).toFixed(precision)}`;
})
);
polys.push({id, bb, pts_set, neighbors: new Set([]) });
}
for (let i = 0; i < l; i++) {
let poly = polys[i];
// vertices1
let {bb, pts_set} = poly;
// find neighbours
for (let j = 0; j < l; j++) {
// same polygon - skip
if (i === j) continue;
let polyN = polys[j];
let bb1 = polyN.bb;
let pts_set1 = polyN.pts_set;
let id1 = polyN.id;
// check for bounding box intersections
let intersectBB = checkBBoxIntersections(bb, bb1);
// no intersection possible - skip
if (!intersectBB) continue;
if (intersectBB) {
// check against polypoints in set
for (pt of pts_set1) {
//console.log(pt, pts_set0)
if (pts_set.has(pt)) {
poly.neighbors.add(id1);
break;
}
}
}
}
}
return polys
}
function getPathDataVertices(pathData) {
let polyPoints = [];
pathData.forEach((com) => {
let values = com.values;
// get final on path point from last 2 values
if (values.length) {
let valuesL = values.slice(-2);
let pt = {
x: valuesL[0],
y: valuesL[1]
};
polyPoints.push(pt);
}
});
return polyPoints;
}
function getPolyBBox(vertices) {
let xArr = vertices.map((pt) => pt.x);
let yArr = vertices.map((pt) => pt.y);
let left = Math.min(...xArr);
let right = Math.max(...xArr);
let top = Math.min(...yArr);
let bottom = Math.max(...yArr);
let bb = {
x: left,
left: left,
right: right,
y: top,
top: top,
bottom: bottom,
width: right - left,
height: bottom - top
};
return bb;
}
function checkBBoxIntersections(bb, bb1) {
let {x,left,y,top,width,height,right,bottom} = bb;
let [x1, left1, y1, top1, width1, height1, right1, bottom1] = [
bb1.x,bb1.left,bb1.y,bb1.top,bb1.width,bb1.height,bb1.right,bb1.bottom
];
let intersects = false;
// congruent
if (width * height === width1 * height1 && x === x1 && y === y1) return true;
if (x <= right1 &&
right >= x1 &&
y <= bottom1 &&
bottom >= y1) {
intersects = true;
}
return intersects;
}
svg {
display: block;
outline: 1px solid #ccc;
overflow: visible;
}
.neighbor{
fill:red!important;
}
<script src="
https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.10/path-data-polyfill.min.js
"></script>
<p>Click on states to highlight neighbour states</p>
<svg xmlns="http://www.w3.org/2000/svg" style="stroke-linejoin:round;stroke:#000;fill:#ccc;stroke-width:0.25%" viewBox="0 0 200 250" id="svg">
<path id="MA" d="m162.3 142.4-.291-.193v.29l.29-.096zm-2.912-2.62.68-.291v-.388l-.68.68zm12.04-7.57-.097-1.36-.194-.776.29 2.135zm-42.42-9.998-.68.29-5.532 1.651-1.941.68-2.233.679-.776.291v.291l.291 5.048.291 4.659.291 4.27.486.292 1.747-.486 7.862-2.33.194.486 13.98-5.338.097.194 1.262-.486 4.465-1.747 4.27 5.145.583-.486.291-1.456-.097 2.33h.97l.292 1.165.874 1.65 4.562-5.533 3.785 1.262.874-1.941 6.212-3.3-2.621-5.145.68 3.3-3.204 2.427-3.591.291-7.183-7.668-3.203-4.853 3.203-3.397-3.3-.194-1.359-3.204-.097-.194-5.532 6.018-12.23 4.077z" data-id="MA" data-name="Massachusetts" />
<path id="CT" d="m143 132.7-13.98 5.338-.194-.485-7.862 2.33-1.747.485.194.97 3.688 14.27 1.165 1.456-2.524 3.106 1.748 1.65 5.63-5.435 3.008-4.174.583 1.165 14.17-6.31.292-.29-.097-2.33-.583-2.038-3.008-8.445-.389-1.067z" data-id="CT" data-name="Connecticut" />
<path id="NH" d="m129.9 65.29-1.262 1.553-1.456-.291-.777 6.406v.097l2.33 8.735-.194 1.748-4.174 5.532 1.068 5.047v6.31l-.777 5.823 4.368 15.92 3.981-1.262 12.23-4.077 5.532-6.018v-1.553l.291-2.523-.679.29-.097-.582-6.115-7.473-.194-.68-14.07-33z" data-id="NH" data-name="New Hampshire" />
<path id="RI" d="m152.5 141.4-.097-2.038-.29.776zm1.942-1.553-1.262-2.426-.097 2.523zm-.583-3.009.68 1.747.873 1.553.583-1.164-.874-1.65-.291-1.165h-.97v.68zm-10.77-3.98.389 1.068 3.009 8.445.582 2.038.097 2.33.194-.291 4.66-3.689-.874-6.6 1.94-.388-4.27-5.145-4.465 1.747-1.262.486z" data-id="RI" data-name="Rhode Island" />
<path id="VT" d="m129 122.2-4.369-15.92.777-5.823v-6.31l-1.068-5.047 4.174-5.532.194-1.748-2.33-8.735-1.261.485-5.339 1.941-5.241 2.039-12.04 4.368-.68.194 4.368 10.19 1.456 9.61 5.145 8.056 4.853 15.92.194-.097.776-.291 2.233-.68 1.941-.68 5.533-1.65z" data-id="VT" data-name="Vermont" />
<path id="NJ" d="m122.8 196.6-.485-1.166v1.165h.485zm.97-3.592.292-2.912-.582 2.524zm-3.979-29.8-10.19-2.523-1.748-.486-1.65-.388-4.076 9.027 1.747 2.135-.097 6.504 1.844.29 3.785 9.901-.194.292-4.756 6.988.485 5.339 11.45 3.882.097 4.465 2.62-3.688.389-5.339 2.718-1.844-1.068-3.98 2.038-4.756v-10.39l-.68-3.397-4.561.097 1.165-4.659.68-7.474z" data-id="NJ" data-name="New Jersey" />
<path id="NY" d="m119.5 171.1-1.456 2.038-.388.582zm13.4-4.757 4.853-4.27v-.194zm-12.72 3.01v-3.3l-.29 1.746.29 1.553zm22.52-15.53-1.165-.292-.194.874 1.359-.583zm2.62-1.748-.096.97.194-.582-.097-.388zm-2.717.097-6.503 6.018-9.998 4.271-2.62 2.232-.195 2.136-2.717 2.232.388 2.912 4.174-1.65 6.115-4.95 6.794-3.591 10.29-9.707-7.28 5.533-2.33.97zm3.009-4.076.388-.583-1.165 1.36zm-109.1-3.592-1.553-.194 1.165 1.553zm32.22-29.7.194-1.068-.776.97zm.485-6.989-.388.195-.485 1.067zm-41.06 55.13.679 2.912 5.144 1.262 29.51-8.056 29.7-8.93 5.436 4.174 1.068 2.426 6.31 2.718.193.388 1.65.389 1.748.485 10.19 2.523-.194-.485.874 3.98 1.94-.486 1.069-4.367v-.098l-1.748-1.65 2.524-3.106-1.165-1.456-3.688-14.27-.194-.97-.486-.292-.29-4.27-.292-4.66-.291-5.047v-.29l-.194.096-4.853-15.92-5.145-8.056-1.456-9.61-4.368-10.19-.582.29-20.29 6.795-4.077 4.368-4.562 8.736.194 1.941-6.018 9.124 3.689 1.262 1.068 6.891-4.271 5.63-12.81 7.765-2.426-.97-9.512 1.358-8.833 5.339.485 2.815 3.106 2.717-1.747 8.542-6.988 8.153z" data-id="NY" data-name="New York" />
<path id="PA" d="m106 159.4-6.309-2.718-1.068-2.426-5.435-4.174-29.7 8.93-29.51 8.056-5.144-1.262-.68-2.912-1.65 1.262-2.62 1.844-4.756 4.853-.583.486 2.718 11.94.485 2.136 2.427 10.48.485 2.038 4.271 18.44 13.78-3.397 1.941-.486 1.747-.485 6.892-1.844 37.95-10.58 6.891-2.135 1.65-.486.777-1.94 1.747-1.36h2.33l.29-.873 3.786-4.271.388-.68.291-.194-3.785-9.9-1.844-.291.097-6.504-1.747-2.135 4.076-9.027z" data-id="PA" data-name="Pennsylvania" />
<path id="ME" d="m152.8 107.7h-.097l.194.096-.097-.097zm7.182-20.67-.776-1.165v.582zm-1.359-1.068v-.97l-.097-.583zm3.495-4.076-.194-.098.097.292zm9.706-4.854-.194-1.456-.388.68zm-2.427.583.097-1.068-1.164.194zm3.592-3.203h-.389v.194zm-4.853 1.456-.874-.195-.097.874.97-.68zm4.95-3.01.873.68-.29-.776-.583.097zm-2.62.098-.874 1.65 1.164-.583zm-4.175.097-.388.582.291.874zm5.727-3.203-.097-.777-.291.486.388.29zm-.485.388-.874-.777v.68zm-5.436 2.232-.194-1.067-.388.873zm9.221-3.397-1.456 2.136-.097-1.553zm7.086-6.503-.68-.97-.388.193zm2.62-4.368-.582-.291-.097.291h.68zm1.262-7.57h-.873l.97.29zm1.456.97-.29-1.747-.777-.097zm-1.553-1.553-.194-1.844-.68 1.261zm-56.3 15.24 14.07 33 .194.68 6.115 7.473v-.097l.97-.29.195-5.533 1.553-1.942.97-5.144-.873-.874 1.65-5.047 5.63-2.62 3.688-4.465 1.359-7.766 5.92-.388-.776-3.98 7.377-2.814.485-4.368 1.747.388 4.854-3.98 2.135-5.144-4.95-4.95-5.242-1.068-1.94-3.98v-2.329l-3.204.97-3.591-1.26v-3.98l-9.707-20.97-7.085-3.689-7.862 6.6-2.136-.387-1.844-3.398-2.524.97-3.591 17.86 1.262 5.824 1.068 11.84-2.815 9.124 1.941 1.456-2.33 4.465-2.523-.388z" data-id="ME" data-name="Maine" />
</svg>
<p><a href="https://simplemaps.com/resources/svg-us">https://simplemaps.com/resources/svg-us</a></p>
<script>
window.addEventListener('DOMContentLoaded', e => {
let paths = document.querySelectorAll("path");
// get poly neighbors
let t0 = performance.now();
let neighborLookup = getNeigbourPaths(paths);
let t1 = performance.now() - t0;
console.log('execution time for lookup:', t1);
/**
* example click event listener
*/
paths.forEach(path => {
path.addEventListener('click', e => {
// reset previous
resetNeighbors();
// find path id in lookup
let item = neighborLookup.filter(item => item.id === path.id)[0];
let neighbors = item.neighbors;
neighbors.forEach(id => {
let el = document.getElementById(id)
el.classList.add('neighbor')
})
})
})
function resetNeighbors() {
let els = document.querySelectorAll('.neighbor')
els.forEach(el => {
el.classList.remove('neighbor')
})
}
})
</script>
pointInStroke()
You can find border/neighbor states via natively supported js method isPointInStroke()
.
let svg = document.querySelector('svg');
let states = svg.querySelectorAll('path');
let checks = 0;
perfStart();
/**
* collect data in info array
* find neighbours by pointInStroke checks
*/
let stateInfo = [];
let checksPerPath = 12;
states.forEach((state, s) => {
let id = state.id;
if (!stateInfo[s]) {
let bb = state.getBBox();
stateInfo.push({
id: id,
neighbours: [],
bb: [bb.x, bb.y, bb.x + bb.width, bb.y + bb.height],
pathLength: state.getTotalLength()
});
}
let [x, y, right, bottom] = stateInfo[s].bb;
let pathLength = stateInfo[s].pathLength;
for (let i = 0; i < states.length; i++) {
let state1 = states[i];
let id = state1.id;
if (!stateInfo[i]) {
let bb = state1.getBBox();
stateInfo.push({
id: id,
neighbours: [],
bb: [bb.x, bb.y, bb.x + bb.width, bb.y + bb.height],
pathLength: state1.getTotalLength()
});
}
let [x1, y1, right1, bottom1] = stateInfo[i].bb;
let pathLength1 = stateInfo[i].pathLength;
/**
* narrow down selection by checking bbox intersections
*/
if (
s != i &&
x <= right1 &&
right >= x1 &&
y <= bottom1 &&
bottom >= y1) {
/**
* refine by point in fill checks
* skip previously compared paths
* (e.g already processed A-B combination makes comparing B-A obsolete)
*/
if (!stateInfo[s].neighbours.includes(state1.id) && !stateInfo[i].neighbours.includes(state.id)) {
let inStroke = false;
let inStroke1 = false;
for (let c = 0; c < checksPerPath && !inStroke && !inStroke1; c++) {
let pt = state.getPointAtLength(pathLength / checksPerPath * c);
inStroke = state1.isPointInStroke(pt)
// check path 1 against path 2
if (inStroke) {
stateInfo[s].neighbours.push(state1.id);
stateInfo[i].neighbours.push(state.id);
}else{
// no intersections found: reverse order
let pt1 = state1.getPointAtLength(pathLength1 / checksPerPath * c);
inStroke1 = state.isPointInStroke(pt1)
if (inStroke1) {
stateInfo[s].neighbours.push(state1.id);
stateInfo[i].neighbours.push(state.id);
}
}
// just for benchmarking
checks++
}
}
}
}
let title = document.createElementNS('http://www.w3.org/2000/svg', 'title');
let neighboursAtt = stateInfo[s].neighbours.join(', ');
title.textContent = 'Neighbour states: ' + neighboursAtt;
state.appendChild(title);
state.dataset.neighbours = neighboursAtt;
});
perfEnd();
//console.log('total checks:', checks, stateInfo);
/**
* highlight neighbours on click: test result
*/
states.forEach(function (state, i) {
state.addEventListener('click', function (e) {
removeNeighbourClass();
let current = e.currentTarget;
current.classList.add('active');
let neighbours = e.currentTarget.getAttribute('data-neighbours');
if (neighbours) {
let neighboursArr = neighbours.split(', ');
if (neighboursArr.length) {
neighboursArr.forEach(function (el, i) {
let neighbour = svg.querySelector('#' + el);
neighbour.classList.add('neighbour');
})
}
}
});
});
function removeNeighbourClass() {
let neighbours = document.querySelectorAll('.neighbour, .active');
neighbours.forEach(function (el, i) {
el.classList.remove('active');
el.classList.remove('neighbour');
})
}
/**
* simple performance test
*/
function perfStart() {
t0 = performance.now();
}
function perfEnd(text = '') {
t1 = performance.now();
total = t1 - t0;
console.log(`excecution time ${text}: ${total} ms`);
return total;
}
function renderPoint(svg, coords, fill = "red", r = "2", opacity = "1", id = "", className = "") {
//console.log(coords);
if (Array.isArray(coords)) {
coords = {
x: coords[0],
y: coords[1]
};
}
let marker = `<circle class="${className}" opacity="${opacity}" id="${id}" cx="${coords.x}" cy="${coords.y}" r="${r}" fill="${fill}">
<title>${coords.x} ${coords.y}</title></circle>`;
svg.insertAdjacentHTML("beforeend", marker);
}
svg{
max-height:90vmin;
width:auto;
}
.active{
fill:blue
}
.neighbour{
fill:#5774ad
}
<p>Click on states to highlight neighbour states</p>
<svg xmlns="http://www.w3.org/2000/svg" style="stroke-linejoin:round;stroke:#000;fill:#ccc" width="1000" viewBox="0 0 200 250" id="svg">
<path id="MA" d="m162.3 142.4-.291-.193v.29l.29-.096zm-2.912-2.62.68-.291v-.388l-.68.68zm12.04-7.57-.097-1.36-.194-.776.29 2.135zm-42.42-9.998-.68.29-5.532 1.651-1.941.68-2.233.679-.776.291v.291l.291 5.048.291 4.659.291 4.27.486.292 1.747-.486 7.862-2.33.194.486 13.98-5.338.097.194 1.262-.486 4.465-1.747 4.27 5.145.583-.486.291-1.456-.097 2.33h.97l.292 1.165.874 1.65 4.562-5.533 3.785 1.262.874-1.941 6.212-3.3-2.621-5.145.68 3.3-3.204 2.427-3.591.291-7.183-7.668-3.203-4.853 3.203-3.397-3.3-.194-1.359-3.204-.097-.194-5.532 6.018-12.23 4.077z" data-id="MA" data-name="Massachusetts"/>
<path id="CT" d="m143 132.7-13.98 5.338-.194-.485-7.862 2.33-1.747.485.194.97 3.688 14.27 1.165 1.456-2.524 3.106 1.748 1.65 5.63-5.435 3.008-4.174.583 1.165 14.17-6.31.292-.29-.097-2.33-.583-2.038-3.008-8.445-.389-1.067z" data-id="CT" data-name="Connecticut"/>
<path id="NH" d="m129.9 65.29-1.262 1.553-1.456-.291-.777 6.406v.097l2.33 8.735-.194 1.748-4.174 5.532 1.068 5.047v6.31l-.777 5.823 4.368 15.92 3.981-1.262 12.23-4.077 5.532-6.018v-1.553l.291-2.523-.679.29-.097-.582-6.115-7.473-.194-.68-14.07-33z" data-id="NH" data-name="New Hampshire"/>
<path id="RI" d="m152.5 141.4-.097-2.038-.29.776zm1.942-1.553-1.262-2.426-.097 2.523zm-.583-3.009.68 1.747.873 1.553.583-1.164-.874-1.65-.291-1.165h-.97v.68zm-10.77-3.98.389 1.068 3.009 8.445.582 2.038.097 2.33.194-.291 4.66-3.689-.874-6.6 1.94-.388-4.27-5.145-4.465 1.747-1.262.486z" data-id="RI" data-name="Rhode Island"/>
<path id="VT" d="m129 122.2-4.369-15.92.777-5.823v-6.31l-1.068-5.047 4.174-5.532.194-1.748-2.33-8.735-1.261.485-5.339 1.941-5.241 2.039-12.04 4.368-.68.194 4.368 10.19 1.456 9.61 5.145 8.056 4.853 15.92.194-.097.776-.291 2.233-.68 1.941-.68 5.533-1.65z" data-id="VT" data-name="Vermont"/>
<path id="NJ" d="m122.8 196.6-.485-1.166v1.165h.485zm.97-3.592.292-2.912-.582 2.524zm-3.979-29.8-10.19-2.523-1.748-.486-1.65-.388-4.076 9.027 1.747 2.135-.097 6.504 1.844.29 3.785 9.901-.194.292-4.756 6.988.485 5.339 11.45 3.882.097 4.465 2.62-3.688.389-5.339 2.718-1.844-1.068-3.98 2.038-4.756v-10.39l-.68-3.397-4.561.097 1.165-4.659.68-7.474z" data-id="NJ" data-name="New Jersey"/>
<path id="NY" d="m119.5 171.1-1.456 2.038-.388.582zm13.4-4.757 4.853-4.27v-.194zm-12.72 3.01v-3.3l-.29 1.746.29 1.553zm22.52-15.53-1.165-.292-.194.874 1.359-.583zm2.62-1.748-.096.97.194-.582-.097-.388zm-2.717.097-6.503 6.018-9.998 4.271-2.62 2.232-.195 2.136-2.717 2.232.388 2.912 4.174-1.65 6.115-4.95 6.794-3.591 10.29-9.707-7.28 5.533-2.33.97zm3.009-4.076.388-.583-1.165 1.36zm-109.1-3.592-1.553-.194 1.165 1.553zm32.22-29.7.194-1.068-.776.97zm.485-6.989-.388.195-.485 1.067zm-41.06 55.13.679 2.912 5.144 1.262 29.51-8.056 29.7-8.93 5.436 4.174 1.068 2.426 6.31 2.718.193.388 1.65.389 1.748.485 10.19 2.523-.194-.485.874 3.98 1.94-.486 1.069-4.367v-.098l-1.748-1.65 2.524-3.106-1.165-1.456-3.688-14.27-.194-.97-.486-.292-.29-4.27-.292-4.66-.291-5.047v-.29l-.194.096-4.853-15.92-5.145-8.056-1.456-9.61-4.368-10.19-.582.29-20.29 6.795-4.077 4.368-4.562 8.736.194 1.941-6.018 9.124 3.689 1.262 1.068 6.891-4.271 5.63-12.81 7.765-2.426-.97-9.512 1.358-8.833 5.339.485 2.815 3.106 2.717-1.747 8.542-6.988 8.153z" data-id="NY" data-name="New York"/>
<path id="PA" d="m106 159.4-6.309-2.718-1.068-2.426-5.435-4.174-29.7 8.93-29.51 8.056-5.144-1.262-.68-2.912-1.65 1.262-2.62 1.844-4.756 4.853-.583.486 2.718 11.94.485 2.136 2.427 10.48.485 2.038 4.271 18.44 13.78-3.397 1.941-.486 1.747-.485 6.892-1.844 37.95-10.58 6.891-2.135 1.65-.486.777-1.94 1.747-1.36h2.33l.29-.873 3.786-4.271.388-.68.291-.194-3.785-9.9-1.844-.291.097-6.504-1.747-2.135 4.076-9.027z" data-id="PA" data-name="Pennsylvania"/>
<path id="ME" d="m152.8 107.7h-.097l.194.096-.097-.097zm7.182-20.67-.776-1.165v.582zm-1.359-1.068v-.97l-.097-.583zm3.495-4.076-.194-.098.097.292zm9.706-4.854-.194-1.456-.388.68zm-2.427.583.097-1.068-1.164.194zm3.592-3.203h-.389v.194zm-4.853 1.456-.874-.195-.097.874.97-.68zm4.95-3.01.873.68-.29-.776-.583.097zm-2.62.098-.874 1.65 1.164-.583zm-4.175.097-.388.582.291.874zm5.727-3.203-.097-.777-.291.486.388.29zm-.485.388-.874-.777v.68zm-5.436 2.232-.194-1.067-.388.873zm9.221-3.397-1.456 2.136-.097-1.553zm7.086-6.503-.68-.97-.388.193zm2.62-4.368-.582-.291-.097.291h.68zm1.262-7.57h-.873l.97.29zm1.456.97-.29-1.747-.777-.097zm-1.553-1.553-.194-1.844-.68 1.261zm-56.3 15.24 14.07 33 .194.68 6.115 7.473v-.097l.97-.29.195-5.533 1.553-1.942.97-5.144-.873-.874 1.65-5.047 5.63-2.62 3.688-4.465 1.359-7.766 5.92-.388-.776-3.98 7.377-2.814.485-4.368 1.747.388 4.854-3.98 2.135-5.144-4.95-4.95-5.242-1.068-1.94-3.98v-2.329l-3.204.97-3.591-1.26v-3.98l-9.707-20.97-7.085-3.689-7.862 6.6-2.136-.387-1.844-3.398-2.524.97-3.591 17.86 1.262 5.824 1.068 11.84-2.815 9.124 1.941 1.456-2.33 4.465-2.523-.388z" data-id="ME" data-name="Maine"/>
</svg>
<p><a href="https://simplemaps.com/resources/svg-us">https://simplemaps.com/resources/svg-us</a></p>
getBBox()
<path>
and check isPointInStroke()
intersections at certain pointsgetPointAtLength()
getPointAtLength()
calls can significantly impact performance when run hundreds of times. That's why you should always try to reduce the number of intervals. In the above example we're calculating 12 points per path. Depending on the path geomerty (e.g if a path is highly concave) you might need to increase the number of points.