ApiService.java
package org.heigit.ors.api.services;
import org.heigit.ors.api.APIEnums;
import org.heigit.ors.api.EndpointsProperties;
import org.heigit.ors.api.requests.common.APIRequest;
import org.heigit.ors.api.requests.common.RequestOptions;
import org.heigit.ors.api.requests.routing.RequestProfileParamsRestrictions;
import org.heigit.ors.api.requests.routing.RequestProfileParamsWeightings;
import org.heigit.ors.api.requests.routing.RouteRequest;
import org.heigit.ors.common.DistanceUnit;
import org.heigit.ors.common.StatusCode;
import org.heigit.ors.exceptions.*;
import org.heigit.ors.geojson.GeometryJSON;
import org.heigit.ors.routing.*;
import org.heigit.ors.routing.graphhopper.extensions.HeavyVehicleAttributes;
import org.heigit.ors.routing.graphhopper.extensions.VehicleLoadCharacteristicsFlags;
import org.heigit.ors.routing.graphhopper.extensions.WheelchairTypesEncoder;
import org.heigit.ors.routing.graphhopper.extensions.reader.borders.CountryBordersReader;
import org.heigit.ors.routing.parameters.ProfileParameters;
import org.heigit.ors.routing.parameters.VehicleParameters;
import org.heigit.ors.routing.parameters.WheelchairParameters;
import org.heigit.ors.routing.pathprocessors.BordersExtractor;
import org.heigit.ors.util.DistanceUnitUtil;
import org.heigit.ors.util.GeomUtility;
import org.json.simple.JSONObject;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Polygon;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class ApiService {
protected EndpointsProperties endpointsProperties;
double getMaximumAvoidPolygonArea() {
return 0d;
}
double getMaximumAvoidPolygonExtent() {
return 0d;
}
public static String[] convertAPIEnumListToStrings(Enum[] valuesIn) {
String[] attributes = new String[valuesIn.length];
for (int i = 0; i < valuesIn.length; i++) {
attributes[i] = convertAPIEnum(valuesIn[i]);
}
return attributes;
}
protected static String convertAPIEnum(Enum valuesIn) {
return valuesIn.toString();
}
public static int convertVehicleType(APIEnums.VehicleType vehicleTypeIn, int profileType) throws IncompatibleParameterException {
if (!RoutingProfileType.isHeavyVehicle(profileType)) {
throw new IncompatibleParameterException(GenericErrorCodes.INVALID_PARAMETER_VALUE,
"vehicle_type", vehicleTypeIn.toString(),
APIRequest.PARAM_PROFILE, RoutingProfileType.getName(profileType));
}
if (vehicleTypeIn == null) {
return HeavyVehicleAttributes.UNKNOWN;
}
return HeavyVehicleAttributes.getFromString(vehicleTypeIn.toString());
}
protected static BordersExtractor.Avoid convertAvoidBorders(APIEnums.AvoidBorders avoidBorders) {
if (avoidBorders != null) {
switch (avoidBorders) {
case ALL:
return BordersExtractor.Avoid.ALL;
case CONTROLLED:
return BordersExtractor.Avoid.CONTROLLED;
default:
return BordersExtractor.Avoid.NONE;
}
}
return null;
}
public static int convertRouteProfileType(APIEnums.Profile profile) {
return RoutingProfileType.getFromString(profile.toString());
}
protected Polygon[] convertAndValidateAvoidAreas(JSONObject geoJson, int profileType) throws StatusCodeException {
Polygon[] avoidAreas = convertAvoidAreas(geoJson);
validateAreaLimits(avoidAreas, profileType);
return avoidAreas;
}
protected Polygon[] convertAvoidAreas(JSONObject geoJson) throws StatusCodeException {
// It seems that arrays in json.simple cannot be converted to strings simply
org.json.JSONObject complexJson = new org.json.JSONObject();
complexJson.put("type", geoJson.get("type"));
List<List<Double[]>> coordinates = (List<List<Double[]>>) geoJson.get("coordinates");
complexJson.put("coordinates", coordinates);
Geometry convertedGeom;
try {
convertedGeom = GeometryJSON.parse(complexJson);
} catch (Exception e) {
throw new ParameterValueException(GenericErrorCodes.INVALID_JSON_FORMAT, RequestOptions.PARAM_AVOID_POLYGONS);
}
Polygon[] avoidAreas;
if (convertedGeom instanceof Polygon) {
avoidAreas = new Polygon[]{(Polygon) convertedGeom};
} else if (convertedGeom instanceof MultiPolygon multiPoly) {
avoidAreas = new Polygon[multiPoly.getNumGeometries()];
for (int i = 0; i < multiPoly.getNumGeometries(); i++)
avoidAreas[i] = (Polygon) multiPoly.getGeometryN(i);
} else {
throw new ParameterValueException(GenericErrorCodes.INVALID_PARAMETER_VALUE, RequestOptions.PARAM_AVOID_POLYGONS);
}
return avoidAreas;
}
protected void validateAreaLimits(Polygon[] avoidAreas, int profileType) throws StatusCodeException {
double areaLimit = getMaximumAvoidPolygonArea();
double extentLimit = getMaximumAvoidPolygonExtent();
for (Polygon avoidArea : avoidAreas) {
try {
if (areaLimit > 0) {
long area = Math.round(GeomUtility.getArea(avoidArea, true));
if (area > areaLimit) {
throw new StatusCodeException(StatusCode.BAD_REQUEST, GenericErrorCodes.INVALID_PARAMETER_VALUE, String.format("The area of a polygon to avoid must not exceed %s square meters.", areaLimit));
}
}
if (extentLimit > 0) {
long extent = Math.round(GeomUtility.calculateMaxExtent(avoidArea));
if (extent > extentLimit) {
throw new StatusCodeException(StatusCode.BAD_REQUEST, GenericErrorCodes.INVALID_PARAMETER_VALUE, String.format("The extent of a polygon to avoid must not exceed %s meters.", extentLimit));
}
}
} catch (InternalServerException e) {
throw new ParameterValueException(GenericErrorCodes.INVALID_PARAMETER_VALUE, RequestOptions.PARAM_AVOID_POLYGONS);
}
}
}
protected static int[] convertAvoidCountries(String[] avoidCountries) throws ParameterValueException {
int[] avoidCountryIds = new int[avoidCountries.length];
if (avoidCountries.length > 0) {
for (int i = 0; i < avoidCountries.length; i++) {
try {
avoidCountryIds[i] = Integer.parseInt(avoidCountries[i]);
} catch (NumberFormatException nfe) {
// Check if ISO-3166-1 Alpha-2 / Alpha-3 code
int countryId = CountryBordersReader.getCountryIdByISOCode(avoidCountries[i]);
if (countryId > 0) {
avoidCountryIds[i] = countryId;
} else {
throw new ParameterValueException(GenericErrorCodes.INVALID_PARAMETER_VALUE, RequestOptions.PARAM_AVOID_COUNTRIES, avoidCountries[i]);
}
}
}
}
return avoidCountryIds;
}
public static DistanceUnit convertUnits(APIEnums.Units unitsIn) throws ParameterValueException {
DistanceUnit units = DistanceUnitUtil.getFromString(unitsIn.toString(), DistanceUnit.UNKNOWN);
if (units == DistanceUnit.UNKNOWN)
throw new ParameterValueException(GenericErrorCodes.INVALID_PARAMETER_VALUE, RouteRequest.PARAM_UNITS, unitsIn.toString());
return units;
}
protected static int convertFeatureTypes(APIEnums.AvoidFeatures[] avoidFeatures, int profileType) throws UnknownParameterValueException, IncompatibleParameterException {
int flags = 0;
for (APIEnums.AvoidFeatures avoid : avoidFeatures) {
String avoidFeatureName = avoid.toString();
int flag = AvoidFeatureFlags.getFromString(avoidFeatureName);
if (flag == 0)
throw new UnknownParameterValueException(GenericErrorCodes.INVALID_PARAMETER_VALUE, RequestOptions.PARAM_AVOID_FEATURES, avoidFeatureName);
if (!AvoidFeatureFlags.isValid(profileType, flag))
throw new IncompatibleParameterException(GenericErrorCodes.INVALID_PARAMETER_VALUE, RequestOptions.PARAM_AVOID_FEATURES, avoidFeatureName, APIRequest.PARAM_PROFILE, RoutingProfileType.getName(profileType));
flags |= flag;
}
return flags;
}
public RouteSearchParameters processRequestOptions(RequestOptions options, RouteSearchParameters params) throws StatusCodeException {
if (options.hasAvoidBorders())
params.setAvoidBorders(convertAvoidBorders(options.getAvoidBorders()));
if (options.hasAvoidPolygonFeatures())
params.setAvoidAreas(convertAndValidateAvoidAreas(options.getAvoidPolygonFeatures(), params.getProfileType()));
if (options.hasAvoidCountries())
params.setAvoidCountries(convertAvoidCountries(options.getAvoidCountries()));
if (options.hasAvoidFeatures())
params.setAvoidFeatureTypes(convertFeatureTypes(options.getAvoidFeatures(), params.getProfileType()));
return params;
}
protected ProfileParameters convertParameters(RequestOptions options, int profileType) throws StatusCodeException {
ProfileParameters params = new ProfileParameters();
if (options.getProfileParams().hasSurfaceQualityKnown() || options.getProfileParams().hasAllowUnsuitable()) {
params = new WheelchairParameters();
}
if (options.getProfileParams().hasRestrictions()) {
RequestProfileParamsRestrictions restrictions = options.getProfileParams().getRestrictions();
APIEnums.VehicleType vehicleType = options.getVehicleType();
validateRestrictionsForProfile(restrictions, profileType);
params = convertSpecificProfileParameters(profileType, restrictions, vehicleType);
}
if (options.getProfileParams().hasWeightings()) {
RequestProfileParamsWeightings weightings = options.getProfileParams().getWeightings();
applyWeightings(weightings, params);
}
if (params instanceof WheelchairParameters) {
if (options.getProfileParams().hasSurfaceQualityKnown()) {
((WheelchairParameters) params).setSurfaceQualityKnown(options.getProfileParams().getSurfaceQualityKnown());
}
if (options.getProfileParams().hasAllowUnsuitable()) {
((WheelchairParameters) params).setAllowUnsuitable(options.getProfileParams().getAllowUnsuitable());
}
}
return params;
}
protected ProfileParameters convertSpecificProfileParameters(int profileType, RequestProfileParamsRestrictions restrictions, APIEnums.VehicleType vehicleType) {
ProfileParameters params = new ProfileParameters();
if (RoutingProfileType.isHeavyVehicle(profileType))
params = convertHeavyVehicleParameters(restrictions, vehicleType);
if (RoutingProfileType.isWheelchair(profileType))
params = convertWheelchairParamRestrictions(restrictions);
return params;
}
private VehicleParameters convertHeavyVehicleParameters(RequestProfileParamsRestrictions restrictions, APIEnums.VehicleType vehicleType) {
VehicleParameters params = new VehicleParameters();
if (vehicleType != null && vehicleType != APIEnums.VehicleType.UNKNOWN) {
setLengthParam(restrictions, params);
setWidthParam(restrictions, params);
setHeightParam(restrictions, params);
setWeightParam(restrictions, params);
setAxleLoadParam(restrictions, params);
setLoadCharacteristicsParam(restrictions, params);
}
return params;
}
private VehicleParameters setLengthParam(RequestProfileParamsRestrictions restrictions, VehicleParameters params) {
if (params != null && restrictions != null && restrictions.hasLength()) {
params.setLength(restrictions.getLength());
}
return params;
}
private VehicleParameters setWidthParam(RequestProfileParamsRestrictions restrictions, VehicleParameters params) {
if (params != null && restrictions != null && restrictions.hasWidth()) {
params.setWidth(restrictions.getWidth());
}
return params;
}
private VehicleParameters setHeightParam(RequestProfileParamsRestrictions restrictions, VehicleParameters params) {
if (params != null && restrictions != null && restrictions.hasHeight()) {
params.setHeight(restrictions.getHeight());
}
return params;
}
private VehicleParameters setWeightParam(RequestProfileParamsRestrictions restrictions, VehicleParameters params) {
if (params != null && restrictions != null && restrictions.hasWeight()) {
params.setWeight(restrictions.getWeight());
}
return params;
}
private VehicleParameters setAxleLoadParam(RequestProfileParamsRestrictions restrictions, VehicleParameters params) {
if (params != null && restrictions != null && restrictions.hasAxleLoad()) {
params.setAxleload(restrictions.getAxleLoad());
}
return params;
}
private VehicleParameters setLoadCharacteristicsParam(RequestProfileParamsRestrictions restrictions, VehicleParameters params) {
if (params != null && restrictions != null) {
int loadCharacteristics = 0;
if (restrictions.hasHazardousMaterial() && restrictions.getHazardousMaterial())
loadCharacteristics |= VehicleLoadCharacteristicsFlags.HAZMAT;
if (loadCharacteristics != 0)
params.setLoadCharacteristics(loadCharacteristics);
}
return params;
}
private WheelchairParameters convertWheelchairParamRestrictions(RequestProfileParamsRestrictions restrictions) {
WheelchairParameters params = new WheelchairParameters();
if (restrictions.hasSurfaceType())
params.setSurfaceType(WheelchairTypesEncoder.getSurfaceType(restrictions.getSurfaceType()));
if (restrictions.hasTrackType())
params.setTrackType(WheelchairTypesEncoder.getTrackType(restrictions.getTrackType()));
if (restrictions.hasSmoothnessType())
params.setSmoothnessType(WheelchairTypesEncoder.getSmoothnessType(restrictions.getSmoothnessType().toString()));
if (restrictions.hasMaxSlopedKerb())
params.setMaximumSlopedKerb(restrictions.getMaxSlopedKerb());
if (restrictions.hasMaxIncline())
params.setMaximumIncline(restrictions.getMaxIncline());
if (restrictions.hasMinWidth())
params.setMinimumWidth(restrictions.getMinWidth());
return params;
}
private void validateRestrictionsForProfile(RequestProfileParamsRestrictions restrictions, int profile) throws IncompatibleParameterException {
// Check that we do not have some parameters that should not be there
List<String> setRestrictions = restrictions.getRestrictionsThatAreSet();
ProfileParameters params = new ProfileParameters();
if (RoutingProfileType.isWheelchair(profile)) {
params = new WheelchairParameters();
}
if (RoutingProfileType.isHeavyVehicle(profile)) {
params = new VehicleParameters();
}
List<String> invalidParams = new ArrayList<>();
for (String setRestriction : setRestrictions) {
boolean valid = false;
for (String validRestriction : params.getValidRestrictions()) {
if (validRestriction.equals(setRestriction)) {
valid = true;
break;
}
}
if (!valid) {
invalidParams.add(setRestriction);
}
}
if (!invalidParams.isEmpty()) {
// There are some parameters present that shouldn't be there
String invalidParamsString = String.join(", ", invalidParams);
throw new IncompatibleParameterException(GenericErrorCodes.UNKNOWN_PARAMETER, "restrictions", invalidParamsString, APIRequest.PARAM_PROFILE, RoutingProfileType.getName(profile));
}
}
private ProfileParameters applyWeightings(RequestProfileParamsWeightings weightings, ProfileParameters params) throws ParameterOutOfRangeException, ParameterValueException {
String factorKey = "factor";
try {
if (weightings.hasGreenIndex()) {
ProfileWeighting pw = new ProfileWeighting("green");
Float greenFactor = weightings.getGreenIndex();
if (greenFactor > 1)
throw new ParameterOutOfRangeException(GenericErrorCodes.INVALID_PARAMETER_VALUE, String.format(Locale.UK, "%.2f", greenFactor), "green factor", "1.0");
pw.addParameter(factorKey, greenFactor);
params.add(pw);
}
if (weightings.hasQuietIndex()) {
ProfileWeighting pw = new ProfileWeighting("quiet");
Float quietFactor = weightings.getQuietIndex();
if (quietFactor > 1)
throw new ParameterOutOfRangeException(GenericErrorCodes.INVALID_PARAMETER_VALUE, String.format(Locale.UK, "%.2f", quietFactor), "quiet factor", "1.0");
pw.addParameter(factorKey, quietFactor);
params.add(pw);
}
if (weightings.hasShadowIndex()) {
ProfileWeighting pw = new ProfileWeighting("shadow");
Float shadowFactor = weightings.getShadowIndex();
if (shadowFactor > 1)
throw new ParameterOutOfRangeException(GenericErrorCodes.INVALID_PARAMETER_VALUE, String.format(Locale.UK, "%.2f", shadowFactor), "shadow factor", "1.0");
pw.addParameter(factorKey, shadowFactor);
params.add(pw);
}
if (weightings.hasSteepnessDifficulty()) {
ProfileWeighting pw = new ProfileWeighting("steepness_difficulty");
pw.addParameter("level", weightings.getSteepnessDifficulty());
params.add(pw);
}
if (weightings.hasCsv()) {
ProfileWeighting pw = new ProfileWeighting("csv");
pw.addParameter("column", weightings.getCsvColumn());
pw.addParameter(factorKey, weightings.getCsvFactor());
params.add(pw);
}
} catch (InternalServerException e) {
throw new ParameterValueException(RoutingErrorCodes.INVALID_PARAMETER_VALUE, "weightings");
}
return params;
}
}