GeoJsonFeatureCollection.java
/*
* Copyright 2015-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bremersee.geojson;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema.RequiredMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.bremersee.geojson.model.UnknownAware;
import org.locationtech.jts.geom.Geometry;
/**
* A GeoJSON object with the type {@code FeatureCollection} is a feature collection object (see
* <a href="https://tools.ietf.org/html/rfc7946#section-3.3">rfc7946 section 3.3</a>).
*
* @param <G> the geometry type parameter
* @param <P> the properties type parameter
* @author Christian Bremer
*/
@Schema(description = "A GeoJSON object with type 'Feature'.")
@JsonPropertyOrder({"type", "bbox", "features"})
public class GeoJsonFeatureCollection<G extends Geometry, P> extends UnknownAware {
@Schema(hidden = true)
@JsonIgnore
private final boolean withBoundingBox;
@Schema(hidden = true)
@JsonIgnore
private final Comparator<GeoJsonFeature<G, P>> comparator;
@Schema(description = "The bounding box of the GeoJSON feature collection.")
@JsonInclude(Include.NON_EMPTY)
@JsonProperty(GeoJsonConstants.BBOX)
private double[] bbox;
@Schema(description = "The features the GeoJSON feature collection.")
@JsonInclude(Include.ALWAYS)
@JsonProperty(GeoJsonConstants.FEATURES)
private final List<GeoJsonFeature<G, P>> features;
/**
* Instantiates a new Geo json feature collection.
*
* @param bbox the bbox
* @param features the features
* @param comparator the comparator
*/
public GeoJsonFeatureCollection(
double[] bbox,
Collection<? extends GeoJsonFeature<G, P>> features,
Comparator<GeoJsonFeature<G, P>> comparator) {
if (isNull(bbox) || (bbox.length == 4) || (bbox.length == 6)) {
this.bbox = bbox;
} else {
throw new IllegalArgumentException(
"Bounding box must be null or must have a length of four or six.");
}
this.withBoundingBox = nonNull(this.bbox);
this.comparator = comparator;
this.features = new ArrayList<>();
addAll(features);
}
/**
* Instantiates a new geo json feature collection.
*
* @param bbox the bbox
* @param features the features
*/
public GeoJsonFeatureCollection(
double[] bbox,
Collection<? extends GeoJsonFeature<G, P>> features) {
this(bbox, features, null);
}
/**
* Instantiates a new Geo json feature collection.
*
* @param features the features
* @param calculateBounds the calculate bounds
*/
public GeoJsonFeatureCollection(
Collection<? extends GeoJsonFeature<G, P>> features,
boolean calculateBounds) {
this(
calculateBounds
? GeoJsonGeometryFactory.getBoundingBox(getGeometries((features)))
: null,
features,
null);
}
/**
* Instantiates a new Geo json feature collection.
*
* @param features the features
* @param calculateBounds the calculate bounds
* @param comparator the comparator
*/
public GeoJsonFeatureCollection(
Collection<? extends GeoJsonFeature<G, P>> features,
boolean calculateBounds, Comparator<GeoJsonFeature<G, P>> comparator) {
this(
calculateBounds
? GeoJsonGeometryFactory.getBoundingBox(getGeometries((features)))
: null,
features,
comparator);
}
/**
* Instantiates a new Geo json feature collection.
*
* @param withBoundingBox the with bounding box
* @param comparator the comparator
*/
public GeoJsonFeatureCollection(
boolean withBoundingBox,
Comparator<GeoJsonFeature<G, P>> comparator) {
this.withBoundingBox = withBoundingBox;
this.features = new ArrayList<>();
this.comparator = comparator;
}
/**
* Instantiates a new geo json feature collection.
*
* @param withBoundingBox the with bounding box
*/
public GeoJsonFeatureCollection(boolean withBoundingBox) {
this(withBoundingBox, null);
}
/**
* Instantiates a new geo json feature collection.
*
* @param type the type
* @param bbox the bbox
* @param features the features
*/
@JsonCreator
GeoJsonFeatureCollection(
@JsonProperty(value = GeoJsonConstants.TYPE, required = true) String type,
@JsonProperty(GeoJsonConstants.BBOX) double[] bbox,
@JsonProperty(GeoJsonConstants.FEATURES)
Collection<? extends GeoJsonFeature<G, P>> features) {
this(bbox, features, null);
if (!GeoJsonConstants.FEATURE_COLLECTION.equals(type)) {
throw new IllegalArgumentException(String
.format("Type must be '%s'.", GeoJsonConstants.FEATURE_COLLECTION));
}
}
/**
* Gets type.
*
* @return the type
*/
@Schema(
description = "The feature collection type.",
requiredMode = RequiredMode.REQUIRED,
example = GeoJsonConstants.FEATURE_COLLECTION)
@JsonProperty(value = GeoJsonConstants.TYPE, required = true)
public final String getType() {
return GeoJsonConstants.FEATURE_COLLECTION;
}
/**
* Return the bounding box of the GeoJSON object or {@code null} if there is no such object (see
* <a href="https://tools.ietf.org/html/rfc7946#section-5">Bounding Box</a>).
*
* @return the bounding box
*/
@JsonIgnore
public double[] getBbox() {
return bbox;
}
/**
* Gets features.
*
* @return the features
*/
@JsonIgnore
public List<GeoJsonFeature<G, P>> getFeatures() {
return isNull(features) ? List.of() : Collections.unmodifiableList(features);
}
/**
* Add.
*
* @param feature the feature
*/
public void add(GeoJsonFeature<G, P> feature) {
if (nonNull(feature)) {
addAll(List.of(feature));
}
}
/**
* Add all.
*
* @param features the features
*/
public void addAll(Collection<? extends GeoJsonFeature<G, P>> features) {
if (nonNull(features) && !features.isEmpty()) {
this.features.addAll(features);
if (withBoundingBox) {
this.bbox = GeoJsonGeometryFactory.getBoundingBox(getGeometries(this.features));
}
if (nonNull(comparator)) {
this.features.sort(this.comparator);
}
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof GeoJsonFeatureCollection<?, ?> that)) {
return false;
}
return Arrays.equals(getBbox(), that.getBbox())
&& Objects.equals(getFeatures(), that.getFeatures());
}
@Override
public int hashCode() {
int result = Objects.hash(getFeatures());
result = 31 * result + Arrays.hashCode(getBbox());
return result;
}
@Override
public String toString() {
return "GeoJsonFeatureCollection {"
+ ", features=" + getFeatures()
+ ", bbox=" + Arrays.toString(getBbox())
+ ", unknown=" + unknown()
+ '}';
}
@SuppressWarnings("unchecked")
private static List<Geometry> getGeometries(Collection<?> features) {
return Optional.ofNullable(features)
.stream()
.flatMap(Collection::stream)
.map(obj -> ((GeoJsonFeature<Geometry, ?>) obj).getGeometry())
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}