use of de.ii.ogcapi.tiles.domain.Rule in project ldproxy by interactive-instruments.
the class CapabilityTiles method onStartup.
@Override
public ValidationResult onStartup(OgcApi api, MODE apiValidation) {
// since building block / capability components are currently always enabled,
// we need to test, if the TILES module is enabled for the API and stop, if not
OgcApiDataV2 apiData = api.getData();
if (!apiData.getExtension(TilesConfiguration.class).map(ExtensionConfiguration::isEnabled).orElse(false)) {
return ValidationResult.of();
}
try {
SQLiteJDBCLoader.initialize();
} catch (Exception e) {
return ImmutableValidationResult.builder().mode(apiValidation).addStrictErrors(MessageFormat.format("Could not load SQLite: {}", e.getMessage())).build();
}
if (apiValidation == MODE.NONE) {
return ValidationResult.of();
}
ImmutableValidationResult.Builder builder = ImmutableValidationResult.builder().mode(apiValidation);
for (Map.Entry<String, TilesConfiguration> entry : apiData.getCollections().entrySet().stream().filter(entry -> entry.getValue().getEnabled() && entry.getValue().getExtension(TilesConfiguration.class).isPresent()).map(entry -> new AbstractMap.SimpleImmutableEntry<>(entry.getKey(), entry.getValue().getExtension(TilesConfiguration.class).get())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)).entrySet()) {
String collectionId = entry.getKey();
TilesConfiguration config = entry.getValue();
Optional<FeatureSchema> schema = providers.getFeatureSchema(apiData, apiData.getCollections().get(collectionId));
List<String> featureProperties = schema.isPresent() ? schemaInfo.getPropertyNames(schema.get(), false, false) : ImmutableList.of();
List<String> formatLabels = extensionRegistry.getExtensionsForType(TileFormatExtension.class).stream().filter(formatExtension -> formatExtension.isEnabledForApi(apiData)).map(format -> format.getMediaType().label()).collect(Collectors.toUnmodifiableList());
List<String> tileEncodings = config.getTileEncodingsDerived();
if (Objects.isNull(tileEncodings)) {
builder.addStrictErrors(MessageFormat.format("No tile encoding has been specified in the TILES module configuration of collection ''{0}''.", collectionId));
} else {
for (String encoding : config.getTileEncodingsDerived()) {
if (!formatLabels.contains(encoding)) {
builder.addStrictErrors(MessageFormat.format("The tile encoding ''{0}'' is specified in the TILES module configuration of collection ''{1}'', but the format does not exist.", encoding, collectionId));
}
}
}
formatLabels = extensionRegistry.getExtensionsForType(TileSetFormatExtension.class).stream().filter(formatExtension -> formatExtension.isEnabledForApi(apiData)).map(format -> format.getMediaType().label()).collect(Collectors.toUnmodifiableList());
for (String encoding : config.getTileSetEncodings()) {
if (!formatLabels.contains(encoding)) {
builder.addStrictErrors(MessageFormat.format("The tile set encoding ''{0}'' is specified in the TILES module configuration of collection ''{1}'', but the format does not exist.", encoding, collectionId));
}
}
List<Double> center = config.getCenterDerived();
if (center.size() != 0 && center.size() != 2)
builder.addStrictErrors(MessageFormat.format("The center has been specified in the TILES module configuration of collection ''{1}'', but the array length is ''{0}'', not 2.", center.size(), collectionId));
Map<String, MinMax> zoomLevels = config.getZoomLevelsDerived();
for (Map.Entry<String, MinMax> entry2 : zoomLevels.entrySet()) {
String tileMatrixSetId = entry2.getKey();
Optional<TileMatrixSet> tileMatrixSet = tileMatrixSetRepository.get(tileMatrixSetId).filter(tms -> config.getTileMatrixSets().contains(tms.getId()));
if (tileMatrixSet.isEmpty()) {
builder.addStrictErrors(MessageFormat.format("The configuration in the TILES module of collection ''{0}'' references a tile matrix set ''{1}'' that is not available in this API.", collectionId, tileMatrixSetId));
} else {
if (tileMatrixSet.get().getMinLevel() > entry2.getValue().getMin()) {
builder.addStrictErrors(MessageFormat.format("The configuration in the TILES module of collection ''{0}'' for tile matrix set ''{1}'' is specified to start at level ''{2}'', but the minimum level of the tile matrix set is ''{3}''.", collectionId, tileMatrixSetId, entry2.getValue().getMin(), tileMatrixSet.get().getMinLevel()));
}
if (tileMatrixSet.get().getMaxLevel() < entry2.getValue().getMax()) {
builder.addStrictErrors(MessageFormat.format("The configuration in the TILES module of collection ''{0}'' for tile matrix set ''{1}'' is specified to end at level ''{2}'', but the maximum level of the tile matrix set is ''{3}''.", collectionId, tileMatrixSetId, entry2.getValue().getMax(), tileMatrixSet.get().getMaxLevel()));
}
if (entry2.getValue().getDefault().isPresent()) {
Integer defaultLevel = entry2.getValue().getDefault().get();
if (defaultLevel < entry2.getValue().getMin() || defaultLevel > entry2.getValue().getMax()) {
builder.addStrictErrors(MessageFormat.format("The configuration in the TILES module of collection ''{0}'' for tile matrix set ''{1}'' specifies a default level ''{2}'' that is outside of the range [ ''{3}'' : ''{4}'' ].", tileMatrixSetId, defaultLevel, entry2.getValue().getMin(), entry2.getValue().getMax()));
}
}
}
}
if (config.getTileProvider() instanceof TileProviderFeatures) {
Map<String, MinMax> zoomLevelsCache = config.getZoomLevelsCacheDerived();
if (Objects.nonNull(zoomLevelsCache)) {
for (Map.Entry<String, MinMax> entry2 : zoomLevelsCache.entrySet()) {
String tileMatrixSetId = entry2.getKey();
MinMax zoomLevelsTms = getZoomLevels(apiData, tileMatrixSetId);
if (Objects.isNull(zoomLevelsTms)) {
builder.addStrictErrors(MessageFormat.format("The cache in the TILES module of collection ''{0}'' references a tile matrix set ''{1}'' that is not configured for this API.", collectionId, tileMatrixSetId));
} else {
if (zoomLevelsTms.getMin() > entry2.getValue().getMin()) {
builder.addStrictErrors(MessageFormat.format("The cache in the TILES module of collection ''{0}'' for tile matrix set ''{1}'' is specified to start at level ''{2}'', but the minimum level is ''{3}''.", collectionId, tileMatrixSetId, entry2.getValue().getMin(), zoomLevelsTms.getMin()));
}
if (zoomLevelsTms.getMax() < entry2.getValue().getMax()) {
builder.addStrictErrors(MessageFormat.format("The cache in the TILES module of collection ''{0}'' for tile matrix set ''{1}'' is specified to end at level ''{2}'', but the maximum level is ''{3}''.", collectionId, tileMatrixSetId, entry2.getValue().getMax(), zoomLevelsTms.getMax()));
}
}
}
}
Map<String, MinMax> seeding = config.getSeedingDerived();
if (Objects.nonNull(seeding)) {
for (Map.Entry<String, MinMax> entry2 : seeding.entrySet()) {
String tileMatrixSetId = entry2.getKey();
MinMax zoomLevelsTms = getZoomLevels(apiData, tileMatrixSetId);
if (Objects.isNull(zoomLevelsTms)) {
builder.addStrictErrors(MessageFormat.format("The seeding in the TILES module of collection ''{0}'' references a tile matrix set ''{1}'' that is not configured for this API.", collectionId, tileMatrixSetId));
} else {
if (zoomLevelsTms.getMin() > entry2.getValue().getMin()) {
builder.addStrictErrors(MessageFormat.format("The seeding in the TILES module of collection ''{0}'' for tile matrix set ''{1}'' is specified to start at level ''{2}'', but the minimum level is ''{3}''.", collectionId, tileMatrixSetId, entry2.getValue().getMin(), zoomLevelsTms.getMin()));
}
if (zoomLevelsTms.getMax() < entry2.getValue().getMax()) {
builder.addStrictErrors(MessageFormat.format("The seeding in the TILES module of collection ''{0}'' for tile matrix set ''{1}'' is specified to end at level ''{2}'', but the maximum level is ''{3}''.", collectionId, tileMatrixSetId, entry2.getValue().getMax(), zoomLevelsTms.getMax()));
}
}
}
}
final Integer limit = Objects.requireNonNullElse(config.getLimitDerived(), 0);
if (limit < 1) {
builder.addStrictErrors(MessageFormat.format("The feature limit in the TILES module must be a positive integer. Found in collection ''{1}'': {0}.", limit, collectionId));
}
final Map<String, List<PredefinedFilter>> filters = config.getFiltersDerived();
if (Objects.nonNull(filters)) {
for (Map.Entry<String, List<PredefinedFilter>> entry2 : filters.entrySet()) {
String tileMatrixSetId = entry2.getKey();
MinMax zoomLevelsCfg = getZoomLevels(apiData, config, tileMatrixSetId);
if (Objects.isNull(zoomLevelsCfg)) {
builder.addStrictErrors(MessageFormat.format("The filters in the TILES module of collection ''{0}'' references a tile matrix set ''{0}'' that is not configured for this API.", collectionId, tileMatrixSetId));
} else {
for (PredefinedFilter filter : entry2.getValue()) {
if (zoomLevelsCfg.getMin() > filter.getMin()) {
builder.addStrictErrors(MessageFormat.format("A filter in the TILES module of collection ''{0}'' for tile matrix set ''{1}'' is specified to start at level ''{2}'', but the minimum level is ''{3}''.", collectionId, tileMatrixSetId, filter.getMin(), zoomLevelsCfg.getMin()));
}
if (zoomLevelsCfg.getMax() < filter.getMax()) {
builder.addStrictErrors(MessageFormat.format("A filter in the TILES module of collection ''{0}'' for tile matrix set ''{1}'' is specified to end at level ''{2}'', but the maximum level is ''{3}''.", collectionId, tileMatrixSetId, filter.getMax(), zoomLevelsCfg.getMax()));
}
if (filter.getFilter().isPresent()) {
// try to convert the filter to CQL2-text
String expression = filter.getFilter().get();
FeatureTypeConfigurationOgcApi collectionData = apiData.getCollections().get(collectionId);
final Map<String, String> filterableFields = queryParser.getFilterableFields(apiData, collectionData);
final Map<String, String> queryableTypes = queryParser.getQueryableTypes(apiData, collectionData);
try {
queryParser.getFilterFromQuery(ImmutableMap.of("filter", expression), filterableFields, ImmutableSet.of("filter"), queryableTypes, Cql.Format.TEXT);
} catch (Exception e) {
builder.addErrors(MessageFormat.format("A filter ''{0}'' in the TILES module of collection ''{1}'' for tile matrix set ''{2}'' is invalid. Reason: {3}", expression, collectionId, tileMatrixSetId, e.getMessage()));
}
}
}
}
}
}
final Map<String, List<Rule>> rules = config.getRulesDerived();
if (Objects.nonNull(rules)) {
for (Map.Entry<String, List<Rule>> entry2 : rules.entrySet()) {
String tileMatrixSetId = entry2.getKey();
MinMax zoomLevelsCfg = getZoomLevels(apiData, config, tileMatrixSetId);
if (Objects.isNull(zoomLevelsCfg)) {
builder.addStrictErrors(MessageFormat.format("The rules in the TILES module of collection ''{0}'' references a tile matrix set ''{0}'' that is not configured for this API.", collectionId, tileMatrixSetId));
} else {
for (Rule rule : entry2.getValue()) {
if (zoomLevelsCfg.getMin() > rule.getMin()) {
builder.addStrictErrors(MessageFormat.format("A rule in the TILES module of collection ''{0}'' for tile matrix set ''{1}'' is specified to start at level ''{2}'', but the minimum level is ''{3}''.", collectionId, tileMatrixSetId, rule.getMin(), zoomLevelsCfg.getMin()));
}
if (zoomLevelsCfg.getMax() < rule.getMax()) {
builder.addStrictErrors(MessageFormat.format("A rule in the TILES module of collection ''{0}'' for tile matrix set ''{1}'' is specified to end at level ''{2}'', but the maximum level is ''{3}''.", collectionId, tileMatrixSetId, rule.getMax(), zoomLevelsCfg.getMax()));
}
for (String property : rule.getProperties()) {
if (!featureProperties.contains(property)) {
builder.addErrors(MessageFormat.format("A rule in the TILES module of collection ''{0}'' for tile matrix set ''{1}'' references property ''{2}'' that is not part of the feature schema.", collectionId, tileMatrixSetId, property));
}
}
for (String property : rule.getGroupBy()) {
if (!featureProperties.contains(property)) {
builder.addErrors(MessageFormat.format("A rule in the TILES module of collection ''{0}'' for tile matrix set ''{1}'' references group-by property ''{2}'' that is not part of the feature schema.", collectionId, tileMatrixSetId, property));
}
}
}
}
}
}
}
}
return builder.build();
}
use of de.ii.ogcapi.tiles.domain.Rule in project ldproxy by interactive-instruments.
the class TileFormatMVT method getQuery.
@Override
public FeatureQuery getQuery(Tile tile, List<OgcApiQueryParameter> allowedParameters, Map<String, String> queryParameters, TilesConfiguration tilesConfiguration, URICustomizer uriCustomizer) {
String collectionId = tile.getCollectionId();
String tileMatrixSetId = tile.getTileMatrixSet().getId();
int level = tile.getTileLevel();
final Map<String, List<PredefinedFilter>> predefFilters = tilesConfiguration.getFiltersDerived();
final String predefFilter = (Objects.nonNull(predefFilters) && predefFilters.containsKey(tileMatrixSetId)) ? predefFilters.get(tileMatrixSetId).stream().filter(filter -> filter.getMax() >= level && filter.getMin() <= level && filter.getFilter().isPresent()).map(filter -> filter.getFilter().get()).findAny().orElse(null) : null;
String featureTypeId = tile.getApiData().getCollections().get(collectionId).getExtension(FeaturesCoreConfiguration.class).map(cfg -> cfg.getFeatureType().orElse(collectionId)).orElse(collectionId);
ImmutableFeatureQuery.Builder queryBuilder = ImmutableFeatureQuery.builder().type(featureTypeId).limit(Objects.requireNonNullElse(tilesConfiguration.getLimitDerived(), CapabilityTiles.LIMIT_DEFAULT)).offset(0).crs(tile.getTileMatrixSet().getCrs()).maxAllowableOffset(getMaxAllowableOffset(tile));
final Map<String, List<Rule>> rules = tilesConfiguration.getRulesDerived();
if (!queryParameters.containsKey("properties") && (Objects.nonNull(rules) && rules.containsKey(tileMatrixSetId))) {
List<String> properties = rules.get(tileMatrixSetId).stream().filter(rule -> rule.getMax() >= level && rule.getMin() <= level).map(Rule::getProperties).flatMap(Collection::stream).collect(Collectors.toList());
if (!properties.isEmpty()) {
queryParameters = ImmutableMap.<String, String>builder().putAll(queryParameters).put("properties", String.join(",", properties)).build();
}
}
OgcApiDataV2 apiData = tile.getApiData();
FeatureTypeConfigurationOgcApi collectionData = apiData.getCollections().get(collectionId);
final Map<String, String> filterableFields = queryParser.getFilterableFields(apiData, collectionData);
final Map<String, String> queryableTypes = queryParser.getQueryableTypes(apiData, collectionData);
Set<String> filterParameters = ImmutableSet.of();
for (OgcApiQueryParameter parameter : allowedParameters) {
filterParameters = parameter.getFilterParameters(filterParameters, apiData, collectionData.getId());
queryParameters = parameter.transformParameters(collectionData, queryParameters, apiData);
}
final Set<String> finalFilterParameters = filterParameters;
final Map<String, String> filters = queryParameters.entrySet().stream().filter(entry -> finalFilterParameters.contains(entry.getKey()) || filterableFields.containsKey(entry.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
for (OgcApiQueryParameter parameter : allowedParameters) {
parameter.transformQuery(collectionData, queryBuilder, queryParameters, apiData);
}
BoundingBox bbox = tile.getBoundingBox();
// reduce bbox to the area in which there is data (to avoid coordinate transformation issues
// with large scale and data that is stored in a regional, projected CRS)
final EpsgCrs crs = bbox.getEpsgCrs();
final Optional<BoundingBox> dataBbox = tile.getApi().getSpatialExtent(collectionId, crs);
if (dataBbox.isPresent()) {
bbox = ImmutableList.of(bbox, dataBbox.get()).stream().map(BoundingBox::toArray).reduce((doubles, doubles2) -> new double[] { Math.max(doubles[0], doubles2[0]), Math.max(doubles[1], doubles2[1]), Math.min(doubles[2], doubles2[2]), Math.min(doubles[3], doubles2[3]) }).map(doubles -> BoundingBox.of(doubles[0], doubles[1], doubles[2], doubles[3], crs)).orElse(bbox);
}
CqlPredicate spatialPredicate = CqlPredicate.of(SpatialOperation.of(SpatialOperator.S_INTERSECTS, filterableFields.get(PARAMETER_BBOX), bbox));
if (predefFilter != null || !filters.isEmpty()) {
Optional<CqlFilter> otherFilter = Optional.empty();
Optional<CqlFilter> configFilter = Optional.empty();
if (!filters.isEmpty()) {
Optional<String> filterLang = uriCustomizer.getQueryParams().stream().filter(param -> "filter-lang".equals(param.getName())).map(NameValuePair::getValue).findFirst();
Cql.Format cqlFormat = Cql.Format.TEXT;
if (filterLang.isPresent() && "cql2-json".equals(filterLang.get())) {
cqlFormat = Cql.Format.JSON;
}
otherFilter = queryParser.getFilterFromQuery(filters, filterableFields, ImmutableSet.of("filter"), queryableTypes, cqlFormat);
}
if (predefFilter != null) {
configFilter = queryParser.getFilterFromQuery(ImmutableMap.of("filter", predefFilter), filterableFields, ImmutableSet.of("filter"), queryableTypes, Cql.Format.TEXT);
}
CqlFilter combinedFilter;
if (otherFilter.isPresent() && configFilter.isPresent()) {
combinedFilter = CqlFilter.of(And.of(otherFilter.get(), configFilter.get(), spatialPredicate));
} else if (otherFilter.isPresent()) {
combinedFilter = CqlFilter.of(And.of(otherFilter.get(), spatialPredicate));
} else if (configFilter.isPresent()) {
combinedFilter = CqlFilter.of(And.of(configFilter.get(), spatialPredicate));
} else {
combinedFilter = CqlFilter.of(spatialPredicate);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.trace("Filter: {}", combinedFilter);
}
queryBuilder.filter(combinedFilter);
} else {
queryBuilder.filter(CqlFilter.of(spatialPredicate));
}
return queryBuilder.build();
}
Aggregations