progress on generating domain models

pull/1/head
Mike Barry 2021-06-10 21:15:27 -04:00
rodzic 84294010fe
commit 31097c669f
14 zmienionych plików z 2552 dodań i 57 usunięć

Wyświetl plik

@ -15,7 +15,6 @@
<properties>
<graphhopper.version>2.3</graphhopper.version>
<geotools.version>25.0</geotools.version>
<junit.version>5.7.1</junit.version>
<log4j.version>2.14.1</log4j.version>
<prometheus.version>0.11.0</prometheus.version>
</properties>
@ -111,31 +110,6 @@
<artifactId>simpleclient_hotspot</artifactId>
<version>${prometheus.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.9.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

Wyświetl plik

@ -1,8 +1,81 @@
package com.onthegomap.flatmap;
import com.carrotsearch.hppc.ObjectIntMap;
import com.graphhopper.coll.GHObjectIntHashMap;
import java.util.Map;
import java.util.Set;
public class Parse {
public static long parseLong(String idString) {
return Long.parseLong(idString);
public static Long parseLongOrNull(Object tag) {
try {
return tag == null ? null : tag instanceof Number number ? number.longValue() : Long.parseLong(tag.toString());
} catch (NumberFormatException e) {
return null;
}
}
public static long parseLong(Object tag) {
try {
return tag == null ? 0 : tag instanceof Number number ? number.longValue() : Long.parseLong(tag.toString());
} catch (NumberFormatException e) {
return 0;
}
}
private static final Set<String> booleanFalseValues = Set.of("", "0", "false", "no");
public static boolean bool(Object tag) {
return !(tag == null || booleanFalseValues.contains(tag.toString()));
}
public static int boolInt(Object tag) {
return bool(tag) ? 1 : 0;
}
private static final Set<String> forwardDirections = Set.of("1", "yes", "true");
public static int direction(Object string) {
if (forwardDirections.contains(string(string))) {
return 1;
} else if ("-1".equals(string)) {
return -1;
} else {
return 0;
}
}
private static final ObjectIntMap<String> defaultRank = new GHObjectIntHashMap<>();
static {
defaultRank.put("minor", 3);
defaultRank.put("road", 3);
defaultRank.put("unclassified", 3);
defaultRank.put("residential", 3);
defaultRank.put("tertiary_link", 3);
defaultRank.put("tertiary", 4);
defaultRank.put("secondary_link", 3);
defaultRank.put("secondary", 5);
defaultRank.put("primary_link", 3);
defaultRank.put("primary", 6);
defaultRank.put("trunk_link", 3);
defaultRank.put("trunk", 8);
defaultRank.put("motorway_link", 3);
defaultRank.put("motorway", 9);
}
private static String string(Object object) {
return object == null ? null : object.toString();
}
public static int wayzorder(Map<String, Object> tags) {
long z = Parse.parseLong(tags.get("layer")) * 10 +
defaultRank.getOrDefault(
string(tags.get("highway")),
tags.containsKey("railway") ? 7 : 0
) +
(Parse.boolInt(tags.get("tunnel")) * -10L) +
(Parse.boolInt(tags.get("bridge")) * 10L);
return Math.abs(z) < 10_000 ? (int) z : 0;
}
}

Wyświetl plik

@ -174,4 +174,25 @@ public abstract class SourceFeature {
public final long id() {
return id;
}
public String getString(String key) {
Object value = getTag(key);
return value == null ? null : value.toString();
}
public boolean getBoolean(String key) {
return Parse.bool(getTag(key));
}
public long getLong(String key) {
return Parse.parseLong(getTag(key));
}
public int getDirection(String key) {
return Parse.direction(getTag(key));
}
public int getWayZorder() {
return Parse.wayzorder(properties);
}
}

Wyświetl plik

@ -0,0 +1,66 @@
package com.onthegomap.flatmap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import java.util.Map;
import java.util.stream.Stream;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
public class ParseTest {
@ParameterizedTest
@CsvSource({
"0, false, 0",
"false, false, 0",
"no, false, 0",
"yes, true, 1",
"true, true, 1",
"ok, true, 1",
})
public void testBoolean(String in, boolean out, int boolint) {
assertEquals(out, Parse.bool(in));
assertEquals(boolint, Parse.boolInt(in));
}
@ParameterizedTest
@CsvSource(value = {
"0, 0, 0",
"false, 0, null",
"123, 123, 123"
}, nullValues = {"null"})
public void testLong(String in, long out, Long obj) {
assertEquals(out, Parse.parseLong(in));
assertEquals(obj, Parse.parseLongOrNull(in));
}
@ParameterizedTest
@CsvSource({
"1, 1",
"yes, 1",
"true, 1",
"-1, -1",
"2, 0",
"0, 0"
})
public void testDirection(String in, int out) {
assertEquals(out, Parse.direction(in));
}
@TestFactory
public Stream<DynamicTest> testWayzorder() {
return Stream.<Map.Entry<Map<String, Object>, Integer>>of(
Map.entry(Map.of(), 0),
Map.entry(Map.of("layer", 1), 10),
Map.entry(Map.of("layer", -3), -30),
Map.entry(Map.of("highway", "motorway"), 9),
Map.entry(Map.of("railway", "anything"), 7),
Map.entry(Map.of("railway", "anything", "tunnel", "1"), -3),
Map.entry(Map.of("railway", "anything", "bridge", "1"), 17)
).map(entry -> dynamicTest(entry.getKey().toString(),
() -> assertEquals(entry.getValue(), Parse.wayzorder(entry.getKey()))));
}
}

Wyświetl plik

@ -19,9 +19,9 @@
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.29</version>
</dependency>
</dependencies>

Wyświetl plik

@ -0,0 +1,130 @@
package com.onthegomap.flatmap.openmaptiles;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public interface Expression {
static And and(Expression... children) {
return and(List.of(children));
}
static And and(List<Expression> children) {
return new And(children);
}
static Or or(Expression... children) {
return or(List.of(children));
}
static Or or(List<Expression> children) {
return new Or(children);
}
static MatchAny matchAny(String field, String... values) {
return matchAny(field, List.of(values));
}
static MatchAny matchAny(String field, List<String> values) {
return new MatchAny(field, values);
}
static MatchField matchField(String field) {
return new MatchField(field);
}
private static String listToString(List<?> items) {
return items.stream().map(Object::toString).collect(Collectors.joining(", "));
}
default Expression simplify() {
return simplify(this);
}
private static Expression simplifyOnce(Expression expression) {
if (expression instanceof Or or) {
if (or.children.size() == 1) {
return simplifyOnce(or.children.get(0));
}
return or(or.children.stream()
// hoist children
.flatMap(child -> child instanceof Or childOr ? childOr.children.stream() : Stream.of(child))
.map(Expression::simplifyOnce).toList());
} else if (expression instanceof And and) {
if (and.children.size() == 1) {
return simplifyOnce(and.children.get(0));
}
return and(and.children.stream()
// hoist children
.flatMap(child -> child instanceof And childAnd ? childAnd.children.stream() : Stream.of(child))
.map(Expression::simplifyOnce).toList());
} else {
return expression;
}
}
private static Expression simplify(Expression initial) {
Expression simplified = initial;
Set<Expression> seen = new HashSet<>();
seen.add(simplified);
while (true) {
simplified = simplifyOnce(simplified);
if (seen.contains(simplified) || seen.size() > 100) {
return simplified;
}
seen.add(simplified);
}
}
record And(List<Expression> children) implements Expression {
@Override
public String toString() {
return "and(" + listToString(children) + ")";
}
}
record Or(List<Expression> children) implements Expression {
@Override
public String toString() {
return "or(" + listToString(children) + ")";
}
}
record MatchExact(String field, String value) implements Expression {
@Override
public String toString() {
return "matchExact(" + Generate.quote(field) + ", " + Generate.quote(value) + ")";
}
}
record MatchAny(String field, List<String> values) implements Expression {
@Override
public String toString() {
return "matchAny(" + Generate.quote(field) + ", " + values.stream().map(Generate::quote)
.collect(Collectors.joining(", ")) + ")";
}
}
record MatchField(String field) implements Expression {
@Override
public String toString() {
return "matchField(" + Generate.quote(field) + ")";
}
}
record MatchWildcard(String field, String wildcard) implements Expression {
@Override
public String toString() {
return "matchWildcard(" + Generate.quote(field) + ", " + Generate.quote(wildcard) + ")";
}
}
}

Wyświetl plik

@ -0,0 +1,5 @@
package com.onthegomap.flatmap.openmaptiles;
import java.util.Map;
public record FieldMapping(Map<String, Expression> mappings) {}

Wyświetl plik

@ -1,27 +1,41 @@
package com.onthegomap.flatmap.openmaptiles;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import static com.onthegomap.flatmap.openmaptiles.Expression.and;
import static com.onthegomap.flatmap.openmaptiles.Expression.matchAny;
import static com.onthegomap.flatmap.openmaptiles.Expression.matchField;
import static com.onthegomap.flatmap.openmaptiles.Expression.or;
import static java.util.stream.Collectors.joining;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.google.common.base.CaseFormat;
import com.onthegomap.flatmap.Arguments;
import com.onthegomap.flatmap.FileUtils;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
public class Generate {
private static Logger LOGGER = LoggerFactory.getLogger(Generate.class);
private static final Logger LOGGER = LoggerFactory.getLogger(Generate.class);
private static record OpenmaptilesConfig(
OpenmaptilesTileSet tileset
@ -38,7 +52,6 @@ public class Generate {
@JsonIgnoreProperties(ignoreUnknown = true)
private static record LayerDetails(
String id,
String requires,
String description,
Map<String, JsonNode> fields,
double buffer_size
@ -55,55 +68,378 @@ public class Generate {
List<Datasource> datasources
) {}
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "columnId")
private static record Imposm3Column(
String columnId,
String type,
String name,
String key
String key,
boolean from_member
) {}
private static record Imposm3Filters(
Map<String, List<String>> reject,
Map<String, List<String>> require
) {}
private static record Imposm3Table(
String type,
@JsonProperty("_resolve_wikidata") boolean resolveWikidata,
List<Imposm3Column> columns,
JsonNode mapping
Imposm3Filters filters,
JsonNode mapping,
Map<String, Map<String, List<String>>> type_mappings
) {}
@JsonIgnoreProperties(ignoreUnknown = true)
private static record Imposm3Mapping(
Map<String, Imposm3Table> tables
) {}
public static void main(String[] args) throws IOException, URISyntaxException {
var objectMapper = new ObjectMapper(new YAMLFactory());
private static final ObjectMapper mapper = new ObjectMapper();
private static final Yaml yaml;
static {
var options = new LoaderOptions();
options.setMaxAliasesForCollections(1_000);
yaml = new Yaml(options);
}
private static <T> T load(URL url, Class<T> clazz) throws IOException {
LOGGER.info("reading " + url);
try (var stream = url.openStream()) {
Map<String, Object> parsed = yaml.load(stream);
return mapper.convertValue(parsed, clazz);
}
}
static <T> T parseYaml(String string, Class<T> clazz) {
Map<String, Object> parsed = yaml.load(string);
return mapper.convertValue(parsed, clazz);
}
static JsonNode parseYaml(String string) {
return parseYaml(string, JsonNode.class);
}
public static void main(String[] args) throws IOException {
Arguments arguments = Arguments.fromJvmProperties();
String tag = arguments.get("tag", "openmaptiles tag to use", "v3.12.2");
String base = "https://raw.githubusercontent.com/openmaptiles/openmaptiles/" + tag + "/";
base = "jar:file:/tmp/openmaptiles-3.12.2.zip!/openmaptiles-3.12.2/";
var rootUrl = new URL(base + "openmaptiles.yaml");
LOGGER.info("reading " + rootUrl);
OpenmaptilesConfig config = objectMapper.readValue(rootUrl, OpenmaptilesConfig.class);
OpenmaptilesConfig config = load(rootUrl, OpenmaptilesConfig.class);
List<LayerConfig> layers = new ArrayList<>();
Set<URI> mappingFiles = new LinkedHashSet<>();
Set<String> mappingFiles = new LinkedHashSet<>();
for (String layerFile : config.tileset.layers) {
URL layerURL = new URL(base + layerFile);
LOGGER.info("reading " + layerURL);
LayerConfig layer = objectMapper.readValue(layerURL, LayerConfig.class);
LayerConfig layer = load(layerURL, LayerConfig.class);
layers.add(layer);
for (Datasource datasource : layer.datasources) {
if ("imposm3".equals(datasource.type)) {
mappingFiles.add(layerURL.toURI().resolve(datasource.mapping_file).normalize());
String mappingPath = Path.of(layerFile).resolveSibling(datasource.mapping_file).normalize().toString();
mappingFiles.add(base + mappingPath);
} else {
LOGGER.warn("Unknown datasource type: " + datasource.type);
}
}
}
for (URI uri : mappingFiles) {
LOGGER.info("reading " + uri);
Imposm3Mapping layer = objectMapper.readValue(uri.toURL(), Imposm3Mapping.class);
LOGGER.info(layer.toString());
Map<String, Imposm3Table> tables = new LinkedHashMap<>();
for (String uri : mappingFiles) {
Imposm3Mapping layer = load(new URL(uri), Imposm3Mapping.class);
tables.putAll(layer.tables);
}
String packageName = "com.onthegomap.flatmap.openmaptiles.generated";
String[] packageParts = packageName.split("\\.");
Path output = Path.of("openmaptiles", "src", "main", "java")
.resolve(Path.of(packageParts[0], Arrays.copyOfRange(packageParts, 1, packageParts.length)));
FileUtils.deleteDirectory(output);
Files.createDirectories(output);
emitLayerDefinitions(layers, packageName, output);
emitTableDefinitions(tables, packageName, output);
// layers.forEach((layer) -> LOGGER.info("layer: " + layer.layer.id));
// tables.forEach((key, val) -> LOGGER.info("table: " + key));
}
private static void emitTableDefinitions(Map<String, Imposm3Table> tables, String packageName, Path output)
throws IOException {
StringBuilder tablesClass = new StringBuilder();
tablesClass.append("""
// AUTOGENERATED BY Generate.java -- DO NOT MODIFY
package %s;
import static com.onthegomap.flatmap.openmaptiles.Expression.*;
import com.onthegomap.flatmap.openmaptiles.FieldMapping;
import java.util.List;
import java.util.Map;
public class Tables {
""".formatted(packageName));
for (var entry : tables.entrySet()) {
String key = entry.getKey();
Imposm3Table table = entry.getValue();
List<OsmTableField> fields = getFields(table);
if (fields.size() <= 1) {
tablesClass.append("public static record %s(%s) {}".formatted(
lowerUnderscoreToUpperCamel("osm_" + key),
fields.stream().map(c -> c.clazz + " " + lowerUnderscoreToLowerCamel(c.name))
.collect(joining(", "))).indent(2));
} else {
tablesClass.append("""
public static record %s(%s) {
public %s(com.onthegomap.flatmap.SourceFeature source) {
this(%s);
}
}
""".formatted(
lowerUnderscoreToUpperCamel("osm_" + key),
fields.stream().map(c -> c.clazz + " " + lowerUnderscoreToLowerCamel(c.name))
.collect(joining(", ")),
lowerUnderscoreToUpperCamel("osm_" + key),
fields.stream().map(c -> c.extractCode).collect(joining(", "))
).indent(2));
}
}
tablesClass.append("}");
Files.writeString(output.resolve("Tables.java"), tablesClass);
}
private static List<OsmTableField> getFields(Imposm3Table tableDefinition) {
List<OsmTableField> result = new ArrayList<>();
result.add(new OsmTableField("com.onthegomap.flatmap.SourceFeature", "source", "source"));
// TODO columns used, and from_member
for (Imposm3Column col : tableDefinition.columns) {
switch (col.type) {
case "id", "validated_geometry", "area", "hstore_tags", "geometry" -> {
// do nothing - already on source feature
}
case "member_id", "member_role", "member_type", "member_index" -> {
}
case "mapping_key" -> {
// TODO?
}
case "mapping_value" -> {
// TODO??
}
case "string" -> result
.add(new OsmTableField("String", col.name,
"source.getString(\"%s\")".formatted(Objects.requireNonNull(col.key, col.toString()))));
case "bool" -> result
.add(new OsmTableField("boolean", col.name,
"source.getBoolean(\"%s\")".formatted(Objects.requireNonNull(col.key, col.toString()))));
case "integer" -> result
.add(new OsmTableField("long", col.name,
"source.getLong(\"%s\")".formatted(Objects.requireNonNull(col.key, col.toString()))));
case "wayzorder" -> result.add(new OsmTableField("int", col.name, "source.getWayZorder()"));
case "direction" -> result.add(new OsmTableField("int", col.name,
"source.getDirection(\"%s\")".formatted(Objects.requireNonNull(col.key, col.toString()))));
default -> throw new IllegalArgumentException("Unhandled column: " + col.type);
}
}
return result;
}
private static record OsmTableField(
String clazz,
String name,
String extractCode
) {}
private static void emitLayerDefinitions(List<LayerConfig> layers, String packageName, Path output)
throws IOException {
StringBuilder layersClass = new StringBuilder();
layersClass.append("""
// AUTOGENERATED BY Generate.java -- DO NOT MODIFY
package %s;
import static com.onthegomap.flatmap.openmaptiles.Expression.*;
import com.onthegomap.flatmap.openmaptiles.FieldMapping;
import java.util.List;
import java.util.Map;
public class Layers {
""".formatted(packageName));
for (var layer : layers) {
String layerName = layer.layer.id;
String className = lowerUnderscoreToUpperCamel(layerName);
StringBuilder fields = new StringBuilder();
StringBuilder fieldValues = new StringBuilder();
StringBuilder fieldMappings = new StringBuilder();
layer.layer.fields.forEach((name, value) -> {
JsonNode valuesNode = value.get("values");
List<String> valuesForComment = valuesNode == null ? List.of() : valuesNode.isArray() ?
iterToList(valuesNode.elements()).stream().map(Objects::toString).toList() :
iterToList(valuesNode.fieldNames());
String javadocDescription = escapeJavadoc(getFieldDescription(value));
fields.append("""
%s
public static final String %s = %s;
""".formatted(
valuesForComment.isEmpty() ? "/** %s */".formatted(javadocDescription) : """
/**
* %s
* <p>
* allowed values:
* <ul>
* %s
* </ul>
*/
""".stripTrailing().formatted(javadocDescription,
valuesForComment.stream().map(v -> "<li>" + v).collect(joining("\n * "))),
name.toUpperCase(Locale.ROOT),
quote(name)
).indent(4));
List<String> values = valuesNode == null ? List.of() : valuesNode.isArray() ?
iterToList(valuesNode.elements()).stream().filter(JsonNode::isTextual).map(JsonNode::textValue)
.map(t -> t.replaceAll(" .*", "")).toList() :
iterToList(valuesNode.fieldNames());
if (values.size() > 0) {
fieldValues.append("""
public static final class %s {
%s
}
""".formatted(
lowerUnderscoreToUpperCamel(name),
values.stream()
.map(v -> "public static final String %s = %s;"
.formatted(v.toUpperCase(Locale.ROOT).replace('-', '_'), quote(v)))
.collect(joining("\n")).indent(2).strip()
).indent(4));
}
if (valuesNode != null && valuesNode.isObject()) {
FieldMapping mapping = generateFieldMapping(valuesNode);
fieldMappings.append(" public static final FieldMapping %s = %s;\n"
.formatted(lowerUnderscoreToUpperCamel(name), generateCode(mapping)));
}
});
layersClass.append("""
/** %s */
public static class %s {
public static final double BUFFER_SIZE = %s;
public static final String NAME = %s;
public static final class Fields {
%s
}
public static final class FieldValues {
%s
}
public static final class FieldMappings {
%s
}
}
""".formatted(
escapeJavadoc(layer.layer.description),
className,
layer.layer.buffer_size,
quote(layerName),
fields.toString().strip(),
"// TODO", // fieldValues.toString().strip(),
fieldMappings.toString().strip()
).indent(2));
}
layersClass.append("}");
Files.writeString(output.resolve("Layers.java"), layersClass);
}
static FieldMapping generateFieldMapping(JsonNode valuesNode) {
FieldMapping mapping = new FieldMapping(new LinkedHashMap<>());
valuesNode.fields().forEachRemaining(entry -> {
String field = entry.getKey();
JsonNode node = entry.getValue();
Expression expression = or(parseExpression(node).toList()).simplify();
if (!expression.equals(or()) && !expression.equals(and())) {
mapping.mappings().put(field, expression);
}
});
return mapping;
}
private static Stream<Expression> parseExpression(JsonNode node) {
if (node.isObject()) {
List<String> keys = iterToList(node.fieldNames());
if (keys.contains("__AND__")) {
if (keys.size() > 1) {
throw new IllegalArgumentException("Cannot combine __AND__ with others");
}
return Stream.of(and(parseExpression(node.get("__AND__")).toList()));
} else if (keys.contains("__OR__")) {
if (keys.size() > 1) {
throw new IllegalArgumentException("Cannot combine __OR__ with others");
}
return Stream.of(or(parseExpression(node.get("__OR__")).toList()));
} else {
return iterToList(node.fields()).stream().map(entry -> {
String field = entry.getKey();
List<String> value = toFlatList(entry.getValue()).map(JsonNode::textValue).filter(Objects::nonNull).toList();
return value.isEmpty() ? matchField(field) : matchAny(field, value);
});
}
} else if (node.isArray()) {
return iterToList(node.elements()).stream().flatMap(Generate::parseExpression);
} else if (node.isNull()) {
return Stream.empty();
} else {
throw new IllegalArgumentException("parseExpression input not handled: " + node);
}
}
private static Stream<JsonNode> toFlatList(JsonNode node) {
return node.isArray() ? iterToList(node.elements()).stream().flatMap(Generate::toFlatList) : Stream.of(node);
}
private static String generateCode(FieldMapping mapping) {
return "new FieldMapping(Map.ofEntries(" + mapping.mappings().entrySet().stream()
.map(s -> "Map.entry(%s, %s)".formatted(quote(s.getKey()), s.getValue()))
.collect(joining(", ")) + "))";
}
private static String lowerUnderscoreToLowerCamel(String name) {
return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name);
}
private static String lowerUnderscoreToUpperCamel(String name) {
return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name);
}
private static <T> List<T> iterToList(Iterator<T> iter) {
List<T> result = new ArrayList<>();
iter.forEachRemaining(result::add);
return result;
}
private static String escapeJavadoc(String description) {
return description.replaceAll("[\n\r*\\s]+", " ");
}
private static String getFieldDescription(JsonNode value) {
if (value.isTextual()) {
return value.textValue();
} else {
return value.get("description").textValue();
}
}
static String quote(String other) {
if (other == null) {
return "null";
}
if (other.contains("\"")) {
throw new IllegalStateException("cannot quote: " + other);
}
return '"' + other + '"';
}
}

Wyświetl plik

@ -15,7 +15,7 @@ import org.slf4j.LoggerFactory;
public class OpenMapTilesProfile implements Profile {
private static boolean MERGE_Z13_BUILDINGS = false;
private static final boolean MERGE_Z13_BUILDINGS = false;
public static final String LAKE_CENTERLINE_SOURCE = "lake_centerlines";
public static final String WATER_POLYGON_SOURCE = "water_polygons";

Wyświetl plik

@ -0,0 +1,339 @@
// AUTOGENERATED BY Generate.java -- DO NOT MODIFY
package com.onthegomap.flatmap.openmaptiles.generated;
public class Tables {
public static record OsmWaterPolygon(
com.onthegomap.flatmap.SourceFeature source, String name, String nameEn, String nameDe, String natural,
String landuse, String waterway, boolean isIntermittent, boolean isTunnel, boolean isBridge
) {
public OsmWaterPolygon(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getString("natural"), source.getString("landuse"), source.getString("waterway"),
source.getBoolean("intermittent"), source.getBoolean("tunnel"), source.getBoolean("bridge"));
}
}
public static record OsmWaterwayLinestring(
com.onthegomap.flatmap.SourceFeature source, String waterway, String name, String nameEn, String nameDe,
boolean isTunnel, boolean isBridge, boolean isIntermittent
) {
public OsmWaterwayLinestring(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("waterway"), source.getString("name"), source.getString("name:en"),
source.getString("name:de"), source.getBoolean("tunnel"), source.getBoolean("bridge"),
source.getBoolean("intermittent"));
}
}
public static record OsmLandcoverPolygon(com.onthegomap.flatmap.SourceFeature source) {}
public static record OsmLandusePolygon(
com.onthegomap.flatmap.SourceFeature source, String landuse, String amenity, String leisure, String tourism,
String place, String waterway
) {
public OsmLandusePolygon(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("landuse"), source.getString("amenity"), source.getString("leisure"),
source.getString("tourism"), source.getString("place"), source.getString("waterway"));
}
}
public static record OsmPeakPoint(
com.onthegomap.flatmap.SourceFeature source, String name, String nameEn, String nameDe, String ele, String wikipedia
) {
public OsmPeakPoint(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getString("ele"), source.getString("wikipedia"));
}
}
public static record OsmParkPolygon(
com.onthegomap.flatmap.SourceFeature source, String name, String nameEn, String nameDe, String landuse,
String leisure, String boundary, String protectionTitle
) {
public OsmParkPolygon(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getString("landuse"), source.getString("leisure"), source.getString("boundary"),
source.getString("protection_title"));
}
}
public static record OsmBorderDispRelation(
com.onthegomap.flatmap.SourceFeature source, String name, String boundary, long adminLevel, String claimedBy,
String disputedBy, boolean maritime
) {
public OsmBorderDispRelation(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("name"), source.getString("boundary"), source.getLong("admin_level"),
source.getString("claimed_by"), source.getString("disputed_by"), source.getBoolean("maritime"));
}
}
public static record OsmAerowayPolygon(com.onthegomap.flatmap.SourceFeature source, String ref) {
public OsmAerowayPolygon(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("ref"));
}
}
public static record OsmAerowayLinestring(com.onthegomap.flatmap.SourceFeature source, String ref, String aeroway) {
public OsmAerowayLinestring(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("ref"), source.getString("aeroway"));
}
}
public static record OsmAerowayPoint(com.onthegomap.flatmap.SourceFeature source, String ref, String aeroway) {
public OsmAerowayPoint(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("ref"), source.getString("aeroway"));
}
}
public static record OsmHighwayLinestring(
com.onthegomap.flatmap.SourceFeature source, String highway, String construction, String ref, String network,
int zOrder, long layer, long level, boolean indoor, String name, String nameEn, String nameDe, String shortName,
boolean isTunnel, boolean isBridge, boolean isRamp, boolean isFord, int isOneway, boolean isArea, String service,
String usage, String publicTransport, String manMade, String bicycle, String foot, String horse, String mtbScale,
String surface
) {
public OsmHighwayLinestring(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("highway"), source.getString("construction"), source.getString("ref"),
source.getString("network"), source.getWayZorder(), source.getLong("layer"), source.getLong("level"),
source.getBoolean("indoor"), source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getString("short_name"), source.getBoolean("tunnel"), source.getBoolean("bridge"),
source.getBoolean("ramp"), source.getBoolean("ford"), source.getDirection("oneway"), source.getBoolean("area"),
source.getString("service"), source.getString("usage"), source.getString("public_transport"),
source.getString("man_made"), source.getString("bicycle"), source.getString("foot"), source.getString("horse"),
source.getString("mtb:scale"), source.getString("surface"));
}
}
public static record OsmRailwayLinestring(
com.onthegomap.flatmap.SourceFeature source, String railway, String ref, String network, int zOrder, long layer,
long level, boolean indoor, String name, String nameEn, String nameDe, String shortName, boolean isTunnel,
boolean isBridge, boolean isRamp, boolean isFord, int isOneway, boolean isArea, String service, String usage
) {
public OsmRailwayLinestring(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("railway"), source.getString("ref"), source.getString("network"),
source.getWayZorder(), source.getLong("layer"), source.getLong("level"), source.getBoolean("indoor"),
source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getString("short_name"), source.getBoolean("tunnel"), source.getBoolean("bridge"),
source.getBoolean("ramp"), source.getBoolean("ford"), source.getDirection("oneway"), source.getBoolean("area"),
source.getString("service"), source.getString("usage"));
}
}
public static record OsmAerialwayLinestring(
com.onthegomap.flatmap.SourceFeature source, String aerialway, int zOrder, long layer, String name, String nameEn,
String nameDe, String shortName, boolean isTunnel, boolean isBridge, boolean isRamp, boolean isFord, int isOneway,
boolean isArea, String service, String usage
) {
public OsmAerialwayLinestring(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("aerialway"), source.getWayZorder(), source.getLong("layer"),
source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getString("short_name"), source.getBoolean("tunnel"), source.getBoolean("bridge"),
source.getBoolean("ramp"), source.getBoolean("ford"), source.getDirection("oneway"), source.getBoolean("area"),
source.getString("service"), source.getString("usage"));
}
}
public static record OsmShipwayLinestring(
com.onthegomap.flatmap.SourceFeature source, String shipway, int zOrder, long layer, String name, String nameEn,
String nameDe, String shortName, boolean isTunnel, boolean isBridge, boolean isRamp, boolean isFord, int isOneway,
boolean isArea, String service, String usage
) {
public OsmShipwayLinestring(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("route"), source.getWayZorder(), source.getLong("layer"), source.getString("name"),
source.getString("name:en"), source.getString("name:de"), source.getString("short_name"),
source.getBoolean("tunnel"), source.getBoolean("bridge"), source.getBoolean("ramp"), source.getBoolean("ford"),
source.getDirection("oneway"), source.getBoolean("area"), source.getString("service"),
source.getString("usage"));
}
}
public static record OsmHighwayPolygon(
com.onthegomap.flatmap.SourceFeature source, String highway, int zOrder, long layer, long level, boolean indoor,
boolean isArea, String publicTransport, String manMade
) {
public OsmHighwayPolygon(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("highway"), source.getWayZorder(), source.getLong("layer"), source.getLong("level"),
source.getBoolean("indoor"), source.getBoolean("area"), source.getString("public_transport"),
source.getString("man_made"));
}
}
public static record OsmRouteMember(
com.onthegomap.flatmap.SourceFeature source, String ref, String network, String name
) {
public OsmRouteMember(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("ref"), source.getString("network"), source.getString("name"));
}
}
public static record OsmBuildingPolygon(
com.onthegomap.flatmap.SourceFeature source, String material, String colour, String building, String buildingpart,
String buildingheight, String buildingminHeight, String buildinglevels, String buildingminLevel, String height,
String minHeight, String levels, String minLevel
) {
public OsmBuildingPolygon(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("building:material"), source.getString("building:colour"),
source.getString("building"), source.getString("building:part"), source.getString("building:height"),
source.getString("building:min_height"), source.getString("building:levels"),
source.getString("building:min_level"), source.getString("height"), source.getString("min_height"),
source.getString("levels"), source.getString("min_level"));
}
}
public static record OsmBuildingRelation(
com.onthegomap.flatmap.SourceFeature source, String building, String material, String colour, String buildingpart,
String buildingheight, String height, String buildingminHeight, String minHeight, String buildinglevels,
String levels, String buildingminLevel, String minLevel, String relbuildingheight, String relheight,
String relbuildingminHeight, String relminHeight, String relbuildinglevels, String rellevels,
String relbuildingminLevel, String relminLevel
) {
public OsmBuildingRelation(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("building"), source.getString("building:material"),
source.getString("building:colour"), source.getString("building:part"), source.getString("building:height"),
source.getString("height"), source.getString("building:min_height"), source.getString("min_height"),
source.getString("building:levels"), source.getString("levels"), source.getString("building:min_level"),
source.getString("min_level"), source.getString("building:height"), source.getString("height"),
source.getString("building:min_height"), source.getString("min_height"), source.getString("building:levels"),
source.getString("levels"), source.getString("building:min_level"), source.getString("min_level"));
}
}
public static record OsmMarinePoint(
com.onthegomap.flatmap.SourceFeature source, String name, String nameEn, String nameDe, String place, long rank,
boolean isIntermittent
) {
public OsmMarinePoint(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getString("place"), source.getLong("rank"), source.getBoolean("intermittent"));
}
}
public static record OsmContinentPoint(
com.onthegomap.flatmap.SourceFeature source, String name, String nameEn, String nameDe
) {
public OsmContinentPoint(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("name"), source.getString("name:en"), source.getString("name:de"));
}
}
public static record OsmCountryPoint(
com.onthegomap.flatmap.SourceFeature source, String name, String nameEn, String nameDe, long rank,
String countryCodeIso31661Alpha2, String iso31661Alpha2, String iso31661
) {
public OsmCountryPoint(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getLong("rank"), source.getString("country_code_iso3166_1_alpha_2"),
source.getString("ISO3166-1:alpha2"), source.getString("ISO3166-1"));
}
}
public static record OsmIslandPolygon(
com.onthegomap.flatmap.SourceFeature source, String name, String nameEn, String nameDe, long rank
) {
public OsmIslandPolygon(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getLong("rank"));
}
}
public static record OsmIslandPoint(
com.onthegomap.flatmap.SourceFeature source, String name, String nameEn, String nameDe, long rank
) {
public OsmIslandPoint(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getLong("rank"));
}
}
public static record OsmStatePoint(
com.onthegomap.flatmap.SourceFeature source, String name, String nameEn, String nameDe, String isInCountry,
String isInCountryCode, String ref, long rank
) {
public OsmStatePoint(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getString("is_in:country"), source.getString("is_in:country_code"), source.getString("ref"),
source.getLong("rank"));
}
}
public static record OsmCityPoint(
com.onthegomap.flatmap.SourceFeature source, String name, String nameEn, String nameDe, String place,
long population, String capital, long rank
) {
public OsmCityPoint(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getString("place"), source.getLong("population"), source.getString("capital"), source.getLong("rank"));
}
}
public static record OsmHousenumberPoint(com.onthegomap.flatmap.SourceFeature source, String housenumber) {
public OsmHousenumberPoint(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("addr:housenumber"));
}
}
public static record OsmPoiPoint(
com.onthegomap.flatmap.SourceFeature source, String name, String nameEn, String nameDe, String station,
String funicular, String information, String uicRef, String religion, long level, boolean indoor, long layer,
String sport
) {
public OsmPoiPoint(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getString("station"), source.getString("funicular"), source.getString("information"),
source.getString("uic_ref"), source.getString("religion"), source.getLong("level"), source.getBoolean("indoor"),
source.getLong("layer"), source.getString("sport"));
}
}
public static record OsmPoiPolygon(
com.onthegomap.flatmap.SourceFeature source, String name, String nameEn, String nameDe, String station,
String funicular, String information, String uicRef, String religion, long level, boolean indoor, long layer,
String sport
) {
public OsmPoiPolygon(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getString("station"), source.getString("funicular"), source.getString("information"),
source.getString("uic_ref"), source.getString("religion"), source.getLong("level"), source.getBoolean("indoor"),
source.getLong("layer"), source.getString("sport"));
}
}
public static record OsmAerodromeLabelPoint(
com.onthegomap.flatmap.SourceFeature source, String name, String nameEn, String nameDe, String aerodromeType,
String aerodrome, String military, String iata, String icao, String ele
) {
public OsmAerodromeLabelPoint(com.onthegomap.flatmap.SourceFeature source) {
this(source, source.getString("name"), source.getString("name:en"), source.getString("name:de"),
source.getString("aerodrome:type"), source.getString("aerodrome"), source.getString("military"),
source.getString("iata"), source.getString("icao"), source.getString("ele"));
}
}
}

Wyświetl plik

@ -0,0 +1,50 @@
package com.onthegomap.flatmap.openmaptiles;
import static com.onthegomap.flatmap.openmaptiles.Expression.and;
import static com.onthegomap.flatmap.openmaptiles.Expression.matchAny;
import static com.onthegomap.flatmap.openmaptiles.Expression.or;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
public class ExpressionTest {
@Test
public void testSimplify() {
assertEquals(matchAny("a", "b"), matchAny("a", "b").simplify());
}
@Test
public void testSimplifyAdjacentOrs() {
assertEquals(or(matchAny("a", "b"), matchAny("c", "d")),
or(or(matchAny("a", "b")), or(matchAny("c", "d"))).simplify()
);
}
@Test
public void testSimplifyOrWithOneChild() {
assertEquals(matchAny("a", "b"), or(matchAny("a", "b")).simplify());
}
@Test
public void testSimplifyOAndWithOneChild() {
assertEquals(matchAny("a", "b"), and(matchAny("a", "b")).simplify());
}
@Test
public void testSimplifyDeeplyNested() {
assertEquals(matchAny("a", "b"), or(or(and(and(matchAny("a", "b"))))).simplify());
}
@Test
public void testSimplifyDeeplyNested2() {
assertEquals(or(matchAny("a", "b"), matchAny("b", "c")),
or(or(and(and(matchAny("a", "b"))), matchAny("b", "c"))).simplify());
}
@Test
public void testSimplifyDeeplyNested3() {
assertEquals(or(and(matchAny("a", "b"), matchAny("c", "d")), matchAny("b", "c")),
or(or(and(and(matchAny("a", "b")), matchAny("c", "d")), matchAny("b", "c"))).simplify());
}
}

Wyświetl plik

@ -0,0 +1,102 @@
package com.onthegomap.flatmap.openmaptiles;
import static com.onthegomap.flatmap.openmaptiles.Expression.and;
import static com.onthegomap.flatmap.openmaptiles.Expression.matchAny;
import static com.onthegomap.flatmap.openmaptiles.Expression.matchField;
import static com.onthegomap.flatmap.openmaptiles.Expression.or;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Map;
import org.junit.jupiter.api.Test;
public class GenerateTest {
@Test
public void testParseSimple() {
FieldMapping parsed = Generate.generateFieldMapping(Generate.parseYaml("""
output:
key: value
key2:
- value2
- '%value3%'
"""));
assertEquals(new FieldMapping(Map.of(
"output", or(
matchAny("key", "value"),
matchAny("key2", "value2", "%value3%")
)
)), parsed);
}
@Test
public void testParseAnd() {
FieldMapping parsed = Generate.generateFieldMapping(Generate.parseYaml("""
output:
__AND__:
key1: val1
key2: val2
"""));
assertEquals(new FieldMapping(Map.of(
"output", and(
matchAny("key1", "val1"),
matchAny("key2", "val2")
)
)), parsed);
}
@Test
public void testParseAndWithOthers() {
FieldMapping parsed = Generate.generateFieldMapping(Generate.parseYaml("""
output:
- key0: val0
- __AND__:
key1: val1
key2: val2
"""));
assertEquals(new FieldMapping(Map.of(
"output", or(
matchAny("key0", "val0"),
and(
matchAny("key1", "val1"),
matchAny("key2", "val2")
)
)
)), parsed);
}
@Test
public void testParseAndContainingOthers() {
FieldMapping parsed = Generate.generateFieldMapping(Generate.parseYaml("""
output:
__AND__:
- key1: val1
- __OR__:
key2: val2
key3: val3
"""));
assertEquals(new FieldMapping(Map.of(
"output", and(
matchAny("key1", "val1"),
or(
matchAny("key2", "val2"),
matchAny("key3", "val3")
)
)
)), parsed);
}
@Test
public void testParseContainsKey() {
FieldMapping parsed = Generate.generateFieldMapping(Generate.parseYaml("""
output:
key1: val1
key2:
"""));
assertEquals(new FieldMapping(Map.of(
"output", or(
matchAny("key1", "val1"),
matchField("key2")
)
)), parsed);
}
}

52
pom.xml
Wyświetl plik

@ -15,6 +15,7 @@
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
<jackson.version>2.12.3</jackson.version>
<junit.version>5.7.1</junit.version>
</properties>
<scm>
@ -43,6 +44,57 @@
<module>openmaptiles</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.9.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>