<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tobe dw</title>
    <description>The latest articles on DEV Community by Tobe dw (@tobias_dw_ca1faced49).</description>
    <link>https://dev.to/tobias_dw_ca1faced49</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4235347%2Fc4ab086c-998f-4c1d-ba78-cd857d1fbf75.jpg</url>
      <title>DEV Community: Tobe dw</title>
      <link>https://dev.to/tobias_dw_ca1faced49</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tobias_dw_ca1faced49"/>
    <language>en</language>
    <item>
      <title>Two polygons, one border: storing map shapes as faces and edges</title>
      <dc:creator>Tobe dw</dc:creator>
      <pubDate>Mon, 09 Nov 2026 14:48:19 +0000</pubDate>
      <link>https://dev.to/tobias_dw_ca1faced49/two-polygons-one-border-storing-map-shapes-as-faces-and-edges-4di3</link>
      <guid>https://dev.to/tobias_dw_ca1faced49/two-polygons-one-border-storing-map-shapes-as-faces-and-edges-4di3</guid>
      <description>&lt;p&gt;&lt;strong&gt;Why two parcels that share a border drift apart in most map editors, and how a planar partition keeps them together. With runnable TypeScript.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Disclosure: I make vectorstream, the library in this post.&lt;/p&gt;

&lt;p&gt;I wanted to edit land parcels on a web map. In the tools I tried, I moved a corner of one parcel and a thin gap opened along the border. I closed the gap by hand, moved the next corner, and it opened again. That is when I stopped patching and started on a different data model.&lt;/p&gt;

&lt;p&gt;Here is the problem in two parcels. Parcel 101 and parcel 102 touch along one line. Most editors store each polygon as its own ring of coordinates. The shared line then exists twice, once per ring. Nothing keeps the two copies equal. Drag a corner on one parcel and you get a gap or an overlap along the border.&lt;/p&gt;

&lt;p&gt;You can patch this with snapping and a validation step after every edit. That works until it doesn't. I decided to remove the cause instead: store the border once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model
&lt;/h2&gt;

&lt;p&gt;vectorstream keeps a &lt;em&gt;planar partition&lt;/em&gt;. It has three kinds of elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nodes&lt;/strong&gt; are points with coordinates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edges&lt;/strong&gt; are straight segments between two nodes. Each edge is stored as two half-edges, one per direction. A half-edge knows the face on its left.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faces&lt;/strong&gt; are the areas the edges enclose. One outer face surrounds everything and has no outline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule is: every location belongs to exactly one face. A border between two faces exists once, as one edge, and both faces use it.&lt;/p&gt;

&lt;p&gt;Four things follow from that, and they are the reason I'd pick this model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dragging a corner on a shared border moves both faces.&lt;/li&gt;
&lt;li&gt;Drawing an outline across two parcels &lt;em&gt;splits&lt;/em&gt; them. The outline doesn't lie on top of them.&lt;/li&gt;
&lt;li&gt;Drawing the same outline twice does nothing. The editor reports &lt;code&gt;unchanged&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;An outline that crosses itself, like a bow tie, is split into the cells it encloses. It isn't rejected.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Point 2 surprised me most when I used it, and it's where people disagree with this design. In an editor with independent polygons, a new shape sits above the old ones. Here, a new shape changes them. If you want overlapping, independent shapes, this model is the wrong tool. Use separate layers, or a different library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;

&lt;p&gt;This example uses only &lt;code&gt;@vectorstream/core&lt;/code&gt;. It is a file in the repository, and a test runs it. The test checks the numbers I give below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;History&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SpatialIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;checkGeometry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;importGeoJSON&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;insertPolygon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FaceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FeatureCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Partition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vectorstream/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;insertOutline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FeatureCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Partition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;History&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FaceId&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;partition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;importGeoJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;History&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;checkInvariants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// structure: half-edge pairs, closed cycles, Euler's formula, ...&lt;/span&gt;
    &lt;span class="na"&gt;validators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;checkGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpatialIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="c1"&gt;// geometry: crossings, overlaps&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// All or nothing: if a check fails, the transaction is rolled back and throws.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;faces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;insert-outline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;insertPolygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;faces&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The input is two adjacent parcels that meet at longitude 7.4555, and a building outline that crosses that line. After import, the shared boundary is one edge, not two.&lt;/p&gt;

&lt;p&gt;I insert the building outline. The call returns two new faces, one piece per parcel. The partition then has four bounded faces. Each piece keeps the attributes of the parcel it was cut from, so you get &lt;code&gt;parcel: '101'&lt;/code&gt; and &lt;code&gt;parcel: '102'&lt;/code&gt; back. Inserting the same outline again applies no operation. One &lt;code&gt;undo()&lt;/code&gt; restores the original two parcels.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;History&lt;/code&gt; object does the safety work. Every change runs in a transaction, and two kinds of check run before it commits. The structure check covers half-edge pairs, closed cycles and Euler's formula. The geometry check covers crossings and overlaps. If either fails, the transaction rolls back. I would not ship an editor on this model without both checks, because a half-edge structure fails in confusing ways when it is slightly wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  A decision I'd defend: the grid
&lt;/h2&gt;

&lt;p&gt;Every node coordinate lies on a grid with a mesh size of 2⁻²⁶. That is about 1.5 · 10⁻⁸ coordinate units. With longitude and latitude, it is between one and two millimeters at 51.5° north. The editor rounds inserted and imported coordinates onto this grid, so &lt;code&gt;51.51&lt;/code&gt; is stored as &lt;code&gt;51.51000000536442&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That looks like a bug in a debugger. It isn't. Crossings, the snapping of crossing points and area signs are all decided on the rounded values, so they are exact and repeatable. Floating-point predicates that give different answers on a second run are what break topology code, and the grid removes that class of problem.&lt;/p&gt;

&lt;p&gt;The cost is real. Data with features smaller than the mesh size needs a finer grid: &lt;code&gt;importGeoJSON(data, { gridExponent })&lt;/code&gt;. Coordinates too large for the chosen grid are rejected on import. And layers that exchange faces must use the same exponent. If your data is in a projected system with metre units, check the exponent before you import.&lt;/p&gt;

&lt;p&gt;My first version used plain floating-point coordinates and tested crossings on them. Two runs on the same data could disagree about whether a point lay left or right of a line. A border that was fine on the first run came out with a sliver on the second. I threw that version away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it on a map
&lt;/h2&gt;

&lt;p&gt;The model works without a map. To edit on one, you add an adapter. This excerpt comes from the tested Leaflet quickstart in the docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setView&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;51.5112&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;7.4555&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LeafletAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Editor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;importGeoJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolbar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Toolbar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;defaultToolbarEntries&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;toolbar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;draw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Every transaction ends in exactly one of three events.&lt;/span&gt;
&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;changed:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unchanged&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nothing to do:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rejected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;violations&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rejected:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;save&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;exportGeoJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leaflet takes &lt;code&gt;[lat, lng]&lt;/code&gt;, and the editor works with &lt;code&gt;x&lt;/code&gt; as longitude and &lt;code&gt;y&lt;/code&gt; as latitude. That is why &lt;code&gt;setView&lt;/code&gt; reads the other way round. Adapters for MapLibre GL and OpenLayers exist too. Export gives you plain GeoJSON, with holes written as inner rings.&lt;/p&gt;

&lt;h2&gt;
  
  
  When not to use it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your shapes are independent and may overlap. Use ordinary polygons.&lt;/li&gt;
&lt;li&gt;You only need to draw a few shapes and export them. A simpler drawing library is less to learn.&lt;/li&gt;
&lt;li&gt;You need a stable API today. vectorstream is a pre-release. The free packages are on npm at version 0.1.0, so expect changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The editor is free under the MIT license. A paid Pro license adds rules between layers. You don't need it for anything in this post.&lt;/p&gt;

&lt;p&gt;If your data has shared borders and you've fought gaps and overlaps, try the model on your own parcels: &lt;code&gt;npm install @vectorstream/core @vectorstream/editor @vectorstream/leaflet leaflet&lt;/code&gt;. There's a playground at &lt;a href="https://vectorstream.dev/playground/" rel="noopener noreferrer"&gt;https://vectorstream.dev/playground/&lt;/a&gt;. I'd like to hear where it breaks.&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>architecture</category>
      <category>programming</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
