Skip to content

Commit

Permalink
Declarative schema from configuration file (#160)
Browse files Browse the repository at this point in the history
  • Loading branch information
ZeLonewolf authored Jun 7, 2022
1 parent 74b7474 commit da12fef
Show file tree
Hide file tree
Showing 45 changed files with 2,007 additions and 21 deletions.
1 change: 0 additions & 1 deletion planetiler-basemap/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.30</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,25 @@ public String getString(String key, String description, String defaultValue) {
return value;
}

/** Returns a {@link Path} parsed from {@code key} argument which may or may not exist. */
/** Returns a {@link Path} parsed from {@code key} argument, or fall back to a default if the argument is not set. */
public Path file(String key, String description, Path defaultValue) {
String value = getArg(key);
Path file = value == null ? defaultValue : Path.of(value);
logArgValue(key, description, file);
return file;
}

/** Returns a {@link Path} parsed from {@code key} argument which may or may not exist. */
public Path file(String key, String description) {
String value = getArg(key);
if (value == null) {
throw new IllegalArgumentException("Missing required parameter: " + key + " (" + description + ")");
}
Path file = Path.of(value);
logArgValue(key, description, file);
return file;
}

/**
* Returns a {@link Path} parsed from {@code key} argument which must exist for the program to function.
*
Expand All @@ -221,6 +232,19 @@ public Path inputFile(String key, String description, Path defaultValue) {
return path;
}

/**
* Returns a {@link Path} parsed from a required {@code key} argument which must exist for the program to function.
*
* @throws IllegalArgumentException if the file does not exist or if the parameter is not provided.
*/
public Path inputFile(String key, String description) {
Path path = file(key, description);
if (!Files.exists(path)) {
throw new IllegalArgumentException(path + " does not exist");
}
return path;
}

/** Returns a boolean parsed from {@code key} argument where {@code "true"} is true and anything else is false. */
public boolean getBoolean(String key, String description, boolean defaultValue) {
boolean value = "true".equalsIgnoreCase(getArg(key, Boolean.toString(defaultValue)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public interface Expression {
Expression FALSE = new Constant(false, "FALSE");
BiFunction<WithTags, String, Object> GET_TAG = WithTags::getTag;

List<String> dummyList = new NoopList<>();

static And and(Expression... children) {
return and(List.of(children));
}
Expand Down Expand Up @@ -247,6 +249,26 @@ default boolean contains(Predicate<Expression> filter) {
*/
boolean evaluate(WithTags input, List<String> matchKeys);

//A list that silently drops all additions
class NoopList<T> extends ArrayList<T> {
private static final long serialVersionUID = 1L;

@Override
public boolean add(T t) {
return true;
}
}

/**
* Returns true if this expression matches an input element.
*
* @param input the input element
* @return true if this expression matches the input element
*/
default boolean evaluate(WithTags input) {
return evaluate(input, dummyList);
}

/** Returns Java code that can be used to reconstruct this expression. */
String generateJavaCode();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
package com.onthegomap.planetiler.geo;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.onthegomap.planetiler.FeatureCollector;
import com.onthegomap.planetiler.FeatureCollector.Feature;
import com.onthegomap.planetiler.expression.Expression;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Lineal;
import org.locationtech.jts.geom.Polygonal;
import org.locationtech.jts.geom.Puntal;
import vector_tile.VectorTileProto;

public enum GeometryType {
UNKNOWN(VectorTileProto.Tile.GeomType.UNKNOWN, 0),
POINT(VectorTileProto.Tile.GeomType.POINT, 1),
LINE(VectorTileProto.Tile.GeomType.LINESTRING, 2),
POLYGON(VectorTileProto.Tile.GeomType.POLYGON, 4);
UNKNOWN(VectorTileProto.Tile.GeomType.UNKNOWN, 0, (f, l) -> {
throw new UnsupportedOperationException();
}, "unknown"),
@JsonProperty("point")
POINT(VectorTileProto.Tile.GeomType.POINT, 1, FeatureCollector::point, "point"),
@JsonProperty("line")
LINE(VectorTileProto.Tile.GeomType.LINESTRING, 2, FeatureCollector::line, "linestring"),
@JsonProperty("polygon")
POLYGON(VectorTileProto.Tile.GeomType.POLYGON, 4, FeatureCollector::polygon, "polygon");

private final VectorTileProto.Tile.GeomType protobufType;
private final int minPoints;
private final BiFunction<FeatureCollector, String, Feature> geometryFactory;
private final String matchTypeString;

GeometryType(VectorTileProto.Tile.GeomType protobufType, int minPoints) {
GeometryType(VectorTileProto.Tile.GeomType protobufType, int minPoints,
BiFunction<FeatureCollector, String, Feature> geometryFactory, String matchTypeString) {
this.protobufType = protobufType;
this.minPoints = minPoints;
this.geometryFactory = geometryFactory;
this.matchTypeString = matchTypeString;
}

public static GeometryType valueOf(Geometry geom) {
Expand Down Expand Up @@ -49,4 +65,25 @@ public VectorTileProto.Tile.GeomType asProtobufType() {
public int minPoints() {
return minPoints;
}

/**
* Generates a factory method which creates a {@link Feature} from a {@link FeatureCollector} of the appropriate
* geometry type.
*
* @param layerName - name of the layer
* @return geometry factory method
*/
public Function<FeatureCollector, Feature> geometryFactory(String layerName) {
return features -> geometryFactory.apply(features, layerName);
}

/**
* Generates a test for whether a source feature is of the correct geometry to be included in the tile.
*
* @return geometry test method
*/
public Expression featureTest() {
return Expression.matchType(matchTypeString);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,18 @@ public static void assertNumFeatures(Mbtiles db, String layer, int zoom, Map<Str
}
}

public static void assertMinFeatureCount(Mbtiles db, String layer, int zoom, Map<String, Object> attrs,
Envelope envelope, int expected, Class<? extends Geometry> clazz) {
try {
int num = Verify.getNumFeatures(db, layer, zoom, attrs, envelope, clazz);

assertTrue(expected < num,
"z%d features in %s, expected at least %d got %d".formatted(zoom, layer, expected, num));
} catch (GeometryException e) {
fail(e);
}
}

public static void assertFeatureNear(Mbtiles db, String layer, Map<String, Object> attrs, double lng, double lat,
int minzoom, int maxzoom) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
package com.onthegomap.planetiler.expression;

import static com.onthegomap.planetiler.TestUtils.newPoint;
import static com.onthegomap.planetiler.expression.Expression.*;
import static com.onthegomap.planetiler.expression.ExpressionTestUtil.featureWithTags;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.onthegomap.planetiler.reader.SimpleFeature;
import com.onthegomap.planetiler.reader.SourceFeature;
import com.onthegomap.planetiler.reader.WithTags;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;

Expand All @@ -22,14 +18,6 @@ class ExpressionTest {
public static final Expression.MatchAny matchCD = matchAny("c", "d");
public static final Expression.MatchAny matchBC = matchAny("b", "c");

static SourceFeature featureWithTags(String... tags) {
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < tags.length; i += 2) {
map.put(tags[i], tags[i + 1]);
}
return SimpleFeature.create(newPoint(0, 0), map);
}

@Test
void testSimplify() {
assertEquals(matchAB, matchAB.simplify());
Expand Down Expand Up @@ -159,4 +147,35 @@ void testStringifyExpression() {
var expression = matchAnyTyped("key", WithTags::getDirection, 1);
assertThrows(UnsupportedOperationException.class, expression::generateJavaCode);
}

@Test
void testEvaluate() {
WithTags feature = featureWithTags("key1", "value1", "key2", "value2");

//And
assertTrue(and(matchAny("key1", "value1"), matchAny("key2", "value2")).evaluate(feature));
assertFalse(and(matchAny("key1", "value1"), matchAny("key2", "wrong")).evaluate(feature));
assertFalse(and(matchAny("key1", "wrong"), matchAny("key2", "value2")).evaluate(feature));
assertFalse(and(matchAny("key1", "wrong"), matchAny("key2", "wrong")).evaluate(feature));

//Or
assertTrue(or(matchAny("key1", "value1"), matchAny("key2", "value2")).evaluate(feature));
assertTrue(or(matchAny("key1", "value1"), matchAny("key2", "wrong")).evaluate(feature));
assertTrue(or(matchAny("key1", "wrong"), matchAny("key2", "value2")).evaluate(feature));
assertFalse(or(matchAny("key1", "wrong"), matchAny("key2", "wrong")).evaluate(feature));

//Not
assertFalse(not(matchAny("key1", "value1")).evaluate(feature));
assertTrue(not(matchAny("key1", "wrong")).evaluate(feature));

//MatchField
assertTrue(matchField("key1").evaluate(feature));
assertFalse(matchField("wrong").evaluate(feature));
assertTrue(not(matchAny("key1", "")).evaluate(feature));
assertTrue(matchAny("wrong", "").evaluate(feature));

//Constants
assertTrue(TRUE.evaluate(feature));
assertFalse(FALSE.evaluate(feature));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.onthegomap.planetiler.expression;

import static com.onthegomap.planetiler.TestUtils.newPoint;

import com.onthegomap.planetiler.reader.SimpleFeature;
import com.onthegomap.planetiler.reader.SourceFeature;
import java.util.HashMap;
import java.util.Map;

public class ExpressionTestUtil {
static SourceFeature featureWithTags(String... tags) {
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < tags.length; i += 2) {
map.put(tags[i], tags[i + 1]);
}
return SimpleFeature.create(newPoint(0, 0), map);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import static com.onthegomap.planetiler.TestUtils.newPoint;
import static com.onthegomap.planetiler.TestUtils.rectangle;
import static com.onthegomap.planetiler.expression.Expression.*;
import static com.onthegomap.planetiler.expression.ExpressionTest.featureWithTags;
import static com.onthegomap.planetiler.expression.ExpressionTestUtil.featureWithTags;
import static com.onthegomap.planetiler.expression.MultiExpression.entry;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.onthegomap.planetiler.geo;

import static java.util.Collections.emptyList;

import com.onthegomap.planetiler.TestUtils;
import com.onthegomap.planetiler.reader.SimpleFeature;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class GeometryTypeTest {

@Test
void testGeometryFactory() throws Exception {
Map<String, Object> tags = Map.of("key1", "value1");

var line =
SimpleFeature.createFakeOsmFeature(TestUtils.newLineString(0, 0, 1, 0, 1, 1), tags, "osm", null, 1, emptyList());
var point =
SimpleFeature.createFakeOsmFeature(TestUtils.newPoint(0, 0), tags, "osm", null, 1, emptyList());
var poly =
SimpleFeature.createFakeOsmFeature(TestUtils.newPolygon(0, 0, 1, 0, 1, 1, 0, 0), tags, "osm", null, 1,
emptyList());

Assertions.assertTrue(GeometryType.LINE.featureTest().evaluate(line));
Assertions.assertFalse(GeometryType.LINE.featureTest().evaluate(point));
Assertions.assertFalse(GeometryType.LINE.featureTest().evaluate(poly));

Assertions.assertFalse(GeometryType.POINT.featureTest().evaluate(line));
Assertions.assertTrue(GeometryType.POINT.featureTest().evaluate(point));
Assertions.assertFalse(GeometryType.POINT.featureTest().evaluate(poly));

Assertions.assertFalse(GeometryType.POLYGON.featureTest().evaluate(line));
Assertions.assertFalse(GeometryType.POLYGON.featureTest().evaluate(point));
Assertions.assertTrue(GeometryType.POLYGON.featureTest().evaluate(poly));

Assertions.assertThrows(Exception.class, () -> GeometryType.UNKNOWN.featureTest().evaluate(point));
Assertions.assertThrows(Exception.class, () -> GeometryType.UNKNOWN.featureTest().evaluate(line));
Assertions.assertThrows(Exception.class, () -> GeometryType.UNKNOWN.featureTest().evaluate(poly));
}
}
1 change: 1 addition & 0 deletions planetiler-custommap/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.mbtiles
Loading

0 comments on commit da12fef

Please sign in to comment.