SVG Path optimization - javascript
We have on our project a JSON file made by another team that helped to create dynamic SVG with binding. The problem with the JSON file is it contains duplicate and unoptimized paths.
I've decided to make a NodeJS script that scan the paths contained into the file and that optimize it.
A sample of an unoptimized path:
"paths": [ "M59245.1734326687,2320.0L59266.994415716,2320.0L59266.994415716,2320.0L59306.410931336,2320.0L59306.410931336,2320.0L59345.827446956,2320.0L59345.827446956,2320.0L59385.243962576,2320.0L59385.243962576,2320.0L59424.660478196,2320.0L59424.660478196,2320.0L59464.076993816,2320.0L59464.076993816,2320.0L59503.493509436,2320.0L59503.493509436,2320.0L59542.910025056,2320.0L59542.910025056,2320.0L59582.326540676,2320.0L59582.326540676,2320.0L59621.743056296,2320.0L59621.743056296,2320.0L59633.0477129758,2320.0L59633.0477129758,2320.0L59661.159571916,2320.0L59661.159571916,2320.0L59700.576087536,2320.0L59700.576087536,2320.0L59739.992603156,2320.0L59739.992603156,2320.0L59779.409118776,2320.0L59779.409118776,2320.0L59818.825634396,2320.0L59818.825634396,2320.0L59858.242150016,2320.0L59858.242150016,2320.0L59897.658665636,2320.0L59897.658665636,2320.0L59937.075181256,2320.0L59937.075181256,2320.0L59976.491696876,2320.0L59976.491696876,2320.0L60015.908212496,2320.0L60015.908212496,2320.0L60030.5396230941,2320.0L60030.5396230941,2320.0L60055.324728116,2320.0L60055.324728116,2320.0L60094.741243736,2320.0L60094.741243736,2320.0L60134.157759356,2320.0L60134.157759356,2320.0L60173.574274976,2320.0L60173.574274976,2320.0L60212.990790596,2320.0L60212.990790596,2320.0L60252.407306216,2320.0L60252.407306216,2320.0L60291.823821836,2320.0L60291.823821836,2320.0L60331.240337456,2320.0L60331.240337456,2320.0L60370.656853076,2320.0L60370.656853076,2320.0L60410.073368696,2320.0L60410.073368696,2320.0L60428.0157666062,2320.0M60428.0,2320.0L60428.0044472058,2319.99990189987L60428.0044472058,2319.99990189987" ]
Path without duplicates (half the length!):
"paths": [ "M59245.1734326687,2320L59266.994415716,2320,59306.410931336,2320,59345.827446956,2320,59385.243962576,2320,59424.660478196,2320,59464.076993816,2320,59503.493509436,2320,59542.910025056,2320,59582.326540676,2320,59621.743056296,2320,59633.0477129758,2320,59661.159571916,2320,59700.576087536,2320,59739.992603156,2320,59779.409118776,2320,59818.825634396,2320,59858.242150016,2320,59897.658665636,2320,59937.075181256,2320,59976.491696876,2320,60015.908212496,2320,60030.5396230941,2320,60055.324728116,2320,60094.741243736,2320,60134.157759356,2320,60173.574274976,2320,60212.990790596,2320,60252.407306216,2320,60291.823821836,2320,60331.240337456,2320,60370.656853076,2320,60410.073368696,2320,60428.0157666062,2320M60428,2320L60428.0044472058,2319.99990189987" ]
After analyzing results, I've come to a conclusion that I can do simplification of paths to reduce useles lines. So I've decided to do also a simplification of each paths using npm package simplify-path. It works fine:
Not simplified:
[ [ [ 59245.1734326687, 2320 ] ], [ [ 59266.994415716, 2320 ], [ 59306.410931336, 2320 ], [ 59345.827446956, 2320 ], [ 59385.243962576, 2320 ], [ 59424.660478196, 2320 ], [ 59464.076993816, 2320 ], [ 59503.493509436, 2320 ], [ 59542.910025056, 2320 ], [ 59582.326540676, 2320 ], [ 59621.743056296, 2320 ], [ 59633.0477129758, 2320 ], [ 59661.159571916, 2320 ], [ 59700.576087536, 2320 ], [ 59739.992603156, 2320 ], [ 59779.409118776, 2320 ], [ 59818.825634396, 2320 ], [ 59858.242150016, 2320 ], [ 59897.658665636, 2320 ], [ 59937.075181256, 2320 ], [ 59976.491696876, 2320 ], [ 60015.908212496, 2320 ], [ 60030.5396230941, 2320 ], [ 60055.324728116, 2320 ], [ 60094.741243736, 2320 ], [ 60134.157759356, 2320 ], [ 60173.574274976, 2320 ], [ 60212.990790596, 2320 ], [ 60252.407306216, 2320 ], [ 60291.823821836, 2320 ], [ 60331.240337456, 2320 ], [ 60370.656853076, 2320 ], [ 60410.073368696, 2320 ], [ 60428.0157666062, 2320 ] ], [ [ 60428, 2320 ] ], [ [ 60428.0044472058, 2319.99990189987 ] ] ]
Simplified:
[ 59245.1734326687, 2320, 60428.0044472058, 2319.99990189987 ]
How can I convert back to SVG Path the simplified array of points? Is there any API to this?
I drop a part from the code where I simplify SVG Path (except for the RegEx, the code is not from me!):
// Parse SVG Path to array.
var commands = pathArrays[i].paths[j].split(/(?=[LMC])/);
// Split the Path array into a Points array.
var pointArrays = commands.map(function(d){
var pointsArray = d.slice(1, d.length).split(/[\s,-]+/);
if (pointsArray[0] == '')
pointsArray.shift();
var pairsArray = [];
for(var i = 0; i < pointsArray.length; i += 2){
pairsArray.push([+pointsArray[i], +pointsArray[i+1]]);
}
return pairsArray;
});
// Call simplify method from npm package.
var simplifyArray = simplify(pointArrays, 10);
// ==> I want to convert array of points to SVG Path <==
Ya if you're literally not doing anything with curves etc, well then you just described a polygon. Which maybe explains why I don't see any commands in the non/simplified examples at the bottom? In which case your task becomes infinitely easier since you don't need to join on commands and just read off the points.
If that's the case, you could do something real simple like this quickie PoC I threw together. Hope it helps, or maybe elaborate more and we'll take another stab. I'm intrigued by the challenge. :)
var geometry = [
[
'109,141',
'59.073,97.825',
'9.146,54.65',
'71.5,33',
'133.854,11.35',
'121.427,76.175'
],
[
'133.989,130.188',
'86.736,114.757',
'47.501,145.276',
'47.272,95.175',
'6.275,66.815',
'53.385,51.282',
'67.284,3.237',
'96.629,43.738',
'146.216,42.404',
'117.242,82.968'
],
[
'8,80',
'58,144',
'143,49',
'127,6',
'60,112',
'35,63'
]
],
translate = 0,
create = function() {
for (i=0,r=geometry.length;i<r;i++) {
var polygon = geometry[i].join(),
colors = '#'+(Math.random()*0xFFFFFF<<0).toString(16),
shape = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
shape.setAttribute("points", polygon);
shape.setAttribute("fill", colors);
shape.setAttribute("transform", "translate(" + translate + ",0)");
translate += 150;
document.getElementById("shapes").appendChild(shape);
}
};
create();
<svg id="shapes"
width="450"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xml:space="preserve">
</svg>
....and a CODEPEN to tinker with.
The following should do the job:
path = 'M' + simplifyArray.join(',');
Related
How edit polygon from static coordinates Leaflet.js
Hell, I have static array I draw on map and I want to make it editable. could you help me with any idea? there is may code var pol = [ [ 46.655639, 24.804925 ], [ 46.655389, 24.804815 ], [ 46.655507, 24.804573 ], [ 46.655752, 24.804689 ], [46.655639, 24.804925 ] ]; var poly = L.polygon(pol,{color: 'red'}); poly.addTo(map); var getPolygonCenter = poly.getBounds().getCenter(); getLocation = [getPolygonCenter.lat, getPolygonCenter.lng]; map.setView(getLocation, defaultZoomLevel);
GeoJson LineString Feature Collection with holes
I have a long list of coordinates (sent by a GPS sensor) that represent the movement of an asset. I'm using leaflet to render the GeoJSON and it works fine if I render the LineString as a single feature, but if I break it down into multiple features (inside a FeatureCollection - to apply a different dynamic color) I start to see "holes" between features. I'm pretty sure that this is due to the fact that there is actually a "hole" in the data I'm receiving. But why it works as a single LineString feature? Is there a way to fix this? This is an extract of the GeoJSON (very large object) there are 3 of the 866 features of the object { "type":"Feature", "properties":{ "type":"traffic", "color":"#ffa600" }, "geometry":{ "type":"LineString", "coordinates":[ [ 7.583125, 45.0485616 ], [ 7.5830532999999996, 45.0485816 ], [ 7.58299, 45.0486133 ], [ 7.582893299999999, 45.0486066 ], [ 7.5828682999999995, 45.04859 ] ] } }, link to bin https://jsbin.com/nexijajake/edit?html,output example with single feature https://jsbin.com/guqihajalu/1/edit?html,output
Actually, there is nothing wrong with rendering. Your data array (in your jsbin link) is an array of linestrings that are not connected with each other. You got a schema like this (imagine each row is a linestring): [pointA-pointB-pointC] [pointD-pointE-pointF] In order for your line to be continuous, the last point of each linestring should exist in the next linestring as first point: [pointA-pointB-pointC] [pointC-pointD-pointE-pointF] This way, your line will be continuous. For example, the following sample (taken from your jsbin) has a gap: const data = [ { "type":"Feature", "properties":{ "type":"traffic", "color":"#ffa600" }, "geometry":{ "type":"LineString", "coordinates":[ [ 7.583125, 45.0485616 ], [ 7.5830532999999996, 45.0485816 ], [ 7.58299, 45.0486133 ], [ 7.582893299999999, 45.0486066 ], [ 7.5828682999999995, 45.04859 ] ] } }, { "type":"Feature", "properties":{ "type":"normal", "color":"#07e36a" }, "geometry":{ "type":"LineString", "coordinates":[ [ 7.582795, 45.0485149 ], [ 7.582624999999999, 45.0483233 ], [ 7.581984899999999, 45.047521599999996 ] ] } } ]; The gap is fixed (the first point of the second linestring is the last point of the first linestring): const data = [ { "type":"Feature", "properties":{ "type":"traffic", "color":"#ffa600" }, "geometry":{ "type":"LineString", "coordinates":[ [ 7.583125, 45.0485616 ], [ 7.5830532999999996, 45.0485816 ], [ 7.58299, 45.0486133 ], [ 7.582893299999999, 45.0486066 ], [ 7.5828682999999995, 45.04859 ] ] } }, { "type":"Feature", "properties":{ "type":"normal", "color":"#07e36a" }, "geometry":{ "type":"LineString", "coordinates":[ //the first point here is the last of previous linestring [ 7.5828682999999995, 45.04859 ], [ 7.582795, 45.0485149 ], [ 7.582624999999999, 45.0483233 ], [ 7.581984899999999, 45.047521599999996 ] ] } } ];
Sort/Order List of points based on another list of points (LineString) - JavaScript
Lets say that i have a LineString (A car route): let lineCoords = [ [ 10.964832305908203, 41.004681939880314 ], [ 10.977363586425781, 40.99096148527727 ], [ 10.983200073242188, 40.97075154073346 ], [ 11.02834701538086, 40.98372150040732 ], [ 11.02508544921875, 41.00716631272605 ], ]; And a another LineString (The stops that i have to make) let arrayOfPoints = [ [ 10.964832305908203, 41.004681939880314 ], [ 10.994186401367188, 41.01947819666632 ], [ 10.977363586425781, 40.99096148527727 ], [ 10.983200073242188, 40.97075154073346 ], [ 11.02508544921875, 41.00716631272605 ], [ 10.964832305908203, 41.004681939880314 ], [ 11.02834701538086, 40.98372150040732 ], ]; Now i wanna sort arrayOfPoints based on lineCoords How can i make it? Can Turf help?
Javascript/json keep in database and special chars
I have layout builder in React to keep data structure and text I use ImmutableJS objects. Such structure with attributes as text or css styles is saved into database as JSON. To make it JSON I using json-immutable library: serialize, deserialize functions. After save in database I provide configuration for react components as javascript variables. For example my backend generate js file with variables or small part is printing directly in html code using script tag. Data are JSON or decoded javascript. The biggest problem I have with save special chars. For example if someone set ' single quote in some attribute it is saved directly. But when I print it in html code as var myConfig = '{anyjson}'; when inside JSON is single quote parser throw error. The same with double quotes, & (ampersant) or any chars used in html code like <,/> Single quote I replace to \' when I print it in html code. But I think does exists any way to save keep all data in JSON and still they will easy to decode by deserialize function to parse JSON to ImmutableJS objects. Code example https://jsfiddle.net/jaroapp/2yzud6ua/2/ var structure = { "__iterable":"Map", "data":[ [ "entityMap", { "__iterable":"Map", "data":[ [ "html_el_qb7tyhi", { "__iterable":"Map", "data":[ [ "imported", false ], [ "path", "html_el_qb7tyhi" ], [ "componentData", { "__iterable":"Map", "data":[ ] } ], [ "draftjsObject", { "__iterable":"OrderedMap", "data":[ ] } ], [ "draftjs", true ], [ "data", { "__iterable":"OrderedMap", "data":[ [ "text", "B&B is the best company. It's my hope for new markets." ] ] } ], [ "chunk", null ], [ "style", { "__iterable":"OrderedMap", "data":[ [ "background-image", "url(\"/path/to/image.jpg\")" ] ] } ], [ "attr", { "__iterable":"OrderedMap", "data":[ ] } ], [ "runEditor", false ], [ "entityMap", { "__iterable":"OrderedMap", "data":[ ] } ], [ "type", "div" ], [ "key", "html_el_qb7tyhi" ] ] } ], [ "html_el_2dgupn7", { "__iterable":"Map", "data":[ [ "imported", false ], [ "path", "html_el_2dgupn7" ], [ "componentData", { "__iterable":"Map", "data":[ ] } ], [ "draftjsObject", { "entityMap":{ }, "blocks":[ { "key":"3ia22", "text":"Text saved with html inside", "type":"unstyled", "depth":0, "inlineStyleRanges":[ ], "entityRanges":[ ], "data":{ } } ] } ], [ "draftjs", true ], [ "data", { "__iterable":"OrderedMap", "data":[ [ "text", null ], [ "html", "<p class=\"md-block-unstyled\">Text saved with html inside</p>" ] ] } ], [ "chunk", null ], [ "style", { "__iterable":"OrderedMap", "data":[ ] } ], [ "attr", { "__iterable":"OrderedMap", "data":[ ] } ], [ "runEditor", false ], [ "entityMap", { "__iterable":"OrderedMap", "data":[ ] } ], [ "type", "div" ], [ "key", "html_el_2dgupn7" ] ] } ] ] } ], [ "containersMap", { "__iterable":"Map", "data":[ ] } ], [ "componentsMap", { "__iterable":"Map", "data":[ [ "entityMap", { "__iterable":"OrderedMap", "data":[ ] } ] ] } ] ] }; Such structure I set as parameter into ReactJS component. If I set it as JSON and wrap in quotes then the browser throws an error. If I set it as JavaScript object into the React component I can't make ImmutableJS from this one, because this structure is read by this https://www.npmjs.com/package/json-immutable library (I use the same to make JSON from Immutable JS to save it in database); Thanks in advance for any hints.
I solved this problem. Short description, maybe will helpful for someone. I get file with deserialize function form json-immutable package. And I modified function deserialize to export function deserialize(json){ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if(typeof json==='string'){ return JSON.parse(json, function(key, value, options){ return revive(key, value, options); }); }else{ return JSON.parse(JSON.stringify(json), function(key, value, options){ return revive(key, value, options); }); } } Function in package use JSON.parse for param. Maybe it's not the most elegant solution but I don't have time to find way to don't "re-json" object. Standard Object.keys and map return origin object.
Masonry.js And Floated Div Issues
I'm using masonry.js quite a lot in a new website that I'm developing but there is one flaw with using it in my design when it comes to floating my columns side by side. Here is a general representation of what I'm trying to do: Normal View: [ column left ] [ main column ] [[masonry post]] [ column right ] [ ] [ ] [[masonry post]] [ ] [ ] [ ] [[masonry post]] [ ] [ ] [ ] [[masonry post]] [ ] [ ] [ ] [[masonry post]] [ ] Responsive view: [ column left ] [ main column ] [ column right ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ [masonry post][masonry post] ] [ ] [ ] [ [masonry post][masonry post] ] [ ] [ ] [ [masonry post][masonry post] ] [ ] So the "main column" and "masonry post" columns are floated left so that when the browser is shrunk down, the masonry column goes beneath the main column, which in theory works perfectly, however because Masonry JS is applied to the masonry column it doesn't move like it's supposed to - Instead, it stays where it is and gets hidden behind the right hand column. Any ideas on how this can be sorted?