package com.onthegomap.planetiler;
import com.carrotsearch.hppc.IntArrayList;
import com.carrotsearch.hppc.IntObjectMap;
import com.carrotsearch.hppc.IntStack;
import com.onthegomap.planetiler.collection.Hppc;
import com.onthegomap.planetiler.geo.DouglasPeuckerSimplifier;
import com.onthegomap.planetiler.geo.GeoUtils;
import com.onthegomap.planetiler.geo.GeometryException;
import com.onthegomap.planetiler.geo.GeometryType;
import com.onthegomap.planetiler.geo.MutableCoordinateSequence;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.locationtech.jts.algorithm.Area;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.Polygonal;
import org.locationtech.jts.index.strtree.STRtree;
import org.locationtech.jts.operation.buffer.BufferOp;
import org.locationtech.jts.operation.buffer.BufferParameters;
import org.locationtech.jts.operation.linemerge.LineMerger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A collection of utilities for merging features with the same attributes in a rendered tile from
* {@link Profile#postProcessLayerFeatures(String, int, List)} immediately before a tile is written to the output
* mbtiles file.
*
* Unlike postgis-based solutions that have a full view of all features after they are loaded into the databse, the
* planetiler engine only sees a single input feature at a time while processing source features, then only has
* visibility into multiple features when they are grouped into a tile immediately before emitting. This ends up being
* sufficient for most real-world use-cases but to do anything more that requires a view of multiple features
* not within the same tile, {@link Profile} implementations must store input features manually.
*/
public class FeatureMerge {
private static final Logger LOGGER = LoggerFactory.getLogger(FeatureMerge.class);
private static final BufferParameters bufferOps = new BufferParameters();
static {
bufferOps.setJoinStyle(BufferParameters.JOIN_MITRE);
}
/** Don't instantiate */
private FeatureMerge() {}
/**
* Combines linestrings with the same set of attributes into a multilinestring where segments with touching endpoints
* are merged by {@link LineMerger}, removing any linestrings under {@code minLength}.
*
* Ignores any non-linestrings and passes them through to the output unaltered.
*
* Orders grouped output multilinestring by the index of the first element in that group from the input list.
*
* @param features all features in a layer
* @param minLength minimum tile pixel length of features to emit, or 0 to emit all merged linestrings
* @param tolerance after merging, simplify linestrings using this pixel tolerance, or -1 to skip simplification step
* @param buffer number of pixels outside the visible tile area to include detail for, or -1 to skip clipping step
* @return a new list containing all unaltered features in their original order, then each of the merged groups
* ordered by the index of the first element in that group from the input list.
*/
public static List mergeLineStrings(List features,
double minLength, double tolerance, double buffer) {
return mergeLineStrings(features, attrs -> minLength, tolerance, buffer);
}
/**
* Merges linestrings with the same attributes like {@link #mergeLineStrings(List, double, double, double)} except
* with a dynamic length limit computed by {@code lengthLimitCalculator} for the attributes of each group.
*/
public static List mergeLineStrings(List features,
Function