thi-ng / umbrella

⛱ Broadly scoped ecosystem & mono-repository of 198 TypeScript projects (and ~175 examples) for general purpose, functional, data driven development
https://thi.ng
Apache License 2.0
3.31k stars 144 forks source link

Polygons with holes? #464

Closed pcace closed 1 month ago

pcace commented 3 months ago

Hi there,

i am currently trying to convert geojsons to svg to be able to print them in a webapplication.

The Problem i am facing is that Polygon does not support holes (https://docs.thi.ng/umbrella/geom/#md:shape-types)

what i do is to is basically this:

export const geoJsonToSvg = (
  polygons: (UnitPolygonFeature | IPolygonGeoJson)[],
  width?: number,
  height?: number
) => {
  const polys = geoJsonToPolygons(polygons);
  return polygonToSvg(polys, bbox);
};
const geoJsonToPolygons = (
  polygons: (UnitPolygonFeature | IPolygonGeoJson)[]
) => {
  const polygonTree = polygons.map((p) => {
    return p.geometry.coordinates.reduce((acc: (Polygon | Circle)[], l) => {
      const poly = polygon(l.map((v) => [v[0], v[1]]));
      acc.push(poly);
      return acc;
    }, []);
  });

  return flattenTree<Polygon | Circle>(polygonTree);
};

const polygonToSvg = (
  polys: (Polygon | Circle | Point | Path | any)[],
  bbox?: number[][]
) => {
  const doc = svg(
    {
      width: 100,
      height: 100,
      viewBox: `${bbox}`,
    },
    ["defs", {}, ...getPatterns(), ...getPatternMasks(bbox)]
  );

  for (let i = 0; i < polys.length; i++) {
    if (Object.prototype.hasOwnProperty.call(polys[i], "toHiccup")) {
      doc.push(polys[i].toHiccup());
    } else {
      doc.push(polys[i]);
    }
  }
  const svgParsed = asSvg(doc);

  return svgParsed;
};

this kind of works, but returns all holes from the geojson as additional polygons, overlaying the others.

How can i turn these polygons into holes?

Thanks a lot!!

postspectacular commented 2 months ago

Hi @pcace - polygons with holes are currently still unsupported (sadly!), because most of the operations in the geom package are relying on a single boundary representation and also because, personally, I had only rare use for them. However, I've got some old implementation which I've been meaning to re-integrate and at least add support for those operators which make sense. Technically, the best way for that will be to define a separate type of polygon, rather than complicating the existing setup. Let me think about this for a few days & I will report back ASAP...

postspectacular commented 2 months ago

@pcace Just FYI, posted an update: https://mastodon.thi.ng/@toxi/112383330661412657

pcace commented 2 months ago

@postspectacular Thank you so much!!!

postspectacular commented 2 months ago

@pcace Just released v7.0.0 with support for polygons with holes and paths with holes (or multiple curves in general). Please see full changelog here:

https://github.com/thi-ng/umbrella/blob/develop/packages/geom/CHANGELOG.md#700-2024-05-08

Most of the polymorphic geom operators also support these new/updated shape types, but please let me know if you run into any problems...

cdaein commented 2 months ago

The addition of complexPolygon() and flip() is very exciting! I tried and worked nicely but I had a TypeScript error. Is there any way to get around this issue?

Screenshot 2024-05-08 at 3 53 47 PM

postspectacular commented 2 months ago

That is an issue I currently have no real solution for, other than having to manually cast the result of some of these functions, like so:

// add type-only import
import { type Polygon } from "@thi.ng/geom";

const combined = complexPolygon(poly1, [<Polygon>flip(poly2)]));

// or via the different syntax:

const combined = complexPolygon(poly1, [flip(poly2) as Polygon]));

This is part of the https://en.wikipedia.org/wiki/Expression_problem (esp. in typed languages) and I've been trying a few approaches to improve that, but none of them are truly "watertight" and/or as extensible as these polymorphic functions are intended to be... e.g. if had a custom shape type defined yourself, you can add an implementation for it via e.g. flip.add("myshapeid", (shape) => { ... }). You can do that for almost all of the thi.ng/geom functions... but that flexibility comes at a price, i.e. only a generic return type...

cdaein commented 2 months ago

I appreciate your suggestion. 🙏

postspectacular commented 2 months ago

@cdaein Hehe... and I'm already working on #467, so far so good, but I already can see a bunch of more complex stuff on the horizon...

postspectacular commented 1 month ago

Closing this since complete, new release next week! :)