locationtech / spatial4j

LocationTech Spatial4j: A Geospatial Library for Java
https://projects.eclipse.org/projects/locationtech.spatial4j
935 stars 168 forks source link

Buffer a LineString and get a Polygon #198

Open trohsb opened 3 years ago

trohsb commented 3 years ago

Is it possible to replicate the following with this library?

SELECT ST_AsText(ST_Buffer( ST_GeomFromText( 'LINESTRING(-105.279057455338 40.0157700624913,-105.279021664612 40.0158066914082,-105.278825611896 40.0158601679504,-105.278177942238 40.0159837172032,-105.278111222289 40.0159908418209,-105.277966634459 40.0158862356693,-105.277770665563 40.0153484527617,-105.277472353629 40.0144385971722,-105.277516023345 40.0144085899588,-105.277577546514 40.0144604739395,-105.277956073261 40.0147031300363,-105.278068055488 40.0147474703041,-105.27818380957 40.0148268469272,-105.278272909201 40.0148850173352,-105.278353710748 40.0149219815282,-105.278412719346 40.0149662379769,-105.278506848119 40.0151822396217,-105.278514056555 40.0152136717586,-105.278559067375 40.0152571738361,-105.278624949134 40.0152834930121,-105.278688735418 40.0152967364191,-105.278781606905 40.0153124943971,-105.278852014891 40.0153315213173,-105.279010349042 40.0155112293213,-105.279043876655 40.0156956311911)' , 4326), 0.00006, 'endcap=round join=round quad_segs=32'));

trohsb commented 3 years ago

Apologies. The following code snippet does what I want although I had to use the JtsSpatialContext.

    SpatialContext sc = JtsSpatialContext.GEO;
    ShapeFactory.LineStringBuilder lsb = sc.getShapeFactory().lineString();
    // add points to lsb        
    lsb.buffer(0.00006);
    Shape ls = lsb.build();
    System.out.println(ls);
dsmiley commented 3 years ago

Hi. Spatial4j provides an extended syntax on WKT for this. In WktShapeParserTest.testLineStringShape, I temporarily modified the method to have an additional assertion for this syntax.

    Shape lsBuf = ctx.getShapeFactory().lineString().pointXY(1, 10).pointXY(2, 20).pointXY(3, 30).buffer(5).build();
    assertParses("BUFFER(LINESTRING (1 10, 2 20, 3 30), 5)", lsBuf);

As it happens, the assertion failed which puzzled me for a bit so I dug deeper. It's because the constructor for BufferedLineString internally has a boolean parameter expandBufForLongitudeSkew which is the shape factory will pass "false" to in one code path yet ctx.isGeo() in another (one used by the WKT parser). The inconsistency is a bug, and this ought to be an option on the factory. I modified the test to use a non-GEO context (thus assume a flat world) and it passed.

  public WktShapeParserTest() {
    this(newNonGeoSpatialContext());
  }

  private static SpatialContext newNonGeoSpatialContext() {
    SpatialContextFactory ctxFactory = new SpatialContextFactory();
    ctxFactory.geo = false;
    ctxFactory.worldBounds = new RectangleImpl(-100, 100, -200, 200, null);
    return new SpatialContext(ctxFactory);
  }
dsmiley commented 3 years ago

Oh I didn't fully answer your question; I was caught up in the syntax. Since you want rounded corners and a polygon -- you'll need to use JtsSpatialContext and not the default implementation which is rectangular. The default implementation is fairly simple and fast -- it may be appropriate for some use-cases. The Spatial4j wrapper around JTS doesn't allow customizing the particulars of the buffering, but you could subclass JtsShapeFactory (a Spatial4j thing) to override lineString(...) to do something special if you wanted.

rwema3 commented 2 years ago

you'll need to use JtsSpatialContext and not the default implementation which is rectangular. The default implementation is fairly simple and fast -- it may be appropriate for some use-cases. The Spatial4j wrapper around JTS doesn't allow customizing the particulars of the buffering, but you could subclass JtsShapeFactory (a Spatial4j thing) to override lineString(...) to do something special if you wanted.