Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clip tileset to a polygonal boundary #351

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open

Clip tileset to a polygonal boundary #351

wants to merge 7 commits into from

Conversation

bdon
Copy link
Member

@bdon bdon commented Jan 9, 2025

Implements #51

This is to address making non-global maps: for example, a map of a single continent, country, or city. The existing tooling has a path to doing this with pmtiles extract on a planet file, but this has a downside: the output tileset is not clipped to the AOI (area of interest) and shows the entire detail of the original source tileset at lower zoom levels.

Additionally, if the input OSM data has features that lie outside of the AOI, like oceans or roads, those extend beyond the boundary. This is not a fatal problem for many use cases, but we can do better in our tile generation program by clipping every feature to the AOI.

The challenges here are:

  • Making this fast: we need a efficient spatial representation of the clipping area, because running a constructive polygon operation like clip on 100% features is extremely slow
  • Robustness: constructive geometry operations can create self-intersections
  • MultiPolygon support: we need to support polygons with holes and multi polygons: for example, South Africa should not include Lesotho, and France should include overseas departments without pulling in lots of extra data.

Copy link

sonarqubecloud bot commented Jan 9, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
1.8% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

tiledGeometries = new HashMap<>();
coverings = new HashMap<>();
try {
for (var i = 0; i <= 15; i++) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to get min/max from the actual program somewhere.


public Clip(Stats stats, Geometry input) {
this.stats = stats;
var clipGeometry = latLonToWorldCoords(input).buffer(0.00001);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be configurable buffer size via arguments?

var extents = TileExtents.computeFromWorldBounds(i, WORLD_BOUNDS);
double scale = 1 << i;
Geometry scaled = AffineTransformation.scaleInstance(scale, scale).transform(clipGeometry);
// var simplified = DouglasPeuckerSimplifier.simplify(scaled, 0.25/256);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to simplify, or rely on the last snapping step?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be surprised if we had to.

double scale = 1 << i;
Geometry scaled = AffineTransformation.scaleInstance(scale, scale).transform(clipGeometry);
// var simplified = DouglasPeuckerSimplifier.simplify(scaled, 0.25/256);
this.tiledGeometries.put(i, sliceIntoTiles(scaled, 0, 0.015625, i, extents.getForZoom(i)).getTileData());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We hardcode the buffer size here. We need a way to determine the actual buffer size taking into account any custom buffer size determination function.

}
}

// Copied from elsewhere in planetiler
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could make these public in planetiler-core?

if (this.tiledGeometries.containsKey(tileCoord.z()) && this.tiledGeometries.get(tileCoord.z()).containsKey(tileCoord)) {
List<List<CoordinateSequence>> coords = tiledGeometries.get(tileCoord.z()).get(tileCoord);
var clipGeometry = reassemblePolygons(coords);
var clipGeometry2 = GeoUtils.fixPolygon(clipGeometry);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixPolygon seems to work fine here instead of needing snapAndFixPolygon

@wipfli
Copy link
Collaborator

wipfli commented Jan 12, 2025

Do you store the clipping mask in world coordinates (web mercator x y of the 0/0/0 tile) or do you store it in tile coordinates?

@wipfli
Copy link
Collaborator

wipfli commented Jan 12, 2025

I saw you have a covering tiles mechanism. I think you could get rid of this by using an STRtree https://locationtech.github.io/jts/javadoc/org/locationtech/jts/index/strtree/STRtree.html and use the tile bounds as an envelope for querying

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants