IsochronesRequest.java

/*
 * This file is part of Openrouteservice.
 *
 * Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this library;
 * if not, see <https://www.gnu.org/licenses/>.
 */

package org.heigit.ors.api.requests.isochrones;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import org.heigit.ors.api.requests.common.APIRequest;
import org.heigit.ors.api.requests.routing.RouteRequestOptions;
import org.heigit.ors.exceptions.ParameterValueException;
import org.heigit.ors.isochrones.IsochroneMapCollection;
import org.heigit.ors.isochrones.IsochroneRequest;
import org.heigit.ors.isochrones.IsochronesErrorCodes;
import org.heigit.ors.api.APIEnums;
import org.heigit.ors.routing.RoutingProfileType;

import java.time.LocalDateTime;
import java.util.List;

import static org.heigit.ors.api.services.ApiService.convertAPIEnumListToStrings;


@Schema(name = "IsochronesRequest", description = "The JSON body request sent to the isochrones service which defines options and parameters regarding the isochrones to generate.")
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class IsochronesRequest extends APIRequest {
    public static final String PARAM_LOCATIONS = "locations";
    public static final String PARAM_LOCATION_TYPE = "location_type";
    public static final String PARAM_OPTIONS = "options";
    public static final String PARAM_RANGE = "range";
    public static final String PARAM_RANGE_TYPE = "range_type";
    public static final String PARAM_RANGE_UNITS = "units";
    public static final String PARAM_AREA_UNITS = "area_units";
    public static final String PARAM_INTERSECTIONS = "intersections";
    public static final String PARAM_ATTRIBUTES = "attributes";
    public static final String PARAM_INTERVAL = "interval";
    public static final String PARAM_SMOOTHING = "smoothing";
    public static final String PARAM_TIME = "time";

    @Schema(name = PARAM_LOCATIONS, description = "The locations to use for the route as an array of `longitude/latitude` pairs in WGS 84 (EPSG:4326)",
            example = "[[8.681495,49.41461],[8.686507,49.41943]]",
            requiredMode = Schema.RequiredMode.REQUIRED)
    @JsonProperty(PARAM_LOCATIONS)
    private Double[][] locations = new Double[][]{};
    @JsonIgnore
    private boolean hasLocations = false;

    @Schema(name = PARAM_LOCATION_TYPE, description = "`start` treats the location(s) as starting point, `destination` as goal.",
            defaultValue = "start")
    @JsonProperty(value = PARAM_LOCATION_TYPE)
    private IsochronesRequestEnums.LocationType locationType;
    @JsonIgnore
    private boolean hasLocationType = false;

    @Schema(name = PARAM_RANGE, description = """
            Maximum range value of the analysis in **seconds** for time and **metres** for distance.\
            Alternatively a comma separated list of specific range values. Ranges will be the same for all locations.\
            """,
            example = "[ 300, 200 ]",
            requiredMode = Schema.RequiredMode.REQUIRED)
    @JsonProperty(PARAM_RANGE)
    private List<Double> range;
    @JsonIgnore
    private boolean hasRange = false;

    @Schema(name = PARAM_RANGE_TYPE,
            description = "Specifies the isochrones reachability type.", defaultValue = "time")
    @JsonProperty(value = PARAM_RANGE_TYPE, defaultValue = "time")
    private IsochronesRequestEnums.RangeType rangeType;
    @JsonIgnore
    private boolean hasRangeType = false;

    // unit only valid for range_type distance, will be ignored for range_time time
    @Schema(name = PARAM_RANGE_UNITS,
            description = """
                    Specifies the distance units only if `range_type` is set to distance.
                    Default: m. \
                    """,
            extensions = {@Extension(name = "validWhen", properties = {
                    @ExtensionProperty(name = "ref", value = "range_type"),
                    @ExtensionProperty(name = "value", value = "distance")}
            )},
            defaultValue = "m")
    @JsonProperty(value = PARAM_RANGE_UNITS)
    private APIEnums.Units rangeUnit;
    @JsonIgnore
    private boolean hasRangeUnits = false;

    @Schema(name = PARAM_OPTIONS,
            description = "Additional options for the isochrones request",
            example = "{\"avoid_borders\":\"all\"}")
    @JsonProperty(PARAM_OPTIONS)
    private RouteRequestOptions isochronesOptions;
    @JsonIgnore
    private boolean hasOptions = false;

    @Schema(hidden = true)
    private APIEnums.RouteResponseType responseType = APIEnums.RouteResponseType.GEOJSON;

    @Schema(name = PARAM_AREA_UNITS,
            description = """
                    Specifies the area unit.
                    Default: m. \
                    """,
            extensions = {@Extension(name = "validWhen", properties = {
                    @ExtensionProperty(name = "ref", value = "attributes"),
                    @ExtensionProperty(name = "value", value = "area")}
            )},
            defaultValue = "m")
    @JsonProperty(value = PARAM_AREA_UNITS)
    private APIEnums.Units areaUnit;
    @JsonIgnore
    private boolean hasAreaUnits = false;

    @Schema(name = PARAM_INTERSECTIONS,
            description = "Specifies whether to return intersecting polygons. ", defaultValue = "false")
    @JsonProperty(value = PARAM_INTERSECTIONS)
    private boolean intersections;
    @JsonIgnore
    private boolean hasIntersections = false;

    @Schema(name = PARAM_ATTRIBUTES, description = "List of isochrones attributes",
            example = "[\"area\"]")
    @JsonProperty(PARAM_ATTRIBUTES)
    private IsochronesRequestEnums.Attributes[] attributes;
    @JsonIgnore
    private boolean hasAttributes = false;

    @Schema(name = PARAM_INTERVAL, description = """
            Interval of isochrones or equidistants. This is only used if a single range value is given. \
            Value in **seconds** for time and **meters** for distance.\
            """,
            example = "30"
    )
    @JsonProperty(PARAM_INTERVAL)
    private Double interval;
    @JsonIgnore
    private boolean hasInterval = false;

    @Schema(name = PARAM_SMOOTHING,
            description = """
                    Applies a level of generalisation to the isochrone polygons generated as a `smoothing_factor` between `0` and `100.0`.
                    Generalisation is produced by determining a maximum length of a connecting line between two points found on the outside of a containing polygon.
                    If the distance is larger than a threshold value, the line between the two points is removed and a smaller connecting line between other points is used.
                    Note that the minimum length of this connecting line is ~1333m, and so when the `smoothing_factor` results in a distance smaller than this, the minimum value is used.
                    The threshold value is determined as `(maximum_radius_of_isochrone / 100) * smoothing_factor`.
                    Therefore, a value closer to 100 will result in a more generalised shape.
                    The polygon generation algorithm is based on Duckham and al. (2008) `"Efficient generation of simple polygons for characterizing the shape of a set of points in the plane."`""",
            example = "25")
    @JsonProperty(value = PARAM_SMOOTHING)
    private Double smoothing;
    @JsonIgnore
    private boolean hasSmoothing = false;

    @Schema(name = PARAM_TIME, description = "Departure date and time provided in local time zone",
            example = "2020-01-31T12:45:00", hidden = true)
    @JsonProperty(PARAM_TIME)
    private LocalDateTime time;
    @JsonIgnore
    private boolean hasTime = false;

    @JsonIgnore
    private IsochroneMapCollection isoMaps;
    @JsonIgnore
    private IsochroneRequest isochroneRequest;

    @JsonCreator
    public IsochronesRequest() {
    }

    public static String[] convertAttributes(IsochronesRequestEnums.Attributes[] attributes) {
        return convertAPIEnumListToStrings(attributes);
    }

    public static int convertToIsochronesProfileType(APIEnums.Profile profile) throws ParameterValueException {
        try {
            int profileFromString = RoutingProfileType.getFromString(profile.toString());
            if (profileFromString == 0) {
                throw new ParameterValueException(IsochronesErrorCodes.INVALID_PARAMETER_VALUE, "profile");
            }
            return profileFromString;
        } catch (Exception e) {
            throw new ParameterValueException(IsochronesErrorCodes.INVALID_PARAMETER_VALUE, "profile");
        }
    }

    public APIEnums.Units getAreaUnit() {
        return areaUnit;
    }

    public void setAreaUnit(APIEnums.Units areaUnit) {
        this.areaUnit = areaUnit;
        hasAreaUnits = true;
    }

    public boolean hasAreaUnits() {
        return hasAreaUnits;
    }

    public Double getSmoothing() {
        return smoothing;
    }

    public void setSmoothing(Double smoothing) {
        this.smoothing = smoothing;
        this.hasSmoothing = true;
    }

    public boolean hasSmoothing() {
        return hasSmoothing;
    }

    public APIEnums.RouteResponseType getResponseType() {
        return responseType;
    }

    public void setResponseType(APIEnums.RouteResponseType responseType) {
        this.responseType = responseType;
    }

    public boolean getIntersections() {
        return intersections;
    }

    public void setIntersections(Boolean intersections) {
        this.intersections = intersections;
        hasIntersections = true;
    }

    public boolean hasIntersections() {
        return hasIntersections;
    }

    public APIEnums.Units getRangeUnit() {
        return rangeUnit;
    }

    public void setRangeUnit(APIEnums.Units rangeUnit) {
        this.rangeUnit = rangeUnit;
        hasRangeUnits = true;
    }

    public boolean hasRangeUnits() {
        return hasRangeUnits;
    }

    public IsochronesRequestEnums.Attributes[] getAttributes() {
        return attributes;
    }

    public void setAttributes(IsochronesRequestEnums.Attributes[] attributes) {
        this.attributes = attributes;
        this.hasAttributes = true;
    }

    public boolean hasAttributes() {
        return hasAttributes;
    }

    public Double[][] getLocations() {
        return locations;
    }

    public void setLocations(Double[][] locations) {
        this.locations = locations;
        hasLocations = true;
    }

    public boolean hasLocations() {
        return hasLocations;
    }

    public IsochronesRequestEnums.LocationType getLocationType() {
        return locationType;
    }

    public void setLocationType(IsochronesRequestEnums.LocationType locationType) {
        this.locationType = locationType;
        hasLocationType = true;
    }

    public boolean hasLocationType() {
        return hasLocationType;
    }

    public RouteRequestOptions getIsochronesOptions() {
        return isochronesOptions;
    }

    public void setIsochronesOptions(RouteRequestOptions isochronesOptions) {
        this.isochronesOptions = isochronesOptions;
        this.hasOptions = true;
    }

    public boolean hasOptions() {
        return this.hasOptions;
    }

    public List<Double> getRange() {
        return range;
    }

    public void setRange(List<Double> range) {
        this.range = range;
        hasRange = true;
    }

    public boolean hasRange() {
        return hasRange;
    }

    public IsochronesRequestEnums.RangeType getRangeType() {
        return rangeType;
    }

    public void setRangeType(IsochronesRequestEnums.RangeType rangeType) {
        this.rangeType = rangeType;
        hasRangeType = true;
    }

    public boolean hasRangeType() {
        return hasRangeType;
    }

    public Double getInterval() {
        return interval;
    }

    public void setInterval(Double interval) {
        this.interval = interval;
        hasInterval = true;
    }

    public boolean hasInterval() {
        return hasInterval;
    }

    public LocalDateTime getTime() {
        return time;
    }

    public void setTime(LocalDateTime time) {
        this.time = time;
        hasTime = true;
    }

    public boolean hasTime() {
        return hasTime;
    }

    public IsochroneMapCollection getIsoMaps() {
        return isoMaps;
    }

    public void setIsoMaps(IsochroneMapCollection isoMaps) {
        this.isoMaps = isoMaps;
    }

    public IsochroneRequest getIsochroneRequest() {
        return isochroneRequest;
    }

    public void setIsochroneRequest(IsochroneRequest isochroneRequest) {
        this.isochroneRequest = isochroneRequest;
    }
}