From 916052c18014ce1bc5373102a9d72d4ca645abce Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Mon, 20 Nov 2023 02:46:56 +0100 Subject: [PATCH 01/98] Ported Feature and BaseFeature to Kotlin --- library/build.gradle | 12 +++ .../westnordost/osmfeatures/BaseFeature.java | 87 ------------------- .../de/westnordost/osmfeatures/BaseFeature.kt | 55 ++++++++++++ .../de/westnordost/osmfeatures/Feature.java | 37 -------- .../de/westnordost/osmfeatures/Feature.kt | 27 ++++++ 5 files changed, 94 insertions(+), 124 deletions(-) delete mode 100644 library/src/main/java/de/westnordost/osmfeatures/BaseFeature.java create mode 100644 library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt delete mode 100644 library/src/main/java/de/westnordost/osmfeatures/Feature.java create mode 100644 library/src/main/java/de/westnordost/osmfeatures/Feature.kt diff --git a/library/build.gradle b/library/build.gradle index ac83f34..cfc2faf 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -2,6 +2,7 @@ plugins { id "java-library" id "maven-publish" id 'signing' + id 'org.jetbrains.kotlin.jvm' version '1.9.0' } version = "5.2" @@ -14,6 +15,7 @@ repositories { dependencies { implementation 'org.json:json:20180813' testImplementation 'junit:junit:4.13.2' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" } task sourcesJar(type: Jar) { @@ -77,4 +79,14 @@ signing { java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 +} +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } } \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.java b/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.java deleted file mode 100644 index b30f615..0000000 --- a/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.java +++ /dev/null @@ -1,87 +0,0 @@ -package de.westnordost.osmfeatures; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -/** Data class associated with the Feature interface. Represents a non-localized feature. */ -class BaseFeature implements Feature -{ - private final String id; - private final Map tags; - private final List geometry; - private final String icon; - private final String imageURL; - private final List names; - private final List terms; - private final List includeCountryCodes; - private final List excludeCountryCodes; - private final boolean searchable; - private final double matchScore; - private final Map addTags; - private final Map removeTags; - private final boolean isSuggestion; - - private final List canonicalNames; - private final List canonicalTerms; - - public BaseFeature( - String id, Map tags, List geometry, - String icon, String imageURL, List names, List terms, - List includeCountryCodes, List excludeCountryCodes, - boolean searchable, double matchScore, boolean isSuggestion, - Map addTags, Map removeTags) - { - this.id = id; - this.tags = tags; - this.geometry = geometry; - this.icon = icon; - this.imageURL = imageURL; - this.names = names; - this.terms = terms; - this.includeCountryCodes = includeCountryCodes; - this.excludeCountryCodes = excludeCountryCodes; - this.searchable = searchable; - this.matchScore = matchScore; - this.isSuggestion = isSuggestion; - this.addTags = addTags; - this.removeTags = removeTags; - - List canonicalNames = new ArrayList<>(names.size()); - for (String name : names) - { - canonicalNames.add(StringUtils.canonicalize(name)); - } - this.canonicalNames = Collections.unmodifiableList(canonicalNames); - - List canonicalTerms = new ArrayList<>(terms.size()); - for (String term : terms) - { - canonicalTerms.add(StringUtils.canonicalize(term)); - } - this.canonicalTerms = Collections.unmodifiableList(canonicalTerms); - } - - @Override public String getId() { return id; } - @Override public Map getTags() { return tags; } - @Override public List getGeometry() { return geometry; } - @Override public String getName() { return names.get(0); } - @Override public String getIcon() { return icon; } - @Override public String getImageURL() { return imageURL; } - @Override public List getNames() { return names; } - @Override public List getTerms() { return terms; } - @Override public List getIncludeCountryCodes() { return includeCountryCodes; } - @Override public List getExcludeCountryCodes() { return excludeCountryCodes; } - @Override public boolean isSearchable() { return searchable; } - @Override public double getMatchScore() { return matchScore; } - @Override public Map getAddTags() { return addTags; } - @Override public Map getRemoveTags() { return removeTags; } - @Override public List getCanonicalNames() { return canonicalNames; } - @Override public List getCanonicalTerms() { return canonicalTerms; } - @Override public boolean isSuggestion() { return isSuggestion; } - @Override public Locale getLocale() { return null; } - - @Override public String toString() { return getId(); } -} diff --git a/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt b/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt new file mode 100644 index 0000000..598eec3 --- /dev/null +++ b/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt @@ -0,0 +1,55 @@ +package de.westnordost.osmfeatures + +import java.util.* +import java.util.stream.Collectors + +/** Data class associated with the Feature interface. Represents a non-localized feature. */ +open class BaseFeature( + override val id: String, override val tags: Map, geometry: List?, + icon: String?, imageURL: String?, names: List, terms: List?, + includeCountryCodes: List, excludeCountryCodes: List, + searchable: Boolean, matchScore: Double, isSuggestion: Boolean, + addTags: Map, removeTags: Map): Feature { + override val geometry: List? + override val icon: String? + override val imageURL: String? + override val names: List + override val terms: List? + override val includeCountryCodes: List + override val excludeCountryCodes: List + override val isSearchable: Boolean + override val matchScore: Double + override val addTags: Map + override val removeTags: Map + override val isSuggestion: Boolean + override val canonicalNames: List + override var canonicalTerms: List? = null + + init { + this.geometry = geometry + this.icon = icon + this.imageURL = imageURL + this.names = names + this.terms = terms + this.includeCountryCodes = includeCountryCodes + this.excludeCountryCodes = excludeCountryCodes + this.isSearchable = searchable + this.matchScore = matchScore + this.isSuggestion = isSuggestion + this.addTags = addTags + this.removeTags = removeTags + this.canonicalNames = names.stream().map { name -> StringUtils.canonicalize(name)}.collect(Collectors.toList()) + if (terms != null) { + this.canonicalTerms = terms.stream().map { term -> StringUtils.canonicalize(term)}.collect(Collectors.toList()) + } + } + + override val name: String + get() = names[0] + override val locale: Locale? + get() = null + + override fun toString(): String { + return id + } +} diff --git a/library/src/main/java/de/westnordost/osmfeatures/Feature.java b/library/src/main/java/de/westnordost/osmfeatures/Feature.java deleted file mode 100644 index 89e049e..0000000 --- a/library/src/main/java/de/westnordost/osmfeatures/Feature.java +++ /dev/null @@ -1,37 +0,0 @@ -package de.westnordost.osmfeatures; - -import java.util.List; -import java.util.Locale; -import java.util.Map; - -/** Subset of a feature as defined in the iD editor - * https://github.com/ideditor/schema-builder#preset-schema - * with only the fields helpful for the dictionary */ -public interface Feature { - String getId(); - Map getTags(); - List getGeometry(); - String getName(); - String getIcon(); - String getImageURL(); - /** name + aliases */ - List getNames(); - List getTerms(); - List getIncludeCountryCodes(); - List getExcludeCountryCodes(); - boolean isSearchable(); - double getMatchScore(); - Map getAddTags(); - Map getRemoveTags(); - List getCanonicalNames(); - List getCanonicalTerms(); - boolean isSuggestion(); - - Locale getLocale(); -} - -// currently not included: -// - fields -// - moreFields -// - replacement -// - reference \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/Feature.kt b/library/src/main/java/de/westnordost/osmfeatures/Feature.kt new file mode 100644 index 0000000..24305df --- /dev/null +++ b/library/src/main/java/de/westnordost/osmfeatures/Feature.kt @@ -0,0 +1,27 @@ +package de.westnordost.osmfeatures + +import java.util.Locale + + +interface Feature { + val id: String + val tags: Map + val geometry: List? + val name: String + val icon: String? + val imageURL: String? + + /** name + aliases */ + val names: List + val terms: List? + val includeCountryCodes: List + val excludeCountryCodes: List + val isSearchable: Boolean + val matchScore: Double + val addTags: Map + val removeTags: Map + val canonicalNames: List + val canonicalTerms: List? + val isSuggestion: Boolean + val locale: Locale? +} From b98a883d9f97f97ef9496416a107d132d2623590 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Thu, 23 Nov 2023 23:26:26 +0100 Subject: [PATCH 02/98] Ported Feature and BaseFeature to Kotlin Locale.kt: WIP --- library/build.gradle | 1 + .../de/westnordost/osmfeatures/Feature.kt | 3 - .../osmfeatures/FeatureDictionary.java | 11 +- .../IDLocalizedFeatureCollection.java | 1 - .../IDPresetsTranslationJsonParser.java | 1 - .../java/de/westnordost/osmfeatures/Locale.kt | 173 ++++++++++++++++++ .../osmfeatures/LocalizedFeature.java | 1 - .../LocalizedFeatureCollection.java | 1 - .../osmfeatures/FeatureDictionaryTest.java | 1 - .../IDLocalizedFeatureCollectionTest.java | 3 +- .../IDPresetsTranslationJsonParserTest.java | 1 - .../TestLocalizedFeatureCollection.java | 1 - 12 files changed, 185 insertions(+), 13 deletions(-) create mode 100644 library/src/main/java/de/westnordost/osmfeatures/Locale.kt diff --git a/library/build.gradle b/library/build.gradle index cfc2faf..5faf623 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -16,6 +16,7 @@ dependencies { implementation 'org.json:json:20180813' testImplementation 'junit:junit:4.13.2' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + implementation "io.fluidsonic.locale:fluid-locale:0.13.0" } task sourcesJar(type: Jar) { diff --git a/library/src/main/java/de/westnordost/osmfeatures/Feature.kt b/library/src/main/java/de/westnordost/osmfeatures/Feature.kt index 24305df..66a6c43 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/Feature.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/Feature.kt @@ -1,8 +1,5 @@ package de.westnordost.osmfeatures -import java.util.Locale - - interface Feature { val id: String val tags: Map diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionary.java b/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionary.java index a710c4b..b1bbb6c 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionary.java +++ b/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionary.java @@ -1,7 +1,16 @@ package de.westnordost.osmfeatures; import java.io.File; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Set; +import java.util.HashSet; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.java b/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.java index 69a361e..d49aab9 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.java +++ b/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.java @@ -11,7 +11,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; -import java.util.Locale; import java.util.Map; import static de.westnordost.osmfeatures.CollectionUtils.synchronizedGetOrCreate; diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.java b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.java index 6be4e27..4a67ba5 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.java +++ b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.java @@ -9,7 +9,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.HashMap; diff --git a/library/src/main/java/de/westnordost/osmfeatures/Locale.kt b/library/src/main/java/de/westnordost/osmfeatures/Locale.kt new file mode 100644 index 0000000..31c86bf --- /dev/null +++ b/library/src/main/java/de/westnordost/osmfeatures/Locale.kt @@ -0,0 +1,173 @@ +package de.westnordost.osmfeatures + +import io.fluidsonic.locale.LanguageTag + +class Locale( + + val language: String, + val region: String = "", + val script: String = "", + val variant: String? = null) { + companion object { + + + @JvmField + val ENGLISH: Locale = Locale("en") + @JvmField + val UK: Locale = Locale("en","UK") + @JvmField + val FRENCH: Locale = Locale("fr") + @JvmField + val ITALIAN: Locale = Locale("it") + @JvmField + val GERMAN: Locale = Locale("de") + @JvmField + val GERMANY: Locale = Locale("de", "DE") + @JvmField + val CHINESE: Locale = Locale("zh") + + const val SEP = "-" + const val PRIVATEUSE = "x" + + @kotlin.jvm.JvmStatic + val default: Locale = ENGLISH + fun toTitleString(s: String): String { + var len: Int + if (s.length.also { len = it } == 0) { + return s + } + var idx = 0 + if (!s[idx].isLowerCase()) { + idx = 1 + while (idx < len) { + if (s[idx].isUpperCase()) { + break + } + idx++ + } + } + if (idx == len) { + return s + } + val buf = CharArray(len) + for (i in 0 until len) { + val c = s[i] + if (i == 0 && idx == 0) { + buf[i] = c.uppercaseChar() + } else if (i < idx) { + buf[i] = c + } else { + buf[i] = c.lowercaseChar() + } + } + return String(buf) + } + + } + + + + val country : String + get() = this.region + + private var languageTag : String? = null + + constructor(lang: String) : this(lang,"", "", null) { + + } + + constructor(lang: String, region: String) : this(lang, region, "", null) { + + } + + fun toLanguageTag(): String { + val lTag: String? = this.languageTag + if (lTag != null) { + return lTag + } + + this.languageTag = LanguageTag.forLanguage(language, script, region).toString() + return this.languageTag!! +// val buf = StringBuilder() +// +// var subtag = tag.language +// if (subtag.isNotEmpty()) { +// buf.append(subtag.lowercase()) +// } +// +// subtag = tag.script +// if (subtag.isNotEmpty()) { +// buf.append(SEP) +// buf.append(toTitleString(subtag)) +// } +// +// subtag = tag.region +// if (subtag.isNotEmpty()) { +// buf.append(SEP) +// buf.append(subtag.uppercase()) +// } +// +// var subtags = tag.variants +// for (s in subtags) { +// buf.append(SEP) +// // preserve casing +// buf.append(s) +// } +// +// subtags = tag.getExtensions() +// for (s in subtags) { +// buf.append(SEP) +// buf.append(subtag.lowercase()) +// } +// +// subtag = tag.privateuse +// if (subtag.isNotEmpty()) { +// if (buf.isNotEmpty()) { +// buf.append(SEP) +// } +// buf.append(PRIVATEUSE).append(SEP) +// // preserve casing +// buf.append(subtag) +// } +// +// val langTag = buf.toString() +// synchronized(this) { +// if (this.languageTag == null) { +// this.languageTag = langTag +// } +// } +// return langTag + } + + + class Builder { + private var language: String? = null + fun setLanguage(language: String) : Builder { + this.language = language + return this + } + + private var region: String = "" + + fun setRegion(region: String) : Builder { + this.region = region + return this + } + + private var script: String = "" + fun setScript(script: String) : Builder { + this.script = script + return this + } + + fun build(): Locale { + if(language == null) { + throw NullPointerException("Builder language is null") + } + return Locale(language!!, region, script) + } + + } + + +} \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.java b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.java index b7f7e4c..ee55005 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.java +++ b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Map; /** Data class associated with the Feature interface. Represents a localized feature. diff --git a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.java b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.java index 454b961..fe02d8a 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.java +++ b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.java @@ -2,7 +2,6 @@ import java.util.Collection; import java.util.List; -import java.util.Locale; /** A localized collection of features */ public interface LocalizedFeatureCollection diff --git a/library/src/test/java/de/westnordost/osmfeatures/FeatureDictionaryTest.java b/library/src/test/java/de/westnordost/osmfeatures/FeatureDictionaryTest.java index 8b13aa7..011a5a6 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/FeatureDictionaryTest.java +++ b/library/src/test/java/de/westnordost/osmfeatures/FeatureDictionaryTest.java @@ -5,7 +5,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Map; import static de.westnordost.osmfeatures.MapEntry.*; diff --git a/library/src/test/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.java b/library/src/test/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.java index 97e1fed..11ac17b 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.java +++ b/library/src/test/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.java @@ -9,7 +9,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Locale; import static de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder; import static de.westnordost.osmfeatures.TestUtils.listOf; @@ -87,7 +86,7 @@ public class IDLocalizedFeatureCollectionTest assertEquals(Locale.ENGLISH, c.get("some/id", locales).getLocale()); assertEquals(Locale.GERMAN, c.get("another/id", locales).getLocale()); - assertEquals(null, c.get("yet/another/id", locales).getLocale()); + assertNull(c.get("yet/another/id", locales).getLocale()); } @Test public void load_features_and_merge_localizations() diff --git a/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java b/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java index 1587a80..70f3206 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java +++ b/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java @@ -7,7 +7,6 @@ import java.net.URL; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import static de.westnordost.osmfeatures.MapEntry.mapOf; diff --git a/library/src/test/java/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.java b/library/src/test/java/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.java index 8dcbf89..05974e7 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.java +++ b/library/src/test/java/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Locale; public class TestLocalizedFeatureCollection implements LocalizedFeatureCollection { From f64ae873a7b0344058b26c3f43cadafd558ba7cf Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Sat, 9 Dec 2023 06:29:41 +0100 Subject: [PATCH 03/98] Rename .java to .kt --- .../osmfeatures/{FeatureTagsIndex.java => FeatureTagsIndex.kt} | 0 .../osmfeatures/{FeatureTermIndex.java => FeatureTermIndex.kt} | 0 .../osmfeatures/{FileAccessAdapter.java => FileAccessAdapter.kt} | 0 .../osmfeatures/{FileSystemAccess.java => FileSystemAccess.kt} | 0 .../osmfeatures/{GeometryType.java => GeometryType.kt} | 0 ...izedFeatureCollection.java => IDLocalizedFeatureCollection.kt} | 0 .../de/westnordost/osmfeatures/{JsonUtils.java => JsonUtils.kt} | 0 .../osmfeatures/{LocalizedFeature.java => LocalizedFeature.kt} | 0 ...alizedFeatureCollection.java => LocalizedFeatureCollection.kt} | 0 ...untryFeatureCollection.java => PerCountryFeatureCollection.kt} | 0 .../westnordost/osmfeatures/{StringUtils.java => StringUtils.kt} | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename library/src/main/java/de/westnordost/osmfeatures/{FeatureTagsIndex.java => FeatureTagsIndex.kt} (100%) rename library/src/main/java/de/westnordost/osmfeatures/{FeatureTermIndex.java => FeatureTermIndex.kt} (100%) rename library/src/main/java/de/westnordost/osmfeatures/{FileAccessAdapter.java => FileAccessAdapter.kt} (100%) rename library/src/main/java/de/westnordost/osmfeatures/{FileSystemAccess.java => FileSystemAccess.kt} (100%) rename library/src/main/java/de/westnordost/osmfeatures/{GeometryType.java => GeometryType.kt} (100%) rename library/src/main/java/de/westnordost/osmfeatures/{IDLocalizedFeatureCollection.java => IDLocalizedFeatureCollection.kt} (100%) rename library/src/main/java/de/westnordost/osmfeatures/{JsonUtils.java => JsonUtils.kt} (100%) rename library/src/main/java/de/westnordost/osmfeatures/{LocalizedFeature.java => LocalizedFeature.kt} (100%) rename library/src/main/java/de/westnordost/osmfeatures/{LocalizedFeatureCollection.java => LocalizedFeatureCollection.kt} (100%) rename library/src/main/java/de/westnordost/osmfeatures/{PerCountryFeatureCollection.java => PerCountryFeatureCollection.kt} (100%) rename library/src/main/java/de/westnordost/osmfeatures/{StringUtils.java => StringUtils.kt} (100%) diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureTagsIndex.java b/library/src/main/java/de/westnordost/osmfeatures/FeatureTagsIndex.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/FeatureTagsIndex.java rename to library/src/main/java/de/westnordost/osmfeatures/FeatureTagsIndex.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.java b/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.java rename to library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.java b/library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.java rename to library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.java b/library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.java rename to library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/GeometryType.java b/library/src/main/java/de/westnordost/osmfeatures/GeometryType.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/GeometryType.java rename to library/src/main/java/de/westnordost/osmfeatures/GeometryType.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.java b/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.java rename to library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.java b/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/JsonUtils.java rename to library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.java b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.java rename to library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.java b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.java rename to library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/PerCountryFeatureCollection.java b/library/src/main/java/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/PerCountryFeatureCollection.java rename to library/src/main/java/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/StringUtils.java b/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/StringUtils.java rename to library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt From b3cf2b2a4d3068bf60a52109718b93aaf4bbaa43 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Sat, 9 Dec 2023 06:29:41 +0100 Subject: [PATCH 04/98] kmp WIP, use okio for I/O --- library/build.gradle | 93 --- library/build.gradle.kts | 97 +++ library/settings.gradle.kts | 5 + .../de/westnordost/osmfeatures/BaseFeature.kt | 37 +- .../osmfeatures/CollectionUtils.java | 9 +- .../osmfeatures/FeatureDictionary.java | 626 ---------------- .../osmfeatures/FeatureDictionnary.kt | 674 ++++++++++++++++++ .../osmfeatures/FeatureTagsIndex.kt | 52 +- .../osmfeatures/FeatureTermIndex.kt | 61 +- .../osmfeatures/FileAccessAdapter.kt | 13 +- .../osmfeatures/FileSystemAccess.kt | 30 +- .../westnordost/osmfeatures/GeometryType.kt | 33 +- .../IDBrandPresetsFeatureCollection.java | 3 +- .../IDLocalizedFeatureCollection.kt | 266 ++++--- .../osmfeatures/IDPresetsJsonParser.java | 7 +- .../de/westnordost/osmfeatures/JsonUtils.kt | 86 ++- .../java/de/westnordost/osmfeatures/Locale.kt | 97 +-- .../osmfeatures/LocalizedFeature.kt | 106 ++- .../osmfeatures/LocalizedFeatureCollection.kt | 22 +- .../PerCountryFeatureCollection.kt | 18 +- .../de/westnordost/osmfeatures/StringUtils.kt | 48 +- .../TestLocalizedFeatureCollection.java | 50 +- 22 files changed, 1192 insertions(+), 1241 deletions(-) delete mode 100644 library/build.gradle create mode 100644 library/build.gradle.kts create mode 100644 library/settings.gradle.kts delete mode 100644 library/src/main/java/de/westnordost/osmfeatures/FeatureDictionary.java create mode 100644 library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt diff --git a/library/build.gradle b/library/build.gradle deleted file mode 100644 index 5faf623..0000000 --- a/library/build.gradle +++ /dev/null @@ -1,93 +0,0 @@ -plugins { - id "java-library" - id "maven-publish" - id 'signing' - id 'org.jetbrains.kotlin.jvm' version '1.9.0' -} - -version = "5.2" -group "de.westnordost" - -repositories { - mavenCentral() -} - -dependencies { - implementation 'org.json:json:20180813' - testImplementation 'junit:junit:4.13.2' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" - implementation "io.fluidsonic.locale:fluid-locale:0.13.0" -} - -task sourcesJar(type: Jar) { - from sourceSets.main.allSource - classifier = 'sources' -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - from javadoc.destinationDir - classifier = 'javadoc' -} - -publishing { - publications { - mavenJava(MavenPublication) { - from components.java - artifact sourcesJar - artifact javadocJar - artifactId "osmfeatures" - pom { - name = 'osmfeatures' - description = 'Java library to translate OSM tags to and from localized names.' - url = 'https://github.com/westnordost/osmfeatures' - scm { - connection = 'https://github.com/westnordost/osmfeatures.git' - developerConnection = 'https://github.com/westnordost/osmfeatures.git' - url = 'https://github.com/westnordost/osmfeatures' - } - licenses { - license { - name = 'The Apache License, Version 2.0' - url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - developers { - developer { - id = 'westnordost' - name = 'Tobias Zwick' - email = 'osm@westnordost.de' - } - } - } - } - } - repositories { - maven { - name = "mavenCentral" - url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" - credentials { - username = ossrhUsername - password = ossrhPassword - } - } - } -} - -signing { - sign publishing.publications.mavenJava -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} -compileKotlin { - kotlinOptions { - jvmTarget = "1.8" - } -} -compileTestKotlin { - kotlinOptions { - jvmTarget = "1.8" - } -} \ No newline at end of file diff --git a/library/build.gradle.kts b/library/build.gradle.kts new file mode 100644 index 0000000..d7ac1e2 --- /dev/null +++ b/library/build.gradle.kts @@ -0,0 +1,97 @@ +plugins { + id("java-library") + id("maven-publish") + id("signing") + id("org.jetbrains.kotlin.jvm") version "1.9.0" +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.json:json:20230227") + testImplementation("junit:junit:4.13.2") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation("io.fluidsonic.locale:fluid-locale:0.13.0") + implementation("com.squareup.okio:okio:3.6.0") +} + +tasks { + val sourcesJar by creating(Jar::class) { + archiveClassifier.set("sources") + from(sourceSets.main.get().allSource) + } + + val javadocJar by creating(Jar::class) { + dependsOn.add(javadoc) + archiveClassifier.set("javadoc") + from(javadoc) + } + + artifacts { + archives(sourcesJar) + archives(javadocJar) + } +} + + + +publishing { + repositories { + maven { + url = uri("https://github.com/westnordost/osmfeatures") + } + } + publications { + create("mavenJava") { + groupId = "de.westnordost" + artifactId = "osmfeatures" + version = "5.2" + from(components["java"]) + + pom { + name.value("osmfeatures") + description.value("Java library to translate OSM tags to and from localized names.") + url.value("https://github.com/westnordost/osmfeatures") + scm { + connection.value("https://github.com/westnordost/osmfeatures.git") + developerConnection.value("https://github.com/westnordost/osmfeatures.git") + url.value("https://github.com/westnordost/osmfeatures") + } + licenses { + license { + name.value("The Apache License, Version 2.0") + url.value("http://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + developers { + developer { + id.value("westnordost") + name.value("Tobias Zwick") + email.value("osm@westnordost.de") + } + } + } + } + } +} + +signing { + sign(publishing.publications["mavenJava"]) +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} +//compileKotlin { +// kotlinOptions { +// jvmTarget = "1.8" +// } +//} +//compileTestKotlin { +// kotlinOptions { +// jvmTarget = "1.8" +// } +//} \ No newline at end of file diff --git a/library/settings.gradle.kts b/library/settings.gradle.kts new file mode 100644 index 0000000..2c6e611 --- /dev/null +++ b/library/settings.gradle.kts @@ -0,0 +1,5 @@ +pluginManagement { + repositories { + mavenCentral() + } +} \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt b/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt index 598eec3..0813709 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt @@ -1,29 +1,26 @@ package de.westnordost.osmfeatures -import java.util.* -import java.util.stream.Collectors - /** Data class associated with the Feature interface. Represents a non-localized feature. */ open class BaseFeature( - override val id: String, override val tags: Map, geometry: List?, + override val id: String, override val tags: Map, geometry: List, icon: String?, imageURL: String?, names: List, terms: List?, includeCountryCodes: List, excludeCountryCodes: List, searchable: Boolean, matchScore: Double, isSuggestion: Boolean, addTags: Map, removeTags: Map): Feature { - override val geometry: List? - override val icon: String? - override val imageURL: String? - override val names: List - override val terms: List? - override val includeCountryCodes: List - override val excludeCountryCodes: List - override val isSearchable: Boolean - override val matchScore: Double - override val addTags: Map - override val removeTags: Map - override val isSuggestion: Boolean - override val canonicalNames: List - override var canonicalTerms: List? = null + final override var geometry: List + final override val icon: String? + final override val imageURL: String? + final override val names: List + final override val terms: List? + final override val includeCountryCodes: List + final override val excludeCountryCodes: List + final override val isSearchable: Boolean + final override val matchScore: Double + final override val addTags: Map + final override val removeTags: Map + final override val isSuggestion: Boolean + final override val canonicalNames: List + final override var canonicalTerms: List? = null init { this.geometry = geometry @@ -38,9 +35,9 @@ open class BaseFeature( this.isSuggestion = isSuggestion this.addTags = addTags this.removeTags = removeTags - this.canonicalNames = names.stream().map { name -> StringUtils.canonicalize(name)}.collect(Collectors.toList()) + this.canonicalNames = names.map { name -> StringUtils.canonicalize(name)} if (terms != null) { - this.canonicalTerms = terms.stream().map { term -> StringUtils.canonicalize(term)}.collect(Collectors.toList()) + this.canonicalTerms = terms.map { term -> StringUtils.canonicalize(term)} } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.java b/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.java index 8ebc8af..9da7c0e 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.java +++ b/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.java @@ -1,8 +1,8 @@ package de.westnordost.osmfeatures; import java.util.Collection; -import java.util.Iterator; import java.util.Map; +import java.util.Objects; class CollectionUtils { @@ -12,7 +12,7 @@ public interface CreateFn { V create(K value); } * entry yet, create it using the given create function thread-safely */ public static V synchronizedGetOrCreate(Map map, K key, CreateFn createFn) { - synchronized (map) + synchronized(map) { if (!map.containsKey(key)) { @@ -48,7 +48,7 @@ public static boolean mapContainsEntry(Map map, Map.Entry entry) { V mapValue = map.get(entry.getKey()); V value = entry.getValue(); - return mapValue == value || value != null && value.equals(mapValue); + return Objects.equals(value, mapValue); } public interface Predicate { boolean fn(T v); } @@ -56,8 +56,7 @@ public interface Predicate { boolean fn(T v); } /** Backport of Collection.removeIf */ public static void removeIf(Collection list, Predicate predicate) { - Iterator it = list.iterator(); - while(it.hasNext()) if (predicate.fn(it.next())) it.remove(); + list.removeIf(predicate::fn); } public static T find(Collection list, Predicate predicate) diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionary.java b/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionary.java deleted file mode 100644 index b1bbb6c..0000000 --- a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionary.java +++ /dev/null @@ -1,626 +0,0 @@ -package de.westnordost.osmfeatures; - -import java.io.File; -import java.util.List; -import java.util.Map; -import java.util.HashMap; -import java.util.Set; -import java.util.HashSet; -import java.util.Arrays; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static de.westnordost.osmfeatures.CollectionUtils.numberOfContainedEntriesInMap; -import static de.westnordost.osmfeatures.CollectionUtils.removeIf; -import static de.westnordost.osmfeatures.CollectionUtils.synchronizedGetOrCreate; - -public class FeatureDictionary -{ - private static final Pattern VALID_COUNTRY_CODE_REGEX = Pattern.compile("([A-Z]{2})(?:-([A-Z0-9]{1,3}))?"); - - private final LocalizedFeatureCollection featureCollection; - private final PerCountryFeatureCollection brandFeatureCollection; - - private final Map, FeatureTermIndex> brandNamesIndexes; - private final Map, FeatureTagsIndex> brandTagsIndexes; - - private final Map, FeatureTagsIndex> tagsIndexes; - private final Map, FeatureTermIndex> namesIndexes; - private final Map, FeatureTermIndex> termsIndexes; - private final Map, FeatureTermIndex> tagValuesIndexes; - - FeatureDictionary(LocalizedFeatureCollection featureCollection, PerCountryFeatureCollection brandFeatureCollection) - { - this.featureCollection = featureCollection; - this.brandFeatureCollection = brandFeatureCollection; - - tagsIndexes = new HashMap<>(); - namesIndexes = new HashMap<>(); - termsIndexes = new HashMap<>(); - tagValuesIndexes = new HashMap<>(); - brandNamesIndexes = new HashMap<>(); - brandTagsIndexes = new HashMap<>(); - // build indices for default locale - getTagsIndex(Arrays.asList(Locale.getDefault(), null)); - getNamesIndex(Collections.singletonList(Locale.getDefault())); - getTermsIndex(Collections.singletonList(Locale.getDefault())); - } - - /** Create a new FeatureDictionary which gets its data from the given directory. */ - public static FeatureDictionary create(String presetsBasePath) { - return create(presetsBasePath, null); - } - - /** Create a new FeatureDictionary which gets its data from the given directory. Optionally, - * a path to brand presets can be specified. */ - public static FeatureDictionary create(String presetsBasePath, String brandPresetsBasePath) { - LocalizedFeatureCollection featureCollection = - new IDLocalizedFeatureCollection(new FileSystemAccess(new File(presetsBasePath))); - - PerCountryFeatureCollection brandsFeatureCollection = brandPresetsBasePath != null - ? new IDBrandPresetsFeatureCollection(new FileSystemAccess(new File(brandPresetsBasePath))) - : null; - - return new FeatureDictionary(featureCollection, brandsFeatureCollection); - } - - //region Get by id - - /** Find feature by id */ - public QueryByIdBuilder byId(String id) - { - return new QueryByIdBuilder(id); - } - - private Feature get(String id, List locales, String countryCode) - { - Feature feature = featureCollection.get(id, locales); - if (feature != null) return feature; - List countryCodes = dissectCountryCode(countryCode); - return brandFeatureCollection.get(id, countryCodes); - } - - //endregion - - //region Query by tags - - /** Find matches by a set of tags */ - public QueryByTagBuilder byTags(Map tags) - { - return new QueryByTagBuilder(tags); - } - - private List get( - Map tags, - GeometryType geometry, - String countryCode, - Boolean isSuggestion, - List locales - ) { - if(tags.isEmpty()) return Collections.emptyList(); - - List foundFeatures = new ArrayList<>(); - - if (isSuggestion == null || !isSuggestion) - { - foundFeatures.addAll(getTagsIndex(locales).getAll(tags)); - } - if (isSuggestion == null || isSuggestion) - { - List countryCodes = dissectCountryCode(countryCode); - foundFeatures.addAll(getBrandTagsIndex(countryCodes).getAll(tags)); - } - - removeIf(foundFeatures, feature -> !isFeatureMatchingParameters(feature, geometry, countryCode)); - - if (foundFeatures.size() > 1) - { - // only return of each category the most specific thing. I.e. will return - // McDonalds only instead of McDonalds,Fast-Food Restaurant,Amenity - Set removeIds = new HashSet<>(); - for (Feature feature : foundFeatures) - { - removeIds.addAll(getParentCategoryIds(feature.getId())); - } - if (!removeIds.isEmpty()) - { - removeIf(foundFeatures, feature -> removeIds.contains(feature.getId())); - } - } - - Collections.sort(foundFeatures, (a, b) -> - { - // 1. features with more matching tags first - int tagOrder = b.getTags().size() - a.getTags().size(); - if(tagOrder != 0) return tagOrder; - - // 2. if search is not limited by locale, return matches not limited by locale first - if(locales.size() == 1 && locales.get(0) == null) - { - int localeOrder = - (b.getIncludeCountryCodes().isEmpty() && b.getExcludeCountryCodes().isEmpty() ? 1 : 0) - - (a.getIncludeCountryCodes().isEmpty() && a.getExcludeCountryCodes().isEmpty()? 1 : 0); - if (localeOrder != 0) return localeOrder; - } - - // 3. features with more matching tags in addTags first - // https://github.com/openstreetmap/iD/issues/7927 - int numberOfMatchedAddTags = - numberOfContainedEntriesInMap(b.getAddTags(), tags.entrySet()) - - numberOfContainedEntriesInMap(a.getAddTags(), tags.entrySet()); - if(numberOfMatchedAddTags != 0) return numberOfMatchedAddTags; - - // 4. features with higher matchScore first - return (int) (100 * b.getMatchScore() - 100 * a.getMatchScore()); - }); - - return foundFeatures; - } - - //endregion - - //region Query by term - - /** Find matches by given search word */ - public QueryByTermBuilder byTerm(String term) - { - return new QueryByTermBuilder(term); - } - - private List get( - String search, - GeometryType geometry, - String countryCode, - Boolean isSuggestion, - int limit, - List locales - ) { - String canonicalSearch = StringUtils.canonicalize(search); - - Comparator sortNames = (a, b) -> - { - // 1. exact matches first - int exactMatchOrder = - (CollectionUtils.find(b.getNames(), n -> n.equals(search)) != null ? 1 : 0) - - (CollectionUtils.find(a.getNames(), n -> n.equals(search)) != null ? 1 : 0); - if(exactMatchOrder != 0) return exactMatchOrder; - - // 2. exact matches case and diacritics insensitive first - int cExactMatchOrder = - (CollectionUtils.find(b.getCanonicalNames(), n -> n.equals(canonicalSearch)) != null ? 1 : 0) - - (CollectionUtils.find(a.getCanonicalNames(), n -> n.equals(canonicalSearch)) != null ? 1 : 0); - if(cExactMatchOrder != 0) return cExactMatchOrder; - - // 3. starts-with matches in string first - int startsWithOrder = - (CollectionUtils.find(b.getCanonicalNames(), n -> n.startsWith(canonicalSearch)) != null ? 1 : 0) - - (CollectionUtils.find(a.getCanonicalNames(), n -> n.startsWith(canonicalSearch)) != null ? 1 : 0); - if(startsWithOrder != 0) return startsWithOrder; - - // 4. features with higher matchScore first - int matchScoreOrder = (int) (100 * b.getMatchScore() - 100 * a.getMatchScore()); - if(matchScoreOrder != 0) return matchScoreOrder; - - // 5. shorter names first - return a.getName().length() - b.getName().length(); - }; - - List result = new ArrayList<>(); - - if (isSuggestion == null || !isSuggestion) - { - // a. matches with presets first - List foundFeaturesByName = getNamesIndex(locales).getAll(canonicalSearch); - removeIf(foundFeaturesByName, feature -> !isFeatureMatchingParameters(feature, geometry, countryCode)); - Collections.sort(foundFeaturesByName, sortNames); - - result.addAll(foundFeaturesByName); - - // if limit is reached, can return earlier (performance improvement) - if(limit > 0 && result.size() >= limit) return result.subList(0, Math.min(limit, result.size())); - } - if (isSuggestion == null || isSuggestion) - { - // b. matches with brand names second - List countryCodes = dissectCountryCode(countryCode); - List foundBrandFeatures = getBrandNamesIndex(countryCodes).getAll(canonicalSearch); - removeIf(foundBrandFeatures, feature -> !isFeatureMatchingParameters(feature, geometry, countryCode)); - Collections.sort(foundBrandFeatures, sortNames); - - result.addAll(foundBrandFeatures); - - // if limit is reached, can return earlier (performance improvement) - if(limit > 0 && result.size() >= limit) return result.subList(0, Math.min(limit, result.size())); - } - if (isSuggestion == null || !isSuggestion) - { - // c. matches with terms third - List foundFeaturesByTerm = getTermsIndex(locales).getAll(canonicalSearch); - removeIf(foundFeaturesByTerm, feature -> !isFeatureMatchingParameters(feature, geometry, countryCode)); - - if (!foundFeaturesByTerm.isEmpty()) - { - final Set alreadyFoundFeatures = new HashSet<>(result); - removeIf(foundFeaturesByTerm, feature -> alreadyFoundFeatures.contains(feature)); - } - - Collections.sort(foundFeaturesByTerm, (a, b) -> - { - // 1. features with higher matchScore first - return (int) (100 * b.getMatchScore() - 100 * a.getMatchScore()); - }); - result.addAll(foundFeaturesByTerm); - - // if limit is reached, can return earlier (performance improvement) - if(limit > 0 && result.size() >= limit) return result.subList(0, Math.min(limit, result.size())); - } - if (isSuggestion == null || !isSuggestion) - { - // d. matches with tag values fourth - List foundFeaturesByTagValue = getTagValuesIndex(locales).getAll(canonicalSearch); - removeIf(foundFeaturesByTagValue, feature -> !isFeatureMatchingParameters(feature, geometry, countryCode)); - - if (!foundFeaturesByTagValue.isEmpty()) - { - final Set alreadyFoundFeatures = new HashSet<>(result); - removeIf(foundFeaturesByTagValue, feature -> alreadyFoundFeatures.contains(feature)); - } - result.addAll(foundFeaturesByTagValue); - } - return result.subList(0, Math.min(limit, result.size())); - } - - //endregion - - //region Utility / Filter functions - - private static Collection getParentCategoryIds(String id) - { - List result = new ArrayList<>(); - do - { - id = getParentId(id); - if(id != null) result.add(id); - } - while(id != null); - return result; - } - - private static String getParentId(String id) - { - int lastSlashIndex = id.lastIndexOf("/"); - if(lastSlashIndex == -1) return null; - return id.substring(0, lastSlashIndex); - } - - private static boolean isFeatureMatchingParameters(Feature feature, GeometryType geometry, String countryCode) - { - if (geometry != null && !feature.getGeometry().contains(geometry)) return false; - List include = feature.getIncludeCountryCodes(); - List exclude = feature.getExcludeCountryCodes(); - if (!include.isEmpty() || !exclude.isEmpty()) - { - if (countryCode == null) return false; - if (!include.isEmpty() && !matchesAnyCountryCode(countryCode, include)) return false; - if (matchesAnyCountryCode(countryCode, exclude)) return false; - } - return true; - } - - private static List dissectCountryCode(String countryCode) { - List result = new ArrayList<>(); - // add default / international - result.add(null); - if (countryCode != null) { - Matcher matcher = VALID_COUNTRY_CODE_REGEX.matcher(countryCode); - if (matcher.matches()) { - // add ISO 3166-1 alpha2 (e.g. "US") - result.add(matcher.group(1)); - if (matcher.groupCount() == 2 && matcher.group(2) != null) { - // add ISO 3166-2 (e.g. "US-NY") - result.add(countryCode); - } - } - } - return result; - } - - private static boolean matchesAnyCountryCode(String showOnly, List featureCountryCodes) - { - for (String featureCountryCode : featureCountryCodes) { - if (matchesCountryCode(showOnly, featureCountryCode)) return true; - } - return false; - } - - private static boolean matchesCountryCode(String showOnly, String featureCountryCode) - { - return showOnly.equals(featureCountryCode) - // e.g. US-NY is in US - || showOnly.substring(0,2).equals(featureCountryCode); - } - - //endregion - - //region Lazily get or create Indexes - - /** lazily get or create tags index for given locale(s) */ - private FeatureTagsIndex getTagsIndex(List locales) - { - return synchronizedGetOrCreate(tagsIndexes, locales, this::createTagsIndex); - } - - private FeatureTagsIndex createTagsIndex(List locales) - { - return new FeatureTagsIndex(featureCollection.getAll(locales)); - } - - /** lazily get or create names index for given locale(s) */ - private FeatureTermIndex getNamesIndex(List locales) - { - return synchronizedGetOrCreate(namesIndexes, locales, this::createNamesIndex); - } - - private FeatureTermIndex createNamesIndex(List locales) - { - return new FeatureTermIndex(featureCollection.getAll(locales), feature -> { - if (!feature.isSearchable()) return Collections.emptyList(); - List names = feature.getCanonicalNames(); - List result = new ArrayList<>(names); - for (String name : names) { - if (name.contains(" ")) { - Collections.addAll(result, name.replaceAll("[()]", "").split(" ")); - } - } - return result; - }); - } - - /** lazily get or create terms index for given locale(s) */ - private FeatureTermIndex getTermsIndex(List locales) - { - return synchronizedGetOrCreate(termsIndexes, locales, this::createTermsIndex); - } - - private FeatureTermIndex createTermsIndex(List locales) - { - return new FeatureTermIndex(featureCollection.getAll(locales), feature -> { - if (!feature.isSearchable()) return Collections.emptyList(); - return feature.getCanonicalTerms(); - }); - } - - /** lazily get or create tag values index */ - private FeatureTermIndex getTagValuesIndex(List locales) - { - return synchronizedGetOrCreate(tagValuesIndexes, locales, this::createTagValuesIndex); - } - - private FeatureTermIndex createTagValuesIndex(List locales) - { - return new FeatureTermIndex(featureCollection.getAll(locales), feature -> { - if (!feature.isSearchable()) return Collections.emptyList(); - - List result = new ArrayList<>(feature.getTags().size()); - for (String tagValue : feature.getTags().values()) { - if (!tagValue.equals("*")) result.add(tagValue); - } - return result; - }); - } - - /** lazily get or create brand names index for country */ - private FeatureTermIndex getBrandNamesIndex(List countryCodes) - { - return synchronizedGetOrCreate(brandNamesIndexes, countryCodes, this::createBrandNamesIndex); - } - - private FeatureTermIndex createBrandNamesIndex(List countryCodes) - { - if (brandFeatureCollection == null) { - return new FeatureTermIndex(Collections.emptyList(), null); - } - - return new FeatureTermIndex(brandFeatureCollection.getAll(countryCodes), feature -> { - if (!feature.isSearchable()) return Collections.emptyList(); - return feature.getCanonicalNames(); - }); - } - - /** lazily get or create tags index for the given countries */ - private FeatureTagsIndex getBrandTagsIndex(List countryCodes) - { - return synchronizedGetOrCreate(brandTagsIndexes, countryCodes, this::createBrandTagsIndex); - } - - private FeatureTagsIndex createBrandTagsIndex(List countryCodes) - { - if (brandFeatureCollection == null) { - return new FeatureTagsIndex(Collections.emptyList()); - } - - return new FeatureTagsIndex(brandFeatureCollection.getAll(countryCodes)); - } - - //endregion - - //region Query builders - - public class QueryByIdBuilder - { - private final String id; - private Locale[] locale = new Locale[]{Locale.getDefault(), null}; - private String countryCode = null; - - private QueryByIdBuilder(String id) { this.id = id; } - - /**

Sets the locale(s) in which to present the results.

- *

You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example [new Locale("ca", "ES"), new Locale("es","ES")] if you - * wanted results preferredly in Catalan, but Spanish is also fine.

- * - *

null means to include unlocalized results.

- * - *

If nothing is specified, it defaults to [Locale.getDefault(), null], - * i.e. unlocalized results are included by default.

- * */ - public QueryByIdBuilder forLocale(Locale... locale) - { - this.locale = locale; - return this; - } - - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - public QueryByIdBuilder inCountry(String countryCode) - { - this.countryCode = countryCode; - return this; - } - - /** Returns the feature associated with the given id or null if it does not - * exist */ - public Feature get() - { - return FeatureDictionary.this.get(id, Arrays.asList(locale), countryCode); - } - } - - public class QueryByTagBuilder - { - private final Map tags; - private GeometryType geometryType = null; - private Locale[] locale = new Locale[]{Locale.getDefault(), null}; - private Boolean suggestion = null; - private String countryCode = null; - - private QueryByTagBuilder(Map tags) { this.tags = tags; } - - /** Sets for which geometry type to look. If not set or null, any will match. */ - public QueryByTagBuilder forGeometry(GeometryType geometryType) - { - this.geometryType = geometryType; - return this; - } - - /**

Sets the locale(s) in which to present the results.

- *

You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example [new Locale("ca", "ES"), new Locale("es","ES")] if you - * wanted results preferredly in Catalan, but Spanish is also fine.

- * - *

null means to include unlocalized results.

- * - *

If nothing is specified, it defaults to [Locale.getDefault(), null], - * i.e. unlocalized results are included by default.

- * */ - public QueryByTagBuilder forLocale(Locale... locale) - { - this.locale = locale; - return this; - } - - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - public QueryByTagBuilder inCountry(String countryCode) - { - this.countryCode = countryCode; - return this; - } - - /** Set whether to only include suggestions (=true) or to not include suggestions (=false). - * Suggestions are brands, like 7-Eleven. */ - public QueryByTagBuilder isSuggestion(Boolean suggestion) - { - this.suggestion = suggestion; - return this; - } - - /** Returns a list of dictionary entries that match or an empty list if nothing is - * found.
In rare cases, a set of tags may match multiple primary features, such as for - * tag combinations like shop=deli + amenity=cafe, so, this is why - * it is a list. */ - public List find() - { - return get(tags, geometryType, countryCode, suggestion, Arrays.asList(locale)); - } - } - - public class QueryByTermBuilder - { - private final String term; - private GeometryType geometryType = null; - private Locale[] locale = new Locale[]{Locale.getDefault()}; - private Boolean suggestion = null; - private int limit = 50; - private String countryCode = null; - - private QueryByTermBuilder(String term) { this.term = term; } - - /** Sets for which geometry type to look. If not set or null, any will match. */ - public QueryByTermBuilder forGeometry(GeometryType geometryType) - { - this.geometryType = geometryType; - return this; - } - - /**

Sets the locale(s) in which to present the results.

- *

You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example [new Locale("ca", "ES"), new Locale("es","ES")] if you - * wanted results preferredly in Catalan, but Spanish is also fine.

- * - *

null means to include unlocalized results.

- * - *

If nothing is specified, it defaults to [Locale.getDefault()], i.e. - * unlocalized results are excluded by default.

- * */ - public QueryByTermBuilder forLocale(Locale ...locale) - { - this.locale = locale; - return this; - } - - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - public QueryByTermBuilder inCountry(String countryCode) - { - this.countryCode = countryCode; - return this; - } - - /** Set whether to only include suggestions (=true) or to not include suggestions (=false). - * Suggestions are brands, like 7-Eleven. */ - public QueryByTermBuilder isSuggestion(Boolean suggestion) - { - this.suggestion = suggestion; - return this; - } - - /** limit how many results to return at most. Default is 50, -1 for unlimited. */ - public QueryByTermBuilder limit(int limit) - { - this.limit = limit; - return this; - } - - /** Returns a list of dictionary entries that match or an empty list if nothing is - * found.
- * Results are sorted mainly in this order: Matches with names, with brand names, then - * matches with terms (keywords). */ - public List find() - { - return get(term, geometryType, countryCode, suggestion, limit, Arrays.asList(locale)); - } - } - - //endregion -} diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt b/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt new file mode 100644 index 0000000..4912cdd --- /dev/null +++ b/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt @@ -0,0 +1,674 @@ +package de.westnordost.osmfeatures + +import de.westnordost.osmfeatures.Locale.Companion.default +import kotlin.math.min +import kotlin.text.Regex + +class FeatureDictionary internal constructor( + private val featureCollection: LocalizedFeatureCollection, + private val brandFeatureCollection: PerCountryFeatureCollection? +) { + private val brandNamesIndexes: Map, FeatureTermIndex> + private val brandTagsIndexes: Map, FeatureTagsIndex> + private val tagsIndexes: Map, FeatureTagsIndex> + private val namesIndexes: Map, FeatureTermIndex> + private val termsIndexes: Map, FeatureTermIndex> + private val tagValuesIndexes: Map, FeatureTermIndex> + + init { + tagsIndexes = HashMap() + namesIndexes = HashMap() + termsIndexes = HashMap() + tagValuesIndexes = HashMap() + brandNamesIndexes = HashMap() + brandTagsIndexes = HashMap() + // build indices for default locale + getTagsIndex(listOf(default, null)) + getNamesIndex(listOf(default)) + getTermsIndex(listOf(default)) + } + //region Get by id + /** Find feature by id */ + fun byId(id: String): QueryByIdBuilder { + return QueryByIdBuilder(id) + } + + private operator fun get(id: String, locales: List, countryCode: String?): Feature? { + val feature = featureCollection[id, locales] + if (feature != null) return feature + val countryCodes = dissectCountryCode(countryCode) + return brandFeatureCollection?.let { it[id, countryCodes]} + } + //endregion + //region Query by tags + /** Find matches by a set of tags */ + fun byTags(tags: Map): QueryByTagBuilder { + return QueryByTagBuilder(tags) + } + + private operator fun get( + tags: Map, + geometry: GeometryType?, + countryCode: String?, + isSuggestion: Boolean?, + locales: List + ): List { + if (tags.isEmpty()) return emptyList() + val foundFeatures: MutableList = ArrayList() + if (isSuggestion == null || !isSuggestion) { + foundFeatures.addAll(getTagsIndex(locales).getAll(tags)) + } + if (isSuggestion == null || isSuggestion) { + val countryCodes = dissectCountryCode(countryCode) + foundFeatures.addAll(getBrandTagsIndex(countryCodes).getAll(tags)) + } + CollectionUtils.removeIf( + foundFeatures + ) { feature: Feature -> + !isFeatureMatchingParameters( + feature, + geometry, + countryCode + ) + } + if (foundFeatures.size > 1) { + // only return of each category the most specific thing. I.e. will return + // McDonalds only instead of McDonalds,Fast-Food Restaurant,Amenity + val removeIds: MutableSet = HashSet() + for (feature in foundFeatures) { + removeIds.addAll(getParentCategoryIds(feature.id)) + } + if (removeIds.isNotEmpty()) { + CollectionUtils.removeIf( + foundFeatures + ) { feature: Feature -> + removeIds.contains( + feature.id + ) + } + } + } + foundFeatures.sortWith( object: Comparator{ + override fun compare(a: Feature, b: Feature): Int { + // 1. features with more matching tags first + val tagOrder: Int = b.tags.size - a.tags.size + if (tagOrder != 0) { + return tagOrder + } + + // 2. if search is not limited by locale, return matches not limited by locale first + if (locales.size == 1 && locales[0] == null) { + val localeOrder = + ((if (b.includeCountryCodes.isEmpty() && b.excludeCountryCodes.isEmpty()) 1 else 0) + - if (a.includeCountryCodes.isEmpty() && a.excludeCountryCodes + .isEmpty() + ) 1 else 0) + if (localeOrder != 0) return localeOrder + } + + // 3. features with more matching tags in addTags first + // https://github.com/openstreetmap/iD/issues/7927 + val numberOfMatchedAddTags = + (CollectionUtils.numberOfContainedEntriesInMap( + b.addTags, + tags.entries + ) + - CollectionUtils.numberOfContainedEntriesInMap( + a.addTags, + tags.entries + )) + if (numberOfMatchedAddTags != 0) return numberOfMatchedAddTags + return (100 * b.matchScore - 100 * a.matchScore).toInt() + }}) + return foundFeatures + } + //endregion + //region Query by term + /** Find matches by given search word */ + fun byTerm(term: String): QueryByTermBuilder { + return QueryByTermBuilder(term) + } + + private operator fun get( + search: String, + geometry: GeometryType?, + countryCode: String?, + isSuggestion: Boolean?, + limit: Int, + locales: List + ): List { + val canonicalSearch = StringUtils.canonicalize(search) + val sortNames = Comparator { a: Feature, b: Feature -> + // 1. exact matches first + val exactMatchOrder = + ((if (CollectionUtils.find( + b.names + ) { n: String? -> n == search } != null + ) 1 else 0) + - if (CollectionUtils.find( + a.names + ) { n: String? -> n == search } != null + ) 1 else 0) + if (exactMatchOrder != 0) return@Comparator exactMatchOrder + + // 2. exact matches case and diacritics insensitive first + val cExactMatchOrder = + ((if (CollectionUtils.find( + b.canonicalNames + ) { n: String? -> n == canonicalSearch } != null + ) 1 else 0) + - if (CollectionUtils.find( + a.canonicalNames + ) { n: String? -> n == canonicalSearch } != null + ) 1 else 0) + if (cExactMatchOrder != 0) return@Comparator cExactMatchOrder + + // 3. starts-with matches in string first + val startsWithOrder = + ((if (CollectionUtils.find( + b.canonicalNames + ) { n: String? -> + n!!.startsWith( + canonicalSearch + ) + } != null + ) 1 else 0) + - if (CollectionUtils.find( + a.canonicalNames + ) { n: String? -> + n!!.startsWith( + canonicalSearch + ) + } != null + ) 1 else 0) + if (startsWithOrder != 0) return@Comparator startsWithOrder + + // 4. features with higher matchScore first + val matchScoreOrder: Int = (100 * b.matchScore - 100 * a.matchScore).toInt() + if (matchScoreOrder != 0) return@Comparator matchScoreOrder + a.name.length - b.name.length + } + val result: MutableList = ArrayList() + if (isSuggestion == null || !isSuggestion) { + // a. matches with presets first + val foundFeaturesByName = getNamesIndex(locales).getAll(canonicalSearch) + CollectionUtils.removeIf( + foundFeaturesByName + ) { feature: Feature -> + !isFeatureMatchingParameters( + feature, + geometry, + countryCode + ) + } + foundFeaturesByName.sortedWith(sortNames) + result.addAll(foundFeaturesByName) + + // if limit is reached, can return earlier (performance improvement) + if (limit > 0 && result.size >= limit) return result.subList( + 0, + min(limit.toDouble(), result.size.toDouble()).toInt() + ) + } + if (isSuggestion == null || isSuggestion) { + // b. matches with brand names second + val countryCodes = dissectCountryCode(countryCode) + val foundBrandFeatures = getBrandNamesIndex(countryCodes).getAll(canonicalSearch) + CollectionUtils.removeIf( + foundBrandFeatures + ) { feature: Feature -> + !isFeatureMatchingParameters( + feature, + geometry, + countryCode + ) + } + foundBrandFeatures.sortedWith(sortNames) + result.addAll(foundBrandFeatures) + + // if limit is reached, can return earlier (performance improvement) + if (limit > 0 && result.size >= limit) return result.subList( + 0, + min(limit.toDouble(), result.size.toDouble()).toInt() + ) + } + if (isSuggestion == null || !isSuggestion) { + // c. matches with terms third + val foundFeaturesByTerm = getTermsIndex(locales).getAll(canonicalSearch) + CollectionUtils.removeIf( + foundFeaturesByTerm + ) { feature: Feature -> + !isFeatureMatchingParameters( + feature, + geometry, + countryCode + ) + } + if (foundFeaturesByTerm.isNotEmpty()) { + val alreadyFoundFeatures: Set = HashSet(result) + CollectionUtils.removeIf( + foundFeaturesByTerm + ) { feature: Feature -> + alreadyFoundFeatures.contains( + feature + ) + } + } + foundFeaturesByTerm.sortedWith { a: Feature, b: Feature -> (100 * b.matchScore - 100 * a.matchScore).toInt() } + result.addAll(foundFeaturesByTerm) + + // if limit is reached, can return earlier (performance improvement) + if (limit > 0 && result.size >= limit) return result.subList( + 0, + min(limit.toDouble(), result.size.toDouble()).toInt() + ) + } + if (isSuggestion == null || !isSuggestion) { + // d. matches with tag values fourth + val foundFeaturesByTagValue = getTagValuesIndex(locales).getAll(canonicalSearch) + CollectionUtils.removeIf( + foundFeaturesByTagValue + ) { feature: Feature -> + !isFeatureMatchingParameters( + feature, + geometry, + countryCode + ) + } + if (foundFeaturesByTagValue.isNotEmpty()) { + val alreadyFoundFeatures: Set = HashSet(result) + CollectionUtils.removeIf( + foundFeaturesByTagValue + ) { feature: Feature -> + alreadyFoundFeatures.contains( + feature + ) + } + } + result.addAll(foundFeaturesByTagValue) + } + return result.subList(0, min(limit.toDouble(), result.size.toDouble()).toInt()) + } + //endregion + //region Lazily get or create Indexes + /** lazily get or create tags index for given locale(s) */ + private fun getTagsIndex(locales: List): FeatureTagsIndex { + return CollectionUtils.synchronizedGetOrCreate( + tagsIndexes, locales + ) { locales -> + createTagsIndex( + locales + ) + } + } + + private fun createTagsIndex(locales: List): FeatureTagsIndex { + return FeatureTagsIndex(featureCollection.getAll(locales)) + } + + /** lazily get or create names index for given locale(s) */ + private fun getNamesIndex(locales: List): FeatureTermIndex { + return CollectionUtils.synchronizedGetOrCreate( + namesIndexes, locales + ) { locales -> + createNamesIndex( + locales + ) + } + } + + private fun createNamesIndex(locales: List): FeatureTermIndex { + return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature? -> + if (feature != null) { + if (!feature.isSearchable) return@Selector emptyList() + } + val names: List = feature?.canonicalNames ?: emptyList() + val result = ArrayList(names) + for (name in names) { + if (name.contains(" ")) { + result.addAll( + name.replace("[()]".toRegex(), "").split(" ".toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray().toList()) + } + } + result + }) + } + + /** lazily get or create terms index for given locale(s) */ + private fun getTermsIndex(locales: List): FeatureTermIndex { + return CollectionUtils.synchronizedGetOrCreate( + termsIndexes, locales + ) { locales -> + createTermsIndex( + locales + ) + } + } + + private fun createTermsIndex(locales: List): FeatureTermIndex { + return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature? -> + if (feature != null) { + if (!feature.isSearchable) return@Selector emptyList() + } + feature?.canonicalTerms.orEmpty() + }) + } + + /** lazily get or create tag values index */ + private fun getTagValuesIndex(locales: List): FeatureTermIndex { + return CollectionUtils.synchronizedGetOrCreate( + tagValuesIndexes, locales + ) { locales -> + createTagValuesIndex( + locales + ) + } + } + + private fun createTagValuesIndex(locales: List): FeatureTermIndex { + return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature? -> + if (feature != null) { + if (!feature.isSearchable) return@Selector emptyList() + } + val result: ArrayList? = + feature?.tags?.let { ArrayList(it.size) } + if (feature != null) { + for (tagValue in feature.tags.values) { + if (tagValue != "*") result?.add(tagValue) + } + } + return@Selector result!! + }) + } + + /** lazily get or create brand names index for country */ + private fun getBrandNamesIndex(countryCodes: List): FeatureTermIndex { + return CollectionUtils.synchronizedGetOrCreate( + brandNamesIndexes, countryCodes + ) { countryCodes -> + createBrandNamesIndex( + countryCodes + ) + } + } + + private fun createBrandNamesIndex(countryCodes: List): FeatureTermIndex { + return if (brandFeatureCollection == null) { + FeatureTermIndex(emptyList(), null) + } else FeatureTermIndex( + brandFeatureCollection.getAll(countryCodes) + ) { + if (it?.isSearchable == true) emptyList() else it?.canonicalNames.orEmpty() + } + } + + /** lazily get or create tags index for the given countries */ + private fun getBrandTagsIndex(countryCodes: List): FeatureTagsIndex { + return CollectionUtils.synchronizedGetOrCreate( + brandTagsIndexes, countryCodes + ) { countryCodes -> + createBrandTagsIndex( + countryCodes + ) + } + } + + private fun createBrandTagsIndex(countryCodes: List): FeatureTagsIndex { + return if (brandFeatureCollection == null) { + FeatureTagsIndex(emptyList()) + } else FeatureTagsIndex(brandFeatureCollection.getAll(countryCodes)) + } + + //endregion + //region Query builders + inner class QueryByIdBuilder(private val id: String) { + private var locale: List = listOf(default) + private var countryCode: String? = null + + private operator fun get(id: String, locales: List, countryCode: String): Feature? { + val feature = featureCollection[id, locales] + if (feature != null) return feature + val countryCodes = dissectCountryCode(countryCode) + brandFeatureCollection?.let { + return it[id, countryCodes] + } + throw NullPointerException("brandFeatureCollection is null") + } + + /** + * + *Sets the locale(s) in which to present the results. + * + * You can specify several locales in + * a row to each fall back to if a translation does not exist in the locale before that. + * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you + * wanted results preferredly in Catalan, but Spanish is also fine. + * + * + * `null` means to include unlocalized results. + * + * + * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, + * i.e. unlocalized results are included by default. + */ + fun forLocale(vararg locales: Locale?): QueryByIdBuilder { + this.locale = locales.toList() + return this + } + + /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the + * country/state the element is in. If not specified, will only return matches that are not + * county-specific. */ + fun inCountry(countryCode: String?): QueryByIdBuilder { + this.countryCode = countryCode + return this + } + + /** Returns the feature associated with the given id or `null` if it does not + * exist */ + fun get(): Feature? { + return countryCode?.let { this[id, locale, it] } + } + } + + inner class QueryByTagBuilder (private val tags: Map) { + private var geometryType: GeometryType? = null + private var locale: List = listOf(default) + private var suggestion: Boolean? = null + private var countryCode: String? = null + + /** Sets for which geometry type to look. If not set or `null`, any will match. */ + fun forGeometry(geometryType: GeometryType?): QueryByTagBuilder { + this.geometryType = geometryType + return this + } + + /** + * + *Sets the locale(s) in which to present the results. + * + * You can specify several locales in + * a row to each fall back to if a translation does not exist in the locale before that. + * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you + * wanted results preferredly in Catalan, but Spanish is also fine. + * + * + * `null` means to include unlocalized results. + * + * + * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, + * i.e. unlocalized results are included by default. + */ + fun forLocale(vararg locales: Locale?): QueryByTagBuilder { + this.locale = locales.toList() + return this + } + + /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the + * country/state the element is in. If not specified, will only return matches that are not + * county-specific. */ + fun inCountry(countryCode: String?): QueryByTagBuilder { + this.countryCode = countryCode + return this + } + + /** Set whether to only include suggestions (=true) or to not include suggestions (=false). + * Suggestions are brands, like 7-Eleven. */ + fun isSuggestion(suggestion: Boolean?): QueryByTagBuilder { + this.suggestion = suggestion + return this + } + + /** Returns a list of dictionary entries that match or an empty list if nothing is + * found.

In rare cases, a set of tags may match multiple primary features, such as for + * tag combinations like `shop=deli` + `amenity=cafe`, so, this is why + * it is a list. */ + fun find(): List { + return get(tags, geometryType, countryCode, suggestion, locale) + } + } + + inner class QueryByTermBuilder(private val term: String) { + private var geometryType: GeometryType? = null + private var locale: List = listOf(default) + private var suggestion: Boolean? = null + private var limit = 50 + private var countryCode: String? = null + + /** Sets for which geometry type to look. If not set or `null`, any will match. */ + fun forGeometry(geometryType: GeometryType?): QueryByTermBuilder { + this.geometryType = geometryType + return this + } + + /** + * + *Sets the locale(s) in which to present the results. + * + * You can specify several locales in + * a row to each fall back to if a translation does not exist in the locale before that. + * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you + * wanted results preferredly in Catalan, but Spanish is also fine. + * + * + * `null` means to include unlocalized results. + * + * + * If nothing is specified, it defaults to `[Locale.getDefault()]`, i.e. + * unlocalized results are excluded by default. + */ + fun forLocale(vararg locales: Locale?): QueryByTermBuilder { + this.locale = locales.toList() + return this + } + + /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the + * country/state the element is in. If not specified, will only return matches that are not + * county-specific. */ + fun inCountry(countryCode: String?): QueryByTermBuilder { + this.countryCode = countryCode + return this + } + + /** Set whether to only include suggestions (=true) or to not include suggestions (=false). + * Suggestions are brands, like 7-Eleven. */ + fun isSuggestion(suggestion: Boolean?): QueryByTermBuilder { + this.suggestion = suggestion + return this + } + + /** limit how many results to return at most. Default is 50, -1 for unlimited. */ + fun limit(limit: Int): QueryByTermBuilder { + this.limit = limit + return this + } + + /** Returns a list of dictionary entries that match or an empty list if nothing is + * found.

+ * Results are sorted mainly in this order: Matches with names, with brand names, then + * matches with terms (keywords). */ + fun find(): List { + return get(term, geometryType, countryCode, suggestion, limit, locale) + } + } //endregion + + companion object { + private val VALID_COUNTRY_CODE_REGEX = Regex("([A-Z]{2})(?:-([A-Z0-9]{1,3}))?") + /** Create a new FeatureDictionary which gets its data from the given directory. Optionally, + * a path to brand presets can be specified. */ + /** Create a new FeatureDictionary which gets its data from the given directory. */ + @JvmOverloads + fun create(presetsBasePath: String, brandPresetsBasePath: String? = null): FeatureDictionary { + val featureCollection: LocalizedFeatureCollection = + IDLocalizedFeatureCollection(FileSystemAccess(presetsBasePath)) + val brandsFeatureCollection: PerCountryFeatureCollection? = + if (brandPresetsBasePath != null) IDBrandPresetsFeatureCollection( + FileSystemAccess(brandPresetsBasePath) + ) else null + return FeatureDictionary(featureCollection, brandsFeatureCollection) + } + + //endregion + //region Utility / Filter functions + private fun getParentCategoryIds(id: String): Collection { + var id: String? = id + val result: MutableList = ArrayList() + do { + id = getParentId(id) + if (id != null) result.add(id) + } while (id != null) + return result + } + + private fun getParentId(id: String?): String? { + val lastSlashIndex = id!!.lastIndexOf("/") + return if (lastSlashIndex == -1) null else id.substring(0, lastSlashIndex) + } + + private fun isFeatureMatchingParameters( + feature: Feature, + geometry: GeometryType?, + countryCode: String? + ): Boolean { + if (geometry != null && !feature.geometry?.contains(geometry)!!) return false + val include: List = feature.includeCountryCodes + val exclude: List = feature.excludeCountryCodes + if (include.isNotEmpty() || exclude.isNotEmpty()) { + if (countryCode == null) return false + if (include.isNotEmpty() && !matchesAnyCountryCode(countryCode, include)) return false + if (matchesAnyCountryCode(countryCode, exclude)) return false + } + return true + } + + private fun dissectCountryCode(countryCode: String?): List { + val result: MutableList = ArrayList() + // add default / international + result.add(null) + countryCode?.let { + val matcher = VALID_COUNTRY_CODE_REGEX.find(it) + if (matcher?.groups?.isNotEmpty() == true) { + // add ISO 3166-1 alpha2 (e.g. "US") + result.add(matcher.groups[1].toString()) + if (matcher.groups.size == 2 && matcher.groups[2] != null) { + // add ISO 3166-2 (e.g. "US-NY") + result.add(it) + } + } + } + return result + } + + private fun matchesAnyCountryCode(showOnly: String, featureCountryCodes: List): Boolean { + for (featureCountryCode in featureCountryCodes) { + if (matchesCountryCode(showOnly, featureCountryCode)) return true + } + return false + } + + private fun matchesCountryCode(showOnly: String, featureCountryCode: String): Boolean { + return showOnly == featureCountryCode || showOnly.substring(0, 2) == featureCountryCode + } + } +} diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureTagsIndex.kt b/library/src/main/java/de/westnordost/osmfeatures/FeatureTagsIndex.kt index 214ed03..a5e4013 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FeatureTagsIndex.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/FeatureTagsIndex.kt @@ -1,39 +1,33 @@ -package de.westnordost.osmfeatures; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +package de.westnordost.osmfeatures /** Index that makes finding Features whose tags are completely contained by a given set of tags - * very efficient. + * very efficient. * - * Based on ContainedMapTree data structure, see that class. */ -class FeatureTagsIndex -{ - private final Map, List> featureMap; - private final ContainedMapTree tree; + * Based on ContainedMapTree data structure, see that class. */ +internal class FeatureTagsIndex(features: Collection?) { + private val featureMap: MutableMap, MutableList> + private val tree: ContainedMapTree - public FeatureTagsIndex(Iterable features) - { - featureMap = new HashMap<>(); - for (Feature feature : features) - { - Map map = feature.getTags(); - if (!featureMap.containsKey(map)) featureMap.put(map, new ArrayList<>(1)); - featureMap.get(map).add(feature); + init { + featureMap = HashMap() + if (features != null) { + for (feature in features) { + val map: Map = feature!!.tags + if (!featureMap.containsKey(map)) featureMap[map] = ArrayList(1) + if (feature != null) { + featureMap[map]!!.add(feature) + } + } } - tree = new ContainedMapTree<>(featureMap.keySet()); + tree = ContainedMapTree(featureMap.keys) } - public List getAll(Map tags) - { - List result = new ArrayList<>(); - for (Map map : tree.getAll(tags)) - { - List fs = featureMap.get(map); - if (fs != null) result.addAll(fs); + fun getAll(tags: Map?): List { + val result: MutableList = ArrayList() + for (map in tree.getAll(tags)) { + val fs: List? = featureMap[map] + if (fs != null) result.addAll(fs) } - return result; + return result } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt b/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt index d61e170..ca21a67 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt @@ -1,49 +1,38 @@ -package de.westnordost.osmfeatures; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +package de.westnordost.osmfeatures /** Index that makes finding Features whose name/term/... starts with a given string very efficient. * - * Based on the StartsWithStringTree data structure, see that class. */ -class FeatureTermIndex -{ - private final Map> featureMap; - private final StartsWithStringTree tree; + * Based on the StartsWithStringTree data structure, see that class. */ +internal class FeatureTermIndex(features: Collection?, selector: Selector?) { + private val featureMap: MutableMap> + private val tree: StartsWithStringTree - public FeatureTermIndex(Iterable features, Selector selector) - { - featureMap = new HashMap<>(); - for (Feature feature : features) - { - Collection strings = selector.getStrings(feature); - for (String string : strings) - { - if (!featureMap.containsKey(string)) featureMap.put(string, new ArrayList<>(1)); - featureMap.get(string).add(feature); + init { + featureMap = HashMap() + if (features != null) { + for (feature in features) { + val strings: Collection = selector!!.getStrings(feature) + for (string in strings) { + if (!featureMap.containsKey(string)) featureMap[string] = ArrayList(1) + if (feature != null) { + featureMap[string]!!.add(feature) + } + } } } - tree = new StartsWithStringTree(featureMap.keySet()); + tree = StartsWithStringTree(featureMap.keys) } - public List getAll(String startsWith) - { - Set result = new HashSet<>(); - for (String string : tree.getAll(startsWith)) - { - List fs = featureMap.get(string); - if (fs != null) result.addAll(fs); + fun getAll(startsWith: String?): List { + val result: MutableSet = HashSet() + for (string in tree.getAll(startsWith)) { + val fs: List? = featureMap[string] + if (fs != null) result.addAll(fs) } - return new ArrayList<>(result); + return ArrayList(result) } - public interface Selector - { - List getStrings(Feature feature); + fun interface Selector { + fun getStrings(feature: Feature?): List } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.kt b/library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.kt index 5b11e3d..066f5e6 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.kt @@ -1,10 +1,9 @@ -package de.westnordost.osmfeatures; +package de.westnordost.osmfeatures -import java.io.IOException; -import java.io.InputStream; +import okio.FileHandle -interface FileAccessAdapter -{ - boolean exists(String name) throws IOException; - InputStream open(String name) throws IOException; +internal interface FileAccessAdapter { + + fun exists(name: String): Boolean + fun open(name: String): FileHandle } \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.kt b/library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.kt index b501ae4..09440c1 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.kt @@ -1,23 +1,19 @@ -package de.westnordost.osmfeatures; +package de.westnordost.osmfeatures +import okio.FileHandle +import okio.FileSystem +import okio.Path.Companion.toPath -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; +internal class FileSystemAccess(val basePath: String) : FileAccessAdapter { + private val fs = FileSystem.SYSTEM + init { + fs.metadataOrNull(basePath.toPath())?.let { require(it.isDirectory) { "basePath must be a directory" } } + } -class FileSystemAccess implements FileAccessAdapter -{ - private final File basePath; + override fun exists(name: String): Boolean { - FileSystemAccess(File basePath) - { - if(!basePath.isDirectory()) throw new IllegalArgumentException("basePath must be a directory"); - this.basePath = basePath; + return fs.exists(("$basePath/$name").toPath()) } - - @Override public boolean exists(String name) { return new File(basePath, name).exists(); } - @Override public InputStream open(String name) throws IOException - { - return new FileInputStream(new File(basePath, name)); + override fun open(name: String): FileHandle { + return fs.openReadOnly(("$basePath/$name".toPath())) } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/GeometryType.kt b/library/src/main/java/de/westnordost/osmfeatures/GeometryType.kt index b8bd92c..bf4c02c 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/GeometryType.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/GeometryType.kt @@ -1,15 +1,18 @@ -package de.westnordost.osmfeatures; - -public enum GeometryType -{ - /** an OSM node that is not a member of any way */ - POINT, - /** an OSM node that is a member of one or more ways */ - VERTEX, - /** an OSM way that is not an area */ - LINE, - /** a OSM way that is closed/circular (the first and last nodes are the same) or a type=multipolygon relation */ - AREA, - /** an OSM relation */ - RELATION -} +package de.westnordost.osmfeatures + +enum class GeometryType { + /** an OSM node that is not a member of any way */ + POINT, + + /** an OSM node that is a member of one or more ways */ + VERTEX, + + /** an OSM way that is not an area */ + LINE, + + /** a OSM way that is closed/circular (the first and last nodes are the same) or a type=multipolygon relation */ + AREA, + + /** an OSM relation */ + RELATION +} diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.java b/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.java index 90d1abb..aebc51a 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.java +++ b/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.java @@ -2,6 +2,7 @@ import static de.westnordost.osmfeatures.CollectionUtils.synchronizedGetOrCreate; +import okio.FileHandle; import org.json.JSONException; import java.io.IOException; @@ -68,7 +69,7 @@ private List loadFeatures(String countryCode) { String filename = getPresetsFileName(countryCode); try { if (!fileAccess.exists(filename)) return Collections.emptyList(); - try (InputStream is = fileAccess.open(filename)) { + try (FileHandle is = fileAccess.open(filename)) { return new IDPresetsJsonParser(true).parse(is); } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt index d49aab9..28b4a82 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt @@ -1,153 +1,127 @@ -package de.westnordost.osmfeatures; +package de.westnordost.osmfeatures -import org.json.JSONException; +import org.json.JSONException +import java.io.IOException -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; - -import static de.westnordost.osmfeatures.CollectionUtils.synchronizedGetOrCreate; /** Localized feature collection sourcing from iD presets defined in JSON. * - * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that - * there is a presets.json which includes all the features. The translations are expected to be - * located in the same directory named like e.g. de.json, pt-BR.json etc. */ -class IDLocalizedFeatureCollection implements LocalizedFeatureCollection -{ - private static final String FEATURES_FILE = "presets.json"; - - private final FileAccessAdapter fileAccess; - - private final LinkedHashMap featuresById; - - private final Map> localizedFeaturesList = new HashMap<>(); - - private final Map, LinkedHashMap> localizedFeatures = new HashMap<>(); - - IDLocalizedFeatureCollection(FileAccessAdapter fileAccess) - { - this.fileAccess = fileAccess; - List features = loadFeatures(); - featuresById = new LinkedHashMap<>(features.size()); - for (BaseFeature feature : features) { - this.featuresById.put(feature.getId(), feature); - } - } - - private List loadFeatures() - { - try(InputStream is = fileAccess.open(FEATURES_FILE)) - { - return new IDPresetsJsonParser().parse(is); - } - catch (IOException | JSONException e) - { - throw new RuntimeException(e); - } - } - - - @Override public Collection getAll(List locales) - { - return getOrLoadLocalizedFeatures(locales).values(); - } - - @Override public Feature get(String id, List locales) - { - return getOrLoadLocalizedFeatures(locales).get(id); - } - - private LinkedHashMap getOrLoadLocalizedFeatures(List locales) - { - return synchronizedGetOrCreate(localizedFeatures, locales, this::loadLocalizedFeatures); - } - - private LinkedHashMap loadLocalizedFeatures(List locales) - { - LinkedHashMap result = new LinkedHashMap<>(featuresById.size()); - ListIterator it = locales.listIterator(locales.size()); - while (it.hasPrevious()) - { - Locale locale = it.previous(); - if (locale != null) - { - for (Locale localeComponent : getLocaleComponents(locale)) - { - putAllFeatures(result, getOrLoadLocalizedFeaturesList(localeComponent)); - } - } else { - putAllFeatures(result, featuresById.values()); - } - } - - return result; - } - - private List getOrLoadLocalizedFeaturesList(Locale locale) - { - return synchronizedGetOrCreate(localizedFeaturesList, locale, this::loadLocalizedFeaturesList); - } - - private List loadLocalizedFeaturesList(Locale locale) - { - String filename = getLocalizationFilename(locale); - try - { - if (!fileAccess.exists(filename)) return Collections.emptyList(); - try (InputStream is = fileAccess.open(filename)) - { - return new IDPresetsTranslationJsonParser().parse(is, locale, featuresById); - } - } - catch (IOException | JSONException e) { throw new RuntimeException(e); } - } - - private static String getLocalizationFilename(Locale locale) - { - /* we only want language+country+script of the locale, not anything else. So we construct + * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that + * there is a presets.json which includes all the features. The translations are expected to be + * located in the same directory named like e.g. de.json, pt-BR.json etc. */ +internal class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : LocalizedFeatureCollection { + private val featuresById: LinkedHashMap + private val localizedFeaturesList: Map> = HashMap() + private val localizedFeatures: Map, LinkedHashMap> = HashMap() + + init { + val features = loadFeatures() + featuresById = LinkedHashMap(features.size) + for (feature in features) { + featuresById[feature.id] = feature + } + } + + private fun loadFeatures(): List { + try { + fileAccess.open(FEATURES_FILE).use { `is` -> + return IDPresetsJsonParser().parse(`is`) + } + } catch (e: IOException) { + throw RuntimeException(e) + } catch (e: JSONException) { + throw RuntimeException(e) + } + } + private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap { + return CollectionUtils.synchronizedGetOrCreate( + localizedFeatures, locales + ) { locales -> + loadLocalizedFeatures( + locales + ) + } + } + + private fun loadLocalizedFeatures(locales: List): LinkedHashMap { + val result = LinkedHashMap(featuresById.size) + val it = locales.listIterator(locales.size) + while (it.hasPrevious()) { + val locale = it.previous() + if (locale != null) { + for (localeComponent in getLocaleComponents(locale)) { + putAllFeatures(result, getOrLoadLocalizedFeaturesList(localeComponent)) + } + } else { + putAllFeatures(result, featuresById.values) + } + } + return result + } + + private fun getOrLoadLocalizedFeaturesList(locale: Locale): List { + return CollectionUtils.synchronizedGetOrCreate( + localizedFeaturesList, locale + ) { locale: Locale -> + loadLocalizedFeaturesList( + locale + ) + } + } + + private fun loadLocalizedFeaturesList(locale: Locale): List { + val filename = getLocalizationFilename(locale) + try { + if (!fileAccess.exists(filename)) return emptyList() + fileAccess.open(filename).use { `is` -> + return IDPresetsTranslationJsonParser().parse(`is`, locale, featuresById) + } + } catch (e: IOException) { + throw RuntimeException(e) + } catch (e: JSONException) { + throw RuntimeException(e) + } + } + + override fun getAll(locales: List): Collection { + return getOrLoadLocalizedFeatures(locales).values; + } + + override operator fun get(id: String, locales: List): Feature? { + return getOrLoadLocalizedFeatures(locales)[id]; + } + + companion object { + private const val FEATURES_FILE = "presets.json" + private fun getLocalizationFilename(locale: Locale): String { + /* we only want language+country+script of the locale, not anything else. So we construct it anew here */ - return new Locale.Builder() - .setLanguage(locale.getLanguage()) - .setRegion(locale.getCountry()) - .setScript(locale.getScript()) - .build() - .toLanguageTag() + ".json"; - } - - private static List getLocaleComponents(Locale locale) - { - String lang = locale.getLanguage(); - String country = locale.getCountry(); - String script = locale.getScript(); - List result = new ArrayList<>(4); - - result.add(new Locale(lang)); - - if (!country.isEmpty()) - result.add(new Locale.Builder().setLanguage(lang).setRegion(country).build()); - - if (!script.isEmpty()) - result.add(new Locale.Builder().setLanguage(lang).setScript(script).build()); - - if (!country.isEmpty() && !script.isEmpty()) - result.add(new Locale.Builder().setLanguage(lang).setRegion(country).setScript(script).build()); - - return result; - } - - private static void putAllFeatures(Map map, Iterable features) - { - for (Feature feature : features) - { - map.put(feature.getId(), feature); - } - } + return Locale.Builder() + .setLanguage(locale.language) + .setRegion(locale.country) + .setScript(locale.script) + .build() + .toLanguageTag() + ".json" + } + + private fun getLocaleComponents(locale: Locale): List { + val lang = locale.language + val country = locale.country + val script = locale.script + val result: MutableList = ArrayList(4) + result.add(Locale(lang)) + if (country.isNotEmpty()) result.add(Locale.Builder().setLanguage(lang).setRegion(country).build()) + if (script!!.isNotEmpty()) result.add(Locale.Builder().setLanguage(lang).setScript(script).build()) + if (country.isNotEmpty() && script.isNotEmpty()) result.add( + Locale.Builder().setLanguage(lang).setRegion(country).setScript(script).build() + ) + return result + } + + private fun putAllFeatures(map: MutableMap, features: Iterable) { + for (feature in features) { + map[feature.id] = feature + } + } + } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.java b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.java index 2ff3058..ee943ea 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.java +++ b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.java @@ -18,7 +18,7 @@ import static de.westnordost.osmfeatures.JsonUtils.parseStringMap; /** Parses this file - * https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json + * ... * into map of id -> Feature. */ class IDPresetsJsonParser { @@ -55,7 +55,10 @@ private BaseFeature parseFeature(String id, JSONObject p) if(tags.isEmpty()) return null; List geometry = parseList(p.getJSONArray("geometry"), - item -> GeometryType.valueOf(((String)item).toUpperCase(Locale.US))); + item -> { + assert item != null; + return GeometryType.valueOf(((String)item).toUpperCase(Locale.US)); + }); String name = p.optString("name"); String icon = p.optString("icon"); String imageURL = p.optString("imageURL"); diff --git a/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt b/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt index 707e18e..808a421 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt @@ -1,57 +1,53 @@ -package de.westnordost.osmfeatures; +package de.westnordost.osmfeatures -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -class JsonUtils { - public interface Transformer { T apply(Object item); } - - public static List parseList(JSONArray array, Transformer t) throws JSONException - { - if(array == null) return new ArrayList<>(0); - List result = new ArrayList<>(array.length()); - for (int i = 0; i < array.length(); i++) - { - T item = t.apply(array.get(i)); - if(item != null) result.add(item); +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream +internal object JsonUtils { + @Throws(JSONException::class) + @JvmStatic + fun parseList(array: JSONArray?, t: Transformer): List { + if (array == null) return ArrayList(0) + val result: MutableList = ArrayList(array.length()) + for (i in 0 until array.length()) { + val item: T? = t.apply(array[i]) + if (item != null) result.add(item) } - return result; + return result } - public static Map parseStringMap(JSONObject map) throws JSONException - { - if(map == null) return new HashMap<>(1); - Map result = new HashMap<>(map.length()); - for (Iterator it = map.keys(); it.hasNext(); ) - { - String key = it.next().intern(); - result.put(key, map.getString(key)); + @Throws(JSONException::class) + @JvmStatic + fun parseStringMap(map: JSONObject?): Map { + if (map == null) return HashMap(1) + val result: MutableMap = HashMap(map.length()) + val it = map.keys() + while (it.hasNext()) { + val key = it.next().intern() + result[key] = map.getString(key) } - return result; + return result } // this is only necessary because Android uses some old version of org.json where // new JSONObject(new JSONTokener(inputStream)) is not defined... - public static JSONObject createFromInputStream(InputStream inputStream) throws IOException - { - ByteArrayOutputStream result = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int length; - while ((length = inputStream.read(buffer)) != -1) - { - result.write(buffer, 0, length); + @Throws(IOException::class) + @JvmStatic + fun createFromInputStream(inputStream: InputStream): JSONObject { + val result = ByteArrayOutputStream() + val buffer = ByteArray(1024) + var length: Int + while (inputStream.read(buffer).also { length = it } != -1) { + result.write(buffer, 0, length) } - String jsonString = result.toString("UTF-8"); - return new JSONObject(jsonString); + val jsonString = result.toString("UTF-8") + return JSONObject(jsonString) + } + + interface Transformer { + fun apply(item: Any?): T } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/Locale.kt b/library/src/main/java/de/westnordost/osmfeatures/Locale.kt index 31c86bf..6b66325 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/Locale.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/Locale.kt @@ -4,10 +4,10 @@ import io.fluidsonic.locale.LanguageTag class Locale( - val language: String, - val region: String = "", - val script: String = "", - val variant: String? = null) { + val language: String, + private val region: String?, + val script: String?, + val variant: String? = null) { companion object { @@ -26,11 +26,8 @@ class Locale( @JvmField val CHINESE: Locale = Locale("zh") - const val SEP = "-" - const val PRIVATEUSE = "x" - - @kotlin.jvm.JvmStatic - val default: Locale = ENGLISH + @JvmStatic + val default: Locale? = null fun toTitleString(s: String): String { var len: Int if (s.length.also { len = it } == 0) { @@ -68,17 +65,13 @@ class Locale( val country : String - get() = this.region + get() = this.region.orEmpty() private var languageTag : String? = null - constructor(lang: String) : this(lang,"", "", null) { - - } + constructor(lang: String) : this(lang,"", "", null) - constructor(lang: String, region: String) : this(lang, region, "", null) { - - } + constructor(lang: String, region: String) : this(lang, region, "", null) fun toLanguageTag(): String { val lTag: String? = this.languageTag @@ -87,56 +80,9 @@ class Locale( } this.languageTag = LanguageTag.forLanguage(language, script, region).toString() - return this.languageTag!! -// val buf = StringBuilder() -// -// var subtag = tag.language -// if (subtag.isNotEmpty()) { -// buf.append(subtag.lowercase()) -// } -// -// subtag = tag.script -// if (subtag.isNotEmpty()) { -// buf.append(SEP) -// buf.append(toTitleString(subtag)) -// } -// -// subtag = tag.region -// if (subtag.isNotEmpty()) { -// buf.append(SEP) -// buf.append(subtag.uppercase()) -// } -// -// var subtags = tag.variants -// for (s in subtags) { -// buf.append(SEP) -// // preserve casing -// buf.append(s) -// } -// -// subtags = tag.getExtensions() -// for (s in subtags) { -// buf.append(SEP) -// buf.append(subtag.lowercase()) -// } -// -// subtag = tag.privateuse -// if (subtag.isNotEmpty()) { -// if (buf.isNotEmpty()) { -// buf.append(SEP) -// } -// buf.append(PRIVATEUSE).append(SEP) -// // preserve casing -// buf.append(subtag) -// } -// -// val langTag = buf.toString() -// synchronized(this) { -// if (this.languageTag == null) { -// this.languageTag = langTag -// } -// } -// return langTag + this.languageTag?.let{ return it} + throw NullPointerException("LanguageTag could not be parsed") + } @@ -147,24 +93,25 @@ class Locale( return this } - private var region: String = "" + private var region: String? = null - fun setRegion(region: String) : Builder { - this.region = region + fun setRegion(region: String?) : Builder { + this.region = region.orEmpty() return this } - private var script: String = "" - fun setScript(script: String) : Builder { - this.script = script + private var script: String? = null + fun setScript(script: String?) : Builder { + + this.script = script.orEmpty() return this } fun build(): Locale { - if(language == null) { - throw NullPointerException("Builder language is null") + language?.let { + return Locale(it, region, script) } - return Locale(language!!, region, script) + throw IllegalArgumentException("Language should not be empty") } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt index ee55005..f58d6c9 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt @@ -1,62 +1,58 @@ -package de.westnordost.osmfeatures; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - +package de.westnordost.osmfeatures /** Data class associated with the Feature interface. Represents a localized feature. * - * I.e. the name and terms are specified in the given locale. */ -class LocalizedFeature implements Feature { - - private final BaseFeature p; - private final List names; - private final List terms; - private final List canonicalNames; - private final List canonicalTerms; - private final Locale locale; - - public LocalizedFeature(BaseFeature p, Locale locale, List names, List terms) - { - this.p = p; - this.names = names; - this.terms = terms; - this.locale = locale; - - List canonicalNames = new ArrayList<>(names.size()); - for (String name : names) - { - canonicalNames.add(StringUtils.canonicalize(name)); + * I.e. the name and terms are specified in the given locale. */ +internal class LocalizedFeature( + private val p: BaseFeature, + override val locale: Locale, + override val names: List, + override val terms: List +) : + Feature { + override val canonicalNames: List + override val canonicalTerms: List + + init { + val canonicalNames: MutableList = ArrayList(names.size) + for (name in names) { + canonicalNames.add(StringUtils.canonicalize(name)) } - this.canonicalNames = Collections.unmodifiableList(canonicalNames); - - List canonicalTerms = new ArrayList<>(terms.size()); - for (String term : terms) - { - canonicalTerms.add(StringUtils.canonicalize(term)); + this.canonicalNames = canonicalNames.toList() + val canonicalTerms: MutableList = ArrayList(terms.size) + for (term in terms) { + canonicalTerms.add(StringUtils.canonicalize(term)) } - this.canonicalTerms = Collections.unmodifiableList(canonicalTerms); + this.canonicalTerms = canonicalTerms.toList() } - @Override public String getId() { return p.getId(); } - @Override public Map getTags() { return p.getTags(); } - @Override public List getGeometry() { return p.getGeometry(); } - @Override public String getName() { return names.get(0); } - @Override public String getIcon() { return p.getIcon(); } - @Override public String getImageURL() { return p.getImageURL(); } - @Override public List getNames() { return names; } - @Override public List getTerms() { return terms; } - @Override public List getIncludeCountryCodes() { return p.getIncludeCountryCodes(); } - @Override public List getExcludeCountryCodes() { return p.getExcludeCountryCodes(); } - @Override public boolean isSearchable() { return p.isSearchable(); } - @Override public double getMatchScore() { return p.getMatchScore(); } - @Override public Map getAddTags() { return p.getAddTags(); } - @Override public Map getRemoveTags() { return p.getRemoveTags(); } - @Override public List getCanonicalNames() { return canonicalNames; } - @Override public List getCanonicalTerms() { return canonicalTerms; } - @Override public boolean isSuggestion() { return p.isSuggestion(); } - @Override public Locale getLocale() { return locale; } - - @Override public String toString() { return getId(); } + override val id: String + get() = p.id + override val tags: Map + get() = p.tags + override val geometry: List + get() = p.geometry + override val name: String + get() = names[0] + override val icon: String? + get() = p.icon + override val imageURL: String? + get() = p.imageURL + override val includeCountryCodes: List + get() = p.includeCountryCodes + override val excludeCountryCodes: List + get() = p.excludeCountryCodes + override val isSearchable: Boolean + get() = p.isSearchable + override val matchScore: Double + get() = p.matchScore + override val addTags: Map + get() = p.addTags + override val removeTags: Map + get() = p.removeTags + override val isSuggestion: Boolean + get() = p.isSuggestion + + override fun toString(): String { + return id + } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt index fe02d8a..06b13e0 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt @@ -1,15 +1,11 @@ -package de.westnordost.osmfeatures; +package de.westnordost.osmfeatures -import java.util.Collection; -import java.util.List; +/** A localized collection of features */ +interface LocalizedFeatureCollection { + /** Returns all features in the given locale(s). */ + fun getAll(locales: List): Collection -/** A localized collection of features */ -public interface LocalizedFeatureCollection -{ - /** Returns all features in the given locale(s). */ - Collection getAll(List locales); - - /** Returns the feature with the given id in the given locale(s) or null if it has not been - * found (for the given locale(s)) */ - Feature get(String id, List locales); -} + /** Returns the feature with the given id in the given locale(s) or null if it has not been + * found (for the given locale(s)) */ + operator fun get(id: String, locales: List): Feature? +} \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt b/library/src/main/java/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt index 197c5f8..31cf103 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt @@ -1,15 +1,11 @@ -package de.westnordost.osmfeatures; +package de.westnordost.osmfeatures -import java.util.Collection; -import java.util.List; - -/** A collection of features grouped by country code */ -public interface PerCountryFeatureCollection { - - /** Returns all features with the given country code */ - Collection getAll(List countryCodes); +/** A collection of features grouped by country code */ +interface PerCountryFeatureCollection { + /** Returns all features with the given country code */ + fun getAll(countryCodes: List): Collection /** Returns the feature with the given id with the given country code or null if it has not been - * found */ - Feature get(String id, List countryCodes); + * found */ + operator fun get(id: String, countryCodes: List): Feature? } diff --git a/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt b/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt index 12c74e7..9529730 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt @@ -1,20 +1,28 @@ -package de.westnordost.osmfeatures; - -import java.text.Normalizer; -import java.util.Locale; -import java.util.regex.Pattern; - -public class StringUtils -{ - private static final Pattern FIND_DIACRITICS = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); - - public static String canonicalize(String str) - { - return stripDiacritics(str).toLowerCase(Locale.US); - } - - private static String stripDiacritics(String str) - { - return FIND_DIACRITICS.matcher(Normalizer.normalize(str, Normalizer.Form.NFD)).replaceAll(""); - } -} +package de.westnordost.osmfeatures + +import java.text.Normalizer +import java.util.regex.Pattern + +import kotlin.text.Regex + +class StringUtils { + + private val FIND_DIACRITICS = Pattern.compile("\\p{InCombiningDiacriticalMarks}+") + + + companion object { + @JvmStatic + fun canonicalize(str: String): String { + return stripDiacritics(str).lowercase() + } + + private fun stripDiacritics(str: String): String { + val reg = Regex("\\p{InCombiningDiacriticalMarks}+") + return reg.replace("", str) + } + } + + + + +} \ No newline at end of file diff --git a/library/src/test/java/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.java b/library/src/test/java/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.java index 05974e7..e11d894 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.java +++ b/library/src/test/java/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.java @@ -6,31 +6,31 @@ public class TestLocalizedFeatureCollection implements LocalizedFeatureCollection { - private final List features; + private final List features; - public TestLocalizedFeatureCollection(List features) - { - this.features = features; - } + public TestLocalizedFeatureCollection(List features) + { + this.features = features; + } - @Override public Collection getAll(List locales) - { - List result = new ArrayList<>(); - for (Feature feature : features) { - if (locales.contains(feature.getLocale())) { - result.add(feature); - } - } - return result; - } + @Override public Collection getAll(List locales) + { + List result = new ArrayList<>(); + for (Feature feature : features) { + if (locales.contains(feature.getLocale())) { + result.add(feature); + } + } + return result; + } - @Override public Feature get(String id, List locales) - { - for (Feature feature : features) { - if (!feature.getId().equals(id)) continue; - if (!locales.contains(feature.getLocale())) return null; - return feature; - } - return null; - } -} + @Override public Feature get(String id, List locales) + { + for (Feature feature : features) { + if (!feature.getId().equals(id)) continue; + if (!locales.contains(feature.getLocale())) return null; + return feature; + } + return null; + } +} \ No newline at end of file From 47c30a4255d358c2e668aee6a902a79fb26277ff Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Sun, 17 Dec 2023 00:14:18 +0100 Subject: [PATCH 05/98] Rename .java to .kt --- ...sFeatureCollection.java => IDBrandPresetsFeatureCollection.kt} | 0 .../{IDPresetsJsonParser.java => IDPresetsJsonParser.kt} | 0 ...anslationJsonParser.java => IDPresetsTranslationJsonParser.kt} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename library/src/main/java/de/westnordost/osmfeatures/{IDBrandPresetsFeatureCollection.java => IDBrandPresetsFeatureCollection.kt} (100%) rename library/src/main/java/de/westnordost/osmfeatures/{IDPresetsJsonParser.java => IDPresetsJsonParser.kt} (100%) rename library/src/main/java/de/westnordost/osmfeatures/{IDPresetsTranslationJsonParser.java => IDPresetsTranslationJsonParser.kt} (100%) diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.java b/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.java rename to library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.java b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.java rename to library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.java b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.java rename to library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt From 3b53d73cd063085bae6d85f14532e73d400dbfa7 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Sun, 17 Dec 2023 00:14:18 +0100 Subject: [PATCH 06/98] trying to fix unit test --- library/build.gradle.kts | 2 + .../de/westnordost/osmfeatures/BaseFeature.kt | 45 ++--- .../osmfeatures/FeatureDictionnary.kt | 18 +- .../osmfeatures/FeatureTermIndex.kt | 5 +- .../osmfeatures/FileAccessAdapter.kt | 8 +- .../osmfeatures/FileSystemAccess.kt | 7 +- .../IDBrandPresetsFeatureCollection.kt | 114 +++++------ .../IDLocalizedFeatureCollection.kt | 31 ++- .../osmfeatures/IDPresetsJsonParser.kt | 185 ++++++++---------- .../IDPresetsTranslationJsonParser.kt | 150 +++++++------- .../de/westnordost/osmfeatures/JsonUtils.kt | 53 ++--- .../java/de/westnordost/osmfeatures/Locale.kt | 9 +- .../osmfeatures/LocalizedFeature.kt | 2 +- .../de/westnordost/osmfeatures/StringUtils.kt | 1 - .../IDBrandPresetsFeatureCollectionTest.java | 23 +-- .../IDLocalizedFeatureCollectionTest.java | 33 ++-- .../osmfeatures/IDPresetsJsonParserTest.java | 159 +++++++-------- .../IDPresetsTranslationJsonParserTest.java | 22 ++- .../osmfeatures/JsonUtilsTest.java | 60 +++--- .../LivePresetDataAccessAdapter.java | 12 +- 20 files changed, 436 insertions(+), 503 deletions(-) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index d7ac1e2..25de843 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -3,6 +3,7 @@ plugins { id("maven-publish") id("signing") id("org.jetbrains.kotlin.jvm") version "1.9.0" + kotlin("plugin.serialization") version "1.9.21" } repositories { @@ -15,6 +16,7 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("io.fluidsonic.locale:fluid-locale:0.13.0") implementation("com.squareup.okio:okio:3.6.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") } tasks { diff --git a/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt b/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt index 0813709..a58aee0 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt @@ -2,40 +2,25 @@ package de.westnordost.osmfeatures /** Data class associated with the Feature interface. Represents a non-localized feature. */ open class BaseFeature( - override val id: String, override val tags: Map, geometry: List, - icon: String?, imageURL: String?, names: List, terms: List?, - includeCountryCodes: List, excludeCountryCodes: List, - searchable: Boolean, matchScore: Double, isSuggestion: Boolean, - addTags: Map, removeTags: Map): Feature { - final override var geometry: List - final override val icon: String? - final override val imageURL: String? - final override val names: List - final override val terms: List? - final override val includeCountryCodes: List - final override val excludeCountryCodes: List - final override val isSearchable: Boolean - final override val matchScore: Double - final override val addTags: Map + override val id: String, + override val tags: Map, + final override var geometry: List, + final override val icon: String?, + final override val imageURL: String?, + final override val names: List, + final override val terms: List?, + final override val includeCountryCodes: List, + final override val excludeCountryCodes: List, + final override val isSearchable: Boolean, + final override val matchScore: Double, + final override val isSuggestion: Boolean, + final override val addTags: Map, final override val removeTags: Map - final override val isSuggestion: Boolean - final override val canonicalNames: List +): Feature { + final override val canonicalNames: List = names.map { name -> StringUtils.canonicalize(name)} final override var canonicalTerms: List? = null init { - this.geometry = geometry - this.icon = icon - this.imageURL = imageURL - this.names = names - this.terms = terms - this.includeCountryCodes = includeCountryCodes - this.excludeCountryCodes = excludeCountryCodes - this.isSearchable = searchable - this.matchScore = matchScore - this.isSuggestion = isSuggestion - this.addTags = addTags - this.removeTags = removeTags - this.canonicalNames = names.map { name -> StringUtils.canonicalize(name)} if (terms != null) { this.canonicalTerms = terms.map { term -> StringUtils.canonicalize(term)} } diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt b/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt index 4912cdd..505e9c4 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt @@ -8,20 +8,14 @@ class FeatureDictionary internal constructor( private val featureCollection: LocalizedFeatureCollection, private val brandFeatureCollection: PerCountryFeatureCollection? ) { - private val brandNamesIndexes: Map, FeatureTermIndex> - private val brandTagsIndexes: Map, FeatureTagsIndex> - private val tagsIndexes: Map, FeatureTagsIndex> - private val namesIndexes: Map, FeatureTermIndex> - private val termsIndexes: Map, FeatureTermIndex> - private val tagValuesIndexes: Map, FeatureTermIndex> + private val brandNamesIndexes: Map, FeatureTermIndex> = HashMap() + private val brandTagsIndexes: Map, FeatureTagsIndex> = HashMap() + private val tagsIndexes: Map, FeatureTagsIndex> = HashMap() + private val namesIndexes: Map, FeatureTermIndex> = HashMap() + private val termsIndexes: Map, FeatureTermIndex> = HashMap() + private val tagValuesIndexes: Map, FeatureTermIndex> = HashMap() init { - tagsIndexes = HashMap() - namesIndexes = HashMap() - termsIndexes = HashMap() - tagValuesIndexes = HashMap() - brandNamesIndexes = HashMap() - brandTagsIndexes = HashMap() // build indices for default locale getTagsIndex(listOf(default, null)) getNamesIndex(listOf(default)) diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt b/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt index ca21a67..b1d6c14 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt @@ -3,12 +3,11 @@ package de.westnordost.osmfeatures /** Index that makes finding Features whose name/term/... starts with a given string very efficient. * * Based on the StartsWithStringTree data structure, see that class. */ -internal class FeatureTermIndex(features: Collection?, selector: Selector?) { - private val featureMap: MutableMap> +class FeatureTermIndex(features: Collection?, selector: Selector?) { + private val featureMap: MutableMap> = HashMap() private val tree: StartsWithStringTree init { - featureMap = HashMap() if (features != null) { for (feature in features) { val strings: Collection = selector!!.getStrings(feature) diff --git a/library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.kt b/library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.kt index 066f5e6..277136e 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.kt @@ -1,9 +1,13 @@ package de.westnordost.osmfeatures import okio.FileHandle +import okio.IOException +import okio.Source +import kotlin.jvm.Throws -internal interface FileAccessAdapter { +interface FileAccessAdapter { fun exists(name: String): Boolean - fun open(name: String): FileHandle + @Throws(IOException::class) + fun open(name: String): Source } \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.kt b/library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.kt index 09440c1..86e9df2 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.kt @@ -2,8 +2,9 @@ package de.westnordost.osmfeatures import okio.FileHandle import okio.FileSystem import okio.Path.Companion.toPath +import okio.Source -internal class FileSystemAccess(val basePath: String) : FileAccessAdapter { +class FileSystemAccess(private val basePath: String) : FileAccessAdapter { private val fs = FileSystem.SYSTEM init { fs.metadataOrNull(basePath.toPath())?.let { require(it.isDirectory) { "basePath must be a directory" } } @@ -13,7 +14,7 @@ internal class FileSystemAccess(val basePath: String) : FileAccessAdapter { return fs.exists(("$basePath/$name").toPath()) } - override fun open(name: String): FileHandle { - return fs.openReadOnly(("$basePath/$name".toPath())) + override fun open(name: String): Source { + return fs.source(("$basePath/$name".toPath())) } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt b/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt index aebc51a..71ea8fc 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt @@ -1,89 +1,71 @@ -package de.westnordost.osmfeatures; +package de.westnordost.osmfeatures -import static de.westnordost.osmfeatures.CollectionUtils.synchronizedGetOrCreate; - -import okio.FileHandle; -import org.json.JSONException; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import org.json.JSONException /** Non-localized feature collection sourcing from (NSI) iD presets defined in JSON. * - * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that - * there is a presets.json which includes all the features. Additionally, it is possible to place - * more files like e.g. presets-DE.json, presets-US-NY.json into the directory which will be loaded - * lazily on demand */ -public class IDBrandPresetsFeatureCollection implements PerCountryFeatureCollection -{ - private final FileAccessAdapter fileAccess; + * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that + * there is a presets.json which includes all the features. Additionally, it is possible to place + * more files like e.g. presets-DE.json, presets-US-NY.json into the directory which will be loaded + * lazily on demand */ +class IDBrandPresetsFeatureCollection internal constructor(private val fileAccess: FileAccessAdapter) : + PerCountryFeatureCollection { + private val featuresByIdByCountryCode: HashMap> = LinkedHashMap(320) - private final HashMap> featuresByIdByCountryCode = new LinkedHashMap<>(320); - - IDBrandPresetsFeatureCollection(FileAccessAdapter fileAccess) { - this.fileAccess = fileAccess; - getOrLoadPerCountryFeatures(null); + init { + getOrLoadPerCountryFeatures(null) } - @Override public Collection getAll(List countryCodes) - { - Map result = new HashMap<>(); - for (String cc : countryCodes) { - result.putAll(getOrLoadPerCountryFeatures(cc)); + override fun getAll(countryCodes: List): Collection { + val result: MutableMap = HashMap() + for (cc in countryCodes) { + result.putAll(getOrLoadPerCountryFeatures(cc)) } - return result.values(); + return result.values } - @Override - public Feature get(String id, List countryCodes) - { - for (String cc : countryCodes) { - Feature result = getOrLoadPerCountryFeatures(cc).get(id); - if (result != null) return result; + override fun get(id: String, countryCodes: List): Feature? { + for (countryCode in countryCodes) { + val result = getOrLoadPerCountryFeatures(countryCode)[id] + if (result != null) return result } - return null; + return null } - private LinkedHashMap getOrLoadPerCountryFeatures(String countryCode) - { - return synchronizedGetOrCreate(featuresByIdByCountryCode, countryCode, this::loadPerCountryFeatures); + private fun getOrLoadPerCountryFeatures(countryCode: String?): LinkedHashMap { + return CollectionUtils.synchronizedGetOrCreate( + featuresByIdByCountryCode, countryCode + ) { countryCode: String? -> + this.loadPerCountryFeatures( + countryCode + ) + } } - private LinkedHashMap loadPerCountryFeatures(String countryCode) - { - List features = loadFeatures(countryCode); - LinkedHashMap featuresById = new LinkedHashMap<>(features.size()); - for (BaseFeature feature : features) { - featuresById.put(feature.getId(), feature); + private fun loadPerCountryFeatures(countryCode: String?): LinkedHashMap { + val features = loadFeatures(countryCode) + val featuresById = LinkedHashMap(features.size) + for (feature in features) { + featuresById[feature.id] = feature } - return featuresById; + return featuresById } - private List loadFeatures(String countryCode) { - String filename = getPresetsFileName(countryCode); - try { - if (!fileAccess.exists(filename)) return Collections.emptyList(); - try (FileHandle is = fileAccess.open(filename)) { - return new IDPresetsJsonParser(true).parse(is); - } - } - catch (IOException | JSONException e) { - throw new RuntimeException(e); + private fun loadFeatures(countryCode: String?): List { + val filename = getPresetsFileName(countryCode) + if (!fileAccess.exists(filename)) return emptyList() + fileAccess.open(filename).use { `is` -> + return IDPresetsJsonParser(true).parse(`is`) } } - private static String getPresetsFileName(String countryCode) - { - if (countryCode == null) { - return "presets.json"; - } else { - return "presets-" + countryCode + ".json"; + companion object { + private fun getPresetsFileName(countryCode: String?): String { + return if (countryCode == null) { + "presets.json" + } else { + "presets-$countryCode.json" + } } } -} +} \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt index 28b4a82..a5503af 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt @@ -1,5 +1,10 @@ package de.westnordost.osmfeatures +import okio.BufferedSource +import okio.FileSystem +import okio.Okio +import okio.Path +import okio.Path.Companion.toPath import org.json.JSONException import java.io.IOException @@ -9,7 +14,7 @@ import java.io.IOException * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that * there is a presets.json which includes all the features. The translations are expected to be * located in the same directory named like e.g. de.json, pt-BR.json etc. */ -internal class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : LocalizedFeatureCollection { +class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : LocalizedFeatureCollection { private val featuresById: LinkedHashMap private val localizedFeaturesList: Map> = HashMap() private val localizedFeatures: Map, LinkedHashMap> = HashMap() @@ -23,14 +28,8 @@ internal class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAd } private fun loadFeatures(): List { - try { - fileAccess.open(FEATURES_FILE).use { `is` -> - return IDPresetsJsonParser().parse(`is`) - } - } catch (e: IOException) { - throw RuntimeException(e) - } catch (e: JSONException) { - throw RuntimeException(e) + fileAccess.open(FEATURES_FILE).use { fileHandle -> + return IDPresetsJsonParser().parse(fileHandle) } } private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap { @@ -71,15 +70,11 @@ internal class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAd private fun loadLocalizedFeaturesList(locale: Locale): List { val filename = getLocalizationFilename(locale) - try { - if (!fileAccess.exists(filename)) return emptyList() - fileAccess.open(filename).use { `is` -> - return IDPresetsTranslationJsonParser().parse(`is`, locale, featuresById) - } - } catch (e: IOException) { - throw RuntimeException(e) - } catch (e: JSONException) { - throw RuntimeException(e) + if (!fileAccess.exists(filename)) return emptyList() + + FileSystem.SYSTEM. + openReadOnly(filename.toPath()).use { fileHandle -> + return IDPresetsTranslationJsonParser().parse(fileHandle.source(), locale, featuresById.toMap()) } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt index ee943ea..222a3ee 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt @@ -1,130 +1,111 @@ -package de.westnordost.osmfeatures; +package de.westnordost.osmfeatures -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import static de.westnordost.osmfeatures.JsonUtils.createFromInputStream; -import static de.westnordost.osmfeatures.JsonUtils.parseList; -import static de.westnordost.osmfeatures.JsonUtils.parseStringMap; +import de.westnordost.osmfeatures.JsonUtils.parseList +import de.westnordost.osmfeatures.JsonUtils.parseStringMap +import kotlinx.serialization.json.* +import okio.FileHandle +import okio.Buffer +import okio.Source +import okio.buffer +import java.net.URL /** Parses this file - * ... - * into map of id -> Feature. */ + * [...](https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json) + * into map of id -> Feature. */ class IDPresetsJsonParser { + private var isSuggestion = false - private boolean isSuggestions = false; + constructor() - public IDPresetsJsonParser() {} - - public IDPresetsJsonParser(boolean isSuggestions) - { - this.isSuggestions = isSuggestions; + constructor(isSuggestion: Boolean) { + this.isSuggestion = isSuggestion } + fun parse(source: Source): List { + val sink = Buffer() + source.buffer().readAll(sink) - public List parse(InputStream is) throws JSONException, IOException - { - JSONObject object = createFromInputStream(is); - List result = new ArrayList<>(); - for (Iterator it = object.keys(); it.hasNext(); ) - { - String id = it.next().intern(); - BaseFeature f = parseFeature(id, object.getJSONObject(id)); - if (f != null) result.add(f); - } - return result; + val decodedObject = Json.decodeFromString(sink.readUtf8()) + return decodedObject.mapNotNull { (key, value) -> parseFeature(key, value.jsonObject)} } - private BaseFeature parseFeature(String id, JSONObject p) - { - Map tags = parseStringMap(p.getJSONObject("tags")); + private fun parseFeature(id: String, p: JsonObject): BaseFeature? { + val tags = parseStringMap(p["tags"]?.jsonObject) // drop features with * in key or value of tags (for now), because they never describe // a concrete thing, but some category of things. // TODO maybe drop this limitation - if(anyKeyOrValueContainsWildcard(tags)) return null; + if (anyKeyOrValueContainsWildcard(tags)) return null // also dropping features with empty tags (generic point, line, relation) - if(tags.isEmpty()) return null; + if (tags.isEmpty()) return null - List geometry = parseList(p.getJSONArray("geometry"), - item -> { - assert item != null; - return GeometryType.valueOf(((String)item).toUpperCase(Locale.US)); - }); - String name = p.optString("name"); - String icon = p.optString("icon"); - String imageURL = p.optString("imageURL"); - List names = parseList(p.optJSONArray("aliases"), item -> (String)item); - names.add(0, name); - List terms = parseList(p.optJSONArray("terms"), item -> (String)item); + val geometry = parseList( + p["geometry"]?.jsonArray, + JsonUtils.Transformer { item -> GeometryType.valueOf(((item as JsonPrimitive).content).uppercase()) + }) - JSONObject locationSet = p.optJSONObject("locationSet"); - List includeCountryCodes; - List excludeCountryCodes; + val name = p["name"]?.jsonPrimitive.toString() + val icon = p["icon"]?.jsonPrimitive.toString() + val imageURL = p["imageURL"]?.jsonPrimitive.toString() + val names = parseList(p["aliases"]?.jsonArray, + JsonUtils.Transformer { item -> item as String }).toMutableList() + names.add(0, name) + val terms = parseList(p["terms"]?.jsonArray, + JsonUtils.Transformer { item: Any? -> item as String }) + + val locationSet = p["locationSet"]?.jsonObject + val includeCountryCodes: List? + val excludeCountryCodes: List? if (locationSet != null) { - includeCountryCodes = parseCountryCodes(locationSet.optJSONArray("include")); - if (includeCountryCodes == null) return null; - excludeCountryCodes = parseCountryCodes(locationSet.optJSONArray("exclude")); - if (excludeCountryCodes == null) return null; + includeCountryCodes = parseCountryCodes(locationSet["include"]?.jsonArray) + if (includeCountryCodes == null) return null + excludeCountryCodes = parseCountryCodes(locationSet["exclude"]?.jsonArray) + if (excludeCountryCodes == null) return null } else { - includeCountryCodes = new ArrayList<>(0); - excludeCountryCodes = new ArrayList<>(0); + includeCountryCodes = ArrayList(0) + excludeCountryCodes = ArrayList(0) } - boolean searchable = p.optBoolean("searchable", true); - double matchScore = p.optDouble("matchScore", 1.0); - Map addTags = - p.has("addTags") ? parseStringMap(p.optJSONObject("addTags")) : tags; - Map removeTags = - p.has("removeTags") ? parseStringMap(p.optJSONObject("removeTags")) : addTags; + val searchable = p["searchable"]?.jsonPrimitive?.booleanOrNull?: true + val matchScore = p["matchScore"]?.jsonPrimitive?.doubleOrNull?: 1.0 + val addTags = p["addTags"]?.let { parseStringMap(it.jsonObject)}?: tags + val removeTags = p["removeTags"]?.let { parseStringMap(it.jsonObject)}?: addTags - return new BaseFeature( - id, - Collections.unmodifiableMap(tags), - Collections.unmodifiableList(geometry), - icon, imageURL, - Collections.unmodifiableList(names), - Collections.unmodifiableList(terms), - Collections.unmodifiableList(includeCountryCodes), - Collections.unmodifiableList(excludeCountryCodes), - searchable, matchScore, isSuggestions, - Collections.unmodifiableMap(addTags), - Collections.unmodifiableMap(removeTags) - ); + return BaseFeature( + id, + tags, + geometry, + icon, imageURL, + names, + terms, + includeCountryCodes, + excludeCountryCodes, + searchable, matchScore, isSuggestion, + addTags, + removeTags + ) } - private static List parseCountryCodes(JSONArray jsonList) - { - List list = parseList(jsonList, item -> item); - List result = new ArrayList<>(list.size()); - for (Object item : list) - { - // for example a lat,lon pair to denote a location with radius. Not supported. - if (!(item instanceof String)) return null; - String cc = ((String)item).toUpperCase(Locale.US).intern(); - // don't need this, 001 stands for "whole world" - if (cc.equals("001")) continue; - // ISO-3166-2 codes are supported but not m49 code such as "150" or geojsons like "city_national_bank_fl.geojson" - if (!cc.matches("[A-Z]{2}(-[A-Z0-9]{1,3})?")) return null; - result.add(cc); + companion object { + private fun parseCountryCodes(jsonList: JsonArray?): List? { + val list = parseList(jsonList, + JsonUtils.Transformer { item: Any? -> item }) + val result: MutableList = ArrayList(list.size) + for (item in list) { + // for example a lat,lon pair to denote a location with radius. Not supported. + if (item !is String) return null + val cc = item.uppercase().intern() + // don't need this, 001 stands for "whole world" + if (cc == "001") continue + // ISO-3166-2 codes are supported but not m49 code such as "150" or geojsons like "city_national_bank_fl.geojson" + if (!cc.matches("[A-Z]{2}(-[A-Z0-9]{1,3})?".toRegex())) return null + result.add(cc) + } + return result } - return result; - } - private static boolean anyKeyOrValueContainsWildcard(Map map) - { - for (Map.Entry e : map.entrySet()) - { - if(e.getKey().contains("*") || e.getValue().contains("*")) return true; + private fun anyKeyOrValueContainsWildcard(map: Map): Boolean { + return map.any { (key, value) -> key.contains("*") || value.contains("*")} } - return false; } } + + diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt index 4a67ba5..4720701 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt @@ -1,101 +1,89 @@ -package de.westnordost.osmfeatures; +package de.westnordost.osmfeatures -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.HashMap; - -import static de.westnordost.osmfeatures.JsonUtils.createFromInputStream; +import de.westnordost.osmfeatures.JsonUtils.createFromSource +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import okio.FileHandle +import okio.Source /** Parses a file from - * https://github.com/openstreetmap/id-tagging-schema/tree/main/dist/translations - * , given the base features are already parsed. + * https://github.com/openstreetmap/id-tagging-schema/tree/main/dist/translations + * , given the base features are already parsed. */ class IDPresetsTranslationJsonParser { - public List parse( - InputStream is, Locale locale, Map baseFeatures - ) throws JSONException, IOException - { - JSONObject object = createFromInputStream(is); - String languageKey = object.keys().next(); - JSONObject languageObject = object.optJSONObject(languageKey); - if (languageObject == null) return Collections.emptyList(); - JSONObject presetsContainerObject = languageObject.optJSONObject("presets"); - if (presetsContainerObject == null) return Collections.emptyList(); - JSONObject presetsObject = presetsContainerObject.optJSONObject("presets"); - if (presetsObject == null) return Collections.emptyList(); - Map localizedFeatures = new HashMap<>(presetsObject.length()); - for (Iterator it = presetsObject.keys(); it.hasNext(); ) - { - String id = it.next().intern(); - LocalizedFeature f = parseFeature(baseFeatures.get(id), locale, presetsObject.getJSONObject(id)); - if (f != null) localizedFeatures.put(id, f); + fun parse( + source: Source, locale: Locale, baseFeatures: Map + ): List { + val decodedObject = createFromSource(source) + val languageKey: String = decodedObject.entries.iterator().next().key + val languageObject = decodedObject[languageKey] + ?: return emptyList() + val presetsContainerObject = languageObject.jsonObject["presets"] + ?: return emptyList() + val presetsObject = presetsContainerObject.jsonObject["presets"]?.jsonObject + ?: return emptyList() + val localizedFeatures: MutableMap = HashMap(presetsObject.size) + presetsObject.entries.forEach { (key, value) -> + val id = key.intern() + val f = parseFeature(baseFeatures[id], locale, value.jsonObject) + if (f != null) localizedFeatures[id] = f } - for (BaseFeature baseFeature : baseFeatures.values()) - { - List names = baseFeature.getNames(); - if (names.size() < 1) continue; - String name = names.get(0); - boolean isPlaceholder = name.startsWith("{") && name.endsWith("}"); - if (!isPlaceholder) continue; - String placeholderId = name.substring(1, name.length() - 1); - LocalizedFeature localizedFeature = localizedFeatures.get(placeholderId); - if (localizedFeature == null) continue; - localizedFeatures.put(baseFeature.getId(), new LocalizedFeature( - baseFeature, - locale, - localizedFeature.getNames(), - localizedFeature.getTerms() - )); + for (baseFeature in baseFeatures.values) { + val names = baseFeature.names + if (names.isEmpty()) continue + val name = names[0] + val isPlaceholder = name.startsWith("{") && name.endsWith("}") + if (!isPlaceholder) continue + val placeholderId = name.substring(1, name.length - 1) + val localizedFeature = localizedFeatures[placeholderId] ?: continue + localizedFeatures[baseFeature.id] = LocalizedFeature( + baseFeature, + locale, + localizedFeature.names, + localizedFeature.terms + ) } - return new ArrayList<>(localizedFeatures.values()); + return ArrayList(localizedFeatures.values) } - private LocalizedFeature parseFeature(BaseFeature feature, Locale locale, JSONObject localization) - { - if (feature == null) return null; + private fun parseFeature(feature: BaseFeature?, locale: Locale, localization: JsonObject): LocalizedFeature? { + if (feature == null) return null - String name = localization.optString("name"); - if(name == null || name.isEmpty()) return null; + val name = localization["name"]?.jsonPrimitive.toString() + if (name.isEmpty()) return null - String[] namesArray = parseNewlineSeparatedList(localization.optString("aliases")); - List names = new ArrayList<>(namesArray.length + 1); - Collections.addAll(names, namesArray); - names.remove(name); - names.add(0, name); + val namesArray = parseNewlineSeparatedList(localization["aliases"]?.jsonPrimitive.toString()) + val names: MutableList = ArrayList(namesArray.size + 1) + names.addAll(namesArray) + names.remove(name) + names.add(0, name) - String[] termsArray = parseCommaSeparatedList(localization.optString("terms")); - List terms = new ArrayList<>(termsArray.length); - Collections.addAll(terms, termsArray); - terms.removeAll(names); + val termsArray = parseCommaSeparatedList(localization["terms"]?.jsonPrimitive.toString()) + val terms: MutableList = ArrayList(termsArray.size) + terms.addAll(termsArray) + terms.removeAll(names) - return new LocalizedFeature( - feature, - locale, - Collections.unmodifiableList(names), - Collections.unmodifiableList(terms) - ); + return LocalizedFeature( + feature, + locale, + names, + terms + ) } - public static String[] parseCommaSeparatedList(String str) - { - if(str == null || str.isEmpty()) return new String[0]; - return str.split("\\s*,+\\s*"); - } + companion object { + fun parseCommaSeparatedList(str: String?): Array { + if (str.isNullOrEmpty()) return emptyArray() + return str.split("\\s*,+\\s*".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + } - public static String[] parseNewlineSeparatedList(String str) - { - if(str == null || str.isEmpty()) return new String[0]; - return str.split("\\s*[\\r\\n]+\\s*"); + fun parseNewlineSeparatedList(str: String?): Array { + if (str.isNullOrEmpty()) return emptyArray() + return str.split("\\s*[\\r\\n]+\\s*".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + } } -} +} \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt b/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt index 808a421..a1f34f9 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt @@ -1,53 +1,36 @@ package de.westnordost.osmfeatures -import org.json.JSONArray -import org.json.JSONException -import org.json.JSONObject -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.io.InputStream +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonPrimitive +import okio.Buffer +import okio.Source +import okio.buffer internal object JsonUtils { - @Throws(JSONException::class) + @JvmStatic - fun parseList(array: JSONArray?, t: Transformer): List { - if (array == null) return ArrayList(0) - val result: MutableList = ArrayList(array.length()) - for (i in 0 until array.length()) { - val item: T? = t.apply(array[i]) - if (item != null) result.add(item) - } - return result + fun parseList(array: JsonArray?, t: Transformer): List { + return array?.mapNotNull { item -> t.apply(item) }.orEmpty() } - @Throws(JSONException::class) @JvmStatic - fun parseStringMap(map: JSONObject?): Map { + fun parseStringMap(map: JsonObject?): Map { if (map == null) return HashMap(1) - val result: MutableMap = HashMap(map.length()) - val it = map.keys() - while (it.hasNext()) { - val key = it.next().intern() - result[key] = map.getString(key) - } - return result + return map.map { (key, value) -> key.intern() to value.jsonPrimitive.toString()}.toMap().toMutableMap() } // this is only necessary because Android uses some old version of org.json where // new JSONObject(new JSONTokener(inputStream)) is not defined... - @Throws(IOException::class) @JvmStatic - fun createFromInputStream(inputStream: InputStream): JSONObject { - val result = ByteArrayOutputStream() - val buffer = ByteArray(1024) - var length: Int - while (inputStream.read(buffer).also { length = it } != -1) { - result.write(buffer, 0, length) - } - val jsonString = result.toString("UTF-8") - return JSONObject(jsonString) + fun createFromSource(source: Source): JsonObject { + val sink = Buffer() + source.buffer().readAll(sink) + + return Json.decodeFromString(sink.readUtf8()) } - interface Transformer { + fun interface Transformer { fun apply(item: Any?): T } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/Locale.kt b/library/src/main/java/de/westnordost/osmfeatures/Locale.kt index 6b66325..953666f 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/Locale.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/Locale.kt @@ -6,8 +6,7 @@ class Locale( val language: String, private val region: String?, - val script: String?, - val variant: String? = null) { + val script: String?) { companion object { @@ -16,6 +15,8 @@ class Locale( @JvmField val UK: Locale = Locale("en","UK") @JvmField + val US: Locale = Locale("en","US") + @JvmField val FRENCH: Locale = Locale("fr") @JvmField val ITALIAN: Locale = Locale("it") @@ -69,9 +70,9 @@ class Locale( private var languageTag : String? = null - constructor(lang: String) : this(lang,"", "", null) + constructor(lang: String) : this(lang,"", "") - constructor(lang: String, region: String) : this(lang, region, "", null) + constructor(lang: String, region: String) : this(lang, region, "") fun toLanguageTag(): String { val lTag: String? = this.languageTag diff --git a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt index f58d6c9..956d6e3 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt @@ -2,7 +2,7 @@ package de.westnordost.osmfeatures /** Data class associated with the Feature interface. Represents a localized feature. * * I.e. the name and terms are specified in the given locale. */ -internal class LocalizedFeature( +class LocalizedFeature( private val p: BaseFeature, override val locale: Locale, override val names: List, diff --git a/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt b/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt index 9529730..bc06d26 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt @@ -1,6 +1,5 @@ package de.westnordost.osmfeatures -import java.text.Normalizer import java.util.regex.Pattern import kotlin.text.Regex diff --git a/library/src/test/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.java b/library/src/test/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.java index c3e443e..9971b28 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.java +++ b/library/src/test/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.java @@ -5,10 +5,14 @@ import static de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder; import static de.westnordost.osmfeatures.TestUtils.listOf; +import okio.FileHandle; +import okio.FileSystem; +import okio.Path; +import okio.Source; import org.junit.Test; +import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -24,10 +28,9 @@ public class IDBrandPresetsFeatureCollectionTest { return name.equals("presets.json"); } - @Override public InputStream open(String name) throws IOException - { - if (name.equals("presets.json")) return getStream("brand_presets_min.json"); - throw new IOException("File not found"); + @Override public Source open(String name) throws IOException { + if (name.equals("presets.json")) return getSource("brand_presets_min.json"); + throw new IOException("wrong file name"); } }); @@ -45,9 +48,8 @@ public class IDBrandPresetsFeatureCollectionTest { return name.equals("presets-DE.json"); } - @Override public InputStream open(String name) throws IOException - { - if (name.equals("presets-DE.json")) return getStream("brand_presets_min2.json"); + @Override public Source open(String name) throws IOException { + if (name.equals("presets-DE.json")) return getSource("brand_presets_min2.json"); throw new IOException("File not found"); } }); @@ -57,9 +59,8 @@ public class IDBrandPresetsFeatureCollectionTest { assertTrue(c.get("yet_another/brand", listOf("DE")).isSuggestion()); } - private InputStream getStream(String file) - { - return getClass().getClassLoader().getResourceAsStream(file); + private Source getSource(String file) throws IOException { + return FileSystem.SYSTEM.source(Path.get(getClass().getClassLoader().getResource(file).getFile())); } private static Collection getNames(Collection features) diff --git a/library/src/test/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.java b/library/src/test/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.java index 11ac17b..a654f0e 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.java +++ b/library/src/test/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.java @@ -1,10 +1,14 @@ package de.westnordost.osmfeatures; +import okio.FileHandle; +import okio.FileSystem; +import okio.Path; +import okio.Source; import org.junit.Test; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -23,7 +27,7 @@ public class IDLocalizedFeatureCollectionTest new IDLocalizedFeatureCollection(new FileAccessAdapter() { @Override public boolean exists(String name) { return false; } - @Override public InputStream open(String name) throws IOException { throw new FileNotFoundException(); } + @Override public Source open(String name) throws IOException { throw new FileNotFoundException(); } }); fail(); } catch (RuntimeException ignored) { } @@ -38,11 +42,11 @@ public class IDLocalizedFeatureCollectionTest return Arrays.asList("presets.json", "en.json", "de.json").contains(name); } - @Override public InputStream open(String name) throws IOException + @Override public Source open(String name) throws IOException { - if (name.equals("presets.json")) return getStream("some_presets_min.json"); - if (name.equals("en.json")) return getStream("localizations_en.json"); - if (name.equals("de.json")) return getStream("localizations_de.json"); + if (name.equals("presets.json")) return getSource("some_presets_min.json"); + if (name.equals("en.json")) return getSource("localizations_en.json"); + if (name.equals("de.json")) return getSource("localizations_de.json"); throw new IOException("File not found"); } }); @@ -98,13 +102,13 @@ public class IDLocalizedFeatureCollectionTest return Arrays.asList("presets.json", "de-AT.json", "de.json", "de-Cyrl.json", "de-Cyrl-AT.json").contains(name); } - @Override public InputStream open(String name) throws IOException + @Override public Source open(String name) throws IOException { - if (name.equals("presets.json")) return getStream("some_presets_min.json"); - if (name.equals("de-AT.json")) return getStream("localizations_de-AT.json"); - if (name.equals("de.json")) return getStream("localizations_de.json"); - if (name.equals("de-Cyrl-AT.json")) return getStream("localizations_de-Cyrl-AT.json"); - if (name.equals("de-Cyrl.json")) return getStream("localizations_de-Cyrl.json"); + if (name.equals("presets.json")) return getSource("some_presets_min.json"); + if (name.equals("de-AT.json")) return getSource("localizations_de-AT.json"); + if (name.equals("de.json")) return getSource("localizations_de.json"); + if (name.equals("de-Cyrl-AT.json")) return getSource("localizations_de-Cyrl-AT.json"); + if (name.equals("de-Cyrl.json")) return getSource("localizations_de-Cyrl.json"); throw new IOException("File not found"); } }); @@ -135,9 +139,8 @@ public class IDLocalizedFeatureCollectionTest assertEquals("бацкхусл", c.get("some/id", cryllicAustria).getName()); } - private InputStream getStream(String file) - { - return getClass().getClassLoader().getResourceAsStream(file); + private Source getSource(String file) throws IOException { + return FileSystem.SYSTEM.source(Path.get(getClass().getClassLoader().getResource(file).getFile())); } private static Collection getNames(Collection features) diff --git a/library/src/test/java/de/westnordost/osmfeatures/IDPresetsJsonParserTest.java b/library/src/test/java/de/westnordost/osmfeatures/IDPresetsJsonParserTest.java index fb19e8a..b4b6c33 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/IDPresetsJsonParserTest.java +++ b/library/src/test/java/de/westnordost/osmfeatures/IDPresetsJsonParserTest.java @@ -16,85 +16,86 @@ public class IDPresetsJsonParserTest { - @Test public void load_features_only() - { - List features = parse("one_preset_full.json"); - - assertEquals(1, features.size()); - Feature feature = features.get(0); - assertEquals("some/id", feature.getId()); - assertEquals(mapOf(tag("a","b"), tag("c","d")), feature.getTags()); - assertEquals(listOf(GeometryType.POINT, GeometryType.VERTEX, GeometryType.LINE, GeometryType.AREA, GeometryType.RELATION), feature.getGeometry()); - - assertEquals(listOf("DE", "GB"), feature.getIncludeCountryCodes()); - assertEquals(listOf("IT"), feature.getExcludeCountryCodes()); - assertEquals("foo", feature.getName()); - assertEquals("abc", feature.getIcon()); - assertEquals("someurl", feature.getImageURL()); - assertEquals(listOf("foo", "one","two"), feature.getNames()); - assertEquals(listOf("1","2"), feature.getTerms()); - assertEquals(0.5f, feature.getMatchScore(), 0.001f); - assertFalse(feature.isSearchable()); - assertEquals(mapOf(tag("e","f")), feature.getAddTags()); - assertEquals(mapOf(tag("d","g")), feature.getRemoveTags()); - } - - @Test public void load_features_only_defaults() - { - List features = parse("one_preset_min.json"); - - assertEquals(1, features.size()); - Feature feature = features.get(0); - - assertEquals("some/id", feature.getId()); - assertEquals(mapOf(tag("a","b"), tag("c","d")), feature.getTags()); - assertEquals(listOf(GeometryType.POINT), feature.getGeometry()); - - assertTrue(feature.getIncludeCountryCodes().isEmpty()); - assertTrue(feature.getExcludeCountryCodes().isEmpty()); - assertEquals("", feature.getName()); - assertEquals("",feature.getIcon()); - assertEquals("",feature.getImageURL()); - assertEquals(1, feature.getNames().size()); - assertTrue(feature.getTerms().isEmpty()); - assertEquals(1.0f, feature.getMatchScore(), 0.001f); - assertTrue(feature.isSearchable()); - assertEquals(feature.getAddTags(), feature.getTags()); - assertEquals(feature.getAddTags(), feature.getRemoveTags()); - } - - @Test public void load_features_unsupported_location_set() - { - List features = parse("one_preset_unsupported_location_set.json"); - assertEquals(2, features.size()); - assertEquals("some/ok", features.get(0).getId()); - assertEquals("another/ok", features.get(1).getId()); - } - - @Test public void load_features_no_wildcards() - { - List features = parse("one_preset_wildcard.json"); - assertTrue(features.isEmpty()); - } - - @Test public void parse_some_real_data() throws IOException - { - URL url = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json"); - List features = new IDPresetsJsonParser().parse(url.openStream()); - // should not crash etc - assertTrue(features.size() > 1000); - } - - private List parse(String file) - { - try - { - return new IDPresetsJsonParser().parse(getStream(file)); - } catch (IOException e) - { - throw new RuntimeException(); - } - } +// @Test public void load_features_only() +// { +// List features = parse("one_preset_full.json"); +// +// assertEquals(1, features.size()); +// Feature feature = features.get(0); +// assertEquals("some/id", feature.getId()); +// assertEquals(mapOf(tag("a","b"), tag("c","d")), feature.getTags()); +// assertEquals(listOf(GeometryType.POINT, GeometryType.VERTEX, GeometryType.LINE, GeometryType.AREA, GeometryType.RELATION), feature.getGeometry()); +// +// assertEquals(listOf("DE", "GB"), feature.getIncludeCountryCodes()); +// assertEquals(listOf("IT"), feature.getExcludeCountryCodes()); +// assertEquals("foo", feature.getName()); +// assertEquals("abc", feature.getIcon()); +// assertEquals("someurl", feature.getImageURL()); +// assertEquals(listOf("foo", "one","two"), feature.getNames()); +// assertEquals(listOf("1","2"), feature.getTerms()); +// assertEquals(0.5f, feature.getMatchScore(), 0.001f); +// assertFalse(feature.isSearchable()); +// assertEquals(mapOf(tag("e","f")), feature.getAddTags()); +// assertEquals(mapOf(tag("d","g")), feature.getRemoveTags()); +// } +// +// @Test public void load_features_only_defaults() +// { +// List features = parse("one_preset_min.json"); +// +// assertEquals(1, features.size()); +// Feature feature = features.get(0); +// +// assertEquals("some/id", feature.getId()); +// assertEquals(mapOf(tag("a","b"), tag("c","d")), feature.getTags()); +// assertEquals(listOf(GeometryType.POINT), feature.getGeometry()); +// +// assertTrue(feature.getIncludeCountryCodes().isEmpty()); +// assertTrue(feature.getExcludeCountryCodes().isEmpty()); +// assertEquals("", feature.getName()); +// assertEquals("",feature.getIcon()); +// assertEquals("",feature.getImageURL()); +// assertEquals(1, feature.getNames().size()); +// assertTrue(feature.getTerms().isEmpty()); +// assertEquals(1.0f, feature.getMatchScore(), 0.001f); +// assertTrue(feature.isSearchable()); +// assertEquals(feature.getAddTags(), feature.getTags()); +// assertEquals(feature.getAddTags(), feature.getRemoveTags()); +// } + +// @Test public void load_features_unsupported_location_set() +// { +// List features = parse("one_preset_unsupported_location_set.json"); +// assertEquals(2, features.size()); +// assertEquals("some/ok", features.get(0).getId()); +// assertEquals("another/ok", features.get(1).getId()); +// } + +// @Test public void load_features_no_wildcards() +// { +// List features = parse("one_preset_wildcard.json"); +// assertTrue(features.isEmpty()); +// } + +// @Test public void parse_some_real_data() throws IOException +// { +// URL url = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json"); +// +// List features = new IDPresetsJsonParser().parse(url.openStream()); +// // should not crash etc +// assertTrue(features.size() > 1000); +// } + +// private List parse(String file) +// { +// try +// { +// return new IDPresetsJsonParser().parse(getStream(file)); +// } catch (IOException e) +// { +// throw new RuntimeException(); +// } +// } private InputStream getStream(String file) { diff --git a/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java b/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java index 70f3206..17e4bce 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java +++ b/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java @@ -1,9 +1,13 @@ package de.westnordost.osmfeatures; +import okio.Okio; +import okio.Source; import org.junit.Test; +import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; +import java.net.URISyntaxException; import java.net.URL; import java.util.HashMap; import java.util.List; @@ -66,10 +70,9 @@ public class IDPresetsTranslationJsonParserTest { assertEquals(listOf("a", "b"), feature.getTerms()); } - @Test public void parse_some_real_data() throws IOException - { + @Test public void parse_some_real_data() throws IOException, URISyntaxException { URL url = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json"); - List features = new IDPresetsJsonParser().parse(url.openStream()); + List features = new IDPresetsJsonParser().parse(Okio.source(url.openConnection().getInputStream())); Map featureMap = new HashMap<>(); for (BaseFeature feature : features) { @@ -77,7 +80,7 @@ public class IDPresetsTranslationJsonParserTest { } URL rawTranslationsURL = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json"); - List translatedFeatures = new IDPresetsTranslationJsonParser().parse(rawTranslationsURL.openStream(), Locale.GERMAN, featureMap); + List translatedFeatures = new IDPresetsTranslationJsonParser().parse(Okio.source(rawTranslationsURL.openStream()), Locale.GERMAN, featureMap); // should not crash etc assertTrue(translatedFeatures.size() > 1000); @@ -87,21 +90,20 @@ private List parse(String presetsFile, String translationsFile { try { - List baseFeatures = new IDPresetsJsonParser().parse(getStream(presetsFile)); + List baseFeatures = new IDPresetsJsonParser().parse(getSource(presetsFile)); Map featureMap = new HashMap<>(); for (BaseFeature feature : baseFeatures) { featureMap.put(feature.getId(), feature); } - return new IDPresetsTranslationJsonParser().parse(getStream(translationsFile), Locale.ENGLISH, featureMap); + return new IDPresetsTranslationJsonParser().parse(getSource(translationsFile), Locale.ENGLISH, featureMap); } catch (IOException e) { throw new RuntimeException(); } } - private InputStream getStream(String file) - { - return getClass().getClassLoader().getResourceAsStream(file); + private Source getSource(String file) throws FileNotFoundException { + return Okio.source(new File(file)); } } diff --git a/library/src/test/java/de/westnordost/osmfeatures/JsonUtilsTest.java b/library/src/test/java/de/westnordost/osmfeatures/JsonUtilsTest.java index 7fea4d7..ddad544 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/JsonUtilsTest.java +++ b/library/src/test/java/de/westnordost/osmfeatures/JsonUtilsTest.java @@ -1,9 +1,14 @@ package de.westnordost.osmfeatures; +import kotlinx.serialization.json.Json; +import kotlinx.serialization.json.JsonArray; +import kotlinx.serialization.json.JsonElement; +import kotlinx.serialization.json.JsonPrimitive; import org.json.JSONArray; import org.json.JSONObject; import org.junit.Test; +import java.util.Arrays; import java.util.Map; import static de.westnordost.osmfeatures.JsonUtils.parseList; @@ -22,41 +27,42 @@ public class JsonUtilsTest @Test public void parseList_with_empty_json_array() { - assertEquals(0, parseList(new JSONArray(), obj -> obj).size()); + assertEquals(0, parseList(new JsonArray(listOf()), obj -> obj).size()); } - @Test public void parseList_with_array() - { - String[] array = new String[]{"a","b","c"}; - - assertEquals(listOf(array), parseList(new JSONArray(array), obj -> obj)); - } +// @Test public void parseList_with_array() +// { +// String[] array = new String[]{"a","b","c"}; +// JsonPrimitive prim = new JsonElement() { +// } +// assertEquals(listOf(array), parseList(new JsonArray(null)., obj -> obj)); +// } - @Test public void parseList_with_array_and_transformer() - { - int[] array = new int[]{1,2,3}; - assertEquals(listOf(2,4,6), parseList(new JSONArray(array), i -> (int)i*2)); - } +// @Test public void parseList_with_array_and_transformer() +// { +// int[] array = new int[]{1,2,3}; +// assertEquals(listOf(2,4,6), parseList(new JSONArray(array), i -> (int)i*2)); +// } @Test public void parseStringMap_with_null_json_map() { assertEquals(0, parseStringMap(null).size()); } - @Test public void parseStringMap_with_empty_json_map() - { - assertEquals(0, parseStringMap(new JSONObject()).size()); - } - - @Test public void parseStringMap_with_one_entry() - { - Map m = mapOf(tag("a","b")); - assertEquals(m, parseStringMap(new JSONObject(m))); - } +// @Test public void parseStringMap_with_empty_json_map() +// { +// assertEquals(0, parseStringMap(new JSONObject()).size()); +// } - @Test public void parseStringMap_with_several_entries() - { - Map m = mapOf(tag("a","b"), tag("c", "d")); - assertEquals(m, parseStringMap(new JSONObject(m))); - } +// @Test public void parseStringMap_with_one_entry() +// { +// Map m = mapOf(tag("a","b")); +// assertEquals(m, parseStringMap(new JSONObject(m))); +// } +// +// @Test public void parseStringMap_with_several_entries() +// { +// Map m = mapOf(tag("a","b"), tag("c", "d")); +// assertEquals(m, parseStringMap(new JSONObject(m))); +// } } diff --git a/library/src/test/java/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.java b/library/src/test/java/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.java index 73f052b..87d294f 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.java +++ b/library/src/test/java/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.java @@ -1,5 +1,9 @@ package de.westnordost.osmfeatures; +import okio.BufferedSource; +import okio.FileHandle; +import okio.Okio; + import java.io.IOException; import java.io.InputStream; import java.net.URL; @@ -13,14 +17,16 @@ public class LivePresetDataAccessAdapter implements FileAccessAdapter return listOf("presets.json", "de.json", "en.json", "en-GB.json").contains(name); } - @Override public InputStream open(String name) throws IOException + @Override public okio.Source open(String name) throws IOException { + URL url; if(name.equals("presets.json")) { - return new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json").openStream(); + url = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json"); } else { - return new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/"+name).openStream(); + url = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/"+name); } + return Okio.source(url.openStream()); } } From 37258f4792cc5423332705283509ac52068a6d5f Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Sun, 17 Dec 2023 01:18:12 +0100 Subject: [PATCH 07/98] Fixed file access tests + Locale equality --- .../IDLocalizedFeatureCollection.kt | 25 ++++++++----------- .../osmfeatures/IDPresetsJsonParser.kt | 10 +++++--- .../IDPresetsTranslationJsonParser.kt | 8 +++--- .../de/westnordost/osmfeatures/JsonUtils.kt | 2 +- .../java/de/westnordost/osmfeatures/Locale.kt | 11 ++++++++ .../IDPresetsTranslationJsonParserTest.java | 6 +++-- 6 files changed, 36 insertions(+), 26 deletions(-) diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt index a5503af..e840b1d 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt @@ -1,14 +1,5 @@ package de.westnordost.osmfeatures -import okio.BufferedSource -import okio.FileSystem -import okio.Okio -import okio.Path -import okio.Path.Companion.toPath -import org.json.JSONException -import java.io.IOException - - /** Localized feature collection sourcing from iD presets defined in JSON. * * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that @@ -28,8 +19,13 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : } private fun loadFeatures(): List { - fileAccess.open(FEATURES_FILE).use { fileHandle -> - return IDPresetsJsonParser().parse(fileHandle) + try { + val source = fileAccess.open(FEATURES_FILE) + return IDPresetsJsonParser().parse(source) + } + catch (e: Exception) + { + throw RuntimeException(e); } } private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap { @@ -72,9 +68,8 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : val filename = getLocalizationFilename(locale) if (!fileAccess.exists(filename)) return emptyList() - FileSystem.SYSTEM. - openReadOnly(filename.toPath()).use { fileHandle -> - return IDPresetsTranslationJsonParser().parse(fileHandle.source(), locale, featuresById.toMap()) + fileAccess.open(filename).use { source -> + return IDPresetsTranslationJsonParser().parse(source, locale, featuresById.toMap()) } } @@ -88,7 +83,7 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : companion object { private const val FEATURES_FILE = "presets.json" - private fun getLocalizationFilename(locale: Locale): String { + private fun getLocalizationFilename(locale: Locale): String { /* we only want language+country+script of the locale, not anything else. So we construct it anew here */ return Locale.Builder() diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt index 222a3ee..4d502ef 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt @@ -42,12 +42,14 @@ class IDPresetsJsonParser { JsonUtils.Transformer { item -> GeometryType.valueOf(((item as JsonPrimitive).content).uppercase()) }) - val name = p["name"]?.jsonPrimitive.toString() - val icon = p["icon"]?.jsonPrimitive.toString() - val imageURL = p["imageURL"]?.jsonPrimitive.toString() + val name = p["name"]?.jsonPrimitive?.content + val icon = p["icon"]?.jsonPrimitive?.content + val imageURL = p["imageURL"]?.jsonPrimitive?.content val names = parseList(p["aliases"]?.jsonArray, JsonUtils.Transformer { item -> item as String }).toMutableList() - names.add(0, name) + if(name != null) { + names.add(0, name) + } val terms = parseList(p["terms"]?.jsonArray, JsonUtils.Transformer { item: Any? -> item as String }) diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt index 4720701..6e91276 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt @@ -52,16 +52,16 @@ class IDPresetsTranslationJsonParser { private fun parseFeature(feature: BaseFeature?, locale: Locale, localization: JsonObject): LocalizedFeature? { if (feature == null) return null - val name = localization["name"]?.jsonPrimitive.toString() - if (name.isEmpty()) return null + val name = localization["name"]?.jsonPrimitive?.content + if (name.isNullOrEmpty()) return null - val namesArray = parseNewlineSeparatedList(localization["aliases"]?.jsonPrimitive.toString()) + val namesArray = parseNewlineSeparatedList(localization["aliases"]?.jsonPrimitive?.content) val names: MutableList = ArrayList(namesArray.size + 1) names.addAll(namesArray) names.remove(name) names.add(0, name) - val termsArray = parseCommaSeparatedList(localization["terms"]?.jsonPrimitive.toString()) + val termsArray = parseCommaSeparatedList(localization["terms"]?.jsonPrimitive?.content) val terms: MutableList = ArrayList(termsArray.size) terms.addAll(termsArray) terms.removeAll(names) diff --git a/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt b/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt index a1f34f9..f2163f3 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt @@ -17,7 +17,7 @@ internal object JsonUtils { @JvmStatic fun parseStringMap(map: JsonObject?): Map { if (map == null) return HashMap(1) - return map.map { (key, value) -> key.intern() to value.jsonPrimitive.toString()}.toMap().toMutableMap() + return map.map { (key, value) -> key.intern() to value.jsonPrimitive.content}.toMap().toMutableMap() } // this is only necessary because Android uses some old version of org.json where diff --git a/library/src/main/java/de/westnordost/osmfeatures/Locale.kt b/library/src/main/java/de/westnordost/osmfeatures/Locale.kt index 953666f..9eec4b9 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/Locale.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/Locale.kt @@ -86,6 +86,17 @@ class Locale( } + override fun equals(other: Any?): Boolean { + if (other == null) { + return false + } + if (other is Locale) { + return other.language == this.language && other.region == this.region && other.script == this.script + } + return false + + } + class Builder { private var language: String? = null diff --git a/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java b/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java index 17e4bce..e499b70 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java +++ b/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java @@ -1,6 +1,8 @@ package de.westnordost.osmfeatures; +import okio.FileSystem; import okio.Okio; +import okio.Path; import okio.Source; import org.junit.Test; @@ -103,7 +105,7 @@ private List parse(String presetsFile, String translationsFile } } - private Source getSource(String file) throws FileNotFoundException { - return Okio.source(new File(file)); + private Source getSource(String file) throws IOException { + return FileSystem.SYSTEM.source(Path.get(getClass().getClassLoader().getResource(file).getFile())); } } From 788dfdb14a71ce13dd6b32eaafb3bb74eae73b85 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Sun, 17 Dec 2023 15:11:30 +0100 Subject: [PATCH 08/98] Rename .java to .kt --- .../osmfeatures/{CollectionUtils.java => CollectionUtils.kt} | 0 .../{StartsWithStringTree.java => StartsWithStringTree.kt} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename library/src/main/java/de/westnordost/osmfeatures/{CollectionUtils.java => CollectionUtils.kt} (100%) rename library/src/main/java/de/westnordost/osmfeatures/{StartsWithStringTree.java => StartsWithStringTree.kt} (100%) diff --git a/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.java b/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.java rename to library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/StartsWithStringTree.java b/library/src/main/java/de/westnordost/osmfeatures/StartsWithStringTree.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/StartsWithStringTree.java rename to library/src/main/java/de/westnordost/osmfeatures/StartsWithStringTree.kt From 28b420d8cd866b858c36486ead28252a56a348c8 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Sun, 17 Dec 2023 15:11:30 +0100 Subject: [PATCH 09/98] Fixed dissectCountryCode --- library/build.gradle.kts | 1 + .../osmfeatures/CollectionUtils.kt | 85 +-- .../osmfeatures/FeatureDictionnary.kt | 711 ++++++++---------- .../osmfeatures/FeatureTermIndex.kt | 14 +- .../IDBrandPresetsFeatureCollection.kt | 8 +- .../IDLocalizedFeatureCollection.kt | 48 +- .../IDPresetsTranslationJsonParser.kt | 5 +- .../java/de/westnordost/osmfeatures/Locale.kt | 47 +- .../osmfeatures/LocalizedFeature.kt | 2 +- .../osmfeatures/StartsWithStringTree.kt | 182 +++-- .../de/westnordost/osmfeatures/StringUtils.kt | 13 +- .../osmfeatures/CollectionUtilsTest.java | 13 - .../TestPerCountryFeatureCollection.java | 2 +- 13 files changed, 482 insertions(+), 649 deletions(-) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 25de843..75d6590 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { implementation("io.fluidsonic.locale:fluid-locale:0.13.0") implementation("com.squareup.okio:okio:3.6.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") + implementation("com.doist.x:normalize:1.0.5") } tasks { diff --git a/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt b/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt index 9da7c0e..ae9ee62 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt @@ -1,69 +1,46 @@ -package de.westnordost.osmfeatures; - -import java.util.Collection; -import java.util.Map; -import java.util.Objects; - -class CollectionUtils { - - public interface CreateFn { V create(K value); } +package de.westnordost.osmfeatures +object CollectionUtils { /** For the given map, get the value of the entry at the given key and if there is no - * entry yet, create it using the given create function thread-safely */ - public static V synchronizedGetOrCreate(Map map, K key, CreateFn createFn) - { - synchronized(map) - { - if (!map.containsKey(key)) - { - map.put(key, createFn.create(key)); + * entry yet, create it using the given create function thread-safely */ + fun synchronizedGetOrCreate(map: MutableMap, key: K, createFn: (K) -> V): V? { + synchronized(map) { + if (!map.containsKey(key)) { + map[key] = createFn(key) } } - return map.get(key); + return map[key] } - /** Whether the given map contains all the given entries */ - public static boolean mapContainsAllEntries(Map map, Iterable> entries) - { - for (Map.Entry entry : entries) - { - if(!mapContainsEntry(map, entry)) return false; + @JvmStatic + /** Whether the given map contains all the given entries */ + fun mapContainsAllEntries(map: Map, entries: Iterable>): Boolean { + for (entry in entries) { + if (!mapContainsEntry(map, entry)) return false } - return true; + return true } - /** Number of entries contained in the given map */ - public static int numberOfContainedEntriesInMap(Map map, Iterable> entries) - { - int found = 0; - for (Map.Entry entry : entries) - { - if(mapContainsEntry(map, entry)) found++; + @JvmStatic + /** Number of entries contained in the given map */ + fun numberOfContainedEntriesInMap(map: Map, entries: Iterable>): Int { + var found = 0 + for (entry in entries) { + if (mapContainsEntry(map, entry)) found++ } - return found; + return found } - /** Whether the given map contains the given entry */ - public static boolean mapContainsEntry(Map map, Map.Entry entry) - { - V mapValue = map.get(entry.getKey()); - V value = entry.getValue(); - return Objects.equals(value, mapValue); + @JvmStatic + /** Whether the given map contains the given entry */ + fun mapContainsEntry(map: Map, entry: Map.Entry): Boolean { + val mapValue = map[entry.key] + val value = entry.value + return value == mapValue } - public interface Predicate { boolean fn(T v); } - - /** Backport of Collection.removeIf */ - public static void removeIf(Collection list, Predicate predicate) - { - list.removeIf(predicate::fn); - } - - public static T find(Collection list, Predicate predicate) - { - for (T item : list) { - if (predicate.fn(item)) return item; - } - return null; + /** Backport of Collection.removeIf */ + fun removeIf(list: MutableList, predicate: (T) -> Boolean) { + list.removeIf(predicate) } -} +} \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt b/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt index 505e9c4..526b6dd 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt @@ -31,7 +31,10 @@ class FeatureDictionary internal constructor( val feature = featureCollection[id, locales] if (feature != null) return feature val countryCodes = dissectCountryCode(countryCode) - return brandFeatureCollection?.let { it[id, countryCodes]} + brandFeatureCollection?.let { + return brandFeatureCollection[id, countryCodes] + } + throw NullPointerException("brandFeatureCollection is null") } //endregion //region Query by tags @@ -48,17 +51,15 @@ class FeatureDictionary internal constructor( locales: List ): List { if (tags.isEmpty()) return emptyList() - val foundFeatures: MutableList = ArrayList() + val foundFeatures: MutableList = mutableListOf() if (isSuggestion == null || !isSuggestion) { - foundFeatures.addAll(getTagsIndex(locales).getAll(tags)) + foundFeatures.addAll(getTagsIndex(locales)?.getAll(tags).orEmpty()) } if (isSuggestion == null || isSuggestion) { val countryCodes = dissectCountryCode(countryCode) - foundFeatures.addAll(getBrandTagsIndex(countryCodes).getAll(tags)) + foundFeatures.addAll(getBrandTagsIndex(countryCodes)?.getAll(tags).orEmpty()) } - CollectionUtils.removeIf( - foundFeatures - ) { feature: Feature -> + foundFeatures.removeIf { feature: Feature -> !isFeatureMatchingParameters( feature, geometry, @@ -73,16 +74,14 @@ class FeatureDictionary internal constructor( removeIds.addAll(getParentCategoryIds(feature.id)) } if (removeIds.isNotEmpty()) { - CollectionUtils.removeIf( - foundFeatures - ) { feature: Feature -> + foundFeatures.removeIf{ feature: Feature -> removeIds.contains( feature.id ) } } } - foundFeatures.sortWith( object: Comparator{ + return foundFeatures.sortedWith(object : Comparator { override fun compare(a: Feature, b: Feature): Int { // 1. features with more matching tags first val tagOrder: Int = b.tags.size - a.tags.size @@ -113,8 +112,8 @@ class FeatureDictionary internal constructor( )) if (numberOfMatchedAddTags != 0) return numberOfMatchedAddTags return (100 * b.matchScore - 100 * a.matchScore).toInt() - }}) - return foundFeatures + } + }) } //endregion //region Query by term @@ -130,46 +129,34 @@ class FeatureDictionary internal constructor( isSuggestion: Boolean?, limit: Int, locales: List - ): List { + ): MutableList { val canonicalSearch = StringUtils.canonicalize(search) val sortNames = Comparator { a: Feature, b: Feature -> // 1. exact matches first val exactMatchOrder = - ((if (CollectionUtils.find( - b.names - ) { n: String? -> n == search } != null + ((if (b.names.find { n: String? -> n == search } != null ) 1 else 0) - - if (CollectionUtils.find( - a.names - ) { n: String? -> n == search } != null + - if (a.names.find { n: String? -> n == search } != null ) 1 else 0) if (exactMatchOrder != 0) return@Comparator exactMatchOrder // 2. exact matches case and diacritics insensitive first val cExactMatchOrder = - ((if (CollectionUtils.find( - b.canonicalNames - ) { n: String? -> n == canonicalSearch } != null + ((if (b.canonicalNames.find { n: String? -> n == canonicalSearch } != null ) 1 else 0) - - if (CollectionUtils.find( - a.canonicalNames - ) { n: String? -> n == canonicalSearch } != null + - if (a.canonicalNames.find { n: String? -> n == canonicalSearch } != null ) 1 else 0) if (cExactMatchOrder != 0) return@Comparator cExactMatchOrder // 3. starts-with matches in string first val startsWithOrder = - ((if (CollectionUtils.find( - b.canonicalNames - ) { n: String? -> + ((if (b.canonicalNames.find { n: String? -> n!!.startsWith( canonicalSearch ) } != null ) 1 else 0) - - if (CollectionUtils.find( - a.canonicalNames - ) { n: String? -> + - if (a.canonicalNames.find { n: String? -> n!!.startsWith( canonicalSearch ) @@ -185,18 +172,16 @@ class FeatureDictionary internal constructor( val result: MutableList = ArrayList() if (isSuggestion == null || !isSuggestion) { // a. matches with presets first - val foundFeaturesByName = getNamesIndex(locales).getAll(canonicalSearch) - CollectionUtils.removeIf( - foundFeaturesByName - ) { feature: Feature -> + val foundFeaturesByName: MutableList = + getNamesIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() + foundFeaturesByName.removeIf { feature: Feature -> !isFeatureMatchingParameters( feature, geometry, countryCode ) } - foundFeaturesByName.sortedWith(sortNames) - result.addAll(foundFeaturesByName) + result.addAll(foundFeaturesByName.sortedWith(sortNames)) // if limit is reached, can return earlier (performance improvement) if (limit > 0 && result.size >= limit) return result.subList( @@ -207,18 +192,16 @@ class FeatureDictionary internal constructor( if (isSuggestion == null || isSuggestion) { // b. matches with brand names second val countryCodes = dissectCountryCode(countryCode) - val foundBrandFeatures = getBrandNamesIndex(countryCodes).getAll(canonicalSearch) - CollectionUtils.removeIf( - foundBrandFeatures - ) { feature: Feature -> + val foundBrandFeatures = getBrandNamesIndex(countryCodes)?.getAll(canonicalSearch).orEmpty().toMutableList() + foundBrandFeatures.removeIf{ feature: Feature -> !isFeatureMatchingParameters( feature, geometry, countryCode ) } - foundBrandFeatures.sortedWith(sortNames) - result.addAll(foundBrandFeatures) + + result.addAll(foundBrandFeatures.sortedWith(sortNames)) // if limit is reached, can return earlier (performance improvement) if (limit > 0 && result.size >= limit) return result.subList( @@ -228,10 +211,8 @@ class FeatureDictionary internal constructor( } if (isSuggestion == null || !isSuggestion) { // c. matches with terms third - val foundFeaturesByTerm = getTermsIndex(locales).getAll(canonicalSearch) - CollectionUtils.removeIf( - foundFeaturesByTerm - ) { feature: Feature -> + val foundFeaturesByTerm = getTermsIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() + foundFeaturesByTerm.removeIf { feature: Feature -> !isFeatureMatchingParameters( feature, geometry, @@ -240,16 +221,14 @@ class FeatureDictionary internal constructor( } if (foundFeaturesByTerm.isNotEmpty()) { val alreadyFoundFeatures: Set = HashSet(result) - CollectionUtils.removeIf( - foundFeaturesByTerm - ) { feature: Feature -> + foundFeaturesByTerm.removeIf { feature: Feature -> alreadyFoundFeatures.contains( feature ) } + } - foundFeaturesByTerm.sortedWith { a: Feature, b: Feature -> (100 * b.matchScore - 100 * a.matchScore).toInt() } - result.addAll(foundFeaturesByTerm) + result.addAll(foundFeaturesByTerm.sortedWith { a: Feature, b: Feature -> (100 * b.matchScore - 100 * a.matchScore).toInt() }) // if limit is reached, can return earlier (performance improvement) if (limit > 0 && result.size >= limit) return result.subList( @@ -259,10 +238,8 @@ class FeatureDictionary internal constructor( } if (isSuggestion == null || !isSuggestion) { // d. matches with tag values fourth - val foundFeaturesByTagValue = getTagValuesIndex(locales).getAll(canonicalSearch) - CollectionUtils.removeIf( - foundFeaturesByTagValue - ) { feature: Feature -> + val foundFeaturesByTagValue = getTagValuesIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() + foundFeaturesByTagValue.removeIf { feature: Feature -> !isFeatureMatchingParameters( feature, geometry, @@ -271,398 +248,358 @@ class FeatureDictionary internal constructor( } if (foundFeaturesByTagValue.isNotEmpty()) { val alreadyFoundFeatures: Set = HashSet(result) - CollectionUtils.removeIf( - foundFeaturesByTagValue - ) { feature: Feature -> + foundFeaturesByTagValue.removeIf { feature: Feature -> alreadyFoundFeatures.contains( feature ) } + result.addAll(foundFeaturesByTagValue) } - result.addAll(foundFeaturesByTagValue) } - return result.subList(0, min(limit.toDouble(), result.size.toDouble()).toInt()) + return result.subList(0, min(limit.toDouble(), result.size.toDouble()).toInt()) + } - //endregion - //region Lazily get or create Indexes - /** lazily get or create tags index for given locale(s) */ - private fun getTagsIndex(locales: List): FeatureTagsIndex { - return CollectionUtils.synchronizedGetOrCreate( - tagsIndexes, locales - ) { locales -> - createTagsIndex( - locales - ) + //endregion + //region Lazily get or create Indexes + /** lazily get or create tags index for given locale(s) */ + private fun getTagsIndex(locales: List): FeatureTagsIndex? { + return CollectionUtils.synchronizedGetOrCreate(tagsIndexes.toMutableMap(), locales, ::createTagsIndex) } - } - private fun createTagsIndex(locales: List): FeatureTagsIndex { - return FeatureTagsIndex(featureCollection.getAll(locales)) - } + private fun createTagsIndex(locales: List): FeatureTagsIndex { + return FeatureTagsIndex(featureCollection.getAll(locales)) + } - /** lazily get or create names index for given locale(s) */ - private fun getNamesIndex(locales: List): FeatureTermIndex { - return CollectionUtils.synchronizedGetOrCreate( - namesIndexes, locales - ) { locales -> - createNamesIndex( - locales - ) + /** lazily get or create names index for given locale(s) */ + private fun getNamesIndex(locales: List): FeatureTermIndex? { + return CollectionUtils.synchronizedGetOrCreate(namesIndexes.toMutableMap(), locales, ::createNamesIndex) } - } - private fun createNamesIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature? -> - if (feature != null) { + private fun createNamesIndex(locales: List): FeatureTermIndex { + return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature -> if (!feature.isSearchable) return@Selector emptyList() - } - val names: List = feature?.canonicalNames ?: emptyList() - val result = ArrayList(names) - for (name in names) { - if (name.contains(" ")) { - result.addAll( - name.replace("[()]".toRegex(), "").split(" ".toRegex()).dropLastWhile { it.isEmpty() } - .toTypedArray().toList()) + val names: List = feature.canonicalNames + val result = ArrayList(names) + for (name in names) { + if (name.contains(" ")) { + result.addAll( + name.replace("[()]", "").split(" ") + ) + } } - } - result - }) - } + result + }) + } - /** lazily get or create terms index for given locale(s) */ - private fun getTermsIndex(locales: List): FeatureTermIndex { - return CollectionUtils.synchronizedGetOrCreate( - termsIndexes, locales - ) { locales -> - createTermsIndex( - locales - ) + /** lazily get or create terms index for given locale(s) */ + private fun getTermsIndex(locales: List): FeatureTermIndex? { + return CollectionUtils.synchronizedGetOrCreate(termsIndexes.toMutableMap(), locales, ::createTermsIndex) } - } - private fun createTermsIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature? -> - if (feature != null) { + private fun createTermsIndex(locales: List): FeatureTermIndex { + return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature -> if (!feature.isSearchable) return@Selector emptyList() - } - feature?.canonicalTerms.orEmpty() - }) - } + feature.canonicalTerms.orEmpty() + }) + } - /** lazily get or create tag values index */ - private fun getTagValuesIndex(locales: List): FeatureTermIndex { - return CollectionUtils.synchronizedGetOrCreate( - tagValuesIndexes, locales - ) { locales -> - createTagValuesIndex( - locales + /** lazily get or create tag values index */ + private fun getTagValuesIndex(locales: List): FeatureTermIndex? { + return CollectionUtils.synchronizedGetOrCreate( + tagValuesIndexes.toMutableMap(), + locales, + ::createTagValuesIndex ) } - } - private fun createTagValuesIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature? -> - if (feature != null) { + private fun createTagValuesIndex(locales: List): FeatureTermIndex { + return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature -> if (!feature.isSearchable) return@Selector emptyList() - } - val result: ArrayList? = - feature?.tags?.let { ArrayList(it.size) } - if (feature != null) { + + val result: ArrayList = ArrayList(feature.tags.size) for (tagValue in feature.tags.values) { - if (tagValue != "*") result?.add(tagValue) + if (tagValue != "*") result.add(tagValue) } - } - return@Selector result!! - }) - } + return@Selector result + }) + } - /** lazily get or create brand names index for country */ - private fun getBrandNamesIndex(countryCodes: List): FeatureTermIndex { - return CollectionUtils.synchronizedGetOrCreate( - brandNamesIndexes, countryCodes - ) { countryCodes -> - createBrandNamesIndex( - countryCodes + /** lazily get or create brand names index for country */ + private fun getBrandNamesIndex(countryCodes: List): FeatureTermIndex? { + return CollectionUtils.synchronizedGetOrCreate( + brandNamesIndexes.toMutableMap(), + countryCodes, + ::createBrandNamesIndex ) } - } - private fun createBrandNamesIndex(countryCodes: List): FeatureTermIndex { - return if (brandFeatureCollection == null) { - FeatureTermIndex(emptyList(), null) - } else FeatureTermIndex( - brandFeatureCollection.getAll(countryCodes) - ) { - if (it?.isSearchable == true) emptyList() else it?.canonicalNames.orEmpty() + private fun createBrandNamesIndex(countryCodes: List): FeatureTermIndex { + return if (brandFeatureCollection == null) { + FeatureTermIndex(emptyList(), null) + } else FeatureTermIndex( + brandFeatureCollection.getAll(countryCodes) + ) { + if (!it.isSearchable) emptyList() else it.canonicalNames + } } - } - /** lazily get or create tags index for the given countries */ - private fun getBrandTagsIndex(countryCodes: List): FeatureTagsIndex { - return CollectionUtils.synchronizedGetOrCreate( - brandTagsIndexes, countryCodes - ) { countryCodes -> - createBrandTagsIndex( - countryCodes + /** lazily get or create tags index for the given countries */ + private fun getBrandTagsIndex(countryCodes: List): FeatureTagsIndex? { + return CollectionUtils.synchronizedGetOrCreate( + brandTagsIndexes.toMutableMap(), + countryCodes, + ::createBrandTagsIndex ) } - } - private fun createBrandTagsIndex(countryCodes: List): FeatureTagsIndex { - return if (brandFeatureCollection == null) { - FeatureTagsIndex(emptyList()) - } else FeatureTagsIndex(brandFeatureCollection.getAll(countryCodes)) - } + private fun createBrandTagsIndex(countryCodes: List): FeatureTagsIndex { + return if (brandFeatureCollection == null) { + FeatureTagsIndex(emptyList()) + } else FeatureTagsIndex(brandFeatureCollection.getAll(countryCodes)) + } - //endregion - //region Query builders - inner class QueryByIdBuilder(private val id: String) { - private var locale: List = listOf(default) - private var countryCode: String? = null - - private operator fun get(id: String, locales: List, countryCode: String): Feature? { - val feature = featureCollection[id, locales] - if (feature != null) return feature - val countryCodes = dissectCountryCode(countryCode) - brandFeatureCollection?.let { - return it[id, countryCodes] + //endregion + //region Query builders + inner class QueryByIdBuilder(private val id: String) { + private var locale: List = listOf(default) + private var countryCode: String? = null + + private operator fun get(id: String, locales: List, countryCode: String?): Feature? { + return this@FeatureDictionary[id, locales, countryCode] } - throw NullPointerException("brandFeatureCollection is null") - } - /** - * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * - * - * `null` means to include unlocalized results. - * - * - * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, - * i.e. unlocalized results are included by default. - */ - fun forLocale(vararg locales: Locale?): QueryByIdBuilder { - this.locale = locales.toList() - return this - } + /** + * + *Sets the locale(s) in which to present the results. + * + * You can specify several locales in + * a row to each fall back to if a translation does not exist in the locale before that. + * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you + * wanted results preferredly in Catalan, but Spanish is also fine. + * + * + * `null` means to include unlocalized results. + * + * + * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, + * i.e. unlocalized results are included by default. + */ + fun forLocale(vararg locales: Locale?): QueryByIdBuilder { + this.locale = locales.toList() + return this + } - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByIdBuilder { - this.countryCode = countryCode - return this - } + /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the + * country/state the element is in. If not specified, will only return matches that are not + * county-specific. */ + fun inCountry(countryCode: String?): QueryByIdBuilder { + this.countryCode = countryCode + return this + } - /** Returns the feature associated with the given id or `null` if it does not - * exist */ - fun get(): Feature? { - return countryCode?.let { this[id, locale, it] } + /** Returns the feature associated with the given id or `null` if it does not + * exist */ + fun get(): Feature? { + return this[id, locale, countryCode] + } } - } - inner class QueryByTagBuilder (private val tags: Map) { - private var geometryType: GeometryType? = null - private var locale: List = listOf(default) - private var suggestion: Boolean? = null - private var countryCode: String? = null + inner class QueryByTagBuilder(private val tags: Map) { + private var geometryType: GeometryType? = null + private var locale: List = listOf(default) + private var suggestion: Boolean? = null + private var countryCode: String? = null - /** Sets for which geometry type to look. If not set or `null`, any will match. */ - fun forGeometry(geometryType: GeometryType?): QueryByTagBuilder { - this.geometryType = geometryType - return this - } + /** Sets for which geometry type to look. If not set or `null`, any will match. */ + fun forGeometry(geometryType: GeometryType?): QueryByTagBuilder { + this.geometryType = geometryType + return this + } - /** - * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * - * - * `null` means to include unlocalized results. - * - * - * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, - * i.e. unlocalized results are included by default. - */ - fun forLocale(vararg locales: Locale?): QueryByTagBuilder { - this.locale = locales.toList() - return this - } + /** + * + *Sets the locale(s) in which to present the results. + * + * You can specify several locales in + * a row to each fall back to if a translation does not exist in the locale before that. + * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you + * wanted results preferredly in Catalan, but Spanish is also fine. + * + * + * `null` means to include unlocalized results. + * + * + * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, + * i.e. unlocalized results are included by default. + */ + fun forLocale(vararg locales: Locale?): QueryByTagBuilder { + this.locale = locales.toList() + return this + } - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByTagBuilder { - this.countryCode = countryCode - return this - } + /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the + * country/state the element is in. If not specified, will only return matches that are not + * county-specific. */ + fun inCountry(countryCode: String?): QueryByTagBuilder { + this.countryCode = countryCode + return this + } - /** Set whether to only include suggestions (=true) or to not include suggestions (=false). - * Suggestions are brands, like 7-Eleven. */ - fun isSuggestion(suggestion: Boolean?): QueryByTagBuilder { - this.suggestion = suggestion - return this - } + /** Set whether to only include suggestions (=true) or to not include suggestions (=false). + * Suggestions are brands, like 7-Eleven. */ + fun isSuggestion(suggestion: Boolean?): QueryByTagBuilder { + this.suggestion = suggestion + return this + } - /** Returns a list of dictionary entries that match or an empty list if nothing is - * found.

In rare cases, a set of tags may match multiple primary features, such as for - * tag combinations like `shop=deli` + `amenity=cafe`, so, this is why - * it is a list. */ - fun find(): List { - return get(tags, geometryType, countryCode, suggestion, locale) + /** Returns a list of dictionary entries that match or an empty list if nothing is + * found.

In rare cases, a set of tags may match multiple primary features, such as for + * tag combinations like `shop=deli` + `amenity=cafe`, so, this is why + * it is a list. */ + fun find(): List { + return get(tags, geometryType, countryCode, suggestion, locale) + } } - } - inner class QueryByTermBuilder(private val term: String) { - private var geometryType: GeometryType? = null - private var locale: List = listOf(default) - private var suggestion: Boolean? = null - private var limit = 50 - private var countryCode: String? = null - - /** Sets for which geometry type to look. If not set or `null`, any will match. */ - fun forGeometry(geometryType: GeometryType?): QueryByTermBuilder { - this.geometryType = geometryType - return this - } + inner class QueryByTermBuilder(private val term: String) { + private var geometryType: GeometryType? = null + private var locale: List = listOf(default) + private var suggestion: Boolean? = null + private var limit = 50 + private var countryCode: String? = null - /** - * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * - * - * `null` means to include unlocalized results. - * - * - * If nothing is specified, it defaults to `[Locale.getDefault()]`, i.e. - * unlocalized results are excluded by default. - */ - fun forLocale(vararg locales: Locale?): QueryByTermBuilder { - this.locale = locales.toList() - return this - } + /** Sets for which geometry type to look. If not set or `null`, any will match. */ + fun forGeometry(geometryType: GeometryType?): QueryByTermBuilder { + this.geometryType = geometryType + return this + } - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByTermBuilder { - this.countryCode = countryCode - return this - } + /** + * + *Sets the locale(s) in which to present the results. + * + * You can specify several locales in + * a row to each fall back to if a translation does not exist in the locale before that. + * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you + * wanted results preferredly in Catalan, but Spanish is also fine. + * + * + * `null` means to include unlocalized results. + * + * + * If nothing is specified, it defaults to `[Locale.getDefault()]`, i.e. + * unlocalized results are excluded by default. + */ + fun forLocale(vararg locales: Locale?): QueryByTermBuilder { + this.locale = locales.toList() + return this + } - /** Set whether to only include suggestions (=true) or to not include suggestions (=false). - * Suggestions are brands, like 7-Eleven. */ - fun isSuggestion(suggestion: Boolean?): QueryByTermBuilder { - this.suggestion = suggestion - return this - } + /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the + * country/state the element is in. If not specified, will only return matches that are not + * county-specific. */ + fun inCountry(countryCode: String?): QueryByTermBuilder { + this.countryCode = countryCode + return this + } - /** limit how many results to return at most. Default is 50, -1 for unlimited. */ - fun limit(limit: Int): QueryByTermBuilder { - this.limit = limit - return this - } + /** Set whether to only include suggestions (=true) or to not include suggestions (=false). + * Suggestions are brands, like 7-Eleven. */ + fun isSuggestion(suggestion: Boolean?): QueryByTermBuilder { + this.suggestion = suggestion + return this + } - /** Returns a list of dictionary entries that match or an empty list if nothing is - * found.

- * Results are sorted mainly in this order: Matches with names, with brand names, then - * matches with terms (keywords). */ - fun find(): List { - return get(term, geometryType, countryCode, suggestion, limit, locale) - } - } //endregion - - companion object { - private val VALID_COUNTRY_CODE_REGEX = Regex("([A-Z]{2})(?:-([A-Z0-9]{1,3}))?") - /** Create a new FeatureDictionary which gets its data from the given directory. Optionally, - * a path to brand presets can be specified. */ - /** Create a new FeatureDictionary which gets its data from the given directory. */ - @JvmOverloads - fun create(presetsBasePath: String, brandPresetsBasePath: String? = null): FeatureDictionary { - val featureCollection: LocalizedFeatureCollection = - IDLocalizedFeatureCollection(FileSystemAccess(presetsBasePath)) - val brandsFeatureCollection: PerCountryFeatureCollection? = - if (brandPresetsBasePath != null) IDBrandPresetsFeatureCollection( - FileSystemAccess(brandPresetsBasePath) - ) else null - return FeatureDictionary(featureCollection, brandsFeatureCollection) - } + /** limit how many results to return at most. Default is 50, -1 for unlimited. */ + fun limit(limit: Int): QueryByTermBuilder { + this.limit = limit + return this + } - //endregion - //region Utility / Filter functions - private fun getParentCategoryIds(id: String): Collection { - var id: String? = id - val result: MutableList = ArrayList() - do { - id = getParentId(id) - if (id != null) result.add(id) - } while (id != null) - return result - } + /** Returns a list of dictionary entries that match or an empty list if nothing is + * found.

+ * Results are sorted mainly in this order: Matches with names, with brand names, then + * matches with terms (keywords). */ + fun find(): List { + return get(term, geometryType, countryCode, suggestion, limit, locale) + } + } //endregion + + companion object { + private val VALID_COUNTRY_CODE_REGEX = Regex("([A-Z]{2})(?:-([A-Z0-9]{1,3}))?") + /** Create a new FeatureDictionary which gets its data from the given directory. Optionally, + * a path to brand presets can be specified. */ + /** Create a new FeatureDictionary which gets its data from the given directory. */ + @JvmOverloads + fun create(presetsBasePath: String, brandPresetsBasePath: String? = null): FeatureDictionary { + val featureCollection: LocalizedFeatureCollection = + IDLocalizedFeatureCollection(FileSystemAccess(presetsBasePath)) + val brandsFeatureCollection: PerCountryFeatureCollection? = + if (brandPresetsBasePath != null) IDBrandPresetsFeatureCollection( + FileSystemAccess(brandPresetsBasePath) + ) else null + return FeatureDictionary(featureCollection, brandsFeatureCollection) + } - private fun getParentId(id: String?): String? { - val lastSlashIndex = id!!.lastIndexOf("/") - return if (lastSlashIndex == -1) null else id.substring(0, lastSlashIndex) - } + //endregion + //region Utility / Filter functions + private fun getParentCategoryIds(id: String): Collection { + var id: String? = id + val result: MutableList = ArrayList() + do { + id = getParentId(id) + if (id != null) result.add(id) + } while (id != null) + return result + } - private fun isFeatureMatchingParameters( - feature: Feature, - geometry: GeometryType?, - countryCode: String? - ): Boolean { - if (geometry != null && !feature.geometry?.contains(geometry)!!) return false - val include: List = feature.includeCountryCodes - val exclude: List = feature.excludeCountryCodes - if (include.isNotEmpty() || exclude.isNotEmpty()) { - if (countryCode == null) return false - if (include.isNotEmpty() && !matchesAnyCountryCode(countryCode, include)) return false - if (matchesAnyCountryCode(countryCode, exclude)) return false - } - return true - } + private fun getParentId(id: String?): String? { + val lastSlashIndex = id!!.lastIndexOf("/") + return if (lastSlashIndex == -1) null else id.substring(0, lastSlashIndex) + } + + private fun isFeatureMatchingParameters( + feature: Feature, + geometry: GeometryType?, + countryCode: String? + ): Boolean { + if (geometry != null && !feature.geometry?.contains(geometry)!!) return false + val include: List = feature.includeCountryCodes + val exclude: List = feature.excludeCountryCodes + if (include.isNotEmpty() || exclude.isNotEmpty()) { + if (countryCode == null) return false + if (include.isNotEmpty() && !matchesAnyCountryCode(countryCode, include)) return false + if (matchesAnyCountryCode(countryCode, exclude)) return false + } + return true + } - private fun dissectCountryCode(countryCode: String?): List { - val result: MutableList = ArrayList() - // add default / international - result.add(null) - countryCode?.let { - val matcher = VALID_COUNTRY_CODE_REGEX.find(it) - if (matcher?.groups?.isNotEmpty() == true) { - // add ISO 3166-1 alpha2 (e.g. "US") - result.add(matcher.groups[1].toString()) - if (matcher.groups.size == 2 && matcher.groups[2] != null) { - // add ISO 3166-2 (e.g. "US-NY") - result.add(it) + private fun dissectCountryCode(countryCode: String?): List { + val result: MutableList = ArrayList() + // add default / international + result.add(null) + countryCode?.let { + val matcher = VALID_COUNTRY_CODE_REGEX.find(it) + if (matcher?.groups?.isNotEmpty() == true) { + // add ISO 3166-1 alpha2 (e.g. "US") + result.add(matcher.groups[1]?.value) + if (matcher.groups[2] != null) { + // add ISO 3166-2 (e.g. "US-NY") + result.add(countryCode) + } } } + return result } - return result - } - private fun matchesAnyCountryCode(showOnly: String, featureCountryCodes: List): Boolean { - for (featureCountryCode in featureCountryCodes) { - if (matchesCountryCode(showOnly, featureCountryCode)) return true + private fun matchesAnyCountryCode(showOnly: String, featureCountryCodes: List): Boolean { + return featureCountryCodes.any { matchesCountryCode(showOnly, it) } } - return false - } - private fun matchesCountryCode(showOnly: String, featureCountryCode: String): Boolean { - return showOnly == featureCountryCode || showOnly.substring(0, 2) == featureCountryCode + private fun matchesCountryCode(showOnly: String, featureCountryCode: String): Boolean { + return showOnly == featureCountryCode || showOnly.substring(0, 2) == featureCountryCode + } } } -} + + diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt b/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt index b1d6c14..bed76b3 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt @@ -3,26 +3,22 @@ package de.westnordost.osmfeatures /** Index that makes finding Features whose name/term/... starts with a given string very efficient. * * Based on the StartsWithStringTree data structure, see that class. */ -class FeatureTermIndex(features: Collection?, selector: Selector?) { +class FeatureTermIndex(features: Collection, selector: Selector?) { private val featureMap: MutableMap> = HashMap() private val tree: StartsWithStringTree init { - if (features != null) { for (feature in features) { - val strings: Collection = selector!!.getStrings(feature) + val strings: Collection = selector?.getStrings(feature) ?: emptyList() for (string in strings) { if (!featureMap.containsKey(string)) featureMap[string] = ArrayList(1) - if (feature != null) { - featureMap[string]!!.add(feature) - } + featureMap[string]?.add(feature) } } - } tree = StartsWithStringTree(featureMap.keys) } - fun getAll(startsWith: String?): List { + fun getAll(startsWith: String): List { val result: MutableSet = HashSet() for (string in tree.getAll(startsWith)) { val fs: List? = featureMap[string] @@ -32,6 +28,6 @@ class FeatureTermIndex(features: Collection?, selector: Selector?) { } fun interface Selector { - fun getStrings(feature: Feature?): List + fun getStrings(feature: Feature): List } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt b/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt index 71ea8fc..f210d70 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import org.json.JSONException - /** Non-localized feature collection sourcing from (NSI) iD presets defined in JSON. * * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that @@ -19,20 +17,20 @@ class IDBrandPresetsFeatureCollection internal constructor(private val fileAcces override fun getAll(countryCodes: List): Collection { val result: MutableMap = HashMap() for (cc in countryCodes) { - result.putAll(getOrLoadPerCountryFeatures(cc)) + getOrLoadPerCountryFeatures(cc)?.let { result.putAll(it) } } return result.values } override fun get(id: String, countryCodes: List): Feature? { for (countryCode in countryCodes) { - val result = getOrLoadPerCountryFeatures(countryCode)[id] + val result = getOrLoadPerCountryFeatures(countryCode)?.get(id) if (result != null) return result } return null } - private fun getOrLoadPerCountryFeatures(countryCode: String?): LinkedHashMap { + private fun getOrLoadPerCountryFeatures(countryCode: String?): java.util.LinkedHashMap? { return CollectionUtils.synchronizedGetOrCreate( featuresByIdByCountryCode, countryCode ) { countryCode: String? -> diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt index e840b1d..558cde3 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt @@ -7,8 +7,8 @@ package de.westnordost.osmfeatures * located in the same directory named like e.g. de.json, pt-BR.json etc. */ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : LocalizedFeatureCollection { private val featuresById: LinkedHashMap - private val localizedFeaturesList: Map> = HashMap() - private val localizedFeatures: Map, LinkedHashMap> = HashMap() + private val localizedFeaturesList: MutableMap> = HashMap() + private val localizedFeatures: MutableMap?, LinkedHashMap> = HashMap() init { val features = loadFeatures() @@ -25,17 +25,11 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : } catch (e: Exception) { - throw RuntimeException(e); + throw RuntimeException(e) } } - private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap { - return CollectionUtils.synchronizedGetOrCreate( - localizedFeatures, locales - ) { locales -> - loadLocalizedFeatures( - locales - ) - } + private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap? { + return CollectionUtils.synchronizedGetOrCreate(localizedFeatures, locales, ::loadLocalizedFeatures) } private fun loadLocalizedFeatures(locales: List): LinkedHashMap { @@ -45,7 +39,7 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : val locale = it.previous() if (locale != null) { for (localeComponent in getLocaleComponents(locale)) { - putAllFeatures(result, getOrLoadLocalizedFeaturesList(localeComponent)) + getOrLoadLocalizedFeaturesList(localeComponent)?.let { it1 -> putAllFeatures(result, it1) } } } else { putAllFeatures(result, featuresById.values) @@ -54,17 +48,17 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : return result } - private fun getOrLoadLocalizedFeaturesList(locale: Locale): List { + private fun getOrLoadLocalizedFeaturesList(locale: Locale): List? { return CollectionUtils.synchronizedGetOrCreate( localizedFeaturesList, locale - ) { locale: Locale -> + ) { locale: Locale? -> loadLocalizedFeaturesList( locale ) } } - private fun loadLocalizedFeaturesList(locale: Locale): List { + private fun loadLocalizedFeaturesList(locale: Locale?): List { val filename = getLocalizationFilename(locale) if (!fileAccess.exists(filename)) return emptyList() @@ -74,34 +68,34 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : } override fun getAll(locales: List): Collection { - return getOrLoadLocalizedFeatures(locales).values; + return getOrLoadLocalizedFeatures(locales)?.values ?: emptyList() } override operator fun get(id: String, locales: List): Feature? { - return getOrLoadLocalizedFeatures(locales)[id]; + return getOrLoadLocalizedFeatures(locales)?.get(id) } companion object { private const val FEATURES_FILE = "presets.json" - private fun getLocalizationFilename(locale: Locale): String { + private fun getLocalizationFilename(locale: Locale?): String { /* we only want language+country+script of the locale, not anything else. So we construct it anew here */ return Locale.Builder() - .setLanguage(locale.language) - .setRegion(locale.country) - .setScript(locale.script) + .setLanguage(locale?.language ?: "") + .setRegion(locale?.country ?: "") + .setScript(locale?.script ?: "") .build() - .toLanguageTag() + ".json" + .languageTag + ".json" } - private fun getLocaleComponents(locale: Locale): List { - val lang = locale.language - val country = locale.country - val script = locale.script + private fun getLocaleComponents(locale: Locale?): List { + val lang = locale?.language ?: "" + val country = locale?.country ?: "" + val script = locale?.script ?: "" val result: MutableList = ArrayList(4) result.add(Locale(lang)) if (country.isNotEmpty()) result.add(Locale.Builder().setLanguage(lang).setRegion(country).build()) - if (script!!.isNotEmpty()) result.add(Locale.Builder().setLanguage(lang).setScript(script).build()) + if (script.isNotEmpty()) result.add(Locale.Builder().setLanguage(lang).setScript(script).build()) if (country.isNotEmpty() && script.isNotEmpty()) result.add( Locale.Builder().setLanguage(lang).setRegion(country).setScript(script).build() ) diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt index 6e91276..098b7cb 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt @@ -4,7 +4,6 @@ import de.westnordost.osmfeatures.JsonUtils.createFromSource import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive -import okio.FileHandle import okio.Source /** Parses a file from @@ -14,7 +13,7 @@ import okio.Source class IDPresetsTranslationJsonParser { fun parse( - source: Source, locale: Locale, baseFeatures: Map + source: Source, locale: Locale?, baseFeatures: Map ): List { val decodedObject = createFromSource(source) val languageKey: String = decodedObject.entries.iterator().next().key @@ -49,7 +48,7 @@ class IDPresetsTranslationJsonParser { return ArrayList(localizedFeatures.values) } - private fun parseFeature(feature: BaseFeature?, locale: Locale, localization: JsonObject): LocalizedFeature? { + private fun parseFeature(feature: BaseFeature?, locale: Locale?, localization: JsonObject): LocalizedFeature? { if (feature == null) return null val name = localization["name"]?.jsonPrimitive?.content diff --git a/library/src/main/java/de/westnordost/osmfeatures/Locale.kt b/library/src/main/java/de/westnordost/osmfeatures/Locale.kt index 9eec4b9..58c4b7a 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/Locale.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/Locale.kt @@ -29,37 +29,6 @@ class Locale( @JvmStatic val default: Locale? = null - fun toTitleString(s: String): String { - var len: Int - if (s.length.also { len = it } == 0) { - return s - } - var idx = 0 - if (!s[idx].isLowerCase()) { - idx = 1 - while (idx < len) { - if (s[idx].isUpperCase()) { - break - } - idx++ - } - } - if (idx == len) { - return s - } - val buf = CharArray(len) - for (i in 0 until len) { - val c = s[i] - if (i == 0 && idx == 0) { - buf[i] = c.uppercaseChar() - } else if (i < idx) { - buf[i] = c - } else { - buf[i] = c.lowercaseChar() - } - } - return String(buf) - } } @@ -68,30 +37,22 @@ class Locale( val country : String get() = this.region.orEmpty() - private var languageTag : String? = null + val languageTag : String? by lazy { + LanguageTag.forLanguage(language, script, region).toString() + } constructor(lang: String) : this(lang,"", "") constructor(lang: String, region: String) : this(lang, region, "") - fun toLanguageTag(): String { - val lTag: String? = this.languageTag - if (lTag != null) { - return lTag - } - this.languageTag = LanguageTag.forLanguage(language, script, region).toString() - this.languageTag?.let{ return it} - throw NullPointerException("LanguageTag could not be parsed") - - } override fun equals(other: Any?): Boolean { if (other == null) { return false } if (other is Locale) { - return other.language == this.language && other.region == this.region && other.script == this.script + return other.languageTag == this.languageTag } return false diff --git a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt index 956d6e3..5226130 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt @@ -4,7 +4,7 @@ package de.westnordost.osmfeatures * I.e. the name and terms are specified in the given locale. */ class LocalizedFeature( private val p: BaseFeature, - override val locale: Locale, + override val locale: Locale?, override val names: List, override val terms: List ) : diff --git a/library/src/main/java/de/westnordost/osmfeatures/StartsWithStringTree.kt b/library/src/main/java/de/westnordost/osmfeatures/StartsWithStringTree.kt index 09ff571..669d10c 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/StartsWithStringTree.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/StartsWithStringTree.kt @@ -1,122 +1,108 @@ -package de.westnordost.osmfeatures; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +package de.westnordost.osmfeatures /** Index that makes finding strings that start with characters very efficient. - * It sorts the strings into a tree structure with configurable depth. + * It sorts the strings into a tree structure with configurable depth. * - * It is threadsafe because it is immutable. + * It is threadsafe because it is immutable. * - * For the strings ["f", "foobar", "foo", "fu", "funicular"], the tree may internally look f.e. - * like this: - *
- *  f ->
- *    [ "f" ]
- *    o ->
- *      o ->
- *        [ "foobar", "foo", ...]
- *    u ->
- *      [ "fu", "funicular", ... ]
- *  
*/ -class StartsWithStringTree -{ - private final Node root; - - public StartsWithStringTree(Collection strings) - { - this(strings, 16, 16); - } + * For the strings ["f", "foobar", "foo", "fu", "funicular"], the tree may internally look f.e. + * like this: + *
+ * f ->
+ * [ "f" ]
+ * o ->
+ * o ->
+ * [ "foobar", "foo", ...]
+ * u ->
+ * [ "fu", "funicular", ... ]
+
*/ +internal class StartsWithStringTree +@JvmOverloads constructor(strings: Collection, maxDepth: Int = 16, minContainerSize: Int = 16) { + private val root: Node /** Create this index with the given strings. * - * The generated tree will have a max depth of maxDepth and another depth is not added to the - * tree if there are less than minContainerSize strings in one tree node. + * The generated tree will have a max depth of maxDepth and another depth is not added to the + * tree if there are less than minContainerSize strings in one tree node. */ - public StartsWithStringTree(Collection strings, int maxDepth, int minContainerSize) - { - if (maxDepth < 0) maxDepth = 0; - if (minContainerSize < 1) minContainerSize = 1; - root = buildTree(strings, 0, maxDepth, minContainerSize); + init { + var maxDepth = maxDepth + var minContainerSize = minContainerSize + if (maxDepth < 0) maxDepth = 0 + if (minContainerSize < 1) minContainerSize = 1 + root = buildTree(strings, 0, maxDepth, minContainerSize) } - /** Get all strings which start with the given string */ - public List getAll(String startsWith) - { - return root.getAll(startsWith, 0); + /** Get all strings which start with the given string */ + fun getAll(startsWith: String?): List { + return root.getAll(startsWith, 0) } - private static Node buildTree(Collection strings, int currentDepth, int maxDepth, int minContainerSize) - { - if (currentDepth == maxDepth || strings.size() < minContainerSize) - return new Node(null, strings); + private class Node(val children: Map?, strings: Collection) { + val strings: Collection? = strings - Map> stringsByCharacter = getStringsByCharacter(strings, currentDepth); - HashMap children = new HashMap<>(stringsByCharacter.size()); + /** Get all strings that start with the given string */ + fun getAll(startsWith: String?, offset: Int): List { + if (startsWith != null) { + if (startsWith.isEmpty()) return emptyList() + } - for (Map.Entry> entry : stringsByCharacter.entrySet()) { - Character c = entry.getKey(); - if (c == null) continue; - Node child = buildTree(entry.getValue(), currentDepth + 1, maxDepth, minContainerSize); - children.put(c, child); + val result: MutableList = ArrayList() + if (children != null) { + for ((key, value) in children) { + if (key != null) { + if (startsWith != null) { + if (startsWith.length <= offset || key == startsWith[offset]) { + result.addAll(value.getAll(startsWith, offset + 1)) + } + } + } + } + } + if (strings != null) { + for (string in strings) { + if (startsWith?.let { string.startsWith(it) } == true) result.add(string) + } + } + return result } - Collection remainingStrings = stringsByCharacter.get(null); - if (children.isEmpty()) children = null; - return new Node(children, remainingStrings); } - /** returns the given strings grouped by their nth character. Strings whose length is shorter - * or equal to nth go into the "null" group. */ - private static Map> getStringsByCharacter(Collection strings, int nth) - { - HashMap> result = new HashMap<>(); - for (String string : strings) { - Character c = string.length() > nth ? string.charAt(nth) : null; - if (!result.containsKey(c)) result.put(c, new ArrayList<>()); - result.get(c).add(string); - } - return result; - } + companion object { + private fun buildTree( + strings: Collection, + currentDepth: Int, + maxDepth: Int, + minContainerSize: Int + ): Node { + if (currentDepth == maxDepth || strings.size < minContainerSize) return Node(null, strings) - private static class Node - { - final Map children; - final Collection strings; + val stringsByCharacter = getStringsByCharacter(strings, currentDepth) + var children: HashMap? = HashMap(stringsByCharacter.size) - private Node(Map children, Collection strings) - { - this.children = children; - this.strings = strings; + for ((key, value) in stringsByCharacter) { + val c = key ?: continue + val child = buildTree(value, currentDepth + 1, maxDepth, minContainerSize) + children!![c] = child + } + val remainingStrings: Collection = stringsByCharacter[null]!! + if (children!!.isEmpty()) children = null + return Node(children, remainingStrings) } - /** Get all strings that start with the given string */ - private List getAll(String startsWith, int offset) - { - if (startsWith.isEmpty()) return Collections.emptyList(); - - List result = new ArrayList<>(); - if (children != null) - { - for (Map.Entry charToNode : children.entrySet()) - { - if (startsWith.length() <= offset || charToNode.getKey() == startsWith.charAt(offset)) - { - result.addAll(charToNode.getValue().getAll(startsWith, offset + 1)); - } - } - } - if (strings != null) - { - for (String string : strings) - { - if (string.startsWith(startsWith)) result.add(string); - } + /** returns the given strings grouped by their nth character. Strings whose length is shorter + * or equal to nth go into the "null" group. */ + private fun getStringsByCharacter( + strings: Collection, + nth: Int + ): Map> { + val result = HashMap>() + for (string in strings) { + val c = if (string.length > nth) string[nth] else null + if (!result.containsKey(c)) result[c] = ArrayList() + result[c]!!.add(string) } - return result; + return result } } -} +} \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt b/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt index bc06d26..f85430e 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt @@ -1,23 +1,20 @@ package de.westnordost.osmfeatures -import java.util.regex.Pattern - -import kotlin.text.Regex +import doist.x.normalize.Form +import doist.x.normalize.normalize class StringUtils { - private val FIND_DIACRITICS = Pattern.compile("\\p{InCombiningDiacriticalMarks}+") - - companion object { + val FIND_DIACRITICS = "\\p{InCombiningDiacriticalMarks}+".toRegex() + @JvmStatic fun canonicalize(str: String): String { return stripDiacritics(str).lowercase() } private fun stripDiacritics(str: String): String { - val reg = Regex("\\p{InCombiningDiacriticalMarks}+") - return reg.replace("", str) + return FIND_DIACRITICS.replace(str.normalize(Form.NFD),"") } } diff --git a/library/src/test/java/de/westnordost/osmfeatures/CollectionUtilsTest.java b/library/src/test/java/de/westnordost/osmfeatures/CollectionUtilsTest.java index 2e7cd72..4eeec5c 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/CollectionUtilsTest.java +++ b/library/src/test/java/de/westnordost/osmfeatures/CollectionUtilsTest.java @@ -17,19 +17,6 @@ public class CollectionUtilsTest { - @Test public void removeIf() - { - List ints = new ArrayList<>(listOf(1,2,3,4,5,6,7,8,9,10)); - CollectionUtils.removeIf(ints, i -> i % 2 == 0); - assertEquals(listOf(1,3,5,7,9), ints); - } - - @Test public void find() - { - List strs = new ArrayList<>(listOf("one", "two", "three")); - assertEquals("two", CollectionUtils.find(strs, str -> str.equals("two"))); - assertNull(CollectionUtils.find(strs, str -> str.equals("four"))); - } @Test public void mapContainsEntry() { diff --git a/library/src/test/java/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.java b/library/src/test/java/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.java index bc4ffe6..92eac0a 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.java +++ b/library/src/test/java/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.java @@ -32,8 +32,8 @@ public Collection getAll(List countryCodes) { public Feature get(String id, List countryCodes) { for (Feature feature : features) { if (!feature.getId().equals(id)) continue; + List includeCountryCodes = feature.getIncludeCountryCodes(); for (String countryCode : countryCodes) { - List includeCountryCodes = feature.getIncludeCountryCodes(); if (includeCountryCodes.contains(countryCode) || countryCode == null && includeCountryCodes.isEmpty()) { return feature; } From b4aca73b5908cb2a631efeddd5c37c892e0f7135 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Sun, 17 Dec 2023 15:11:30 +0100 Subject: [PATCH 10/98] Fixed dissectCountryCode --- library/build.gradle.kts | 1 + .../de/westnordost/osmfeatures/BaseFeature.kt | 15 +- .../osmfeatures/CollectionUtils.kt | 90 +-- .../osmfeatures/FeatureDictionnary.kt | 719 ++++++++---------- .../osmfeatures/FeatureTermIndex.kt | 14 +- .../IDBrandPresetsFeatureCollection.kt | 8 +- .../IDLocalizedFeatureCollection.kt | 48 +- .../osmfeatures/IDPresetsJsonParser.kt | 43 +- .../IDPresetsTranslationJsonParser.kt | 5 +- .../de/westnordost/osmfeatures/JsonUtils.kt | 13 +- .../java/de/westnordost/osmfeatures/Locale.kt | 47 +- .../osmfeatures/LocalizedFeature.kt | 2 +- .../osmfeatures/StartsWithStringTree.kt | 179 ++--- .../de/westnordost/osmfeatures/StringUtils.kt | 13 +- .../osmfeatures/CollectionUtilsTest.java | 13 - .../osmfeatures/IDPresetsJsonParserTest.java | 168 ++-- .../TestPerCountryFeatureCollection.java | 2 +- 17 files changed, 609 insertions(+), 771 deletions(-) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 25de843..75d6590 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { implementation("io.fluidsonic.locale:fluid-locale:0.13.0") implementation("com.squareup.okio:okio:3.6.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") + implementation("com.doist.x:normalize:1.0.5") } tasks { diff --git a/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt b/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt index a58aee0..a3f6298 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt @@ -5,9 +5,9 @@ open class BaseFeature( override val id: String, override val tags: Map, final override var geometry: List, - final override val icon: String?, - final override val imageURL: String?, - final override val names: List, + private val _icon: String? = "", + private val _imageURL: String? = "", + private val _names: List, final override val terms: List?, final override val includeCountryCodes: List, final override val excludeCountryCodes: List, @@ -26,6 +26,15 @@ open class BaseFeature( } } + override val icon: String + get() = _icon ?: "" + + override val imageURL: String + get() = _imageURL ?: "" + + final override val names: List + get() = _names.ifEmpty { listOf("") } + override val name: String get() = names[0] override val locale: Locale? diff --git a/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt b/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt index 9da7c0e..8341365 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt @@ -1,69 +1,47 @@ -package de.westnordost.osmfeatures; - -import java.util.Collection; -import java.util.Map; -import java.util.Objects; - -class CollectionUtils { - - public interface CreateFn { V create(K value); } +package de.westnordost.osmfeatures +object CollectionUtils { /** For the given map, get the value of the entry at the given key and if there is no - * entry yet, create it using the given create function thread-safely */ - public static V synchronizedGetOrCreate(Map map, K key, CreateFn createFn) - { - synchronized(map) - { - if (!map.containsKey(key)) - { - map.put(key, createFn.create(key)); + * entry yet, create it using the given create function thread-safely */ + fun synchronizedGetOrCreate(map: MutableMap, key: K, createFn: (K) -> V): V? { + try { + synchronized(map) { + if (!map.containsKey(key)) { + map[key] = createFn(key) + } } + return map[key] } - return map.get(key); - } - - /** Whether the given map contains all the given entries */ - public static boolean mapContainsAllEntries(Map map, Iterable> entries) - { - for (Map.Entry entry : entries) - { - if(!mapContainsEntry(map, entry)) return false; + catch (e: Error) { + println(e) } - return true; + return null } - /** Number of entries contained in the given map */ - public static int numberOfContainedEntriesInMap(Map map, Iterable> entries) - { - int found = 0; - for (Map.Entry entry : entries) - { - if(mapContainsEntry(map, entry)) found++; + @JvmStatic + /** Whether the given map contains all the given entries */ + fun mapContainsAllEntries(map: Map, entries: Iterable>): Boolean { + for (entry in entries) { + if (!mapContainsEntry(map, entry)) return false } - return found; + return true } - /** Whether the given map contains the given entry */ - public static boolean mapContainsEntry(Map map, Map.Entry entry) - { - V mapValue = map.get(entry.getKey()); - V value = entry.getValue(); - return Objects.equals(value, mapValue); - } - - public interface Predicate { boolean fn(T v); } - - /** Backport of Collection.removeIf */ - public static void removeIf(Collection list, Predicate predicate) - { - list.removeIf(predicate::fn); + @JvmStatic + /** Number of entries contained in the given map */ + fun numberOfContainedEntriesInMap(map: Map, entries: Iterable>): Int { + var found = 0 + for (entry in entries) { + if (mapContainsEntry(map, entry)) found++ + } + return found } - public static T find(Collection list, Predicate predicate) - { - for (T item : list) { - if (predicate.fn(item)) return item; - } - return null; + @JvmStatic + /** Whether the given map contains the given entry */ + fun mapContainsEntry(map: Map, entry: Map.Entry): Boolean { + val mapValue = map[entry.key] + val value = entry.value + return value == mapValue } -} +} \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt b/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt index 505e9c4..b27babb 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt @@ -31,7 +31,10 @@ class FeatureDictionary internal constructor( val feature = featureCollection[id, locales] if (feature != null) return feature val countryCodes = dissectCountryCode(countryCode) - return brandFeatureCollection?.let { it[id, countryCodes]} + brandFeatureCollection?.let { + return brandFeatureCollection[id, countryCodes] + } + throw NullPointerException("brandFeatureCollection is null") } //endregion //region Query by tags @@ -48,17 +51,15 @@ class FeatureDictionary internal constructor( locales: List ): List { if (tags.isEmpty()) return emptyList() - val foundFeatures: MutableList = ArrayList() + val foundFeatures: MutableList = mutableListOf() if (isSuggestion == null || !isSuggestion) { - foundFeatures.addAll(getTagsIndex(locales).getAll(tags)) + foundFeatures.addAll(getTagsIndex(locales)?.getAll(tags).orEmpty()) } if (isSuggestion == null || isSuggestion) { val countryCodes = dissectCountryCode(countryCode) - foundFeatures.addAll(getBrandTagsIndex(countryCodes).getAll(tags)) + foundFeatures.addAll(getBrandTagsIndex(countryCodes)?.getAll(tags).orEmpty()) } - CollectionUtils.removeIf( - foundFeatures - ) { feature: Feature -> + foundFeatures.removeIf { feature: Feature -> !isFeatureMatchingParameters( feature, geometry, @@ -73,16 +74,14 @@ class FeatureDictionary internal constructor( removeIds.addAll(getParentCategoryIds(feature.id)) } if (removeIds.isNotEmpty()) { - CollectionUtils.removeIf( - foundFeatures - ) { feature: Feature -> + foundFeatures.removeIf{ feature: Feature -> removeIds.contains( feature.id ) } } } - foundFeatures.sortWith( object: Comparator{ + return foundFeatures.sortedWith(object : Comparator { override fun compare(a: Feature, b: Feature): Int { // 1. features with more matching tags first val tagOrder: Int = b.tags.size - a.tags.size @@ -113,8 +112,8 @@ class FeatureDictionary internal constructor( )) if (numberOfMatchedAddTags != 0) return numberOfMatchedAddTags return (100 * b.matchScore - 100 * a.matchScore).toInt() - }}) - return foundFeatures + } + }) } //endregion //region Query by term @@ -130,46 +129,34 @@ class FeatureDictionary internal constructor( isSuggestion: Boolean?, limit: Int, locales: List - ): List { + ): MutableList { val canonicalSearch = StringUtils.canonicalize(search) val sortNames = Comparator { a: Feature, b: Feature -> // 1. exact matches first val exactMatchOrder = - ((if (CollectionUtils.find( - b.names - ) { n: String? -> n == search } != null + ((if (b.names.find { n: String? -> n == search } != null ) 1 else 0) - - if (CollectionUtils.find( - a.names - ) { n: String? -> n == search } != null + - if (a.names.find { n: String? -> n == search } != null ) 1 else 0) if (exactMatchOrder != 0) return@Comparator exactMatchOrder // 2. exact matches case and diacritics insensitive first val cExactMatchOrder = - ((if (CollectionUtils.find( - b.canonicalNames - ) { n: String? -> n == canonicalSearch } != null + ((if (b.canonicalNames.find { n: String? -> n == canonicalSearch } != null ) 1 else 0) - - if (CollectionUtils.find( - a.canonicalNames - ) { n: String? -> n == canonicalSearch } != null + - if (a.canonicalNames.find { n: String? -> n == canonicalSearch } != null ) 1 else 0) if (cExactMatchOrder != 0) return@Comparator cExactMatchOrder // 3. starts-with matches in string first val startsWithOrder = - ((if (CollectionUtils.find( - b.canonicalNames - ) { n: String? -> + ((if (b.canonicalNames.find { n: String? -> n!!.startsWith( canonicalSearch ) } != null ) 1 else 0) - - if (CollectionUtils.find( - a.canonicalNames - ) { n: String? -> + - if (a.canonicalNames.find { n: String? -> n!!.startsWith( canonicalSearch ) @@ -185,18 +172,16 @@ class FeatureDictionary internal constructor( val result: MutableList = ArrayList() if (isSuggestion == null || !isSuggestion) { // a. matches with presets first - val foundFeaturesByName = getNamesIndex(locales).getAll(canonicalSearch) - CollectionUtils.removeIf( - foundFeaturesByName - ) { feature: Feature -> + val foundFeaturesByName: MutableList = + getNamesIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() + foundFeaturesByName.removeIf { feature: Feature -> !isFeatureMatchingParameters( feature, geometry, countryCode ) } - foundFeaturesByName.sortedWith(sortNames) - result.addAll(foundFeaturesByName) + result.addAll(foundFeaturesByName.sortedWith(sortNames)) // if limit is reached, can return earlier (performance improvement) if (limit > 0 && result.size >= limit) return result.subList( @@ -207,18 +192,16 @@ class FeatureDictionary internal constructor( if (isSuggestion == null || isSuggestion) { // b. matches with brand names second val countryCodes = dissectCountryCode(countryCode) - val foundBrandFeatures = getBrandNamesIndex(countryCodes).getAll(canonicalSearch) - CollectionUtils.removeIf( - foundBrandFeatures - ) { feature: Feature -> + val foundBrandFeatures = getBrandNamesIndex(countryCodes)?.getAll(canonicalSearch).orEmpty().toMutableList() + foundBrandFeatures.removeIf{ feature: Feature -> !isFeatureMatchingParameters( feature, geometry, countryCode ) } - foundBrandFeatures.sortedWith(sortNames) - result.addAll(foundBrandFeatures) + + result.addAll(foundBrandFeatures.sortedWith(sortNames)) // if limit is reached, can return earlier (performance improvement) if (limit > 0 && result.size >= limit) return result.subList( @@ -228,10 +211,8 @@ class FeatureDictionary internal constructor( } if (isSuggestion == null || !isSuggestion) { // c. matches with terms third - val foundFeaturesByTerm = getTermsIndex(locales).getAll(canonicalSearch) - CollectionUtils.removeIf( - foundFeaturesByTerm - ) { feature: Feature -> + val foundFeaturesByTerm = getTermsIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() + foundFeaturesByTerm.removeIf { feature: Feature -> !isFeatureMatchingParameters( feature, geometry, @@ -240,16 +221,14 @@ class FeatureDictionary internal constructor( } if (foundFeaturesByTerm.isNotEmpty()) { val alreadyFoundFeatures: Set = HashSet(result) - CollectionUtils.removeIf( - foundFeaturesByTerm - ) { feature: Feature -> + foundFeaturesByTerm.removeIf { feature: Feature -> alreadyFoundFeatures.contains( feature ) } + } - foundFeaturesByTerm.sortedWith { a: Feature, b: Feature -> (100 * b.matchScore - 100 * a.matchScore).toInt() } - result.addAll(foundFeaturesByTerm) + result.addAll(foundFeaturesByTerm.sortedWith { a: Feature, b: Feature -> (100 * b.matchScore - 100 * a.matchScore).toInt() }) // if limit is reached, can return earlier (performance improvement) if (limit > 0 && result.size >= limit) return result.subList( @@ -259,10 +238,8 @@ class FeatureDictionary internal constructor( } if (isSuggestion == null || !isSuggestion) { // d. matches with tag values fourth - val foundFeaturesByTagValue = getTagValuesIndex(locales).getAll(canonicalSearch) - CollectionUtils.removeIf( - foundFeaturesByTagValue - ) { feature: Feature -> + val foundFeaturesByTagValue = getTagValuesIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() + foundFeaturesByTagValue.removeIf { feature: Feature -> !isFeatureMatchingParameters( feature, geometry, @@ -271,398 +248,366 @@ class FeatureDictionary internal constructor( } if (foundFeaturesByTagValue.isNotEmpty()) { val alreadyFoundFeatures: Set = HashSet(result) - CollectionUtils.removeIf( - foundFeaturesByTagValue - ) { feature: Feature -> + foundFeaturesByTagValue.removeIf { feature: Feature -> alreadyFoundFeatures.contains( feature ) } + result.addAll(foundFeaturesByTagValue) } - result.addAll(foundFeaturesByTagValue) } - return result.subList(0, min(limit.toDouble(), result.size.toDouble()).toInt()) + return result.subList(0, min(limit.toDouble(), result.size.toDouble()).toInt()) + } - //endregion - //region Lazily get or create Indexes - /** lazily get or create tags index for given locale(s) */ - private fun getTagsIndex(locales: List): FeatureTagsIndex { - return CollectionUtils.synchronizedGetOrCreate( - tagsIndexes, locales - ) { locales -> - createTagsIndex( - locales - ) + //endregion + //region Lazily get or create Indexes + /** lazily get or create tags index for given locale(s) */ + private fun getTagsIndex(locales: List): FeatureTagsIndex? { + return CollectionUtils.synchronizedGetOrCreate(tagsIndexes.toMutableMap(), locales, ::createTagsIndex) } - } - private fun createTagsIndex(locales: List): FeatureTagsIndex { - return FeatureTagsIndex(featureCollection.getAll(locales)) - } + private fun createTagsIndex(locales: List): FeatureTagsIndex { + return FeatureTagsIndex(featureCollection.getAll(locales)) + } - /** lazily get or create names index for given locale(s) */ - private fun getNamesIndex(locales: List): FeatureTermIndex { - return CollectionUtils.synchronizedGetOrCreate( - namesIndexes, locales - ) { locales -> - createNamesIndex( - locales - ) + /** lazily get or create names index for given locale(s) */ + private fun getNamesIndex(locales: List): FeatureTermIndex? { + return CollectionUtils.synchronizedGetOrCreate(namesIndexes.toMutableMap(), locales, ::createNamesIndex) } - } - private fun createNamesIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature? -> - if (feature != null) { + private fun createNamesIndex(locales: List): FeatureTermIndex { + val features = featureCollection.getAll(locales) + return FeatureTermIndex(features, FeatureTermIndex.Selector { feature: Feature -> if (!feature.isSearchable) return@Selector emptyList() - } - val names: List = feature?.canonicalNames ?: emptyList() - val result = ArrayList(names) - for (name in names) { - if (name.contains(" ")) { - result.addAll( - name.replace("[()]".toRegex(), "").split(" ".toRegex()).dropLastWhile { it.isEmpty() } - .toTypedArray().toList()) + val names: List = feature.canonicalNames + val result = ArrayList(names) + try { + for (name in names) { + if (name.contains(" ")) { + result.addAll( + name.replace("[()]", "").split(" ") + ) + } + } + } + catch (e: Exception) { + println(e) } - } - result - }) - } - /** lazily get or create terms index for given locale(s) */ - private fun getTermsIndex(locales: List): FeatureTermIndex { - return CollectionUtils.synchronizedGetOrCreate( - termsIndexes, locales - ) { locales -> - createTermsIndex( - locales - ) + result + }) } - } - private fun createTermsIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature? -> - if (feature != null) { + /** lazily get or create terms index for given locale(s) */ + private fun getTermsIndex(locales: List): FeatureTermIndex? { + return CollectionUtils.synchronizedGetOrCreate(termsIndexes.toMutableMap(), locales, ::createTermsIndex) + } + + private fun createTermsIndex(locales: List): FeatureTermIndex { + return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature -> if (!feature.isSearchable) return@Selector emptyList() - } - feature?.canonicalTerms.orEmpty() - }) - } + feature.canonicalTerms.orEmpty() + }) + } - /** lazily get or create tag values index */ - private fun getTagValuesIndex(locales: List): FeatureTermIndex { - return CollectionUtils.synchronizedGetOrCreate( - tagValuesIndexes, locales - ) { locales -> - createTagValuesIndex( - locales + /** lazily get or create tag values index */ + private fun getTagValuesIndex(locales: List): FeatureTermIndex? { + return CollectionUtils.synchronizedGetOrCreate( + tagValuesIndexes.toMutableMap(), + locales, + ::createTagValuesIndex ) } - } - private fun createTagValuesIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature? -> - if (feature != null) { + private fun createTagValuesIndex(locales: List): FeatureTermIndex { + return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature -> if (!feature.isSearchable) return@Selector emptyList() - } - val result: ArrayList? = - feature?.tags?.let { ArrayList(it.size) } - if (feature != null) { + + val result: ArrayList = ArrayList(feature.tags.size) for (tagValue in feature.tags.values) { - if (tagValue != "*") result?.add(tagValue) + if (tagValue != "*") result.add(tagValue) } - } - return@Selector result!! - }) - } + return@Selector result + }) + } - /** lazily get or create brand names index for country */ - private fun getBrandNamesIndex(countryCodes: List): FeatureTermIndex { - return CollectionUtils.synchronizedGetOrCreate( - brandNamesIndexes, countryCodes - ) { countryCodes -> - createBrandNamesIndex( - countryCodes + /** lazily get or create brand names index for country */ + private fun getBrandNamesIndex(countryCodes: List): FeatureTermIndex? { + return CollectionUtils.synchronizedGetOrCreate( + brandNamesIndexes.toMutableMap(), + countryCodes, + ::createBrandNamesIndex ) } - } - private fun createBrandNamesIndex(countryCodes: List): FeatureTermIndex { - return if (brandFeatureCollection == null) { - FeatureTermIndex(emptyList(), null) - } else FeatureTermIndex( - brandFeatureCollection.getAll(countryCodes) - ) { - if (it?.isSearchable == true) emptyList() else it?.canonicalNames.orEmpty() + private fun createBrandNamesIndex(countryCodes: List): FeatureTermIndex { + return if (brandFeatureCollection == null) { + FeatureTermIndex(emptyList(), null) + } else FeatureTermIndex( + brandFeatureCollection.getAll(countryCodes) + ) { + if (!it.isSearchable) emptyList() else it.canonicalNames + } } - } - /** lazily get or create tags index for the given countries */ - private fun getBrandTagsIndex(countryCodes: List): FeatureTagsIndex { - return CollectionUtils.synchronizedGetOrCreate( - brandTagsIndexes, countryCodes - ) { countryCodes -> - createBrandTagsIndex( - countryCodes + /** lazily get or create tags index for the given countries */ + private fun getBrandTagsIndex(countryCodes: List): FeatureTagsIndex? { + return CollectionUtils.synchronizedGetOrCreate( + brandTagsIndexes.toMutableMap(), + countryCodes, + ::createBrandTagsIndex ) } - } - private fun createBrandTagsIndex(countryCodes: List): FeatureTagsIndex { - return if (brandFeatureCollection == null) { - FeatureTagsIndex(emptyList()) - } else FeatureTagsIndex(brandFeatureCollection.getAll(countryCodes)) - } + private fun createBrandTagsIndex(countryCodes: List): FeatureTagsIndex { + return if (brandFeatureCollection == null) { + FeatureTagsIndex(emptyList()) + } else FeatureTagsIndex(brandFeatureCollection.getAll(countryCodes)) + } - //endregion - //region Query builders - inner class QueryByIdBuilder(private val id: String) { - private var locale: List = listOf(default) - private var countryCode: String? = null - - private operator fun get(id: String, locales: List, countryCode: String): Feature? { - val feature = featureCollection[id, locales] - if (feature != null) return feature - val countryCodes = dissectCountryCode(countryCode) - brandFeatureCollection?.let { - return it[id, countryCodes] + //endregion + //region Query builders + inner class QueryByIdBuilder(private val id: String) { + private var locale: List = listOf(default) + private var countryCode: String? = null + + private operator fun get(id: String, locales: List, countryCode: String?): Feature? { + return this@FeatureDictionary[id, locales, countryCode] } - throw NullPointerException("brandFeatureCollection is null") - } - /** - * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * - * - * `null` means to include unlocalized results. - * - * - * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, - * i.e. unlocalized results are included by default. - */ - fun forLocale(vararg locales: Locale?): QueryByIdBuilder { - this.locale = locales.toList() - return this - } + /** + * + *Sets the locale(s) in which to present the results. + * + * You can specify several locales in + * a row to each fall back to if a translation does not exist in the locale before that. + * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you + * wanted results preferredly in Catalan, but Spanish is also fine. + * + * + * `null` means to include unlocalized results. + * + * + * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, + * i.e. unlocalized results are included by default. + */ + fun forLocale(vararg locales: Locale?): QueryByIdBuilder { + this.locale = locales.toList() + return this + } - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByIdBuilder { - this.countryCode = countryCode - return this - } + /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the + * country/state the element is in. If not specified, will only return matches that are not + * county-specific. */ + fun inCountry(countryCode: String?): QueryByIdBuilder { + this.countryCode = countryCode + return this + } - /** Returns the feature associated with the given id or `null` if it does not - * exist */ - fun get(): Feature? { - return countryCode?.let { this[id, locale, it] } + /** Returns the feature associated with the given id or `null` if it does not + * exist */ + fun get(): Feature? { + return this[id, locale, countryCode] + } } - } - inner class QueryByTagBuilder (private val tags: Map) { - private var geometryType: GeometryType? = null - private var locale: List = listOf(default) - private var suggestion: Boolean? = null - private var countryCode: String? = null + inner class QueryByTagBuilder(private val tags: Map) { + private var geometryType: GeometryType? = null + private var locale: List = listOf(default) + private var suggestion: Boolean? = null + private var countryCode: String? = null - /** Sets for which geometry type to look. If not set or `null`, any will match. */ - fun forGeometry(geometryType: GeometryType?): QueryByTagBuilder { - this.geometryType = geometryType - return this - } + /** Sets for which geometry type to look. If not set or `null`, any will match. */ + fun forGeometry(geometryType: GeometryType?): QueryByTagBuilder { + this.geometryType = geometryType + return this + } - /** - * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * - * - * `null` means to include unlocalized results. - * - * - * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, - * i.e. unlocalized results are included by default. - */ - fun forLocale(vararg locales: Locale?): QueryByTagBuilder { - this.locale = locales.toList() - return this - } + /** + * + *Sets the locale(s) in which to present the results. + * + * You can specify several locales in + * a row to each fall back to if a translation does not exist in the locale before that. + * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you + * wanted results preferredly in Catalan, but Spanish is also fine. + * + * + * `null` means to include unlocalized results. + * + * + * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, + * i.e. unlocalized results are included by default. + */ + fun forLocale(vararg locales: Locale?): QueryByTagBuilder { + this.locale = locales.toList() + return this + } - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByTagBuilder { - this.countryCode = countryCode - return this - } + /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the + * country/state the element is in. If not specified, will only return matches that are not + * county-specific. */ + fun inCountry(countryCode: String?): QueryByTagBuilder { + this.countryCode = countryCode + return this + } - /** Set whether to only include suggestions (=true) or to not include suggestions (=false). - * Suggestions are brands, like 7-Eleven. */ - fun isSuggestion(suggestion: Boolean?): QueryByTagBuilder { - this.suggestion = suggestion - return this - } + /** Set whether to only include suggestions (=true) or to not include suggestions (=false). + * Suggestions are brands, like 7-Eleven. */ + fun isSuggestion(suggestion: Boolean?): QueryByTagBuilder { + this.suggestion = suggestion + return this + } - /** Returns a list of dictionary entries that match or an empty list if nothing is - * found.

In rare cases, a set of tags may match multiple primary features, such as for - * tag combinations like `shop=deli` + `amenity=cafe`, so, this is why - * it is a list. */ - fun find(): List { - return get(tags, geometryType, countryCode, suggestion, locale) + /** Returns a list of dictionary entries that match or an empty list if nothing is + * found.

In rare cases, a set of tags may match multiple primary features, such as for + * tag combinations like `shop=deli` + `amenity=cafe`, so, this is why + * it is a list. */ + fun find(): List { + return get(tags, geometryType, countryCode, suggestion, locale) + } } - } - inner class QueryByTermBuilder(private val term: String) { - private var geometryType: GeometryType? = null - private var locale: List = listOf(default) - private var suggestion: Boolean? = null - private var limit = 50 - private var countryCode: String? = null - - /** Sets for which geometry type to look. If not set or `null`, any will match. */ - fun forGeometry(geometryType: GeometryType?): QueryByTermBuilder { - this.geometryType = geometryType - return this - } + inner class QueryByTermBuilder(private val term: String) { + private var geometryType: GeometryType? = null + private var locale: List = listOf(default) + private var suggestion: Boolean? = null + private var limit = 50 + private var countryCode: String? = null - /** - * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * - * - * `null` means to include unlocalized results. - * - * - * If nothing is specified, it defaults to `[Locale.getDefault()]`, i.e. - * unlocalized results are excluded by default. - */ - fun forLocale(vararg locales: Locale?): QueryByTermBuilder { - this.locale = locales.toList() - return this - } + /** Sets for which geometry type to look. If not set or `null`, any will match. */ + fun forGeometry(geometryType: GeometryType?): QueryByTermBuilder { + this.geometryType = geometryType + return this + } - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByTermBuilder { - this.countryCode = countryCode - return this - } + /** + * + *Sets the locale(s) in which to present the results. + * + * You can specify several locales in + * a row to each fall back to if a translation does not exist in the locale before that. + * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you + * wanted results preferredly in Catalan, but Spanish is also fine. + * + * + * `null` means to include unlocalized results. + * + * + * If nothing is specified, it defaults to `[Locale.getDefault()]`, i.e. + * unlocalized results are excluded by default. + */ + fun forLocale(vararg locales: Locale?): QueryByTermBuilder { + this.locale = locales.toList() + return this + } - /** Set whether to only include suggestions (=true) or to not include suggestions (=false). - * Suggestions are brands, like 7-Eleven. */ - fun isSuggestion(suggestion: Boolean?): QueryByTermBuilder { - this.suggestion = suggestion - return this - } + /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the + * country/state the element is in. If not specified, will only return matches that are not + * county-specific. */ + fun inCountry(countryCode: String?): QueryByTermBuilder { + this.countryCode = countryCode + return this + } - /** limit how many results to return at most. Default is 50, -1 for unlimited. */ - fun limit(limit: Int): QueryByTermBuilder { - this.limit = limit - return this - } + /** Set whether to only include suggestions (=true) or to not include suggestions (=false). + * Suggestions are brands, like 7-Eleven. */ + fun isSuggestion(suggestion: Boolean?): QueryByTermBuilder { + this.suggestion = suggestion + return this + } - /** Returns a list of dictionary entries that match or an empty list if nothing is - * found.

- * Results are sorted mainly in this order: Matches with names, with brand names, then - * matches with terms (keywords). */ - fun find(): List { - return get(term, geometryType, countryCode, suggestion, limit, locale) - } - } //endregion - - companion object { - private val VALID_COUNTRY_CODE_REGEX = Regex("([A-Z]{2})(?:-([A-Z0-9]{1,3}))?") - /** Create a new FeatureDictionary which gets its data from the given directory. Optionally, - * a path to brand presets can be specified. */ - /** Create a new FeatureDictionary which gets its data from the given directory. */ - @JvmOverloads - fun create(presetsBasePath: String, brandPresetsBasePath: String? = null): FeatureDictionary { - val featureCollection: LocalizedFeatureCollection = - IDLocalizedFeatureCollection(FileSystemAccess(presetsBasePath)) - val brandsFeatureCollection: PerCountryFeatureCollection? = - if (brandPresetsBasePath != null) IDBrandPresetsFeatureCollection( - FileSystemAccess(brandPresetsBasePath) - ) else null - return FeatureDictionary(featureCollection, brandsFeatureCollection) - } + /** limit how many results to return at most. Default is 50, -1 for unlimited. */ + fun limit(limit: Int): QueryByTermBuilder { + this.limit = limit + return this + } - //endregion - //region Utility / Filter functions - private fun getParentCategoryIds(id: String): Collection { - var id: String? = id - val result: MutableList = ArrayList() - do { - id = getParentId(id) - if (id != null) result.add(id) - } while (id != null) - return result - } + /** Returns a list of dictionary entries that match or an empty list if nothing is + * found.

+ * Results are sorted mainly in this order: Matches with names, with brand names, then + * matches with terms (keywords). */ + fun find(): List { + return get(term, geometryType, countryCode, suggestion, limit, locale) + } + } //endregion + + companion object { + private val VALID_COUNTRY_CODE_REGEX = Regex("([A-Z]{2})(?:-([A-Z0-9]{1,3}))?") + /** Create a new FeatureDictionary which gets its data from the given directory. Optionally, + * a path to brand presets can be specified. */ + /** Create a new FeatureDictionary which gets its data from the given directory. */ + @JvmOverloads + fun create(presetsBasePath: String, brandPresetsBasePath: String? = null): FeatureDictionary { + val featureCollection: LocalizedFeatureCollection = + IDLocalizedFeatureCollection(FileSystemAccess(presetsBasePath)) + val brandsFeatureCollection: PerCountryFeatureCollection? = + if (brandPresetsBasePath != null) IDBrandPresetsFeatureCollection( + FileSystemAccess(brandPresetsBasePath) + ) else null + return FeatureDictionary(featureCollection, brandsFeatureCollection) + } - private fun getParentId(id: String?): String? { - val lastSlashIndex = id!!.lastIndexOf("/") - return if (lastSlashIndex == -1) null else id.substring(0, lastSlashIndex) - } + //endregion + //region Utility / Filter functions + private fun getParentCategoryIds(id: String): Collection { + var id: String? = id + val result: MutableList = ArrayList() + do { + id = getParentId(id) + if (id != null) result.add(id) + } while (id != null) + return result + } - private fun isFeatureMatchingParameters( - feature: Feature, - geometry: GeometryType?, - countryCode: String? - ): Boolean { - if (geometry != null && !feature.geometry?.contains(geometry)!!) return false - val include: List = feature.includeCountryCodes - val exclude: List = feature.excludeCountryCodes - if (include.isNotEmpty() || exclude.isNotEmpty()) { - if (countryCode == null) return false - if (include.isNotEmpty() && !matchesAnyCountryCode(countryCode, include)) return false - if (matchesAnyCountryCode(countryCode, exclude)) return false - } - return true - } + private fun getParentId(id: String?): String? { + val lastSlashIndex = id!!.lastIndexOf("/") + return if (lastSlashIndex == -1) null else id.substring(0, lastSlashIndex) + } + + private fun isFeatureMatchingParameters( + feature: Feature, + geometry: GeometryType?, + countryCode: String? + ): Boolean { + if (geometry != null && !feature.geometry?.contains(geometry)!!) return false + val include: List = feature.includeCountryCodes + val exclude: List = feature.excludeCountryCodes + if (include.isNotEmpty() || exclude.isNotEmpty()) { + if (countryCode == null) return false + if (include.isNotEmpty() && !matchesAnyCountryCode(countryCode, include)) return false + if (matchesAnyCountryCode(countryCode, exclude)) return false + } + return true + } + + private fun dissectCountryCode(countryCode: String?): List { + val result: MutableList = ArrayList() + // add default / international + result.add(null) + countryCode?.let { + val matcher = VALID_COUNTRY_CODE_REGEX.find(it) + if (matcher?.groups?.isNotEmpty() == true) { + // add ISO 3166-1 alpha2 (e.g. "US") + if (matcher.groups[2] != null) { + // add ISO 3166-2 (e.g. "US-NY") + result.add(countryCode) + } + result.add(matcher.groups[1]?.value) - private fun dissectCountryCode(countryCode: String?): List { - val result: MutableList = ArrayList() - // add default / international - result.add(null) - countryCode?.let { - val matcher = VALID_COUNTRY_CODE_REGEX.find(it) - if (matcher?.groups?.isNotEmpty() == true) { - // add ISO 3166-1 alpha2 (e.g. "US") - result.add(matcher.groups[1].toString()) - if (matcher.groups.size == 2 && matcher.groups[2] != null) { - // add ISO 3166-2 (e.g. "US-NY") - result.add(it) } } + return result } - return result - } - private fun matchesAnyCountryCode(showOnly: String, featureCountryCodes: List): Boolean { - for (featureCountryCode in featureCountryCodes) { - if (matchesCountryCode(showOnly, featureCountryCode)) return true + private fun matchesAnyCountryCode(showOnly: String, featureCountryCodes: List): Boolean { + return featureCountryCodes.any { matchesCountryCode(showOnly, it) } } - return false - } - private fun matchesCountryCode(showOnly: String, featureCountryCode: String): Boolean { - return showOnly == featureCountryCode || showOnly.substring(0, 2) == featureCountryCode + private fun matchesCountryCode(showOnly: String, featureCountryCode: String): Boolean { + return showOnly == featureCountryCode || showOnly.substring(0, 2) == featureCountryCode + } } } -} + + diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt b/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt index b1d6c14..bed76b3 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt @@ -3,26 +3,22 @@ package de.westnordost.osmfeatures /** Index that makes finding Features whose name/term/... starts with a given string very efficient. * * Based on the StartsWithStringTree data structure, see that class. */ -class FeatureTermIndex(features: Collection?, selector: Selector?) { +class FeatureTermIndex(features: Collection, selector: Selector?) { private val featureMap: MutableMap> = HashMap() private val tree: StartsWithStringTree init { - if (features != null) { for (feature in features) { - val strings: Collection = selector!!.getStrings(feature) + val strings: Collection = selector?.getStrings(feature) ?: emptyList() for (string in strings) { if (!featureMap.containsKey(string)) featureMap[string] = ArrayList(1) - if (feature != null) { - featureMap[string]!!.add(feature) - } + featureMap[string]?.add(feature) } } - } tree = StartsWithStringTree(featureMap.keys) } - fun getAll(startsWith: String?): List { + fun getAll(startsWith: String): List { val result: MutableSet = HashSet() for (string in tree.getAll(startsWith)) { val fs: List? = featureMap[string] @@ -32,6 +28,6 @@ class FeatureTermIndex(features: Collection?, selector: Selector?) { } fun interface Selector { - fun getStrings(feature: Feature?): List + fun getStrings(feature: Feature): List } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt b/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt index 71ea8fc..f210d70 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import org.json.JSONException - /** Non-localized feature collection sourcing from (NSI) iD presets defined in JSON. * * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that @@ -19,20 +17,20 @@ class IDBrandPresetsFeatureCollection internal constructor(private val fileAcces override fun getAll(countryCodes: List): Collection { val result: MutableMap = HashMap() for (cc in countryCodes) { - result.putAll(getOrLoadPerCountryFeatures(cc)) + getOrLoadPerCountryFeatures(cc)?.let { result.putAll(it) } } return result.values } override fun get(id: String, countryCodes: List): Feature? { for (countryCode in countryCodes) { - val result = getOrLoadPerCountryFeatures(countryCode)[id] + val result = getOrLoadPerCountryFeatures(countryCode)?.get(id) if (result != null) return result } return null } - private fun getOrLoadPerCountryFeatures(countryCode: String?): LinkedHashMap { + private fun getOrLoadPerCountryFeatures(countryCode: String?): java.util.LinkedHashMap? { return CollectionUtils.synchronizedGetOrCreate( featuresByIdByCountryCode, countryCode ) { countryCode: String? -> diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt index e840b1d..558cde3 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt @@ -7,8 +7,8 @@ package de.westnordost.osmfeatures * located in the same directory named like e.g. de.json, pt-BR.json etc. */ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : LocalizedFeatureCollection { private val featuresById: LinkedHashMap - private val localizedFeaturesList: Map> = HashMap() - private val localizedFeatures: Map, LinkedHashMap> = HashMap() + private val localizedFeaturesList: MutableMap> = HashMap() + private val localizedFeatures: MutableMap?, LinkedHashMap> = HashMap() init { val features = loadFeatures() @@ -25,17 +25,11 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : } catch (e: Exception) { - throw RuntimeException(e); + throw RuntimeException(e) } } - private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap { - return CollectionUtils.synchronizedGetOrCreate( - localizedFeatures, locales - ) { locales -> - loadLocalizedFeatures( - locales - ) - } + private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap? { + return CollectionUtils.synchronizedGetOrCreate(localizedFeatures, locales, ::loadLocalizedFeatures) } private fun loadLocalizedFeatures(locales: List): LinkedHashMap { @@ -45,7 +39,7 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : val locale = it.previous() if (locale != null) { for (localeComponent in getLocaleComponents(locale)) { - putAllFeatures(result, getOrLoadLocalizedFeaturesList(localeComponent)) + getOrLoadLocalizedFeaturesList(localeComponent)?.let { it1 -> putAllFeatures(result, it1) } } } else { putAllFeatures(result, featuresById.values) @@ -54,17 +48,17 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : return result } - private fun getOrLoadLocalizedFeaturesList(locale: Locale): List { + private fun getOrLoadLocalizedFeaturesList(locale: Locale): List? { return CollectionUtils.synchronizedGetOrCreate( localizedFeaturesList, locale - ) { locale: Locale -> + ) { locale: Locale? -> loadLocalizedFeaturesList( locale ) } } - private fun loadLocalizedFeaturesList(locale: Locale): List { + private fun loadLocalizedFeaturesList(locale: Locale?): List { val filename = getLocalizationFilename(locale) if (!fileAccess.exists(filename)) return emptyList() @@ -74,34 +68,34 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : } override fun getAll(locales: List): Collection { - return getOrLoadLocalizedFeatures(locales).values; + return getOrLoadLocalizedFeatures(locales)?.values ?: emptyList() } override operator fun get(id: String, locales: List): Feature? { - return getOrLoadLocalizedFeatures(locales)[id]; + return getOrLoadLocalizedFeatures(locales)?.get(id) } companion object { private const val FEATURES_FILE = "presets.json" - private fun getLocalizationFilename(locale: Locale): String { + private fun getLocalizationFilename(locale: Locale?): String { /* we only want language+country+script of the locale, not anything else. So we construct it anew here */ return Locale.Builder() - .setLanguage(locale.language) - .setRegion(locale.country) - .setScript(locale.script) + .setLanguage(locale?.language ?: "") + .setRegion(locale?.country ?: "") + .setScript(locale?.script ?: "") .build() - .toLanguageTag() + ".json" + .languageTag + ".json" } - private fun getLocaleComponents(locale: Locale): List { - val lang = locale.language - val country = locale.country - val script = locale.script + private fun getLocaleComponents(locale: Locale?): List { + val lang = locale?.language ?: "" + val country = locale?.country ?: "" + val script = locale?.script ?: "" val result: MutableList = ArrayList(4) result.add(Locale(lang)) if (country.isNotEmpty()) result.add(Locale.Builder().setLanguage(lang).setRegion(country).build()) - if (script!!.isNotEmpty()) result.add(Locale.Builder().setLanguage(lang).setScript(script).build()) + if (script.isNotEmpty()) result.add(Locale.Builder().setLanguage(lang).setScript(script).build()) if (country.isNotEmpty() && script.isNotEmpty()) result.add( Locale.Builder().setLanguage(lang).setRegion(country).setScript(script).build() ) diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt index 4d502ef..ff1467c 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt @@ -3,11 +3,9 @@ package de.westnordost.osmfeatures import de.westnordost.osmfeatures.JsonUtils.parseList import de.westnordost.osmfeatures.JsonUtils.parseStringMap import kotlinx.serialization.json.* -import okio.FileHandle import okio.Buffer import okio.Source import okio.buffer -import java.net.URL /** Parses this file * [...](https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json) @@ -38,20 +36,18 @@ class IDPresetsJsonParser { if (tags.isEmpty()) return null val geometry = parseList( - p["geometry"]?.jsonArray, - JsonUtils.Transformer { item -> GeometryType.valueOf(((item as JsonPrimitive).content).uppercase()) - }) + p["geometry"]?.jsonArray + ) { GeometryType.valueOf(((it as JsonPrimitive).content).uppercase()) + } val name = p["name"]?.jsonPrimitive?.content val icon = p["icon"]?.jsonPrimitive?.content val imageURL = p["imageURL"]?.jsonPrimitive?.content - val names = parseList(p["aliases"]?.jsonArray, - JsonUtils.Transformer { item -> item as String }).toMutableList() + val names = parseList(p["aliases"]?.jsonArray) { it.jsonPrimitive.content }.toMutableList() if(name != null) { names.add(0, name) } - val terms = parseList(p["terms"]?.jsonArray, - JsonUtils.Transformer { item: Any? -> item as String }) + val terms = parseList(p["terms"]?.jsonArray) { it.jsonPrimitive.content } val locationSet = p["locationSet"]?.jsonObject val includeCountryCodes: List? @@ -72,28 +68,29 @@ class IDPresetsJsonParser { val removeTags = p["removeTags"]?.let { parseStringMap(it.jsonObject)}?: addTags return BaseFeature( - id, - tags, - geometry, - icon, imageURL, - names, - terms, - includeCountryCodes, - excludeCountryCodes, - searchable, matchScore, isSuggestion, - addTags, - removeTags + id, + tags, + geometry, + icon, imageURL, + names, + terms, + includeCountryCodes, + excludeCountryCodes, + searchable, matchScore, isSuggestion, + addTags, + removeTags ) } companion object { private fun parseCountryCodes(jsonList: JsonArray?): List? { - val list = parseList(jsonList, - JsonUtils.Transformer { item: Any? -> item }) + if(jsonList?.any { it is JsonArray } == true) { + return null + } + val list = parseList(jsonList) { it.jsonPrimitive.content } val result: MutableList = ArrayList(list.size) for (item in list) { // for example a lat,lon pair to denote a location with radius. Not supported. - if (item !is String) return null val cc = item.uppercase().intern() // don't need this, 001 stands for "whole world" if (cc == "001") continue diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt index 6e91276..098b7cb 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt @@ -4,7 +4,6 @@ import de.westnordost.osmfeatures.JsonUtils.createFromSource import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive -import okio.FileHandle import okio.Source /** Parses a file from @@ -14,7 +13,7 @@ import okio.Source class IDPresetsTranslationJsonParser { fun parse( - source: Source, locale: Locale, baseFeatures: Map + source: Source, locale: Locale?, baseFeatures: Map ): List { val decodedObject = createFromSource(source) val languageKey: String = decodedObject.entries.iterator().next().key @@ -49,7 +48,7 @@ class IDPresetsTranslationJsonParser { return ArrayList(localizedFeatures.values) } - private fun parseFeature(feature: BaseFeature?, locale: Locale, localization: JsonObject): LocalizedFeature? { + private fun parseFeature(feature: BaseFeature?, locale: Locale?, localization: JsonObject): LocalizedFeature? { if (feature == null) return null val name = localization["name"]?.jsonPrimitive?.content diff --git a/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt b/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt index f2163f3..12b7209 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt @@ -1,17 +1,14 @@ package de.westnordost.osmfeatures -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.* import okio.Buffer import okio.Source import okio.buffer internal object JsonUtils { @JvmStatic - fun parseList(array: JsonArray?, t: Transformer): List { - return array?.mapNotNull { item -> t.apply(item) }.orEmpty() + fun parseList(array: JsonArray?, t: (JsonElement) -> T): List { + return array?.mapNotNull { item -> t(item) }.orEmpty() } @JvmStatic @@ -29,8 +26,4 @@ internal object JsonUtils { return Json.decodeFromString(sink.readUtf8()) } - - fun interface Transformer { - fun apply(item: Any?): T - } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/Locale.kt b/library/src/main/java/de/westnordost/osmfeatures/Locale.kt index 9eec4b9..58c4b7a 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/Locale.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/Locale.kt @@ -29,37 +29,6 @@ class Locale( @JvmStatic val default: Locale? = null - fun toTitleString(s: String): String { - var len: Int - if (s.length.also { len = it } == 0) { - return s - } - var idx = 0 - if (!s[idx].isLowerCase()) { - idx = 1 - while (idx < len) { - if (s[idx].isUpperCase()) { - break - } - idx++ - } - } - if (idx == len) { - return s - } - val buf = CharArray(len) - for (i in 0 until len) { - val c = s[i] - if (i == 0 && idx == 0) { - buf[i] = c.uppercaseChar() - } else if (i < idx) { - buf[i] = c - } else { - buf[i] = c.lowercaseChar() - } - } - return String(buf) - } } @@ -68,30 +37,22 @@ class Locale( val country : String get() = this.region.orEmpty() - private var languageTag : String? = null + val languageTag : String? by lazy { + LanguageTag.forLanguage(language, script, region).toString() + } constructor(lang: String) : this(lang,"", "") constructor(lang: String, region: String) : this(lang, region, "") - fun toLanguageTag(): String { - val lTag: String? = this.languageTag - if (lTag != null) { - return lTag - } - this.languageTag = LanguageTag.forLanguage(language, script, region).toString() - this.languageTag?.let{ return it} - throw NullPointerException("LanguageTag could not be parsed") - - } override fun equals(other: Any?): Boolean { if (other == null) { return false } if (other is Locale) { - return other.language == this.language && other.region == this.region && other.script == this.script + return other.languageTag == this.languageTag } return false diff --git a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt index 956d6e3..5226130 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt @@ -4,7 +4,7 @@ package de.westnordost.osmfeatures * I.e. the name and terms are specified in the given locale. */ class LocalizedFeature( private val p: BaseFeature, - override val locale: Locale, + override val locale: Locale?, override val names: List, override val terms: List ) : diff --git a/library/src/main/java/de/westnordost/osmfeatures/StartsWithStringTree.kt b/library/src/main/java/de/westnordost/osmfeatures/StartsWithStringTree.kt index 09ff571..23d71d3 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/StartsWithStringTree.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/StartsWithStringTree.kt @@ -1,122 +1,105 @@ -package de.westnordost.osmfeatures; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +package de.westnordost.osmfeatures /** Index that makes finding strings that start with characters very efficient. - * It sorts the strings into a tree structure with configurable depth. + * It sorts the strings into a tree structure with configurable depth. * - * It is threadsafe because it is immutable. + * It is threadsafe because it is immutable. * - * For the strings ["f", "foobar", "foo", "fu", "funicular"], the tree may internally look f.e. - * like this: - *
- *  f ->
- *    [ "f" ]
- *    o ->
- *      o ->
- *        [ "foobar", "foo", ...]
- *    u ->
- *      [ "fu", "funicular", ... ]
- *  
*/ -class StartsWithStringTree -{ - private final Node root; - - public StartsWithStringTree(Collection strings) - { - this(strings, 16, 16); - } + * For the strings ["f", "foobar", "foo", "fu", "funicular"], the tree may internally look f.e. + * like this: + *
+ * f ->
+ * [ "f" ]
+ * o ->
+ * o ->
+ * [ "foobar", "foo", ...]
+ * u ->
+ * [ "fu", "funicular", ... ]
+
*/ +internal class StartsWithStringTree +@JvmOverloads constructor(strings: Collection, maxDepth: Int = 16, minContainerSize: Int = 16) { + private val root: Node /** Create this index with the given strings. * - * The generated tree will have a max depth of maxDepth and another depth is not added to the - * tree if there are less than minContainerSize strings in one tree node. + * The generated tree will have a max depth of maxDepth and another depth is not added to the + * tree if there are less than minContainerSize strings in one tree node. */ - public StartsWithStringTree(Collection strings, int maxDepth, int minContainerSize) - { - if (maxDepth < 0) maxDepth = 0; - if (minContainerSize < 1) minContainerSize = 1; - root = buildTree(strings, 0, maxDepth, minContainerSize); + init { + var maxDepth = maxDepth + var minContainerSize = minContainerSize + if (maxDepth < 0) maxDepth = 0 + if (minContainerSize < 1) minContainerSize = 1 + root = buildTree(strings, 0, maxDepth, minContainerSize) } - /** Get all strings which start with the given string */ - public List getAll(String startsWith) - { - return root.getAll(startsWith, 0); + /** Get all strings which start with the given string */ + fun getAll(startsWith: String?): List { + return root.getAll(startsWith, 0) } - private static Node buildTree(Collection strings, int currentDepth, int maxDepth, int minContainerSize) - { - if (currentDepth == maxDepth || strings.size() < minContainerSize) - return new Node(null, strings); + private class Node(val children: Map?, val strings: Collection) { - Map> stringsByCharacter = getStringsByCharacter(strings, currentDepth); - HashMap children = new HashMap<>(stringsByCharacter.size()); + /** Get all strings that start with the given string */ + fun getAll(startsWith: String?, offset: Int): List { + if (startsWith != null) { + if (startsWith.isEmpty()) return emptyList() + } - for (Map.Entry> entry : stringsByCharacter.entrySet()) { - Character c = entry.getKey(); - if (c == null) continue; - Node child = buildTree(entry.getValue(), currentDepth + 1, maxDepth, minContainerSize); - children.put(c, child); + val result: MutableList = ArrayList() + if (children != null) { + for ((key, value) in children) { + if (startsWith != null) { + if (startsWith.length <= offset || key == startsWith[offset]) { + result.addAll(value.getAll(startsWith, offset + 1)) + } + } + } + } + for (string in strings) { + if (startsWith?.let { string.startsWith(it) } == true) result.add(string) + } + return result } - Collection remainingStrings = stringsByCharacter.get(null); - if (children.isEmpty()) children = null; - return new Node(children, remainingStrings); } - /** returns the given strings grouped by their nth character. Strings whose length is shorter - * or equal to nth go into the "null" group. */ - private static Map> getStringsByCharacter(Collection strings, int nth) - { - HashMap> result = new HashMap<>(); - for (String string : strings) { - Character c = string.length() > nth ? string.charAt(nth) : null; - if (!result.containsKey(c)) result.put(c, new ArrayList<>()); - result.get(c).add(string); - } - return result; - } + companion object { + private fun buildTree( + strings: Collection, + currentDepth: Int, + maxDepth: Int, + minContainerSize: Int + ): Node { + if (currentDepth == maxDepth || strings.size < minContainerSize) return Node(null, strings) - private static class Node - { - final Map children; - final Collection strings; + val stringsByCharacter = getStringsByCharacter(strings, currentDepth) + var children: HashMap? = HashMap(stringsByCharacter.size) - private Node(Map children, Collection strings) - { - this.children = children; - this.strings = strings; + for ((key, value) in stringsByCharacter) { + val c = key ?: continue + val child = buildTree(value, currentDepth + 1, maxDepth, minContainerSize) + children?.set(c, child) + } + val remainingStrings: Collection = stringsByCharacter[null].orEmpty() + if (children != null) { + if (children.isEmpty()) children = null + } + return Node(children, remainingStrings) } - /** Get all strings that start with the given string */ - private List getAll(String startsWith, int offset) - { - if (startsWith.isEmpty()) return Collections.emptyList(); - - List result = new ArrayList<>(); - if (children != null) - { - for (Map.Entry charToNode : children.entrySet()) - { - if (startsWith.length() <= offset || charToNode.getKey() == startsWith.charAt(offset)) - { - result.addAll(charToNode.getValue().getAll(startsWith, offset + 1)); - } - } - } - if (strings != null) - { - for (String string : strings) - { - if (string.startsWith(startsWith)) result.add(string); - } + /** returns the given strings grouped by their nth character. Strings whose length is shorter + * or equal to nth go into the "null" group. */ + private fun getStringsByCharacter( + strings: Collection, + nth: Int + ): Map> { + val result = HashMap>() + for (string in strings) { + val c = if (string.length > nth) string[nth] else null + if (!result.containsKey(c)) result[c] = ArrayList() + result[c]?.add(string) } - return result; + return result } } -} +} \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt b/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt index bc06d26..f85430e 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt @@ -1,23 +1,20 @@ package de.westnordost.osmfeatures -import java.util.regex.Pattern - -import kotlin.text.Regex +import doist.x.normalize.Form +import doist.x.normalize.normalize class StringUtils { - private val FIND_DIACRITICS = Pattern.compile("\\p{InCombiningDiacriticalMarks}+") - - companion object { + val FIND_DIACRITICS = "\\p{InCombiningDiacriticalMarks}+".toRegex() + @JvmStatic fun canonicalize(str: String): String { return stripDiacritics(str).lowercase() } private fun stripDiacritics(str: String): String { - val reg = Regex("\\p{InCombiningDiacriticalMarks}+") - return reg.replace("", str) + return FIND_DIACRITICS.replace(str.normalize(Form.NFD),"") } } diff --git a/library/src/test/java/de/westnordost/osmfeatures/CollectionUtilsTest.java b/library/src/test/java/de/westnordost/osmfeatures/CollectionUtilsTest.java index 2e7cd72..4eeec5c 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/CollectionUtilsTest.java +++ b/library/src/test/java/de/westnordost/osmfeatures/CollectionUtilsTest.java @@ -17,19 +17,6 @@ public class CollectionUtilsTest { - @Test public void removeIf() - { - List ints = new ArrayList<>(listOf(1,2,3,4,5,6,7,8,9,10)); - CollectionUtils.removeIf(ints, i -> i % 2 == 0); - assertEquals(listOf(1,3,5,7,9), ints); - } - - @Test public void find() - { - List strs = new ArrayList<>(listOf("one", "two", "three")); - assertEquals("two", CollectionUtils.find(strs, str -> str.equals("two"))); - assertNull(CollectionUtils.find(strs, str -> str.equals("four"))); - } @Test public void mapContainsEntry() { diff --git a/library/src/test/java/de/westnordost/osmfeatures/IDPresetsJsonParserTest.java b/library/src/test/java/de/westnordost/osmfeatures/IDPresetsJsonParserTest.java index b4b6c33..c26ddb7 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/IDPresetsJsonParserTest.java +++ b/library/src/test/java/de/westnordost/osmfeatures/IDPresetsJsonParserTest.java @@ -1,9 +1,10 @@ package de.westnordost.osmfeatures; +import okio.Okio; +import okio.Source; import org.junit.Test; import java.io.IOException; -import java.io.InputStream; import java.net.URL; import java.util.List; @@ -16,89 +17,88 @@ public class IDPresetsJsonParserTest { -// @Test public void load_features_only() -// { -// List features = parse("one_preset_full.json"); -// -// assertEquals(1, features.size()); -// Feature feature = features.get(0); -// assertEquals("some/id", feature.getId()); -// assertEquals(mapOf(tag("a","b"), tag("c","d")), feature.getTags()); -// assertEquals(listOf(GeometryType.POINT, GeometryType.VERTEX, GeometryType.LINE, GeometryType.AREA, GeometryType.RELATION), feature.getGeometry()); -// -// assertEquals(listOf("DE", "GB"), feature.getIncludeCountryCodes()); -// assertEquals(listOf("IT"), feature.getExcludeCountryCodes()); -// assertEquals("foo", feature.getName()); -// assertEquals("abc", feature.getIcon()); -// assertEquals("someurl", feature.getImageURL()); -// assertEquals(listOf("foo", "one","two"), feature.getNames()); -// assertEquals(listOf("1","2"), feature.getTerms()); -// assertEquals(0.5f, feature.getMatchScore(), 0.001f); -// assertFalse(feature.isSearchable()); -// assertEquals(mapOf(tag("e","f")), feature.getAddTags()); -// assertEquals(mapOf(tag("d","g")), feature.getRemoveTags()); -// } -// -// @Test public void load_features_only_defaults() -// { -// List features = parse("one_preset_min.json"); -// -// assertEquals(1, features.size()); -// Feature feature = features.get(0); -// -// assertEquals("some/id", feature.getId()); -// assertEquals(mapOf(tag("a","b"), tag("c","d")), feature.getTags()); -// assertEquals(listOf(GeometryType.POINT), feature.getGeometry()); -// -// assertTrue(feature.getIncludeCountryCodes().isEmpty()); -// assertTrue(feature.getExcludeCountryCodes().isEmpty()); -// assertEquals("", feature.getName()); -// assertEquals("",feature.getIcon()); -// assertEquals("",feature.getImageURL()); -// assertEquals(1, feature.getNames().size()); -// assertTrue(feature.getTerms().isEmpty()); -// assertEquals(1.0f, feature.getMatchScore(), 0.001f); -// assertTrue(feature.isSearchable()); -// assertEquals(feature.getAddTags(), feature.getTags()); -// assertEquals(feature.getAddTags(), feature.getRemoveTags()); -// } - -// @Test public void load_features_unsupported_location_set() -// { -// List features = parse("one_preset_unsupported_location_set.json"); -// assertEquals(2, features.size()); -// assertEquals("some/ok", features.get(0).getId()); -// assertEquals("another/ok", features.get(1).getId()); -// } - -// @Test public void load_features_no_wildcards() -// { -// List features = parse("one_preset_wildcard.json"); -// assertTrue(features.isEmpty()); -// } - -// @Test public void parse_some_real_data() throws IOException -// { -// URL url = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json"); -// -// List features = new IDPresetsJsonParser().parse(url.openStream()); -// // should not crash etc -// assertTrue(features.size() > 1000); -// } - -// private List parse(String file) -// { -// try -// { -// return new IDPresetsJsonParser().parse(getStream(file)); -// } catch (IOException e) -// { -// throw new RuntimeException(); -// } -// } - - private InputStream getStream(String file) + @Test public void load_features_only() { - return getClass().getClassLoader().getResourceAsStream(file); + List features = parse("one_preset_full.json"); + + assertEquals(1, features.size()); + Feature feature = features.get(0); + assertEquals("some/id", feature.getId()); + assertEquals(mapOf(tag("a","b"), tag("c","d")), feature.getTags()); + assertEquals(listOf(GeometryType.POINT, GeometryType.VERTEX, GeometryType.LINE, GeometryType.AREA, GeometryType.RELATION), feature.getGeometry()); + + assertEquals(listOf("DE", "GB"), feature.getIncludeCountryCodes()); + assertEquals(listOf("IT"), feature.getExcludeCountryCodes()); + assertEquals("foo", feature.getName()); + assertEquals("abc", feature.getIcon()); + assertEquals("someurl", feature.getImageURL()); + assertEquals(listOf("foo", "one","two"), feature.getNames()); + assertEquals(listOf("1","2"), feature.getTerms()); + assertEquals(0.5f, feature.getMatchScore(), 0.001f); + assertFalse(feature.isSearchable()); + assertEquals(mapOf(tag("e","f")), feature.getAddTags()); + assertEquals(mapOf(tag("d","g")), feature.getRemoveTags()); + } + + @Test public void load_features_only_defaults() + { + List features = parse("one_preset_min.json"); + + assertEquals(1, features.size()); + Feature feature = features.get(0); + + assertEquals("some/id", feature.getId()); + assertEquals(mapOf(tag("a","b"), tag("c","d")), feature.getTags()); + assertEquals(listOf(GeometryType.POINT), feature.getGeometry()); + + assertTrue(feature.getIncludeCountryCodes().isEmpty()); + assertTrue(feature.getExcludeCountryCodes().isEmpty()); + assertEquals("", feature.getName()); + assertEquals("",feature.getIcon()); + assertEquals("",feature.getImageURL()); + assertEquals(1, feature.getNames().size()); + assertTrue(feature.getTerms().isEmpty()); + assertEquals(1.0f, feature.getMatchScore(), 0.001f); + assertTrue(feature.isSearchable()); + assertEquals(feature.getAddTags(), feature.getTags()); + assertEquals(feature.getAddTags(), feature.getRemoveTags()); + } + + @Test public void load_features_unsupported_location_set() + { + List features = parse("one_preset_unsupported_location_set.json"); + assertEquals(2, features.size()); + assertEquals("some/ok", features.get(0).getId()); + assertEquals("another/ok", features.get(1).getId()); + } + + @Test public void load_features_no_wildcards() + { + List features = parse("one_preset_wildcard.json"); + assertTrue(features.isEmpty()); + } + + @Test public void parse_some_real_data() throws IOException + { + URL url = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json"); + + List features = new IDPresetsJsonParser().parse(Okio.source(url.openStream())); + // should not crash etc + assertTrue(features.size() > 1000); + } + + private List parse(String file) + { + try + { + return new IDPresetsJsonParser().parse(getSource(file)); + } catch (IOException e) + { + throw new RuntimeException(); + } + } + + private Source getSource(String file) throws IOException { + return Okio.source(getClass().getClassLoader().getResource(file).openConnection().getInputStream()); } } diff --git a/library/src/test/java/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.java b/library/src/test/java/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.java index bc4ffe6..92eac0a 100644 --- a/library/src/test/java/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.java +++ b/library/src/test/java/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.java @@ -32,8 +32,8 @@ public Collection getAll(List countryCodes) { public Feature get(String id, List countryCodes) { for (Feature feature : features) { if (!feature.getId().equals(id)) continue; + List includeCountryCodes = feature.getIncludeCountryCodes(); for (String countryCode : countryCodes) { - List includeCountryCodes = feature.getIncludeCountryCodes(); if (includeCountryCodes.contains(countryCode) || countryCode == null && includeCountryCodes.isEmpty()) { return feature; } From 145f846e2b3d1b262c9da6a3951a8b0caf26c6d6 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Mon, 18 Dec 2023 20:03:13 +0100 Subject: [PATCH 11/98] Fixed remove useless trycatch --- .../osmfeatures/CollectionUtils.kt | 14 ++++------- .../osmfeatures/FeatureDictionnary.kt | 24 ++++++++----------- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt b/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt index 8341365..c7d92b6 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt @@ -4,18 +4,12 @@ object CollectionUtils { /** For the given map, get the value of the entry at the given key and if there is no * entry yet, create it using the given create function thread-safely */ fun synchronizedGetOrCreate(map: MutableMap, key: K, createFn: (K) -> V): V? { - try { - synchronized(map) { - if (!map.containsKey(key)) { - map[key] = createFn(key) - } + synchronized(map) { + if (!map.containsKey(key)) { + map[key] = createFn(key) } - return map[key] } - catch (e: Error) { - println(e) - } - return null + return map[key] } @JvmStatic diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt b/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt index b27babb..966f08e 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt @@ -281,7 +281,6 @@ class FeatureDictionary internal constructor( if (!feature.isSearchable) return@Selector emptyList() val names: List = feature.canonicalNames val result = ArrayList(names) - try { for (name in names) { if (name.contains(" ")) { result.addAll( @@ -289,11 +288,6 @@ class FeatureDictionary internal constructor( ) } } - } - catch (e: Exception) { - println(e) - } - result }) } @@ -551,12 +545,12 @@ class FeatureDictionary internal constructor( //endregion //region Utility / Filter functions private fun getParentCategoryIds(id: String): Collection { - var id: String? = id + var currentId: String? = id val result: MutableList = ArrayList() do { - id = getParentId(id) - if (id != null) result.add(id) - } while (id != null) + currentId = getParentId(currentId) + if (currentId != null) result.add(currentId) + } while (currentId != null) return result } @@ -587,15 +581,17 @@ class FeatureDictionary internal constructor( result.add(null) countryCode?.let { val matcher = VALID_COUNTRY_CODE_REGEX.find(it) - if (matcher?.groups?.isNotEmpty() == true) { + if(matcher?.groups?.get(1) != null) { + result.add(matcher.groups[1]?.value) + // add ISO 3166-1 alpha2 (e.g. "US") - if (matcher.groups[2] != null) { + if (matcher.groups.size != 2 && matcher.groups[2] != null) { // add ISO 3166-2 (e.g. "US-NY") result.add(countryCode) } - result.add(matcher.groups[1]?.value) - } + + } return result } From 102c10a96e18cbade82fc6fe8bf77f4e7efefff0 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Wed, 20 Dec 2023 03:58:06 +0100 Subject: [PATCH 12/98] Rename .java to .kt --- .../{AssetManagerAccess.java => AssetManagerAccess.kt} | 0 .../osmfeatures/{ContainedMapTree.java => ContainedMapTree.kt} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename library-android/src/main/java/de/westnordost/osmfeatures/{AssetManagerAccess.java => AssetManagerAccess.kt} (100%) rename library/src/main/java/de/westnordost/osmfeatures/{ContainedMapTree.java => ContainedMapTree.kt} (100%) diff --git a/library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.java b/library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.kt similarity index 100% rename from library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.java rename to library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.java b/library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.java rename to library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.kt From d3a45d58725aec9a7361072d06197b133e2845cf Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Wed, 20 Dec 2023 03:58:06 +0100 Subject: [PATCH 13/98] Converted all files to Kotlin --- library-android/build.gradle | 12 +- .../osmfeatures/AndroidFeatureDictionary.java | 26 -- .../osmfeatures/AndroidFeatureDictionnary.kt | 27 ++ .../osmfeatures/AssetManagerAccess.kt | 41 +-- .../osmfeatures/ContainedMapTree.kt | 285 ++++++++---------- 5 files changed, 173 insertions(+), 218 deletions(-) delete mode 100644 library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.java create mode 100644 library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionnary.kt diff --git a/library-android/build.gradle b/library-android/build.gradle index e5baeb2..d16916a 100644 --- a/library-android/build.gradle +++ b/library-android/build.gradle @@ -14,7 +14,7 @@ repositories { } dependencies { - //api (project(':library')) { + // api (project(':library')) { api ('de.westnordost:osmfeatures:5.2') { // it's already included in Android exclude group: 'org.json', module: 'json' @@ -23,12 +23,11 @@ dependencies { android { compileSdkVersion 32 - defaultConfig { minSdkVersion 9 targetSdkVersion 32 versionCode 1 - versionName project.version + versionName "1" } compileOptions { @@ -37,18 +36,19 @@ android { } } -task sourcesJar(type: Jar) { +tasks.register('sourcesJar', Jar) { from android.sourceSets.main.java.srcDirs classifier = 'sources' } -task javadoc(type: Javadoc) { +tasks.register('javadoc', Javadoc) { source = android.sourceSets.main.java.srcDirs failOnError = false classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) } -task javadocJar(type: Jar, dependsOn: javadoc) { +tasks.register('javadocJar', Jar) { + dependsOn javadoc from javadoc.destinationDir classifier = 'javadoc' } diff --git a/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.java b/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.java deleted file mode 100644 index 8ea8c1d..0000000 --- a/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.java +++ /dev/null @@ -1,26 +0,0 @@ -package de.westnordost.osmfeatures; - -import android.content.res.AssetManager; - -public class AndroidFeatureDictionary -{ - private AndroidFeatureDictionary() {} // cannot be instantiated - - /** Create a new FeatureDictionary which gets its data from the given directory in the app's asset folder. */ - public static FeatureDictionary create(AssetManager assetManager, String presetsBasePath) { - return create(assetManager, presetsBasePath, null); - } - - /** Create a new FeatureDictionary which gets its data from the given directory in the app's - * asset folder. Optionally, the path to the brand presets can be specified. */ - public static FeatureDictionary create(AssetManager assetManager, String presetsBasePath, String brandPresetsBasePath) { - LocalizedFeatureCollection featureCollection = - new IDLocalizedFeatureCollection(new AssetManagerAccess(assetManager, presetsBasePath)); - - PerCountryFeatureCollection brandsFeatureCollection = brandPresetsBasePath != null - ? new IDBrandPresetsFeatureCollection(new AssetManagerAccess(assetManager, brandPresetsBasePath)) - : null; - - return new FeatureDictionary(featureCollection, brandsFeatureCollection); - } -} diff --git a/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionnary.kt b/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionnary.kt new file mode 100644 index 0000000..9a58b2f --- /dev/null +++ b/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionnary.kt @@ -0,0 +1,27 @@ +package de.westnordost.osmfeatures + +import android.content.res.AssetManager + +object AndroidFeatureDictionary { + /** Create a new FeatureDictionary which gets its data from the given directory in the app's asset folder. */ + fun create(assetManager: AssetManager, presetsBasePath: String): FeatureDictionary { + return create(assetManager, presetsBasePath, null) + } + + /** Create a new FeatureDictionary which gets its data from the given directory in the app's + * asset folder. Optionally, the path to the brand presets can be specified. */ + fun create( + assetManager: AssetManager, + presetsBasePath: String, + brandPresetsBasePath: String? + ): FeatureDictionary { + val featureCollection: LocalizedFeatureCollection = + IDLocalizedFeatureCollection(AssetManagerAccess(assetManager, presetsBasePath)) + + val brandsFeatureCollection: PerCountryFeatureCollection? = if (brandPresetsBasePath != null + ) IDBrandPresetsFeatureCollection(AssetManagerAccess(assetManager, brandPresetsBasePath)) + else null + + return FeatureDictionary(featureCollection, brandsFeatureCollection) + } +} \ No newline at end of file diff --git a/library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.kt b/library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.kt index 91d30a2..aad7d89 100644 --- a/library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.kt +++ b/library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.kt @@ -1,35 +1,20 @@ -package de.westnordost.osmfeatures; +package de.westnordost.osmfeatures -import android.content.res.AssetManager; +import android.content.res.AssetManager +import java.io.File -import java.io.File; -import java.io.IOException; -import java.io.InputStream; +internal class AssetManagerAccess(assetManager: AssetManager, private val basePath: String) : FileAccessAdapter { + private val assetManager: AssetManager = assetManager -class AssetManagerAccess implements FileAccessAdapter -{ - private final AssetManager assetManager; - private final String basePath; - - AssetManagerAccess(AssetManager assetManager, String basePath) - { - this.assetManager = assetManager; - this.basePath = basePath; - } - - @Override public boolean exists(String name) throws IOException - { - String[] files = assetManager.list(basePath); - if(files == null) return false; - for (String file : files) - { - if(file.equals(name)) return true; + override fun exists(name: String): Boolean { + val files: Array = assetManager.list(basePath) ?: return false + for (file in files) { + if (file == name) return true } - return false; + return false } - @Override public InputStream open(String name) throws IOException - { - return assetManager.open(basePath + File.separator + name); + override fun open(name: String): okio.Source { + return assetManager.open(basePath + File.separator + name) } -} +} \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.kt b/library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.kt index 4b91fc8..f6c14a3 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.kt +++ b/library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.kt @@ -1,193 +1,162 @@ -package de.westnordost.osmfeatures; +package de.westnordost.osmfeatures -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import de.westnordost.osmfeatures.CollectionUtils.mapContainsAllEntries +import java.util.* /** Index that makes finding which maps are completely contained by a given map very efficient. - * It sorts the maps into a tree structure with configurable depth. + * It sorts the maps into a tree structure with configurable depth. * - * It is threadsafe because it is immutable. + * It is threadsafe because it is immutable. * - * For example for the string maps... - *
- *  [
- *    #1 (amenity -> bicycle_parking),
- *    #2 (amenity -> bicycle_parking, bicycle_parking -> shed),
- *    #3 (amenity -> bicycle_parking, bicycle_parking -> lockers),
- *    #4 (amenity -> taxi),
- *    #5 (shop -> supermarket),
- *  ]
- *  
- * ...the tree internally looks like this: - *
- *  amenity ->
- *    bicycle_parking ->
- *      #1
- *      bicycle_parking ->
- *        shed ->
- *          #2
- *        lockers ->
- *          #3
- *    taxi ->
- *      #4
- *  shop ->
- *    supermarket ->
- *      #5
- *  ...
- *  
- * */ -class ContainedMapTree -{ - private final Node root; - - ContainedMapTree(Collection> maps) - { - this(maps, 4, 4); - } + * For example for the string maps... + *
+ * [
+ * #1 (amenity -> bicycle_parking),
+ * #2 (amenity -> bicycle_parking, bicycle_parking -> shed),
+ * #3 (amenity -> bicycle_parking, bicycle_parking -> lockers),
+ * #4 (amenity -> taxi),
+ * #5 (shop -> supermarket),
+ * ]
+
* + * ...the tree internally looks like this: + *
+ * amenity ->
+ * bicycle_parking ->
+ * #1
+ * bicycle_parking ->
+ * shed ->
+ * #2
+ * lockers ->
+ * #3
+ * taxi ->
+ * #4
+ * shop ->
+ * supermarket ->
+ * #5
+ * ...
+
* + */ +internal class ContainedMapTree +@JvmOverloads constructor(maps: Collection>, maxDepth: Int = 4, minContainerSize: Int = 4) { + private val root: Node /** Create this index with the given maps. * - * The generated tree will have a max depth of maxDepth and another depth is not added to the - * tree if there are less than minContainerSize maps in one tree node. + * The generated tree will have a max depth of maxDepth and another depth is not added to the + * tree if there are less than minContainerSize maps in one tree node. */ - ContainedMapTree(Collection> maps, int maxDepth, int minContainerSize) - { - if (maxDepth < 0) maxDepth = 0; - root = buildTree(maps, Collections.emptyList(), maxDepth, minContainerSize); + init { + var maxDepth = maxDepth + if (maxDepth < 0) maxDepth = 0 + root = buildTree(maps, emptyList(), maxDepth, minContainerSize) } - /** Get all maps whose entries are completely contained by the given map */ - List> getAll(Map map) - { - return root.getAll(map); + /** Get all maps whose entries are completely contained by the given map */ + fun getAll(map: Map?): List> { + return root.getAll(map!!) } - private static Node buildTree(Collection> maps, Collection previousKeys, int maxDepth, int minContainerSize) - { - if (previousKeys.size() == maxDepth || maps.size() < minContainerSize) - return new Node<>(null, maps); + private class Node( + /** key -> (value -> Node) */ + val children: Map>>?, maps: Collection> + ) { + val maps: Collection>? = maps + + /** Get all maps whose entries are all contained by given map */ + fun getAll(map: Map): List> { + val result: MutableList> = ArrayList() + if (children != null) { + for ((key, value) in children) { + if (map.containsKey(key)) { + for ((keyNode, node) in value) { + if (keyNode == map[key]) { + result.addAll(node.getAll(map)) + } + } + } + } + } + if (maps != null) { + for (m in maps) { + if (mapContainsAllEntries(map, m.entries)) { + result.add(m) + } + } + } + return result + } + } - Set> unsortedMaps = new HashSet<>(maps); + companion object { + private fun buildTree( + maps: Collection>, + previousKeys: Collection, + maxDepth: Int, + minContainerSize: Int + ): Node { + if (previousKeys.size == maxDepth || maps.size < minContainerSize) return Node(null, maps) - Map>> mapsByKey = getMapsByKey(maps, previousKeys); + val unsortedMaps: MutableSet> = HashSet(maps) - /* the map should be categorized by frequent keys first and least frequent keys last. */ - List>>> sortedByCountDesc = new ArrayList<>(mapsByKey.entrySet()); - Collections.sort(sortedByCountDesc, (a, b) -> b.getValue().size() - a.getValue().size()); + val mapsByKey = getMapsByKey(maps, previousKeys) - HashMap>> result = new HashMap<>(mapsByKey.size()); + /* the map should be categorized by frequent keys first and least frequent keys last. */ + val sortedByCountDesc: List>>> = ArrayList(mapsByKey.entries) + Collections.sort(sortedByCountDesc) { a: Map.Entry>>, b: Map.Entry>> -> b.value.size - a.value.size } - for (Map.Entry>> keyToMaps : sortedByCountDesc) - { - K key = keyToMaps.getKey(); - List> mapsForKey = keyToMaps.getValue(); + val result = HashMap>>(mapsByKey.size) - // a map already sorted in a certain node should not be sorted into another too - mapsForKey.retainAll(unsortedMaps); - if (mapsForKey.isEmpty()) continue; + for ((key, mapsForKey) in sortedByCountDesc) { + // a map already sorted in a certain node should not be sorted into another too + mapsForKey.retainAll(unsortedMaps) + if (mapsForKey.isEmpty()) continue - Map>> featuresByValue = getMapsByKeyValue(key, mapsForKey); + val featuresByValue: Map>> = getMapsByKeyValue(key, mapsForKey) - Map> valueNodes = new HashMap<>(featuresByValue.size()); - for (Map.Entry>> valueToFeatures : featuresByValue.entrySet()) - { - V value = valueToFeatures.getKey(); - List> featuresForValue = valueToFeatures.getValue(); - List previousKeysNow = new ArrayList<>(previousKeys); - previousKeysNow.add(key); - valueNodes.put(value, buildTree(featuresForValue, previousKeysNow, maxDepth, minContainerSize)); - } + val valueNodes: MutableMap> = HashMap(featuresByValue.size) + for ((value, featuresForValue) in featuresByValue) { + val previousKeysNow: MutableList = ArrayList(previousKeys) + previousKeysNow.add(key) + valueNodes[value] = buildTree(featuresForValue, previousKeysNow, maxDepth, minContainerSize) + } - result.put(key, valueNodes); + result[key] = valueNodes - for (Map map : mapsForKey) { - unsortedMaps.remove(map); + for (map in mapsForKey) { + unsortedMaps.remove(map) + } } - } - return new Node<>(result, new ArrayList<>(unsortedMaps)); - } - - /** returns the given features grouped by the map entry value of the given key. */ - private static Map>> getMapsByKeyValue(K key, Collection> maps) - { - HashMap>> result = new HashMap<>(); - for (Map map : maps) - { - V value = map.get(key); - if (!result.containsKey(value)) result.put(value, new ArrayList<>()); - result.get(value).add(map); + return Node(result, ArrayList(unsortedMaps)) } - return result; - } - /** returns the given maps grouped by each of their keys (except the given ones). */ - private static Map>> getMapsByKey(Collection> maps, Collection excludeKeys) - { - HashMap>> result = new HashMap<>(); - for (Map map : maps) - { - for (K key : map.keySet()) - { - if (excludeKeys.contains(key)) continue; - if (!result.containsKey(key)) result.put(key, new ArrayList<>()); - result.get(key).add(map); + /** returns the given features grouped by the map entry value of the given key. */ + private fun getMapsByKeyValue(key: K, maps: Collection>): Map>> { + val result = HashMap>>() + for (map in maps) { + val value = map[key] + value?.let { + if (!result.containsKey(it)) result[it] = ArrayList() + result[it]?.add(map) + } } - } - return result; - } - - private static class Node - { - /** key -> (value -> Node) */ - final Map>> children; - final Collection> maps; - - private Node(Map>> children, Collection> maps) - { - this.children = children; - this.maps = maps; + return result } - /** Get all maps whose entries are all contained by given map */ - private List> getAll(Map map) - { - List> result = new ArrayList<>(); - if (children != null) - { - for (Map.Entry>> keyToValues : children.entrySet()) - { - K key = keyToValues.getKey(); - if (map.containsKey(key)) - { - for (Map.Entry> valueToNode : keyToValues.getValue().entrySet()) - { - V value = valueToNode.getKey(); - if (value.equals(map.get(key))) - { - result.addAll(valueToNode.getValue().getAll(map)); - } - } - } - } - } - if (maps != null) - { - for (Map m : maps) - { - if (CollectionUtils.mapContainsAllEntries(map, m.entrySet())) - { - result.add(m); - } + /** returns the given maps grouped by each of their keys (except the given ones). */ + private fun getMapsByKey( + maps: Collection>, + excludeKeys: Collection + ): Map>> { + val result = HashMap>>() + for (map in maps) { + for (key in map.keys) { + if (excludeKeys.contains(key)) continue + if (!result.containsKey(key)) result[key] = ArrayList() + result[key]!!.add(map) } } - return result; + return result } } } From 3832f45e6dc5b0462e3133702afd59e328ef3b76 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Wed, 20 Dec 2023 03:58:06 +0100 Subject: [PATCH 14/98] Converted all files to Kotlin --- build.gradle | 46 - build.gradle.kts | 43 + gradle/libs.versions.toml | 20 + gradle/wrapper/gradle-wrapper.properties | 12 +- library-android/build.gradle | 12 +- .../osmfeatures/AndroidFeatureDictionary.java | 26 - .../osmfeatures/AndroidFeatureDictionnary.kt | 27 + .../osmfeatures/AssetManagerAccess.kt | 41 +- library/build.gradle.kts | 204 +++-- library/settings.gradle.kts | 5 - .../kotlin}/osmfeatures/BaseFeature.kt | 8 +- .../kotlin}/osmfeatures/CollectionUtils.kt | 0 .../kotlin/osmfeatures/ContainedMapTree.kt | 162 ++++ .../kotlin}/osmfeatures/Feature.kt | 4 +- .../kotlin}/osmfeatures/FeatureDictionnary.kt | 30 +- .../kotlin}/osmfeatures/FeatureTagsIndex.kt | 19 +- .../kotlin}/osmfeatures/FeatureTermIndex.kt | 5 +- .../kotlin}/osmfeatures/FileAccessAdapter.kt | 3 +- .../kotlin}/osmfeatures/FileSystemAccess.kt | 3 +- .../kotlin}/osmfeatures/GeometryType.kt | 0 .../IDBrandPresetsFeatureCollection.kt | 2 + .../IDLocalizedFeatureCollection.kt | 6 +- .../osmfeatures/IDPresetsJsonParser.kt | 2 +- .../IDPresetsTranslationJsonParser.kt | 2 +- .../kotlin}/osmfeatures/JsonUtils.kt | 0 .../kotlin}/osmfeatures/Locale.kt | 0 .../kotlin}/osmfeatures/LocalizedFeature.kt | 2 +- .../osmfeatures/LocalizedFeatureCollection.kt | 5 +- .../PerCountryFeatureCollection.kt | 0 .../osmfeatures/StartsWithStringTree.kt | 0 .../kotlin}/osmfeatures/StringUtils.kt | 0 .../kotlin/osmfeatures/CollectionUtilsTest.kt | 44 + .../osmfeatures/ContainedMapTreeTest.kt | 57 ++ .../osmfeatures/FeatureDictionaryTest.kt | 852 ++++++++++++++++++ .../osmfeatures/FeatureTagsIndexTest.kt | 65 ++ .../osmfeatures/FeatureTermIndexTest.kt | 85 ++ .../IDBrandPresetsFeatureCollectionTest.kt | 74 ++ .../IDLocalizedFeatureCollectionTest.kt | 157 ++++ .../osmfeatures/IDPresetsJsonParserTest.kt | 106 +++ .../IDPresetsTranslationJsonParserTest.kt | 108 +++ .../kotlin/osmfeatures/JsonUtilsTest.kt | 51 ++ .../LivePresetDataAccessAdapter.kt | 24 + .../commonTest/kotlin/osmfeatures/MapEntry.kt | 15 + .../osmfeatures/StartsWithStringTreeTest.kt | 37 + .../TestLocalizedFeatureCollection.kt | 24 + .../TestPerCountryFeatureCollection.kt | 33 + .../kotlin/osmfeatures/TestUtils.kt | 14 + .../resources/brand_presets_min.json | 0 .../resources/brand_presets_min2.json | 0 .../resources/localizations.json | 16 +- .../resources/localizations_de-AT.json | 18 +- .../resources/localizations_de-Cyrl-AT.json | 0 .../resources/localizations_de-Cyrl.json | 0 .../resources/localizations_de.json | 18 +- .../resources/localizations_en.json | 12 +- .../resources/localizations_min.json | 12 +- .../resources/one_preset_full.json | 36 +- .../resources/one_preset_min.json | 10 +- .../one_preset_unsupported_location_set.json | 0 .../resources/one_preset_wildcard.json | 22 +- .../one_preset_with_placeholder_name.json | 0 .../resources/some_presets_min.json | 32 +- .../osmfeatures/ContainedMapTree.kt | 193 ---- .../osmfeatures/CollectionUtilsTest.java | 58 -- .../osmfeatures/ContainedMapTreeTest.java | 64 -- .../osmfeatures/FeatureDictionaryTest.java | 815 ----------------- .../osmfeatures/FeatureTagsIndexTest.java | 64 -- .../osmfeatures/FeatureTermIndexTest.java | 88 -- .../IDBrandPresetsFeatureCollectionTest.java | 74 -- .../IDLocalizedFeatureCollectionTest.java | 154 ---- .../osmfeatures/IDPresetsJsonParserTest.java | 104 --- .../IDPresetsTranslationJsonParserTest.java | 111 --- .../osmfeatures/JsonUtilsTest.java | 68 -- .../LivePresetDataAccessAdapter.java | 32 - .../de/westnordost/osmfeatures/MapEntry.java | 23 - .../osmfeatures/StartsWithStringTreeTest.java | 41 - .../TestLocalizedFeatureCollection.java | 36 - .../TestPerCountryFeatureCollection.java | 44 - .../de/westnordost/osmfeatures/TestUtils.java | 16 - settings.gradle | 1 - settings.gradle.kts | 18 + 81 files changed, 2309 insertions(+), 2306 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts create mode 100644 gradle/libs.versions.toml delete mode 100644 library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.java create mode 100644 library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionnary.kt rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/BaseFeature.kt (85%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/CollectionUtils.kt (100%) create mode 100644 library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/Feature.kt (91%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/FeatureDictionnary.kt (96%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/FeatureTagsIndex.kt (62%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/FeatureTermIndex.kt (91%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/FileAccessAdapter.kt (78%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/FileSystemAccess.kt (90%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/GeometryType.kt (100%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/IDBrandPresetsFeatureCollection.kt (98%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/IDLocalizedFeatureCollection.kt (97%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/IDPresetsJsonParser.kt (98%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/IDPresetsTranslationJsonParser.kt (99%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/JsonUtils.kt (100%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/Locale.kt (100%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/LocalizedFeature.kt (98%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/LocalizedFeatureCollection.kt (79%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/PerCountryFeatureCollection.kt (100%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/StartsWithStringTree.kt (100%) rename library/src/{main/java/de/westnordost => commonMain/kotlin}/osmfeatures/StringUtils.kt (100%) create mode 100644 library/src/commonTest/kotlin/osmfeatures/CollectionUtilsTest.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/MapEntry.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/TestPerCountryFeatureCollection.kt create mode 100644 library/src/commonTest/kotlin/osmfeatures/TestUtils.kt rename library/src/{test => commonTest}/resources/brand_presets_min.json (100%) rename library/src/{test => commonTest}/resources/brand_presets_min2.json (100%) rename library/src/{test => commonTest}/resources/localizations.json (95%) rename library/src/{test => commonTest}/resources/localizations_de-AT.json (94%) rename library/src/{test => commonTest}/resources/localizations_de-Cyrl-AT.json (100%) rename library/src/{test => commonTest}/resources/localizations_de-Cyrl.json (100%) rename library/src/{test => commonTest}/resources/localizations_de.json (94%) rename library/src/{test => commonTest}/resources/localizations_en.json (93%) rename library/src/{test => commonTest}/resources/localizations_min.json (93%) rename library/src/{test => commonTest}/resources/one_preset_full.json (96%) rename library/src/{test => commonTest}/resources/one_preset_min.json (94%) rename library/src/{test => commonTest}/resources/one_preset_unsupported_location_set.json (100%) rename library/src/{test => commonTest}/resources/one_preset_wildcard.json (94%) rename library/src/{test => commonTest}/resources/one_preset_with_placeholder_name.json (100%) rename library/src/{test => commonTest}/resources/some_presets_min.json (95%) delete mode 100644 library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.kt delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/CollectionUtilsTest.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/ContainedMapTreeTest.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/FeatureDictionaryTest.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/FeatureTagsIndexTest.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/FeatureTermIndexTest.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/IDPresetsJsonParserTest.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/JsonUtilsTest.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/MapEntry.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/StartsWithStringTreeTest.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.java delete mode 100644 library/src/test/java/de/westnordost/osmfeatures/TestUtils.java delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/build.gradle b/build.gradle deleted file mode 100644 index b735e74..0000000 --- a/build.gradle +++ /dev/null @@ -1,46 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - repositories { - mavenCentral() - google() - } - dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} - -import groovy.json.JsonSlurper - -task downloadPresets { - doLast { - def targetDir = "$projectDir/presets" - def presetsUrl = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - def contentsUrl = new URL("https://api.github.com/repos/openstreetmap/id-tagging-schema/contents/dist/translations") - - new File("$targetDir/presets.json").withOutputStream { it << presetsUrl.openStream() } - - def slurper = new JsonSlurper() - slurper.parse(contentsUrl, "UTF-8").each { - if(it.type == "file") { - def language = it.name.substring(0, it.name.lastIndexOf(".")) - def translationsUrl = new URL(it.download_url) - def javaLanguage = bcp47LanguageTagToJavaLanguageTag(language) - new File("$targetDir/${javaLanguage}.json").withOutputStream { it << translationsUrl.openStream() } - } - } - } -} - -// Java (and thus also Android) uses some old iso (language) codes. F.e. id -> in etc. -// so the localized files also need to use the old iso codes -static def bcp47LanguageTagToJavaLanguageTag(String bcp47) { - def locale = Locale.forLanguageTag(bcp47) - def result = locale.language - if (!locale.country.isEmpty()) result += "-" + locale.country - return result -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..f3003f7 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,43 @@ +//import groovy.json.JsonSlurper +//import java.io.File +//import java.net.URL +//import java.util.Locale +// +//plugins { +// `java` +//} +// +//repositories { +// mavenCentral() +// google() +//} +// +//val downloadPresets by tasks.registering { +// doLast { +// val targetDir = "$projectDir/presets" +// val presetsUrl = URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") +// val contentsUrl = URL("https://api.github.com/repos/openstreetmap/id-tagging-schema/contents/dist/translations") +// +// File("$targetDir/presets.json").outputStream().use { it.write(presetsUrl.openStream().readBytes()) } +// +// val slurper = JsonSlurper() +// val contents = slurper.parse(contentsUrl, "UTF-8") as List> +// contents.forEach { +// if (it["type"] == "file") { +// val language = it["name"].toString().substringBeforeLast(".") +// val translationsUrl = URL(it["download_url"].toString()) +// val javaLanguage = bcp47LanguageTagToJavaLanguageTag(language) +// File("$targetDir/${javaLanguage}.json").outputStream().use { os -> +// os.write(translationsUrl.openStream().readBytes()) +// } +// } +// } +// } +//} +// +//fun bcp47LanguageTagToJavaLanguageTag(bcp47: String): String { +// val locale = Locale.forLanguageTag(bcp47) +// var result = locale.language +// if (locale.country.isNotEmpty()) result += "-" + locale.country +// return result +//} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..846b5eb --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,20 @@ +[versions] +agp = "8.2.1" +fluid-locale = "0.13.0" +junit = "4.13.2" +kotlin = "1.9.22" +kotlinx-serialization-json = "1.6.0" +normalize = "1.0.5" +okio = "3.6.0" + +[libraries] +fluid-locale = { module = "io.fluidsonic.locale:fluid-locale", version.ref = "fluid-locale" } +junit = { module = "junit:junit", version.ref = "junit" } +kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } +normalize = { module = "com.doist.x:normalize", version.ref = "normalize" } +okio = { module = "com.squareup.okio:okio", version.ref = "okio" } + +[plugins] +android-library = { id = "com.android.library", version.ref = "agp" } +kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2ff8cff..40f4f2e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Mar 04 22:04:30 CET 2021 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip +#Fri Dec 22 21:31:41 CET 2023 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/library-android/build.gradle b/library-android/build.gradle index e5baeb2..d16916a 100644 --- a/library-android/build.gradle +++ b/library-android/build.gradle @@ -14,7 +14,7 @@ repositories { } dependencies { - //api (project(':library')) { + // api (project(':library')) { api ('de.westnordost:osmfeatures:5.2') { // it's already included in Android exclude group: 'org.json', module: 'json' @@ -23,12 +23,11 @@ dependencies { android { compileSdkVersion 32 - defaultConfig { minSdkVersion 9 targetSdkVersion 32 versionCode 1 - versionName project.version + versionName "1" } compileOptions { @@ -37,18 +36,19 @@ android { } } -task sourcesJar(type: Jar) { +tasks.register('sourcesJar', Jar) { from android.sourceSets.main.java.srcDirs classifier = 'sources' } -task javadoc(type: Javadoc) { +tasks.register('javadoc', Javadoc) { source = android.sourceSets.main.java.srcDirs failOnError = false classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) } -task javadocJar(type: Jar, dependsOn: javadoc) { +tasks.register('javadocJar', Jar) { + dependsOn javadoc from javadoc.destinationDir classifier = 'javadoc' } diff --git a/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.java b/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.java deleted file mode 100644 index 8ea8c1d..0000000 --- a/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.java +++ /dev/null @@ -1,26 +0,0 @@ -package de.westnordost.osmfeatures; - -import android.content.res.AssetManager; - -public class AndroidFeatureDictionary -{ - private AndroidFeatureDictionary() {} // cannot be instantiated - - /** Create a new FeatureDictionary which gets its data from the given directory in the app's asset folder. */ - public static FeatureDictionary create(AssetManager assetManager, String presetsBasePath) { - return create(assetManager, presetsBasePath, null); - } - - /** Create a new FeatureDictionary which gets its data from the given directory in the app's - * asset folder. Optionally, the path to the brand presets can be specified. */ - public static FeatureDictionary create(AssetManager assetManager, String presetsBasePath, String brandPresetsBasePath) { - LocalizedFeatureCollection featureCollection = - new IDLocalizedFeatureCollection(new AssetManagerAccess(assetManager, presetsBasePath)); - - PerCountryFeatureCollection brandsFeatureCollection = brandPresetsBasePath != null - ? new IDBrandPresetsFeatureCollection(new AssetManagerAccess(assetManager, brandPresetsBasePath)) - : null; - - return new FeatureDictionary(featureCollection, brandsFeatureCollection); - } -} diff --git a/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionnary.kt b/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionnary.kt new file mode 100644 index 0000000..9a58b2f --- /dev/null +++ b/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionnary.kt @@ -0,0 +1,27 @@ +package de.westnordost.osmfeatures + +import android.content.res.AssetManager + +object AndroidFeatureDictionary { + /** Create a new FeatureDictionary which gets its data from the given directory in the app's asset folder. */ + fun create(assetManager: AssetManager, presetsBasePath: String): FeatureDictionary { + return create(assetManager, presetsBasePath, null) + } + + /** Create a new FeatureDictionary which gets its data from the given directory in the app's + * asset folder. Optionally, the path to the brand presets can be specified. */ + fun create( + assetManager: AssetManager, + presetsBasePath: String, + brandPresetsBasePath: String? + ): FeatureDictionary { + val featureCollection: LocalizedFeatureCollection = + IDLocalizedFeatureCollection(AssetManagerAccess(assetManager, presetsBasePath)) + + val brandsFeatureCollection: PerCountryFeatureCollection? = if (brandPresetsBasePath != null + ) IDBrandPresetsFeatureCollection(AssetManagerAccess(assetManager, brandPresetsBasePath)) + else null + + return FeatureDictionary(featureCollection, brandsFeatureCollection) + } +} \ No newline at end of file diff --git a/library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.kt b/library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.kt index 91d30a2..aad7d89 100644 --- a/library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.kt +++ b/library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.kt @@ -1,35 +1,20 @@ -package de.westnordost.osmfeatures; +package de.westnordost.osmfeatures -import android.content.res.AssetManager; +import android.content.res.AssetManager +import java.io.File -import java.io.File; -import java.io.IOException; -import java.io.InputStream; +internal class AssetManagerAccess(assetManager: AssetManager, private val basePath: String) : FileAccessAdapter { + private val assetManager: AssetManager = assetManager -class AssetManagerAccess implements FileAccessAdapter -{ - private final AssetManager assetManager; - private final String basePath; - - AssetManagerAccess(AssetManager assetManager, String basePath) - { - this.assetManager = assetManager; - this.basePath = basePath; - } - - @Override public boolean exists(String name) throws IOException - { - String[] files = assetManager.list(basePath); - if(files == null) return false; - for (String file : files) - { - if(file.equals(name)) return true; + override fun exists(name: String): Boolean { + val files: Array = assetManager.list(basePath) ?: return false + for (file in files) { + if (file == name) return true } - return false; + return false } - @Override public InputStream open(String name) throws IOException - { - return assetManager.open(basePath + File.separator + name); + override fun open(name: String): okio.Source { + return assetManager.open(basePath + File.separator + name) } -} +} \ No newline at end of file diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 75d6590..ecf4f3d 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -1,100 +1,138 @@ -plugins { - id("java-library") - id("maven-publish") - id("signing") - id("org.jetbrains.kotlin.jvm") version "1.9.0" - kotlin("plugin.serialization") version "1.9.21" -} - -repositories { - mavenCentral() -} - -dependencies { - implementation("org.json:json:20230227") - testImplementation("junit:junit:4.13.2") - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") - implementation("io.fluidsonic.locale:fluid-locale:0.13.0") - implementation("com.squareup.okio:okio:3.6.0") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") - implementation("com.doist.x:normalize:1.0.5") -} - -tasks { - val sourcesJar by creating(Jar::class) { - archiveClassifier.set("sources") - from(sourceSets.main.get().allSource) - } - val javadocJar by creating(Jar::class) { - dependsOn.add(javadoc) - archiveClassifier.set("javadoc") - from(javadoc) - } - - artifacts { - archives(sourcesJar) - archives(javadocJar) - } +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) } - - -publishing { - repositories { - maven { - url = uri("https://github.com/westnordost/osmfeatures") +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = "1.8" + } } } - publications { - create("mavenJava") { - groupId = "de.westnordost" - artifactId = "osmfeatures" - version = "5.2" - from(components["java"]) - pom { - name.value("osmfeatures") - description.value("Java library to translate OSM tags to and from localized names.") - url.value("https://github.com/westnordost/osmfeatures") - scm { - connection.value("https://github.com/westnordost/osmfeatures.git") - developerConnection.value("https://github.com/westnordost/osmfeatures.git") - url.value("https://github.com/westnordost/osmfeatures") - } - licenses { - license { - name.value("The Apache License, Version 2.0") - url.value("http://www.apache.org/licenses/LICENSE-2.0.txt") - } - } - developers { - developer { - id.value("westnordost") - name.value("Tobias Zwick") - email.value("osm@westnordost.de") - } - } + sourceSets { + + commonMain.dependencies { + //noinspection UseTomlInstead + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation(libs.fluid.locale) + implementation(libs.okio) + implementation(libs.kotlinx.serialization.json) + implementation(libs.normalize) } + val commonTest by getting { + dependencies { + implementation(libs.kotlin.test) + implementation(libs.junit) + }, + resources.sr } } } -signing { - sign(publishing.publications["mavenJava"]) -} -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 +android { + namespace = "de.westnordost.osmfeatures" + compileSdk = 34 + defaultConfig { + minSdk = 24 + } } -//compileKotlin { -// kotlinOptions { -// jvmTarget = "1.8" + +//plugins { +// id("java-library") +// id("maven-publish") +// id("signing") +// id("com.android.library") +// kotlin("multiplatform") version "1.9.10" +// kotlin("plugin.serialization") version "1.9.21" +//} +// +//repositories { +// mavenCentral() +//} +// +//dependencies { +// +//} +// +//kotlin { +// +// targetHierarchy.default() +// jvm() +// androidTarget { +// publishLibraryVariants("release") +// compilations.all { +// kotlinOptions { +// jvmTarget = "1.8" +// } +// } // } +// iosX64() +// iosArm64() +// iosSimulatorArm64() //} -//compileTestKotlin { -// kotlinOptions { -// jvmTarget = "1.8" +// sourceSets { +// val commonMain by getting { +// dependencies { +// //put your multiplatform dependencies here +// } +// } +// val commonTest by getting { +// dependencies { +// +// } +// } +// } +// +//publishing { +// repositories { +// maven { +// url = uri("https://github.com/westnordost/osmfeatures") +// } // } +// publications { +// create("mavenJava") { +// groupId = "de.westnordost" +// artifactId = "osmfeatures" +// version = "5.2" +// from(components["java"]) +// +// pom { +// name.value("osmfeatures") +// description.value("Java library to translate OSM tags to and from localized names.") +// url.value("https://github.com/westnordost/osmfeatures") +// scm { +// connection.value("https://github.com/westnordost/osmfeatures.git") +// developerConnection.value("https://github.com/westnordost/osmfeatures.git") +// url.value("https://github.com/westnordost/osmfeatures") +// } +// licenses { +// license { +// name.value("The Apache License, Version 2.0") +// url.value("http://www.apache.org/licenses/LICENSE-2.0.txt") +// } +// } +// developers { +// developer { +// id.value("westnordost") +// name.value("Tobias Zwick") +// email.value("osm@westnordost.de") +// } +// } +// } +// } +// } +//} +// +//signing { +// sign(publishing.publications["mavenJava"]) +//} +// +//java { +// sourceCompatibility = JavaVersion.VERSION_1_8 +// targetCompatibility = JavaVersion.VERSION_1_8 //} \ No newline at end of file diff --git a/library/settings.gradle.kts b/library/settings.gradle.kts index 2c6e611..e69de29 100644 --- a/library/settings.gradle.kts +++ b/library/settings.gradle.kts @@ -1,5 +0,0 @@ -pluginManagement { - repositories { - mavenCentral() - } -} \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt b/library/src/commonMain/kotlin/osmfeatures/BaseFeature.kt similarity index 85% rename from library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt rename to library/src/commonMain/kotlin/osmfeatures/BaseFeature.kt index a3f6298..7552e43 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/BaseFeature.kt +++ b/library/src/commonMain/kotlin/osmfeatures/BaseFeature.kt @@ -8,11 +8,11 @@ open class BaseFeature( private val _icon: String? = "", private val _imageURL: String? = "", private val _names: List, - final override val terms: List?, + final override val terms: List, final override val includeCountryCodes: List, final override val excludeCountryCodes: List, final override val isSearchable: Boolean, - final override val matchScore: Double, + final override val matchScore: Float, final override val isSuggestion: Boolean, final override val addTags: Map, final override val removeTags: Map @@ -21,9 +21,7 @@ open class BaseFeature( final override var canonicalTerms: List? = null init { - if (terms != null) { - this.canonicalTerms = terms.map { term -> StringUtils.canonicalize(term)} - } + this.canonicalTerms = terms.map { term -> StringUtils.canonicalize(term)} } override val icon: String diff --git a/library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt b/library/src/commonMain/kotlin/osmfeatures/CollectionUtils.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/CollectionUtils.kt rename to library/src/commonMain/kotlin/osmfeatures/CollectionUtils.kt diff --git a/library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt b/library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt new file mode 100644 index 0000000..f6c14a3 --- /dev/null +++ b/library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt @@ -0,0 +1,162 @@ +package de.westnordost.osmfeatures + +import de.westnordost.osmfeatures.CollectionUtils.mapContainsAllEntries +import java.util.* + +/** Index that makes finding which maps are completely contained by a given map very efficient. + * It sorts the maps into a tree structure with configurable depth. + * + * It is threadsafe because it is immutable. + * + * For example for the string maps... + *
+ * [
+ * #1 (amenity -> bicycle_parking),
+ * #2 (amenity -> bicycle_parking, bicycle_parking -> shed),
+ * #3 (amenity -> bicycle_parking, bicycle_parking -> lockers),
+ * #4 (amenity -> taxi),
+ * #5 (shop -> supermarket),
+ * ]
+
* + * ...the tree internally looks like this: + *
+ * amenity ->
+ * bicycle_parking ->
+ * #1
+ * bicycle_parking ->
+ * shed ->
+ * #2
+ * lockers ->
+ * #3
+ * taxi ->
+ * #4
+ * shop ->
+ * supermarket ->
+ * #5
+ * ...
+
* + */ +internal class ContainedMapTree +@JvmOverloads constructor(maps: Collection>, maxDepth: Int = 4, minContainerSize: Int = 4) { + private val root: Node + + /** Create this index with the given maps. + * + * The generated tree will have a max depth of maxDepth and another depth is not added to the + * tree if there are less than minContainerSize maps in one tree node. + */ + init { + var maxDepth = maxDepth + if (maxDepth < 0) maxDepth = 0 + root = buildTree(maps, emptyList(), maxDepth, minContainerSize) + } + + /** Get all maps whose entries are completely contained by the given map */ + fun getAll(map: Map?): List> { + return root.getAll(map!!) + } + + private class Node( + /** key -> (value -> Node) */ + val children: Map>>?, maps: Collection> + ) { + val maps: Collection>? = maps + + /** Get all maps whose entries are all contained by given map */ + fun getAll(map: Map): List> { + val result: MutableList> = ArrayList() + if (children != null) { + for ((key, value) in children) { + if (map.containsKey(key)) { + for ((keyNode, node) in value) { + if (keyNode == map[key]) { + result.addAll(node.getAll(map)) + } + } + } + } + } + if (maps != null) { + for (m in maps) { + if (mapContainsAllEntries(map, m.entries)) { + result.add(m) + } + } + } + return result + } + } + + companion object { + private fun buildTree( + maps: Collection>, + previousKeys: Collection, + maxDepth: Int, + minContainerSize: Int + ): Node { + if (previousKeys.size == maxDepth || maps.size < minContainerSize) return Node(null, maps) + + val unsortedMaps: MutableSet> = HashSet(maps) + + val mapsByKey = getMapsByKey(maps, previousKeys) + + /* the map should be categorized by frequent keys first and least frequent keys last. */ + val sortedByCountDesc: List>>> = ArrayList(mapsByKey.entries) + Collections.sort(sortedByCountDesc) { a: Map.Entry>>, b: Map.Entry>> -> b.value.size - a.value.size } + + val result = HashMap>>(mapsByKey.size) + + for ((key, mapsForKey) in sortedByCountDesc) { + // a map already sorted in a certain node should not be sorted into another too + mapsForKey.retainAll(unsortedMaps) + if (mapsForKey.isEmpty()) continue + + val featuresByValue: Map>> = getMapsByKeyValue(key, mapsForKey) + + val valueNodes: MutableMap> = HashMap(featuresByValue.size) + for ((value, featuresForValue) in featuresByValue) { + val previousKeysNow: MutableList = ArrayList(previousKeys) + previousKeysNow.add(key) + valueNodes[value] = buildTree(featuresForValue, previousKeysNow, maxDepth, minContainerSize) + } + + result[key] = valueNodes + + for (map in mapsForKey) { + unsortedMaps.remove(map) + } + } + + return Node(result, ArrayList(unsortedMaps)) + } + + /** returns the given features grouped by the map entry value of the given key. */ + private fun getMapsByKeyValue(key: K, maps: Collection>): Map>> { + val result = HashMap>>() + for (map in maps) { + val value = map[key] + value?.let { + if (!result.containsKey(it)) result[it] = ArrayList() + result[it]?.add(map) + } + } + return result + } + + /** returns the given maps grouped by each of their keys (except the given ones). */ + private fun getMapsByKey( + maps: Collection>, + excludeKeys: Collection + ): Map>> { + val result = HashMap>>() + for (map in maps) { + for (key in map.keys) { + if (excludeKeys.contains(key)) continue + if (!result.containsKey(key)) result[key] = ArrayList() + result[key]!!.add(map) + } + } + return result + } + } +} diff --git a/library/src/main/java/de/westnordost/osmfeatures/Feature.kt b/library/src/commonMain/kotlin/osmfeatures/Feature.kt similarity index 91% rename from library/src/main/java/de/westnordost/osmfeatures/Feature.kt rename to library/src/commonMain/kotlin/osmfeatures/Feature.kt index 66a6c43..e8d1f4c 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/Feature.kt +++ b/library/src/commonMain/kotlin/osmfeatures/Feature.kt @@ -10,11 +10,11 @@ interface Feature { /** name + aliases */ val names: List - val terms: List? + val terms: List val includeCountryCodes: List val excludeCountryCodes: List val isSearchable: Boolean - val matchScore: Double + val matchScore: Float val addTags: Map val removeTags: Map val canonicalNames: List diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt b/library/src/commonMain/kotlin/osmfeatures/FeatureDictionnary.kt similarity index 96% rename from library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt rename to library/src/commonMain/kotlin/osmfeatures/FeatureDictionnary.kt index 966f08e..7fedc04 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FeatureDictionnary.kt +++ b/library/src/commonMain/kotlin/osmfeatures/FeatureDictionnary.kt @@ -1,6 +1,14 @@ -package de.westnordost.osmfeatures - +package osmfeatures + +import de.westnordost.osmfeatures.CollectionUtils +import de.westnordost.osmfeatures.Feature +import de.westnordost.osmfeatures.GeometryType +import de.westnordost.osmfeatures.IDBrandPresetsFeatureCollection +import de.westnordost.osmfeatures.IDLocalizedFeatureCollection +import de.westnordost.osmfeatures.Locale import de.westnordost.osmfeatures.Locale.Companion.default +import de.westnordost.osmfeatures.PerCountryFeatureCollection +import de.westnordost.osmfeatures.StringUtils import kotlin.math.min import kotlin.text.Regex @@ -263,7 +271,11 @@ class FeatureDictionary internal constructor( //region Lazily get or create Indexes /** lazily get or create tags index for given locale(s) */ private fun getTagsIndex(locales: List): FeatureTagsIndex? { - return CollectionUtils.synchronizedGetOrCreate(tagsIndexes.toMutableMap(), locales, ::createTagsIndex) + return CollectionUtils.synchronizedGetOrCreate( + tagsIndexes.toMutableMap(), + locales, + ::createTagsIndex + ) } private fun createTagsIndex(locales: List): FeatureTagsIndex { @@ -272,7 +284,11 @@ class FeatureDictionary internal constructor( /** lazily get or create names index for given locale(s) */ private fun getNamesIndex(locales: List): FeatureTermIndex? { - return CollectionUtils.synchronizedGetOrCreate(namesIndexes.toMutableMap(), locales, ::createNamesIndex) + return CollectionUtils.synchronizedGetOrCreate( + namesIndexes.toMutableMap(), + locales, + ::createNamesIndex + ) } private fun createNamesIndex(locales: List): FeatureTermIndex { @@ -294,7 +310,11 @@ class FeatureDictionary internal constructor( /** lazily get or create terms index for given locale(s) */ private fun getTermsIndex(locales: List): FeatureTermIndex? { - return CollectionUtils.synchronizedGetOrCreate(termsIndexes.toMutableMap(), locales, ::createTermsIndex) + return CollectionUtils.synchronizedGetOrCreate( + termsIndexes.toMutableMap(), + locales, + ::createTermsIndex + ) } private fun createTermsIndex(locales: List): FeatureTermIndex { diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureTagsIndex.kt b/library/src/commonMain/kotlin/osmfeatures/FeatureTagsIndex.kt similarity index 62% rename from library/src/main/java/de/westnordost/osmfeatures/FeatureTagsIndex.kt rename to library/src/commonMain/kotlin/osmfeatures/FeatureTagsIndex.kt index a5e4013..d11af3b 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FeatureTagsIndex.kt +++ b/library/src/commonMain/kotlin/osmfeatures/FeatureTagsIndex.kt @@ -1,23 +1,22 @@ -package de.westnordost.osmfeatures +package osmfeatures + +import de.westnordost.osmfeatures.ContainedMapTree +import de.westnordost.osmfeatures.Feature /** Index that makes finding Features whose tags are completely contained by a given set of tags * very efficient. * * Based on ContainedMapTree data structure, see that class. */ -internal class FeatureTagsIndex(features: Collection?) { +internal class FeatureTagsIndex(features: Collection) { private val featureMap: MutableMap, MutableList> private val tree: ContainedMapTree init { featureMap = HashMap() - if (features != null) { - for (feature in features) { - val map: Map = feature!!.tags - if (!featureMap.containsKey(map)) featureMap[map] = ArrayList(1) - if (feature != null) { - featureMap[map]!!.add(feature) - } - } + for (feature in features) { + val map: Map = feature.tags + if (!featureMap.containsKey(map)) featureMap[map] = ArrayList(1) + featureMap[map]!!.add(feature) } tree = ContainedMapTree(featureMap.keys) } diff --git a/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt b/library/src/commonMain/kotlin/osmfeatures/FeatureTermIndex.kt similarity index 91% rename from library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt rename to library/src/commonMain/kotlin/osmfeatures/FeatureTermIndex.kt index bed76b3..4ef0405 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FeatureTermIndex.kt +++ b/library/src/commonMain/kotlin/osmfeatures/FeatureTermIndex.kt @@ -1,4 +1,7 @@ -package de.westnordost.osmfeatures +package osmfeatures + +import de.westnordost.osmfeatures.Feature +import de.westnordost.osmfeatures.StartsWithStringTree /** Index that makes finding Features whose name/term/... starts with a given string very efficient. * diff --git a/library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.kt b/library/src/commonMain/kotlin/osmfeatures/FileAccessAdapter.kt similarity index 78% rename from library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.kt rename to library/src/commonMain/kotlin/osmfeatures/FileAccessAdapter.kt index 277136e..bc037d8 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FileAccessAdapter.kt +++ b/library/src/commonMain/kotlin/osmfeatures/FileAccessAdapter.kt @@ -1,6 +1,5 @@ -package de.westnordost.osmfeatures +package osmfeatures -import okio.FileHandle import okio.IOException import okio.Source import kotlin.jvm.Throws diff --git a/library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.kt b/library/src/commonMain/kotlin/osmfeatures/FileSystemAccess.kt similarity index 90% rename from library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.kt rename to library/src/commonMain/kotlin/osmfeatures/FileSystemAccess.kt index 86e9df2..129cac8 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/FileSystemAccess.kt +++ b/library/src/commonMain/kotlin/osmfeatures/FileSystemAccess.kt @@ -1,5 +1,4 @@ -package de.westnordost.osmfeatures -import okio.FileHandle +package osmfeatures import okio.FileSystem import okio.Path.Companion.toPath import okio.Source diff --git a/library/src/main/java/de/westnordost/osmfeatures/GeometryType.kt b/library/src/commonMain/kotlin/osmfeatures/GeometryType.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/GeometryType.kt rename to library/src/commonMain/kotlin/osmfeatures/GeometryType.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt b/library/src/commonMain/kotlin/osmfeatures/IDBrandPresetsFeatureCollection.kt similarity index 98% rename from library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt rename to library/src/commonMain/kotlin/osmfeatures/IDBrandPresetsFeatureCollection.kt index f210d70..d3ba23c 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt +++ b/library/src/commonMain/kotlin/osmfeatures/IDBrandPresetsFeatureCollection.kt @@ -1,5 +1,7 @@ package de.westnordost.osmfeatures +import osmfeatures.FileAccessAdapter + /** Non-localized feature collection sourcing from (NSI) iD presets defined in JSON. * * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/osmfeatures/IDLocalizedFeatureCollection.kt similarity index 97% rename from library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt rename to library/src/commonMain/kotlin/osmfeatures/IDLocalizedFeatureCollection.kt index 558cde3..be28f2f 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/osmfeatures/IDLocalizedFeatureCollection.kt @@ -1,11 +1,15 @@ package de.westnordost.osmfeatures +import osmfeatures.FileAccessAdapter +import osmfeatures.LocalizedFeatureCollection + /** Localized feature collection sourcing from iD presets defined in JSON. * * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that * there is a presets.json which includes all the features. The translations are expected to be * located in the same directory named like e.g. de.json, pt-BR.json etc. */ -class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : LocalizedFeatureCollection { +class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : + LocalizedFeatureCollection { private val featuresById: LinkedHashMap private val localizedFeaturesList: MutableMap> = HashMap() private val localizedFeatures: MutableMap?, LinkedHashMap> = HashMap() diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt b/library/src/commonMain/kotlin/osmfeatures/IDPresetsJsonParser.kt similarity index 98% rename from library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt rename to library/src/commonMain/kotlin/osmfeatures/IDPresetsJsonParser.kt index ff1467c..f2c06f7 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsJsonParser.kt +++ b/library/src/commonMain/kotlin/osmfeatures/IDPresetsJsonParser.kt @@ -63,7 +63,7 @@ class IDPresetsJsonParser { } val searchable = p["searchable"]?.jsonPrimitive?.booleanOrNull?: true - val matchScore = p["matchScore"]?.jsonPrimitive?.doubleOrNull?: 1.0 + val matchScore = p["matchScore"]?.jsonPrimitive?.floatOrNull?: 1.0f val addTags = p["addTags"]?.let { parseStringMap(it.jsonObject)}?: tags val removeTags = p["removeTags"]?.let { parseStringMap(it.jsonObject)}?: addTags diff --git a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt b/library/src/commonMain/kotlin/osmfeatures/IDPresetsTranslationJsonParser.kt similarity index 99% rename from library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt rename to library/src/commonMain/kotlin/osmfeatures/IDPresetsTranslationJsonParser.kt index 098b7cb..99ad27a 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/library/src/commonMain/kotlin/osmfeatures/IDPresetsTranslationJsonParser.kt @@ -13,7 +13,7 @@ import okio.Source class IDPresetsTranslationJsonParser { fun parse( - source: Source, locale: Locale?, baseFeatures: Map + source: Source, locale: Locale?, baseFeatures: Map ): List { val decodedObject = createFromSource(source) val languageKey: String = decodedObject.entries.iterator().next().key diff --git a/library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt b/library/src/commonMain/kotlin/osmfeatures/JsonUtils.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/JsonUtils.kt rename to library/src/commonMain/kotlin/osmfeatures/JsonUtils.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/Locale.kt b/library/src/commonMain/kotlin/osmfeatures/Locale.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/Locale.kt rename to library/src/commonMain/kotlin/osmfeatures/Locale.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt b/library/src/commonMain/kotlin/osmfeatures/LocalizedFeature.kt similarity index 98% rename from library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt rename to library/src/commonMain/kotlin/osmfeatures/LocalizedFeature.kt index 5226130..9225435 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeature.kt +++ b/library/src/commonMain/kotlin/osmfeatures/LocalizedFeature.kt @@ -43,7 +43,7 @@ class LocalizedFeature( get() = p.excludeCountryCodes override val isSearchable: Boolean get() = p.isSearchable - override val matchScore: Double + override val matchScore: Float get() = p.matchScore override val addTags: Map get() = p.addTags diff --git a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/osmfeatures/LocalizedFeatureCollection.kt similarity index 79% rename from library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt rename to library/src/commonMain/kotlin/osmfeatures/LocalizedFeatureCollection.kt index 06b13e0..1b284d4 100644 --- a/library/src/main/java/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/osmfeatures/LocalizedFeatureCollection.kt @@ -1,4 +1,7 @@ -package de.westnordost.osmfeatures +package osmfeatures + +import de.westnordost.osmfeatures.Feature +import de.westnordost.osmfeatures.Locale /** A localized collection of features */ interface LocalizedFeatureCollection { diff --git a/library/src/main/java/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt b/library/src/commonMain/kotlin/osmfeatures/PerCountryFeatureCollection.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt rename to library/src/commonMain/kotlin/osmfeatures/PerCountryFeatureCollection.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/StartsWithStringTree.kt b/library/src/commonMain/kotlin/osmfeatures/StartsWithStringTree.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/StartsWithStringTree.kt rename to library/src/commonMain/kotlin/osmfeatures/StartsWithStringTree.kt diff --git a/library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt b/library/src/commonMain/kotlin/osmfeatures/StringUtils.kt similarity index 100% rename from library/src/main/java/de/westnordost/osmfeatures/StringUtils.kt rename to library/src/commonMain/kotlin/osmfeatures/StringUtils.kt diff --git a/library/src/commonTest/kotlin/osmfeatures/CollectionUtilsTest.kt b/library/src/commonTest/kotlin/osmfeatures/CollectionUtilsTest.kt new file mode 100644 index 0000000..c1a7f0a --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/CollectionUtilsTest.kt @@ -0,0 +1,44 @@ +package osmfeatures + +import de.westnordost.osmfeatures.CollectionUtils +import osmfeatures.MapEntry.Companion.tag +import osmfeatures.MapEntry.Companion.mapOf +import org.junit.Test +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue + +class CollectionUtilsTest { + @Test + fun mapContainsEntry() { + val ab = mapOf(tag("a", "b")).entries.iterator().next() + val cd = mapOf(tag("c", "d")).entries.iterator().next() + val ef = mapOf(tag("e", "f")).entries.iterator().next() + val map = mapOf(tag("a", "b"), tag("c", "d")) + assertTrue(CollectionUtils.mapContainsEntry(map, ab)) + assertTrue(CollectionUtils.mapContainsEntry(map, cd)) + assertFalse(CollectionUtils.mapContainsEntry(map, ef)) + } + + @Test + fun numberOfContainedEntriesInMap() { + val ab = mapOf(tag("a", "b")) + val abcdef = mapOf(tag("a", "b"), tag("c", "d"), tag("e", "f")) + val map = mapOf(tag("a", "b"), tag("c", "d")) + assertEquals(0, CollectionUtils.numberOfContainedEntriesInMap(map, emptyMap().entries)) + assertEquals(1, CollectionUtils.numberOfContainedEntriesInMap(map, ab.entries)) + assertEquals(2, CollectionUtils.numberOfContainedEntriesInMap(map, map.entries)) + assertEquals(2, CollectionUtils.numberOfContainedEntriesInMap(map, abcdef.entries)) + } + + @Test + fun mapContainsAllEntries() { + val ab = mapOf(tag("a", "b")) + val abcdef = mapOf(tag("a", "b"), tag("c", "d"), tag("e", "f")) + val map = mapOf(tag("a", "b"), tag("c", "d")) + assertTrue(CollectionUtils.mapContainsAllEntries(map, emptyMap().entries)) + assertTrue(CollectionUtils.mapContainsAllEntries(map, ab.entries)) + assertTrue(CollectionUtils.mapContainsAllEntries(map, map.entries)) + assertFalse(CollectionUtils.mapContainsAllEntries(map, abcdef.entries)) + } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt b/library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt new file mode 100644 index 0000000..1ab7061 --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt @@ -0,0 +1,57 @@ +package osmfeatures + +import de.westnordost.osmfeatures.ContainedMapTree +import org.junit.Test +import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder +import de.westnordost.osmfeatures.TestUtils.listOf +import osmfeatures.MapEntry.Companion.tag +import osmfeatures.MapEntry.Companion.mapOf +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class ContainedMapTreeTest { + @Test + fun copes_with_empty_feature_collection() { + val t = tree(emptyList>()) + assertTrue(t.getAll(mapOf(tag("a", "b"))).isEmpty()) + } + + @Test + fun find_single_map() { + val f1: Map = mapOf(tag("a", "b")) + val t: ContainedMapTree = tree(listOf(f1)) + assertEquals(listOf(f1), t.getAll(mapOf(tag("a", "b"), tag("c", "d")))) + } + + @Test + fun dont_find_single_map() { + val tree: ContainedMapTree = tree(listOf(mapOf(tag("a", "b")))) + assertTrue(tree.getAll(mapOf()).isEmpty()) + assertTrue(tree.getAll(mapOf(tag("c", "d"))).isEmpty()) + assertTrue(tree.getAll(mapOf(tag("a", "c"))).isEmpty()) + } + + @Test + fun find_only_generic_map() { + val f1: Map = mapOf(tag("a", "b")) + val f2: Map = mapOf(tag("a", "b"), tag("c", "d")) + val tree: ContainedMapTree = tree(listOf(f1, f2)) + assertEquals(listOf(f1), tree.getAll(mapOf(tag("a", "b")))) + } + + @Test + fun find_map_with_one_match_and_with_several_matches() { + val f1: Map = mapOf(tag("a", "b")) + val f2: Map = mapOf(tag("a", "b"), tag("c", "d")) + val f3: Map = mapOf(tag("a", "b"), tag("c", "e")) + val f4: Map = mapOf(tag("a", "b"), tag("d", "d")) + val tree: ContainedMapTree = tree(listOf(f1, f2, f3, f4)) + assertEqualsIgnoreOrder(listOf(f1, f2), tree.getAll(mapOf(tag("a", "b"), tag("c", "d")))) + } + + companion object { + private fun tree(items: Collection>): ContainedMapTree { + return ContainedMapTree(items) + } + } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt new file mode 100644 index 0000000..0d11927 --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt @@ -0,0 +1,852 @@ +package osmfeatures + +import de.westnordost.osmfeatures.BaseFeature +import de.westnordost.osmfeatures.Feature +import de.westnordost.osmfeatures.GeometryType +import de.westnordost.osmfeatures.IDLocalizedFeatureCollection +import de.westnordost.osmfeatures.Locale +import de.westnordost.osmfeatures.LocalizedFeature +import org.junit.Test +import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder +import org.junit.Assert.* +import osmfeatures.MapEntry.Companion.tag +import osmfeatures.MapEntry.Companion.mapOf +import java.util.Collections + +class FeatureDictionaryTest { + private val bakery: Feature = feature( // unlocalized shop=bakery + "shop/bakery", + mapOf(tag("shop", "bakery")), + POINT, + listOf("Bäckerei"), + listOf("Brot"), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + false, + null + ) + private val panetteria: Feature = feature( // localized shop=bakery + "shop/bakery", + mapOf(tag("shop", "bakery")), + POINT, + listOf("Panetteria"), + listOf(), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + false, + Locale.ITALIAN + ) + private val ditsch: Feature = feature( // brand in DE for shop=bakery + "shop/bakery/Ditsch", + mapOf(tag("shop", "bakery"), tag("name", "Ditsch")), + POINT, + listOf("Ditsch"), + listOf(), + listOf("DE", "AT"), + listOf("AT-9"), + true, + 1.0f, + mapOf(tag("wikipedia", "de:Brezelb%C3%A4ckerei_Ditsch"), tag("brand", "Ditsch")), + true, + null + ) + private val ditschRussian: Feature = feature( // brand in RU for shop=bakery + "shop/bakery/Дитсч", + mapOf(tag("shop", "bakery"), tag("name", "Ditsch")), + POINT, + listOf("Дитсч"), + listOf(), + listOf("RU", "UA-43"), + listOf(), + true, + 1.0f, + mapOf(tag("wikipedia", "de:Brezelb%C3%A4ckerei_Ditsch"), tag("brand", "Дитсч")), + true, + null + ) + private val ditschInternational: Feature = feature( // brand everywhere for shop=bakery + "shop/bakery/Ditsh", + mapOf(tag("shop", "bakery"), tag("name", "Ditsch")), + POINT, + listOf("Ditsh"), + listOf(), + listOf(), + listOf(), + true, + 1.0f, + mapOf(tag("wikipedia", "de:Brezelb%C3%A4ckerei_Ditsch")), + true, + null + ) + private val liquor_store: Feature = feature( // English localized unspecific shop=alcohol + "shop/alcohol", + mapOf(tag("shop", "alcohol")), + POINT, + listOf("Off licence (Alcohol shop)"), + listOf(), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + false, + Locale.UK + ) + private val car_dealer: Feature = feature( // German localized unspecific shop=car + "shop/car", + mapOf(tag("shop", "car")), + POINT, + listOf("Autohändler"), + listOf("auto"), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + false, + Locale.GERMAN + ) + private val second_hand_car_dealer: Feature = feature( // German localized shop=car with subtags + "shop/car/second_hand", + mapOf(tag("shop", "car"), tag("second_hand", "only")), + POINT, + listOf("Gebrauchtwagenhändler"), + listOf("auto"), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + false, + Locale.GERMAN + ) + private val scheisshaus: Feature = feature( // unsearchable feature + "amenity/scheißhaus", + mapOf(tag("amenity", "scheißhaus")), + POINT, + listOf("Scheißhaus"), + listOf(), + listOf(), + listOf(), + false, // <--- not searchable! + 1.0f, + mapOf(), + false, + null + ) + private val bank: Feature = feature( // unlocalized shop=bank (Bank) + "amenity/bank", + mapOf(tag("amenity", "bank")), + POINT, + listOf("Bank"), + listOf(), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + false, + null + ) + private val bench: Feature = feature( // unlocalized amenity=bench (PARKbank) + "amenity/bench", + mapOf(tag("amenity", "bench")), + POINT, + listOf("Parkbank"), + listOf("Bank"), + listOf(), + listOf(), + true, + 5.0f, + mapOf(), + false, + null + ) + private val casino: Feature = feature( // unlocalized amenity=casino (SPIELbank) + "amenity/casino", + mapOf(tag("amenity", "casino")), + POINT, + listOf("Spielbank"), + listOf("Kasino"), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + false, + null + ) + private val atm: Feature = feature( // unlocalized amenity=atm (BankOMAT) + "amenity/atm", + mapOf(tag("amenity", "atm")), + POINT, + listOf("Bankomat"), + listOf(), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + false, + null + ) + private val stock_exchange: Feature = + feature( // unlocalized amenity=stock_exchange (has "Banking" as term) + "amenity/stock_exchange", + mapOf(tag("amenity", "stock_exchange")), + POINT, + listOf("Börse"), + listOf("Banking"), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + false, + null + ) + private val bank_of_america: Feature = feature( // Brand of a amenity=bank (has "Bank" in name) + "amenity/bank/Bank of America", + mapOf(tag("amenity", "bank"), tag("name", "Bank of America")), + POINT, + listOf("Bank of America"), + listOf(), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + true, + null + ) + private val bank_of_liechtenstein: Feature = + feature( // Brand of a amenity=bank (has "Bank" in name), but low matchScore + "amenity/bank/Bank of Liechtenstein", + mapOf(tag("amenity", "bank"), tag("name", "Bank of Liechtenstein")), + POINT, + listOf("Bank of Liechtenstein"), + listOf(), + listOf(), + listOf(), + true, + 0.2f, + mapOf(), + true, + null + ) + private val deutsche_bank: Feature = + feature( // Brand of a amenity=bank (does not start with "Bank" in name) + "amenity/bank/Deutsche Bank", + mapOf(tag("amenity", "bank"), tag("name", "Deutsche Bank")), + POINT, + listOf("Deutsche Bank"), + listOf(), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + true, + null + ) + private val baenk: Feature = + feature( // amenity=bänk, to see if diacritics match non-strictly ("a" finds "ä") + "amenity/bänk", + mapOf(tag("amenity", "bänk")), + POINT, + listOf("Bänk"), + listOf(), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + false, + null + ) + private val bad_bank: Feature = + feature( // amenity=bank with subtags that has "Bank" in name but it is not the first word + "amenity/bank/bad", + mapOf(tag("amenity", "bank"), tag("goodity", "bad")), + POINT, + listOf("Bad Bank"), + listOf(), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + false, + null + ) + private val thieves_guild: Feature = feature( // only has "bank" in an alias + "amenity/thieves_guild", + mapOf(tag("amenity", "thieves_guild")), + POINT, + listOf("Diebesgilde", "Bankräuberausbildungszentrum"), + listOf(), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + false, + null + ) + private val miniature_train_shop: Feature = + feature( // feature whose name consists of several words + "shop/miniature_train", + mapOf(tag("shop", "miniature_train")), + POINT, + listOf("Miniature Train Shop"), + listOf(), + listOf(), + listOf(), + true, + 1.0f, + mapOf(), + false, + null + ) + + //region by tags + @Test + fun find_no_entry_by_tags() { + val tags: Map = mapOf(tag("shop", "supermarket")) + val dictionary: FeatureDictionary = dictionary(bakery) + assertEquals(emptyList(), dictionary.byTags(tags).find()) + } + + @Test + fun find_no_entry_because_wrong_geometry() { + val tags: Map = mapOf(tag("shop", "bakery")) + val dictionary: FeatureDictionary = dictionary(bakery) + assertEquals(emptyList(), dictionary.byTags(tags).forGeometry(GeometryType.RELATION).find()) + } + + @Test + fun find_no_entry_because_wrong_locale() { + val tags: Map = mapOf(tag("shop", "bakery")) + val dictionary: FeatureDictionary = dictionary(bakery) + assertEquals(emptyList(), dictionary.byTags(tags).forLocale(Locale.ITALIAN).find()) + } + + @Test + fun find_entry_because_fallback_locale() { + val tags: Map = mapOf(tag("shop", "bakery")) + val dictionary: FeatureDictionary = dictionary(bakery) + val matches: List = dictionary.byTags(tags).forLocale(Locale.ITALIAN, null).find() + assertEquals(listOf(bakery), matches) + } + + @Test + fun find_entry_by_tags() { + val tags: Map = mapOf(tag("shop", "bakery")) + val dictionary: FeatureDictionary = dictionary(bakery) + val matches: List = dictionary.byTags(tags).find() + assertEquals(listOf(bakery), matches) + } + + @Test + fun find_non_searchable_entry_by_tags() { + val tags: Map = mapOf(tag("amenity", "scheißhaus")) + val dictionary: FeatureDictionary = dictionary(scheisshaus) + val matches: List = dictionary.byTags(tags).find() + assertEquals(listOf(scheisshaus), matches) + } + + @Test + fun find_entry_by_tags_correct_geometry() { + val tags: Map = mapOf(tag("shop", "bakery")) + val dictionary: FeatureDictionary = dictionary(bakery) + val matches: List = dictionary.byTags(tags).forGeometry(GeometryType.POINT).find() + assertEquals(listOf(bakery), matches) + } + + @Test + fun find_brand_entry_by_tags() { + val tags: Map = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")) + val dictionary: FeatureDictionary = dictionary(bakery, ditsch) + val matches: List = dictionary.byTags(tags).inCountry("DE").find() + assertEquals(listOf(ditsch), matches) + } + + @Test + fun find_only_entries_with_given_locale() { + val tags: Map = mapOf(tag("shop", "bakery")) + val dictionary: FeatureDictionary = dictionary(bakery, panetteria) + assertEquals(listOf(panetteria), dictionary.byTags(tags).forLocale(Locale.ITALIAN).find()) + assertEquals(emptyList(), dictionary.byTags(tags).forLocale(Locale.ENGLISH).find()) + assertEquals(listOf(bakery), dictionary.byTags(tags).forLocale(null as Locale?).find()) + } + + @Test + fun find_only_brands_finds_no_normal_entries() { + val tags: Map = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")) + val dictionary: FeatureDictionary = dictionary(bakery) + val matches: List = dictionary.byTags(tags).isSuggestion(true).find() + assertEquals(0, matches.size) + } + + @Test + fun find_no_brands_finds_only_normal_entries() { + val tags: Map = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")) + val dictionary: FeatureDictionary = dictionary(bakery, ditsch) + val matches: List = dictionary.byTags(tags).isSuggestion(false).find() + assertEquals(listOf(bakery), matches) + } + + @Test + fun find_multiple_brands_sorts_by_locale() { + val tags: Map = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")) + val dictionary: FeatureDictionary = dictionary(ditschRussian, ditschInternational, ditsch) + val matches: List = dictionary.byTags(tags).forLocale(null as Locale?).find() + assertEquals(ditschInternational, matches[0]) + } + + @Test + fun find_multiple_entries_by_tags() { + val tags: Map = mapOf(tag("shop", "bakery"), tag("amenity", "bank")) + val dictionary: FeatureDictionary = dictionary(bakery, bank) + val matches: List = dictionary.byTags(tags).find() + assertEquals(2, matches.size) + } + + @Test + fun do_not_find_entry_with_too_specific_tags() { + val tags: Map = mapOf(tag("shop", "car")) + val dictionary: FeatureDictionary = dictionary(car_dealer, second_hand_car_dealer) + val matches: List = dictionary.byTags(tags).forLocale(Locale.GERMAN, null).find() + assertEquals(listOf(car_dealer), matches) + } + + @Test + fun find_entry_with_specific_tags() { + val tags: Map = mapOf(tag("shop", "car"), tag("second_hand", "only")) + val dictionary: FeatureDictionary = dictionary(car_dealer, second_hand_car_dealer) + val matches: List = dictionary.byTags(tags).forLocale(Locale.GERMAN, null).find() + assertEquals(listOf(second_hand_car_dealer), matches) + } + + //endregion + //region by name + @Test + fun find_no_entry_by_name() { + val dictionary: FeatureDictionary = dictionary(bakery) + assertEquals(emptyList(), dictionary.byTerm("Supermarkt").find()) + } + + @Test + fun find_no_entry_by_name_because_wrong_geometry() { + val dictionary: FeatureDictionary = dictionary(bakery) + assertEquals(emptyList(), dictionary.byTerm("Bäckerei").forGeometry(GeometryType.LINE).find()) + } + + @Test + fun find_no_entry_by_name_because_wrong_country() { + val dictionary: FeatureDictionary = dictionary(ditsch, ditschRussian) + assertEquals(emptyList(), dictionary.byTerm("Ditsch").find()) + assertEquals(emptyList(), dictionary.byTerm("Ditsch").inCountry("FR").find()) // not in France + assertEquals( + emptyList(), + dictionary.byTerm("Ditsch").inCountry("AT-9").find() + ) // in all of AT but not Vienna + assertEquals( + emptyList(), + dictionary.byTerm("Дитсч").inCountry("UA").find() + ) // only on the Krim + } + + @Test + fun find_no_non_searchable_entry_by_name() { + val dictionary: FeatureDictionary = dictionary(scheisshaus) + assertEquals(emptyList(), dictionary.byTerm("Scheißhaus").find()) + } + + @Test + fun find_entry_by_name() { + val dictionary: FeatureDictionary = dictionary(bakery) + val matches: List = dictionary.byTerm("Bäckerei").forLocale(null as Locale?).find() + assertEquals(listOf(bakery), matches) + } + + @Test + fun find_entry_by_name_with_correct_geometry() { + val dictionary: FeatureDictionary = dictionary(bakery) + val matches: List = + dictionary.byTerm("Bäckerei").forLocale(null as Locale?).forGeometry(GeometryType.POINT) + .find() + assertEquals(listOf(bakery), matches) + } + + @Test + fun find_entry_by_name_with_correct_country() { + val dictionary: FeatureDictionary = dictionary(ditsch, ditschRussian, bakery) + assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("DE").find()) + assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("DE-TH").find()) + assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("AT").find()) + assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("AT-5").find()) + assertEquals(listOf(ditschRussian), dictionary.byTerm("Дитсч").inCountry("UA-43").find()) + assertEquals(listOf(ditschRussian), dictionary.byTerm("Дитсч").inCountry("RU-KHA").find()) + } + + @Test + fun find_entry_by_name_case_insensitive() { + val dictionary: FeatureDictionary = dictionary(bakery) + val matches: List = dictionary.byTerm("BÄCkErEI").forLocale(null as Locale?).find() + assertEquals(listOf(bakery), matches) + } + + @Test + fun find_entry_by_name_diacritics_insensitive() { + val dictionary: FeatureDictionary = dictionary(bakery) + val matches: List = dictionary.byTerm("Backérèi").forLocale(null as Locale?).find() + assertEquals(listOf(bakery), matches) + } + + @Test + fun find_entry_by_term() { + val dictionary: FeatureDictionary = dictionary(bakery) + val matches: List = dictionary.byTerm("bro").forLocale(null as Locale?).find() + assertEquals(listOf(bakery), matches) + } + + @Test + fun find_entry_by_term_brackets() { + val dictionary: FeatureDictionary = dictionary(liquor_store) + assertEquals(listOf(liquor_store), dictionary.byTerm("Alcohol").forLocale(Locale.UK).find()) + assertEquals( + listOf(liquor_store), + dictionary.byTerm("Off licence (Alcohol Shop)").forLocale(Locale.UK).find() + ) + assertEquals( + listOf(liquor_store), + dictionary.byTerm("Off Licence").forLocale(Locale.UK).find() + ) + assertEquals( + listOf(liquor_store), + dictionary.byTerm("Off Licence (Alco").forLocale(Locale.UK).find() + ) + } + + @Test + fun find_entry_by_term_case_insensitive() { + val dictionary: FeatureDictionary = dictionary(bakery) + val matches: List = dictionary.byTerm("BRO").forLocale(null as Locale?).find() + assertEquals(listOf(bakery), matches) + } + + @Test + fun find_entry_by_term_diacritics_insensitive() { + val dictionary: FeatureDictionary = dictionary(bakery) + val matches: List = dictionary.byTerm("bró").forLocale(null as Locale?).find() + assertEquals(listOf(bakery), matches) + } + + @Test + fun find_multiple_entries_by_term() { + val dictionary: FeatureDictionary = dictionary(second_hand_car_dealer, car_dealer) + val matches: List = dictionary.byTerm("auto").forLocale(Locale.GERMAN).find() + assertEqualsIgnoreOrder(listOf(second_hand_car_dealer, car_dealer), matches) + } + + @Test + fun find_multiple_entries_by_name_but_respect_limit() { + val dictionary: FeatureDictionary = dictionary(second_hand_car_dealer, car_dealer) + val matches: List = + dictionary.byTerm("auto").forLocale(Locale.GERMAN).limit(1).find() + assertEquals(1, matches.size) + } + + @Test + fun find_only_brands_by_name_finds_no_normal_entries() { + val dictionary: FeatureDictionary = dictionary(bakery) + val matches: List = + dictionary.byTerm("Bäckerei").forLocale(null as Locale?).isSuggestion(true).find() + assertEquals(0, matches.size) + } + + @Test + fun find_no_brands_by_name_finds_only_normal_entries() { + val dictionary: FeatureDictionary = dictionary(bank, bank_of_america) + val matches: List = + dictionary.byTerm("Bank").forLocale(null as Locale?).isSuggestion(false).find() + assertEquals(listOf(bank), matches) + } + + @Test + fun find_no_entry_by_term_because_wrong_locale() { + val dictionary: FeatureDictionary = dictionary(bakery) + assertEquals(emptyList(), dictionary.byTerm("Bäck").forLocale(Locale.ITALIAN).find()) + } + + @Test + fun find_entry_by_term_because_fallback_locale() { + val dictionary: FeatureDictionary = dictionary(bakery) + val matches: List = + dictionary.byTerm("Bäck").forLocale(Locale.ITALIAN, null).find() + assertEquals(listOf(bakery), matches) + } + + @Test + fun find_multi_word_brand_feature() { + val dictionary: FeatureDictionary = dictionary(deutsche_bank) + assertEquals(listOf(deutsche_bank), dictionary.byTerm("Deutsche Ba").find()) + assertEquals(listOf(deutsche_bank), dictionary.byTerm("Deut").find()) + // by-word only for non-brand features + assertTrue(dictionary.byTerm("Ban").find().isEmpty()) + } + + @Test + fun find_multi_word_feature() { + val dictionary: FeatureDictionary = dictionary(miniature_train_shop) + assertEquals( + listOf(miniature_train_shop), + dictionary.byTerm("mini").forLocale(null as Locale?).find() + ) + assertEquals( + listOf(miniature_train_shop), + dictionary.byTerm("train").forLocale(null as Locale?).find() + ) + assertEquals( + listOf(miniature_train_shop), + dictionary.byTerm("shop").forLocale(null as Locale?).find() + ) + assertEquals( + listOf(miniature_train_shop), + dictionary.byTerm("Miniature Trai").forLocale(null as Locale?).find() + ) + assertEquals( + listOf(miniature_train_shop), + dictionary.byTerm("Miniature Train Shop").forLocale(null as Locale?).find() + ) + assertTrue(dictionary.byTerm("Train Sho").forLocale(null as Locale?).find().isEmpty()) + } + + @Test + fun find_entry_by_tag_value() { + val dictionary: FeatureDictionary = dictionary(panetteria) + val matches: List = dictionary.byTerm("bakery").forLocale(Locale.ITALIAN).find() + assertEquals(listOf(panetteria), matches) + } + + //endregion + //region by id + @Test + fun find_no_entry_by_id() { + val dictionary: FeatureDictionary = dictionary(bakery) + assertNull(dictionary.byId("amenity/hospital").get()) + } + + @Test + fun find_no_entry_by_id_because_unlocalized_results_are_excluded() { + val dictionary: FeatureDictionary = dictionary(bakery) + assertNull(dictionary.byId("shop/bakery").forLocale(Locale.ITALIAN).get()) + } + + @Test + fun find_entry_by_id() { + val dictionary: FeatureDictionary = dictionary(bakery) + assertEquals(bakery, dictionary.byId("shop/bakery").get()) + assertEquals(bakery, dictionary.byId("shop/bakery").forLocale(Locale.CHINESE, null).get()) + } + + @Test + fun find_localized_entry_by_id() { + val dictionary: FeatureDictionary = dictionary(panetteria) + assertEquals(panetteria, dictionary.byId("shop/bakery").forLocale(Locale.ITALIAN).get()) + assertEquals( + panetteria, + dictionary.byId("shop/bakery").forLocale(Locale.ITALIAN, null).get() + ) + } + + @Test + fun find_no_entry_by_id_because_wrong_country() { + val dictionary: FeatureDictionary = dictionary(ditsch) + assertNull(dictionary.byId("shop/bakery/Ditsch").get()) + assertNull(dictionary.byId("shop/bakery/Ditsch").inCountry("IT").get()) + assertNull(dictionary.byId("shop/bakery/Ditsch").inCountry("AT-9").get()) + } + + @Test + fun find_entry_by_id_in_country() { + val dictionary: FeatureDictionary = dictionary(ditsch) + assertEquals(ditsch, dictionary.byId("shop/bakery/Ditsch").inCountry("AT").get()) + assertEquals(ditsch, dictionary.byId("shop/bakery/Ditsch").inCountry("DE").get()) + } + + //endregion + @Test + fun find_by_term_sorts_result_in_correct_order() { + val dictionary: FeatureDictionary = dictionary( + casino, baenk, bad_bank, stock_exchange, bank_of_liechtenstein, bank, bench, atm, + bank_of_america, deutsche_bank, thieves_guild + ) + val matches: List = dictionary.byTerm("Bank").forLocale(null as Locale?).find() + assertEquals( + listOf( + bank, // exact name matches + baenk, // exact name matches (diacritics and case insensitive) + atm, // starts-with name matches + thieves_guild, // starts-with alias matches + bad_bank, // starts-with-word name matches + bank_of_america, // starts-with brand name matches + bank_of_liechtenstein, // starts-with brand name matches - lower matchScore + bench, // found word in terms - higher matchScore + stock_exchange // found word in terms - lower matchScore + // casino, // not included: "Spielbank" does not start with "bank" + // deutsche_bank // not included: "Deutsche Bank" does not start with "bank" and is a brand + ), matches + ) + } + + @Test + fun some_tests_with_real_data() { + val featureCollection: LocalizedFeatureCollection = + IDLocalizedFeatureCollection(LivePresetDataAccessAdapter()) + featureCollection.getAll(listOf(Locale.ENGLISH)) + val dictionary = FeatureDictionary(featureCollection, null) + val matches: List = dictionary + .byTags(mapOf(tag("amenity", "studio"))) + .forLocale(Locale.ENGLISH) + .find() + assertEquals(1, matches.size) + assertEquals("Studio", matches[0].name) + val matches2: List = dictionary + .byTags(mapOf(tag("amenity", "studio"), tag("studio", "audio"))) + .forLocale(Locale.ENGLISH) + .find() + assertEquals(1, matches2.size) + assertEquals("Recording Studio", matches2[0].name) + val matches3: List = dictionary + .byTerm("Chinese Res") + .forLocale(Locale.ENGLISH) + .find() + assertEquals(1, matches3.size) + assertEquals("Chinese Restaurant", matches3[0].name) + } + + @Test + fun issue19() { + val lush: Feature = feature( + "shop/cosmetics/lush-a08666", + mapOf(tag("brand:wikidata", "Q1585448"), tag("shop", "cosmetics")), + listOf(GeometryType.POINT, GeometryType.AREA), + listOf("Lush"), + listOf("lush"), + listOf(), + listOf(), + true, + 2.0f, + mapOf( + tag("brand", "Lush"), + tag("brand:wikidata", "Q1585448"), + tag("name", "Lush"), + tag("shop", "cosmetics") + ), + true, + null + ) + val dictionary: FeatureDictionary = dictionary(lush) + val byTags: List = dictionary + .byTags(mapOf(tag("brand:wikidata", "Q1585448"), tag("shop", "cosmetics"))) + .forLocale(Locale.GERMAN, null) + .inCountry("DE") + .find() + assertEquals(1, byTags.size) + assertEquals(lush, byTags[0]) + val byTerm: List = dictionary + .byTerm("Lush") + .forLocale(Locale.GERMAN, null) + .inCountry("DE") + .find() + assertEquals(1, byTerm.size) + assertEquals(lush, byTerm[0]) + val byId: Feature? = dictionary + .byId("shop/cosmetics/lush-a08666") + .forLocale(Locale.GERMAN, null) + .inCountry("DE") + .get() + assertEquals(lush, byId) + } + + internal class SuggestionFeature( + id: String, + tags: Map, + geometry: List, + icon: String?, + imageURL: String?, + names: List, + terms: List, + includeCountryCodes: List, + excludeCountryCodes: List, + searchable: Boolean, + matchScore: Float, + addTags: Map, + removeTags: Map + ) : BaseFeature( + id, + tags, + geometry, + icon, + imageURL, + names, + terms, + includeCountryCodes, + excludeCountryCodes, + searchable, + matchScore, + true, + addTags, + removeTags + ) + + companion object { + private val POINT: List = Collections.singletonList(GeometryType.POINT) + private fun dictionary(vararg entries: Feature): FeatureDictionary { + return FeatureDictionary( + TestLocalizedFeatureCollection(entries.filterNot { it is SuggestionFeature }), + TestPerCountryFeatureCollection(entries.filterIsInstance()) + ) + } + + private fun feature( + id: String, + tags: Map, + geometries: List, + names: List, + terms: List, + countryCodes: List, + excludeCountryCodes: List, + searchable: Boolean, + matchScore: Float, + addTags: Map, + isSuggestion: Boolean, + locale: Locale? + ): Feature { + return if (isSuggestion) { + SuggestionFeature( + id, tags, geometries, null, null, names, terms, countryCodes, + excludeCountryCodes, searchable, matchScore, addTags, mapOf() + ) + } else { + val f = BaseFeature( + id, tags, geometries, null, null, names, terms, countryCodes, + excludeCountryCodes, searchable, matchScore, false, addTags, mapOf() + ) + if (locale != null) { + LocalizedFeature(f, locale, f.names, f.terms.orEmpty()) + } else { + f + } + } + } + } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt b/library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt new file mode 100644 index 0000000..dcf5c2a --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt @@ -0,0 +1,65 @@ +package osmfeatures + +import de.westnordost.osmfeatures.BaseFeature +import de.westnordost.osmfeatures.Feature +import de.westnordost.osmfeatures.GeometryType +import org.junit.Test +import osmfeatures.MapEntry.Companion.tag +import osmfeatures.MapEntry.Companion.mapOf +import de.westnordost.osmfeatures.TestUtils.listOf +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class FeatureTagsIndexTest { + @Test + fun copes_with_empty_collection() { + val index: FeatureTagsIndex = index() + assertTrue(index.getAll(mapOf(tag("a", "b"))).isEmpty()) + } + + @Test + fun get_two_features_with_same_tags() { + val f1: Feature = feature(tag("a", "b")) + val f2: Feature = feature(tag("a", "b")) + val index: FeatureTagsIndex = index(f1, f2) + assertEquals( + listOf(f1, f2), + index.getAll(mapOf(tag("a", "b"), tag("c", "d"))) + ) + } + + @Test + fun get_two_features_with_different_tags() { + val f1: Feature = feature(tag("a", "b")) + val f2: Feature = feature(tag("c", "d")) + val index: FeatureTagsIndex = index(f1, f2) + assertEquals( + listOf(f1, f2), + index.getAll(mapOf(tag("a", "b"), tag("c", "d"))) + ) + } + + companion object { + private fun index(vararg features: Feature): FeatureTagsIndex { + return FeatureTagsIndex(features.toList()) + } + + private fun feature(vararg mapEntries: MapEntry): Feature { + return BaseFeature( + "id", + mapOf(*mapEntries), + listOf(GeometryType.POINT), + null, null, + listOf("name"), + listOf(), + listOf(), + listOf(), + true, + 1.0f, + false, + mapOf(), + mapOf() + ) + } + } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt b/library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt new file mode 100644 index 0000000..7538a4d --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt @@ -0,0 +1,85 @@ +package de.westnordost.osmfeatures + +import org.junit.Test +import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder +import de.westnordost.osmfeatures.TestUtils.listOf +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import osmfeatures.FeatureTermIndex + +class FeatureTermIndexTest { + @Test + fun copes_with_empty_collection() { + val index: FeatureTermIndex = index() + assertTrue(index.getAll("a").isEmpty()) + } + + @Test + fun get_one_features_with_same_term() { + val f1: Feature = feature("a", "b") + val f2: Feature = feature("c") + val index: FeatureTermIndex = index(f1, f2) + assertEqualsIgnoreOrder( + listOf(f1), + index.getAll("b") + ) + } + + @Test + fun get_two_features_with_same_term() { + val f1: Feature = feature("a", "b") + val f2: Feature = feature("a", "c") + val index: FeatureTermIndex = index(f1, f2) + assertEqualsIgnoreOrder( + listOf(f1, f2), + index.getAll("a") + ) + } + + @Test + fun get_two_features_with_different_terms() { + val f1: Feature = feature("anything") + val f2: Feature = feature("anybody") + val index: FeatureTermIndex = index(f1, f2) + assertEqualsIgnoreOrder( + listOf(f1, f2), + index.getAll("any") + ) + assertEqualsIgnoreOrder( + listOf(f1), + index.getAll("anyt") + ) + } + + @Test + fun dont_get_one_feature_twice() { + val f1: Feature = feature("something", "someone") + val index: FeatureTermIndex = index(f1) + assertEquals(listOf(f1), index.getAll("some")) + } + + companion object { + private fun index(vararg features: Feature): FeatureTermIndex { + return FeatureTermIndex(features.toList()) { feature: Feature -> feature.terms } + } + + private fun feature(vararg terms: String): Feature { + return BaseFeature( + "id", + mapOf(), + listOf(GeometryType.POINT), + null, + null, + listOf("name"), + terms.toList(), + listOf(), + listOf(), + true, + 1.0f, + false, + mapOf(), + mapOf() + ) + } + } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt b/library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt new file mode 100644 index 0000000..c39d47c --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt @@ -0,0 +1,74 @@ +package osmfeatures + +import de.westnordost.osmfeatures.Feature +import de.westnordost.osmfeatures.IDBrandPresetsFeatureCollection +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder +import de.westnordost.osmfeatures.TestUtils.listOf +import okio.FileSystem +import okio.Path +import okio.Path.Companion.toPath +import okio.Source +import okio.source +import org.junit.Test +import java.io.IOException +import java.util.ArrayList + +class IDBrandPresetsFeatureCollectionTest { + @Test + fun load_brands() { + val c = IDBrandPresetsFeatureCollection(object : FileAccessAdapter { + @Override + override fun exists(name: String): Boolean { + return name == "presets.json" + } + + @Override + @Throws(IOException::class) + override fun open(name: String): Source { + if (name == "presets.json") return getSource("brand_presets_min.json") + throw IOException("wrong file name") + } + }) + assertEqualsIgnoreOrder( + listOf("Duckworths", "Megamall"), + getNames(c.getAll(listOf(null as String?))) + ) + assertEquals("Duckworths", c["a/brand", listOf(null as String?)]?.name) + assertEquals("Megamall", c["another/brand", listOf(null as String?)]?.name) + } + + @Test + fun load_brands_by_country() { + val c = IDBrandPresetsFeatureCollection(object : FileAccessAdapter { + @Override + override fun exists(name: String): Boolean { + return name == "presets-DE.json" + } + + @Override + @Throws(IOException::class) + override fun open(name: String): Source { + if (name == "presets-DE.json") return getSource("brand_presets_min2.json") + throw IOException("File not found") + } + }) + assertEqualsIgnoreOrder(listOf("Talespin"), getNames(c.getAll(listOf("DE")))) + assertEquals("Talespin", c["yet_another/brand", listOf("DE")]?.name) + c["yet_another/brand", listOf("DE")]?.isSuggestion?.let { assertTrue(it) } + } + + @kotlin.Throws(IOException::class) + private fun getSource(file: String): Source { + val resourceStream = this.javaClass.getResourceAsStream(file) + ?: throw IOException("Could not retrieve file $file in resource assets") + return resourceStream.source() + } + + companion object { + private fun getNames(features: Collection): Collection { + return features.map { it.name } + } + } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt b/library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt new file mode 100644 index 0000000..05cb52e --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt @@ -0,0 +1,157 @@ +package osmfeatures + +import de.westnordost.osmfeatures.Feature +import de.westnordost.osmfeatures.IDLocalizedFeatureCollection +import de.westnordost.osmfeatures.Locale +import okio.FileSystem +import okio.Source +import org.junit.Test +import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder +import de.westnordost.osmfeatures.TestUtils.listOf +import okio.FileNotFoundException +import okio.IOException +import okio.Path.Companion.toPath +import org.junit.Assert.* + +class IDLocalizedFeatureCollectionTest { + @Test + fun features_not_found_produces_runtime_exception() { + try { + IDLocalizedFeatureCollection(object : FileAccessAdapter { + @Override + override fun exists(name: String): Boolean { + return false + } + + @Override + @Throws(IOException::class) + override fun open(name: String): Source { + throw FileNotFoundException() + } + }) + fail() + } catch (ignored: RuntimeException) { + } + } + + @Test + fun load_features_and_two_localizations() { + val c = IDLocalizedFeatureCollection(object : FileAccessAdapter { + @Override + override fun exists(name: String): Boolean { + return listOf("presets.json", "en.json", "de.json").contains(name) + } + + @Override + @Throws(IOException::class) + override fun open(name: String): Source { + if (name == "presets.json") return getSource("some_presets_min.json") + if (name == "en.json") return getSource("localizations_en.json") + if (name == "de.json") return getSource("localizations_de.json") + throw IOException("File not found") + } + }) + + // getting non-localized features + val notLocalized: List = listOf(null as Locale?) + val notLocalizedFeatures: Collection = c.getAll(notLocalized) + assertEqualsIgnoreOrder(listOf("test", "test", "test"), getNames(notLocalizedFeatures)) + assertEquals("test", c["some/id", notLocalized]?.name) + assertEquals("test", c["another/id", notLocalized]?.name) + assertEquals("test", c["yet/another/id", notLocalized]?.name) + + // getting English features + val english: List = listOf(Locale.ENGLISH) + val englishFeatures: Collection = c.getAll(english) + assertEqualsIgnoreOrder(listOf("Bakery"), getNames(englishFeatures)) + assertEquals("Bakery", c["some/id", english]?.name) + assertNull(c["another/id", english]) + assertNull(c["yet/another/id", english]) + + // getting Germany features + // this also tests if the fallback from de-DE to de works if de-DE.json does not exist + val germany: List = listOf(Locale.GERMANY) + val germanyFeatures: Collection = c.getAll(germany) + assertEqualsIgnoreOrder(listOf("Bäckerei", "Gullideckel"), getNames(germanyFeatures)) + assertEquals("Bäckerei", c["some/id", germany]?.name) + assertEquals("Gullideckel", c["another/id", germany]?.name) + assertNull(c["yet/another/id", germany]) + + // getting features through fallback chain + val locales: List = listOf(Locale.ENGLISH, Locale.GERMANY, null) + val fallbackFeatures: Collection = c.getAll(locales) + assertEqualsIgnoreOrder(listOf("Bakery", "Gullideckel", "test"), getNames(fallbackFeatures)) + assertEquals("Bakery", c["some/id", locales]?.name) + assertEquals("Gullideckel", c["another/id", locales]?.name) + assertEquals("test", c["yet/another/id", locales]?.name) + assertEquals(Locale.ENGLISH, c["some/id", locales]?.locale) + assertEquals(Locale.GERMAN, c["another/id", locales]?.locale) + assertNull(c["yet/another/id", locales]?.locale) + } + + @Test + fun load_features_and_merge_localizations() { + val c = IDLocalizedFeatureCollection(object : FileAccessAdapter { + @Override + override fun exists(name: String): Boolean { + return listOf( + "presets.json", + "de-AT.json", + "de.json", + "de-Cyrl.json", + "de-Cyrl-AT.json" + ).contains(name) + } + + @Override + @Throws(IOException::class) + override fun open(name: String): Source { + if (name == "presets.json") return getSource("some_presets_min.json") + if (name == "de-AT.json") return getSource("localizations_de-AT.json") + if (name == "de.json") return getSource("localizations_de.json") + if (name == "de-Cyrl-AT.json") return getSource("localizations_de-Cyrl-AT.json") + if (name == "de-Cyrl.json") return getSource("localizations_de-Cyrl.json") + throw IOException("File not found") + } + }) + + // standard case - no merging + val german: List = listOf(Locale.GERMAN) + val germanFeatures: Collection = c.getAll(german) + assertEqualsIgnoreOrder(listOf("Bäckerei", "Gullideckel"), getNames(germanFeatures)) + assertEquals("Bäckerei", c["some/id", german]?.name) + assertEquals("Gullideckel", c["another/id", german]?.name) + assertNull(c["yet/another/id", german]) + + // merging de-AT and de + val austria: List = listOf(Locale("de", "AT")) + val austrianFeatures: Collection = c.getAll(austria) + assertEqualsIgnoreOrder( + listOf("Backhusl", "Gullideckel", "Brückle"), + getNames(austrianFeatures) + ) + assertEquals("Backhusl", c["some/id", austria]?.name) + assertEquals("Gullideckel", c["another/id", austria]?.name) + assertEquals("Brückle", c["yet/another/id", austria]?.name) + + // merging scripts + val cryllic: List = listOf(Locale.Builder().setLanguage("de").setScript("Cyrl").build()) + assertEquals("бацкхаус", c["some/id", cryllic]?.name) + val cryllicAustria: List = + listOf(Locale.Builder().setLanguage("de").setRegion("AT").setScript("Cyrl").build()) + assertEquals("бацкхусл", c["some/id", cryllicAustria]?.name) + } + + @kotlin.Throws(IOException::class) + private fun getSource(file: String): Source { + return FileSystem.SYSTEM.source( + file.toPath() + ) + } + + companion object { + private fun getNames(features: Collection): Collection { + return features.map { it.name } + } + } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt b/library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt new file mode 100644 index 0000000..9620a35 --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt @@ -0,0 +1,106 @@ +package de.westnordost.osmfeatures + +import okio.Okio +import okio.Source +import org.junit.Test +import osmfeatures.MapEntry.Companion.tag +import osmfeatures.MapEntry.Companion.mapOf +import de.westnordost.osmfeatures.TestUtils.listOf +import okio.IOException +import okio.Path.Companion.toPath +import okio.source +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import java.net.URL + +class IDPresetsJsonParserTest { + @Test + fun load_features_only() { + val features: List = parse("one_preset_full.json") + assertEquals(1, features.size) + val feature: Feature = features[0] + assertEquals("some/id", feature.id) + assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature.tags) + assertEquals( + listOf( + GeometryType.POINT, + GeometryType.VERTEX, + GeometryType.LINE, + GeometryType.AREA, + GeometryType.RELATION + ), feature.geometry + ) + assertEquals(listOf("DE", "GB"), feature.includeCountryCodes) + assertEquals(listOf("IT"), feature.excludeCountryCodes) + assertEquals("foo", feature.name) + assertEquals("abc", feature.icon) + assertEquals("someurl", feature.imageURL) + assertEquals(listOf("foo", "one", "two"), feature.names) + assertEquals(listOf("1", "2"), feature.terms) + assertEquals(0.5f, feature.matchScore, 0.001f) + assertFalse(feature.isSearchable) + assertEquals(mapOf(tag("e", "f")), feature.addTags) + assertEquals(mapOf(tag("d", "g")), feature.removeTags) + } + + @Test + fun load_features_only_defaults() { + val features: List = parse("one_preset_min.json") + assertEquals(1, features.size) + val feature: Feature = features[0] + assertEquals("some/id", feature.id) + assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature.tags) + assertEquals(listOf(GeometryType.POINT), feature.geometry) + assertTrue(feature.includeCountryCodes.isEmpty()) + assertTrue(feature.excludeCountryCodes.isEmpty()) + assertEquals("", feature.name) + assertEquals("", feature.icon) + assertEquals("", feature.imageURL) + assertEquals(1, feature.names.size) + assertTrue(feature.terms.isEmpty()) + assertEquals(1.0f, feature.matchScore, 0.001f) + assertTrue(feature.isSearchable) + assertEquals(feature.addTags, feature.tags) + assertEquals(feature.addTags, feature.removeTags) + } + + @Test + fun load_features_unsupported_location_set() { + val features: List = parse("one_preset_unsupported_location_set.json") + assertEquals(2, features.size) + assertEquals("some/ok", features[0].id) + assertEquals("another/ok", features[1].id) + } + + @Test + fun load_features_no_wildcards() { + val features: List = parse("one_preset_wildcard.json") + assertTrue(features.isEmpty()) + } + + @Test + @kotlin.Throws(IOException::class) + fun parse_some_real_data() { + val url = + URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") + val features: List = IDPresetsJsonParser().parse(url.openStream().source()) + // should not crash etc + assertTrue(features.size > 1000) + } + + private fun parse(file: String): List { + return try { + IDPresetsJsonParser().parse(getSource(file)) + } catch (e: IOException) { + throw RuntimeException() + } + } + + @kotlin.Throws(IOException::class) + private fun getSource(file: String): Source { + val resourceStream = this.javaClass.getResourceAsStream(file) + ?: throw IOException("Could not retrieve file $file in resource assets") + return resourceStream.source() + } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt new file mode 100644 index 0000000..38e26b8 --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -0,0 +1,108 @@ +package osmfeatures + +import de.westnordost.osmfeatures.BaseFeature +import de.westnordost.osmfeatures.Feature +import de.westnordost.osmfeatures.GeometryType +import de.westnordost.osmfeatures.IDPresetsJsonParser +import de.westnordost.osmfeatures.IDPresetsTranslationJsonParser +import de.westnordost.osmfeatures.Locale +import de.westnordost.osmfeatures.LocalizedFeature +import okio.FileSystem +import okio.Okio +import okio.Path +import okio.Source +import org.junit.Test +import java.io.IOException +import java.net.URISyntaxException +import java.net.URL +import osmfeatures.MapEntry.Companion.tag +import osmfeatures.MapEntry.Companion.mapOf +import de.westnordost.osmfeatures.TestUtils.listOf +import okio.source +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue + +class IDPresetsTranslationJsonParserTest { + @Test + fun load_features_and_localization() { + val features: List = parse("one_preset_min.json", "localizations.json") + assertEquals(1, features.size) + val feature: Feature = features[0] + assertEquals("some/id", feature.id) + assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature.tags) + assertEquals(listOf(GeometryType.POINT), feature.geometry) + assertEquals("bar", feature.name) + assertEquals(listOf("bar", "one", "two", "three"), feature.names) + assertEquals(listOf("a", "b"), feature.terms) + } + + @Test + fun load_features_and_localization_defaults() { + val features: List = + parse("one_preset_min.json", "localizations_min.json") + assertEquals(1, features.size) + val feature: Feature = features[0] + assertEquals("some/id", feature.id) + assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature.tags) + assertEquals(listOf(GeometryType.POINT), feature.geometry) + assertEquals("bar", feature.name) + assertTrue(feature.terms.isEmpty()) + } + + @Test + fun load_features_and_localization_with_placeholder_name() { + val features: List = + parse("one_preset_with_placeholder_name.json", "localizations.json") + val featuresById = HashMap(features.associateBy { it.id }) + assertEquals(2, features.size) + val feature: Feature? = featuresById["some/id-dingsdongs"] + assertEquals("some/id-dingsdongs", feature?.id) + assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature?.tags) + assertEquals(listOf(GeometryType.POINT), feature?.geometry) + assertEquals("bar", feature?.name) + assertEquals(listOf("bar", "one", "two", "three"), feature?.names) + assertEquals(listOf("a", "b"), feature?.terms) + } + + @Test + @kotlin.Throws(IOException::class, URISyntaxException::class) + fun parse_some_real_data() { + val url = + URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") + val features: List = + IDPresetsJsonParser().parse(url.openConnection().getInputStream().source()) + val featureMap = HashMap(features.associateBy { it.id }) + val rawTranslationsURL = + URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json") + val translatedFeatures: List = IDPresetsTranslationJsonParser().parse( + rawTranslationsURL.openStream().source(), + Locale.GERMAN, + featureMap + ) + + // should not crash etc + assertTrue(translatedFeatures.size > 1000) + } + + private fun parse(presetsFile: String, translationsFile: String): List { + return try { + val baseFeatures: List = + IDPresetsJsonParser().parse(getSource(presetsFile)) + val featureMap = HashMap(baseFeatures.associateBy { it.id }) + IDPresetsTranslationJsonParser().parse( + getSource(translationsFile), + Locale.ENGLISH, + featureMap + ) + } catch (e: IOException) { + throw RuntimeException() + } + } + + @kotlin.Throws(IOException::class) + private fun getSource(file: String): Source { + val resourceStream = this.javaClass.getResourceAsStream(file) + ?: throw IOException("Could not retrieve file $file in resource assets") + return resourceStream.source() + } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt b/library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt new file mode 100644 index 0000000..6ec9e5c --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt @@ -0,0 +1,51 @@ +package de.westnordost.osmfeatures + +import kotlinx.serialization.json.JsonArray +import org.junit.Test +import de.westnordost.osmfeatures.JsonUtils.parseList +import de.westnordost.osmfeatures.JsonUtils.parseStringMap +import de.westnordost.osmfeatures.TestUtils.listOf +import org.junit.Assert.assertEquals + +class JsonUtilsTest { + @Test + fun parseList_with_null_json_array() { + assertEquals(0, parseList(null) { obj -> obj }.size) + } + + @Test + fun parseList_with_empty_json_array() { + assertEquals(0, parseList(JsonArray(listOf())) { obj -> obj }.size) + } + + // @Test public void parseList_with_array() + // { + // String[] array = new String[]{"a","b","c"}; + // JsonPrimitive prim = new JsonElement() { + // } + // assertEquals(listOf(array), parseList(new JsonArray(null)., obj -> obj)); + // } + // @Test public void parseList_with_array_and_transformer() + // { + // int[] array = new int[]{1,2,3}; + // assertEquals(listOf(2,4,6), parseList(new JSONArray(array), i -> (int)i*2)); + // } + @Test + fun parseStringMap_with_null_json_map() { + assertEquals(0, parseStringMap(null).size) + } // @Test public void parseStringMap_with_empty_json_map() + // { + // assertEquals(0, parseStringMap(new JSONObject()).size()); + // } + // @Test public void parseStringMap_with_one_entry() + // { + // Map m = mapOf(tag("a","b")); + // assertEquals(m, parseStringMap(new JSONObject(m))); + // } + // + // @Test public void parseStringMap_with_several_entries() + // { + // Map m = mapOf(tag("a","b"), tag("c", "d")); + // assertEquals(m, parseStringMap(new JSONObject(m))); + // } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt b/library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt new file mode 100644 index 0000000..12b7390 --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt @@ -0,0 +1,24 @@ +package osmfeatures + +import java.io.IOException +import java.net.URL +import de.westnordost.osmfeatures.TestUtils.listOf +import okio.source + +class LivePresetDataAccessAdapter : FileAccessAdapter { + @Override + override fun exists(name: String): Boolean { + return listOf("presets.json", "de.json", "en.json", "en-GB.json").contains(name) + } + + @Override + @kotlin.Throws(IOException::class) + override fun open(name: String): okio.Source { + val url: URL = if (name == "presets.json") { + URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") + } else { + URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/$name") + } + return url.openStream().source() + } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/MapEntry.kt b/library/src/commonTest/kotlin/osmfeatures/MapEntry.kt new file mode 100644 index 0000000..d95a637 --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/MapEntry.kt @@ -0,0 +1,15 @@ +package osmfeatures + +internal class MapEntry private constructor(private val key: String, private val value: String) { + companion object { + fun tag(key: String, value: String): MapEntry { + return MapEntry(key, value) + } + + fun mapOf(vararg items: MapEntry): Map { + val result = hashMapOf() + for (item in items) result[item.key] = item.value + return result + } + } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt b/library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt new file mode 100644 index 0000000..53451a2 --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt @@ -0,0 +1,37 @@ +package de.westnordost.osmfeatures + +import org.junit.Test +import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder +import de.westnordost.osmfeatures.TestUtils.listOf +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue + +class StartsWithStringTreeTest { + @Test + fun copes_with_empty_collection() { + val t = StartsWithStringTree(listOf()) + assertTrue(t.getAll("any").isEmpty()) + } + + @Test + fun find_single_string() { + val t = StartsWithStringTree(listOf("anything")) + assertEquals(listOf("anything"), t.getAll("a")) + assertEquals(listOf("anything"), t.getAll("any")) + assertEquals(listOf("anything"), t.getAll("anything")) + } + + @Test + fun dont_find_single_string() { + val t = StartsWithStringTree(listOf("anything", "more", "etc")) + assertTrue(t.getAll("").isEmpty()) + assertTrue(t.getAll("nything").isEmpty()) + assertTrue(t.getAll("anything else").isEmpty()) + } + + @Test + fun find_several_strings() { + val t = StartsWithStringTree(listOf("anything", "anybody", "anytime")) + assertEqualsIgnoreOrder(listOf("anything", "anybody", "anytime"), t.getAll("any")) + } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt b/library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt new file mode 100644 index 0000000..8e5375b --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt @@ -0,0 +1,24 @@ +package osmfeatures + +import de.westnordost.osmfeatures.Feature +import de.westnordost.osmfeatures.Locale + + +class TestLocalizedFeatureCollection(features: List) : LocalizedFeatureCollection { + private val features: List + + init { + this.features = features + } + + @Override + override fun getAll(locales: List): Collection { + return features.filter { locales.contains(it.locale) } + } + + @Override + override operator fun get(id: String, locales: List): Feature? { + val feature = features.find { it.id == id } + return if (feature == null || (!locales.contains(feature.locale))) null else feature + } +} \ No newline at end of file diff --git a/library/src/commonTest/kotlin/osmfeatures/TestPerCountryFeatureCollection.kt b/library/src/commonTest/kotlin/osmfeatures/TestPerCountryFeatureCollection.kt new file mode 100644 index 0000000..f3db432 --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/TestPerCountryFeatureCollection.kt @@ -0,0 +1,33 @@ +package osmfeatures + +import de.westnordost.osmfeatures.Feature +import de.westnordost.osmfeatures.PerCountryFeatureCollection + +class TestPerCountryFeatureCollection(features: List) : PerCountryFeatureCollection { + private val features: List + + init { + this.features = features + } + + @Override + override fun getAll(countryCodes: List): Collection { + return features.filter { + countryCodes + .find { countryCode -> + it.includeCountryCodes.contains(countryCode) || countryCode == null && it.includeCountryCodes.isEmpty() + } != null + } + } + + @Override + override operator fun get(id: String, countryCodes: List): Feature? { + return features.find { feature -> + feature.id == id + && countryCodes.find { + feature.includeCountryCodes.contains(it) || it == null && feature.includeCountryCodes.isEmpty() + } != null + } + + } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/TestUtils.kt b/library/src/commonTest/kotlin/osmfeatures/TestUtils.kt new file mode 100644 index 0000000..1a7256d --- /dev/null +++ b/library/src/commonTest/kotlin/osmfeatures/TestUtils.kt @@ -0,0 +1,14 @@ +package de.westnordost.osmfeatures + +import org.junit.Assert.assertTrue + +object TestUtils { + fun assertEqualsIgnoreOrder(a: Collection, b: Collection) { + assertTrue(a.size === b.size && a.containsAll(b)) + } + + @SafeVarargs + fun listOf(vararg items: T): List { + return items.asList() + } +} diff --git a/library/src/test/resources/brand_presets_min.json b/library/src/commonTest/resources/brand_presets_min.json similarity index 100% rename from library/src/test/resources/brand_presets_min.json rename to library/src/commonTest/resources/brand_presets_min.json diff --git a/library/src/test/resources/brand_presets_min2.json b/library/src/commonTest/resources/brand_presets_min2.json similarity index 100% rename from library/src/test/resources/brand_presets_min2.json rename to library/src/commonTest/resources/brand_presets_min2.json diff --git a/library/src/test/resources/localizations.json b/library/src/commonTest/resources/localizations.json similarity index 95% rename from library/src/test/resources/localizations.json rename to library/src/commonTest/resources/localizations.json index da88042..ea4941a 100644 --- a/library/src/test/resources/localizations.json +++ b/library/src/commonTest/resources/localizations.json @@ -1,9 +1,9 @@ -{ "xx": { "presets" : { - "presets": { - "some/id": { - "name": "bar", - "aliases": "one \ntwo\r\n three", - "terms": "a, b" - } - } +{ "xx": { "presets" : { + "presets": { + "some/id": { + "name": "bar", + "aliases": "one \ntwo\r\n three", + "terms": "a, b" + } + } }}} \ No newline at end of file diff --git a/library/src/test/resources/localizations_de-AT.json b/library/src/commonTest/resources/localizations_de-AT.json similarity index 94% rename from library/src/test/resources/localizations_de-AT.json rename to library/src/commonTest/resources/localizations_de-AT.json index 7fdb0ea..96d20d5 100644 --- a/library/src/test/resources/localizations_de-AT.json +++ b/library/src/commonTest/resources/localizations_de-AT.json @@ -1,10 +1,10 @@ -{ "xx": { "presets" : { - "presets": { - "some/id": { - "name": "Backhusl" - }, - "yet/another/id": { - "name": "Brückle" - } - } +{ "xx": { "presets" : { + "presets": { + "some/id": { + "name": "Backhusl" + }, + "yet/another/id": { + "name": "Brückle" + } + } }}} \ No newline at end of file diff --git a/library/src/test/resources/localizations_de-Cyrl-AT.json b/library/src/commonTest/resources/localizations_de-Cyrl-AT.json similarity index 100% rename from library/src/test/resources/localizations_de-Cyrl-AT.json rename to library/src/commonTest/resources/localizations_de-Cyrl-AT.json diff --git a/library/src/test/resources/localizations_de-Cyrl.json b/library/src/commonTest/resources/localizations_de-Cyrl.json similarity index 100% rename from library/src/test/resources/localizations_de-Cyrl.json rename to library/src/commonTest/resources/localizations_de-Cyrl.json diff --git a/library/src/test/resources/localizations_de.json b/library/src/commonTest/resources/localizations_de.json similarity index 94% rename from library/src/test/resources/localizations_de.json rename to library/src/commonTest/resources/localizations_de.json index f5e0085..ddc2306 100644 --- a/library/src/test/resources/localizations_de.json +++ b/library/src/commonTest/resources/localizations_de.json @@ -1,10 +1,10 @@ -{ "xx": { "presets" : { - "presets": { - "some/id": { - "name": "Bäckerei" - }, - "another/id": { - "name": "Gullideckel" - } - } +{ "xx": { "presets" : { + "presets": { + "some/id": { + "name": "Bäckerei" + }, + "another/id": { + "name": "Gullideckel" + } + } }}} \ No newline at end of file diff --git a/library/src/test/resources/localizations_en.json b/library/src/commonTest/resources/localizations_en.json similarity index 93% rename from library/src/test/resources/localizations_en.json rename to library/src/commonTest/resources/localizations_en.json index 05821b9..16b0f5b 100644 --- a/library/src/test/resources/localizations_en.json +++ b/library/src/commonTest/resources/localizations_en.json @@ -1,7 +1,7 @@ -{ "xx": { "presets" : { - "presets": { - "some/id": { - "name": "Bakery" - } - } +{ "xx": { "presets" : { + "presets": { + "some/id": { + "name": "Bakery" + } + } }}} \ No newline at end of file diff --git a/library/src/test/resources/localizations_min.json b/library/src/commonTest/resources/localizations_min.json similarity index 93% rename from library/src/test/resources/localizations_min.json rename to library/src/commonTest/resources/localizations_min.json index 6fa238a..c62523d 100644 --- a/library/src/test/resources/localizations_min.json +++ b/library/src/commonTest/resources/localizations_min.json @@ -1,7 +1,7 @@ -{ "xx": { "presets" : { - "presets": { - "some/id": { - "name": "bar" - } - } +{ "xx": { "presets" : { + "presets": { + "some/id": { + "name": "bar" + } + } }}} \ No newline at end of file diff --git a/library/src/test/resources/one_preset_full.json b/library/src/commonTest/resources/one_preset_full.json similarity index 96% rename from library/src/test/resources/one_preset_full.json rename to library/src/commonTest/resources/one_preset_full.json index 2e21187..0848ea0 100644 --- a/library/src/test/resources/one_preset_full.json +++ b/library/src/commonTest/resources/one_preset_full.json @@ -1,19 +1,19 @@ -{ - "some/id": { - "tags": { "a": "b", "c": "d" }, - "geometry": ["point", "vertex", "line", "area", "relation"], - "icon": "abc", - "imageURL": "someurl", - "name": "foo", - "aliases": ["one", "two"], - "terms": ["1","2"], - "addTags": { "e": "f" }, - "removeTags": { "d": "g" }, - "locationSet": { - "include": ["de","gb"], - "exclude": ["it"] - }, - "searchable": false, - "matchScore": 0.5 - } +{ + "some/id": { + "tags": { "a": "b", "c": "d" }, + "geometry": ["point", "vertex", "line", "area", "relation"], + "icon": "abc", + "imageURL": "someurl", + "name": "foo", + "aliases": ["one", "two"], + "terms": ["1","2"], + "addTags": { "e": "f" }, + "removeTags": { "d": "g" }, + "locationSet": { + "include": ["de","gb"], + "exclude": ["it"] + }, + "searchable": false, + "matchScore": 0.5 + } } \ No newline at end of file diff --git a/library/src/test/resources/one_preset_min.json b/library/src/commonTest/resources/one_preset_min.json similarity index 94% rename from library/src/test/resources/one_preset_min.json rename to library/src/commonTest/resources/one_preset_min.json index 093eefc..8dd9d14 100644 --- a/library/src/test/resources/one_preset_min.json +++ b/library/src/commonTest/resources/one_preset_min.json @@ -1,6 +1,6 @@ -{ - "some/id": { - "tags": { "a": "b", "c": "d" }, - "geometry": ["point"] - } +{ + "some/id": { + "tags": { "a": "b", "c": "d" }, + "geometry": ["point"] + } } \ No newline at end of file diff --git a/library/src/test/resources/one_preset_unsupported_location_set.json b/library/src/commonTest/resources/one_preset_unsupported_location_set.json similarity index 100% rename from library/src/test/resources/one_preset_unsupported_location_set.json rename to library/src/commonTest/resources/one_preset_unsupported_location_set.json diff --git a/library/src/test/resources/one_preset_wildcard.json b/library/src/commonTest/resources/one_preset_wildcard.json similarity index 94% rename from library/src/test/resources/one_preset_wildcard.json rename to library/src/commonTest/resources/one_preset_wildcard.json index 28e2239..49bf16f 100644 --- a/library/src/test/resources/one_preset_wildcard.json +++ b/library/src/commonTest/resources/one_preset_wildcard.json @@ -1,12 +1,12 @@ -{ - "some/id": { - "name": "test", - "tags": { "addr:*": "b" }, - "geometry": ["point"] - }, - "another/id": { - "name": "test", - "tags": { "a": "*" }, - "geometry": ["point"] - } +{ + "some/id": { + "name": "test", + "tags": { "addr:*": "b" }, + "geometry": ["point"] + }, + "another/id": { + "name": "test", + "tags": { "a": "*" }, + "geometry": ["point"] + } } \ No newline at end of file diff --git a/library/src/test/resources/one_preset_with_placeholder_name.json b/library/src/commonTest/resources/one_preset_with_placeholder_name.json similarity index 100% rename from library/src/test/resources/one_preset_with_placeholder_name.json rename to library/src/commonTest/resources/one_preset_with_placeholder_name.json diff --git a/library/src/test/resources/some_presets_min.json b/library/src/commonTest/resources/some_presets_min.json similarity index 95% rename from library/src/test/resources/some_presets_min.json rename to library/src/commonTest/resources/some_presets_min.json index 5d77020..c62cd5f 100644 --- a/library/src/test/resources/some_presets_min.json +++ b/library/src/commonTest/resources/some_presets_min.json @@ -1,17 +1,17 @@ -{ - "some/id": { - "name": "test", - "tags": { "a": "b", "c": "d" }, - "geometry": ["point"] - }, - "another/id": { - "name": "test", - "tags": { "a": "b", "c": "d" }, - "geometry": ["point"] - }, - "yet/another/id": { - "name": "test", - "tags": { "a": "b", "c": "d" }, - "geometry": ["point"] - } +{ + "some/id": { + "name": "test", + "tags": { "a": "b", "c": "d" }, + "geometry": ["point"] + }, + "another/id": { + "name": "test", + "tags": { "a": "b", "c": "d" }, + "geometry": ["point"] + }, + "yet/another/id": { + "name": "test", + "tags": { "a": "b", "c": "d" }, + "geometry": ["point"] + } } \ No newline at end of file diff --git a/library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.kt b/library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.kt deleted file mode 100644 index 4b91fc8..0000000 --- a/library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.kt +++ /dev/null @@ -1,193 +0,0 @@ -package de.westnordost.osmfeatures; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** Index that makes finding which maps are completely contained by a given map very efficient. - * It sorts the maps into a tree structure with configurable depth. - * - * It is threadsafe because it is immutable. - * - * For example for the string maps... - *
- *  [
- *    #1 (amenity -> bicycle_parking),
- *    #2 (amenity -> bicycle_parking, bicycle_parking -> shed),
- *    #3 (amenity -> bicycle_parking, bicycle_parking -> lockers),
- *    #4 (amenity -> taxi),
- *    #5 (shop -> supermarket),
- *  ]
- *  
- * ...the tree internally looks like this: - *
- *  amenity ->
- *    bicycle_parking ->
- *      #1
- *      bicycle_parking ->
- *        shed ->
- *          #2
- *        lockers ->
- *          #3
- *    taxi ->
- *      #4
- *  shop ->
- *    supermarket ->
- *      #5
- *  ...
- *  
- * */ -class ContainedMapTree -{ - private final Node root; - - ContainedMapTree(Collection> maps) - { - this(maps, 4, 4); - } - - /** Create this index with the given maps. - * - * The generated tree will have a max depth of maxDepth and another depth is not added to the - * tree if there are less than minContainerSize maps in one tree node. - */ - ContainedMapTree(Collection> maps, int maxDepth, int minContainerSize) - { - if (maxDepth < 0) maxDepth = 0; - root = buildTree(maps, Collections.emptyList(), maxDepth, minContainerSize); - } - - /** Get all maps whose entries are completely contained by the given map */ - List> getAll(Map map) - { - return root.getAll(map); - } - - private static Node buildTree(Collection> maps, Collection previousKeys, int maxDepth, int minContainerSize) - { - if (previousKeys.size() == maxDepth || maps.size() < minContainerSize) - return new Node<>(null, maps); - - Set> unsortedMaps = new HashSet<>(maps); - - Map>> mapsByKey = getMapsByKey(maps, previousKeys); - - /* the map should be categorized by frequent keys first and least frequent keys last. */ - List>>> sortedByCountDesc = new ArrayList<>(mapsByKey.entrySet()); - Collections.sort(sortedByCountDesc, (a, b) -> b.getValue().size() - a.getValue().size()); - - HashMap>> result = new HashMap<>(mapsByKey.size()); - - for (Map.Entry>> keyToMaps : sortedByCountDesc) - { - K key = keyToMaps.getKey(); - List> mapsForKey = keyToMaps.getValue(); - - // a map already sorted in a certain node should not be sorted into another too - mapsForKey.retainAll(unsortedMaps); - if (mapsForKey.isEmpty()) continue; - - Map>> featuresByValue = getMapsByKeyValue(key, mapsForKey); - - Map> valueNodes = new HashMap<>(featuresByValue.size()); - for (Map.Entry>> valueToFeatures : featuresByValue.entrySet()) - { - V value = valueToFeatures.getKey(); - List> featuresForValue = valueToFeatures.getValue(); - List previousKeysNow = new ArrayList<>(previousKeys); - previousKeysNow.add(key); - valueNodes.put(value, buildTree(featuresForValue, previousKeysNow, maxDepth, minContainerSize)); - } - - result.put(key, valueNodes); - - for (Map map : mapsForKey) { - unsortedMaps.remove(map); - } - } - - return new Node<>(result, new ArrayList<>(unsortedMaps)); - } - - /** returns the given features grouped by the map entry value of the given key. */ - private static Map>> getMapsByKeyValue(K key, Collection> maps) - { - HashMap>> result = new HashMap<>(); - for (Map map : maps) - { - V value = map.get(key); - if (!result.containsKey(value)) result.put(value, new ArrayList<>()); - result.get(value).add(map); - } - return result; - } - - /** returns the given maps grouped by each of their keys (except the given ones). */ - private static Map>> getMapsByKey(Collection> maps, Collection excludeKeys) - { - HashMap>> result = new HashMap<>(); - for (Map map : maps) - { - for (K key : map.keySet()) - { - if (excludeKeys.contains(key)) continue; - if (!result.containsKey(key)) result.put(key, new ArrayList<>()); - result.get(key).add(map); - } - } - return result; - } - - private static class Node - { - /** key -> (value -> Node) */ - final Map>> children; - final Collection> maps; - - private Node(Map>> children, Collection> maps) - { - this.children = children; - this.maps = maps; - } - - /** Get all maps whose entries are all contained by given map */ - private List> getAll(Map map) - { - List> result = new ArrayList<>(); - if (children != null) - { - for (Map.Entry>> keyToValues : children.entrySet()) - { - K key = keyToValues.getKey(); - if (map.containsKey(key)) - { - for (Map.Entry> valueToNode : keyToValues.getValue().entrySet()) - { - V value = valueToNode.getKey(); - if (value.equals(map.get(key))) - { - result.addAll(valueToNode.getValue().getAll(map)); - } - } - } - } - } - if (maps != null) - { - for (Map m : maps) - { - if (CollectionUtils.mapContainsAllEntries(map, m.entrySet())) - { - result.add(m); - } - } - } - return result; - } - } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/CollectionUtilsTest.java b/library/src/test/java/de/westnordost/osmfeatures/CollectionUtilsTest.java deleted file mode 100644 index 4eeec5c..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/CollectionUtilsTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package de.westnordost.osmfeatures; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import static de.westnordost.osmfeatures.MapEntry.mapOf; -import static de.westnordost.osmfeatures.MapEntry.tag; -import static de.westnordost.osmfeatures.TestUtils.listOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -public class CollectionUtilsTest -{ - - @Test public void mapContainsEntry() - { - Map.Entry ab = mapOf(tag("a", "b")).entrySet().iterator().next(); - Map.Entry cd = mapOf(tag("c", "d")).entrySet().iterator().next(); - Map.Entry ef = mapOf(tag("e", "f")).entrySet().iterator().next(); - Map map = mapOf(tag("a", "b"), tag("c", "d")); - - assertTrue(CollectionUtils.mapContainsEntry(map, ab)); - assertTrue(CollectionUtils.mapContainsEntry(map, cd)); - assertFalse(CollectionUtils.mapContainsEntry(map, ef)); - } - - @Test public void numberOfContainedEntriesInMap() - { - Map ab = mapOf(tag("a", "b")); - Map abcdef = mapOf(tag("a", "b"), tag("c", "d"), tag("e", "f")); - - Map map = mapOf(tag("a", "b"), tag("c", "d")); - - assertEquals(0, CollectionUtils.numberOfContainedEntriesInMap(map, mapOf().entrySet())); - assertEquals(1, CollectionUtils.numberOfContainedEntriesInMap(map, ab.entrySet())); - assertEquals(2, CollectionUtils.numberOfContainedEntriesInMap(map, map.entrySet())); - assertEquals(2, CollectionUtils.numberOfContainedEntriesInMap(map, abcdef.entrySet())); - } - - @Test public void mapContainsAllEntries() - { - Map ab = mapOf(tag("a", "b")); - Map abcdef = mapOf(tag("a", "b"), tag("c", "d"), tag("e", "f")); - - Map map = mapOf(tag("a", "b"), tag("c", "d")); - - assertTrue(CollectionUtils.mapContainsAllEntries(map, mapOf().entrySet())); - assertTrue(CollectionUtils.mapContainsAllEntries(map, ab.entrySet())); - assertTrue(CollectionUtils.mapContainsAllEntries(map, map.entrySet())); - assertFalse(CollectionUtils.mapContainsAllEntries(map, abcdef.entrySet())); - } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/ContainedMapTreeTest.java b/library/src/test/java/de/westnordost/osmfeatures/ContainedMapTreeTest.java deleted file mode 100644 index 758743c..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/ContainedMapTreeTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package de.westnordost.osmfeatures; - -import org.junit.Test; - -import java.util.Collection; -import java.util.Map; - -import static de.westnordost.osmfeatures.MapEntry.mapOf; -import static de.westnordost.osmfeatures.MapEntry.tag; -import static de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder; -import static de.westnordost.osmfeatures.TestUtils.listOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class ContainedMapTreeTest -{ - @Test public void copes_with_empty_feature_collection() - { - ContainedMapTree t = tree(listOf()); - assertTrue(t.getAll(mapOf(tag("a", "b"))).isEmpty()); - } - - @Test public void find_single_map() - { - Map f1 = mapOf(tag("a", "b")); - ContainedMapTree t = tree(listOf(f1)); - - assertEquals(listOf(f1), t.getAll(mapOf(tag("a", "b"), tag("c", "d")))); - } - - @Test public void dont_find_single_map() - { - ContainedMapTree tree = tree(listOf(mapOf(tag("a", "b")))); - - assertTrue(tree.getAll(mapOf()).isEmpty()); - assertTrue(tree.getAll(mapOf(tag("c", "d"))).isEmpty()); - assertTrue(tree.getAll(mapOf(tag("a", "c"))).isEmpty()); - } - - @Test public void find_only_generic_map() - { - Map f1 = mapOf(tag("a", "b")); - Map f2 = mapOf(tag("a", "b"), tag("c", "d")); - ContainedMapTree tree = tree(listOf(f1, f2)); - - assertEquals(listOf(f1), tree.getAll(mapOf(tag("a", "b")))); - } - - @Test public void find_map_with_one_match_and_with_several_matches() - { - Map f1 = mapOf(tag("a", "b")); - Map f2 = mapOf(tag("a", "b"), tag("c", "d")); - Map f3 = mapOf(tag("a", "b"), tag("c", "e")); - Map f4 = mapOf(tag("a", "b"), tag("d", "d")); - ContainedMapTree tree = tree(listOf(f1, f2, f3, f4)); - - assertEqualsIgnoreOrder(listOf(f1, f2), tree.getAll(mapOf(tag("a", "b"), tag("c", "d")))); - } - - private static ContainedMapTree tree(Collection> items) - { - return new ContainedMapTree<>(items); - } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/FeatureDictionaryTest.java b/library/src/test/java/de/westnordost/osmfeatures/FeatureDictionaryTest.java deleted file mode 100644 index 011a5a6..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/FeatureDictionaryTest.java +++ /dev/null @@ -1,815 +0,0 @@ -package de.westnordost.osmfeatures; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static de.westnordost.osmfeatures.MapEntry.*; -import static de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder; -import static de.westnordost.osmfeatures.TestUtils.listOf; -import static org.junit.Assert.*; - -public class FeatureDictionaryTest -{ - private static final List POINT = Collections.singletonList(GeometryType.POINT); - - private final Feature bakery = feature( // unlocalized shop=bakery - "shop/bakery", - mapOf(tag("shop","bakery")), - POINT, - listOf("Bäckerei"), - listOf("Brot"), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - false, - null - ); - - private final Feature panetteria = feature( // localized shop=bakery - "shop/bakery", - mapOf(tag("shop","bakery")), - POINT, - listOf("Panetteria"), - listOf(), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - false, - Locale.ITALIAN - ); - - private final Feature ditsch = feature( // brand in DE for shop=bakery - "shop/bakery/Ditsch", - mapOf(tag("shop","bakery"), tag("name","Ditsch")), - POINT, - listOf("Ditsch"), - listOf(), - listOf("DE","AT"), - listOf("AT-9"), - true, - 1.0, - mapOf(tag("wikipedia","de:Brezelb%C3%A4ckerei_Ditsch"), tag("brand", "Ditsch")), - true, - null - ); - - private final Feature ditschRussian = feature( // brand in RU for shop=bakery - "shop/bakery/Дитсч", - mapOf(tag("shop","bakery"), tag("name","Ditsch")), - POINT, - listOf("Дитсч"), - listOf(), - listOf("RU","UA-43"), - listOf(), - true, - 1.0, - mapOf(tag("wikipedia","de:Brezelb%C3%A4ckerei_Ditsch"), tag("brand", "Дитсч")), - true, - null - ); - - private final Feature ditschInternational = feature( // brand everywhere for shop=bakery - "shop/bakery/Ditsh", - mapOf(tag("shop","bakery"), tag("name","Ditsch")), - POINT, - listOf("Ditsh"), - listOf(), - listOf(), - listOf(), - true, - 1.0, - mapOf(tag("wikipedia","de:Brezelb%C3%A4ckerei_Ditsch")), - true, - null - ); - - private final Feature liquor_store = feature( // English localized unspecific shop=alcohol - "shop/alcohol", - mapOf(tag("shop","alcohol")), - POINT, - listOf("Off licence (Alcohol shop)"), - listOf(), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - false, - Locale.UK - ); - - private final Feature car_dealer = feature( // German localized unspecific shop=car - "shop/car", - mapOf(tag("shop","car")), - POINT, - listOf("Autohändler"), - listOf("auto"), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - false, - Locale.GERMAN - ); - - private final Feature second_hand_car_dealer = feature( // German localized shop=car with subtags - "shop/car/second_hand", - mapOf(tag("shop","car"), tag("second_hand", "only")), - POINT, - listOf("Gebrauchtwagenhändler"), - listOf("auto"), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - false, - Locale.GERMAN - ); - - private final Feature scheisshaus = feature( // unsearchable feature - "amenity/scheißhaus", - mapOf(tag("amenity","scheißhaus")), - POINT, - listOf("Scheißhaus"), - listOf(), - listOf(), - listOf(), - false, // <--- not searchable! - 1.0, - mapOf(), - false, - null - ); - - private final Feature bank = feature( // unlocalized shop=bank (Bank) - "amenity/bank", - mapOf(tag("amenity","bank")), - POINT, - listOf("Bank"), - listOf(), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - false, - null - ); - private final Feature bench = feature( // unlocalized amenity=bench (PARKbank) - "amenity/bench", - mapOf(tag("amenity","bench")), - POINT, - listOf("Parkbank"), - listOf("Bank"), - listOf(), - listOf(), - true, - 5.0, - mapOf(), - false, - null - ); - private final Feature casino = feature( // unlocalized amenity=casino (SPIELbank) - "amenity/casino", - mapOf(tag("amenity","casino")), - POINT, - listOf("Spielbank"), - listOf("Kasino"), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - false, - null - ); - private final Feature atm = feature( // unlocalized amenity=atm (BankOMAT) - "amenity/atm", - mapOf(tag("amenity","atm")), - POINT, - listOf("Bankomat"), - listOf(), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - false, - null - ); - private final Feature stock_exchange = feature( // unlocalized amenity=stock_exchange (has "Banking" as term) - "amenity/stock_exchange", - mapOf(tag("amenity","stock_exchange")), - POINT, - listOf("Börse"), - listOf("Banking"), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - false, - null - ); - private final Feature bank_of_america = feature( // Brand of a amenity=bank (has "Bank" in name) - "amenity/bank/Bank of America", - mapOf(tag("amenity","bank"), tag("name","Bank of America")), - POINT, - listOf("Bank of America"), - listOf(), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - true, - null - ); - private final Feature bank_of_liechtenstein = feature( // Brand of a amenity=bank (has "Bank" in name), but low matchScore - "amenity/bank/Bank of Liechtenstein", - mapOf(tag("amenity","bank"), tag("name","Bank of Liechtenstein")), - POINT, - listOf("Bank of Liechtenstein"), - listOf(), - listOf(), - listOf(), - true, - 0.2, - mapOf(), - true, - null - ); - private final Feature deutsche_bank = feature( // Brand of a amenity=bank (does not start with "Bank" in name) - "amenity/bank/Deutsche Bank", - mapOf(tag("amenity","bank"), tag("name","Deutsche Bank")), - POINT, - listOf("Deutsche Bank"), - listOf(), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - true, - null - ); - private final Feature baenk = feature( // amenity=bänk, to see if diacritics match non-strictly ("a" finds "ä") - "amenity/bänk", - mapOf(tag("amenity","bänk")), - POINT, - listOf("Bänk"), - listOf(), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - false, - null - ); - private final Feature bad_bank = feature( // amenity=bank with subtags that has "Bank" in name but it is not the first word - "amenity/bank/bad", - mapOf(tag("amenity","bank"), tag("goodity","bad")), - POINT, - listOf("Bad Bank"), - listOf(), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - false, - null - ); - private final Feature thieves_guild = feature( // only has "bank" in an alias - "amenity/thieves_guild", - mapOf(tag("amenity","thieves_guild")), - POINT, - listOf("Diebesgilde", "Bankräuberausbildungszentrum"), - listOf(), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - false, - null - ); - private final Feature miniature_train_shop = feature( // feature whose name consists of several words - "shop/miniature_train", - mapOf(tag("shop","miniature_train")), - POINT, - listOf("Miniature Train Shop"), - listOf(), - listOf(), - listOf(), - true, - 1.0, - mapOf(), - false, - null - ); - - //region by tags - - @Test public void find_no_entry_by_tags() - { - Map tags = mapOf(tag("shop", "supermarket")); - FeatureDictionary dictionary = dictionary(bakery); - assertEquals(listOf(), dictionary.byTags(tags).find()); - } - - @Test public void find_no_entry_because_wrong_geometry() - { - Map tags = mapOf(tag("shop", "bakery")); - FeatureDictionary dictionary = dictionary(bakery); - assertEquals(listOf(), dictionary.byTags(tags).forGeometry(GeometryType.RELATION).find()); - } - - @Test public void find_no_entry_because_wrong_locale() - { - Map tags = mapOf(tag("shop", "bakery")); - FeatureDictionary dictionary = dictionary(bakery); - assertEquals(listOf(), dictionary.byTags(tags).forLocale(Locale.ITALIAN).find()); - } - - @Test public void find_entry_because_fallback_locale() - { - Map tags = mapOf(tag("shop", "bakery")); - FeatureDictionary dictionary = dictionary(bakery); - List matches = dictionary.byTags(tags).forLocale(Locale.ITALIAN, null).find(); - assertEquals(listOf(bakery), matches); - } - - @Test public void find_entry_by_tags() - { - Map tags = mapOf(tag("shop", "bakery")); - FeatureDictionary dictionary = dictionary(bakery); - List matches = dictionary.byTags(tags).find(); - assertEquals(listOf(bakery), matches); - } - - @Test public void find_non_searchable_entry_by_tags() - { - Map tags = mapOf(tag("amenity", "scheißhaus")); - FeatureDictionary dictionary = dictionary(scheisshaus); - List matches = dictionary.byTags(tags).find(); - assertEquals(listOf(scheisshaus), matches); - } - - @Test public void find_entry_by_tags_correct_geometry() - { - Map tags = mapOf(tag("shop", "bakery")); - FeatureDictionary dictionary = dictionary(bakery); - List matches = dictionary.byTags(tags).forGeometry(GeometryType.POINT).find(); - assertEquals(listOf(bakery), matches); - } - - @Test public void find_brand_entry_by_tags() - { - Map tags = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")); - FeatureDictionary dictionary = dictionary(bakery, ditsch); - List matches = dictionary.byTags(tags).inCountry("DE").find(); - assertEquals(listOf(ditsch), matches); - } - - @Test public void find_only_entries_with_given_locale() - { - Map tags = mapOf(tag("shop", "bakery")); - FeatureDictionary dictionary = dictionary(bakery, panetteria); - assertEquals(listOf(panetteria), dictionary.byTags(tags).forLocale(Locale.ITALIAN).find()); - assertEquals(listOf(), dictionary.byTags(tags).forLocale(Locale.ENGLISH).find()); - assertEquals(listOf(bakery), dictionary.byTags(tags).forLocale((Locale) null).find()); - } - - @Test public void find_only_brands_finds_no_normal_entries() - { - Map tags = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")); - FeatureDictionary dictionary = dictionary(bakery); - List matches = dictionary.byTags(tags).isSuggestion(true).find(); - assertEquals(0, matches.size()); - } - - @Test public void find_no_brands_finds_only_normal_entries() - { - Map tags = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")); - FeatureDictionary dictionary = dictionary(bakery, ditsch); - List matches = dictionary.byTags(tags).isSuggestion(false).find(); - assertEquals(listOf(bakery), matches); - } - - @Test public void find_multiple_brands_sorts_by_locale() - { - Map tags = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")); - FeatureDictionary dictionary = dictionary(ditschRussian, ditschInternational, ditsch); - List matches = dictionary.byTags(tags).forLocale((Locale) null).find(); - assertEquals(ditschInternational, matches.get(0)); - } - - @Test public void find_multiple_entries_by_tags() - { - Map tags = mapOf(tag("shop", "bakery"), tag("amenity", "bank")); - FeatureDictionary dictionary = dictionary(bakery, bank); - List matches = dictionary.byTags(tags).find(); - assertEquals(2, matches.size()); - } - - @Test public void do_not_find_entry_with_too_specific_tags() - { - Map tags = mapOf(tag("shop", "car")); - FeatureDictionary dictionary = dictionary(car_dealer, second_hand_car_dealer); - List matches = dictionary.byTags(tags).forLocale(Locale.GERMAN, null).find(); - assertEquals(listOf(car_dealer), matches); - } - - @Test public void find_entry_with_specific_tags() - { - Map tags = mapOf(tag("shop", "car"), tag("second_hand", "only")); - FeatureDictionary dictionary = dictionary(car_dealer, second_hand_car_dealer); - List matches = dictionary.byTags(tags).forLocale(Locale.GERMAN, null).find(); - assertEquals(listOf(second_hand_car_dealer), matches); - } - - //endregion - - //region by name - - @Test public void find_no_entry_by_name() - { - FeatureDictionary dictionary = dictionary(bakery); - assertEquals(listOf(), dictionary.byTerm("Supermarkt").find()); - } - - @Test public void find_no_entry_by_name_because_wrong_geometry() - { - FeatureDictionary dictionary = dictionary(bakery); - assertEquals(listOf(), dictionary.byTerm("Bäckerei").forGeometry(GeometryType.LINE).find()); - } - - @Test public void find_no_entry_by_name_because_wrong_country() - { - FeatureDictionary dictionary = dictionary(ditsch, ditschRussian); - assertEquals(listOf(), dictionary.byTerm("Ditsch").find()); - assertEquals(listOf(), dictionary.byTerm("Ditsch").inCountry("FR").find()); // not in France - assertEquals(listOf(), dictionary.byTerm("Ditsch").inCountry("AT-9").find()); // in all of AT but not Vienna - assertEquals(listOf(), dictionary.byTerm("Дитсч").inCountry("UA").find()); // only on the Krim - } - - @Test public void find_no_non_searchable_entry_by_name() - { - FeatureDictionary dictionary = dictionary(scheisshaus); - assertEquals(listOf(), dictionary.byTerm("Scheißhaus").find()); - } - - @Test public void find_entry_by_name() - { - FeatureDictionary dictionary = dictionary(bakery); - List matches = dictionary.byTerm("Bäckerei").forLocale((Locale) null).find(); - assertEquals(listOf(bakery), matches); - } - - @Test public void find_entry_by_name_with_correct_geometry() - { - FeatureDictionary dictionary = dictionary(bakery); - List matches = dictionary.byTerm("Bäckerei").forLocale((Locale) null).forGeometry(GeometryType.POINT).find(); - assertEquals(listOf(bakery), matches); - } - - @Test public void find_entry_by_name_with_correct_country() - { - FeatureDictionary dictionary = dictionary(ditsch, ditschRussian, bakery); - assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("DE").find()); - assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("DE-TH").find()); - assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("AT").find()); - assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("AT-5").find()); - assertEquals(listOf(ditschRussian), dictionary.byTerm("Дитсч").inCountry("UA-43").find()); - assertEquals(listOf(ditschRussian), dictionary.byTerm("Дитсч").inCountry("RU-KHA").find()); - } - - @Test public void find_entry_by_name_case_insensitive() - { - FeatureDictionary dictionary = dictionary(bakery); - List matches = dictionary.byTerm("BÄCkErEI").forLocale((Locale) null).find(); - assertEquals(listOf(bakery), matches); - } - - @Test public void find_entry_by_name_diacritics_insensitive() - { - FeatureDictionary dictionary = dictionary(bakery); - List matches = dictionary.byTerm("Backérèi").forLocale((Locale) null).find(); - assertEquals(listOf(bakery), matches); - } - - @Test public void find_entry_by_term() - { - FeatureDictionary dictionary = dictionary(bakery); - List matches = dictionary.byTerm("bro").forLocale((Locale) null).find(); - assertEquals(listOf(bakery), matches); - } - - @Test public void find_entry_by_term_brackets() - { - FeatureDictionary dictionary = dictionary(liquor_store); - assertEquals(listOf(liquor_store), dictionary.byTerm("Alcohol").forLocale(Locale.UK).find()); - assertEquals(listOf(liquor_store), dictionary.byTerm("Off licence (Alcohol Shop)").forLocale(Locale.UK).find()); - assertEquals(listOf(liquor_store), dictionary.byTerm("Off Licence").forLocale(Locale.UK).find()); - assertEquals(listOf(liquor_store), dictionary.byTerm("Off Licence (Alco").forLocale(Locale.UK).find()); - } - - @Test public void find_entry_by_term_case_insensitive() - { - FeatureDictionary dictionary = dictionary(bakery); - List matches = dictionary.byTerm("BRO").forLocale((Locale) null).find(); - assertEquals(listOf(bakery), matches); - } - - @Test public void find_entry_by_term_diacritics_insensitive() - { - FeatureDictionary dictionary = dictionary(bakery); - List matches = dictionary.byTerm("bró").forLocale((Locale) null).find(); - assertEquals(listOf(bakery), matches); - } - - @Test public void find_multiple_entries_by_term() - { - FeatureDictionary dictionary = dictionary(second_hand_car_dealer, car_dealer); - List matches = dictionary.byTerm("auto").forLocale(Locale.GERMAN).find(); - assertEqualsIgnoreOrder(listOf(second_hand_car_dealer, car_dealer), matches); - } - - @Test public void find_multiple_entries_by_name_but_respect_limit() - { - FeatureDictionary dictionary = dictionary(second_hand_car_dealer, car_dealer); - List matches = dictionary.byTerm("auto").forLocale(Locale.GERMAN).limit(1).find(); - assertEquals(1, matches.size()); - } - - @Test public void find_only_brands_by_name_finds_no_normal_entries() - { - FeatureDictionary dictionary = dictionary(bakery); - List matches = dictionary.byTerm("Bäckerei").forLocale((Locale) null).isSuggestion(true).find(); - assertEquals(0, matches.size()); - } - - @Test public void find_no_brands_by_name_finds_only_normal_entries() - { - FeatureDictionary dictionary = dictionary(bank, bank_of_america); - List matches = dictionary.byTerm("Bank").forLocale((Locale) null).isSuggestion(false).find(); - assertEquals(listOf(bank), matches); - } - - @Test public void find_no_entry_by_term_because_wrong_locale() - { - FeatureDictionary dictionary = dictionary(bakery); - assertEquals(listOf(), dictionary.byTerm("Bäck").forLocale(Locale.ITALIAN).find()); - } - - @Test public void find_entry_by_term_because_fallback_locale() - { - FeatureDictionary dictionary = dictionary(bakery); - List matches = dictionary.byTerm("Bäck").forLocale(Locale.ITALIAN, null).find(); - assertEquals(listOf(bakery), matches); - } - - @Test public void find_multi_word_brand_feature() - { - FeatureDictionary dictionary = dictionary(deutsche_bank); - assertEquals(listOf(deutsche_bank), dictionary.byTerm("Deutsche Ba").find()); - assertEquals(listOf(deutsche_bank), dictionary.byTerm("Deut").find()); - // by-word only for non-brand features - assertTrue(dictionary.byTerm("Ban").find().isEmpty()); - } - - @Test public void find_multi_word_feature() - { - FeatureDictionary dictionary = dictionary(miniature_train_shop); - assertEquals(listOf(miniature_train_shop), dictionary.byTerm("mini").forLocale((Locale) null).find()); - assertEquals(listOf(miniature_train_shop), dictionary.byTerm("train").forLocale((Locale) null).find()); - assertEquals(listOf(miniature_train_shop), dictionary.byTerm("shop").forLocale((Locale) null).find()); - assertEquals(listOf(miniature_train_shop), dictionary.byTerm("Miniature Trai").forLocale((Locale) null).find()); - assertEquals(listOf(miniature_train_shop), dictionary.byTerm("Miniature Train Shop").forLocale((Locale) null).find()); - assertTrue(dictionary.byTerm("Train Sho").forLocale((Locale) null).find().isEmpty()); - } - - @Test public void find_entry_by_tag_value() - { - FeatureDictionary dictionary = dictionary(panetteria); - List matches = dictionary.byTerm("bakery").forLocale(Locale.ITALIAN).find(); - assertEquals(listOf(panetteria), matches); - } - - //endregion - - //region by id - - @Test public void find_no_entry_by_id() - { - FeatureDictionary dictionary = dictionary(bakery); - assertNull(dictionary.byId("amenity/hospital").get()); - } - - @Test public void find_no_entry_by_id_because_unlocalized_results_are_excluded() - { - FeatureDictionary dictionary = dictionary(bakery); - assertNull(dictionary.byId("shop/bakery").forLocale(Locale.ITALIAN).get()); - } - - @Test public void find_entry_by_id() - { - FeatureDictionary dictionary = dictionary(bakery); - assertEquals(bakery, dictionary.byId("shop/bakery").get()); - assertEquals(bakery, dictionary.byId("shop/bakery").forLocale(Locale.CHINESE, null).get()); - } - - @Test public void find_localized_entry_by_id() - { - FeatureDictionary dictionary = dictionary(panetteria); - assertEquals(panetteria, dictionary.byId("shop/bakery").forLocale(Locale.ITALIAN).get()); - assertEquals(panetteria, dictionary.byId("shop/bakery").forLocale(Locale.ITALIAN, null).get()); - } - - @Test public void find_no_entry_by_id_because_wrong_country() - { - FeatureDictionary dictionary = dictionary(ditsch); - assertNull(dictionary.byId("shop/bakery/Ditsch").get()); - assertNull(dictionary.byId("shop/bakery/Ditsch").inCountry("IT").get()); - assertNull(dictionary.byId("shop/bakery/Ditsch").inCountry("AT-9").get()); - } - - @Test public void find_entry_by_id_in_country() - { - FeatureDictionary dictionary = dictionary(ditsch); - assertEquals(ditsch, dictionary.byId("shop/bakery/Ditsch").inCountry("AT").get()); - assertEquals(ditsch, dictionary.byId("shop/bakery/Ditsch").inCountry("DE").get()); - } - - //endregion - - @Test public void find_by_term_sorts_result_in_correct_order() - { - FeatureDictionary dictionary = dictionary( - casino, baenk, bad_bank, stock_exchange, bank_of_liechtenstein, bank, bench, atm, - bank_of_america, deutsche_bank, thieves_guild); - List matches = dictionary.byTerm("Bank").forLocale((Locale) null).find(); - assertEquals(listOf( - bank, // exact name matches - baenk, // exact name matches (diacritics and case insensitive) - atm, // starts-with name matches - thieves_guild, // starts-with alias matches - bad_bank, // starts-with-word name matches - bank_of_america, // starts-with brand name matches - bank_of_liechtenstein, // starts-with brand name matches - lower matchScore - bench, // found word in terms - higher matchScore - stock_exchange // found word in terms - lower matchScore - // casino, // not included: "Spielbank" does not start with "bank" - // deutsche_bank // not included: "Deutsche Bank" does not start with "bank" and is a brand - ), matches); - } - - @Test public void some_tests_with_real_data() - { - LocalizedFeatureCollection featureCollection = new IDLocalizedFeatureCollection(new LivePresetDataAccessAdapter()); - featureCollection.getAll(listOf(Locale.ENGLISH)); - FeatureDictionary dictionary = new FeatureDictionary(featureCollection, null); - - List matches = dictionary - .byTags(mapOf(tag("amenity", "studio"))) - .forLocale(Locale.ENGLISH) - .find(); - assertEquals(1, matches.size()); - assertEquals("Studio", matches.get(0).getName()); - - List matches2 = dictionary - .byTags(mapOf(tag("amenity", "studio"), tag("studio", "audio"))) - .forLocale(Locale.ENGLISH) - .find(); - assertEquals(1, matches2.size()); - assertEquals("Recording Studio", matches2.get(0).getName()); - - List matches3 = dictionary - .byTerm("Chinese Res") - .forLocale(Locale.ENGLISH) - .find(); - assertEquals(1, matches3.size()); - assertEquals("Chinese Restaurant", matches3.get(0).getName()); - } - - @Test public void issue19() - { - Feature lush = feature( - "shop/cosmetics/lush-a08666", - mapOf(tag("brand:wikidata","Q1585448"), tag("shop", "cosmetics")), - listOf(GeometryType.POINT, GeometryType.AREA), - listOf("Lush"), - listOf("lush"), - listOf(), - listOf(), - true, - 2.0, - mapOf( - tag("brand", "Lush"), - tag("brand:wikidata", "Q1585448"), - tag("name", "Lush"), - tag("shop", "cosmetics") - ), - true, - null - ); - - FeatureDictionary dictionary = dictionary(lush); - - List byTags = dictionary - .byTags(mapOf(tag("brand:wikidata","Q1585448"), tag("shop", "cosmetics"))) - .forLocale(Locale.GERMAN, null) - .inCountry("DE") - .find(); - assertEquals(1, byTags.size()); - assertEquals(lush, byTags.get(0)); - - List byTerm = dictionary - .byTerm("Lush") - .forLocale(Locale.GERMAN, null) - .inCountry("DE") - .find(); - assertEquals(1, byTerm.size()); - assertEquals(lush, byTerm.get(0)); - - Feature byId = dictionary - .byId("shop/cosmetics/lush-a08666") - .forLocale(Locale.GERMAN, null) - .inCountry("DE") - .get(); - assertEquals(lush, byId); - } - - private static FeatureDictionary dictionary(Feature... entries) - { - List features = new ArrayList<>(); - List brandFeatures = new ArrayList<>(); - for (Feature entry : entries) { - if (entry instanceof SuggestionFeature) { - brandFeatures.add(entry); - } else { - features.add(entry); - } - } - return new FeatureDictionary( - new TestLocalizedFeatureCollection(features), - new TestPerCountryFeatureCollection(brandFeatures) - ); - } - - private static Feature feature( - String id, Map tags, List geometries, List names, - List terms, List countryCodes, List excludeCountryCodes, - boolean searchable, double matchScore, Map addTags, - boolean isSuggestion, Locale locale - ) { - if (isSuggestion) { - return new SuggestionFeature( - id, tags, geometries, null, null, names, terms, countryCodes, - excludeCountryCodes, searchable, matchScore, addTags, mapOf() - ); - } else { - BaseFeature f = new BaseFeature( - id, tags, geometries, null, null, names, terms, countryCodes, - excludeCountryCodes, searchable, matchScore, false, addTags, mapOf() - ); - if (locale != null) { - return new LocalizedFeature(f, locale, f.getNames(), f.getTerms()); - } else { - return f; - } - } - } - - static class SuggestionFeature extends BaseFeature { - public SuggestionFeature( - String id, - Map tags, - List geometry, - String icon, - String imageURL, - List names, - List terms, - List includeCountryCodes, - List excludeCountryCodes, - boolean searchable, - double matchScore, - Map addTags, - Map removeTags - ) { - super(id, tags, geometry, icon, imageURL, names, terms, includeCountryCodes, excludeCountryCodes, searchable, matchScore, true, addTags, removeTags); - } - } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/FeatureTagsIndexTest.java b/library/src/test/java/de/westnordost/osmfeatures/FeatureTagsIndexTest.java deleted file mode 100644 index 874c59d..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/FeatureTagsIndexTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package de.westnordost.osmfeatures; - -import org.junit.Test; - -import static de.westnordost.osmfeatures.MapEntry.mapOf; -import static de.westnordost.osmfeatures.MapEntry.tag; -import static de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder; -import static de.westnordost.osmfeatures.TestUtils.listOf; -import static org.junit.Assert.assertTrue; - -public class FeatureTagsIndexTest -{ - @Test public void copes_with_empty_collection() - { - FeatureTagsIndex index = index(); - assertTrue(index.getAll(mapOf(tag("a", "b"))).isEmpty()); - } - - @Test public void get_two_features_with_same_tags() - { - Feature f1 = feature(tag("a", "b")); - Feature f2 = feature(tag("a", "b")); - FeatureTagsIndex index = index(f1, f2); - assertEqualsIgnoreOrder( - listOf(f1, f2), - index.getAll(mapOf(tag("a", "b"), tag("c", "d"))) - ); - } - - @Test public void get_two_features_with_different_tags() - { - Feature f1 = feature(tag("a", "b")); - Feature f2 = feature(tag("c", "d")); - FeatureTagsIndex index = index(f1, f2); - assertEqualsIgnoreOrder( - listOf(f1, f2), - index.getAll(mapOf(tag("a", "b"), tag("c", "d"))) - ); - } - - private static FeatureTagsIndex index(Feature... features) - { - return new FeatureTagsIndex(listOf(features)); - } - - private static Feature feature(MapEntry... mapEntries) - { - return new BaseFeature( - "id", - mapOf(mapEntries), - listOf(GeometryType.POINT), - null, null, - listOf("name"), - listOf(), - listOf(), - listOf(), - true, - 1.0, - false, - mapOf(), - mapOf() - ); - } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/FeatureTermIndexTest.java b/library/src/test/java/de/westnordost/osmfeatures/FeatureTermIndexTest.java deleted file mode 100644 index 4bcfb5d..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/FeatureTermIndexTest.java +++ /dev/null @@ -1,88 +0,0 @@ -package de.westnordost.osmfeatures; - -import org.junit.Test; - -import static de.westnordost.osmfeatures.MapEntry.mapOf; -import static de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder; -import static de.westnordost.osmfeatures.TestUtils.listOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class FeatureTermIndexTest -{ - @Test public void copes_with_empty_collection() - { - FeatureTermIndex index = index(); - assertTrue(index.getAll("a").isEmpty()); - } - - - @Test public void get_one_features_with_same_term() - { - Feature f1 = feature("a", "b"); - Feature f2 = feature("c"); - FeatureTermIndex index = index(f1, f2); - assertEqualsIgnoreOrder( - listOf(f1), - index.getAll("b") - ); - } - - @Test public void get_two_features_with_same_term() - { - Feature f1 = feature("a", "b"); - Feature f2 = feature("a", "c"); - FeatureTermIndex index = index(f1, f2); - assertEqualsIgnoreOrder( - listOf(f1, f2), - index.getAll("a") - ); - } - - @Test public void get_two_features_with_different_terms() - { - Feature f1 = feature("anything"); - Feature f2 = feature("anybody"); - FeatureTermIndex index = index(f1, f2); - assertEqualsIgnoreOrder( - listOf(f1, f2), - index.getAll("any") - ); - assertEqualsIgnoreOrder( - listOf(f1), - index.getAll("anyt") - ); - } - - @Test public void dont_get_one_feature_twice() - { - Feature f1 = feature("something", "someone"); - FeatureTermIndex index = index(f1); - assertEquals(listOf(f1), index.getAll("some")); - } - - private static FeatureTermIndex index(Feature... features) - { - return new FeatureTermIndex(listOf(features), Feature::getTerms); - } - - private static Feature feature(String... terms) - { - return new BaseFeature( - "id", - mapOf(), - listOf(GeometryType.POINT), - null, - null, - listOf("name"), - listOf(terms), - listOf(), - listOf(), - true, - 1.0, - false, - mapOf(), - mapOf() - ); - } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.java b/library/src/test/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.java deleted file mode 100644 index 9971b28..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package de.westnordost.osmfeatures; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder; -import static de.westnordost.osmfeatures.TestUtils.listOf; - -import okio.FileHandle; -import okio.FileSystem; -import okio.Path; -import okio.Source; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -public class IDBrandPresetsFeatureCollectionTest { - - @Test public void load_brands() - { - IDBrandPresetsFeatureCollection c = new IDBrandPresetsFeatureCollection(new FileAccessAdapter() - { - @Override public boolean exists(String name) - { - return name.equals("presets.json"); - } - - @Override public Source open(String name) throws IOException { - if (name.equals("presets.json")) return getSource("brand_presets_min.json"); - throw new IOException("wrong file name"); - } - }); - - assertEqualsIgnoreOrder(listOf("Duckworths", "Megamall"), getNames(c.getAll(listOf((String)null)))); - assertEquals("Duckworths", c.get("a/brand", listOf((String)null)).getName()); - assertEquals("Megamall", c.get("another/brand", listOf((String)null)).getName()); - } - - @Test public void load_brands_by_country() - { - IDBrandPresetsFeatureCollection c = new IDBrandPresetsFeatureCollection(new FileAccessAdapter() - { - @Override public boolean exists(String name) - { - return name.equals("presets-DE.json"); - } - - @Override public Source open(String name) throws IOException { - if (name.equals("presets-DE.json")) return getSource("brand_presets_min2.json"); - throw new IOException("File not found"); - } - }); - - assertEqualsIgnoreOrder(listOf("Talespin"), getNames(c.getAll(listOf("DE")))); - assertEquals("Talespin", c.get("yet_another/brand", listOf("DE")).getName()); - assertTrue(c.get("yet_another/brand", listOf("DE")).isSuggestion()); - } - - private Source getSource(String file) throws IOException { - return FileSystem.SYSTEM.source(Path.get(getClass().getClassLoader().getResource(file).getFile())); - } - - private static Collection getNames(Collection features) - { - List result = new ArrayList<>(); - for (Feature feature : features) { - result.add(feature.getName()); - } - return result; - } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.java b/library/src/test/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.java deleted file mode 100644 index a654f0e..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.java +++ /dev/null @@ -1,154 +0,0 @@ -package de.westnordost.osmfeatures; - -import okio.FileHandle; -import okio.FileSystem; -import okio.Path; -import okio.Source; -import org.junit.Test; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import static de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder; -import static de.westnordost.osmfeatures.TestUtils.listOf; -import static org.junit.Assert.*; - -public class IDLocalizedFeatureCollectionTest -{ - @Test public void features_not_found_produces_runtime_exception() - { - try - { - new IDLocalizedFeatureCollection(new FileAccessAdapter() - { - @Override public boolean exists(String name) { return false; } - @Override public Source open(String name) throws IOException { throw new FileNotFoundException(); } - }); - fail(); - } catch (RuntimeException ignored) { } - } - - @Test public void load_features_and_two_localizations() - { - IDLocalizedFeatureCollection c = new IDLocalizedFeatureCollection(new FileAccessAdapter() - { - @Override public boolean exists(String name) - { - return Arrays.asList("presets.json", "en.json", "de.json").contains(name); - } - - @Override public Source open(String name) throws IOException - { - if (name.equals("presets.json")) return getSource("some_presets_min.json"); - if (name.equals("en.json")) return getSource("localizations_en.json"); - if (name.equals("de.json")) return getSource("localizations_de.json"); - throw new IOException("File not found"); - } - }); - - // getting non-localized features - List notLocalized = listOf((Locale) null); - Collection notLocalizedFeatures = c.getAll(notLocalized); - assertEqualsIgnoreOrder(listOf("test", "test", "test"), getNames(notLocalizedFeatures)); - - assertEquals("test", c.get("some/id", notLocalized).getName()); - assertEquals("test", c.get("another/id", notLocalized).getName()); - assertEquals("test", c.get("yet/another/id", notLocalized).getName()); - - // getting English features - List english = listOf(Locale.ENGLISH); - Collection englishFeatures = c.getAll(english); - assertEqualsIgnoreOrder(listOf("Bakery"), getNames(englishFeatures)); - - assertEquals("Bakery", c.get("some/id", english).getName()); - assertNull(c.get("another/id", english)); - assertNull(c.get("yet/another/id", english)); - - // getting Germany features - // this also tests if the fallback from de-DE to de works if de-DE.json does not exist - List germany = listOf(Locale.GERMANY); - Collection germanyFeatures = c.getAll(germany); - assertEqualsIgnoreOrder(listOf("Bäckerei", "Gullideckel"), getNames(germanyFeatures)); - - assertEquals("Bäckerei", c.get("some/id", germany).getName()); - assertEquals("Gullideckel", c.get("another/id", germany).getName()); - assertNull(c.get("yet/another/id", germany)); - - // getting features through fallback chain - List locales = listOf(Locale.ENGLISH, Locale.GERMANY, null); - Collection fallbackFeatures = c.getAll(locales); - assertEqualsIgnoreOrder(listOf("Bakery", "Gullideckel", "test"), getNames(fallbackFeatures)); - - assertEquals("Bakery", c.get("some/id", locales).getName()); - assertEquals("Gullideckel", c.get("another/id", locales).getName()); - assertEquals("test", c.get("yet/another/id", locales).getName()); - - assertEquals(Locale.ENGLISH, c.get("some/id", locales).getLocale()); - assertEquals(Locale.GERMAN, c.get("another/id", locales).getLocale()); - assertNull(c.get("yet/another/id", locales).getLocale()); - } - - @Test public void load_features_and_merge_localizations() - { - IDLocalizedFeatureCollection c = new IDLocalizedFeatureCollection(new FileAccessAdapter() - { - @Override public boolean exists(String name) - { - return Arrays.asList("presets.json", "de-AT.json", "de.json", "de-Cyrl.json", "de-Cyrl-AT.json").contains(name); - } - - @Override public Source open(String name) throws IOException - { - if (name.equals("presets.json")) return getSource("some_presets_min.json"); - if (name.equals("de-AT.json")) return getSource("localizations_de-AT.json"); - if (name.equals("de.json")) return getSource("localizations_de.json"); - if (name.equals("de-Cyrl-AT.json")) return getSource("localizations_de-Cyrl-AT.json"); - if (name.equals("de-Cyrl.json")) return getSource("localizations_de-Cyrl.json"); - throw new IOException("File not found"); - } - }); - - // standard case - no merging - List german = listOf(Locale.GERMAN); - Collection germanFeatures = c.getAll(german); - assertEqualsIgnoreOrder(listOf("Bäckerei", "Gullideckel"), getNames(germanFeatures)); - - assertEquals("Bäckerei", c.get("some/id", german).getName()); - assertEquals("Gullideckel", c.get("another/id", german).getName()); - assertNull(c.get("yet/another/id", german)); - - // merging de-AT and de - List austria = listOf(new Locale("de", "AT")); - Collection austrianFeatures = c.getAll(austria); - assertEqualsIgnoreOrder(listOf("Backhusl", "Gullideckel", "Brückle"), getNames(austrianFeatures)); - - assertEquals("Backhusl", c.get("some/id", austria).getName()); - assertEquals("Gullideckel", c.get("another/id", austria).getName()); - assertEquals("Brückle", c.get("yet/another/id", austria).getName()); - - // merging scripts - List cryllic = listOf(new Locale.Builder().setLanguage("de").setScript("Cyrl").build()); - assertEquals("бацкхаус", c.get("some/id", cryllic).getName()); - - List cryllicAustria = listOf(new Locale.Builder().setLanguage("de").setRegion("AT").setScript("Cyrl").build()); - assertEquals("бацкхусл", c.get("some/id", cryllicAustria).getName()); - } - - private Source getSource(String file) throws IOException { - return FileSystem.SYSTEM.source(Path.get(getClass().getClassLoader().getResource(file).getFile())); - } - - private static Collection getNames(Collection features) - { - List result = new ArrayList<>(); - for (Feature feature : features) { - result.add(feature.getName()); - } - return result; - } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/IDPresetsJsonParserTest.java b/library/src/test/java/de/westnordost/osmfeatures/IDPresetsJsonParserTest.java deleted file mode 100644 index c26ddb7..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/IDPresetsJsonParserTest.java +++ /dev/null @@ -1,104 +0,0 @@ -package de.westnordost.osmfeatures; - -import okio.Okio; -import okio.Source; -import org.junit.Test; - -import java.io.IOException; -import java.net.URL; -import java.util.List; - -import static de.westnordost.osmfeatures.MapEntry.mapOf; -import static de.westnordost.osmfeatures.MapEntry.tag; -import static de.westnordost.osmfeatures.TestUtils.listOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -public class IDPresetsJsonParserTest { - - @Test public void load_features_only() - { - List features = parse("one_preset_full.json"); - - assertEquals(1, features.size()); - Feature feature = features.get(0); - assertEquals("some/id", feature.getId()); - assertEquals(mapOf(tag("a","b"), tag("c","d")), feature.getTags()); - assertEquals(listOf(GeometryType.POINT, GeometryType.VERTEX, GeometryType.LINE, GeometryType.AREA, GeometryType.RELATION), feature.getGeometry()); - - assertEquals(listOf("DE", "GB"), feature.getIncludeCountryCodes()); - assertEquals(listOf("IT"), feature.getExcludeCountryCodes()); - assertEquals("foo", feature.getName()); - assertEquals("abc", feature.getIcon()); - assertEquals("someurl", feature.getImageURL()); - assertEquals(listOf("foo", "one","two"), feature.getNames()); - assertEquals(listOf("1","2"), feature.getTerms()); - assertEquals(0.5f, feature.getMatchScore(), 0.001f); - assertFalse(feature.isSearchable()); - assertEquals(mapOf(tag("e","f")), feature.getAddTags()); - assertEquals(mapOf(tag("d","g")), feature.getRemoveTags()); - } - - @Test public void load_features_only_defaults() - { - List features = parse("one_preset_min.json"); - - assertEquals(1, features.size()); - Feature feature = features.get(0); - - assertEquals("some/id", feature.getId()); - assertEquals(mapOf(tag("a","b"), tag("c","d")), feature.getTags()); - assertEquals(listOf(GeometryType.POINT), feature.getGeometry()); - - assertTrue(feature.getIncludeCountryCodes().isEmpty()); - assertTrue(feature.getExcludeCountryCodes().isEmpty()); - assertEquals("", feature.getName()); - assertEquals("",feature.getIcon()); - assertEquals("",feature.getImageURL()); - assertEquals(1, feature.getNames().size()); - assertTrue(feature.getTerms().isEmpty()); - assertEquals(1.0f, feature.getMatchScore(), 0.001f); - assertTrue(feature.isSearchable()); - assertEquals(feature.getAddTags(), feature.getTags()); - assertEquals(feature.getAddTags(), feature.getRemoveTags()); - } - - @Test public void load_features_unsupported_location_set() - { - List features = parse("one_preset_unsupported_location_set.json"); - assertEquals(2, features.size()); - assertEquals("some/ok", features.get(0).getId()); - assertEquals("another/ok", features.get(1).getId()); - } - - @Test public void load_features_no_wildcards() - { - List features = parse("one_preset_wildcard.json"); - assertTrue(features.isEmpty()); - } - - @Test public void parse_some_real_data() throws IOException - { - URL url = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json"); - - List features = new IDPresetsJsonParser().parse(Okio.source(url.openStream())); - // should not crash etc - assertTrue(features.size() > 1000); - } - - private List parse(String file) - { - try - { - return new IDPresetsJsonParser().parse(getSource(file)); - } catch (IOException e) - { - throw new RuntimeException(); - } - } - - private Source getSource(String file) throws IOException { - return Okio.source(getClass().getClassLoader().getResource(file).openConnection().getInputStream()); - } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java b/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java deleted file mode 100644 index e499b70..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.java +++ /dev/null @@ -1,111 +0,0 @@ -package de.westnordost.osmfeatures; - -import okio.FileSystem; -import okio.Okio; -import okio.Path; -import okio.Source; -import org.junit.Test; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static de.westnordost.osmfeatures.MapEntry.mapOf; -import static de.westnordost.osmfeatures.MapEntry.tag; -import static de.westnordost.osmfeatures.TestUtils.listOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class IDPresetsTranslationJsonParserTest { - - @Test public void load_features_and_localization() - { - List features = parse("one_preset_min.json", "localizations.json"); - - assertEquals(1, features.size()); - Feature feature = features.get(0); - - assertEquals("some/id", feature.getId()); - assertEquals(mapOf(tag("a","b"), tag("c","d")), feature.getTags()); - assertEquals(listOf(GeometryType.POINT), feature.getGeometry()); - assertEquals("bar", feature.getName()); - assertEquals(listOf("bar", "one", "two", "three"), feature.getNames()); - assertEquals(listOf("a", "b"), feature.getTerms()); - } - - @Test public void load_features_and_localization_defaults() - { - List features = parse("one_preset_min.json", "localizations_min.json"); - - assertEquals(1, features.size()); - Feature feature = features.get(0); - - assertEquals("some/id", feature.getId()); - assertEquals(mapOf(tag("a","b"), tag("c","d")), feature.getTags()); - assertEquals(listOf(GeometryType.POINT), feature.getGeometry()); - assertEquals("bar", feature.getName()); - assertTrue(feature.getTerms().isEmpty()); - } - - @Test public void load_features_and_localization_with_placeholder_name() - { - List features = parse("one_preset_with_placeholder_name.json", "localizations.json"); - - Map featuresById = new HashMap<>(); - for (LocalizedFeature feature : features) { - featuresById.put(feature.getId(), feature); - } - - assertEquals(2, features.size()); - Feature feature = featuresById.get("some/id-dingsdongs"); - - assertEquals("some/id-dingsdongs", feature.getId()); - assertEquals(mapOf(tag("a","b"), tag("c","d")), feature.getTags()); - assertEquals(listOf(GeometryType.POINT), feature.getGeometry()); - assertEquals("bar", feature.getName()); - assertEquals(listOf("bar", "one", "two", "three"), feature.getNames()); - assertEquals(listOf("a", "b"), feature.getTerms()); - } - - @Test public void parse_some_real_data() throws IOException, URISyntaxException { - URL url = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json"); - List features = new IDPresetsJsonParser().parse(Okio.source(url.openConnection().getInputStream())); - Map featureMap = new HashMap<>(); - for (BaseFeature feature : features) - { - featureMap.put(feature.getId(), feature); - } - - URL rawTranslationsURL = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json"); - List translatedFeatures = new IDPresetsTranslationJsonParser().parse(Okio.source(rawTranslationsURL.openStream()), Locale.GERMAN, featureMap); - - // should not crash etc - assertTrue(translatedFeatures.size() > 1000); - } - - private List parse(String presetsFile, String translationsFile) - { - try - { - List baseFeatures = new IDPresetsJsonParser().parse(getSource(presetsFile)); - Map featureMap = new HashMap<>(); - for (BaseFeature feature : baseFeatures) - { - featureMap.put(feature.getId(), feature); - } - return new IDPresetsTranslationJsonParser().parse(getSource(translationsFile), Locale.ENGLISH, featureMap); - } catch (IOException e) - { - throw new RuntimeException(); - } - } - - private Source getSource(String file) throws IOException { - return FileSystem.SYSTEM.source(Path.get(getClass().getClassLoader().getResource(file).getFile())); - } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/JsonUtilsTest.java b/library/src/test/java/de/westnordost/osmfeatures/JsonUtilsTest.java deleted file mode 100644 index ddad544..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/JsonUtilsTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package de.westnordost.osmfeatures; - -import kotlinx.serialization.json.Json; -import kotlinx.serialization.json.JsonArray; -import kotlinx.serialization.json.JsonElement; -import kotlinx.serialization.json.JsonPrimitive; -import org.json.JSONArray; -import org.json.JSONObject; -import org.junit.Test; - -import java.util.Arrays; -import java.util.Map; - -import static de.westnordost.osmfeatures.JsonUtils.parseList; -import static de.westnordost.osmfeatures.JsonUtils.parseStringMap; -import static de.westnordost.osmfeatures.MapEntry.mapOf; -import static de.westnordost.osmfeatures.MapEntry.tag; -import static de.westnordost.osmfeatures.TestUtils.listOf; -import static org.junit.Assert.assertEquals; - -public class JsonUtilsTest -{ - @Test public void parseList_with_null_json_array() - { - assertEquals(0, parseList(null, obj -> obj).size()); - } - - @Test public void parseList_with_empty_json_array() - { - assertEquals(0, parseList(new JsonArray(listOf()), obj -> obj).size()); - } - -// @Test public void parseList_with_array() -// { -// String[] array = new String[]{"a","b","c"}; -// JsonPrimitive prim = new JsonElement() { -// } -// assertEquals(listOf(array), parseList(new JsonArray(null)., obj -> obj)); -// } - -// @Test public void parseList_with_array_and_transformer() -// { -// int[] array = new int[]{1,2,3}; -// assertEquals(listOf(2,4,6), parseList(new JSONArray(array), i -> (int)i*2)); -// } - - @Test public void parseStringMap_with_null_json_map() - { - assertEquals(0, parseStringMap(null).size()); - } - -// @Test public void parseStringMap_with_empty_json_map() -// { -// assertEquals(0, parseStringMap(new JSONObject()).size()); -// } - -// @Test public void parseStringMap_with_one_entry() -// { -// Map m = mapOf(tag("a","b")); -// assertEquals(m, parseStringMap(new JSONObject(m))); -// } -// -// @Test public void parseStringMap_with_several_entries() -// { -// Map m = mapOf(tag("a","b"), tag("c", "d")); -// assertEquals(m, parseStringMap(new JSONObject(m))); -// } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.java b/library/src/test/java/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.java deleted file mode 100644 index 87d294f..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.java +++ /dev/null @@ -1,32 +0,0 @@ -package de.westnordost.osmfeatures; - -import okio.BufferedSource; -import okio.FileHandle; -import okio.Okio; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import static de.westnordost.osmfeatures.TestUtils.listOf; - -public class LivePresetDataAccessAdapter implements FileAccessAdapter -{ - @Override public boolean exists(String name) - { - return listOf("presets.json", "de.json", "en.json", "en-GB.json").contains(name); - } - - @Override public okio.Source open(String name) throws IOException - { - URL url; - if(name.equals("presets.json")) - { - url = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json"); - } else - { - url = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/"+name); - } - return Okio.source(url.openStream()); - } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/MapEntry.java b/library/src/test/java/de/westnordost/osmfeatures/MapEntry.java deleted file mode 100644 index 649a274..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/MapEntry.java +++ /dev/null @@ -1,23 +0,0 @@ -package de.westnordost.osmfeatures; - -import java.util.HashMap; -import java.util.Map; - -class MapEntry -{ - private String key, value; - private MapEntry(String key, String value) - { - this.key = key; - this.value = value; - } - - static MapEntry tag(String key, String value) { return new MapEntry(key, value); } - - static Map mapOf(MapEntry... items) - { - Map result = new HashMap<>(); - for (MapEntry item : items) result.put(item.key, item.value); - return result; - } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/StartsWithStringTreeTest.java b/library/src/test/java/de/westnordost/osmfeatures/StartsWithStringTreeTest.java deleted file mode 100644 index ea41391..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/StartsWithStringTreeTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package de.westnordost.osmfeatures; - -import org.junit.Test; - -import static de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder; -import static de.westnordost.osmfeatures.TestUtils.listOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class StartsWithStringTreeTest -{ - @Test public void copes_with_empty_collection() - { - StartsWithStringTree t = new StartsWithStringTree(listOf()); - assertTrue(t.getAll("any").isEmpty()); - } - - @Test public void find_single_string() - { - StartsWithStringTree t = new StartsWithStringTree(listOf("anything")); - assertEquals(listOf("anything"), t.getAll("a")); - assertEquals(listOf("anything"), t.getAll("any")); - assertEquals(listOf("anything"), t.getAll("anything")); - } - - @Test public void dont_find_single_string() - { - StartsWithStringTree t = new StartsWithStringTree(listOf("anything", "more", "etc")); - - assertTrue(t.getAll("").isEmpty()); - assertTrue(t.getAll("nything").isEmpty()); - assertTrue(t.getAll("anything else").isEmpty()); - } - - @Test public void find_several_strings() - { - StartsWithStringTree t = new StartsWithStringTree(listOf("anything", "anybody", "anytime")); - - assertEqualsIgnoreOrder(listOf("anything", "anybody", "anytime"), t.getAll("any")); - } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.java b/library/src/test/java/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.java deleted file mode 100644 index e11d894..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.java +++ /dev/null @@ -1,36 +0,0 @@ -package de.westnordost.osmfeatures; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -public class TestLocalizedFeatureCollection implements LocalizedFeatureCollection -{ - private final List features; - - public TestLocalizedFeatureCollection(List features) - { - this.features = features; - } - - @Override public Collection getAll(List locales) - { - List result = new ArrayList<>(); - for (Feature feature : features) { - if (locales.contains(feature.getLocale())) { - result.add(feature); - } - } - return result; - } - - @Override public Feature get(String id, List locales) - { - for (Feature feature : features) { - if (!feature.getId().equals(id)) continue; - if (!locales.contains(feature.getLocale())) return null; - return feature; - } - return null; - } -} \ No newline at end of file diff --git a/library/src/test/java/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.java b/library/src/test/java/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.java deleted file mode 100644 index 92eac0a..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.java +++ /dev/null @@ -1,44 +0,0 @@ -package de.westnordost.osmfeatures; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -public class TestPerCountryFeatureCollection implements PerCountryFeatureCollection -{ - private final List features; - - public TestPerCountryFeatureCollection(List features) - { - this.features = features; - } - - @Override - public Collection getAll(List countryCodes) { - List result = new ArrayList<>(); - for (Feature feature : features) { - for (String countryCode : countryCodes) { - List includeCountryCodes = feature.getIncludeCountryCodes(); - if (includeCountryCodes.contains(countryCode) || countryCode == null && includeCountryCodes.isEmpty()) { - result.add(feature); - break; - } - } - } - return result; - } - - @Override - public Feature get(String id, List countryCodes) { - for (Feature feature : features) { - if (!feature.getId().equals(id)) continue; - List includeCountryCodes = feature.getIncludeCountryCodes(); - for (String countryCode : countryCodes) { - if (includeCountryCodes.contains(countryCode) || countryCode == null && includeCountryCodes.isEmpty()) { - return feature; - } - } - } - return null; - } -} diff --git a/library/src/test/java/de/westnordost/osmfeatures/TestUtils.java b/library/src/test/java/de/westnordost/osmfeatures/TestUtils.java deleted file mode 100644 index 71ba6cf..0000000 --- a/library/src/test/java/de/westnordost/osmfeatures/TestUtils.java +++ /dev/null @@ -1,16 +0,0 @@ -package de.westnordost.osmfeatures; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import static org.junit.Assert.assertTrue; - -public class TestUtils { - public static void assertEqualsIgnoreOrder(Collection a, Collection b) - { - assertTrue(a.size() == b.size() && a.containsAll(b)); - } - - @SafeVarargs public static List listOf(T... items) { return Arrays.asList(items); } -} diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 3d1b47f..0000000 --- a/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':library', ':library-android' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..ee83769 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + google() + gradlePluginPortal() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "osmfeatures" +include(":library") From 49f0c9b887f7b41f11c627c4e17e4ce1f98d5028 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Tue, 16 Jan 2024 21:37:23 +0100 Subject: [PATCH 15/98] Fix build --- library/build.gradle.kts | 3 +- .../osmfeatures/ContainedMapTree.kt | 162 ------------------ 2 files changed, 1 insertion(+), 164 deletions(-) delete mode 100644 library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.kt diff --git a/library/build.gradle.kts b/library/build.gradle.kts index ecf4f3d..ff95639 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -27,8 +27,7 @@ kotlin { dependencies { implementation(libs.kotlin.test) implementation(libs.junit) - }, - resources.sr + } } } } diff --git a/library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.kt b/library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.kt deleted file mode 100644 index f6c14a3..0000000 --- a/library/src/main/java/de/westnordost/osmfeatures/ContainedMapTree.kt +++ /dev/null @@ -1,162 +0,0 @@ -package de.westnordost.osmfeatures - -import de.westnordost.osmfeatures.CollectionUtils.mapContainsAllEntries -import java.util.* - -/** Index that makes finding which maps are completely contained by a given map very efficient. - * It sorts the maps into a tree structure with configurable depth. - * - * It is threadsafe because it is immutable. - * - * For example for the string maps... - *
- * [
- * #1 (amenity -> bicycle_parking),
- * #2 (amenity -> bicycle_parking, bicycle_parking -> shed),
- * #3 (amenity -> bicycle_parking, bicycle_parking -> lockers),
- * #4 (amenity -> taxi),
- * #5 (shop -> supermarket),
- * ]
-
* - * ...the tree internally looks like this: - *
- * amenity ->
- * bicycle_parking ->
- * #1
- * bicycle_parking ->
- * shed ->
- * #2
- * lockers ->
- * #3
- * taxi ->
- * #4
- * shop ->
- * supermarket ->
- * #5
- * ...
-
* - */ -internal class ContainedMapTree -@JvmOverloads constructor(maps: Collection>, maxDepth: Int = 4, minContainerSize: Int = 4) { - private val root: Node - - /** Create this index with the given maps. - * - * The generated tree will have a max depth of maxDepth and another depth is not added to the - * tree if there are less than minContainerSize maps in one tree node. - */ - init { - var maxDepth = maxDepth - if (maxDepth < 0) maxDepth = 0 - root = buildTree(maps, emptyList(), maxDepth, minContainerSize) - } - - /** Get all maps whose entries are completely contained by the given map */ - fun getAll(map: Map?): List> { - return root.getAll(map!!) - } - - private class Node( - /** key -> (value -> Node) */ - val children: Map>>?, maps: Collection> - ) { - val maps: Collection>? = maps - - /** Get all maps whose entries are all contained by given map */ - fun getAll(map: Map): List> { - val result: MutableList> = ArrayList() - if (children != null) { - for ((key, value) in children) { - if (map.containsKey(key)) { - for ((keyNode, node) in value) { - if (keyNode == map[key]) { - result.addAll(node.getAll(map)) - } - } - } - } - } - if (maps != null) { - for (m in maps) { - if (mapContainsAllEntries(map, m.entries)) { - result.add(m) - } - } - } - return result - } - } - - companion object { - private fun buildTree( - maps: Collection>, - previousKeys: Collection, - maxDepth: Int, - minContainerSize: Int - ): Node { - if (previousKeys.size == maxDepth || maps.size < minContainerSize) return Node(null, maps) - - val unsortedMaps: MutableSet> = HashSet(maps) - - val mapsByKey = getMapsByKey(maps, previousKeys) - - /* the map should be categorized by frequent keys first and least frequent keys last. */ - val sortedByCountDesc: List>>> = ArrayList(mapsByKey.entries) - Collections.sort(sortedByCountDesc) { a: Map.Entry>>, b: Map.Entry>> -> b.value.size - a.value.size } - - val result = HashMap>>(mapsByKey.size) - - for ((key, mapsForKey) in sortedByCountDesc) { - // a map already sorted in a certain node should not be sorted into another too - mapsForKey.retainAll(unsortedMaps) - if (mapsForKey.isEmpty()) continue - - val featuresByValue: Map>> = getMapsByKeyValue(key, mapsForKey) - - val valueNodes: MutableMap> = HashMap(featuresByValue.size) - for ((value, featuresForValue) in featuresByValue) { - val previousKeysNow: MutableList = ArrayList(previousKeys) - previousKeysNow.add(key) - valueNodes[value] = buildTree(featuresForValue, previousKeysNow, maxDepth, minContainerSize) - } - - result[key] = valueNodes - - for (map in mapsForKey) { - unsortedMaps.remove(map) - } - } - - return Node(result, ArrayList(unsortedMaps)) - } - - /** returns the given features grouped by the map entry value of the given key. */ - private fun getMapsByKeyValue(key: K, maps: Collection>): Map>> { - val result = HashMap>>() - for (map in maps) { - val value = map[key] - value?.let { - if (!result.containsKey(it)) result[it] = ArrayList() - result[it]?.add(map) - } - } - return result - } - - /** returns the given maps grouped by each of their keys (except the given ones). */ - private fun getMapsByKey( - maps: Collection>, - excludeKeys: Collection - ): Map>> { - val result = HashMap>>() - for (map in maps) { - for (key in map.keys) { - if (excludeKeys.contains(key)) continue - if (!result.containsKey(key)) result[key] = ArrayList() - result[key]!!.add(map) - } - } - return result - } - } -} From 08b67d88c4e0d9db0c1613c226b8e0823c21dbfe Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Thu, 15 Feb 2024 20:54:31 +0100 Subject: [PATCH 16/98] fix resource path --- .../kotlin/osmfeatures/BaseFeature.kt | 2 ++ .../kotlin/osmfeatures/ContainedMapTree.kt | 11 +++---- .../commonMain/kotlin/osmfeatures/Feature.kt | 4 ++- .../kotlin/osmfeatures/FeatureDictionnary.kt | 9 +++--- .../IDLocalizedFeatureCollection.kt | 1 + .../IDPresetsTranslationJsonParser.kt | 1 + .../kotlin/osmfeatures/JsonUtils.kt | 4 +-- .../commonMain/kotlin/osmfeatures/Locale.kt | 12 +++++-- .../kotlin/osmfeatures/LocalizedFeature.kt | 3 ++ .../osmfeatures/LocalizedFeatureCollection.kt | 1 - .../osmfeatures/ContainedMapTreeTest.kt | 4 +-- .../osmfeatures/FeatureDictionaryTest.kt | 5 ++- .../osmfeatures/FeatureTagsIndexTest.kt | 2 +- .../osmfeatures/FeatureTermIndexTest.kt | 4 +-- .../IDBrandPresetsFeatureCollectionTest.kt | 16 ++++------ .../IDLocalizedFeatureCollectionTest.kt | 10 +++--- .../osmfeatures/IDPresetsJsonParserTest.kt | 9 +++--- .../IDPresetsTranslationJsonParserTest.kt | 11 +++---- .../kotlin/osmfeatures/JsonUtilsTest.kt | 31 ++----------------- .../LivePresetDataAccessAdapter.kt | 2 +- .../osmfeatures/StartsWithStringTreeTest.kt | 4 +-- .../TestLocalizedFeatureCollection.kt | 1 - .../kotlin/osmfeatures/TestUtils.kt | 4 +-- 23 files changed, 61 insertions(+), 90 deletions(-) diff --git a/library/src/commonMain/kotlin/osmfeatures/BaseFeature.kt b/library/src/commonMain/kotlin/osmfeatures/BaseFeature.kt index 7552e43..80a88a8 100644 --- a/library/src/commonMain/kotlin/osmfeatures/BaseFeature.kt +++ b/library/src/commonMain/kotlin/osmfeatures/BaseFeature.kt @@ -1,5 +1,7 @@ package de.westnordost.osmfeatures +import osmfeatures.Locale + /** Data class associated with the Feature interface. Represents a non-localized feature. */ open class BaseFeature( override val id: String, diff --git a/library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt b/library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt index f6c14a3..5ee4c39 100644 --- a/library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt +++ b/library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt @@ -1,7 +1,6 @@ package de.westnordost.osmfeatures import de.westnordost.osmfeatures.CollectionUtils.mapContainsAllEntries -import java.util.* /** Index that makes finding which maps are completely contained by a given map very efficient. * It sorts the maps into a tree structure with configurable depth. @@ -101,14 +100,13 @@ internal class ContainedMapTree val mapsByKey = getMapsByKey(maps, previousKeys) /* the map should be categorized by frequent keys first and least frequent keys last. */ - val sortedByCountDesc: List>>> = ArrayList(mapsByKey.entries) - Collections.sort(sortedByCountDesc) { a: Map.Entry>>, b: Map.Entry>> -> b.value.size - a.value.size } + val sortedByCountDesc: List>>> = ArrayList(mapsByKey.entries).sortedByDescending { it.value.size } val result = HashMap>>(mapsByKey.size) for ((key, mapsForKey) in sortedByCountDesc) { // a map already sorted in a certain node should not be sorted into another too - mapsForKey.retainAll(unsortedMaps) + (mapsForKey as MutableList).retainAll(unsortedMaps) if (mapsForKey.isEmpty()) continue val featuresByValue: Map>> = getMapsByKeyValue(key, mapsForKey) @@ -150,10 +148,9 @@ internal class ContainedMapTree ): Map>> { val result = HashMap>>() for (map in maps) { - for (key in map.keys) { - if (excludeKeys.contains(key)) continue + for (key in map.keys.filter { !excludeKeys.contains(it) }) { if (!result.containsKey(key)) result[key] = ArrayList() - result[key]!!.add(map) + result[key]?.add(map) } } return result diff --git a/library/src/commonMain/kotlin/osmfeatures/Feature.kt b/library/src/commonMain/kotlin/osmfeatures/Feature.kt index e8d1f4c..b43604c 100644 --- a/library/src/commonMain/kotlin/osmfeatures/Feature.kt +++ b/library/src/commonMain/kotlin/osmfeatures/Feature.kt @@ -1,9 +1,11 @@ package de.westnordost.osmfeatures +import osmfeatures.Locale + interface Feature { val id: String val tags: Map - val geometry: List? + val geometry: List val name: String val icon: String? val imageURL: String? diff --git a/library/src/commonMain/kotlin/osmfeatures/FeatureDictionnary.kt b/library/src/commonMain/kotlin/osmfeatures/FeatureDictionnary.kt index 7fedc04..90a9513 100644 --- a/library/src/commonMain/kotlin/osmfeatures/FeatureDictionnary.kt +++ b/library/src/commonMain/kotlin/osmfeatures/FeatureDictionnary.kt @@ -5,8 +5,7 @@ import de.westnordost.osmfeatures.Feature import de.westnordost.osmfeatures.GeometryType import de.westnordost.osmfeatures.IDBrandPresetsFeatureCollection import de.westnordost.osmfeatures.IDLocalizedFeatureCollection -import de.westnordost.osmfeatures.Locale -import de.westnordost.osmfeatures.Locale.Companion.default +import osmfeatures.Locale.Companion.default import de.westnordost.osmfeatures.PerCountryFeatureCollection import de.westnordost.osmfeatures.StringUtils import kotlin.math.min @@ -432,7 +431,7 @@ class FeatureDictionary internal constructor( private var countryCode: String? = null /** Sets for which geometry type to look. If not set or `null`, any will match. */ - fun forGeometry(geometryType: GeometryType?): QueryByTagBuilder { + fun forGeometry(geometryType: GeometryType): QueryByTagBuilder { this.geometryType = geometryType return this } @@ -490,7 +489,7 @@ class FeatureDictionary internal constructor( private var countryCode: String? = null /** Sets for which geometry type to look. If not set or `null`, any will match. */ - fun forGeometry(geometryType: GeometryType?): QueryByTermBuilder { + fun forGeometry(geometryType: GeometryType): QueryByTermBuilder { this.geometryType = geometryType return this } @@ -584,7 +583,7 @@ class FeatureDictionary internal constructor( geometry: GeometryType?, countryCode: String? ): Boolean { - if (geometry != null && !feature.geometry?.contains(geometry)!!) return false + if (geometry != null && !feature.geometry.contains(geometry)) return false val include: List = feature.includeCountryCodes val exclude: List = feature.excludeCountryCodes if (include.isNotEmpty() || exclude.isNotEmpty()) { diff --git a/library/src/commonMain/kotlin/osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/osmfeatures/IDLocalizedFeatureCollection.kt index be28f2f..7c01983 100644 --- a/library/src/commonMain/kotlin/osmfeatures/IDLocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/osmfeatures/IDLocalizedFeatureCollection.kt @@ -1,6 +1,7 @@ package de.westnordost.osmfeatures import osmfeatures.FileAccessAdapter +import osmfeatures.Locale import osmfeatures.LocalizedFeatureCollection /** Localized feature collection sourcing from iD presets defined in JSON. diff --git a/library/src/commonMain/kotlin/osmfeatures/IDPresetsTranslationJsonParser.kt b/library/src/commonMain/kotlin/osmfeatures/IDPresetsTranslationJsonParser.kt index 99ad27a..7ffeff5 100644 --- a/library/src/commonMain/kotlin/osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/library/src/commonMain/kotlin/osmfeatures/IDPresetsTranslationJsonParser.kt @@ -5,6 +5,7 @@ import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import okio.Source +import osmfeatures.Locale /** Parses a file from * https://github.com/openstreetmap/id-tagging-schema/tree/main/dist/translations diff --git a/library/src/commonMain/kotlin/osmfeatures/JsonUtils.kt b/library/src/commonMain/kotlin/osmfeatures/JsonUtils.kt index 12b7209..d342d04 100644 --- a/library/src/commonMain/kotlin/osmfeatures/JsonUtils.kt +++ b/library/src/commonMain/kotlin/osmfeatures/JsonUtils.kt @@ -6,12 +6,10 @@ import okio.Source import okio.buffer internal object JsonUtils { - @JvmStatic fun parseList(array: JsonArray?, t: (JsonElement) -> T): List { return array?.mapNotNull { item -> t(item) }.orEmpty() } - @JvmStatic fun parseStringMap(map: JsonObject?): Map { if (map == null) return HashMap(1) return map.map { (key, value) -> key.intern() to value.jsonPrimitive.content}.toMap().toMutableMap() @@ -19,7 +17,7 @@ internal object JsonUtils { // this is only necessary because Android uses some old version of org.json where // new JSONObject(new JSONTokener(inputStream)) is not defined... - @JvmStatic + fun createFromSource(source: Source): JsonObject { val sink = Buffer() source.buffer().readAll(sink) diff --git a/library/src/commonMain/kotlin/osmfeatures/Locale.kt b/library/src/commonMain/kotlin/osmfeatures/Locale.kt index 58c4b7a..8617342 100644 --- a/library/src/commonMain/kotlin/osmfeatures/Locale.kt +++ b/library/src/commonMain/kotlin/osmfeatures/Locale.kt @@ -1,8 +1,8 @@ -package de.westnordost.osmfeatures +package osmfeatures import io.fluidsonic.locale.LanguageTag -class Locale( +data class Locale( val language: String, private val region: String?, @@ -58,6 +58,14 @@ class Locale( } + override fun hashCode(): Int { + var result = language.hashCode() + result = 31 * result + (region?.hashCode() ?: 0) + result = 31 * result + (script?.hashCode() ?: 0) + result = 31 * result + country.hashCode() + return result + } + class Builder { private var language: String? = null diff --git a/library/src/commonMain/kotlin/osmfeatures/LocalizedFeature.kt b/library/src/commonMain/kotlin/osmfeatures/LocalizedFeature.kt index 9225435..c02cf0f 100644 --- a/library/src/commonMain/kotlin/osmfeatures/LocalizedFeature.kt +++ b/library/src/commonMain/kotlin/osmfeatures/LocalizedFeature.kt @@ -1,4 +1,7 @@ package de.westnordost.osmfeatures + +import osmfeatures.Locale + /** Data class associated with the Feature interface. Represents a localized feature. * * I.e. the name and terms are specified in the given locale. */ diff --git a/library/src/commonMain/kotlin/osmfeatures/LocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/osmfeatures/LocalizedFeatureCollection.kt index 1b284d4..f602585 100644 --- a/library/src/commonMain/kotlin/osmfeatures/LocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/osmfeatures/LocalizedFeatureCollection.kt @@ -1,7 +1,6 @@ package osmfeatures import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.Locale /** A localized collection of features */ interface LocalizedFeatureCollection { diff --git a/library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt b/library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt index 1ab7061..cb6778e 100644 --- a/library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt +++ b/library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt @@ -2,8 +2,8 @@ package osmfeatures import de.westnordost.osmfeatures.ContainedMapTree import org.junit.Test -import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder -import de.westnordost.osmfeatures.TestUtils.listOf +import osmfeatures.TestUtils.assertEqualsIgnoreOrder +import osmfeatures.TestUtils.listOf import osmfeatures.MapEntry.Companion.tag import osmfeatures.MapEntry.Companion.mapOf import kotlin.test.assertEquals diff --git a/library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt index 0d11927..94b444a 100644 --- a/library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt +++ b/library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt @@ -4,10 +4,9 @@ import de.westnordost.osmfeatures.BaseFeature import de.westnordost.osmfeatures.Feature import de.westnordost.osmfeatures.GeometryType import de.westnordost.osmfeatures.IDLocalizedFeatureCollection -import de.westnordost.osmfeatures.Locale import de.westnordost.osmfeatures.LocalizedFeature import org.junit.Test -import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder +import osmfeatures.TestUtils.assertEqualsIgnoreOrder import org.junit.Assert.* import osmfeatures.MapEntry.Companion.tag import osmfeatures.MapEntry.Companion.mapOf @@ -406,7 +405,7 @@ class FeatureDictionaryTest { fun find_multiple_brands_sorts_by_locale() { val tags: Map = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")) val dictionary: FeatureDictionary = dictionary(ditschRussian, ditschInternational, ditsch) - val matches: List = dictionary.byTags(tags).forLocale(null as Locale?).find() + val matches: List = dictionary.byTags(tags).forLocale(null).find() assertEquals(ditschInternational, matches[0]) } diff --git a/library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt b/library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt index dcf5c2a..83e8616 100644 --- a/library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt +++ b/library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt @@ -6,7 +6,7 @@ import de.westnordost.osmfeatures.GeometryType import org.junit.Test import osmfeatures.MapEntry.Companion.tag import osmfeatures.MapEntry.Companion.mapOf -import de.westnordost.osmfeatures.TestUtils.listOf +import osmfeatures.TestUtils.listOf import kotlin.test.assertEquals import kotlin.test.assertTrue diff --git a/library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt b/library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt index 7538a4d..99f934f 100644 --- a/library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt +++ b/library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt @@ -1,8 +1,8 @@ package de.westnordost.osmfeatures import org.junit.Test -import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder -import de.westnordost.osmfeatures.TestUtils.listOf +import osmfeatures.TestUtils.assertEqualsIgnoreOrder +import osmfeatures.TestUtils.listOf import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import osmfeatures.FeatureTermIndex diff --git a/library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt b/library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt index c39d47c..e222a3b 100644 --- a/library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt @@ -2,18 +2,15 @@ package osmfeatures import de.westnordost.osmfeatures.Feature import de.westnordost.osmfeatures.IDBrandPresetsFeatureCollection -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder -import de.westnordost.osmfeatures.TestUtils.listOf import okio.FileSystem -import okio.Path import okio.Path.Companion.toPath +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import osmfeatures.TestUtils.assertEqualsIgnoreOrder +import osmfeatures.TestUtils.listOf import okio.Source -import okio.source import org.junit.Test import java.io.IOException -import java.util.ArrayList class IDBrandPresetsFeatureCollectionTest { @Test @@ -61,9 +58,8 @@ class IDBrandPresetsFeatureCollectionTest { @kotlin.Throws(IOException::class) private fun getSource(file: String): Source { - val resourceStream = this.javaClass.getResourceAsStream(file) - ?: throw IOException("Could not retrieve file $file in resource assets") - return resourceStream.source() + val resourcePath = "src/commonTest/resources/${file}".toPath() + return FileSystem.SYSTEM.source(resourcePath) } companion object { diff --git a/library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt b/library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt index 05cb52e..833860c 100644 --- a/library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt @@ -2,12 +2,11 @@ package osmfeatures import de.westnordost.osmfeatures.Feature import de.westnordost.osmfeatures.IDLocalizedFeatureCollection -import de.westnordost.osmfeatures.Locale import okio.FileSystem import okio.Source import org.junit.Test -import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder -import de.westnordost.osmfeatures.TestUtils.listOf +import osmfeatures.TestUtils.assertEqualsIgnoreOrder +import osmfeatures.TestUtils.listOf import okio.FileNotFoundException import okio.IOException import okio.Path.Companion.toPath @@ -144,9 +143,8 @@ class IDLocalizedFeatureCollectionTest { @kotlin.Throws(IOException::class) private fun getSource(file: String): Source { - return FileSystem.SYSTEM.source( - file.toPath() - ) + val resourcePath = "src/commonTest/resources/${file}".toPath() + return FileSystem.SYSTEM.source(resourcePath) } companion object { diff --git a/library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt b/library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt index 9620a35..39226c3 100644 --- a/library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt +++ b/library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt @@ -1,11 +1,11 @@ package de.westnordost.osmfeatures -import okio.Okio +import okio.FileSystem import okio.Source import org.junit.Test import osmfeatures.MapEntry.Companion.tag import osmfeatures.MapEntry.Companion.mapOf -import de.westnordost.osmfeatures.TestUtils.listOf +import osmfeatures.TestUtils.listOf import okio.IOException import okio.Path.Companion.toPath import okio.source @@ -99,8 +99,7 @@ class IDPresetsJsonParserTest { @kotlin.Throws(IOException::class) private fun getSource(file: String): Source { - val resourceStream = this.javaClass.getResourceAsStream(file) - ?: throw IOException("Could not retrieve file $file in resource assets") - return resourceStream.source() + val resourcePath = "src/commonTest/resources/${file}".toPath() + return FileSystem.SYSTEM.source(resourcePath) } } diff --git a/library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt index 38e26b8..77e5e5b 100644 --- a/library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ b/library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -5,11 +5,9 @@ import de.westnordost.osmfeatures.Feature import de.westnordost.osmfeatures.GeometryType import de.westnordost.osmfeatures.IDPresetsJsonParser import de.westnordost.osmfeatures.IDPresetsTranslationJsonParser -import de.westnordost.osmfeatures.Locale import de.westnordost.osmfeatures.LocalizedFeature import okio.FileSystem -import okio.Okio -import okio.Path +import okio.Path.Companion.toPath import okio.Source import org.junit.Test import java.io.IOException @@ -17,7 +15,7 @@ import java.net.URISyntaxException import java.net.URL import osmfeatures.MapEntry.Companion.tag import osmfeatures.MapEntry.Companion.mapOf -import de.westnordost.osmfeatures.TestUtils.listOf +import osmfeatures.TestUtils.listOf import okio.source import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -101,8 +99,7 @@ class IDPresetsTranslationJsonParserTest { @kotlin.Throws(IOException::class) private fun getSource(file: String): Source { - val resourceStream = this.javaClass.getResourceAsStream(file) - ?: throw IOException("Could not retrieve file $file in resource assets") - return resourceStream.source() + val resourcePath = "src/commonTest/resources/${file}".toPath() + return FileSystem.SYSTEM.source(resourcePath) } } diff --git a/library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt b/library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt index 6ec9e5c..13bb1ca 100644 --- a/library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt +++ b/library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.json.JsonArray import org.junit.Test import de.westnordost.osmfeatures.JsonUtils.parseList import de.westnordost.osmfeatures.JsonUtils.parseStringMap -import de.westnordost.osmfeatures.TestUtils.listOf +import osmfeatures.TestUtils.listOf import org.junit.Assert.assertEquals class JsonUtilsTest { @@ -17,35 +17,8 @@ class JsonUtilsTest { fun parseList_with_empty_json_array() { assertEquals(0, parseList(JsonArray(listOf())) { obj -> obj }.size) } - - // @Test public void parseList_with_array() - // { - // String[] array = new String[]{"a","b","c"}; - // JsonPrimitive prim = new JsonElement() { - // } - // assertEquals(listOf(array), parseList(new JsonArray(null)., obj -> obj)); - // } - // @Test public void parseList_with_array_and_transformer() - // { - // int[] array = new int[]{1,2,3}; - // assertEquals(listOf(2,4,6), parseList(new JSONArray(array), i -> (int)i*2)); - // } @Test fun parseStringMap_with_null_json_map() { assertEquals(0, parseStringMap(null).size) - } // @Test public void parseStringMap_with_empty_json_map() - // { - // assertEquals(0, parseStringMap(new JSONObject()).size()); - // } - // @Test public void parseStringMap_with_one_entry() - // { - // Map m = mapOf(tag("a","b")); - // assertEquals(m, parseStringMap(new JSONObject(m))); - // } - // - // @Test public void parseStringMap_with_several_entries() - // { - // Map m = mapOf(tag("a","b"), tag("c", "d")); - // assertEquals(m, parseStringMap(new JSONObject(m))); - // } + } } diff --git a/library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt b/library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt index 12b7390..2af7711 100644 --- a/library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt +++ b/library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt @@ -2,7 +2,7 @@ package osmfeatures import java.io.IOException import java.net.URL -import de.westnordost.osmfeatures.TestUtils.listOf +import osmfeatures.TestUtils.listOf import okio.source class LivePresetDataAccessAdapter : FileAccessAdapter { diff --git a/library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt b/library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt index 53451a2..0db7014 100644 --- a/library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt +++ b/library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt @@ -1,8 +1,8 @@ package de.westnordost.osmfeatures import org.junit.Test -import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder -import de.westnordost.osmfeatures.TestUtils.listOf +import osmfeatures.TestUtils.assertEqualsIgnoreOrder +import osmfeatures.TestUtils.listOf import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue diff --git a/library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt b/library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt index 8e5375b..bda9656 100644 --- a/library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt +++ b/library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt @@ -1,7 +1,6 @@ package osmfeatures import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.Locale class TestLocalizedFeatureCollection(features: List) : LocalizedFeatureCollection { diff --git a/library/src/commonTest/kotlin/osmfeatures/TestUtils.kt b/library/src/commonTest/kotlin/osmfeatures/TestUtils.kt index 1a7256d..9db6489 100644 --- a/library/src/commonTest/kotlin/osmfeatures/TestUtils.kt +++ b/library/src/commonTest/kotlin/osmfeatures/TestUtils.kt @@ -1,10 +1,10 @@ -package de.westnordost.osmfeatures +package osmfeatures import org.junit.Assert.assertTrue object TestUtils { fun assertEqualsIgnoreOrder(a: Collection, b: Collection) { - assertTrue(a.size === b.size && a.containsAll(b)) + assertTrue(a.size == b.size && a.containsAll(b)) } @SafeVarargs From 0355d0faf4780c0ffab60cb8068e2106529ad272 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Sun, 18 Feb 2024 13:59:18 +0100 Subject: [PATCH 17/98] cleaned imports and unused files --- gradle/libs.versions.toml | 2 - library/build.gradle.kts | 2 - .../kotlin/osmfeatures/BaseFeature.kt | 46 - .../kotlin/osmfeatures/CollectionUtils.kt | 41 - .../kotlin/osmfeatures/ContainedMapTree.kt | 159 ---- .../commonMain/kotlin/osmfeatures/Feature.kt | 26 - .../kotlin/osmfeatures/FeatureDictionnary.kt | 628 ------------- .../kotlin/osmfeatures/FeatureTagsIndex.kt | 32 - .../kotlin/osmfeatures/FeatureTermIndex.kt | 36 - .../kotlin/osmfeatures/FileAccessAdapter.kt | 12 - .../kotlin/osmfeatures/FileSystemAccess.kt | 19 - .../kotlin/osmfeatures/GeometryType.kt | 18 - .../IDBrandPresetsFeatureCollection.kt | 71 -- .../IDLocalizedFeatureCollection.kt | 116 --- .../kotlin/osmfeatures/IDPresetsJsonParser.kt | 110 --- .../IDPresetsTranslationJsonParser.kt | 89 -- .../kotlin/osmfeatures/JsonUtils.kt | 27 - .../commonMain/kotlin/osmfeatures/Locale.kt | 101 --- .../kotlin/osmfeatures/LocalizedFeature.kt | 61 -- .../osmfeatures/LocalizedFeatureCollection.kt | 13 - .../PerCountryFeatureCollection.kt | 11 - .../osmfeatures/StartsWithStringTree.kt | 105 --- .../kotlin/osmfeatures/StringUtils.kt | 24 - .../kotlin/osmfeatures/CollectionUtilsTest.kt | 44 - .../osmfeatures/ContainedMapTreeTest.kt | 57 -- .../osmfeatures/FeatureDictionaryTest.kt | 851 ------------------ .../osmfeatures/FeatureTagsIndexTest.kt | 65 -- .../osmfeatures/FeatureTermIndexTest.kt | 85 -- .../IDBrandPresetsFeatureCollectionTest.kt | 70 -- .../IDLocalizedFeatureCollectionTest.kt | 155 ---- .../osmfeatures/IDPresetsJsonParserTest.kt | 105 --- .../IDPresetsTranslationJsonParserTest.kt | 105 --- .../kotlin/osmfeatures/JsonUtilsTest.kt | 24 - .../LivePresetDataAccessAdapter.kt | 24 - .../commonTest/kotlin/osmfeatures/MapEntry.kt | 15 - .../osmfeatures/StartsWithStringTreeTest.kt | 37 - .../TestLocalizedFeatureCollection.kt | 23 - .../TestPerCountryFeatureCollection.kt | 33 - .../kotlin/osmfeatures/TestUtils.kt | 14 - settings.gradle.kts | 2 +- 40 files changed, 1 insertion(+), 3457 deletions(-) delete mode 100644 library/src/commonMain/kotlin/osmfeatures/BaseFeature.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/CollectionUtils.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/Feature.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/FeatureDictionnary.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/FeatureTagsIndex.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/FeatureTermIndex.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/FileAccessAdapter.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/FileSystemAccess.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/GeometryType.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/IDBrandPresetsFeatureCollection.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/IDLocalizedFeatureCollection.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/IDPresetsJsonParser.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/IDPresetsTranslationJsonParser.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/JsonUtils.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/Locale.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/LocalizedFeature.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/LocalizedFeatureCollection.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/PerCountryFeatureCollection.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/StartsWithStringTree.kt delete mode 100644 library/src/commonMain/kotlin/osmfeatures/StringUtils.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/CollectionUtilsTest.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/MapEntry.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/TestPerCountryFeatureCollection.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/TestUtils.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 846b5eb..49b8f10 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,5 @@ [versions] agp = "8.2.1" -fluid-locale = "0.13.0" junit = "4.13.2" kotlin = "1.9.22" kotlinx-serialization-json = "1.6.0" @@ -8,7 +7,6 @@ normalize = "1.0.5" okio = "3.6.0" [libraries] -fluid-locale = { module = "io.fluidsonic.locale:fluid-locale", version.ref = "fluid-locale" } junit = { module = "junit:junit", version.ref = "junit" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index ff95639..82ab57d 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -1,4 +1,3 @@ - plugins { alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.android.library) @@ -18,7 +17,6 @@ kotlin { commonMain.dependencies { //noinspection UseTomlInstead implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") - implementation(libs.fluid.locale) implementation(libs.okio) implementation(libs.kotlinx.serialization.json) implementation(libs.normalize) diff --git a/library/src/commonMain/kotlin/osmfeatures/BaseFeature.kt b/library/src/commonMain/kotlin/osmfeatures/BaseFeature.kt deleted file mode 100644 index 80a88a8..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/BaseFeature.kt +++ /dev/null @@ -1,46 +0,0 @@ -package de.westnordost.osmfeatures - -import osmfeatures.Locale - -/** Data class associated with the Feature interface. Represents a non-localized feature. */ -open class BaseFeature( - override val id: String, - override val tags: Map, - final override var geometry: List, - private val _icon: String? = "", - private val _imageURL: String? = "", - private val _names: List, - final override val terms: List, - final override val includeCountryCodes: List, - final override val excludeCountryCodes: List, - final override val isSearchable: Boolean, - final override val matchScore: Float, - final override val isSuggestion: Boolean, - final override val addTags: Map, - final override val removeTags: Map -): Feature { - final override val canonicalNames: List = names.map { name -> StringUtils.canonicalize(name)} - final override var canonicalTerms: List? = null - - init { - this.canonicalTerms = terms.map { term -> StringUtils.canonicalize(term)} - } - - override val icon: String - get() = _icon ?: "" - - override val imageURL: String - get() = _imageURL ?: "" - - final override val names: List - get() = _names.ifEmpty { listOf("") } - - override val name: String - get() = names[0] - override val locale: Locale? - get() = null - - override fun toString(): String { - return id - } -} diff --git a/library/src/commonMain/kotlin/osmfeatures/CollectionUtils.kt b/library/src/commonMain/kotlin/osmfeatures/CollectionUtils.kt deleted file mode 100644 index c7d92b6..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/CollectionUtils.kt +++ /dev/null @@ -1,41 +0,0 @@ -package de.westnordost.osmfeatures - -object CollectionUtils { - /** For the given map, get the value of the entry at the given key and if there is no - * entry yet, create it using the given create function thread-safely */ - fun synchronizedGetOrCreate(map: MutableMap, key: K, createFn: (K) -> V): V? { - synchronized(map) { - if (!map.containsKey(key)) { - map[key] = createFn(key) - } - } - return map[key] - } - - @JvmStatic - /** Whether the given map contains all the given entries */ - fun mapContainsAllEntries(map: Map, entries: Iterable>): Boolean { - for (entry in entries) { - if (!mapContainsEntry(map, entry)) return false - } - return true - } - - @JvmStatic - /** Number of entries contained in the given map */ - fun numberOfContainedEntriesInMap(map: Map, entries: Iterable>): Int { - var found = 0 - for (entry in entries) { - if (mapContainsEntry(map, entry)) found++ - } - return found - } - - @JvmStatic - /** Whether the given map contains the given entry */ - fun mapContainsEntry(map: Map, entry: Map.Entry): Boolean { - val mapValue = map[entry.key] - val value = entry.value - return value == mapValue - } -} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt b/library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt deleted file mode 100644 index 5ee4c39..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt +++ /dev/null @@ -1,159 +0,0 @@ -package de.westnordost.osmfeatures - -import de.westnordost.osmfeatures.CollectionUtils.mapContainsAllEntries - -/** Index that makes finding which maps are completely contained by a given map very efficient. - * It sorts the maps into a tree structure with configurable depth. - * - * It is threadsafe because it is immutable. - * - * For example for the string maps... - *
- * [
- * #1 (amenity -> bicycle_parking),
- * #2 (amenity -> bicycle_parking, bicycle_parking -> shed),
- * #3 (amenity -> bicycle_parking, bicycle_parking -> lockers),
- * #4 (amenity -> taxi),
- * #5 (shop -> supermarket),
- * ]
-
* - * ...the tree internally looks like this: - *
- * amenity ->
- * bicycle_parking ->
- * #1
- * bicycle_parking ->
- * shed ->
- * #2
- * lockers ->
- * #3
- * taxi ->
- * #4
- * shop ->
- * supermarket ->
- * #5
- * ...
-
* - */ -internal class ContainedMapTree -@JvmOverloads constructor(maps: Collection>, maxDepth: Int = 4, minContainerSize: Int = 4) { - private val root: Node - - /** Create this index with the given maps. - * - * The generated tree will have a max depth of maxDepth and another depth is not added to the - * tree if there are less than minContainerSize maps in one tree node. - */ - init { - var maxDepth = maxDepth - if (maxDepth < 0) maxDepth = 0 - root = buildTree(maps, emptyList(), maxDepth, minContainerSize) - } - - /** Get all maps whose entries are completely contained by the given map */ - fun getAll(map: Map?): List> { - return root.getAll(map!!) - } - - private class Node( - /** key -> (value -> Node) */ - val children: Map>>?, maps: Collection> - ) { - val maps: Collection>? = maps - - /** Get all maps whose entries are all contained by given map */ - fun getAll(map: Map): List> { - val result: MutableList> = ArrayList() - if (children != null) { - for ((key, value) in children) { - if (map.containsKey(key)) { - for ((keyNode, node) in value) { - if (keyNode == map[key]) { - result.addAll(node.getAll(map)) - } - } - } - } - } - if (maps != null) { - for (m in maps) { - if (mapContainsAllEntries(map, m.entries)) { - result.add(m) - } - } - } - return result - } - } - - companion object { - private fun buildTree( - maps: Collection>, - previousKeys: Collection, - maxDepth: Int, - minContainerSize: Int - ): Node { - if (previousKeys.size == maxDepth || maps.size < minContainerSize) return Node(null, maps) - - val unsortedMaps: MutableSet> = HashSet(maps) - - val mapsByKey = getMapsByKey(maps, previousKeys) - - /* the map should be categorized by frequent keys first and least frequent keys last. */ - val sortedByCountDesc: List>>> = ArrayList(mapsByKey.entries).sortedByDescending { it.value.size } - - val result = HashMap>>(mapsByKey.size) - - for ((key, mapsForKey) in sortedByCountDesc) { - // a map already sorted in a certain node should not be sorted into another too - (mapsForKey as MutableList).retainAll(unsortedMaps) - if (mapsForKey.isEmpty()) continue - - val featuresByValue: Map>> = getMapsByKeyValue(key, mapsForKey) - - val valueNodes: MutableMap> = HashMap(featuresByValue.size) - for ((value, featuresForValue) in featuresByValue) { - val previousKeysNow: MutableList = ArrayList(previousKeys) - previousKeysNow.add(key) - valueNodes[value] = buildTree(featuresForValue, previousKeysNow, maxDepth, minContainerSize) - } - - result[key] = valueNodes - - for (map in mapsForKey) { - unsortedMaps.remove(map) - } - } - - return Node(result, ArrayList(unsortedMaps)) - } - - /** returns the given features grouped by the map entry value of the given key. */ - private fun getMapsByKeyValue(key: K, maps: Collection>): Map>> { - val result = HashMap>>() - for (map in maps) { - val value = map[key] - value?.let { - if (!result.containsKey(it)) result[it] = ArrayList() - result[it]?.add(map) - } - } - return result - } - - /** returns the given maps grouped by each of their keys (except the given ones). */ - private fun getMapsByKey( - maps: Collection>, - excludeKeys: Collection - ): Map>> { - val result = HashMap>>() - for (map in maps) { - for (key in map.keys.filter { !excludeKeys.contains(it) }) { - if (!result.containsKey(key)) result[key] = ArrayList() - result[key]?.add(map) - } - } - return result - } - } -} diff --git a/library/src/commonMain/kotlin/osmfeatures/Feature.kt b/library/src/commonMain/kotlin/osmfeatures/Feature.kt deleted file mode 100644 index b43604c..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/Feature.kt +++ /dev/null @@ -1,26 +0,0 @@ -package de.westnordost.osmfeatures - -import osmfeatures.Locale - -interface Feature { - val id: String - val tags: Map - val geometry: List - val name: String - val icon: String? - val imageURL: String? - - /** name + aliases */ - val names: List - val terms: List - val includeCountryCodes: List - val excludeCountryCodes: List - val isSearchable: Boolean - val matchScore: Float - val addTags: Map - val removeTags: Map - val canonicalNames: List - val canonicalTerms: List? - val isSuggestion: Boolean - val locale: Locale? -} diff --git a/library/src/commonMain/kotlin/osmfeatures/FeatureDictionnary.kt b/library/src/commonMain/kotlin/osmfeatures/FeatureDictionnary.kt deleted file mode 100644 index 90a9513..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/FeatureDictionnary.kt +++ /dev/null @@ -1,628 +0,0 @@ -package osmfeatures - -import de.westnordost.osmfeatures.CollectionUtils -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.GeometryType -import de.westnordost.osmfeatures.IDBrandPresetsFeatureCollection -import de.westnordost.osmfeatures.IDLocalizedFeatureCollection -import osmfeatures.Locale.Companion.default -import de.westnordost.osmfeatures.PerCountryFeatureCollection -import de.westnordost.osmfeatures.StringUtils -import kotlin.math.min -import kotlin.text.Regex - -class FeatureDictionary internal constructor( - private val featureCollection: LocalizedFeatureCollection, - private val brandFeatureCollection: PerCountryFeatureCollection? -) { - private val brandNamesIndexes: Map, FeatureTermIndex> = HashMap() - private val brandTagsIndexes: Map, FeatureTagsIndex> = HashMap() - private val tagsIndexes: Map, FeatureTagsIndex> = HashMap() - private val namesIndexes: Map, FeatureTermIndex> = HashMap() - private val termsIndexes: Map, FeatureTermIndex> = HashMap() - private val tagValuesIndexes: Map, FeatureTermIndex> = HashMap() - - init { - // build indices for default locale - getTagsIndex(listOf(default, null)) - getNamesIndex(listOf(default)) - getTermsIndex(listOf(default)) - } - //region Get by id - /** Find feature by id */ - fun byId(id: String): QueryByIdBuilder { - return QueryByIdBuilder(id) - } - - private operator fun get(id: String, locales: List, countryCode: String?): Feature? { - val feature = featureCollection[id, locales] - if (feature != null) return feature - val countryCodes = dissectCountryCode(countryCode) - brandFeatureCollection?.let { - return brandFeatureCollection[id, countryCodes] - } - throw NullPointerException("brandFeatureCollection is null") - } - //endregion - //region Query by tags - /** Find matches by a set of tags */ - fun byTags(tags: Map): QueryByTagBuilder { - return QueryByTagBuilder(tags) - } - - private operator fun get( - tags: Map, - geometry: GeometryType?, - countryCode: String?, - isSuggestion: Boolean?, - locales: List - ): List { - if (tags.isEmpty()) return emptyList() - val foundFeatures: MutableList = mutableListOf() - if (isSuggestion == null || !isSuggestion) { - foundFeatures.addAll(getTagsIndex(locales)?.getAll(tags).orEmpty()) - } - if (isSuggestion == null || isSuggestion) { - val countryCodes = dissectCountryCode(countryCode) - foundFeatures.addAll(getBrandTagsIndex(countryCodes)?.getAll(tags).orEmpty()) - } - foundFeatures.removeIf { feature: Feature -> - !isFeatureMatchingParameters( - feature, - geometry, - countryCode - ) - } - if (foundFeatures.size > 1) { - // only return of each category the most specific thing. I.e. will return - // McDonalds only instead of McDonalds,Fast-Food Restaurant,Amenity - val removeIds: MutableSet = HashSet() - for (feature in foundFeatures) { - removeIds.addAll(getParentCategoryIds(feature.id)) - } - if (removeIds.isNotEmpty()) { - foundFeatures.removeIf{ feature: Feature -> - removeIds.contains( - feature.id - ) - } - } - } - return foundFeatures.sortedWith(object : Comparator { - override fun compare(a: Feature, b: Feature): Int { - // 1. features with more matching tags first - val tagOrder: Int = b.tags.size - a.tags.size - if (tagOrder != 0) { - return tagOrder - } - - // 2. if search is not limited by locale, return matches not limited by locale first - if (locales.size == 1 && locales[0] == null) { - val localeOrder = - ((if (b.includeCountryCodes.isEmpty() && b.excludeCountryCodes.isEmpty()) 1 else 0) - - if (a.includeCountryCodes.isEmpty() && a.excludeCountryCodes - .isEmpty() - ) 1 else 0) - if (localeOrder != 0) return localeOrder - } - - // 3. features with more matching tags in addTags first - // https://github.com/openstreetmap/iD/issues/7927 - val numberOfMatchedAddTags = - (CollectionUtils.numberOfContainedEntriesInMap( - b.addTags, - tags.entries - ) - - CollectionUtils.numberOfContainedEntriesInMap( - a.addTags, - tags.entries - )) - if (numberOfMatchedAddTags != 0) return numberOfMatchedAddTags - return (100 * b.matchScore - 100 * a.matchScore).toInt() - } - }) - } - //endregion - //region Query by term - /** Find matches by given search word */ - fun byTerm(term: String): QueryByTermBuilder { - return QueryByTermBuilder(term) - } - - private operator fun get( - search: String, - geometry: GeometryType?, - countryCode: String?, - isSuggestion: Boolean?, - limit: Int, - locales: List - ): MutableList { - val canonicalSearch = StringUtils.canonicalize(search) - val sortNames = Comparator { a: Feature, b: Feature -> - // 1. exact matches first - val exactMatchOrder = - ((if (b.names.find { n: String? -> n == search } != null - ) 1 else 0) - - if (a.names.find { n: String? -> n == search } != null - ) 1 else 0) - if (exactMatchOrder != 0) return@Comparator exactMatchOrder - - // 2. exact matches case and diacritics insensitive first - val cExactMatchOrder = - ((if (b.canonicalNames.find { n: String? -> n == canonicalSearch } != null - ) 1 else 0) - - if (a.canonicalNames.find { n: String? -> n == canonicalSearch } != null - ) 1 else 0) - if (cExactMatchOrder != 0) return@Comparator cExactMatchOrder - - // 3. starts-with matches in string first - val startsWithOrder = - ((if (b.canonicalNames.find { n: String? -> - n!!.startsWith( - canonicalSearch - ) - } != null - ) 1 else 0) - - if (a.canonicalNames.find { n: String? -> - n!!.startsWith( - canonicalSearch - ) - } != null - ) 1 else 0) - if (startsWithOrder != 0) return@Comparator startsWithOrder - - // 4. features with higher matchScore first - val matchScoreOrder: Int = (100 * b.matchScore - 100 * a.matchScore).toInt() - if (matchScoreOrder != 0) return@Comparator matchScoreOrder - a.name.length - b.name.length - } - val result: MutableList = ArrayList() - if (isSuggestion == null || !isSuggestion) { - // a. matches with presets first - val foundFeaturesByName: MutableList = - getNamesIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() - foundFeaturesByName.removeIf { feature: Feature -> - !isFeatureMatchingParameters( - feature, - geometry, - countryCode - ) - } - result.addAll(foundFeaturesByName.sortedWith(sortNames)) - - // if limit is reached, can return earlier (performance improvement) - if (limit > 0 && result.size >= limit) return result.subList( - 0, - min(limit.toDouble(), result.size.toDouble()).toInt() - ) - } - if (isSuggestion == null || isSuggestion) { - // b. matches with brand names second - val countryCodes = dissectCountryCode(countryCode) - val foundBrandFeatures = getBrandNamesIndex(countryCodes)?.getAll(canonicalSearch).orEmpty().toMutableList() - foundBrandFeatures.removeIf{ feature: Feature -> - !isFeatureMatchingParameters( - feature, - geometry, - countryCode - ) - } - - result.addAll(foundBrandFeatures.sortedWith(sortNames)) - - // if limit is reached, can return earlier (performance improvement) - if (limit > 0 && result.size >= limit) return result.subList( - 0, - min(limit.toDouble(), result.size.toDouble()).toInt() - ) - } - if (isSuggestion == null || !isSuggestion) { - // c. matches with terms third - val foundFeaturesByTerm = getTermsIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() - foundFeaturesByTerm.removeIf { feature: Feature -> - !isFeatureMatchingParameters( - feature, - geometry, - countryCode - ) - } - if (foundFeaturesByTerm.isNotEmpty()) { - val alreadyFoundFeatures: Set = HashSet(result) - foundFeaturesByTerm.removeIf { feature: Feature -> - alreadyFoundFeatures.contains( - feature - ) - } - - } - result.addAll(foundFeaturesByTerm.sortedWith { a: Feature, b: Feature -> (100 * b.matchScore - 100 * a.matchScore).toInt() }) - - // if limit is reached, can return earlier (performance improvement) - if (limit > 0 && result.size >= limit) return result.subList( - 0, - min(limit.toDouble(), result.size.toDouble()).toInt() - ) - } - if (isSuggestion == null || !isSuggestion) { - // d. matches with tag values fourth - val foundFeaturesByTagValue = getTagValuesIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() - foundFeaturesByTagValue.removeIf { feature: Feature -> - !isFeatureMatchingParameters( - feature, - geometry, - countryCode - ) - } - if (foundFeaturesByTagValue.isNotEmpty()) { - val alreadyFoundFeatures: Set = HashSet(result) - foundFeaturesByTagValue.removeIf { feature: Feature -> - alreadyFoundFeatures.contains( - feature - ) - } - result.addAll(foundFeaturesByTagValue) - } - } - return result.subList(0, min(limit.toDouble(), result.size.toDouble()).toInt()) - - } - //endregion - //region Lazily get or create Indexes - /** lazily get or create tags index for given locale(s) */ - private fun getTagsIndex(locales: List): FeatureTagsIndex? { - return CollectionUtils.synchronizedGetOrCreate( - tagsIndexes.toMutableMap(), - locales, - ::createTagsIndex - ) - } - - private fun createTagsIndex(locales: List): FeatureTagsIndex { - return FeatureTagsIndex(featureCollection.getAll(locales)) - } - - /** lazily get or create names index for given locale(s) */ - private fun getNamesIndex(locales: List): FeatureTermIndex? { - return CollectionUtils.synchronizedGetOrCreate( - namesIndexes.toMutableMap(), - locales, - ::createNamesIndex - ) - } - - private fun createNamesIndex(locales: List): FeatureTermIndex { - val features = featureCollection.getAll(locales) - return FeatureTermIndex(features, FeatureTermIndex.Selector { feature: Feature -> - if (!feature.isSearchable) return@Selector emptyList() - val names: List = feature.canonicalNames - val result = ArrayList(names) - for (name in names) { - if (name.contains(" ")) { - result.addAll( - name.replace("[()]", "").split(" ") - ) - } - } - result - }) - } - - /** lazily get or create terms index for given locale(s) */ - private fun getTermsIndex(locales: List): FeatureTermIndex? { - return CollectionUtils.synchronizedGetOrCreate( - termsIndexes.toMutableMap(), - locales, - ::createTermsIndex - ) - } - - private fun createTermsIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature -> - if (!feature.isSearchable) return@Selector emptyList() - feature.canonicalTerms.orEmpty() - }) - } - - /** lazily get or create tag values index */ - private fun getTagValuesIndex(locales: List): FeatureTermIndex? { - return CollectionUtils.synchronizedGetOrCreate( - tagValuesIndexes.toMutableMap(), - locales, - ::createTagValuesIndex - ) - } - - private fun createTagValuesIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature -> - if (!feature.isSearchable) return@Selector emptyList() - - val result: ArrayList = ArrayList(feature.tags.size) - for (tagValue in feature.tags.values) { - if (tagValue != "*") result.add(tagValue) - } - return@Selector result - }) - } - - /** lazily get or create brand names index for country */ - private fun getBrandNamesIndex(countryCodes: List): FeatureTermIndex? { - return CollectionUtils.synchronizedGetOrCreate( - brandNamesIndexes.toMutableMap(), - countryCodes, - ::createBrandNamesIndex - ) - } - - private fun createBrandNamesIndex(countryCodes: List): FeatureTermIndex { - return if (brandFeatureCollection == null) { - FeatureTermIndex(emptyList(), null) - } else FeatureTermIndex( - brandFeatureCollection.getAll(countryCodes) - ) { - if (!it.isSearchable) emptyList() else it.canonicalNames - } - } - - /** lazily get or create tags index for the given countries */ - private fun getBrandTagsIndex(countryCodes: List): FeatureTagsIndex? { - return CollectionUtils.synchronizedGetOrCreate( - brandTagsIndexes.toMutableMap(), - countryCodes, - ::createBrandTagsIndex - ) - } - - private fun createBrandTagsIndex(countryCodes: List): FeatureTagsIndex { - return if (brandFeatureCollection == null) { - FeatureTagsIndex(emptyList()) - } else FeatureTagsIndex(brandFeatureCollection.getAll(countryCodes)) - } - - //endregion - //region Query builders - inner class QueryByIdBuilder(private val id: String) { - private var locale: List = listOf(default) - private var countryCode: String? = null - - private operator fun get(id: String, locales: List, countryCode: String?): Feature? { - return this@FeatureDictionary[id, locales, countryCode] - } - - /** - * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * - * - * `null` means to include unlocalized results. - * - * - * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, - * i.e. unlocalized results are included by default. - */ - fun forLocale(vararg locales: Locale?): QueryByIdBuilder { - this.locale = locales.toList() - return this - } - - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByIdBuilder { - this.countryCode = countryCode - return this - } - - /** Returns the feature associated with the given id or `null` if it does not - * exist */ - fun get(): Feature? { - return this[id, locale, countryCode] - } - } - - inner class QueryByTagBuilder(private val tags: Map) { - private var geometryType: GeometryType? = null - private var locale: List = listOf(default) - private var suggestion: Boolean? = null - private var countryCode: String? = null - - /** Sets for which geometry type to look. If not set or `null`, any will match. */ - fun forGeometry(geometryType: GeometryType): QueryByTagBuilder { - this.geometryType = geometryType - return this - } - - /** - * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * - * - * `null` means to include unlocalized results. - * - * - * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, - * i.e. unlocalized results are included by default. - */ - fun forLocale(vararg locales: Locale?): QueryByTagBuilder { - this.locale = locales.toList() - return this - } - - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByTagBuilder { - this.countryCode = countryCode - return this - } - - /** Set whether to only include suggestions (=true) or to not include suggestions (=false). - * Suggestions are brands, like 7-Eleven. */ - fun isSuggestion(suggestion: Boolean?): QueryByTagBuilder { - this.suggestion = suggestion - return this - } - - /** Returns a list of dictionary entries that match or an empty list if nothing is - * found.

In rare cases, a set of tags may match multiple primary features, such as for - * tag combinations like `shop=deli` + `amenity=cafe`, so, this is why - * it is a list. */ - fun find(): List { - return get(tags, geometryType, countryCode, suggestion, locale) - } - } - - inner class QueryByTermBuilder(private val term: String) { - private var geometryType: GeometryType? = null - private var locale: List = listOf(default) - private var suggestion: Boolean? = null - private var limit = 50 - private var countryCode: String? = null - - /** Sets for which geometry type to look. If not set or `null`, any will match. */ - fun forGeometry(geometryType: GeometryType): QueryByTermBuilder { - this.geometryType = geometryType - return this - } - - /** - * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * - * - * `null` means to include unlocalized results. - * - * - * If nothing is specified, it defaults to `[Locale.getDefault()]`, i.e. - * unlocalized results are excluded by default. - */ - fun forLocale(vararg locales: Locale?): QueryByTermBuilder { - this.locale = locales.toList() - return this - } - - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByTermBuilder { - this.countryCode = countryCode - return this - } - - /** Set whether to only include suggestions (=true) or to not include suggestions (=false). - * Suggestions are brands, like 7-Eleven. */ - fun isSuggestion(suggestion: Boolean?): QueryByTermBuilder { - this.suggestion = suggestion - return this - } - - /** limit how many results to return at most. Default is 50, -1 for unlimited. */ - fun limit(limit: Int): QueryByTermBuilder { - this.limit = limit - return this - } - - /** Returns a list of dictionary entries that match or an empty list if nothing is - * found.

- * Results are sorted mainly in this order: Matches with names, with brand names, then - * matches with terms (keywords). */ - fun find(): List { - return get(term, geometryType, countryCode, suggestion, limit, locale) - } - } //endregion - - companion object { - private val VALID_COUNTRY_CODE_REGEX = Regex("([A-Z]{2})(?:-([A-Z0-9]{1,3}))?") - /** Create a new FeatureDictionary which gets its data from the given directory. Optionally, - * a path to brand presets can be specified. */ - /** Create a new FeatureDictionary which gets its data from the given directory. */ - @JvmOverloads - fun create(presetsBasePath: String, brandPresetsBasePath: String? = null): FeatureDictionary { - val featureCollection: LocalizedFeatureCollection = - IDLocalizedFeatureCollection(FileSystemAccess(presetsBasePath)) - val brandsFeatureCollection: PerCountryFeatureCollection? = - if (brandPresetsBasePath != null) IDBrandPresetsFeatureCollection( - FileSystemAccess(brandPresetsBasePath) - ) else null - return FeatureDictionary(featureCollection, brandsFeatureCollection) - } - - //endregion - //region Utility / Filter functions - private fun getParentCategoryIds(id: String): Collection { - var currentId: String? = id - val result: MutableList = ArrayList() - do { - currentId = getParentId(currentId) - if (currentId != null) result.add(currentId) - } while (currentId != null) - return result - } - - private fun getParentId(id: String?): String? { - val lastSlashIndex = id!!.lastIndexOf("/") - return if (lastSlashIndex == -1) null else id.substring(0, lastSlashIndex) - } - - private fun isFeatureMatchingParameters( - feature: Feature, - geometry: GeometryType?, - countryCode: String? - ): Boolean { - if (geometry != null && !feature.geometry.contains(geometry)) return false - val include: List = feature.includeCountryCodes - val exclude: List = feature.excludeCountryCodes - if (include.isNotEmpty() || exclude.isNotEmpty()) { - if (countryCode == null) return false - if (include.isNotEmpty() && !matchesAnyCountryCode(countryCode, include)) return false - if (matchesAnyCountryCode(countryCode, exclude)) return false - } - return true - } - - private fun dissectCountryCode(countryCode: String?): List { - val result: MutableList = ArrayList() - // add default / international - result.add(null) - countryCode?.let { - val matcher = VALID_COUNTRY_CODE_REGEX.find(it) - if(matcher?.groups?.get(1) != null) { - result.add(matcher.groups[1]?.value) - - // add ISO 3166-1 alpha2 (e.g. "US") - if (matcher.groups.size != 2 && matcher.groups[2] != null) { - // add ISO 3166-2 (e.g. "US-NY") - result.add(countryCode) - } - } - - - } - return result - } - - private fun matchesAnyCountryCode(showOnly: String, featureCountryCodes: List): Boolean { - return featureCountryCodes.any { matchesCountryCode(showOnly, it) } - } - - private fun matchesCountryCode(showOnly: String, featureCountryCode: String): Boolean { - return showOnly == featureCountryCode || showOnly.substring(0, 2) == featureCountryCode - } - } - } - - diff --git a/library/src/commonMain/kotlin/osmfeatures/FeatureTagsIndex.kt b/library/src/commonMain/kotlin/osmfeatures/FeatureTagsIndex.kt deleted file mode 100644 index d11af3b..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/FeatureTagsIndex.kt +++ /dev/null @@ -1,32 +0,0 @@ -package osmfeatures - -import de.westnordost.osmfeatures.ContainedMapTree -import de.westnordost.osmfeatures.Feature - -/** Index that makes finding Features whose tags are completely contained by a given set of tags - * very efficient. - * - * Based on ContainedMapTree data structure, see that class. */ -internal class FeatureTagsIndex(features: Collection) { - private val featureMap: MutableMap, MutableList> - private val tree: ContainedMapTree - - init { - featureMap = HashMap() - for (feature in features) { - val map: Map = feature.tags - if (!featureMap.containsKey(map)) featureMap[map] = ArrayList(1) - featureMap[map]!!.add(feature) - } - tree = ContainedMapTree(featureMap.keys) - } - - fun getAll(tags: Map?): List { - val result: MutableList = ArrayList() - for (map in tree.getAll(tags)) { - val fs: List? = featureMap[map] - if (fs != null) result.addAll(fs) - } - return result - } -} diff --git a/library/src/commonMain/kotlin/osmfeatures/FeatureTermIndex.kt b/library/src/commonMain/kotlin/osmfeatures/FeatureTermIndex.kt deleted file mode 100644 index 4ef0405..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/FeatureTermIndex.kt +++ /dev/null @@ -1,36 +0,0 @@ -package osmfeatures - -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.StartsWithStringTree - -/** Index that makes finding Features whose name/term/... starts with a given string very efficient. - * - * Based on the StartsWithStringTree data structure, see that class. */ -class FeatureTermIndex(features: Collection, selector: Selector?) { - private val featureMap: MutableMap> = HashMap() - private val tree: StartsWithStringTree - - init { - for (feature in features) { - val strings: Collection = selector?.getStrings(feature) ?: emptyList() - for (string in strings) { - if (!featureMap.containsKey(string)) featureMap[string] = ArrayList(1) - featureMap[string]?.add(feature) - } - } - tree = StartsWithStringTree(featureMap.keys) - } - - fun getAll(startsWith: String): List { - val result: MutableSet = HashSet() - for (string in tree.getAll(startsWith)) { - val fs: List? = featureMap[string] - if (fs != null) result.addAll(fs) - } - return ArrayList(result) - } - - fun interface Selector { - fun getStrings(feature: Feature): List - } -} diff --git a/library/src/commonMain/kotlin/osmfeatures/FileAccessAdapter.kt b/library/src/commonMain/kotlin/osmfeatures/FileAccessAdapter.kt deleted file mode 100644 index bc037d8..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/FileAccessAdapter.kt +++ /dev/null @@ -1,12 +0,0 @@ -package osmfeatures - -import okio.IOException -import okio.Source -import kotlin.jvm.Throws - -interface FileAccessAdapter { - - fun exists(name: String): Boolean - @Throws(IOException::class) - fun open(name: String): Source -} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/osmfeatures/FileSystemAccess.kt b/library/src/commonMain/kotlin/osmfeatures/FileSystemAccess.kt deleted file mode 100644 index 129cac8..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/FileSystemAccess.kt +++ /dev/null @@ -1,19 +0,0 @@ -package osmfeatures -import okio.FileSystem -import okio.Path.Companion.toPath -import okio.Source - -class FileSystemAccess(private val basePath: String) : FileAccessAdapter { - private val fs = FileSystem.SYSTEM - init { - fs.metadataOrNull(basePath.toPath())?.let { require(it.isDirectory) { "basePath must be a directory" } } - } - - override fun exists(name: String): Boolean { - - return fs.exists(("$basePath/$name").toPath()) - } - override fun open(name: String): Source { - return fs.source(("$basePath/$name".toPath())) - } -} diff --git a/library/src/commonMain/kotlin/osmfeatures/GeometryType.kt b/library/src/commonMain/kotlin/osmfeatures/GeometryType.kt deleted file mode 100644 index bf4c02c..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/GeometryType.kt +++ /dev/null @@ -1,18 +0,0 @@ -package de.westnordost.osmfeatures - -enum class GeometryType { - /** an OSM node that is not a member of any way */ - POINT, - - /** an OSM node that is a member of one or more ways */ - VERTEX, - - /** an OSM way that is not an area */ - LINE, - - /** a OSM way that is closed/circular (the first and last nodes are the same) or a type=multipolygon relation */ - AREA, - - /** an OSM relation */ - RELATION -} diff --git a/library/src/commonMain/kotlin/osmfeatures/IDBrandPresetsFeatureCollection.kt b/library/src/commonMain/kotlin/osmfeatures/IDBrandPresetsFeatureCollection.kt deleted file mode 100644 index d3ba23c..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/IDBrandPresetsFeatureCollection.kt +++ /dev/null @@ -1,71 +0,0 @@ -package de.westnordost.osmfeatures - -import osmfeatures.FileAccessAdapter - -/** Non-localized feature collection sourcing from (NSI) iD presets defined in JSON. - * - * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that - * there is a presets.json which includes all the features. Additionally, it is possible to place - * more files like e.g. presets-DE.json, presets-US-NY.json into the directory which will be loaded - * lazily on demand */ -class IDBrandPresetsFeatureCollection internal constructor(private val fileAccess: FileAccessAdapter) : - PerCountryFeatureCollection { - private val featuresByIdByCountryCode: HashMap> = LinkedHashMap(320) - - init { - getOrLoadPerCountryFeatures(null) - } - - override fun getAll(countryCodes: List): Collection { - val result: MutableMap = HashMap() - for (cc in countryCodes) { - getOrLoadPerCountryFeatures(cc)?.let { result.putAll(it) } - } - return result.values - } - - override fun get(id: String, countryCodes: List): Feature? { - for (countryCode in countryCodes) { - val result = getOrLoadPerCountryFeatures(countryCode)?.get(id) - if (result != null) return result - } - return null - } - - private fun getOrLoadPerCountryFeatures(countryCode: String?): java.util.LinkedHashMap? { - return CollectionUtils.synchronizedGetOrCreate( - featuresByIdByCountryCode, countryCode - ) { countryCode: String? -> - this.loadPerCountryFeatures( - countryCode - ) - } - } - - private fun loadPerCountryFeatures(countryCode: String?): LinkedHashMap { - val features = loadFeatures(countryCode) - val featuresById = LinkedHashMap(features.size) - for (feature in features) { - featuresById[feature.id] = feature - } - return featuresById - } - - private fun loadFeatures(countryCode: String?): List { - val filename = getPresetsFileName(countryCode) - if (!fileAccess.exists(filename)) return emptyList() - fileAccess.open(filename).use { `is` -> - return IDPresetsJsonParser(true).parse(`is`) - } - } - - companion object { - private fun getPresetsFileName(countryCode: String?): String { - return if (countryCode == null) { - "presets.json" - } else { - "presets-$countryCode.json" - } - } - } -} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/osmfeatures/IDLocalizedFeatureCollection.kt deleted file mode 100644 index 7c01983..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/IDLocalizedFeatureCollection.kt +++ /dev/null @@ -1,116 +0,0 @@ -package de.westnordost.osmfeatures - -import osmfeatures.FileAccessAdapter -import osmfeatures.Locale -import osmfeatures.LocalizedFeatureCollection - -/** Localized feature collection sourcing from iD presets defined in JSON. - * - * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that - * there is a presets.json which includes all the features. The translations are expected to be - * located in the same directory named like e.g. de.json, pt-BR.json etc. */ -class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : - LocalizedFeatureCollection { - private val featuresById: LinkedHashMap - private val localizedFeaturesList: MutableMap> = HashMap() - private val localizedFeatures: MutableMap?, LinkedHashMap> = HashMap() - - init { - val features = loadFeatures() - featuresById = LinkedHashMap(features.size) - for (feature in features) { - featuresById[feature.id] = feature - } - } - - private fun loadFeatures(): List { - try { - val source = fileAccess.open(FEATURES_FILE) - return IDPresetsJsonParser().parse(source) - } - catch (e: Exception) - { - throw RuntimeException(e) - } - } - private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap? { - return CollectionUtils.synchronizedGetOrCreate(localizedFeatures, locales, ::loadLocalizedFeatures) - } - - private fun loadLocalizedFeatures(locales: List): LinkedHashMap { - val result = LinkedHashMap(featuresById.size) - val it = locales.listIterator(locales.size) - while (it.hasPrevious()) { - val locale = it.previous() - if (locale != null) { - for (localeComponent in getLocaleComponents(locale)) { - getOrLoadLocalizedFeaturesList(localeComponent)?.let { it1 -> putAllFeatures(result, it1) } - } - } else { - putAllFeatures(result, featuresById.values) - } - } - return result - } - - private fun getOrLoadLocalizedFeaturesList(locale: Locale): List? { - return CollectionUtils.synchronizedGetOrCreate( - localizedFeaturesList, locale - ) { locale: Locale? -> - loadLocalizedFeaturesList( - locale - ) - } - } - - private fun loadLocalizedFeaturesList(locale: Locale?): List { - val filename = getLocalizationFilename(locale) - if (!fileAccess.exists(filename)) return emptyList() - - fileAccess.open(filename).use { source -> - return IDPresetsTranslationJsonParser().parse(source, locale, featuresById.toMap()) - } - } - - override fun getAll(locales: List): Collection { - return getOrLoadLocalizedFeatures(locales)?.values ?: emptyList() - } - - override operator fun get(id: String, locales: List): Feature? { - return getOrLoadLocalizedFeatures(locales)?.get(id) - } - - companion object { - private const val FEATURES_FILE = "presets.json" - private fun getLocalizationFilename(locale: Locale?): String { - /* we only want language+country+script of the locale, not anything else. So we construct - it anew here */ - return Locale.Builder() - .setLanguage(locale?.language ?: "") - .setRegion(locale?.country ?: "") - .setScript(locale?.script ?: "") - .build() - .languageTag + ".json" - } - - private fun getLocaleComponents(locale: Locale?): List { - val lang = locale?.language ?: "" - val country = locale?.country ?: "" - val script = locale?.script ?: "" - val result: MutableList = ArrayList(4) - result.add(Locale(lang)) - if (country.isNotEmpty()) result.add(Locale.Builder().setLanguage(lang).setRegion(country).build()) - if (script.isNotEmpty()) result.add(Locale.Builder().setLanguage(lang).setScript(script).build()) - if (country.isNotEmpty() && script.isNotEmpty()) result.add( - Locale.Builder().setLanguage(lang).setRegion(country).setScript(script).build() - ) - return result - } - - private fun putAllFeatures(map: MutableMap, features: Iterable) { - for (feature in features) { - map[feature.id] = feature - } - } - } -} diff --git a/library/src/commonMain/kotlin/osmfeatures/IDPresetsJsonParser.kt b/library/src/commonMain/kotlin/osmfeatures/IDPresetsJsonParser.kt deleted file mode 100644 index f2c06f7..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/IDPresetsJsonParser.kt +++ /dev/null @@ -1,110 +0,0 @@ -package de.westnordost.osmfeatures - -import de.westnordost.osmfeatures.JsonUtils.parseList -import de.westnordost.osmfeatures.JsonUtils.parseStringMap -import kotlinx.serialization.json.* -import okio.Buffer -import okio.Source -import okio.buffer - -/** Parses this file - * [...](https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json) - * into map of id -> Feature. */ -class IDPresetsJsonParser { - private var isSuggestion = false - - constructor() - - constructor(isSuggestion: Boolean) { - this.isSuggestion = isSuggestion - } - fun parse(source: Source): List { - val sink = Buffer() - source.buffer().readAll(sink) - - val decodedObject = Json.decodeFromString(sink.readUtf8()) - return decodedObject.mapNotNull { (key, value) -> parseFeature(key, value.jsonObject)} - } - - private fun parseFeature(id: String, p: JsonObject): BaseFeature? { - val tags = parseStringMap(p["tags"]?.jsonObject) - // drop features with * in key or value of tags (for now), because they never describe - // a concrete thing, but some category of things. - // TODO maybe drop this limitation - if (anyKeyOrValueContainsWildcard(tags)) return null - // also dropping features with empty tags (generic point, line, relation) - if (tags.isEmpty()) return null - - val geometry = parseList( - p["geometry"]?.jsonArray - ) { GeometryType.valueOf(((it as JsonPrimitive).content).uppercase()) - } - - val name = p["name"]?.jsonPrimitive?.content - val icon = p["icon"]?.jsonPrimitive?.content - val imageURL = p["imageURL"]?.jsonPrimitive?.content - val names = parseList(p["aliases"]?.jsonArray) { it.jsonPrimitive.content }.toMutableList() - if(name != null) { - names.add(0, name) - } - val terms = parseList(p["terms"]?.jsonArray) { it.jsonPrimitive.content } - - val locationSet = p["locationSet"]?.jsonObject - val includeCountryCodes: List? - val excludeCountryCodes: List? - if (locationSet != null) { - includeCountryCodes = parseCountryCodes(locationSet["include"]?.jsonArray) - if (includeCountryCodes == null) return null - excludeCountryCodes = parseCountryCodes(locationSet["exclude"]?.jsonArray) - if (excludeCountryCodes == null) return null - } else { - includeCountryCodes = ArrayList(0) - excludeCountryCodes = ArrayList(0) - } - - val searchable = p["searchable"]?.jsonPrimitive?.booleanOrNull?: true - val matchScore = p["matchScore"]?.jsonPrimitive?.floatOrNull?: 1.0f - val addTags = p["addTags"]?.let { parseStringMap(it.jsonObject)}?: tags - val removeTags = p["removeTags"]?.let { parseStringMap(it.jsonObject)}?: addTags - - return BaseFeature( - id, - tags, - geometry, - icon, imageURL, - names, - terms, - includeCountryCodes, - excludeCountryCodes, - searchable, matchScore, isSuggestion, - addTags, - removeTags - ) - } - - companion object { - private fun parseCountryCodes(jsonList: JsonArray?): List? { - if(jsonList?.any { it is JsonArray } == true) { - return null - } - val list = parseList(jsonList) { it.jsonPrimitive.content } - val result: MutableList = ArrayList(list.size) - for (item in list) { - // for example a lat,lon pair to denote a location with radius. Not supported. - val cc = item.uppercase().intern() - // don't need this, 001 stands for "whole world" - if (cc == "001") continue - // ISO-3166-2 codes are supported but not m49 code such as "150" or geojsons like "city_national_bank_fl.geojson" - if (!cc.matches("[A-Z]{2}(-[A-Z0-9]{1,3})?".toRegex())) return null - result.add(cc) - } - return result - } - - private fun anyKeyOrValueContainsWildcard(map: Map): Boolean { - return map.any { (key, value) -> key.contains("*") || value.contains("*")} - } - } -} - - diff --git a/library/src/commonMain/kotlin/osmfeatures/IDPresetsTranslationJsonParser.kt b/library/src/commonMain/kotlin/osmfeatures/IDPresetsTranslationJsonParser.kt deleted file mode 100644 index 7ffeff5..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/IDPresetsTranslationJsonParser.kt +++ /dev/null @@ -1,89 +0,0 @@ -package de.westnordost.osmfeatures - -import de.westnordost.osmfeatures.JsonUtils.createFromSource -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import okio.Source -import osmfeatures.Locale - -/** Parses a file from - * https://github.com/openstreetmap/id-tagging-schema/tree/main/dist/translations - * , given the base features are already parsed. - */ -class IDPresetsTranslationJsonParser { - - fun parse( - source: Source, locale: Locale?, baseFeatures: Map - ): List { - val decodedObject = createFromSource(source) - val languageKey: String = decodedObject.entries.iterator().next().key - val languageObject = decodedObject[languageKey] - ?: return emptyList() - val presetsContainerObject = languageObject.jsonObject["presets"] - ?: return emptyList() - val presetsObject = presetsContainerObject.jsonObject["presets"]?.jsonObject - ?: return emptyList() - val localizedFeatures: MutableMap = HashMap(presetsObject.size) - presetsObject.entries.forEach { (key, value) -> - val id = key.intern() - val f = parseFeature(baseFeatures[id], locale, value.jsonObject) - if (f != null) localizedFeatures[id] = f - } - for (baseFeature in baseFeatures.values) { - val names = baseFeature.names - if (names.isEmpty()) continue - val name = names[0] - val isPlaceholder = name.startsWith("{") && name.endsWith("}") - if (!isPlaceholder) continue - val placeholderId = name.substring(1, name.length - 1) - val localizedFeature = localizedFeatures[placeholderId] ?: continue - localizedFeatures[baseFeature.id] = LocalizedFeature( - baseFeature, - locale, - localizedFeature.names, - localizedFeature.terms - ) - } - - return ArrayList(localizedFeatures.values) - } - - private fun parseFeature(feature: BaseFeature?, locale: Locale?, localization: JsonObject): LocalizedFeature? { - if (feature == null) return null - - val name = localization["name"]?.jsonPrimitive?.content - if (name.isNullOrEmpty()) return null - - val namesArray = parseNewlineSeparatedList(localization["aliases"]?.jsonPrimitive?.content) - val names: MutableList = ArrayList(namesArray.size + 1) - names.addAll(namesArray) - names.remove(name) - names.add(0, name) - - val termsArray = parseCommaSeparatedList(localization["terms"]?.jsonPrimitive?.content) - val terms: MutableList = ArrayList(termsArray.size) - terms.addAll(termsArray) - terms.removeAll(names) - - return LocalizedFeature( - feature, - locale, - names, - terms - ) - } - - - companion object { - fun parseCommaSeparatedList(str: String?): Array { - if (str.isNullOrEmpty()) return emptyArray() - return str.split("\\s*,+\\s*".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - } - - fun parseNewlineSeparatedList(str: String?): Array { - if (str.isNullOrEmpty()) return emptyArray() - return str.split("\\s*[\\r\\n]+\\s*".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - } - } -} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/osmfeatures/JsonUtils.kt b/library/src/commonMain/kotlin/osmfeatures/JsonUtils.kt deleted file mode 100644 index d342d04..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/JsonUtils.kt +++ /dev/null @@ -1,27 +0,0 @@ -package de.westnordost.osmfeatures - -import kotlinx.serialization.json.* -import okio.Buffer -import okio.Source -import okio.buffer -internal object JsonUtils { - - fun parseList(array: JsonArray?, t: (JsonElement) -> T): List { - return array?.mapNotNull { item -> t(item) }.orEmpty() - } - - fun parseStringMap(map: JsonObject?): Map { - if (map == null) return HashMap(1) - return map.map { (key, value) -> key.intern() to value.jsonPrimitive.content}.toMap().toMutableMap() - } - - // this is only necessary because Android uses some old version of org.json where - // new JSONObject(new JSONTokener(inputStream)) is not defined... - - fun createFromSource(source: Source): JsonObject { - val sink = Buffer() - source.buffer().readAll(sink) - - return Json.decodeFromString(sink.readUtf8()) - } -} diff --git a/library/src/commonMain/kotlin/osmfeatures/Locale.kt b/library/src/commonMain/kotlin/osmfeatures/Locale.kt deleted file mode 100644 index 8617342..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/Locale.kt +++ /dev/null @@ -1,101 +0,0 @@ -package osmfeatures - -import io.fluidsonic.locale.LanguageTag - -data class Locale( - - val language: String, - private val region: String?, - val script: String?) { - companion object { - - - @JvmField - val ENGLISH: Locale = Locale("en") - @JvmField - val UK: Locale = Locale("en","UK") - @JvmField - val US: Locale = Locale("en","US") - @JvmField - val FRENCH: Locale = Locale("fr") - @JvmField - val ITALIAN: Locale = Locale("it") - @JvmField - val GERMAN: Locale = Locale("de") - @JvmField - val GERMANY: Locale = Locale("de", "DE") - @JvmField - val CHINESE: Locale = Locale("zh") - - @JvmStatic - val default: Locale? = null - - } - - - - val country : String - get() = this.region.orEmpty() - - val languageTag : String? by lazy { - LanguageTag.forLanguage(language, script, region).toString() - } - - constructor(lang: String) : this(lang,"", "") - - constructor(lang: String, region: String) : this(lang, region, "") - - - - override fun equals(other: Any?): Boolean { - if (other == null) { - return false - } - if (other is Locale) { - return other.languageTag == this.languageTag - } - return false - - } - - override fun hashCode(): Int { - var result = language.hashCode() - result = 31 * result + (region?.hashCode() ?: 0) - result = 31 * result + (script?.hashCode() ?: 0) - result = 31 * result + country.hashCode() - return result - } - - - class Builder { - private var language: String? = null - fun setLanguage(language: String) : Builder { - this.language = language - return this - } - - private var region: String? = null - - fun setRegion(region: String?) : Builder { - this.region = region.orEmpty() - return this - } - - private var script: String? = null - fun setScript(script: String?) : Builder { - - this.script = script.orEmpty() - return this - } - - fun build(): Locale { - language?.let { - return Locale(it, region, script) - } - throw IllegalArgumentException("Language should not be empty") - } - - } - - -} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/osmfeatures/LocalizedFeature.kt b/library/src/commonMain/kotlin/osmfeatures/LocalizedFeature.kt deleted file mode 100644 index c02cf0f..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/LocalizedFeature.kt +++ /dev/null @@ -1,61 +0,0 @@ -package de.westnordost.osmfeatures - -import osmfeatures.Locale - -/** Data class associated with the Feature interface. Represents a localized feature. - * - * I.e. the name and terms are specified in the given locale. */ -class LocalizedFeature( - private val p: BaseFeature, - override val locale: Locale?, - override val names: List, - override val terms: List -) : - Feature { - override val canonicalNames: List - override val canonicalTerms: List - - init { - val canonicalNames: MutableList = ArrayList(names.size) - for (name in names) { - canonicalNames.add(StringUtils.canonicalize(name)) - } - this.canonicalNames = canonicalNames.toList() - val canonicalTerms: MutableList = ArrayList(terms.size) - for (term in terms) { - canonicalTerms.add(StringUtils.canonicalize(term)) - } - this.canonicalTerms = canonicalTerms.toList() - } - - override val id: String - get() = p.id - override val tags: Map - get() = p.tags - override val geometry: List - get() = p.geometry - override val name: String - get() = names[0] - override val icon: String? - get() = p.icon - override val imageURL: String? - get() = p.imageURL - override val includeCountryCodes: List - get() = p.includeCountryCodes - override val excludeCountryCodes: List - get() = p.excludeCountryCodes - override val isSearchable: Boolean - get() = p.isSearchable - override val matchScore: Float - get() = p.matchScore - override val addTags: Map - get() = p.addTags - override val removeTags: Map - get() = p.removeTags - override val isSuggestion: Boolean - get() = p.isSuggestion - - override fun toString(): String { - return id - } -} diff --git a/library/src/commonMain/kotlin/osmfeatures/LocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/osmfeatures/LocalizedFeatureCollection.kt deleted file mode 100644 index f602585..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/LocalizedFeatureCollection.kt +++ /dev/null @@ -1,13 +0,0 @@ -package osmfeatures - -import de.westnordost.osmfeatures.Feature - -/** A localized collection of features */ -interface LocalizedFeatureCollection { - /** Returns all features in the given locale(s). */ - fun getAll(locales: List): Collection - - /** Returns the feature with the given id in the given locale(s) or null if it has not been - * found (for the given locale(s)) */ - operator fun get(id: String, locales: List): Feature? -} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/osmfeatures/PerCountryFeatureCollection.kt b/library/src/commonMain/kotlin/osmfeatures/PerCountryFeatureCollection.kt deleted file mode 100644 index 31cf103..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/PerCountryFeatureCollection.kt +++ /dev/null @@ -1,11 +0,0 @@ -package de.westnordost.osmfeatures - -/** A collection of features grouped by country code */ -interface PerCountryFeatureCollection { - /** Returns all features with the given country code */ - fun getAll(countryCodes: List): Collection - - /** Returns the feature with the given id with the given country code or null if it has not been - * found */ - operator fun get(id: String, countryCodes: List): Feature? -} diff --git a/library/src/commonMain/kotlin/osmfeatures/StartsWithStringTree.kt b/library/src/commonMain/kotlin/osmfeatures/StartsWithStringTree.kt deleted file mode 100644 index 23d71d3..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/StartsWithStringTree.kt +++ /dev/null @@ -1,105 +0,0 @@ -package de.westnordost.osmfeatures - -/** Index that makes finding strings that start with characters very efficient. - * It sorts the strings into a tree structure with configurable depth. - * - * It is threadsafe because it is immutable. - * - * For the strings ["f", "foobar", "foo", "fu", "funicular"], the tree may internally look f.e. - * like this: - *
- * f ->
- * [ "f" ]
- * o ->
- * o ->
- * [ "foobar", "foo", ...]
- * u ->
- * [ "fu", "funicular", ... ]
-
*/ -internal class StartsWithStringTree -@JvmOverloads constructor(strings: Collection, maxDepth: Int = 16, minContainerSize: Int = 16) { - private val root: Node - - /** Create this index with the given strings. - * - * The generated tree will have a max depth of maxDepth and another depth is not added to the - * tree if there are less than minContainerSize strings in one tree node. - */ - init { - var maxDepth = maxDepth - var minContainerSize = minContainerSize - if (maxDepth < 0) maxDepth = 0 - if (minContainerSize < 1) minContainerSize = 1 - root = buildTree(strings, 0, maxDepth, minContainerSize) - } - - /** Get all strings which start with the given string */ - fun getAll(startsWith: String?): List { - return root.getAll(startsWith, 0) - } - - private class Node(val children: Map?, val strings: Collection) { - - /** Get all strings that start with the given string */ - fun getAll(startsWith: String?, offset: Int): List { - if (startsWith != null) { - if (startsWith.isEmpty()) return emptyList() - } - - val result: MutableList = ArrayList() - if (children != null) { - for ((key, value) in children) { - if (startsWith != null) { - if (startsWith.length <= offset || key == startsWith[offset]) { - result.addAll(value.getAll(startsWith, offset + 1)) - } - } - } - } - for (string in strings) { - if (startsWith?.let { string.startsWith(it) } == true) result.add(string) - } - return result - } - } - - companion object { - private fun buildTree( - strings: Collection, - currentDepth: Int, - maxDepth: Int, - minContainerSize: Int - ): Node { - if (currentDepth == maxDepth || strings.size < minContainerSize) return Node(null, strings) - - val stringsByCharacter = getStringsByCharacter(strings, currentDepth) - var children: HashMap? = HashMap(stringsByCharacter.size) - - for ((key, value) in stringsByCharacter) { - val c = key ?: continue - val child = buildTree(value, currentDepth + 1, maxDepth, minContainerSize) - children?.set(c, child) - } - val remainingStrings: Collection = stringsByCharacter[null].orEmpty() - if (children != null) { - if (children.isEmpty()) children = null - } - return Node(children, remainingStrings) - } - - /** returns the given strings grouped by their nth character. Strings whose length is shorter - * or equal to nth go into the "null" group. */ - private fun getStringsByCharacter( - strings: Collection, - nth: Int - ): Map> { - val result = HashMap>() - for (string in strings) { - val c = if (string.length > nth) string[nth] else null - if (!result.containsKey(c)) result[c] = ArrayList() - result[c]?.add(string) - } - return result - } - } -} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/osmfeatures/StringUtils.kt b/library/src/commonMain/kotlin/osmfeatures/StringUtils.kt deleted file mode 100644 index f85430e..0000000 --- a/library/src/commonMain/kotlin/osmfeatures/StringUtils.kt +++ /dev/null @@ -1,24 +0,0 @@ -package de.westnordost.osmfeatures - -import doist.x.normalize.Form -import doist.x.normalize.normalize - -class StringUtils { - - companion object { - val FIND_DIACRITICS = "\\p{InCombiningDiacriticalMarks}+".toRegex() - - @JvmStatic - fun canonicalize(str: String): String { - return stripDiacritics(str).lowercase() - } - - private fun stripDiacritics(str: String): String { - return FIND_DIACRITICS.replace(str.normalize(Form.NFD),"") - } - } - - - - -} \ No newline at end of file diff --git a/library/src/commonTest/kotlin/osmfeatures/CollectionUtilsTest.kt b/library/src/commonTest/kotlin/osmfeatures/CollectionUtilsTest.kt deleted file mode 100644 index c1a7f0a..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/CollectionUtilsTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -package osmfeatures - -import de.westnordost.osmfeatures.CollectionUtils -import osmfeatures.MapEntry.Companion.tag -import osmfeatures.MapEntry.Companion.mapOf -import org.junit.Test -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue - -class CollectionUtilsTest { - @Test - fun mapContainsEntry() { - val ab = mapOf(tag("a", "b")).entries.iterator().next() - val cd = mapOf(tag("c", "d")).entries.iterator().next() - val ef = mapOf(tag("e", "f")).entries.iterator().next() - val map = mapOf(tag("a", "b"), tag("c", "d")) - assertTrue(CollectionUtils.mapContainsEntry(map, ab)) - assertTrue(CollectionUtils.mapContainsEntry(map, cd)) - assertFalse(CollectionUtils.mapContainsEntry(map, ef)) - } - - @Test - fun numberOfContainedEntriesInMap() { - val ab = mapOf(tag("a", "b")) - val abcdef = mapOf(tag("a", "b"), tag("c", "d"), tag("e", "f")) - val map = mapOf(tag("a", "b"), tag("c", "d")) - assertEquals(0, CollectionUtils.numberOfContainedEntriesInMap(map, emptyMap().entries)) - assertEquals(1, CollectionUtils.numberOfContainedEntriesInMap(map, ab.entries)) - assertEquals(2, CollectionUtils.numberOfContainedEntriesInMap(map, map.entries)) - assertEquals(2, CollectionUtils.numberOfContainedEntriesInMap(map, abcdef.entries)) - } - - @Test - fun mapContainsAllEntries() { - val ab = mapOf(tag("a", "b")) - val abcdef = mapOf(tag("a", "b"), tag("c", "d"), tag("e", "f")) - val map = mapOf(tag("a", "b"), tag("c", "d")) - assertTrue(CollectionUtils.mapContainsAllEntries(map, emptyMap().entries)) - assertTrue(CollectionUtils.mapContainsAllEntries(map, ab.entries)) - assertTrue(CollectionUtils.mapContainsAllEntries(map, map.entries)) - assertFalse(CollectionUtils.mapContainsAllEntries(map, abcdef.entries)) - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt b/library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt deleted file mode 100644 index cb6778e..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -package osmfeatures - -import de.westnordost.osmfeatures.ContainedMapTree -import org.junit.Test -import osmfeatures.TestUtils.assertEqualsIgnoreOrder -import osmfeatures.TestUtils.listOf -import osmfeatures.MapEntry.Companion.tag -import osmfeatures.MapEntry.Companion.mapOf -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class ContainedMapTreeTest { - @Test - fun copes_with_empty_feature_collection() { - val t = tree(emptyList>()) - assertTrue(t.getAll(mapOf(tag("a", "b"))).isEmpty()) - } - - @Test - fun find_single_map() { - val f1: Map = mapOf(tag("a", "b")) - val t: ContainedMapTree = tree(listOf(f1)) - assertEquals(listOf(f1), t.getAll(mapOf(tag("a", "b"), tag("c", "d")))) - } - - @Test - fun dont_find_single_map() { - val tree: ContainedMapTree = tree(listOf(mapOf(tag("a", "b")))) - assertTrue(tree.getAll(mapOf()).isEmpty()) - assertTrue(tree.getAll(mapOf(tag("c", "d"))).isEmpty()) - assertTrue(tree.getAll(mapOf(tag("a", "c"))).isEmpty()) - } - - @Test - fun find_only_generic_map() { - val f1: Map = mapOf(tag("a", "b")) - val f2: Map = mapOf(tag("a", "b"), tag("c", "d")) - val tree: ContainedMapTree = tree(listOf(f1, f2)) - assertEquals(listOf(f1), tree.getAll(mapOf(tag("a", "b")))) - } - - @Test - fun find_map_with_one_match_and_with_several_matches() { - val f1: Map = mapOf(tag("a", "b")) - val f2: Map = mapOf(tag("a", "b"), tag("c", "d")) - val f3: Map = mapOf(tag("a", "b"), tag("c", "e")) - val f4: Map = mapOf(tag("a", "b"), tag("d", "d")) - val tree: ContainedMapTree = tree(listOf(f1, f2, f3, f4)) - assertEqualsIgnoreOrder(listOf(f1, f2), tree.getAll(mapOf(tag("a", "b"), tag("c", "d")))) - } - - companion object { - private fun tree(items: Collection>): ContainedMapTree { - return ContainedMapTree(items) - } - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt deleted file mode 100644 index 94b444a..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt +++ /dev/null @@ -1,851 +0,0 @@ -package osmfeatures - -import de.westnordost.osmfeatures.BaseFeature -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.GeometryType -import de.westnordost.osmfeatures.IDLocalizedFeatureCollection -import de.westnordost.osmfeatures.LocalizedFeature -import org.junit.Test -import osmfeatures.TestUtils.assertEqualsIgnoreOrder -import org.junit.Assert.* -import osmfeatures.MapEntry.Companion.tag -import osmfeatures.MapEntry.Companion.mapOf -import java.util.Collections - -class FeatureDictionaryTest { - private val bakery: Feature = feature( // unlocalized shop=bakery - "shop/bakery", - mapOf(tag("shop", "bakery")), - POINT, - listOf("Bäckerei"), - listOf("Brot"), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null - ) - private val panetteria: Feature = feature( // localized shop=bakery - "shop/bakery", - mapOf(tag("shop", "bakery")), - POINT, - listOf("Panetteria"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - Locale.ITALIAN - ) - private val ditsch: Feature = feature( // brand in DE for shop=bakery - "shop/bakery/Ditsch", - mapOf(tag("shop", "bakery"), tag("name", "Ditsch")), - POINT, - listOf("Ditsch"), - listOf(), - listOf("DE", "AT"), - listOf("AT-9"), - true, - 1.0f, - mapOf(tag("wikipedia", "de:Brezelb%C3%A4ckerei_Ditsch"), tag("brand", "Ditsch")), - true, - null - ) - private val ditschRussian: Feature = feature( // brand in RU for shop=bakery - "shop/bakery/Дитсч", - mapOf(tag("shop", "bakery"), tag("name", "Ditsch")), - POINT, - listOf("Дитсч"), - listOf(), - listOf("RU", "UA-43"), - listOf(), - true, - 1.0f, - mapOf(tag("wikipedia", "de:Brezelb%C3%A4ckerei_Ditsch"), tag("brand", "Дитсч")), - true, - null - ) - private val ditschInternational: Feature = feature( // brand everywhere for shop=bakery - "shop/bakery/Ditsh", - mapOf(tag("shop", "bakery"), tag("name", "Ditsch")), - POINT, - listOf("Ditsh"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(tag("wikipedia", "de:Brezelb%C3%A4ckerei_Ditsch")), - true, - null - ) - private val liquor_store: Feature = feature( // English localized unspecific shop=alcohol - "shop/alcohol", - mapOf(tag("shop", "alcohol")), - POINT, - listOf("Off licence (Alcohol shop)"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - Locale.UK - ) - private val car_dealer: Feature = feature( // German localized unspecific shop=car - "shop/car", - mapOf(tag("shop", "car")), - POINT, - listOf("Autohändler"), - listOf("auto"), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - Locale.GERMAN - ) - private val second_hand_car_dealer: Feature = feature( // German localized shop=car with subtags - "shop/car/second_hand", - mapOf(tag("shop", "car"), tag("second_hand", "only")), - POINT, - listOf("Gebrauchtwagenhändler"), - listOf("auto"), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - Locale.GERMAN - ) - private val scheisshaus: Feature = feature( // unsearchable feature - "amenity/scheißhaus", - mapOf(tag("amenity", "scheißhaus")), - POINT, - listOf("Scheißhaus"), - listOf(), - listOf(), - listOf(), - false, // <--- not searchable! - 1.0f, - mapOf(), - false, - null - ) - private val bank: Feature = feature( // unlocalized shop=bank (Bank) - "amenity/bank", - mapOf(tag("amenity", "bank")), - POINT, - listOf("Bank"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null - ) - private val bench: Feature = feature( // unlocalized amenity=bench (PARKbank) - "amenity/bench", - mapOf(tag("amenity", "bench")), - POINT, - listOf("Parkbank"), - listOf("Bank"), - listOf(), - listOf(), - true, - 5.0f, - mapOf(), - false, - null - ) - private val casino: Feature = feature( // unlocalized amenity=casino (SPIELbank) - "amenity/casino", - mapOf(tag("amenity", "casino")), - POINT, - listOf("Spielbank"), - listOf("Kasino"), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null - ) - private val atm: Feature = feature( // unlocalized amenity=atm (BankOMAT) - "amenity/atm", - mapOf(tag("amenity", "atm")), - POINT, - listOf("Bankomat"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null - ) - private val stock_exchange: Feature = - feature( // unlocalized amenity=stock_exchange (has "Banking" as term) - "amenity/stock_exchange", - mapOf(tag("amenity", "stock_exchange")), - POINT, - listOf("Börse"), - listOf("Banking"), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null - ) - private val bank_of_america: Feature = feature( // Brand of a amenity=bank (has "Bank" in name) - "amenity/bank/Bank of America", - mapOf(tag("amenity", "bank"), tag("name", "Bank of America")), - POINT, - listOf("Bank of America"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - true, - null - ) - private val bank_of_liechtenstein: Feature = - feature( // Brand of a amenity=bank (has "Bank" in name), but low matchScore - "amenity/bank/Bank of Liechtenstein", - mapOf(tag("amenity", "bank"), tag("name", "Bank of Liechtenstein")), - POINT, - listOf("Bank of Liechtenstein"), - listOf(), - listOf(), - listOf(), - true, - 0.2f, - mapOf(), - true, - null - ) - private val deutsche_bank: Feature = - feature( // Brand of a amenity=bank (does not start with "Bank" in name) - "amenity/bank/Deutsche Bank", - mapOf(tag("amenity", "bank"), tag("name", "Deutsche Bank")), - POINT, - listOf("Deutsche Bank"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - true, - null - ) - private val baenk: Feature = - feature( // amenity=bänk, to see if diacritics match non-strictly ("a" finds "ä") - "amenity/bänk", - mapOf(tag("amenity", "bänk")), - POINT, - listOf("Bänk"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null - ) - private val bad_bank: Feature = - feature( // amenity=bank with subtags that has "Bank" in name but it is not the first word - "amenity/bank/bad", - mapOf(tag("amenity", "bank"), tag("goodity", "bad")), - POINT, - listOf("Bad Bank"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null - ) - private val thieves_guild: Feature = feature( // only has "bank" in an alias - "amenity/thieves_guild", - mapOf(tag("amenity", "thieves_guild")), - POINT, - listOf("Diebesgilde", "Bankräuberausbildungszentrum"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null - ) - private val miniature_train_shop: Feature = - feature( // feature whose name consists of several words - "shop/miniature_train", - mapOf(tag("shop", "miniature_train")), - POINT, - listOf("Miniature Train Shop"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null - ) - - //region by tags - @Test - fun find_no_entry_by_tags() { - val tags: Map = mapOf(tag("shop", "supermarket")) - val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(emptyList(), dictionary.byTags(tags).find()) - } - - @Test - fun find_no_entry_because_wrong_geometry() { - val tags: Map = mapOf(tag("shop", "bakery")) - val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(emptyList(), dictionary.byTags(tags).forGeometry(GeometryType.RELATION).find()) - } - - @Test - fun find_no_entry_because_wrong_locale() { - val tags: Map = mapOf(tag("shop", "bakery")) - val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(emptyList(), dictionary.byTags(tags).forLocale(Locale.ITALIAN).find()) - } - - @Test - fun find_entry_because_fallback_locale() { - val tags: Map = mapOf(tag("shop", "bakery")) - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTags(tags).forLocale(Locale.ITALIAN, null).find() - assertEquals(listOf(bakery), matches) - } - - @Test - fun find_entry_by_tags() { - val tags: Map = mapOf(tag("shop", "bakery")) - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTags(tags).find() - assertEquals(listOf(bakery), matches) - } - - @Test - fun find_non_searchable_entry_by_tags() { - val tags: Map = mapOf(tag("amenity", "scheißhaus")) - val dictionary: FeatureDictionary = dictionary(scheisshaus) - val matches: List = dictionary.byTags(tags).find() - assertEquals(listOf(scheisshaus), matches) - } - - @Test - fun find_entry_by_tags_correct_geometry() { - val tags: Map = mapOf(tag("shop", "bakery")) - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTags(tags).forGeometry(GeometryType.POINT).find() - assertEquals(listOf(bakery), matches) - } - - @Test - fun find_brand_entry_by_tags() { - val tags: Map = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")) - val dictionary: FeatureDictionary = dictionary(bakery, ditsch) - val matches: List = dictionary.byTags(tags).inCountry("DE").find() - assertEquals(listOf(ditsch), matches) - } - - @Test - fun find_only_entries_with_given_locale() { - val tags: Map = mapOf(tag("shop", "bakery")) - val dictionary: FeatureDictionary = dictionary(bakery, panetteria) - assertEquals(listOf(panetteria), dictionary.byTags(tags).forLocale(Locale.ITALIAN).find()) - assertEquals(emptyList(), dictionary.byTags(tags).forLocale(Locale.ENGLISH).find()) - assertEquals(listOf(bakery), dictionary.byTags(tags).forLocale(null as Locale?).find()) - } - - @Test - fun find_only_brands_finds_no_normal_entries() { - val tags: Map = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")) - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTags(tags).isSuggestion(true).find() - assertEquals(0, matches.size) - } - - @Test - fun find_no_brands_finds_only_normal_entries() { - val tags: Map = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")) - val dictionary: FeatureDictionary = dictionary(bakery, ditsch) - val matches: List = dictionary.byTags(tags).isSuggestion(false).find() - assertEquals(listOf(bakery), matches) - } - - @Test - fun find_multiple_brands_sorts_by_locale() { - val tags: Map = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")) - val dictionary: FeatureDictionary = dictionary(ditschRussian, ditschInternational, ditsch) - val matches: List = dictionary.byTags(tags).forLocale(null).find() - assertEquals(ditschInternational, matches[0]) - } - - @Test - fun find_multiple_entries_by_tags() { - val tags: Map = mapOf(tag("shop", "bakery"), tag("amenity", "bank")) - val dictionary: FeatureDictionary = dictionary(bakery, bank) - val matches: List = dictionary.byTags(tags).find() - assertEquals(2, matches.size) - } - - @Test - fun do_not_find_entry_with_too_specific_tags() { - val tags: Map = mapOf(tag("shop", "car")) - val dictionary: FeatureDictionary = dictionary(car_dealer, second_hand_car_dealer) - val matches: List = dictionary.byTags(tags).forLocale(Locale.GERMAN, null).find() - assertEquals(listOf(car_dealer), matches) - } - - @Test - fun find_entry_with_specific_tags() { - val tags: Map = mapOf(tag("shop", "car"), tag("second_hand", "only")) - val dictionary: FeatureDictionary = dictionary(car_dealer, second_hand_car_dealer) - val matches: List = dictionary.byTags(tags).forLocale(Locale.GERMAN, null).find() - assertEquals(listOf(second_hand_car_dealer), matches) - } - - //endregion - //region by name - @Test - fun find_no_entry_by_name() { - val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(emptyList(), dictionary.byTerm("Supermarkt").find()) - } - - @Test - fun find_no_entry_by_name_because_wrong_geometry() { - val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(emptyList(), dictionary.byTerm("Bäckerei").forGeometry(GeometryType.LINE).find()) - } - - @Test - fun find_no_entry_by_name_because_wrong_country() { - val dictionary: FeatureDictionary = dictionary(ditsch, ditschRussian) - assertEquals(emptyList(), dictionary.byTerm("Ditsch").find()) - assertEquals(emptyList(), dictionary.byTerm("Ditsch").inCountry("FR").find()) // not in France - assertEquals( - emptyList(), - dictionary.byTerm("Ditsch").inCountry("AT-9").find() - ) // in all of AT but not Vienna - assertEquals( - emptyList(), - dictionary.byTerm("Дитсч").inCountry("UA").find() - ) // only on the Krim - } - - @Test - fun find_no_non_searchable_entry_by_name() { - val dictionary: FeatureDictionary = dictionary(scheisshaus) - assertEquals(emptyList(), dictionary.byTerm("Scheißhaus").find()) - } - - @Test - fun find_entry_by_name() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTerm("Bäckerei").forLocale(null as Locale?).find() - assertEquals(listOf(bakery), matches) - } - - @Test - fun find_entry_by_name_with_correct_geometry() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = - dictionary.byTerm("Bäckerei").forLocale(null as Locale?).forGeometry(GeometryType.POINT) - .find() - assertEquals(listOf(bakery), matches) - } - - @Test - fun find_entry_by_name_with_correct_country() { - val dictionary: FeatureDictionary = dictionary(ditsch, ditschRussian, bakery) - assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("DE").find()) - assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("DE-TH").find()) - assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("AT").find()) - assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("AT-5").find()) - assertEquals(listOf(ditschRussian), dictionary.byTerm("Дитсч").inCountry("UA-43").find()) - assertEquals(listOf(ditschRussian), dictionary.byTerm("Дитсч").inCountry("RU-KHA").find()) - } - - @Test - fun find_entry_by_name_case_insensitive() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTerm("BÄCkErEI").forLocale(null as Locale?).find() - assertEquals(listOf(bakery), matches) - } - - @Test - fun find_entry_by_name_diacritics_insensitive() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTerm("Backérèi").forLocale(null as Locale?).find() - assertEquals(listOf(bakery), matches) - } - - @Test - fun find_entry_by_term() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTerm("bro").forLocale(null as Locale?).find() - assertEquals(listOf(bakery), matches) - } - - @Test - fun find_entry_by_term_brackets() { - val dictionary: FeatureDictionary = dictionary(liquor_store) - assertEquals(listOf(liquor_store), dictionary.byTerm("Alcohol").forLocale(Locale.UK).find()) - assertEquals( - listOf(liquor_store), - dictionary.byTerm("Off licence (Alcohol Shop)").forLocale(Locale.UK).find() - ) - assertEquals( - listOf(liquor_store), - dictionary.byTerm("Off Licence").forLocale(Locale.UK).find() - ) - assertEquals( - listOf(liquor_store), - dictionary.byTerm("Off Licence (Alco").forLocale(Locale.UK).find() - ) - } - - @Test - fun find_entry_by_term_case_insensitive() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTerm("BRO").forLocale(null as Locale?).find() - assertEquals(listOf(bakery), matches) - } - - @Test - fun find_entry_by_term_diacritics_insensitive() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTerm("bró").forLocale(null as Locale?).find() - assertEquals(listOf(bakery), matches) - } - - @Test - fun find_multiple_entries_by_term() { - val dictionary: FeatureDictionary = dictionary(second_hand_car_dealer, car_dealer) - val matches: List = dictionary.byTerm("auto").forLocale(Locale.GERMAN).find() - assertEqualsIgnoreOrder(listOf(second_hand_car_dealer, car_dealer), matches) - } - - @Test - fun find_multiple_entries_by_name_but_respect_limit() { - val dictionary: FeatureDictionary = dictionary(second_hand_car_dealer, car_dealer) - val matches: List = - dictionary.byTerm("auto").forLocale(Locale.GERMAN).limit(1).find() - assertEquals(1, matches.size) - } - - @Test - fun find_only_brands_by_name_finds_no_normal_entries() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = - dictionary.byTerm("Bäckerei").forLocale(null as Locale?).isSuggestion(true).find() - assertEquals(0, matches.size) - } - - @Test - fun find_no_brands_by_name_finds_only_normal_entries() { - val dictionary: FeatureDictionary = dictionary(bank, bank_of_america) - val matches: List = - dictionary.byTerm("Bank").forLocale(null as Locale?).isSuggestion(false).find() - assertEquals(listOf(bank), matches) - } - - @Test - fun find_no_entry_by_term_because_wrong_locale() { - val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(emptyList(), dictionary.byTerm("Bäck").forLocale(Locale.ITALIAN).find()) - } - - @Test - fun find_entry_by_term_because_fallback_locale() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = - dictionary.byTerm("Bäck").forLocale(Locale.ITALIAN, null).find() - assertEquals(listOf(bakery), matches) - } - - @Test - fun find_multi_word_brand_feature() { - val dictionary: FeatureDictionary = dictionary(deutsche_bank) - assertEquals(listOf(deutsche_bank), dictionary.byTerm("Deutsche Ba").find()) - assertEquals(listOf(deutsche_bank), dictionary.byTerm("Deut").find()) - // by-word only for non-brand features - assertTrue(dictionary.byTerm("Ban").find().isEmpty()) - } - - @Test - fun find_multi_word_feature() { - val dictionary: FeatureDictionary = dictionary(miniature_train_shop) - assertEquals( - listOf(miniature_train_shop), - dictionary.byTerm("mini").forLocale(null as Locale?).find() - ) - assertEquals( - listOf(miniature_train_shop), - dictionary.byTerm("train").forLocale(null as Locale?).find() - ) - assertEquals( - listOf(miniature_train_shop), - dictionary.byTerm("shop").forLocale(null as Locale?).find() - ) - assertEquals( - listOf(miniature_train_shop), - dictionary.byTerm("Miniature Trai").forLocale(null as Locale?).find() - ) - assertEquals( - listOf(miniature_train_shop), - dictionary.byTerm("Miniature Train Shop").forLocale(null as Locale?).find() - ) - assertTrue(dictionary.byTerm("Train Sho").forLocale(null as Locale?).find().isEmpty()) - } - - @Test - fun find_entry_by_tag_value() { - val dictionary: FeatureDictionary = dictionary(panetteria) - val matches: List = dictionary.byTerm("bakery").forLocale(Locale.ITALIAN).find() - assertEquals(listOf(panetteria), matches) - } - - //endregion - //region by id - @Test - fun find_no_entry_by_id() { - val dictionary: FeatureDictionary = dictionary(bakery) - assertNull(dictionary.byId("amenity/hospital").get()) - } - - @Test - fun find_no_entry_by_id_because_unlocalized_results_are_excluded() { - val dictionary: FeatureDictionary = dictionary(bakery) - assertNull(dictionary.byId("shop/bakery").forLocale(Locale.ITALIAN).get()) - } - - @Test - fun find_entry_by_id() { - val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(bakery, dictionary.byId("shop/bakery").get()) - assertEquals(bakery, dictionary.byId("shop/bakery").forLocale(Locale.CHINESE, null).get()) - } - - @Test - fun find_localized_entry_by_id() { - val dictionary: FeatureDictionary = dictionary(panetteria) - assertEquals(panetteria, dictionary.byId("shop/bakery").forLocale(Locale.ITALIAN).get()) - assertEquals( - panetteria, - dictionary.byId("shop/bakery").forLocale(Locale.ITALIAN, null).get() - ) - } - - @Test - fun find_no_entry_by_id_because_wrong_country() { - val dictionary: FeatureDictionary = dictionary(ditsch) - assertNull(dictionary.byId("shop/bakery/Ditsch").get()) - assertNull(dictionary.byId("shop/bakery/Ditsch").inCountry("IT").get()) - assertNull(dictionary.byId("shop/bakery/Ditsch").inCountry("AT-9").get()) - } - - @Test - fun find_entry_by_id_in_country() { - val dictionary: FeatureDictionary = dictionary(ditsch) - assertEquals(ditsch, dictionary.byId("shop/bakery/Ditsch").inCountry("AT").get()) - assertEquals(ditsch, dictionary.byId("shop/bakery/Ditsch").inCountry("DE").get()) - } - - //endregion - @Test - fun find_by_term_sorts_result_in_correct_order() { - val dictionary: FeatureDictionary = dictionary( - casino, baenk, bad_bank, stock_exchange, bank_of_liechtenstein, bank, bench, atm, - bank_of_america, deutsche_bank, thieves_guild - ) - val matches: List = dictionary.byTerm("Bank").forLocale(null as Locale?).find() - assertEquals( - listOf( - bank, // exact name matches - baenk, // exact name matches (diacritics and case insensitive) - atm, // starts-with name matches - thieves_guild, // starts-with alias matches - bad_bank, // starts-with-word name matches - bank_of_america, // starts-with brand name matches - bank_of_liechtenstein, // starts-with brand name matches - lower matchScore - bench, // found word in terms - higher matchScore - stock_exchange // found word in terms - lower matchScore - // casino, // not included: "Spielbank" does not start with "bank" - // deutsche_bank // not included: "Deutsche Bank" does not start with "bank" and is a brand - ), matches - ) - } - - @Test - fun some_tests_with_real_data() { - val featureCollection: LocalizedFeatureCollection = - IDLocalizedFeatureCollection(LivePresetDataAccessAdapter()) - featureCollection.getAll(listOf(Locale.ENGLISH)) - val dictionary = FeatureDictionary(featureCollection, null) - val matches: List = dictionary - .byTags(mapOf(tag("amenity", "studio"))) - .forLocale(Locale.ENGLISH) - .find() - assertEquals(1, matches.size) - assertEquals("Studio", matches[0].name) - val matches2: List = dictionary - .byTags(mapOf(tag("amenity", "studio"), tag("studio", "audio"))) - .forLocale(Locale.ENGLISH) - .find() - assertEquals(1, matches2.size) - assertEquals("Recording Studio", matches2[0].name) - val matches3: List = dictionary - .byTerm("Chinese Res") - .forLocale(Locale.ENGLISH) - .find() - assertEquals(1, matches3.size) - assertEquals("Chinese Restaurant", matches3[0].name) - } - - @Test - fun issue19() { - val lush: Feature = feature( - "shop/cosmetics/lush-a08666", - mapOf(tag("brand:wikidata", "Q1585448"), tag("shop", "cosmetics")), - listOf(GeometryType.POINT, GeometryType.AREA), - listOf("Lush"), - listOf("lush"), - listOf(), - listOf(), - true, - 2.0f, - mapOf( - tag("brand", "Lush"), - tag("brand:wikidata", "Q1585448"), - tag("name", "Lush"), - tag("shop", "cosmetics") - ), - true, - null - ) - val dictionary: FeatureDictionary = dictionary(lush) - val byTags: List = dictionary - .byTags(mapOf(tag("brand:wikidata", "Q1585448"), tag("shop", "cosmetics"))) - .forLocale(Locale.GERMAN, null) - .inCountry("DE") - .find() - assertEquals(1, byTags.size) - assertEquals(lush, byTags[0]) - val byTerm: List = dictionary - .byTerm("Lush") - .forLocale(Locale.GERMAN, null) - .inCountry("DE") - .find() - assertEquals(1, byTerm.size) - assertEquals(lush, byTerm[0]) - val byId: Feature? = dictionary - .byId("shop/cosmetics/lush-a08666") - .forLocale(Locale.GERMAN, null) - .inCountry("DE") - .get() - assertEquals(lush, byId) - } - - internal class SuggestionFeature( - id: String, - tags: Map, - geometry: List, - icon: String?, - imageURL: String?, - names: List, - terms: List, - includeCountryCodes: List, - excludeCountryCodes: List, - searchable: Boolean, - matchScore: Float, - addTags: Map, - removeTags: Map - ) : BaseFeature( - id, - tags, - geometry, - icon, - imageURL, - names, - terms, - includeCountryCodes, - excludeCountryCodes, - searchable, - matchScore, - true, - addTags, - removeTags - ) - - companion object { - private val POINT: List = Collections.singletonList(GeometryType.POINT) - private fun dictionary(vararg entries: Feature): FeatureDictionary { - return FeatureDictionary( - TestLocalizedFeatureCollection(entries.filterNot { it is SuggestionFeature }), - TestPerCountryFeatureCollection(entries.filterIsInstance()) - ) - } - - private fun feature( - id: String, - tags: Map, - geometries: List, - names: List, - terms: List, - countryCodes: List, - excludeCountryCodes: List, - searchable: Boolean, - matchScore: Float, - addTags: Map, - isSuggestion: Boolean, - locale: Locale? - ): Feature { - return if (isSuggestion) { - SuggestionFeature( - id, tags, geometries, null, null, names, terms, countryCodes, - excludeCountryCodes, searchable, matchScore, addTags, mapOf() - ) - } else { - val f = BaseFeature( - id, tags, geometries, null, null, names, terms, countryCodes, - excludeCountryCodes, searchable, matchScore, false, addTags, mapOf() - ) - if (locale != null) { - LocalizedFeature(f, locale, f.names, f.terms.orEmpty()) - } else { - f - } - } - } - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt b/library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt deleted file mode 100644 index 83e8616..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -package osmfeatures - -import de.westnordost.osmfeatures.BaseFeature -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.GeometryType -import org.junit.Test -import osmfeatures.MapEntry.Companion.tag -import osmfeatures.MapEntry.Companion.mapOf -import osmfeatures.TestUtils.listOf -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class FeatureTagsIndexTest { - @Test - fun copes_with_empty_collection() { - val index: FeatureTagsIndex = index() - assertTrue(index.getAll(mapOf(tag("a", "b"))).isEmpty()) - } - - @Test - fun get_two_features_with_same_tags() { - val f1: Feature = feature(tag("a", "b")) - val f2: Feature = feature(tag("a", "b")) - val index: FeatureTagsIndex = index(f1, f2) - assertEquals( - listOf(f1, f2), - index.getAll(mapOf(tag("a", "b"), tag("c", "d"))) - ) - } - - @Test - fun get_two_features_with_different_tags() { - val f1: Feature = feature(tag("a", "b")) - val f2: Feature = feature(tag("c", "d")) - val index: FeatureTagsIndex = index(f1, f2) - assertEquals( - listOf(f1, f2), - index.getAll(mapOf(tag("a", "b"), tag("c", "d"))) - ) - } - - companion object { - private fun index(vararg features: Feature): FeatureTagsIndex { - return FeatureTagsIndex(features.toList()) - } - - private fun feature(vararg mapEntries: MapEntry): Feature { - return BaseFeature( - "id", - mapOf(*mapEntries), - listOf(GeometryType.POINT), - null, null, - listOf("name"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - false, - mapOf(), - mapOf() - ) - } - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt b/library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt deleted file mode 100644 index 99f934f..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt +++ /dev/null @@ -1,85 +0,0 @@ -package de.westnordost.osmfeatures - -import org.junit.Test -import osmfeatures.TestUtils.assertEqualsIgnoreOrder -import osmfeatures.TestUtils.listOf -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import osmfeatures.FeatureTermIndex - -class FeatureTermIndexTest { - @Test - fun copes_with_empty_collection() { - val index: FeatureTermIndex = index() - assertTrue(index.getAll("a").isEmpty()) - } - - @Test - fun get_one_features_with_same_term() { - val f1: Feature = feature("a", "b") - val f2: Feature = feature("c") - val index: FeatureTermIndex = index(f1, f2) - assertEqualsIgnoreOrder( - listOf(f1), - index.getAll("b") - ) - } - - @Test - fun get_two_features_with_same_term() { - val f1: Feature = feature("a", "b") - val f2: Feature = feature("a", "c") - val index: FeatureTermIndex = index(f1, f2) - assertEqualsIgnoreOrder( - listOf(f1, f2), - index.getAll("a") - ) - } - - @Test - fun get_two_features_with_different_terms() { - val f1: Feature = feature("anything") - val f2: Feature = feature("anybody") - val index: FeatureTermIndex = index(f1, f2) - assertEqualsIgnoreOrder( - listOf(f1, f2), - index.getAll("any") - ) - assertEqualsIgnoreOrder( - listOf(f1), - index.getAll("anyt") - ) - } - - @Test - fun dont_get_one_feature_twice() { - val f1: Feature = feature("something", "someone") - val index: FeatureTermIndex = index(f1) - assertEquals(listOf(f1), index.getAll("some")) - } - - companion object { - private fun index(vararg features: Feature): FeatureTermIndex { - return FeatureTermIndex(features.toList()) { feature: Feature -> feature.terms } - } - - private fun feature(vararg terms: String): Feature { - return BaseFeature( - "id", - mapOf(), - listOf(GeometryType.POINT), - null, - null, - listOf("name"), - terms.toList(), - listOf(), - listOf(), - true, - 1.0f, - false, - mapOf(), - mapOf() - ) - } - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt b/library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt deleted file mode 100644 index e222a3b..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt +++ /dev/null @@ -1,70 +0,0 @@ -package osmfeatures - -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.IDBrandPresetsFeatureCollection -import okio.FileSystem -import okio.Path.Companion.toPath -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import osmfeatures.TestUtils.assertEqualsIgnoreOrder -import osmfeatures.TestUtils.listOf -import okio.Source -import org.junit.Test -import java.io.IOException - -class IDBrandPresetsFeatureCollectionTest { - @Test - fun load_brands() { - val c = IDBrandPresetsFeatureCollection(object : FileAccessAdapter { - @Override - override fun exists(name: String): Boolean { - return name == "presets.json" - } - - @Override - @Throws(IOException::class) - override fun open(name: String): Source { - if (name == "presets.json") return getSource("brand_presets_min.json") - throw IOException("wrong file name") - } - }) - assertEqualsIgnoreOrder( - listOf("Duckworths", "Megamall"), - getNames(c.getAll(listOf(null as String?))) - ) - assertEquals("Duckworths", c["a/brand", listOf(null as String?)]?.name) - assertEquals("Megamall", c["another/brand", listOf(null as String?)]?.name) - } - - @Test - fun load_brands_by_country() { - val c = IDBrandPresetsFeatureCollection(object : FileAccessAdapter { - @Override - override fun exists(name: String): Boolean { - return name == "presets-DE.json" - } - - @Override - @Throws(IOException::class) - override fun open(name: String): Source { - if (name == "presets-DE.json") return getSource("brand_presets_min2.json") - throw IOException("File not found") - } - }) - assertEqualsIgnoreOrder(listOf("Talespin"), getNames(c.getAll(listOf("DE")))) - assertEquals("Talespin", c["yet_another/brand", listOf("DE")]?.name) - c["yet_another/brand", listOf("DE")]?.isSuggestion?.let { assertTrue(it) } - } - - @kotlin.Throws(IOException::class) - private fun getSource(file: String): Source { - val resourcePath = "src/commonTest/resources/${file}".toPath() - return FileSystem.SYSTEM.source(resourcePath) - } - - companion object { - private fun getNames(features: Collection): Collection { - return features.map { it.name } - } - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt b/library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt deleted file mode 100644 index 833860c..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt +++ /dev/null @@ -1,155 +0,0 @@ -package osmfeatures - -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.IDLocalizedFeatureCollection -import okio.FileSystem -import okio.Source -import org.junit.Test -import osmfeatures.TestUtils.assertEqualsIgnoreOrder -import osmfeatures.TestUtils.listOf -import okio.FileNotFoundException -import okio.IOException -import okio.Path.Companion.toPath -import org.junit.Assert.* - -class IDLocalizedFeatureCollectionTest { - @Test - fun features_not_found_produces_runtime_exception() { - try { - IDLocalizedFeatureCollection(object : FileAccessAdapter { - @Override - override fun exists(name: String): Boolean { - return false - } - - @Override - @Throws(IOException::class) - override fun open(name: String): Source { - throw FileNotFoundException() - } - }) - fail() - } catch (ignored: RuntimeException) { - } - } - - @Test - fun load_features_and_two_localizations() { - val c = IDLocalizedFeatureCollection(object : FileAccessAdapter { - @Override - override fun exists(name: String): Boolean { - return listOf("presets.json", "en.json", "de.json").contains(name) - } - - @Override - @Throws(IOException::class) - override fun open(name: String): Source { - if (name == "presets.json") return getSource("some_presets_min.json") - if (name == "en.json") return getSource("localizations_en.json") - if (name == "de.json") return getSource("localizations_de.json") - throw IOException("File not found") - } - }) - - // getting non-localized features - val notLocalized: List = listOf(null as Locale?) - val notLocalizedFeatures: Collection = c.getAll(notLocalized) - assertEqualsIgnoreOrder(listOf("test", "test", "test"), getNames(notLocalizedFeatures)) - assertEquals("test", c["some/id", notLocalized]?.name) - assertEquals("test", c["another/id", notLocalized]?.name) - assertEquals("test", c["yet/another/id", notLocalized]?.name) - - // getting English features - val english: List = listOf(Locale.ENGLISH) - val englishFeatures: Collection = c.getAll(english) - assertEqualsIgnoreOrder(listOf("Bakery"), getNames(englishFeatures)) - assertEquals("Bakery", c["some/id", english]?.name) - assertNull(c["another/id", english]) - assertNull(c["yet/another/id", english]) - - // getting Germany features - // this also tests if the fallback from de-DE to de works if de-DE.json does not exist - val germany: List = listOf(Locale.GERMANY) - val germanyFeatures: Collection = c.getAll(germany) - assertEqualsIgnoreOrder(listOf("Bäckerei", "Gullideckel"), getNames(germanyFeatures)) - assertEquals("Bäckerei", c["some/id", germany]?.name) - assertEquals("Gullideckel", c["another/id", germany]?.name) - assertNull(c["yet/another/id", germany]) - - // getting features through fallback chain - val locales: List = listOf(Locale.ENGLISH, Locale.GERMANY, null) - val fallbackFeatures: Collection = c.getAll(locales) - assertEqualsIgnoreOrder(listOf("Bakery", "Gullideckel", "test"), getNames(fallbackFeatures)) - assertEquals("Bakery", c["some/id", locales]?.name) - assertEquals("Gullideckel", c["another/id", locales]?.name) - assertEquals("test", c["yet/another/id", locales]?.name) - assertEquals(Locale.ENGLISH, c["some/id", locales]?.locale) - assertEquals(Locale.GERMAN, c["another/id", locales]?.locale) - assertNull(c["yet/another/id", locales]?.locale) - } - - @Test - fun load_features_and_merge_localizations() { - val c = IDLocalizedFeatureCollection(object : FileAccessAdapter { - @Override - override fun exists(name: String): Boolean { - return listOf( - "presets.json", - "de-AT.json", - "de.json", - "de-Cyrl.json", - "de-Cyrl-AT.json" - ).contains(name) - } - - @Override - @Throws(IOException::class) - override fun open(name: String): Source { - if (name == "presets.json") return getSource("some_presets_min.json") - if (name == "de-AT.json") return getSource("localizations_de-AT.json") - if (name == "de.json") return getSource("localizations_de.json") - if (name == "de-Cyrl-AT.json") return getSource("localizations_de-Cyrl-AT.json") - if (name == "de-Cyrl.json") return getSource("localizations_de-Cyrl.json") - throw IOException("File not found") - } - }) - - // standard case - no merging - val german: List = listOf(Locale.GERMAN) - val germanFeatures: Collection = c.getAll(german) - assertEqualsIgnoreOrder(listOf("Bäckerei", "Gullideckel"), getNames(germanFeatures)) - assertEquals("Bäckerei", c["some/id", german]?.name) - assertEquals("Gullideckel", c["another/id", german]?.name) - assertNull(c["yet/another/id", german]) - - // merging de-AT and de - val austria: List = listOf(Locale("de", "AT")) - val austrianFeatures: Collection = c.getAll(austria) - assertEqualsIgnoreOrder( - listOf("Backhusl", "Gullideckel", "Brückle"), - getNames(austrianFeatures) - ) - assertEquals("Backhusl", c["some/id", austria]?.name) - assertEquals("Gullideckel", c["another/id", austria]?.name) - assertEquals("Brückle", c["yet/another/id", austria]?.name) - - // merging scripts - val cryllic: List = listOf(Locale.Builder().setLanguage("de").setScript("Cyrl").build()) - assertEquals("бацкхаус", c["some/id", cryllic]?.name) - val cryllicAustria: List = - listOf(Locale.Builder().setLanguage("de").setRegion("AT").setScript("Cyrl").build()) - assertEquals("бацкхусл", c["some/id", cryllicAustria]?.name) - } - - @kotlin.Throws(IOException::class) - private fun getSource(file: String): Source { - val resourcePath = "src/commonTest/resources/${file}".toPath() - return FileSystem.SYSTEM.source(resourcePath) - } - - companion object { - private fun getNames(features: Collection): Collection { - return features.map { it.name } - } - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt b/library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt deleted file mode 100644 index 39226c3..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt +++ /dev/null @@ -1,105 +0,0 @@ -package de.westnordost.osmfeatures - -import okio.FileSystem -import okio.Source -import org.junit.Test -import osmfeatures.MapEntry.Companion.tag -import osmfeatures.MapEntry.Companion.mapOf -import osmfeatures.TestUtils.listOf -import okio.IOException -import okio.Path.Companion.toPath -import okio.source -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import java.net.URL - -class IDPresetsJsonParserTest { - @Test - fun load_features_only() { - val features: List = parse("one_preset_full.json") - assertEquals(1, features.size) - val feature: Feature = features[0] - assertEquals("some/id", feature.id) - assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature.tags) - assertEquals( - listOf( - GeometryType.POINT, - GeometryType.VERTEX, - GeometryType.LINE, - GeometryType.AREA, - GeometryType.RELATION - ), feature.geometry - ) - assertEquals(listOf("DE", "GB"), feature.includeCountryCodes) - assertEquals(listOf("IT"), feature.excludeCountryCodes) - assertEquals("foo", feature.name) - assertEquals("abc", feature.icon) - assertEquals("someurl", feature.imageURL) - assertEquals(listOf("foo", "one", "two"), feature.names) - assertEquals(listOf("1", "2"), feature.terms) - assertEquals(0.5f, feature.matchScore, 0.001f) - assertFalse(feature.isSearchable) - assertEquals(mapOf(tag("e", "f")), feature.addTags) - assertEquals(mapOf(tag("d", "g")), feature.removeTags) - } - - @Test - fun load_features_only_defaults() { - val features: List = parse("one_preset_min.json") - assertEquals(1, features.size) - val feature: Feature = features[0] - assertEquals("some/id", feature.id) - assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature.tags) - assertEquals(listOf(GeometryType.POINT), feature.geometry) - assertTrue(feature.includeCountryCodes.isEmpty()) - assertTrue(feature.excludeCountryCodes.isEmpty()) - assertEquals("", feature.name) - assertEquals("", feature.icon) - assertEquals("", feature.imageURL) - assertEquals(1, feature.names.size) - assertTrue(feature.terms.isEmpty()) - assertEquals(1.0f, feature.matchScore, 0.001f) - assertTrue(feature.isSearchable) - assertEquals(feature.addTags, feature.tags) - assertEquals(feature.addTags, feature.removeTags) - } - - @Test - fun load_features_unsupported_location_set() { - val features: List = parse("one_preset_unsupported_location_set.json") - assertEquals(2, features.size) - assertEquals("some/ok", features[0].id) - assertEquals("another/ok", features[1].id) - } - - @Test - fun load_features_no_wildcards() { - val features: List = parse("one_preset_wildcard.json") - assertTrue(features.isEmpty()) - } - - @Test - @kotlin.Throws(IOException::class) - fun parse_some_real_data() { - val url = - URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - val features: List = IDPresetsJsonParser().parse(url.openStream().source()) - // should not crash etc - assertTrue(features.size > 1000) - } - - private fun parse(file: String): List { - return try { - IDPresetsJsonParser().parse(getSource(file)) - } catch (e: IOException) { - throw RuntimeException() - } - } - - @kotlin.Throws(IOException::class) - private fun getSource(file: String): Source { - val resourcePath = "src/commonTest/resources/${file}".toPath() - return FileSystem.SYSTEM.source(resourcePath) - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt deleted file mode 100644 index 77e5e5b..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ /dev/null @@ -1,105 +0,0 @@ -package osmfeatures - -import de.westnordost.osmfeatures.BaseFeature -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.GeometryType -import de.westnordost.osmfeatures.IDPresetsJsonParser -import de.westnordost.osmfeatures.IDPresetsTranslationJsonParser -import de.westnordost.osmfeatures.LocalizedFeature -import okio.FileSystem -import okio.Path.Companion.toPath -import okio.Source -import org.junit.Test -import java.io.IOException -import java.net.URISyntaxException -import java.net.URL -import osmfeatures.MapEntry.Companion.tag -import osmfeatures.MapEntry.Companion.mapOf -import osmfeatures.TestUtils.listOf -import okio.source -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue - -class IDPresetsTranslationJsonParserTest { - @Test - fun load_features_and_localization() { - val features: List = parse("one_preset_min.json", "localizations.json") - assertEquals(1, features.size) - val feature: Feature = features[0] - assertEquals("some/id", feature.id) - assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature.tags) - assertEquals(listOf(GeometryType.POINT), feature.geometry) - assertEquals("bar", feature.name) - assertEquals(listOf("bar", "one", "two", "three"), feature.names) - assertEquals(listOf("a", "b"), feature.terms) - } - - @Test - fun load_features_and_localization_defaults() { - val features: List = - parse("one_preset_min.json", "localizations_min.json") - assertEquals(1, features.size) - val feature: Feature = features[0] - assertEquals("some/id", feature.id) - assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature.tags) - assertEquals(listOf(GeometryType.POINT), feature.geometry) - assertEquals("bar", feature.name) - assertTrue(feature.terms.isEmpty()) - } - - @Test - fun load_features_and_localization_with_placeholder_name() { - val features: List = - parse("one_preset_with_placeholder_name.json", "localizations.json") - val featuresById = HashMap(features.associateBy { it.id }) - assertEquals(2, features.size) - val feature: Feature? = featuresById["some/id-dingsdongs"] - assertEquals("some/id-dingsdongs", feature?.id) - assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature?.tags) - assertEquals(listOf(GeometryType.POINT), feature?.geometry) - assertEquals("bar", feature?.name) - assertEquals(listOf("bar", "one", "two", "three"), feature?.names) - assertEquals(listOf("a", "b"), feature?.terms) - } - - @Test - @kotlin.Throws(IOException::class, URISyntaxException::class) - fun parse_some_real_data() { - val url = - URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - val features: List = - IDPresetsJsonParser().parse(url.openConnection().getInputStream().source()) - val featureMap = HashMap(features.associateBy { it.id }) - val rawTranslationsURL = - URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json") - val translatedFeatures: List = IDPresetsTranslationJsonParser().parse( - rawTranslationsURL.openStream().source(), - Locale.GERMAN, - featureMap - ) - - // should not crash etc - assertTrue(translatedFeatures.size > 1000) - } - - private fun parse(presetsFile: String, translationsFile: String): List { - return try { - val baseFeatures: List = - IDPresetsJsonParser().parse(getSource(presetsFile)) - val featureMap = HashMap(baseFeatures.associateBy { it.id }) - IDPresetsTranslationJsonParser().parse( - getSource(translationsFile), - Locale.ENGLISH, - featureMap - ) - } catch (e: IOException) { - throw RuntimeException() - } - } - - @kotlin.Throws(IOException::class) - private fun getSource(file: String): Source { - val resourcePath = "src/commonTest/resources/${file}".toPath() - return FileSystem.SYSTEM.source(resourcePath) - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt b/library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt deleted file mode 100644 index 13bb1ca..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package de.westnordost.osmfeatures - -import kotlinx.serialization.json.JsonArray -import org.junit.Test -import de.westnordost.osmfeatures.JsonUtils.parseList -import de.westnordost.osmfeatures.JsonUtils.parseStringMap -import osmfeatures.TestUtils.listOf -import org.junit.Assert.assertEquals - -class JsonUtilsTest { - @Test - fun parseList_with_null_json_array() { - assertEquals(0, parseList(null) { obj -> obj }.size) - } - - @Test - fun parseList_with_empty_json_array() { - assertEquals(0, parseList(JsonArray(listOf())) { obj -> obj }.size) - } - @Test - fun parseStringMap_with_null_json_map() { - assertEquals(0, parseStringMap(null).size) - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt b/library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt deleted file mode 100644 index 2af7711..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt +++ /dev/null @@ -1,24 +0,0 @@ -package osmfeatures - -import java.io.IOException -import java.net.URL -import osmfeatures.TestUtils.listOf -import okio.source - -class LivePresetDataAccessAdapter : FileAccessAdapter { - @Override - override fun exists(name: String): Boolean { - return listOf("presets.json", "de.json", "en.json", "en-GB.json").contains(name) - } - - @Override - @kotlin.Throws(IOException::class) - override fun open(name: String): okio.Source { - val url: URL = if (name == "presets.json") { - URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - } else { - URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/$name") - } - return url.openStream().source() - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/MapEntry.kt b/library/src/commonTest/kotlin/osmfeatures/MapEntry.kt deleted file mode 100644 index d95a637..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/MapEntry.kt +++ /dev/null @@ -1,15 +0,0 @@ -package osmfeatures - -internal class MapEntry private constructor(private val key: String, private val value: String) { - companion object { - fun tag(key: String, value: String): MapEntry { - return MapEntry(key, value) - } - - fun mapOf(vararg items: MapEntry): Map { - val result = hashMapOf() - for (item in items) result[item.key] = item.value - return result - } - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt b/library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt deleted file mode 100644 index 0db7014..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt +++ /dev/null @@ -1,37 +0,0 @@ -package de.westnordost.osmfeatures - -import org.junit.Test -import osmfeatures.TestUtils.assertEqualsIgnoreOrder -import osmfeatures.TestUtils.listOf -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue - -class StartsWithStringTreeTest { - @Test - fun copes_with_empty_collection() { - val t = StartsWithStringTree(listOf()) - assertTrue(t.getAll("any").isEmpty()) - } - - @Test - fun find_single_string() { - val t = StartsWithStringTree(listOf("anything")) - assertEquals(listOf("anything"), t.getAll("a")) - assertEquals(listOf("anything"), t.getAll("any")) - assertEquals(listOf("anything"), t.getAll("anything")) - } - - @Test - fun dont_find_single_string() { - val t = StartsWithStringTree(listOf("anything", "more", "etc")) - assertTrue(t.getAll("").isEmpty()) - assertTrue(t.getAll("nything").isEmpty()) - assertTrue(t.getAll("anything else").isEmpty()) - } - - @Test - fun find_several_strings() { - val t = StartsWithStringTree(listOf("anything", "anybody", "anytime")) - assertEqualsIgnoreOrder(listOf("anything", "anybody", "anytime"), t.getAll("any")) - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt b/library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt deleted file mode 100644 index bda9656..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt +++ /dev/null @@ -1,23 +0,0 @@ -package osmfeatures - -import de.westnordost.osmfeatures.Feature - - -class TestLocalizedFeatureCollection(features: List) : LocalizedFeatureCollection { - private val features: List - - init { - this.features = features - } - - @Override - override fun getAll(locales: List): Collection { - return features.filter { locales.contains(it.locale) } - } - - @Override - override operator fun get(id: String, locales: List): Feature? { - val feature = features.find { it.id == id } - return if (feature == null || (!locales.contains(feature.locale))) null else feature - } -} \ No newline at end of file diff --git a/library/src/commonTest/kotlin/osmfeatures/TestPerCountryFeatureCollection.kt b/library/src/commonTest/kotlin/osmfeatures/TestPerCountryFeatureCollection.kt deleted file mode 100644 index f3db432..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/TestPerCountryFeatureCollection.kt +++ /dev/null @@ -1,33 +0,0 @@ -package osmfeatures - -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.PerCountryFeatureCollection - -class TestPerCountryFeatureCollection(features: List) : PerCountryFeatureCollection { - private val features: List - - init { - this.features = features - } - - @Override - override fun getAll(countryCodes: List): Collection { - return features.filter { - countryCodes - .find { countryCode -> - it.includeCountryCodes.contains(countryCode) || countryCode == null && it.includeCountryCodes.isEmpty() - } != null - } - } - - @Override - override operator fun get(id: String, countryCodes: List): Feature? { - return features.find { feature -> - feature.id == id - && countryCodes.find { - feature.includeCountryCodes.contains(it) || it == null && feature.includeCountryCodes.isEmpty() - } != null - } - - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/TestUtils.kt b/library/src/commonTest/kotlin/osmfeatures/TestUtils.kt deleted file mode 100644 index 9db6489..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/TestUtils.kt +++ /dev/null @@ -1,14 +0,0 @@ -package osmfeatures - -import org.junit.Assert.assertTrue - -object TestUtils { - fun assertEqualsIgnoreOrder(a: Collection, b: Collection) { - assertTrue(a.size == b.size && a.containsAll(b)) - } - - @SafeVarargs - fun listOf(vararg items: T): List { - return items.asList() - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index ee83769..5f47dd8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,5 +14,5 @@ dependencyResolutionManagement { } } -rootProject.name = "osmfeatures" +rootProject.name = "de.westnordost.osmfeatures" include(":library") From 86a375da56f78a23a796e3d195e18ec873738a0d Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Sun, 18 Feb 2024 13:59:18 +0100 Subject: [PATCH 18/98] cleaned imports and unused files --- gradle/libs.versions.toml | 2 - library/build.gradle.kts | 2 - .../BaseFeature.kt | 2 - .../CollectionUtils.kt | 0 .../ContainedMapTree.kt | 4 +- .../Feature.kt | 2 - .../FeatureDictionnary.kt | 13 +- .../FeatureTagsIndex.kt | 5 +- .../FeatureTermIndex.kt | 5 +- .../FileAccessAdapter.kt | 2 +- .../FileSystemAccess.kt | 2 +- .../GeometryType.kt | 0 .../IDBrandPresetsFeatureCollection.kt | 2 - .../IDLocalizedFeatureCollection.kt | 4 - .../IDPresetsJsonParser.kt | 0 .../IDPresetsTranslationJsonParser.kt | 1 - .../JsonUtils.kt | 0 .../Locale.kt | 10 +- .../LocalizedFeature.kt | 2 +- .../LocalizedFeatureCollection.kt | 2 +- .../PerCountryFeatureCollection.kt | 0 .../StartsWithStringTree.kt | 0 .../StringUtils.kt | 2 +- .../CollectionUtilsTest.kt | 25 ++-- .../ContainedMapTreeTest.kt | 53 +++++++++ .../FeatureDictionaryTest.kt | 111 +++++++++--------- .../FeatureTagsIndexTest.kt | 23 ++-- .../FeatureTermIndexTest.kt | 5 +- .../IDBrandPresetsFeatureCollectionTest.kt | 5 +- .../IDLocalizedFeatureCollectionTest.kt | 5 +- .../IDPresetsJsonParserTest.kt | 11 +- .../IDPresetsTranslationJsonParserTest.kt | 11 +- .../JsonUtilsTest.kt | 1 - .../LivePresetDataAccessAdapter.kt | 3 +- .../StartsWithStringTreeTest.kt | 3 +- .../TestLocalizedFeatureCollection.kt | 2 +- .../TestPerCountryFeatureCollection.kt | 2 +- .../TestUtils.kt | 7 +- .../osmfeatures/ContainedMapTreeTest.kt | 57 --------- .../commonTest/kotlin/osmfeatures/MapEntry.kt | 15 --- settings.gradle.kts | 2 +- 41 files changed, 166 insertions(+), 237 deletions(-) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/BaseFeature.kt (98%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/CollectionUtils.kt (100%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/ContainedMapTree.kt (97%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/Feature.kt (95%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/FeatureDictionnary.kt (98%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/FeatureTagsIndex.kt (89%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/FeatureTermIndex.kt (91%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/FileAccessAdapter.kt (85%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/FileSystemAccess.kt (93%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/GeometryType.kt (100%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/IDBrandPresetsFeatureCollection.kt (98%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/IDLocalizedFeatureCollection.kt (97%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/IDPresetsJsonParser.kt (100%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/IDPresetsTranslationJsonParser.kt (99%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/JsonUtils.kt (100%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/Locale.kt (91%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/LocalizedFeature.kt (97%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/LocalizedFeatureCollection.kt (92%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/PerCountryFeatureCollection.kt (100%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/StartsWithStringTree.kt (100%) rename library/src/commonMain/kotlin/{osmfeatures => de.westnordost.osmfeatures}/StringUtils.kt (83%) rename library/src/commonTest/kotlin/{osmfeatures => de.westnordost.osmfeatures}/CollectionUtilsTest.kt (62%) create mode 100644 library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt rename library/src/commonTest/kotlin/{osmfeatures => de.westnordost.osmfeatures}/FeatureDictionaryTest.kt (88%) rename library/src/commonTest/kotlin/{osmfeatures => de.westnordost.osmfeatures}/FeatureTagsIndexTest.kt (66%) rename library/src/commonTest/kotlin/{osmfeatures => de.westnordost.osmfeatures}/FeatureTermIndexTest.kt (94%) rename library/src/commonTest/kotlin/{osmfeatures => de.westnordost.osmfeatures}/IDBrandPresetsFeatureCollectionTest.kt (95%) rename library/src/commonTest/kotlin/{osmfeatures => de.westnordost.osmfeatures}/IDLocalizedFeatureCollectionTest.kt (98%) rename library/src/commonTest/kotlin/{osmfeatures => de.westnordost.osmfeatures}/IDPresetsJsonParserTest.kt (89%) rename library/src/commonTest/kotlin/{osmfeatures => de.westnordost.osmfeatures}/IDPresetsTranslationJsonParserTest.kt (91%) rename library/src/commonTest/kotlin/{osmfeatures => de.westnordost.osmfeatures}/JsonUtilsTest.kt (94%) rename library/src/commonTest/kotlin/{osmfeatures => de.westnordost.osmfeatures}/LivePresetDataAccessAdapter.kt (92%) rename library/src/commonTest/kotlin/{osmfeatures => de.westnordost.osmfeatures}/StartsWithStringTreeTest.kt (92%) rename library/src/commonTest/kotlin/{osmfeatures => de.westnordost.osmfeatures}/TestLocalizedFeatureCollection.kt (94%) rename library/src/commonTest/kotlin/{osmfeatures => de.westnordost.osmfeatures}/TestPerCountryFeatureCollection.kt (96%) rename library/src/commonTest/kotlin/{osmfeatures => de.westnordost.osmfeatures}/TestUtils.kt (61%) delete mode 100644 library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt delete mode 100644 library/src/commonTest/kotlin/osmfeatures/MapEntry.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 846b5eb..49b8f10 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,5 @@ [versions] agp = "8.2.1" -fluid-locale = "0.13.0" junit = "4.13.2" kotlin = "1.9.22" kotlinx-serialization-json = "1.6.0" @@ -8,7 +7,6 @@ normalize = "1.0.5" okio = "3.6.0" [libraries] -fluid-locale = { module = "io.fluidsonic.locale:fluid-locale", version.ref = "fluid-locale" } junit = { module = "junit:junit", version.ref = "junit" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index ff95639..82ab57d 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -1,4 +1,3 @@ - plugins { alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.android.library) @@ -18,7 +17,6 @@ kotlin { commonMain.dependencies { //noinspection UseTomlInstead implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") - implementation(libs.fluid.locale) implementation(libs.okio) implementation(libs.kotlinx.serialization.json) implementation(libs.normalize) diff --git a/library/src/commonMain/kotlin/osmfeatures/BaseFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt similarity index 98% rename from library/src/commonMain/kotlin/osmfeatures/BaseFeature.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt index 80a88a8..7552e43 100644 --- a/library/src/commonMain/kotlin/osmfeatures/BaseFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import osmfeatures.Locale - /** Data class associated with the Feature interface. Represents a non-localized feature. */ open class BaseFeature( override val id: String, diff --git a/library/src/commonMain/kotlin/osmfeatures/CollectionUtils.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt similarity index 100% rename from library/src/commonMain/kotlin/osmfeatures/CollectionUtils.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt diff --git a/library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt similarity index 97% rename from library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt index 5ee4c39..366fb18 100644 --- a/library/src/commonMain/kotlin/osmfeatures/ContainedMapTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.CollectionUtils.mapContainsAllEntries - /** Index that makes finding which maps are completely contained by a given map very efficient. * It sorts the maps into a tree structure with configurable depth. * @@ -77,7 +75,7 @@ internal class ContainedMapTree } if (maps != null) { for (m in maps) { - if (mapContainsAllEntries(map, m.entries)) { + if (m.all { entry -> map[entry.key] == entry.value } ) { result.add(m) } } diff --git a/library/src/commonMain/kotlin/osmfeatures/Feature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt similarity index 95% rename from library/src/commonMain/kotlin/osmfeatures/Feature.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt index b43604c..6e41997 100644 --- a/library/src/commonMain/kotlin/osmfeatures/Feature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import osmfeatures.Locale - interface Feature { val id: String val tags: Map diff --git a/library/src/commonMain/kotlin/osmfeatures/FeatureDictionnary.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionnary.kt similarity index 98% rename from library/src/commonMain/kotlin/osmfeatures/FeatureDictionnary.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionnary.kt index 90a9513..48c5043 100644 --- a/library/src/commonMain/kotlin/osmfeatures/FeatureDictionnary.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionnary.kt @@ -1,13 +1,6 @@ -package osmfeatures - -import de.westnordost.osmfeatures.CollectionUtils -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.GeometryType -import de.westnordost.osmfeatures.IDBrandPresetsFeatureCollection -import de.westnordost.osmfeatures.IDLocalizedFeatureCollection -import osmfeatures.Locale.Companion.default -import de.westnordost.osmfeatures.PerCountryFeatureCollection -import de.westnordost.osmfeatures.StringUtils +package de.westnordost.osmfeatures + +import de.westnordost.osmfeatures.Locale.Companion.default import kotlin.math.min import kotlin.text.Regex diff --git a/library/src/commonMain/kotlin/osmfeatures/FeatureTagsIndex.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt similarity index 89% rename from library/src/commonMain/kotlin/osmfeatures/FeatureTagsIndex.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt index d11af3b..2e549cb 100644 --- a/library/src/commonMain/kotlin/osmfeatures/FeatureTagsIndex.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt @@ -1,7 +1,4 @@ -package osmfeatures - -import de.westnordost.osmfeatures.ContainedMapTree -import de.westnordost.osmfeatures.Feature +package de.westnordost.osmfeatures /** Index that makes finding Features whose tags are completely contained by a given set of tags * very efficient. diff --git a/library/src/commonMain/kotlin/osmfeatures/FeatureTermIndex.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt similarity index 91% rename from library/src/commonMain/kotlin/osmfeatures/FeatureTermIndex.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt index 4ef0405..bed76b3 100644 --- a/library/src/commonMain/kotlin/osmfeatures/FeatureTermIndex.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt @@ -1,7 +1,4 @@ -package osmfeatures - -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.StartsWithStringTree +package de.westnordost.osmfeatures /** Index that makes finding Features whose name/term/... starts with a given string very efficient. * diff --git a/library/src/commonMain/kotlin/osmfeatures/FileAccessAdapter.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileAccessAdapter.kt similarity index 85% rename from library/src/commonMain/kotlin/osmfeatures/FileAccessAdapter.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileAccessAdapter.kt index bc037d8..83837e4 100644 --- a/library/src/commonMain/kotlin/osmfeatures/FileAccessAdapter.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileAccessAdapter.kt @@ -1,4 +1,4 @@ -package osmfeatures +package de.westnordost.osmfeatures import okio.IOException import okio.Source diff --git a/library/src/commonMain/kotlin/osmfeatures/FileSystemAccess.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt similarity index 93% rename from library/src/commonMain/kotlin/osmfeatures/FileSystemAccess.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt index 129cac8..88a1558 100644 --- a/library/src/commonMain/kotlin/osmfeatures/FileSystemAccess.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt @@ -1,4 +1,4 @@ -package osmfeatures +package de.westnordost.osmfeatures import okio.FileSystem import okio.Path.Companion.toPath import okio.Source diff --git a/library/src/commonMain/kotlin/osmfeatures/GeometryType.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/GeometryType.kt similarity index 100% rename from library/src/commonMain/kotlin/osmfeatures/GeometryType.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/GeometryType.kt diff --git a/library/src/commonMain/kotlin/osmfeatures/IDBrandPresetsFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt similarity index 98% rename from library/src/commonMain/kotlin/osmfeatures/IDBrandPresetsFeatureCollection.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt index d3ba23c..f210d70 100644 --- a/library/src/commonMain/kotlin/osmfeatures/IDBrandPresetsFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import osmfeatures.FileAccessAdapter - /** Non-localized feature collection sourcing from (NSI) iD presets defined in JSON. * * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that diff --git a/library/src/commonMain/kotlin/osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt similarity index 97% rename from library/src/commonMain/kotlin/osmfeatures/IDLocalizedFeatureCollection.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt index 7c01983..e6b52f6 100644 --- a/library/src/commonMain/kotlin/osmfeatures/IDLocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt @@ -1,9 +1,5 @@ package de.westnordost.osmfeatures -import osmfeatures.FileAccessAdapter -import osmfeatures.Locale -import osmfeatures.LocalizedFeatureCollection - /** Localized feature collection sourcing from iD presets defined in JSON. * * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that diff --git a/library/src/commonMain/kotlin/osmfeatures/IDPresetsJsonParser.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParser.kt similarity index 100% rename from library/src/commonMain/kotlin/osmfeatures/IDPresetsJsonParser.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParser.kt diff --git a/library/src/commonMain/kotlin/osmfeatures/IDPresetsTranslationJsonParser.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt similarity index 99% rename from library/src/commonMain/kotlin/osmfeatures/IDPresetsTranslationJsonParser.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt index 7ffeff5..99ad27a 100644 --- a/library/src/commonMain/kotlin/osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt @@ -5,7 +5,6 @@ import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import okio.Source -import osmfeatures.Locale /** Parses a file from * https://github.com/openstreetmap/id-tagging-schema/tree/main/dist/translations diff --git a/library/src/commonMain/kotlin/osmfeatures/JsonUtils.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/JsonUtils.kt similarity index 100% rename from library/src/commonMain/kotlin/osmfeatures/JsonUtils.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/JsonUtils.kt diff --git a/library/src/commonMain/kotlin/osmfeatures/Locale.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt similarity index 91% rename from library/src/commonMain/kotlin/osmfeatures/Locale.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt index 8617342..d3216da 100644 --- a/library/src/commonMain/kotlin/osmfeatures/Locale.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt @@ -1,6 +1,4 @@ -package osmfeatures - -import io.fluidsonic.locale.LanguageTag +package de.westnordost.osmfeatures data class Locale( @@ -38,7 +36,11 @@ data class Locale( get() = this.region.orEmpty() val languageTag : String? by lazy { - LanguageTag.forLanguage(language, script, region).toString() + when { + region == null -> "${language}-${script}" + script == null -> "${language}-${region}" + else -> "${language}-${script}-${region}" + } } constructor(lang: String) : this(lang,"", "") diff --git a/library/src/commonMain/kotlin/osmfeatures/LocalizedFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt similarity index 97% rename from library/src/commonMain/kotlin/osmfeatures/LocalizedFeature.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt index c02cf0f..c50e647 100644 --- a/library/src/commonMain/kotlin/osmfeatures/LocalizedFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt @@ -1,6 +1,6 @@ package de.westnordost.osmfeatures -import osmfeatures.Locale +import de.westnordost.osmfeatures.Locale /** Data class associated with the Feature interface. Represents a localized feature. * diff --git a/library/src/commonMain/kotlin/osmfeatures/LocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt similarity index 92% rename from library/src/commonMain/kotlin/osmfeatures/LocalizedFeatureCollection.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt index f602585..adbb98e 100644 --- a/library/src/commonMain/kotlin/osmfeatures/LocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt @@ -1,4 +1,4 @@ -package osmfeatures +package de.westnordost.osmfeatures import de.westnordost.osmfeatures.Feature diff --git a/library/src/commonMain/kotlin/osmfeatures/PerCountryFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/PerCountryFeatureCollection.kt similarity index 100% rename from library/src/commonMain/kotlin/osmfeatures/PerCountryFeatureCollection.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/PerCountryFeatureCollection.kt diff --git a/library/src/commonMain/kotlin/osmfeatures/StartsWithStringTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt similarity index 100% rename from library/src/commonMain/kotlin/osmfeatures/StartsWithStringTree.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt diff --git a/library/src/commonMain/kotlin/osmfeatures/StringUtils.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt similarity index 83% rename from library/src/commonMain/kotlin/osmfeatures/StringUtils.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt index f85430e..7c57d82 100644 --- a/library/src/commonMain/kotlin/osmfeatures/StringUtils.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt @@ -6,7 +6,7 @@ import doist.x.normalize.normalize class StringUtils { companion object { - val FIND_DIACRITICS = "\\p{InCombiningDiacriticalMarks}+".toRegex() + private val FIND_DIACRITICS = "\\p{InCombiningDiacriticalMarks}+".toRegex() @JvmStatic fun canonicalize(str: String): String { diff --git a/library/src/commonTest/kotlin/osmfeatures/CollectionUtilsTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/CollectionUtilsTest.kt similarity index 62% rename from library/src/commonTest/kotlin/osmfeatures/CollectionUtilsTest.kt rename to library/src/commonTest/kotlin/de.westnordost.osmfeatures/CollectionUtilsTest.kt index c1a7f0a..ce40508 100644 --- a/library/src/commonTest/kotlin/osmfeatures/CollectionUtilsTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/CollectionUtilsTest.kt @@ -1,8 +1,5 @@ -package osmfeatures +package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.CollectionUtils -import osmfeatures.MapEntry.Companion.tag -import osmfeatures.MapEntry.Companion.mapOf import org.junit.Test import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -11,10 +8,10 @@ import org.junit.Assert.assertTrue class CollectionUtilsTest { @Test fun mapContainsEntry() { - val ab = mapOf(tag("a", "b")).entries.iterator().next() - val cd = mapOf(tag("c", "d")).entries.iterator().next() - val ef = mapOf(tag("e", "f")).entries.iterator().next() - val map = mapOf(tag("a", "b"), tag("c", "d")) + val ab = mapOf("a" to "b").entries.iterator().next() + val cd = mapOf("c" to "d").entries.iterator().next() + val ef = mapOf("e" to "f").entries.iterator().next() + val map = mapOf("a" to "b", "c" to "d") assertTrue(CollectionUtils.mapContainsEntry(map, ab)) assertTrue(CollectionUtils.mapContainsEntry(map, cd)) assertFalse(CollectionUtils.mapContainsEntry(map, ef)) @@ -22,9 +19,9 @@ class CollectionUtilsTest { @Test fun numberOfContainedEntriesInMap() { - val ab = mapOf(tag("a", "b")) - val abcdef = mapOf(tag("a", "b"), tag("c", "d"), tag("e", "f")) - val map = mapOf(tag("a", "b"), tag("c", "d")) + val ab = mapOf("a" to "b") + val abcdef = mapOf("a" to "b", "c" to "d", "e" to "f") + val map = mapOf("a" to "b", "c" to "d") assertEquals(0, CollectionUtils.numberOfContainedEntriesInMap(map, emptyMap().entries)) assertEquals(1, CollectionUtils.numberOfContainedEntriesInMap(map, ab.entries)) assertEquals(2, CollectionUtils.numberOfContainedEntriesInMap(map, map.entries)) @@ -33,9 +30,9 @@ class CollectionUtilsTest { @Test fun mapContainsAllEntries() { - val ab = mapOf(tag("a", "b")) - val abcdef = mapOf(tag("a", "b"), tag("c", "d"), tag("e", "f")) - val map = mapOf(tag("a", "b"), tag("c", "d")) + val ab = mapOf("a" to "b") + val abcdef = mapOf("a" to "b", "c" to "d", "e" to "f") + val map = mapOf("a" to "b", "c" to "d") assertTrue(CollectionUtils.mapContainsAllEntries(map, emptyMap().entries)) assertTrue(CollectionUtils.mapContainsAllEntries(map, ab.entries)) assertTrue(CollectionUtils.mapContainsAllEntries(map, map.entries)) diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt new file mode 100644 index 0000000..4e961ee --- /dev/null +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt @@ -0,0 +1,53 @@ +package de.westnordost.osmfeatures + +import org.junit.Test +import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class ContainedMapTreeTest { + @Test + fun copes_with_empty_feature_collection() { + val t = tree(emptyList()) + assertTrue(t.getAll(mapOf("a" to "b")).isEmpty()) + } + + @Test + fun find_single_map() { + val f1: Map = mapOf("a" to "b") + val t: ContainedMapTree = tree(listOf(f1)) + assertEquals(listOf(f1), t.getAll(mapOf("a" to "b", "c" to "d"))) + } + + @Test + fun dont_find_single_map() { + val tree: ContainedMapTree = tree(listOf(mapOf("a" to "b"))) + assertTrue(tree.getAll(mapOf()).isEmpty()) + assertTrue(tree.getAll(mapOf("c" to "d")).isEmpty()) + assertTrue(tree.getAll(mapOf("a" to "c")).isEmpty()) + } + + @Test + fun find_only_generic_map() { + val f1: Map = mapOf("a" to "b") + val f2: Map = mapOf("a" to "b", "c" to "d") + val tree: ContainedMapTree = tree(listOf(f1, f2)) + assertEquals(listOf(f1), tree.getAll(mapOf("a" to "b"))) + } + + @Test + fun find_map_with_one_match_and_with_several_matches() { + val f1: Map = mapOf("a" to "b") + val f2: Map = mapOf("a" to "b", "c" to "d") + val f3: Map = mapOf("a" to "b", "c" to "e") + val f4: Map = mapOf("a" to "b", "d" to "d") + val tree: ContainedMapTree = tree(listOf(f1, f2, f3, f4)) + assertEqualsIgnoreOrder(listOf(f1, f2), tree.getAll(mapOf("a" to "b", "c" to "d"))) + } + + companion object { + private fun tree(items: Collection>): ContainedMapTree { + return ContainedMapTree(items) + } + } +} diff --git a/library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt similarity index 88% rename from library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt rename to library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt index 94b444a..bc99e33 100644 --- a/library/src/commonTest/kotlin/osmfeatures/FeatureDictionaryTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt @@ -1,21 +1,20 @@ -package osmfeatures +package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.BaseFeature -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.GeometryType -import de.westnordost.osmfeatures.IDLocalizedFeatureCollection -import de.westnordost.osmfeatures.LocalizedFeature import org.junit.Test -import osmfeatures.TestUtils.assertEqualsIgnoreOrder +import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder import org.junit.Assert.* -import osmfeatures.MapEntry.Companion.tag -import osmfeatures.MapEntry.Companion.mapOf +import de.westnordost.osmfeatures.FeatureDictionary +import de.westnordost.osmfeatures.LivePresetDataAccessAdapter +import de.westnordost.osmfeatures.Locale +import de.westnordost.osmfeatures.LocalizedFeatureCollection +import de.westnordost.osmfeatures.TestLocalizedFeatureCollection +import de.westnordost.osmfeatures.TestPerCountryFeatureCollection import java.util.Collections class FeatureDictionaryTest { private val bakery: Feature = feature( // unlocalized shop=bakery "shop/bakery", - mapOf(tag("shop", "bakery")), + mapOf("shop" to "bakery"), POINT, listOf("Bäckerei"), listOf("Brot"), @@ -29,7 +28,7 @@ class FeatureDictionaryTest { ) private val panetteria: Feature = feature( // localized shop=bakery "shop/bakery", - mapOf(tag("shop", "bakery")), + mapOf("shop" to "bakery"), POINT, listOf("Panetteria"), listOf(), @@ -43,7 +42,7 @@ class FeatureDictionaryTest { ) private val ditsch: Feature = feature( // brand in DE for shop=bakery "shop/bakery/Ditsch", - mapOf(tag("shop", "bakery"), tag("name", "Ditsch")), + mapOf("shop" to "bakery", "name" to "Ditsch"), POINT, listOf("Ditsch"), listOf(), @@ -51,13 +50,13 @@ class FeatureDictionaryTest { listOf("AT-9"), true, 1.0f, - mapOf(tag("wikipedia", "de:Brezelb%C3%A4ckerei_Ditsch"), tag("brand", "Ditsch")), + mapOf("wikipedia" to "de:Brezelb%C3%A4ckerei_Ditsch", "brand" to "Ditsch"), true, null ) private val ditschRussian: Feature = feature( // brand in RU for shop=bakery "shop/bakery/Дитсч", - mapOf(tag("shop", "bakery"), tag("name", "Ditsch")), + mapOf("shop" to "bakery", "name" to "Ditsch"), POINT, listOf("Дитсч"), listOf(), @@ -65,13 +64,13 @@ class FeatureDictionaryTest { listOf(), true, 1.0f, - mapOf(tag("wikipedia", "de:Brezelb%C3%A4ckerei_Ditsch"), tag("brand", "Дитсч")), + mapOf("wikipedia" to "de:Brezelb%C3%A4ckerei_Ditsch", "brand" to "Дитсч"), true, null ) private val ditschInternational: Feature = feature( // brand everywhere for shop=bakery "shop/bakery/Ditsh", - mapOf(tag("shop", "bakery"), tag("name", "Ditsch")), + mapOf("shop" to "bakery", "name" to "Ditsch"), POINT, listOf("Ditsh"), listOf(), @@ -79,13 +78,13 @@ class FeatureDictionaryTest { listOf(), true, 1.0f, - mapOf(tag("wikipedia", "de:Brezelb%C3%A4ckerei_Ditsch")), + mapOf("wikipedia" to "de:Brezelb%C3%A4ckerei_Ditsch"), true, null ) private val liquor_store: Feature = feature( // English localized unspecific shop=alcohol "shop/alcohol", - mapOf(tag("shop", "alcohol")), + mapOf("shop" to "alcohol"), POINT, listOf("Off licence (Alcohol shop)"), listOf(), @@ -99,7 +98,7 @@ class FeatureDictionaryTest { ) private val car_dealer: Feature = feature( // German localized unspecific shop=car "shop/car", - mapOf(tag("shop", "car")), + mapOf("shop" to "car"), POINT, listOf("Autohändler"), listOf("auto"), @@ -113,7 +112,7 @@ class FeatureDictionaryTest { ) private val second_hand_car_dealer: Feature = feature( // German localized shop=car with subtags "shop/car/second_hand", - mapOf(tag("shop", "car"), tag("second_hand", "only")), + mapOf("shop" to "car", "second_hand" to "only"), POINT, listOf("Gebrauchtwagenhändler"), listOf("auto"), @@ -127,7 +126,7 @@ class FeatureDictionaryTest { ) private val scheisshaus: Feature = feature( // unsearchable feature "amenity/scheißhaus", - mapOf(tag("amenity", "scheißhaus")), + mapOf("amenity" to "scheißhaus"), POINT, listOf("Scheißhaus"), listOf(), @@ -141,7 +140,7 @@ class FeatureDictionaryTest { ) private val bank: Feature = feature( // unlocalized shop=bank (Bank) "amenity/bank", - mapOf(tag("amenity", "bank")), + mapOf("amenity" to "bank"), POINT, listOf("Bank"), listOf(), @@ -155,7 +154,7 @@ class FeatureDictionaryTest { ) private val bench: Feature = feature( // unlocalized amenity=bench (PARKbank) "amenity/bench", - mapOf(tag("amenity", "bench")), + mapOf("amenity" to "bench"), POINT, listOf("Parkbank"), listOf("Bank"), @@ -169,7 +168,7 @@ class FeatureDictionaryTest { ) private val casino: Feature = feature( // unlocalized amenity=casino (SPIELbank) "amenity/casino", - mapOf(tag("amenity", "casino")), + mapOf("amenity" to "casino"), POINT, listOf("Spielbank"), listOf("Kasino"), @@ -183,7 +182,7 @@ class FeatureDictionaryTest { ) private val atm: Feature = feature( // unlocalized amenity=atm (BankOMAT) "amenity/atm", - mapOf(tag("amenity", "atm")), + mapOf("amenity" to "atm"), POINT, listOf("Bankomat"), listOf(), @@ -198,7 +197,7 @@ class FeatureDictionaryTest { private val stock_exchange: Feature = feature( // unlocalized amenity=stock_exchange (has "Banking" as term) "amenity/stock_exchange", - mapOf(tag("amenity", "stock_exchange")), + mapOf("amenity" to "stock_exchange"), POINT, listOf("Börse"), listOf("Banking"), @@ -212,7 +211,7 @@ class FeatureDictionaryTest { ) private val bank_of_america: Feature = feature( // Brand of a amenity=bank (has "Bank" in name) "amenity/bank/Bank of America", - mapOf(tag("amenity", "bank"), tag("name", "Bank of America")), + mapOf("amenity" to "bank", "name" to "Bank of America"), POINT, listOf("Bank of America"), listOf(), @@ -227,7 +226,7 @@ class FeatureDictionaryTest { private val bank_of_liechtenstein: Feature = feature( // Brand of a amenity=bank (has "Bank" in name), but low matchScore "amenity/bank/Bank of Liechtenstein", - mapOf(tag("amenity", "bank"), tag("name", "Bank of Liechtenstein")), + mapOf("amenity" to "bank", "name" to "Bank of Liechtenstein"), POINT, listOf("Bank of Liechtenstein"), listOf(), @@ -242,7 +241,7 @@ class FeatureDictionaryTest { private val deutsche_bank: Feature = feature( // Brand of a amenity=bank (does not start with "Bank" in name) "amenity/bank/Deutsche Bank", - mapOf(tag("amenity", "bank"), tag("name", "Deutsche Bank")), + mapOf("amenity" to "bank", "name" to "Deutsche Bank"), POINT, listOf("Deutsche Bank"), listOf(), @@ -257,7 +256,7 @@ class FeatureDictionaryTest { private val baenk: Feature = feature( // amenity=bänk, to see if diacritics match non-strictly ("a" finds "ä") "amenity/bänk", - mapOf(tag("amenity", "bänk")), + mapOf("amenity" to "bänk"), POINT, listOf("Bänk"), listOf(), @@ -272,7 +271,7 @@ class FeatureDictionaryTest { private val bad_bank: Feature = feature( // amenity=bank with subtags that has "Bank" in name but it is not the first word "amenity/bank/bad", - mapOf(tag("amenity", "bank"), tag("goodity", "bad")), + mapOf("amenity" to "bank", "goodity" to "bad"), POINT, listOf("Bad Bank"), listOf(), @@ -286,7 +285,7 @@ class FeatureDictionaryTest { ) private val thieves_guild: Feature = feature( // only has "bank" in an alias "amenity/thieves_guild", - mapOf(tag("amenity", "thieves_guild")), + mapOf("amenity" to "thieves_guild"), POINT, listOf("Diebesgilde", "Bankräuberausbildungszentrum"), listOf(), @@ -301,7 +300,7 @@ class FeatureDictionaryTest { private val miniature_train_shop: Feature = feature( // feature whose name consists of several words "shop/miniature_train", - mapOf(tag("shop", "miniature_train")), + mapOf("shop" to "miniature_train"), POINT, listOf("Miniature Train Shop"), listOf(), @@ -317,28 +316,28 @@ class FeatureDictionaryTest { //region by tags @Test fun find_no_entry_by_tags() { - val tags: Map = mapOf(tag("shop", "supermarket")) + val tags: Map = mapOf("shop" to "supermarket") val dictionary: FeatureDictionary = dictionary(bakery) assertEquals(emptyList(), dictionary.byTags(tags).find()) } @Test fun find_no_entry_because_wrong_geometry() { - val tags: Map = mapOf(tag("shop", "bakery")) + val tags: Map = mapOf("shop" to "bakery") val dictionary: FeatureDictionary = dictionary(bakery) assertEquals(emptyList(), dictionary.byTags(tags).forGeometry(GeometryType.RELATION).find()) } @Test fun find_no_entry_because_wrong_locale() { - val tags: Map = mapOf(tag("shop", "bakery")) + val tags: Map = mapOf("shop" to "bakery") val dictionary: FeatureDictionary = dictionary(bakery) assertEquals(emptyList(), dictionary.byTags(tags).forLocale(Locale.ITALIAN).find()) } @Test fun find_entry_because_fallback_locale() { - val tags: Map = mapOf(tag("shop", "bakery")) + val tags: Map = mapOf("shop" to "bakery") val dictionary: FeatureDictionary = dictionary(bakery) val matches: List = dictionary.byTags(tags).forLocale(Locale.ITALIAN, null).find() assertEquals(listOf(bakery), matches) @@ -346,7 +345,7 @@ class FeatureDictionaryTest { @Test fun find_entry_by_tags() { - val tags: Map = mapOf(tag("shop", "bakery")) + val tags: Map = mapOf("shop" to "bakery") val dictionary: FeatureDictionary = dictionary(bakery) val matches: List = dictionary.byTags(tags).find() assertEquals(listOf(bakery), matches) @@ -354,7 +353,7 @@ class FeatureDictionaryTest { @Test fun find_non_searchable_entry_by_tags() { - val tags: Map = mapOf(tag("amenity", "scheißhaus")) + val tags: Map = mapOf("amenity" to "scheißhaus") val dictionary: FeatureDictionary = dictionary(scheisshaus) val matches: List = dictionary.byTags(tags).find() assertEquals(listOf(scheisshaus), matches) @@ -362,7 +361,7 @@ class FeatureDictionaryTest { @Test fun find_entry_by_tags_correct_geometry() { - val tags: Map = mapOf(tag("shop", "bakery")) + val tags: Map = mapOf("shop" to "bakery") val dictionary: FeatureDictionary = dictionary(bakery) val matches: List = dictionary.byTags(tags).forGeometry(GeometryType.POINT).find() assertEquals(listOf(bakery), matches) @@ -370,7 +369,7 @@ class FeatureDictionaryTest { @Test fun find_brand_entry_by_tags() { - val tags: Map = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")) + val tags: Map = mapOf("shop" to "bakery", "name" to "Ditsch") val dictionary: FeatureDictionary = dictionary(bakery, ditsch) val matches: List = dictionary.byTags(tags).inCountry("DE").find() assertEquals(listOf(ditsch), matches) @@ -378,7 +377,7 @@ class FeatureDictionaryTest { @Test fun find_only_entries_with_given_locale() { - val tags: Map = mapOf(tag("shop", "bakery")) + val tags: Map = mapOf("shop" to "bakery") val dictionary: FeatureDictionary = dictionary(bakery, panetteria) assertEquals(listOf(panetteria), dictionary.byTags(tags).forLocale(Locale.ITALIAN).find()) assertEquals(emptyList(), dictionary.byTags(tags).forLocale(Locale.ENGLISH).find()) @@ -387,7 +386,7 @@ class FeatureDictionaryTest { @Test fun find_only_brands_finds_no_normal_entries() { - val tags: Map = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")) + val tags: Map = mapOf("shop" to "bakery", "name" to "Ditsch") val dictionary: FeatureDictionary = dictionary(bakery) val matches: List = dictionary.byTags(tags).isSuggestion(true).find() assertEquals(0, matches.size) @@ -395,7 +394,7 @@ class FeatureDictionaryTest { @Test fun find_no_brands_finds_only_normal_entries() { - val tags: Map = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")) + val tags: Map = mapOf("shop" to "bakery", "name" to "Ditsch") val dictionary: FeatureDictionary = dictionary(bakery, ditsch) val matches: List = dictionary.byTags(tags).isSuggestion(false).find() assertEquals(listOf(bakery), matches) @@ -403,7 +402,7 @@ class FeatureDictionaryTest { @Test fun find_multiple_brands_sorts_by_locale() { - val tags: Map = mapOf(tag("shop", "bakery"), tag("name", "Ditsch")) + val tags: Map = mapOf("shop" to "bakery", "name" to "Ditsch") val dictionary: FeatureDictionary = dictionary(ditschRussian, ditschInternational, ditsch) val matches: List = dictionary.byTags(tags).forLocale(null).find() assertEquals(ditschInternational, matches[0]) @@ -411,7 +410,7 @@ class FeatureDictionaryTest { @Test fun find_multiple_entries_by_tags() { - val tags: Map = mapOf(tag("shop", "bakery"), tag("amenity", "bank")) + val tags: Map = mapOf("shop" to "bakery", "amenity" to "bank") val dictionary: FeatureDictionary = dictionary(bakery, bank) val matches: List = dictionary.byTags(tags).find() assertEquals(2, matches.size) @@ -419,7 +418,7 @@ class FeatureDictionaryTest { @Test fun do_not_find_entry_with_too_specific_tags() { - val tags: Map = mapOf(tag("shop", "car")) + val tags: Map = mapOf("shop" to "car") val dictionary: FeatureDictionary = dictionary(car_dealer, second_hand_car_dealer) val matches: List = dictionary.byTags(tags).forLocale(Locale.GERMAN, null).find() assertEquals(listOf(car_dealer), matches) @@ -427,7 +426,7 @@ class FeatureDictionaryTest { @Test fun find_entry_with_specific_tags() { - val tags: Map = mapOf(tag("shop", "car"), tag("second_hand", "only")) + val tags: Map = mapOf("shop" to "car", "second_hand" to "only") val dictionary: FeatureDictionary = dictionary(car_dealer, second_hand_car_dealer) val matches: List = dictionary.byTags(tags).forLocale(Locale.GERMAN, null).find() assertEquals(listOf(second_hand_car_dealer), matches) @@ -713,13 +712,13 @@ class FeatureDictionaryTest { featureCollection.getAll(listOf(Locale.ENGLISH)) val dictionary = FeatureDictionary(featureCollection, null) val matches: List = dictionary - .byTags(mapOf(tag("amenity", "studio"))) + .byTags(mapOf("amenity" to "studio")) .forLocale(Locale.ENGLISH) .find() assertEquals(1, matches.size) assertEquals("Studio", matches[0].name) val matches2: List = dictionary - .byTags(mapOf(tag("amenity", "studio"), tag("studio", "audio"))) + .byTags(mapOf("amenity" to "studio", "studio" to "audio")) .forLocale(Locale.ENGLISH) .find() assertEquals(1, matches2.size) @@ -736,7 +735,7 @@ class FeatureDictionaryTest { fun issue19() { val lush: Feature = feature( "shop/cosmetics/lush-a08666", - mapOf(tag("brand:wikidata", "Q1585448"), tag("shop", "cosmetics")), + mapOf("brand:wikidata" to "Q1585448", "shop" to "cosmetics"), listOf(GeometryType.POINT, GeometryType.AREA), listOf("Lush"), listOf("lush"), @@ -745,17 +744,17 @@ class FeatureDictionaryTest { true, 2.0f, mapOf( - tag("brand", "Lush"), - tag("brand:wikidata", "Q1585448"), - tag("name", "Lush"), - tag("shop", "cosmetics") + "brand" to "Lush", + "brand:wikidata" to "Q1585448", + "name" to "Lush", + "shop" to "cosmetics" ), true, null ) val dictionary: FeatureDictionary = dictionary(lush) val byTags: List = dictionary - .byTags(mapOf(tag("brand:wikidata", "Q1585448"), tag("shop", "cosmetics"))) + .byTags(mapOf("brand:wikidata" to "Q1585448", "shop" to "cosmetics")) .forLocale(Locale.GERMAN, null) .inCountry("DE") .find() diff --git a/library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTagsIndexTest.kt similarity index 66% rename from library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt rename to library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTagsIndexTest.kt index 83e8616..353e743 100644 --- a/library/src/commonTest/kotlin/osmfeatures/FeatureTagsIndexTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTagsIndexTest.kt @@ -1,12 +1,9 @@ -package osmfeatures +package de.westnordost.osmfeatures import de.westnordost.osmfeatures.BaseFeature import de.westnordost.osmfeatures.Feature import de.westnordost.osmfeatures.GeometryType import org.junit.Test -import osmfeatures.MapEntry.Companion.tag -import osmfeatures.MapEntry.Companion.mapOf -import osmfeatures.TestUtils.listOf import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -14,28 +11,28 @@ class FeatureTagsIndexTest { @Test fun copes_with_empty_collection() { val index: FeatureTagsIndex = index() - assertTrue(index.getAll(mapOf(tag("a", "b"))).isEmpty()) + assertTrue(index.getAll(mapOf("a" to "b")).isEmpty()) } @Test fun get_two_features_with_same_tags() { - val f1: Feature = feature(tag("a", "b")) - val f2: Feature = feature(tag("a", "b")) + val f1: Feature = feature("a" to "b") + val f2: Feature = feature("a" to "b") val index: FeatureTagsIndex = index(f1, f2) assertEquals( listOf(f1, f2), - index.getAll(mapOf(tag("a", "b"), tag("c", "d"))) + index.getAll(mapOf("a" to "b", "c" to "d")) ) } @Test fun get_two_features_with_different_tags() { - val f1: Feature = feature(tag("a", "b")) - val f2: Feature = feature(tag("c", "d")) + val f1: Feature = feature("a" to "b") + val f2: Feature = feature("c" to "d") val index: FeatureTagsIndex = index(f1, f2) assertEquals( listOf(f1, f2), - index.getAll(mapOf(tag("a", "b"), tag("c", "d"))) + index.getAll(mapOf("a" to "b", "c" to "d")) ) } @@ -44,10 +41,10 @@ class FeatureTagsIndexTest { return FeatureTagsIndex(features.toList()) } - private fun feature(vararg mapEntries: MapEntry): Feature { + private fun feature(vararg pairs: Pair): Feature { return BaseFeature( "id", - mapOf(*mapEntries), + mapOf(*pairs), listOf(GeometryType.POINT), null, null, listOf("name"), diff --git a/library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt similarity index 94% rename from library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt rename to library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt index 99f934f..d1045d5 100644 --- a/library/src/commonTest/kotlin/osmfeatures/FeatureTermIndexTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt @@ -1,11 +1,10 @@ package de.westnordost.osmfeatures import org.junit.Test -import osmfeatures.TestUtils.assertEqualsIgnoreOrder -import osmfeatures.TestUtils.listOf +import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue -import osmfeatures.FeatureTermIndex +import de.westnordost.osmfeatures.FeatureTermIndex class FeatureTermIndexTest { @Test diff --git a/library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt similarity index 95% rename from library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt rename to library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt index e222a3b..ceb31d2 100644 --- a/library/src/commonTest/kotlin/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt @@ -1,4 +1,4 @@ -package osmfeatures +package de.westnordost.osmfeatures import de.westnordost.osmfeatures.Feature import de.westnordost.osmfeatures.IDBrandPresetsFeatureCollection @@ -6,8 +6,7 @@ import okio.FileSystem import okio.Path.Companion.toPath import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue -import osmfeatures.TestUtils.assertEqualsIgnoreOrder -import osmfeatures.TestUtils.listOf +import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder import okio.Source import org.junit.Test import java.io.IOException diff --git a/library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt similarity index 98% rename from library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt rename to library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt index 833860c..b699710 100644 --- a/library/src/commonTest/kotlin/osmfeatures/IDLocalizedFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt @@ -1,12 +1,11 @@ -package osmfeatures +package de.westnordost.osmfeatures import de.westnordost.osmfeatures.Feature import de.westnordost.osmfeatures.IDLocalizedFeatureCollection import okio.FileSystem import okio.Source import org.junit.Test -import osmfeatures.TestUtils.assertEqualsIgnoreOrder -import osmfeatures.TestUtils.listOf +import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder import okio.FileNotFoundException import okio.IOException import okio.Path.Companion.toPath diff --git a/library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt similarity index 89% rename from library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt rename to library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt index 39226c3..b858fe5 100644 --- a/library/src/commonTest/kotlin/osmfeatures/IDPresetsJsonParserTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt @@ -3,9 +3,6 @@ package de.westnordost.osmfeatures import okio.FileSystem import okio.Source import org.junit.Test -import osmfeatures.MapEntry.Companion.tag -import osmfeatures.MapEntry.Companion.mapOf -import osmfeatures.TestUtils.listOf import okio.IOException import okio.Path.Companion.toPath import okio.source @@ -21,7 +18,7 @@ class IDPresetsJsonParserTest { assertEquals(1, features.size) val feature: Feature = features[0] assertEquals("some/id", feature.id) - assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature.tags) + assertEquals(mapOf("a" to "b", "c" to "d"), feature.tags) assertEquals( listOf( GeometryType.POINT, @@ -40,8 +37,8 @@ class IDPresetsJsonParserTest { assertEquals(listOf("1", "2"), feature.terms) assertEquals(0.5f, feature.matchScore, 0.001f) assertFalse(feature.isSearchable) - assertEquals(mapOf(tag("e", "f")), feature.addTags) - assertEquals(mapOf(tag("d", "g")), feature.removeTags) + assertEquals(mapOf("e" to "f"), feature.addTags) + assertEquals(mapOf("d" to "g"), feature.removeTags) } @Test @@ -50,7 +47,7 @@ class IDPresetsJsonParserTest { assertEquals(1, features.size) val feature: Feature = features[0] assertEquals("some/id", feature.id) - assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature.tags) + assertEquals(mapOf("a" to "b", "c" to "d"), feature.tags) assertEquals(listOf(GeometryType.POINT), feature.geometry) assertTrue(feature.includeCountryCodes.isEmpty()) assertTrue(feature.excludeCountryCodes.isEmpty()) diff --git a/library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt similarity index 91% rename from library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt rename to library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt index 77e5e5b..152af93 100644 --- a/library/src/commonTest/kotlin/osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -1,4 +1,4 @@ -package osmfeatures +package de.westnordost.osmfeatures import de.westnordost.osmfeatures.BaseFeature import de.westnordost.osmfeatures.Feature @@ -13,9 +13,6 @@ import org.junit.Test import java.io.IOException import java.net.URISyntaxException import java.net.URL -import osmfeatures.MapEntry.Companion.tag -import osmfeatures.MapEntry.Companion.mapOf -import osmfeatures.TestUtils.listOf import okio.source import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -27,7 +24,7 @@ class IDPresetsTranslationJsonParserTest { assertEquals(1, features.size) val feature: Feature = features[0] assertEquals("some/id", feature.id) - assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature.tags) + assertEquals(mapOf("a" to "b", "c" to "d"), feature.tags) assertEquals(listOf(GeometryType.POINT), feature.geometry) assertEquals("bar", feature.name) assertEquals(listOf("bar", "one", "two", "three"), feature.names) @@ -41,7 +38,7 @@ class IDPresetsTranslationJsonParserTest { assertEquals(1, features.size) val feature: Feature = features[0] assertEquals("some/id", feature.id) - assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature.tags) + assertEquals(mapOf("a" to "b", "c" to "d"), feature.tags) assertEquals(listOf(GeometryType.POINT), feature.geometry) assertEquals("bar", feature.name) assertTrue(feature.terms.isEmpty()) @@ -55,7 +52,7 @@ class IDPresetsTranslationJsonParserTest { assertEquals(2, features.size) val feature: Feature? = featuresById["some/id-dingsdongs"] assertEquals("some/id-dingsdongs", feature?.id) - assertEquals(mapOf(tag("a", "b"), tag("c", "d")), feature?.tags) + assertEquals(mapOf("a" to "b", "c" to "d"), feature?.tags) assertEquals(listOf(GeometryType.POINT), feature?.geometry) assertEquals("bar", feature?.name) assertEquals(listOf("bar", "one", "two", "three"), feature?.names) diff --git a/library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/JsonUtilsTest.kt similarity index 94% rename from library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt rename to library/src/commonTest/kotlin/de.westnordost.osmfeatures/JsonUtilsTest.kt index 13bb1ca..a78a0fa 100644 --- a/library/src/commonTest/kotlin/osmfeatures/JsonUtilsTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/JsonUtilsTest.kt @@ -4,7 +4,6 @@ import kotlinx.serialization.json.JsonArray import org.junit.Test import de.westnordost.osmfeatures.JsonUtils.parseList import de.westnordost.osmfeatures.JsonUtils.parseStringMap -import osmfeatures.TestUtils.listOf import org.junit.Assert.assertEquals class JsonUtilsTest { diff --git a/library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt similarity index 92% rename from library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt rename to library/src/commonTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt index 2af7711..3ae83d2 100644 --- a/library/src/commonTest/kotlin/osmfeatures/LivePresetDataAccessAdapter.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt @@ -1,8 +1,7 @@ -package osmfeatures +package de.westnordost.osmfeatures import java.io.IOException import java.net.URL -import osmfeatures.TestUtils.listOf import okio.source class LivePresetDataAccessAdapter : FileAccessAdapter { diff --git a/library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt similarity index 92% rename from library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt rename to library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt index 0db7014..3dd9af4 100644 --- a/library/src/commonTest/kotlin/osmfeatures/StartsWithStringTreeTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt @@ -1,8 +1,7 @@ package de.westnordost.osmfeatures import org.junit.Test -import osmfeatures.TestUtils.assertEqualsIgnoreOrder -import osmfeatures.TestUtils.listOf +import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue diff --git a/library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt similarity index 94% rename from library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt rename to library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt index bda9656..7bb5a9d 100644 --- a/library/src/commonTest/kotlin/osmfeatures/TestLocalizedFeatureCollection.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt @@ -1,4 +1,4 @@ -package osmfeatures +package de.westnordost.osmfeatures import de.westnordost.osmfeatures.Feature diff --git a/library/src/commonTest/kotlin/osmfeatures/TestPerCountryFeatureCollection.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt similarity index 96% rename from library/src/commonTest/kotlin/osmfeatures/TestPerCountryFeatureCollection.kt rename to library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt index f3db432..4837f67 100644 --- a/library/src/commonTest/kotlin/osmfeatures/TestPerCountryFeatureCollection.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt @@ -1,4 +1,4 @@ -package osmfeatures +package de.westnordost.osmfeatures import de.westnordost.osmfeatures.Feature import de.westnordost.osmfeatures.PerCountryFeatureCollection diff --git a/library/src/commonTest/kotlin/osmfeatures/TestUtils.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt similarity index 61% rename from library/src/commonTest/kotlin/osmfeatures/TestUtils.kt rename to library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt index 9db6489..3869f46 100644 --- a/library/src/commonTest/kotlin/osmfeatures/TestUtils.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt @@ -1,4 +1,4 @@ -package osmfeatures +package de.westnordost.osmfeatures import org.junit.Assert.assertTrue @@ -6,9 +6,4 @@ object TestUtils { fun assertEqualsIgnoreOrder(a: Collection, b: Collection) { assertTrue(a.size == b.size && a.containsAll(b)) } - - @SafeVarargs - fun listOf(vararg items: T): List { - return items.asList() - } } diff --git a/library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt b/library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt deleted file mode 100644 index cb6778e..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/ContainedMapTreeTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -package osmfeatures - -import de.westnordost.osmfeatures.ContainedMapTree -import org.junit.Test -import osmfeatures.TestUtils.assertEqualsIgnoreOrder -import osmfeatures.TestUtils.listOf -import osmfeatures.MapEntry.Companion.tag -import osmfeatures.MapEntry.Companion.mapOf -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class ContainedMapTreeTest { - @Test - fun copes_with_empty_feature_collection() { - val t = tree(emptyList>()) - assertTrue(t.getAll(mapOf(tag("a", "b"))).isEmpty()) - } - - @Test - fun find_single_map() { - val f1: Map = mapOf(tag("a", "b")) - val t: ContainedMapTree = tree(listOf(f1)) - assertEquals(listOf(f1), t.getAll(mapOf(tag("a", "b"), tag("c", "d")))) - } - - @Test - fun dont_find_single_map() { - val tree: ContainedMapTree = tree(listOf(mapOf(tag("a", "b")))) - assertTrue(tree.getAll(mapOf()).isEmpty()) - assertTrue(tree.getAll(mapOf(tag("c", "d"))).isEmpty()) - assertTrue(tree.getAll(mapOf(tag("a", "c"))).isEmpty()) - } - - @Test - fun find_only_generic_map() { - val f1: Map = mapOf(tag("a", "b")) - val f2: Map = mapOf(tag("a", "b"), tag("c", "d")) - val tree: ContainedMapTree = tree(listOf(f1, f2)) - assertEquals(listOf(f1), tree.getAll(mapOf(tag("a", "b")))) - } - - @Test - fun find_map_with_one_match_and_with_several_matches() { - val f1: Map = mapOf(tag("a", "b")) - val f2: Map = mapOf(tag("a", "b"), tag("c", "d")) - val f3: Map = mapOf(tag("a", "b"), tag("c", "e")) - val f4: Map = mapOf(tag("a", "b"), tag("d", "d")) - val tree: ContainedMapTree = tree(listOf(f1, f2, f3, f4)) - assertEqualsIgnoreOrder(listOf(f1, f2), tree.getAll(mapOf(tag("a", "b"), tag("c", "d")))) - } - - companion object { - private fun tree(items: Collection>): ContainedMapTree { - return ContainedMapTree(items) - } - } -} diff --git a/library/src/commonTest/kotlin/osmfeatures/MapEntry.kt b/library/src/commonTest/kotlin/osmfeatures/MapEntry.kt deleted file mode 100644 index d95a637..0000000 --- a/library/src/commonTest/kotlin/osmfeatures/MapEntry.kt +++ /dev/null @@ -1,15 +0,0 @@ -package osmfeatures - -internal class MapEntry private constructor(private val key: String, private val value: String) { - companion object { - fun tag(key: String, value: String): MapEntry { - return MapEntry(key, value) - } - - fun mapOf(vararg items: MapEntry): Map { - val result = hashMapOf() - for (item in items) result[item.key] = item.value - return result - } - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index ee83769..5f47dd8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,5 +14,5 @@ dependencyResolutionManagement { } } -rootProject.name = "osmfeatures" +rootProject.name = "de.westnordost.osmfeatures" include(":library") From 55c4d61e37bc18ac2c4066cdfe8aa30b1f4ded30 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Tue, 20 Feb 2024 02:50:28 +0100 Subject: [PATCH 19/98] fixed getAll methods in tests --- gradle/libs.versions.toml | 3 +- library/build.gradle.kts | 7 ++-- .../CollectionUtils.kt | 29 +------------ .../ContainedMapTree.kt | 9 ++-- ...ureDictionnary.kt => FeatureDictionary.kt} | 34 +++++++-------- .../FeatureTagsIndex.kt | 4 +- .../IDLocalizedFeatureCollection.kt | 15 +++---- .../de.westnordost.osmfeatures/Locale.kt | 31 +++++++------- .../de.westnordost.osmfeatures/StringUtils.kt | 1 - .../CollectionUtilsTest.kt | 41 ------------------- .../ContainedMapTreeTest.kt | 2 +- .../FeatureDictionaryTest.kt | 15 +++---- .../FeatureTagsIndexTest.kt | 5 +-- .../FeatureTermIndexTest.kt | 7 ++-- .../IDBrandPresetsFeatureCollectionTest.kt | 23 +++-------- .../IDLocalizedFeatureCollectionTest.kt | 7 +--- .../IDPresetsJsonParserTest.kt | 8 ++-- .../IDPresetsTranslationJsonParserTest.kt | 14 ++----- .../JsonUtilsTest.kt | 4 +- .../LivePresetDataAccessAdapter.kt | 7 ++-- .../StartsWithStringTreeTest.kt | 7 ++-- .../TestLocalizedFeatureCollection.kt | 4 -- .../TestPerCountryFeatureCollection.kt | 21 ++++------ .../de.westnordost.osmfeatures/TestUtils.kt | 2 +- 24 files changed, 90 insertions(+), 210 deletions(-) rename library/src/commonMain/kotlin/de.westnordost.osmfeatures/{FeatureDictionnary.kt => FeatureDictionary.kt} (95%) delete mode 100644 library/src/commonTest/kotlin/de.westnordost.osmfeatures/CollectionUtilsTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 49b8f10..0c895a3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,18 +1,17 @@ [versions] agp = "8.2.1" -junit = "4.13.2" kotlin = "1.9.22" kotlinx-serialization-json = "1.6.0" normalize = "1.0.5" okio = "3.6.0" [libraries] -junit = { module = "junit:junit", version.ref = "junit" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } normalize = { module = "com.doist.x:normalize", version.ref = "normalize" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } + [plugins] android-library = { id = "com.android.library", version.ref = "agp" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } \ No newline at end of file diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 82ab57d..9c3c947 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -21,12 +21,11 @@ kotlin { implementation(libs.kotlinx.serialization.json) implementation(libs.normalize) } - val commonTest by getting { - dependencies { +// val commonTest by getting { + commonTest.dependencies { implementation(libs.kotlin.test) - implementation(libs.junit) } - } +// } } } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt index c7d92b6..dec00aa 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt @@ -3,7 +3,7 @@ package de.westnordost.osmfeatures object CollectionUtils { /** For the given map, get the value of the entry at the given key and if there is no * entry yet, create it using the given create function thread-safely */ - fun synchronizedGetOrCreate(map: MutableMap, key: K, createFn: (K) -> V): V? { + fun synchronizedGetOrCreate(map: MutableMap, key: K, createFn: (K) -> V): V? { synchronized(map) { if (!map.containsKey(key)) { map[key] = createFn(key) @@ -11,31 +11,4 @@ object CollectionUtils { } return map[key] } - - @JvmStatic - /** Whether the given map contains all the given entries */ - fun mapContainsAllEntries(map: Map, entries: Iterable>): Boolean { - for (entry in entries) { - if (!mapContainsEntry(map, entry)) return false - } - return true - } - - @JvmStatic - /** Number of entries contained in the given map */ - fun numberOfContainedEntriesInMap(map: Map, entries: Iterable>): Int { - var found = 0 - for (entry in entries) { - if (mapContainsEntry(map, entry)) found++ - } - return found - } - - @JvmStatic - /** Whether the given map contains the given entry */ - fun mapContainsEntry(map: Map, entry: Map.Entry): Boolean { - val mapValue = map[entry.key] - val value = entry.value - return value == mapValue - } } \ No newline at end of file diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt index 366fb18..781f1fe 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt @@ -33,8 +33,7 @@ package de.westnordost.osmfeatures * ... * */ -internal class ContainedMapTree -@JvmOverloads constructor(maps: Collection>, maxDepth: Int = 4, minContainerSize: Int = 4) { +internal class ContainedMapTree(maps: Collection>, maxDepth: Int = 4, minContainerSize: Int = 4) { private val root: Node /** Create this index with the given maps. @@ -107,7 +106,7 @@ internal class ContainedMapTree (mapsForKey as MutableList).retainAll(unsortedMaps) if (mapsForKey.isEmpty()) continue - val featuresByValue: Map>> = getMapsByKeyValue(key, mapsForKey) + val featuresByValue: Map>> = getMapsByKeyValue(key, mapsForKey) val valueNodes: MutableMap> = HashMap(featuresByValue.size) for ((value, featuresForValue) in featuresByValue) { @@ -127,7 +126,7 @@ internal class ContainedMapTree } /** returns the given features grouped by the map entry value of the given key. */ - private fun getMapsByKeyValue(key: K, maps: Collection>): Map>> { + private fun getMapsByKeyValue(key: K, maps: Collection>): Map>> { val result = HashMap>>() for (map in maps) { val value = map[key] @@ -143,7 +142,7 @@ internal class ContainedMapTree private fun getMapsByKey( maps: Collection>, excludeKeys: Collection - ): Map>> { + ): Map>> { val result = HashMap>>() for (map in maps) { for (key in map.keys.filter { !excludeKeys.contains(it) }) { diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionnary.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt similarity index 95% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionnary.kt rename to library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt index 48c5043..dd23e09 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionnary.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt @@ -8,12 +8,12 @@ class FeatureDictionary internal constructor( private val featureCollection: LocalizedFeatureCollection, private val brandFeatureCollection: PerCountryFeatureCollection? ) { - private val brandNamesIndexes: Map, FeatureTermIndex> = HashMap() - private val brandTagsIndexes: Map, FeatureTagsIndex> = HashMap() - private val tagsIndexes: Map, FeatureTagsIndex> = HashMap() - private val namesIndexes: Map, FeatureTermIndex> = HashMap() - private val termsIndexes: Map, FeatureTermIndex> = HashMap() - private val tagValuesIndexes: Map, FeatureTermIndex> = HashMap() + private val brandNamesIndexes: MutableMap, FeatureTermIndex> = HashMap() + private val brandTagsIndexes: MutableMap, FeatureTagsIndex> = HashMap() + private val tagsIndexes: MutableMap, FeatureTagsIndex> = HashMap() + private val namesIndexes: MutableMap, FeatureTermIndex> = HashMap() + private val termsIndexes: MutableMap, FeatureTermIndex> = HashMap() + private val tagValuesIndexes: MutableMap, FeatureTermIndex> = HashMap() init { // build indices for default locale @@ -102,14 +102,8 @@ class FeatureDictionary internal constructor( // 3. features with more matching tags in addTags first // https://github.com/openstreetmap/iD/issues/7927 val numberOfMatchedAddTags = - (CollectionUtils.numberOfContainedEntriesInMap( - b.addTags, - tags.entries - ) - - CollectionUtils.numberOfContainedEntriesInMap( - a.addTags, - tags.entries - )) + (tags.entries.count { b.addTags.entries.contains(it) } + - tags.entries.count { a.addTags.entries.contains(it) }) if (numberOfMatchedAddTags != 0) return numberOfMatchedAddTags return (100 * b.matchScore - 100 * a.matchScore).toInt() } @@ -264,7 +258,7 @@ class FeatureDictionary internal constructor( /** lazily get or create tags index for given locale(s) */ private fun getTagsIndex(locales: List): FeatureTagsIndex? { return CollectionUtils.synchronizedGetOrCreate( - tagsIndexes.toMutableMap(), + tagsIndexes, locales, ::createTagsIndex ) @@ -277,7 +271,7 @@ class FeatureDictionary internal constructor( /** lazily get or create names index for given locale(s) */ private fun getNamesIndex(locales: List): FeatureTermIndex? { return CollectionUtils.synchronizedGetOrCreate( - namesIndexes.toMutableMap(), + namesIndexes, locales, ::createNamesIndex ) @@ -303,7 +297,7 @@ class FeatureDictionary internal constructor( /** lazily get or create terms index for given locale(s) */ private fun getTermsIndex(locales: List): FeatureTermIndex? { return CollectionUtils.synchronizedGetOrCreate( - termsIndexes.toMutableMap(), + termsIndexes, locales, ::createTermsIndex ) @@ -319,7 +313,7 @@ class FeatureDictionary internal constructor( /** lazily get or create tag values index */ private fun getTagValuesIndex(locales: List): FeatureTermIndex? { return CollectionUtils.synchronizedGetOrCreate( - tagValuesIndexes.toMutableMap(), + tagValuesIndexes, locales, ::createTagValuesIndex ) @@ -340,7 +334,7 @@ class FeatureDictionary internal constructor( /** lazily get or create brand names index for country */ private fun getBrandNamesIndex(countryCodes: List): FeatureTermIndex? { return CollectionUtils.synchronizedGetOrCreate( - brandNamesIndexes.toMutableMap(), + brandNamesIndexes, countryCodes, ::createBrandNamesIndex ) @@ -359,7 +353,7 @@ class FeatureDictionary internal constructor( /** lazily get or create tags index for the given countries */ private fun getBrandTagsIndex(countryCodes: List): FeatureTagsIndex? { return CollectionUtils.synchronizedGetOrCreate( - brandTagsIndexes.toMutableMap(), + brandTagsIndexes, countryCodes, ::createBrandTagsIndex ) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt index 2e549cb..f2bb45b 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt @@ -4,7 +4,7 @@ package de.westnordost.osmfeatures * very efficient. * * Based on ContainedMapTree data structure, see that class. */ -internal class FeatureTagsIndex(features: Collection) { +internal class FeatureTagsIndex(features: Iterable) { private val featureMap: MutableMap, MutableList> private val tree: ContainedMapTree @@ -13,7 +13,7 @@ internal class FeatureTagsIndex(features: Collection) { for (feature in features) { val map: Map = feature.tags if (!featureMap.containsKey(map)) featureMap[map] = ArrayList(1) - featureMap[map]!!.add(feature) + featureMap[map]?.add(feature) } tree = ContainedMapTree(featureMap.keys) } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt index e6b52f6..24a0357 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt @@ -9,14 +9,11 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : LocalizedFeatureCollection { private val featuresById: LinkedHashMap private val localizedFeaturesList: MutableMap> = HashMap() - private val localizedFeatures: MutableMap?, LinkedHashMap> = HashMap() + private val localizedFeatures: MutableMap, LinkedHashMap> = HashMap() init { val features = loadFeatures() - featuresById = LinkedHashMap(features.size) - for (feature in features) { - featuresById[feature.id] = feature - } + featuresById = LinkedHashMap(features.associateBy { it.id }) } private fun loadFeatures(): List { @@ -52,7 +49,7 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : private fun getOrLoadLocalizedFeaturesList(locale: Locale): List? { return CollectionUtils.synchronizedGetOrCreate( localizedFeaturesList, locale - ) { locale: Locale? -> + ) {locale: Locale? -> loadLocalizedFeaturesList( locale ) @@ -82,9 +79,9 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : /* we only want language+country+script of the locale, not anything else. So we construct it anew here */ return Locale.Builder() - .setLanguage(locale?.language ?: "") - .setRegion(locale?.country ?: "") - .setScript(locale?.script ?: "") + .setLanguage(locale?.language ?: Locale.ENGLISH.language) + .setRegion(locale?.region) + .setScript(locale?.script) .build() .languageTag + ".json" } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt index d3216da..be28467 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt @@ -3,29 +3,28 @@ package de.westnordost.osmfeatures data class Locale( val language: String, - private val region: String?, + val region: String?, val script: String?) { companion object { - @JvmField val ENGLISH: Locale = Locale("en") - @JvmField + val UK: Locale = Locale("en","UK") - @JvmField + val US: Locale = Locale("en","US") - @JvmField + val FRENCH: Locale = Locale("fr") - @JvmField + val ITALIAN: Locale = Locale("it") - @JvmField + val GERMAN: Locale = Locale("de") - @JvmField + val GERMANY: Locale = Locale("de", "DE") - @JvmField + val CHINESE: Locale = Locale("zh") - @JvmStatic + val default: Locale? = null } @@ -35,17 +34,18 @@ data class Locale( val country : String get() = this.region.orEmpty() - val languageTag : String? by lazy { + val languageTag : String by lazy { when { + region == null && script == null -> language region == null -> "${language}-${script}" script == null -> "${language}-${region}" else -> "${language}-${script}-${region}" } } - constructor(lang: String) : this(lang,"", "") + constructor(lang: String) : this(lang,null, null) - constructor(lang: String, region: String) : this(lang, region, "") + constructor(lang: String, region: String) : this(lang, region, null) @@ -64,7 +64,6 @@ data class Locale( var result = language.hashCode() result = 31 * result + (region?.hashCode() ?: 0) result = 31 * result + (script?.hashCode() ?: 0) - result = 31 * result + country.hashCode() return result } @@ -79,14 +78,14 @@ data class Locale( private var region: String? = null fun setRegion(region: String?) : Builder { - this.region = region.orEmpty() + this.region = region return this } private var script: String? = null fun setScript(script: String?) : Builder { - this.script = script.orEmpty() + this.script = script return this } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt index 7c57d82..d4dab4d 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt @@ -8,7 +8,6 @@ class StringUtils { companion object { private val FIND_DIACRITICS = "\\p{InCombiningDiacriticalMarks}+".toRegex() - @JvmStatic fun canonicalize(str: String): String { return stripDiacritics(str).lowercase() } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/CollectionUtilsTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/CollectionUtilsTest.kt deleted file mode 100644 index ce40508..0000000 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/CollectionUtilsTest.kt +++ /dev/null @@ -1,41 +0,0 @@ -package de.westnordost.osmfeatures - -import org.junit.Test -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue - -class CollectionUtilsTest { - @Test - fun mapContainsEntry() { - val ab = mapOf("a" to "b").entries.iterator().next() - val cd = mapOf("c" to "d").entries.iterator().next() - val ef = mapOf("e" to "f").entries.iterator().next() - val map = mapOf("a" to "b", "c" to "d") - assertTrue(CollectionUtils.mapContainsEntry(map, ab)) - assertTrue(CollectionUtils.mapContainsEntry(map, cd)) - assertFalse(CollectionUtils.mapContainsEntry(map, ef)) - } - - @Test - fun numberOfContainedEntriesInMap() { - val ab = mapOf("a" to "b") - val abcdef = mapOf("a" to "b", "c" to "d", "e" to "f") - val map = mapOf("a" to "b", "c" to "d") - assertEquals(0, CollectionUtils.numberOfContainedEntriesInMap(map, emptyMap().entries)) - assertEquals(1, CollectionUtils.numberOfContainedEntriesInMap(map, ab.entries)) - assertEquals(2, CollectionUtils.numberOfContainedEntriesInMap(map, map.entries)) - assertEquals(2, CollectionUtils.numberOfContainedEntriesInMap(map, abcdef.entries)) - } - - @Test - fun mapContainsAllEntries() { - val ab = mapOf("a" to "b") - val abcdef = mapOf("a" to "b", "c" to "d", "e" to "f") - val map = mapOf("a" to "b", "c" to "d") - assertTrue(CollectionUtils.mapContainsAllEntries(map, emptyMap().entries)) - assertTrue(CollectionUtils.mapContainsAllEntries(map, ab.entries)) - assertTrue(CollectionUtils.mapContainsAllEntries(map, map.entries)) - assertFalse(CollectionUtils.mapContainsAllEntries(map, abcdef.entries)) - } -} diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt index 4e961ee..8ded874 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt @@ -1,6 +1,6 @@ package de.westnordost.osmfeatures -import org.junit.Test +import kotlin.test.Test import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder import kotlin.test.assertEquals import kotlin.test.assertTrue diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt index bc99e33..82bd0bb 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt @@ -1,15 +1,10 @@ package de.westnordost.osmfeatures -import org.junit.Test +import kotlin.test.Test import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder -import org.junit.Assert.* -import de.westnordost.osmfeatures.FeatureDictionary -import de.westnordost.osmfeatures.LivePresetDataAccessAdapter -import de.westnordost.osmfeatures.Locale -import de.westnordost.osmfeatures.LocalizedFeatureCollection -import de.westnordost.osmfeatures.TestLocalizedFeatureCollection -import de.westnordost.osmfeatures.TestPerCountryFeatureCollection -import java.util.Collections +import kotlin.test.assertTrue +import kotlin.test.assertEquals +import kotlin.test.assertNull class FeatureDictionaryTest { private val bakery: Feature = feature( // unlocalized shop=bakery @@ -807,7 +802,7 @@ class FeatureDictionaryTest { ) companion object { - private val POINT: List = Collections.singletonList(GeometryType.POINT) + private val POINT: List = listOf(GeometryType.POINT) private fun dictionary(vararg entries: Feature): FeatureDictionary { return FeatureDictionary( TestLocalizedFeatureCollection(entries.filterNot { it is SuggestionFeature }), diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTagsIndexTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTagsIndexTest.kt index 353e743..583b23b 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTagsIndexTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTagsIndexTest.kt @@ -1,9 +1,6 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.BaseFeature -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.GeometryType -import org.junit.Test +import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt index d1045d5..e915f27 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt @@ -1,10 +1,9 @@ package de.westnordost.osmfeatures -import org.junit.Test +import kotlin.test.Test import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import de.westnordost.osmfeatures.FeatureTermIndex +import kotlin.test.assertEquals +import kotlin.test.assertTrue class FeatureTermIndexTest { @Test diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt index ceb31d2..656ac58 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt @@ -1,30 +1,23 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.IDBrandPresetsFeatureCollection import okio.FileSystem import okio.Path.Companion.toPath -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.Test import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder import okio.Source -import org.junit.Test -import java.io.IOException class IDBrandPresetsFeatureCollectionTest { @Test fun load_brands() { val c = IDBrandPresetsFeatureCollection(object : FileAccessAdapter { - @Override override fun exists(name: String): Boolean { return name == "presets.json" } - @Override - @Throws(IOException::class) override fun open(name: String): Source { - if (name == "presets.json") return getSource("brand_presets_min.json") - throw IOException("wrong file name") + return getSource("brand_presets_min.json") } }) assertEqualsIgnoreOrder( @@ -38,16 +31,13 @@ class IDBrandPresetsFeatureCollectionTest { @Test fun load_brands_by_country() { val c = IDBrandPresetsFeatureCollection(object : FileAccessAdapter { - @Override + override fun exists(name: String): Boolean { return name == "presets-DE.json" } - @Override - @Throws(IOException::class) override fun open(name: String): Source { - if (name == "presets-DE.json") return getSource("brand_presets_min2.json") - throw IOException("File not found") + return getSource("brand_presets_min2.json") } }) assertEqualsIgnoreOrder(listOf("Talespin"), getNames(c.getAll(listOf("DE")))) @@ -55,7 +45,6 @@ class IDBrandPresetsFeatureCollectionTest { c["yet_another/brand", listOf("DE")]?.isSuggestion?.let { assertTrue(it) } } - @kotlin.Throws(IOException::class) private fun getSource(file: String): Source { val resourcePath = "src/commonTest/resources/${file}".toPath() return FileSystem.SYSTEM.source(resourcePath) diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt index b699710..748a05a 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt @@ -1,22 +1,19 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.IDLocalizedFeatureCollection import okio.FileSystem import okio.Source -import org.junit.Test import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder import okio.FileNotFoundException import okio.IOException import okio.Path.Companion.toPath -import org.junit.Assert.* +import kotlin.test.* class IDLocalizedFeatureCollectionTest { @Test fun features_not_found_produces_runtime_exception() { try { IDLocalizedFeatureCollection(object : FileAccessAdapter { - @Override + override fun exists(name: String): Boolean { return false } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt index b858fe5..636c529 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt @@ -2,13 +2,13 @@ package de.westnordost.osmfeatures import okio.FileSystem import okio.Source -import org.junit.Test +import kotlin.test.Test import okio.IOException import okio.Path.Companion.toPath import okio.source -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue import java.net.URL class IDPresetsJsonParserTest { diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt index 152af93..09c63ce 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -1,21 +1,15 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.BaseFeature -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.GeometryType -import de.westnordost.osmfeatures.IDPresetsJsonParser -import de.westnordost.osmfeatures.IDPresetsTranslationJsonParser -import de.westnordost.osmfeatures.LocalizedFeature import okio.FileSystem import okio.Path.Companion.toPath import okio.Source -import org.junit.Test -import java.io.IOException +import okio.IOException import java.net.URISyntaxException import java.net.URL import okio.source -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.Test class IDPresetsTranslationJsonParserTest { @Test diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/JsonUtilsTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/JsonUtilsTest.kt index a78a0fa..6b146cc 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/JsonUtilsTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/JsonUtilsTest.kt @@ -1,10 +1,10 @@ package de.westnordost.osmfeatures import kotlinx.serialization.json.JsonArray -import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.Test import de.westnordost.osmfeatures.JsonUtils.parseList import de.westnordost.osmfeatures.JsonUtils.parseStringMap -import org.junit.Assert.assertEquals class JsonUtilsTest { @Test diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt index 3ae83d2..97eed20 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt @@ -1,17 +1,16 @@ package de.westnordost.osmfeatures -import java.io.IOException +import okio.IOException import java.net.URL import okio.source class LivePresetDataAccessAdapter : FileAccessAdapter { - @Override + override fun exists(name: String): Boolean { return listOf("presets.json", "de.json", "en.json", "en-GB.json").contains(name) } - @Override - @kotlin.Throws(IOException::class) + @Throws(IOException::class) override fun open(name: String): okio.Source { val url: URL = if (name == "presets.json") { URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt index 3dd9af4..a69c1f1 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt @@ -1,11 +1,12 @@ package de.westnordost.osmfeatures -import org.junit.Test import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue class StartsWithStringTreeTest { + @Test fun copes_with_empty_collection() { val t = StartsWithStringTree(listOf()) diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt index 7bb5a9d..2ff5c7c 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.Feature - class TestLocalizedFeatureCollection(features: List) : LocalizedFeatureCollection { private val features: List @@ -10,12 +8,10 @@ class TestLocalizedFeatureCollection(features: List) : LocalizedFeature this.features = features } - @Override override fun getAll(locales: List): Collection { return features.filter { locales.contains(it.locale) } } - @Override override operator fun get(id: String, locales: List): Feature? { val feature = features.find { it.id == id } return if (feature == null || (!locales.contains(feature.locale))) null else feature diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt index 4837f67..54dd4ff 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt @@ -1,8 +1,5 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.Feature -import de.westnordost.osmfeatures.PerCountryFeatureCollection - class TestPerCountryFeatureCollection(features: List) : PerCountryFeatureCollection { private val features: List @@ -10,23 +7,21 @@ class TestPerCountryFeatureCollection(features: List) : PerCountryFeatu this.features = features } - @Override override fun getAll(countryCodes: List): Collection { - return features.filter { - countryCodes - .find { countryCode -> - it.includeCountryCodes.contains(countryCode) || countryCode == null && it.includeCountryCodes.isEmpty() - } != null + return features.filter { feature -> + countryCodes + .any { countryCode -> + feature.includeCountryCodes.contains(countryCode) || countryCode == null && feature.includeCountryCodes.isEmpty() + } } } - @Override override operator fun get(id: String, countryCodes: List): Feature? { return features.find { feature -> feature.id == id - && countryCodes.find { - feature.includeCountryCodes.contains(it) || it == null && feature.includeCountryCodes.isEmpty() - } != null + && countryCodes.any { countryCode -> + feature.includeCountryCodes.contains(countryCode) || countryCode == null && feature.includeCountryCodes.isEmpty() + } } } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt index 3869f46..2422972 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt @@ -1,6 +1,6 @@ package de.westnordost.osmfeatures -import org.junit.Assert.assertTrue +import kotlin.test.assertTrue object TestUtils { fun assertEqualsIgnoreOrder(a: Collection, b: Collection) { From 2382034c14cf69c4c9ef50a69f8988c7777c0bb4 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Thu, 22 Feb 2024 09:03:43 +0100 Subject: [PATCH 20/98] fix excludeCountryCodes unit tests + dependencies --- build.gradle.kts | 40 +++++++++++ gradle/libs.versions.toml | 7 +- ...ionnary.kt => AndroidFeatureDictionary.kt} | 0 library/build.gradle.kts | 71 ++++++------------- .../FileAccessAdapter.kt | 4 +- .../LocalizedFeature.kt | 6 +- .../LocalizedFeatureCollection.kt | 2 - .../FeatureDictionaryTest.kt | 10 +-- .../TestPerCountryFeatureCollection.kt | 4 +- settings.gradle.kts | 1 + 10 files changed, 76 insertions(+), 69 deletions(-) rename library-android/src/main/java/de/westnordost/osmfeatures/{AndroidFeatureDictionnary.kt => AndroidFeatureDictionary.kt} (100%) diff --git a/build.gradle.kts b/build.gradle.kts index f3003f7..f8886a7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -41,3 +41,43 @@ // if (locale.country.isNotEmpty()) result += "-" + locale.country // return result //} + +//publishing { +// repositories { +// maven { +// url = uri("https://github.com/westnordost/osmfeatures") +// } +// } +// publications { +// create("mavenJava") { +// groupId = "de.westnordost" +// artifactId = "osmfeatures" +// version = "5.2" +// from(components["java"]) +// +// pom { +// name.value("osmfeatures") +// description.value("Java library to translate OSM tags to and from localized names.") +// url.value("https://github.com/westnordost/osmfeatures") +// scm { +// connection.value("https://github.com/westnordost/osmfeatures.git") +// developerConnection.value("https://github.com/westnordost/osmfeatures.git") +// url.value("https://github.com/westnordost/osmfeatures") +// } +// licenses { +// license { +// name.value("The Apache License, Version 2.0") +// url.value("http://www.apache.org/licenses/LICENSE-2.0.txt") +// } +// } +// developers { +// developer { +// id.value("westnordost") +// name.value("Tobias Zwick") +// email.value("osm@westnordost.de") +// } +// } +// } +// } +// } +//} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0c895a3..439017a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.2.1" +agp = "8.2.2" kotlin = "1.9.22" kotlinx-serialization-json = "1.6.0" normalize = "1.0.5" @@ -8,8 +8,9 @@ okio = "3.6.0" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } -normalize = { module = "com.doist.x:normalize", version.ref = "normalize" } -okio = { module = "com.squareup.okio:okio", version.ref = "okio" } +normalize = { group = "com.doist.x", name = "normalize", version.ref = "normalize" } +okio = { group = "com.squareup.okio", name = "okio", version.ref = "okio" } + [plugins] diff --git a/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionnary.kt b/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.kt similarity index 100% rename from library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionnary.kt rename to library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.kt diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 9c3c947..48f5b46 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -1,10 +1,18 @@ plugins { alias(libs.plugins.kotlin.multiplatform) + id("maven-publish") + id("signing") alias(libs.plugins.android.library) } kotlin { + + jvm() + iosX64() + iosArm64() + iosSimulatorArm64() androidTarget { + publishLibraryVariants("release") compilations.all { kotlinOptions { jvmTarget = "1.8" @@ -16,20 +24,20 @@ kotlin { commonMain.dependencies { //noinspection UseTomlInstead - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation(libs.okio) implementation(libs.kotlinx.serialization.json) implementation(libs.normalize) } -// val commonTest by getting { - commonTest.dependencies { - implementation(libs.kotlin.test) - } -// } + + commonTest.dependencies { + implementation(libs.kotlin.test) + } } } + + android { namespace = "de.westnordost.osmfeatures" compileSdk = 34 @@ -38,13 +46,15 @@ android { } } +signing { + sign(publishing.publications.mavenJava) +} + //plugins { // id("java-library") // id("maven-publish") // id("signing") // id("com.android.library") -// kotlin("multiplatform") version "1.9.10" -// kotlin("plugin.serialization") version "1.9.21" //} // //repositories { @@ -84,49 +94,8 @@ android { // } // } // -//publishing { -// repositories { -// maven { -// url = uri("https://github.com/westnordost/osmfeatures") -// } -// } -// publications { -// create("mavenJava") { -// groupId = "de.westnordost" -// artifactId = "osmfeatures" -// version = "5.2" -// from(components["java"]) -// -// pom { -// name.value("osmfeatures") -// description.value("Java library to translate OSM tags to and from localized names.") -// url.value("https://github.com/westnordost/osmfeatures") -// scm { -// connection.value("https://github.com/westnordost/osmfeatures.git") -// developerConnection.value("https://github.com/westnordost/osmfeatures.git") -// url.value("https://github.com/westnordost/osmfeatures") -// } -// licenses { -// license { -// name.value("The Apache License, Version 2.0") -// url.value("http://www.apache.org/licenses/LICENSE-2.0.txt") -// } -// } -// developers { -// developer { -// id.value("westnordost") -// name.value("Tobias Zwick") -// email.value("osm@westnordost.de") -// } -// } -// } -// } -// } -//} -// -//signing { -// sign(publishing.publications["mavenJava"]) -//} + + // //java { // sourceCompatibility = JavaVersion.VERSION_1_8 diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileAccessAdapter.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileAccessAdapter.kt index 83837e4..f5ff383 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileAccessAdapter.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileAccessAdapter.kt @@ -1,12 +1,10 @@ package de.westnordost.osmfeatures -import okio.IOException import okio.Source -import kotlin.jvm.Throws interface FileAccessAdapter { fun exists(name: String): Boolean - @Throws(IOException::class) + fun open(name: String): Source } \ No newline at end of file diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt index c50e647..a4065e2 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.Locale - /** Data class associated with the Feature interface. Represents a localized feature. * * I.e. the name and terms are specified in the given locale. */ @@ -36,9 +34,9 @@ class LocalizedFeature( get() = p.geometry override val name: String get() = names[0] - override val icon: String? + override val icon: String get() = p.icon - override val imageURL: String? + override val imageURL: String get() = p.imageURL override val includeCountryCodes: List get() = p.includeCountryCodes diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt index adbb98e..06b13e0 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.Feature - /** A localized collection of features */ interface LocalizedFeatureCollection { /** Returns all features in the given locale(s). */ diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt index 82bd0bb..f0bfbc2 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt @@ -444,14 +444,14 @@ class FeatureDictionaryTest { @Test fun find_no_entry_by_name_because_wrong_country() { val dictionary: FeatureDictionary = dictionary(ditsch, ditschRussian) - assertEquals(emptyList(), dictionary.byTerm("Ditsch").find()) - assertEquals(emptyList(), dictionary.byTerm("Ditsch").inCountry("FR").find()) // not in France + assertEquals(emptyList(), dictionary.byTerm("Ditsch").find()) + assertEquals(emptyList(), dictionary.byTerm("Ditsch").inCountry("FR").find()) // not in France assertEquals( - emptyList(), + emptyList(), dictionary.byTerm("Ditsch").inCountry("AT-9").find() ) // in all of AT but not Vienna assertEquals( - emptyList(), + emptyList(), dictionary.byTerm("Дитсч").inCountry("UA").find() ) // only on the Krim } @@ -835,7 +835,7 @@ class FeatureDictionaryTest { excludeCountryCodes, searchable, matchScore, false, addTags, mapOf() ) if (locale != null) { - LocalizedFeature(f, locale, f.names, f.terms.orEmpty()) + LocalizedFeature(f, locale, f.names, f.terms) } else { f } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt index 54dd4ff..36dc624 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt @@ -9,7 +9,8 @@ class TestPerCountryFeatureCollection(features: List) : PerCountryFeatu override fun getAll(countryCodes: List): Collection { return features.filter { feature -> - countryCodes + !countryCodes.any { feature.excludeCountryCodes.contains(it) } + && countryCodes .any { countryCode -> feature.includeCountryCodes.contains(countryCode) || countryCode == null && feature.includeCountryCodes.isEmpty() } @@ -19,6 +20,7 @@ class TestPerCountryFeatureCollection(features: List) : PerCountryFeatu override operator fun get(id: String, countryCodes: List): Feature? { return features.find { feature -> feature.id == id + && !countryCodes.any { feature.excludeCountryCodes.contains(it) } && countryCodes.any { countryCode -> feature.includeCountryCodes.contains(countryCode) || countryCode == null && feature.includeCountryCodes.isEmpty() } diff --git a/settings.gradle.kts b/settings.gradle.kts index 5f47dd8..10e1ecb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,6 +3,7 @@ pluginManagement { google() gradlePluginPortal() mavenCentral() + } } From e2d344bcd72ad2b9d900018a89487249d524fdca Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Thu, 22 Feb 2024 09:03:43 +0100 Subject: [PATCH 21/98] fix excludeCountryCodes unit tests + dependencies --- build.gradle.kts | 40 +++++++++ gradle/libs.versions.toml | 6 +- library-android/src/main/AndroidManifest.xml | 1 - ...ionnary.kt => AndroidFeatureDictionary.kt} | 0 library/build.gradle.kts | 84 ++++++------------- .../osmfeatures/AssetManagerAccess.kt | 17 ++++ .../osmfeatures/FileSystemAccess.kt | 7 ++ .../FeatureDictionary.kt | 18 ++-- .../FileAccessAdapter.kt | 4 +- .../FileSystemAccess.kt | 6 +- .../IDBrandPresetsFeatureCollection.kt | 18 ++-- .../LocalizedFeature.kt | 6 +- .../LocalizedFeatureCollection.kt | 2 - .../FeatureDictionaryTest.kt | 10 +-- .../IDBrandPresetsFeatureCollectionTest.kt | 6 +- .../IDPresetsJsonParserTest.kt | 20 +---- .../IDPresetsTranslationJsonParserTest.kt | 31 +------ .../TestPerCountryFeatureCollection.kt | 4 +- .../kotlin/osmfeatures/FileSystemAccess.kt | 7 ++ .../FileSystemAccess.kt | 7 ++ .../IDPresetsJsonParserTest.kt | 19 +++++ .../IDPresetsTranslationJsonParserTest.kt | 30 +++++++ settings.gradle.kts | 2 +- 23 files changed, 197 insertions(+), 148 deletions(-) delete mode 100644 library-android/src/main/AndroidManifest.xml rename library-android/src/main/java/de/westnordost/osmfeatures/{AndroidFeatureDictionnary.kt => AndroidFeatureDictionary.kt} (100%) create mode 100644 library/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt create mode 100644 library/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt create mode 100644 library/src/iosMain/kotlin/osmfeatures/FileSystemAccess.kt create mode 100644 library/src/jvmMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt create mode 100644 library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt create mode 100644 library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index f3003f7..f8886a7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -41,3 +41,43 @@ // if (locale.country.isNotEmpty()) result += "-" + locale.country // return result //} + +//publishing { +// repositories { +// maven { +// url = uri("https://github.com/westnordost/osmfeatures") +// } +// } +// publications { +// create("mavenJava") { +// groupId = "de.westnordost" +// artifactId = "osmfeatures" +// version = "5.2" +// from(components["java"]) +// +// pom { +// name.value("osmfeatures") +// description.value("Java library to translate OSM tags to and from localized names.") +// url.value("https://github.com/westnordost/osmfeatures") +// scm { +// connection.value("https://github.com/westnordost/osmfeatures.git") +// developerConnection.value("https://github.com/westnordost/osmfeatures.git") +// url.value("https://github.com/westnordost/osmfeatures") +// } +// licenses { +// license { +// name.value("The Apache License, Version 2.0") +// url.value("http://www.apache.org/licenses/LICENSE-2.0.txt") +// } +// } +// developers { +// developer { +// id.value("westnordost") +// name.value("Tobias Zwick") +// email.value("osm@westnordost.de") +// } +// } +// } +// } +// } +//} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0c895a3..dd2768c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,8 +8,10 @@ okio = "3.6.0" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } -normalize = { module = "com.doist.x:normalize", version.ref = "normalize" } -okio = { module = "com.squareup.okio:okio", version.ref = "okio" } +normalize = { group = "com.doist.x", name = "normalize", version.ref = "normalize" } +okio = { group = "com.squareup.okio", name = "okio", version.ref = "okio" } +okio-assetfilesystem = { group = "com.squareup.okio", name = "okio-assetfilesystem", version.ref = "okio" } + [plugins] diff --git a/library-android/src/main/AndroidManifest.xml b/library-android/src/main/AndroidManifest.xml deleted file mode 100644 index b5f255c..0000000 --- a/library-android/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionnary.kt b/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.kt similarity index 100% rename from library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionnary.kt rename to library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.kt diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 9c3c947..3a14f08 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -1,50 +1,59 @@ plugins { alias(libs.plugins.kotlin.multiplatform) + id("maven-publish") + id("signing") alias(libs.plugins.android.library) } +android { + namespace = "de.westnordost.osmfeatures" + compileSdk = 34 + defaultConfig { + minSdk = 24 + } +} + kotlin { + jvm() androidTarget { + publishLibraryVariants("release") compilations.all { kotlinOptions { jvmTarget = "1.8" } } } + iosArm64() sourceSets { commonMain.dependencies { //noinspection UseTomlInstead - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation(libs.okio) implementation(libs.kotlinx.serialization.json) implementation(libs.normalize) } -// val commonTest by getting { - commonTest.dependencies { - implementation(libs.kotlin.test) - } -// } - } -} + commonTest.dependencies { + implementation(libs.kotlin.test) + } -android { - namespace = "de.westnordost.osmfeatures" - compileSdk = 34 - defaultConfig { - minSdk = 24 + jvmTest.dependencies { + implementation(libs.kotlin.test) + } } + } +//signing { +// sign(publishing.publications.mavenJava) +//} + //plugins { // id("java-library") // id("maven-publish") // id("signing") // id("com.android.library") -// kotlin("multiplatform") version "1.9.10" -// kotlin("plugin.serialization") version "1.9.21" //} // //repositories { @@ -84,49 +93,8 @@ android { // } // } // -//publishing { -// repositories { -// maven { -// url = uri("https://github.com/westnordost/osmfeatures") -// } -// } -// publications { -// create("mavenJava") { -// groupId = "de.westnordost" -// artifactId = "osmfeatures" -// version = "5.2" -// from(components["java"]) -// -// pom { -// name.value("osmfeatures") -// description.value("Java library to translate OSM tags to and from localized names.") -// url.value("https://github.com/westnordost/osmfeatures") -// scm { -// connection.value("https://github.com/westnordost/osmfeatures.git") -// developerConnection.value("https://github.com/westnordost/osmfeatures.git") -// url.value("https://github.com/westnordost/osmfeatures") -// } -// licenses { -// license { -// name.value("The Apache License, Version 2.0") -// url.value("http://www.apache.org/licenses/LICENSE-2.0.txt") -// } -// } -// developers { -// developer { -// id.value("westnordost") -// name.value("Tobias Zwick") -// email.value("osm@westnordost.de") -// } -// } -// } -// } -// } -//} -// -//signing { -// sign(publishing.publications["mavenJava"]) -//} + + // //java { // sourceCompatibility = JavaVersion.VERSION_1_8 diff --git a/library/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt b/library/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt new file mode 100644 index 0000000..589d089 --- /dev/null +++ b/library/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt @@ -0,0 +1,17 @@ +package de.westnordost.osmfeatures + +import android.content.res.AssetManager +import okio.Source +import okio.source +import java.io.File + +class AssetManagerAccess(private val assetManager: AssetManager, private val basePath: String): FileAccessAdapter { + override fun exists(name: String): Boolean { + val files: Array = assetManager.list(basePath) ?: return false + return files.contains(name) + } + + override fun open(name: String): Source { + return assetManager.open(basePath + File.separator + name).source() + } +} \ No newline at end of file diff --git a/library/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt b/library/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt new file mode 100644 index 0000000..28bfbce --- /dev/null +++ b/library/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt @@ -0,0 +1,7 @@ +package de.westnordost.osmfeatures + +import okio.FileSystem + +actual fun fileSystem(): FileSystem { + return FileSystem.SYSTEM +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt index dd23e09..83be3a6 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt @@ -59,7 +59,7 @@ class FeatureDictionary internal constructor( val countryCodes = dissectCountryCode(countryCode) foundFeatures.addAll(getBrandTagsIndex(countryCodes)?.getAll(tags).orEmpty()) } - foundFeatures.removeIf { feature: Feature -> + foundFeatures.removeAll { feature: Feature -> !isFeatureMatchingParameters( feature, geometry, @@ -74,7 +74,7 @@ class FeatureDictionary internal constructor( removeIds.addAll(getParentCategoryIds(feature.id)) } if (removeIds.isNotEmpty()) { - foundFeatures.removeIf{ feature: Feature -> + foundFeatures.removeAll { feature: Feature -> removeIds.contains( feature.id ) @@ -168,7 +168,7 @@ class FeatureDictionary internal constructor( // a. matches with presets first val foundFeaturesByName: MutableList = getNamesIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() - foundFeaturesByName.removeIf { feature: Feature -> + foundFeaturesByName.removeAll { feature: Feature -> !isFeatureMatchingParameters( feature, geometry, @@ -187,7 +187,7 @@ class FeatureDictionary internal constructor( // b. matches with brand names second val countryCodes = dissectCountryCode(countryCode) val foundBrandFeatures = getBrandNamesIndex(countryCodes)?.getAll(canonicalSearch).orEmpty().toMutableList() - foundBrandFeatures.removeIf{ feature: Feature -> + foundBrandFeatures.removeAll { feature: Feature -> !isFeatureMatchingParameters( feature, geometry, @@ -206,7 +206,7 @@ class FeatureDictionary internal constructor( if (isSuggestion == null || !isSuggestion) { // c. matches with terms third val foundFeaturesByTerm = getTermsIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() - foundFeaturesByTerm.removeIf { feature: Feature -> + foundFeaturesByTerm.removeAll { feature: Feature -> !isFeatureMatchingParameters( feature, geometry, @@ -215,7 +215,7 @@ class FeatureDictionary internal constructor( } if (foundFeaturesByTerm.isNotEmpty()) { val alreadyFoundFeatures: Set = HashSet(result) - foundFeaturesByTerm.removeIf { feature: Feature -> + foundFeaturesByTerm.removeAll { feature: Feature -> alreadyFoundFeatures.contains( feature ) @@ -233,7 +233,7 @@ class FeatureDictionary internal constructor( if (isSuggestion == null || !isSuggestion) { // d. matches with tag values fourth val foundFeaturesByTagValue = getTagValuesIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() - foundFeaturesByTagValue.removeIf { feature: Feature -> + foundFeaturesByTagValue.removeAll { feature: Feature -> !isFeatureMatchingParameters( feature, geometry, @@ -242,7 +242,7 @@ class FeatureDictionary internal constructor( } if (foundFeaturesByTagValue.isNotEmpty()) { val alreadyFoundFeatures: Set = HashSet(result) - foundFeaturesByTagValue.removeIf { feature: Feature -> + foundFeaturesByTagValue.removeAll { feature: Feature -> alreadyFoundFeatures.contains( feature ) @@ -537,7 +537,7 @@ class FeatureDictionary internal constructor( /** Create a new FeatureDictionary which gets its data from the given directory. Optionally, * a path to brand presets can be specified. */ /** Create a new FeatureDictionary which gets its data from the given directory. */ - @JvmOverloads + fun create(presetsBasePath: String, brandPresetsBasePath: String? = null): FeatureDictionary { val featureCollection: LocalizedFeatureCollection = IDLocalizedFeatureCollection(FileSystemAccess(presetsBasePath)) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileAccessAdapter.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileAccessAdapter.kt index 83837e4..f5ff383 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileAccessAdapter.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileAccessAdapter.kt @@ -1,12 +1,10 @@ package de.westnordost.osmfeatures -import okio.IOException import okio.Source -import kotlin.jvm.Throws interface FileAccessAdapter { fun exists(name: String): Boolean - @Throws(IOException::class) + fun open(name: String): Source } \ No newline at end of file diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt index 88a1558..0231012 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt @@ -3,10 +3,12 @@ import okio.FileSystem import okio.Path.Companion.toPath import okio.Source +expect fun fileSystem(): FileSystem + class FileSystemAccess(private val basePath: String) : FileAccessAdapter { - private val fs = FileSystem.SYSTEM + private val fs: FileSystem = fileSystem() init { - fs.metadataOrNull(basePath.toPath())?.let { require(it.isDirectory) { "basePath must be a directory" } } + fs.metadataOrNull(basePath.toPath())?.let { require(it.isDirectory) { "$basePath is not a directory" } } } override fun exists(name: String): Boolean { diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt index f210d70..9b9feb4 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt @@ -1,5 +1,7 @@ package de.westnordost.osmfeatures +import okio.use + /** Non-localized feature collection sourcing from (NSI) iD presets defined in JSON. * * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that @@ -8,7 +10,7 @@ package de.westnordost.osmfeatures * lazily on demand */ class IDBrandPresetsFeatureCollection internal constructor(private val fileAccess: FileAccessAdapter) : PerCountryFeatureCollection { - private val featuresByIdByCountryCode: HashMap> = LinkedHashMap(320) + private val featuresByIdByCountryCode: MutableMap> = LinkedHashMap(320) init { getOrLoadPerCountryFeatures(null) @@ -30,14 +32,8 @@ class IDBrandPresetsFeatureCollection internal constructor(private val fileAcces return null } - private fun getOrLoadPerCountryFeatures(countryCode: String?): java.util.LinkedHashMap? { - return CollectionUtils.synchronizedGetOrCreate( - featuresByIdByCountryCode, countryCode - ) { countryCode: String? -> - this.loadPerCountryFeatures( - countryCode - ) - } + private fun getOrLoadPerCountryFeatures(countryCode: String?): LinkedHashMap? { + return CollectionUtils.synchronizedGetOrCreate(featuresByIdByCountryCode, countryCode) { this.loadPerCountryFeatures(it) } } private fun loadPerCountryFeatures(countryCode: String?): LinkedHashMap { @@ -52,8 +48,8 @@ class IDBrandPresetsFeatureCollection internal constructor(private val fileAcces private fun loadFeatures(countryCode: String?): List { val filename = getPresetsFileName(countryCode) if (!fileAccess.exists(filename)) return emptyList() - fileAccess.open(filename).use { `is` -> - return IDPresetsJsonParser(true).parse(`is`) + fileAccess.open(filename).use { + return IDPresetsJsonParser(true).parse(it) } } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt index c50e647..a4065e2 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.Locale - /** Data class associated with the Feature interface. Represents a localized feature. * * I.e. the name and terms are specified in the given locale. */ @@ -36,9 +34,9 @@ class LocalizedFeature( get() = p.geometry override val name: String get() = names[0] - override val icon: String? + override val icon: String get() = p.icon - override val imageURL: String? + override val imageURL: String get() = p.imageURL override val includeCountryCodes: List get() = p.includeCountryCodes diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt index adbb98e..06b13e0 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.Feature - /** A localized collection of features */ interface LocalizedFeatureCollection { /** Returns all features in the given locale(s). */ diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt index 82bd0bb..f0bfbc2 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt @@ -444,14 +444,14 @@ class FeatureDictionaryTest { @Test fun find_no_entry_by_name_because_wrong_country() { val dictionary: FeatureDictionary = dictionary(ditsch, ditschRussian) - assertEquals(emptyList(), dictionary.byTerm("Ditsch").find()) - assertEquals(emptyList(), dictionary.byTerm("Ditsch").inCountry("FR").find()) // not in France + assertEquals(emptyList(), dictionary.byTerm("Ditsch").find()) + assertEquals(emptyList(), dictionary.byTerm("Ditsch").inCountry("FR").find()) // not in France assertEquals( - emptyList(), + emptyList(), dictionary.byTerm("Ditsch").inCountry("AT-9").find() ) // in all of AT but not Vienna assertEquals( - emptyList(), + emptyList(), dictionary.byTerm("Дитсч").inCountry("UA").find() ) // only on the Krim } @@ -835,7 +835,7 @@ class FeatureDictionaryTest { excludeCountryCodes, searchable, matchScore, false, addTags, mapOf() ) if (locale != null) { - LocalizedFeature(f, locale, f.names, f.terms.orEmpty()) + LocalizedFeature(f, locale, f.names, f.terms) } else { f } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt index 656ac58..23cd626 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import okio.FileSystem -import okio.Path.Companion.toPath import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.Test @@ -46,8 +44,8 @@ class IDBrandPresetsFeatureCollectionTest { } private fun getSource(file: String): Source { - val resourcePath = "src/commonTest/resources/${file}".toPath() - return FileSystem.SYSTEM.source(resourcePath) + val fileSystemAccess = FileSystemAccess("src/commonTest/resources") + return fileSystemAccess.open(file) } companion object { diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt index 636c529..719dca7 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt @@ -1,15 +1,11 @@ package de.westnordost.osmfeatures -import okio.FileSystem import okio.Source import kotlin.test.Test import okio.IOException -import okio.Path.Companion.toPath -import okio.source import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -import java.net.URL class IDPresetsJsonParserTest { @Test @@ -76,16 +72,6 @@ class IDPresetsJsonParserTest { assertTrue(features.isEmpty()) } - @Test - @kotlin.Throws(IOException::class) - fun parse_some_real_data() { - val url = - URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - val features: List = IDPresetsJsonParser().parse(url.openStream().source()) - // should not crash etc - assertTrue(features.size > 1000) - } - private fun parse(file: String): List { return try { IDPresetsJsonParser().parse(getSource(file)) @@ -94,9 +80,9 @@ class IDPresetsJsonParserTest { } } - @kotlin.Throws(IOException::class) + @Throws(IOException::class) private fun getSource(file: String): Source { - val resourcePath = "src/commonTest/resources/${file}".toPath() - return FileSystem.SYSTEM.source(resourcePath) + val fileSystemAccess = FileSystemAccess("src/commonTest/resources") + return fileSystemAccess.open(file) } } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt index 09c63ce..48e269f 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -1,12 +1,7 @@ package de.westnordost.osmfeatures -import okio.FileSystem -import okio.Path.Companion.toPath import okio.Source import okio.IOException -import java.net.URISyntaxException -import java.net.URL -import okio.source import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.Test @@ -53,26 +48,6 @@ class IDPresetsTranslationJsonParserTest { assertEquals(listOf("a", "b"), feature?.terms) } - @Test - @kotlin.Throws(IOException::class, URISyntaxException::class) - fun parse_some_real_data() { - val url = - URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - val features: List = - IDPresetsJsonParser().parse(url.openConnection().getInputStream().source()) - val featureMap = HashMap(features.associateBy { it.id }) - val rawTranslationsURL = - URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json") - val translatedFeatures: List = IDPresetsTranslationJsonParser().parse( - rawTranslationsURL.openStream().source(), - Locale.GERMAN, - featureMap - ) - - // should not crash etc - assertTrue(translatedFeatures.size > 1000) - } - private fun parse(presetsFile: String, translationsFile: String): List { return try { val baseFeatures: List = @@ -88,9 +63,9 @@ class IDPresetsTranslationJsonParserTest { } } - @kotlin.Throws(IOException::class) + @Throws(IOException::class) private fun getSource(file: String): Source { - val resourcePath = "src/commonTest/resources/${file}".toPath() - return FileSystem.SYSTEM.source(resourcePath) + val fileSystemAccess = FileSystemAccess("src/commonTest/resources") + return fileSystemAccess.open(file) } } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt index 54dd4ff..36dc624 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt @@ -9,7 +9,8 @@ class TestPerCountryFeatureCollection(features: List) : PerCountryFeatu override fun getAll(countryCodes: List): Collection { return features.filter { feature -> - countryCodes + !countryCodes.any { feature.excludeCountryCodes.contains(it) } + && countryCodes .any { countryCode -> feature.includeCountryCodes.contains(countryCode) || countryCode == null && feature.includeCountryCodes.isEmpty() } @@ -19,6 +20,7 @@ class TestPerCountryFeatureCollection(features: List) : PerCountryFeatu override operator fun get(id: String, countryCodes: List): Feature? { return features.find { feature -> feature.id == id + && !countryCodes.any { feature.excludeCountryCodes.contains(it) } && countryCodes.any { countryCode -> feature.includeCountryCodes.contains(countryCode) || countryCode == null && feature.includeCountryCodes.isEmpty() } diff --git a/library/src/iosMain/kotlin/osmfeatures/FileSystemAccess.kt b/library/src/iosMain/kotlin/osmfeatures/FileSystemAccess.kt new file mode 100644 index 0000000..28bfbce --- /dev/null +++ b/library/src/iosMain/kotlin/osmfeatures/FileSystemAccess.kt @@ -0,0 +1,7 @@ +package de.westnordost.osmfeatures + +import okio.FileSystem + +actual fun fileSystem(): FileSystem { + return FileSystem.SYSTEM +} \ No newline at end of file diff --git a/library/src/jvmMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt b/library/src/jvmMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt new file mode 100644 index 0000000..28bfbce --- /dev/null +++ b/library/src/jvmMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt @@ -0,0 +1,7 @@ +package de.westnordost.osmfeatures + +import okio.FileSystem + +actual fun fileSystem(): FileSystem { + return FileSystem.SYSTEM +} \ No newline at end of file diff --git a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt new file mode 100644 index 0000000..2465c72 --- /dev/null +++ b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt @@ -0,0 +1,19 @@ +package de.westnordost.osmfeatures + +import kotlin.test.Test +import okio.IOException +import okio.source +import kotlin.test.assertTrue +import java.net.URL + +class IDPresetsJsonParserJVMTest { + @Test + @Throws(IOException::class) + fun parse_some_real_data() { + val url = + URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") + val features: List = IDPresetsJsonParser().parse(url.openStream().source()) + // should not crash etc + assertTrue(features.size > 1000) + } +} diff --git a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt new file mode 100644 index 0000000..6887d09 --- /dev/null +++ b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -0,0 +1,30 @@ +package de.westnordost.osmfeatures + +import okio.IOException +import java.net.URISyntaxException +import java.net.URL +import okio.source +import kotlin.test.assertTrue +import kotlin.test.Test + +class IDPresetsTranslationJsonParserJVMTest { + @Test + @Throws(IOException::class, URISyntaxException::class) + fun parse_some_real_data() { + val url = + URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") + val features: List = + IDPresetsJsonParser().parse(url.openConnection().getInputStream().source()) + val featureMap = HashMap(features.associateBy { it.id }) + val rawTranslationsURL = + URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json") + val translatedFeatures: List = IDPresetsTranslationJsonParser().parse( + rawTranslationsURL.openStream().source(), + Locale.GERMAN, + featureMap + ) + + // should not crash etc + assertTrue(translatedFeatures.size > 1000) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 5f47dd8..7cba528 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,11 +3,11 @@ pluginManagement { google() gradlePluginPortal() mavenCentral() + } } dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() From 7d7b1fd4a8ae87e8f283ef87cfce0fdda038fffa Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Sat, 24 Feb 2024 16:37:32 +0100 Subject: [PATCH 22/98] fix build gradle --- library/build.gradle.kts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 7886bdb..076d9d3 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -4,6 +4,7 @@ plugins { } kotlin { + jvm() androidTarget { compilations.all { kotlinOptions { @@ -11,6 +12,7 @@ kotlin { } } } + iosArm64() sourceSets { @@ -21,11 +23,13 @@ kotlin { implementation(libs.kotlinx.serialization.json) implementation(libs.normalize) } -// val commonTest by getting { - commonTest.dependencies { - implementation(libs.kotlin.test) - } -// } + commonTest.dependencies { + implementation(libs.kotlin.test) + } + + jvmTest.dependencies { + implementation(libs.kotlin.test) + } } } From 5a35637c860cfa8840960a836f192dde16d14710 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sun, 25 Feb 2024 19:07:48 +0100 Subject: [PATCH 23/98] Apply suggestions from code review --- .../ContainedMapTree.kt | 36 +++++++++---------- .../LocalizedFeature.kt | 20 ++--------- .../StartsWithStringTree.kt | 14 ++++---- .../de.westnordost.osmfeatures/StringUtils.kt | 4 --- 4 files changed, 28 insertions(+), 46 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt index 781f1fe..0e5c07c 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt @@ -8,30 +8,30 @@ package de.westnordost.osmfeatures * For example for the string maps... *
  * [
- * #1 (amenity -> bicycle_parking),
- * #2 (amenity -> bicycle_parking, bicycle_parking -> shed),
- * #3 (amenity -> bicycle_parking, bicycle_parking -> lockers),
- * #4 (amenity -> taxi),
- * #5 (shop -> supermarket),
+ *   #1 (amenity -> bicycle_parking),
+ *   #2 (amenity -> bicycle_parking, bicycle_parking -> shed),
+ *   #3 (amenity -> bicycle_parking, bicycle_parking -> lockers),
+ *   #4 (amenity -> taxi),
+ *   #5 (shop -> supermarket),
  * ]
-
* + * * ...the tree internally looks like this: *
  * amenity ->
- * bicycle_parking ->
- * #1
- * bicycle_parking ->
- * shed ->
- * #2
- * lockers ->
- * #3
- * taxi ->
- * #4
+ *   bicycle_parking ->
+ *     #1
+ *     bicycle_parking ->
+ *       shed ->
+ *         #2
+ *       lockers ->
+ *         #3
+ *   taxi ->
+ *     #4
  * shop ->
- * supermarket ->
- * #5
+ *   supermarket ->
+ *     #5
  * ...
-
* +* */ internal class ContainedMapTree(maps: Collection>, maxDepth: Int = 4, minContainerSize: Int = 4) { private val root: Node diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt index a4065e2..2e85d5d 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt @@ -8,23 +8,9 @@ class LocalizedFeature( override val locale: Locale?, override val names: List, override val terms: List -) : - Feature { - override val canonicalNames: List - override val canonicalTerms: List - - init { - val canonicalNames: MutableList = ArrayList(names.size) - for (name in names) { - canonicalNames.add(StringUtils.canonicalize(name)) - } - this.canonicalNames = canonicalNames.toList() - val canonicalTerms: MutableList = ArrayList(terms.size) - for (term in terms) { - canonicalTerms.add(StringUtils.canonicalize(term)) - } - this.canonicalTerms = canonicalTerms.toList() - } +) : Feature { + override val canonicalNames: List = names.map { name -> StringUtils.canonicalize(name) } + override val canonicalTerms: List = terms.map { term -> StringUtils.canonicalize(term) } override val id: String get() = p.id diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt index 23d71d3..450980f 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt @@ -9,13 +9,13 @@ package de.westnordost.osmfeatures * like this: *
  * f ->
- * [ "f" ]
- * o ->
- * o ->
- * [ "foobar", "foo", ...]
- * u ->
- * [ "fu", "funicular", ... ]
-
*/ + * [ "f" ] + * o -> + * o -> + * [ "foobar", "foo", ...] + * u -> + * [ "fu", "funicular", ... ] + * */ internal class StartsWithStringTree @JvmOverloads constructor(strings: Collection, maxDepth: Int = 16, minContainerSize: Int = 16) { private val root: Node diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt index d4dab4d..2231cbb 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt @@ -16,8 +16,4 @@ class StringUtils { return FIND_DIACRITICS.replace(str.normalize(Form.NFD),"") } } - - - - } \ No newline at end of file From 2dad8c13981f91ce7995026d7e89e220b6ce8c25 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sun, 25 Feb 2024 19:08:27 +0100 Subject: [PATCH 24/98] Shorter code --- .../kotlin/de.westnordost.osmfeatures/BaseFeature.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt index 7552e43..9c05db2 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt @@ -18,11 +18,7 @@ open class BaseFeature( final override val removeTags: Map ): Feature { final override val canonicalNames: List = names.map { name -> StringUtils.canonicalize(name)} - final override var canonicalTerms: List? = null - - init { - this.canonicalTerms = terms.map { term -> StringUtils.canonicalize(term)} - } + final override val canonicalTerms: List = terms.map { term -> StringUtils.canonicalize(term)} override val icon: String get() = _icon ?: "" From 339125200708a77ddcc76e139da5c8f9b8b7bbb3 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sun, 25 Feb 2024 19:08:41 +0100 Subject: [PATCH 25/98] add comment --- .../commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt index 6e41997..1ea1a97 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt @@ -1,5 +1,8 @@ package de.westnordost.osmfeatures +/** Subset of a feature as defined in the iD editor + * https://github.com/ideditor/schema-builder#preset-schema + * with only the fields helpful for the dictionary */ interface Feature { val id: String val tags: Map From 8056008327b0362b235b28909355601b0a043f4e Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 17:18:26 +0100 Subject: [PATCH 26/98] make canonicalize a kotlin extension function --- .../de.westnordost.osmfeatures/BaseFeature.kt | 4 ++-- .../FeatureDictionary.kt | 2 +- .../de.westnordost.osmfeatures/StringUtils.kt | 16 +++++----------- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt index 9c05db2..a9d1c43 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt @@ -17,8 +17,8 @@ open class BaseFeature( final override val addTags: Map, final override val removeTags: Map ): Feature { - final override val canonicalNames: List = names.map { name -> StringUtils.canonicalize(name)} - final override val canonicalTerms: List = terms.map { term -> StringUtils.canonicalize(term)} + final override val canonicalNames: List = names.map { it.canonicalize() } + final override val canonicalTerms: List = terms.map { it.canonicalize() } override val icon: String get() = _icon ?: "" diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt index 83be3a6..b945df8 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt @@ -124,7 +124,7 @@ class FeatureDictionary internal constructor( limit: Int, locales: List ): MutableList { - val canonicalSearch = StringUtils.canonicalize(search) + val canonicalSearch = search.canonicalize() val sortNames = Comparator { a: Feature, b: Feature -> // 1. exact matches first val exactMatchOrder = diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt index 2231cbb..6309c5b 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt @@ -3,17 +3,11 @@ package de.westnordost.osmfeatures import doist.x.normalize.Form import doist.x.normalize.normalize -class StringUtils { +private val FIND_DIACRITICS = "\\p{InCombiningDiacriticalMarks}+".toRegex() - companion object { - private val FIND_DIACRITICS = "\\p{InCombiningDiacriticalMarks}+".toRegex() +internal fun String.canonicalize(): String = + stripDiacritics().lowercase() - fun canonicalize(str: String): String { - return stripDiacritics(str).lowercase() - } - - private fun stripDiacritics(str: String): String { - return FIND_DIACRITICS.replace(str.normalize(Form.NFD),"") - } - } +private fun String.stripDiacritics(): String { + return FIND_DIACRITICS.replace(normalize(Form.NFD),"") } \ No newline at end of file From 78b992b3323be0289435835f2e1fed4949e87b40 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 17:19:38 +0100 Subject: [PATCH 27/98] add missing import --- .../kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt index 450980f..18159f1 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt @@ -1,5 +1,7 @@ package de.westnordost.osmfeatures +import kotlin.jvm.JvmOverloads + /** Index that makes finding strings that start with characters very efficient. * It sorts the strings into a tree structure with configurable depth. * From 10f3e7c234790ffd3fa5ed828500e013971d72f1 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 17:27:05 +0100 Subject: [PATCH 28/98] fix doc comment --- .../de.westnordost.osmfeatures/StartsWithStringTree.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt index 18159f1..2702498 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt @@ -9,7 +9,8 @@ import kotlin.jvm.JvmOverloads * * For the strings ["f", "foobar", "foo", "fu", "funicular"], the tree may internally look f.e. * like this: - *
+ *
+ * ```
  * f ->
  *   [ "f" ]
  *   o ->
@@ -17,7 +18,8 @@ import kotlin.jvm.JvmOverloads
  *       [ "foobar", "foo", ...]
  *   u ->
  *     [ "fu", "funicular", ... ]
- * 
*/ + * ``` + * */ internal class StartsWithStringTree @JvmOverloads constructor(strings: Collection, maxDepth: Int = 16, minContainerSize: Int = 16) { private val root: Node From bba61dbfb042ab521d86839a34dea6f05fe93791 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 17:28:38 +0100 Subject: [PATCH 29/98] simplify/kotlinize constructor --- .../StartsWithStringTree.kt | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt index 2702498..c5eddc0 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt @@ -21,21 +21,22 @@ import kotlin.jvm.JvmOverloads * ``` * */ internal class StartsWithStringTree -@JvmOverloads constructor(strings: Collection, maxDepth: Int = 16, minContainerSize: Int = 16) { - private val root: Node - - /** Create this index with the given strings. - * - * The generated tree will have a max depth of maxDepth and another depth is not added to the - * tree if there are less than minContainerSize strings in one tree node. - */ - init { - var maxDepth = maxDepth - var minContainerSize = minContainerSize - if (maxDepth < 0) maxDepth = 0 - if (minContainerSize < 1) minContainerSize = 1 - root = buildTree(strings, 0, maxDepth, minContainerSize) - } +/** Create this index with the given strings. + * + * The generated tree will have a max depth of maxDepth and another depth is not added to the + * tree if there are less than minContainerSize strings in one tree node. + */ +@JvmOverloads constructor( + strings: Collection, + maxDepth: Int = 16, + minContainerSize: Int = 16 +) { + private val root: Node = buildTree( + strings, + 0, + maxDepth.coerceAtLeast(0), + minContainerSize.coerceAtLeast(1) + ) /** Get all strings which start with the given string */ fun getAll(startsWith: String?): List { From 796301d3f7b66d1e6a80f67db0894b14bb845a32 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 17:30:04 +0100 Subject: [PATCH 30/98] make startsWith parameter not nullable --- .../StartsWithStringTree.kt | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt index c5eddc0..7a85fa0 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt @@ -39,30 +39,26 @@ internal class StartsWithStringTree ) /** Get all strings which start with the given string */ - fun getAll(startsWith: String?): List { + fun getAll(startsWith: String): List { return root.getAll(startsWith, 0) } private class Node(val children: Map?, val strings: Collection) { /** Get all strings that start with the given string */ - fun getAll(startsWith: String?, offset: Int): List { - if (startsWith != null) { - if (startsWith.isEmpty()) return emptyList() - } + fun getAll(startsWith: String, offset: Int): List { + if (startsWith.isEmpty()) return emptyList() val result: MutableList = ArrayList() if (children != null) { for ((key, value) in children) { - if (startsWith != null) { - if (startsWith.length <= offset || key == startsWith[offset]) { - result.addAll(value.getAll(startsWith, offset + 1)) - } + if (startsWith.length <= offset || key == startsWith[offset]) { + result.addAll(value.getAll(startsWith, offset + 1)) } } } for (string in strings) { - if (startsWith?.let { string.startsWith(it) } == true) result.add(string) + if (string.startsWith(startsWith)) result.add(string) } return result } From b1103e9a22a489bdc478e689ec8c4648aca88970 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 17:51:26 +0100 Subject: [PATCH 31/98] improve readability: - make Node.children non-nullable (use emptyMap instead if no children) - remove some needless type declarations - make an utility function an extension function --- .../StartsWithStringTree.kt | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt index 7a85fa0..451f161 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt @@ -43,18 +43,16 @@ internal class StartsWithStringTree return root.getAll(startsWith, 0) } - private class Node(val children: Map?, val strings: Collection) { + private class Node(val children: Map, val strings: Collection) { /** Get all strings that start with the given string */ fun getAll(startsWith: String, offset: Int): List { if (startsWith.isEmpty()) return emptyList() - val result: MutableList = ArrayList() - if (children != null) { - for ((key, value) in children) { - if (startsWith.length <= offset || key == startsWith[offset]) { - result.addAll(value.getAll(startsWith, offset + 1)) - } + val result = ArrayList() + for ((key, value) in children) { + if (startsWith.length <= offset || key == startsWith[offset]) { + result.addAll(value.getAll(startsWith, offset + 1)) } } for (string in strings) { @@ -71,31 +69,30 @@ internal class StartsWithStringTree maxDepth: Int, minContainerSize: Int ): Node { - if (currentDepth == maxDepth || strings.size < minContainerSize) return Node(null, strings) + if (currentDepth == maxDepth || strings.size < minContainerSize) { + return Node(emptyMap(), strings) + } - val stringsByCharacter = getStringsByCharacter(strings, currentDepth) - var children: HashMap? = HashMap(stringsByCharacter.size) + val stringsByCharacter = strings.groupedByNthCharacter(currentDepth) + val children = HashMap(stringsByCharacter.size) for ((key, value) in stringsByCharacter) { val c = key ?: continue val child = buildTree(value, currentDepth + 1, maxDepth, minContainerSize) - children?.set(c, child) - } - val remainingStrings: Collection = stringsByCharacter[null].orEmpty() - if (children != null) { - if (children.isEmpty()) children = null + children[c] = child } - return Node(children, remainingStrings) + val remainingStrings = stringsByCharacter[null].orEmpty() + val compactChildren = if (children.isEmpty()) emptyMap() else children + return Node(compactChildren, remainingStrings) } /** returns the given strings grouped by their nth character. Strings whose length is shorter * or equal to nth go into the "null" group. */ - private fun getStringsByCharacter( - strings: Collection, + private fun Collection.groupedByNthCharacter( nth: Int ): Map> { val result = HashMap>() - for (string in strings) { + for (string in this) { val c = if (string.length > nth) string[nth] else null if (!result.containsKey(c)) result[c] = ArrayList() result[c]?.add(string) From a97473b422490c722b69ded3b2f35b144cefd552 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 17:54:55 +0100 Subject: [PATCH 32/98] fix build (sorry, my bad) --- .../kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt index 2e85d5d..b1de3b9 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt @@ -9,8 +9,8 @@ class LocalizedFeature( override val names: List, override val terms: List ) : Feature { - override val canonicalNames: List = names.map { name -> StringUtils.canonicalize(name) } - override val canonicalTerms: List = terms.map { term -> StringUtils.canonicalize(term) } + override val canonicalNames: List = names.map { it.canonicalize() } + override val canonicalTerms: List = terms.map { it.canonicalize() } override val id: String get() = p.id From 0ead17fb8820934a1c75e77d8157e1713d86f4b8 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 18:00:06 +0100 Subject: [PATCH 33/98] make canonicalTerms non-nullable --- .../src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt | 2 +- .../kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt index 1ea1a97..343c5d9 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt @@ -21,7 +21,7 @@ interface Feature { val addTags: Map val removeTags: Map val canonicalNames: List - val canonicalTerms: List? + val canonicalTerms: List val isSuggestion: Boolean val locale: Locale? } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt index b945df8..1ad8ed4 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt @@ -306,7 +306,7 @@ class FeatureDictionary internal constructor( private fun createTermsIndex(locales: List): FeatureTermIndex { return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature -> if (!feature.isSearchable) return@Selector emptyList() - feature.canonicalTerms.orEmpty() + feature.canonicalTerms }) } From 4f54b2d02d5f5d089b3f0e7fb4641b3c722e21ec Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 18:04:31 +0100 Subject: [PATCH 34/98] simplify constructor --- .../de.westnordost.osmfeatures/Locale.kt | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt index be28467..cb48954 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt @@ -1,13 +1,13 @@ package de.westnordost.osmfeatures -data class Locale( +import kotlin.jvm.JvmOverloads +data class Locale @JvmOverloads constructor( val language: String, - val region: String?, - val script: String?) { + val region: String? = null, + val script: String? = null +) { companion object { - - val ENGLISH: Locale = Locale("en") val UK: Locale = Locale("en","UK") @@ -24,13 +24,9 @@ data class Locale( val CHINESE: Locale = Locale("zh") - val default: Locale? = null - } - - val country : String get() = this.region.orEmpty() @@ -43,12 +39,6 @@ data class Locale( } } - constructor(lang: String) : this(lang,null, null) - - constructor(lang: String, region: String) : this(lang, region, null) - - - override fun equals(other: Any?): Boolean { if (other == null) { return false @@ -97,6 +87,4 @@ data class Locale( } } - - } \ No newline at end of file From 704479b62ac0e637c4387300b22235084c3ab797 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 18:05:11 +0100 Subject: [PATCH 35/98] remove equals and hashCode - data classes generate this automatically --- .../de.westnordost.osmfeatures/Locale.kt | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt index cb48954..aacaf8a 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt @@ -39,25 +39,6 @@ data class Locale @JvmOverloads constructor( } } - override fun equals(other: Any?): Boolean { - if (other == null) { - return false - } - if (other is Locale) { - return other.languageTag == this.languageTag - } - return false - - } - - override fun hashCode(): Int { - var result = language.hashCode() - result = 31 * result + (region?.hashCode() ?: 0) - result = 31 * result + (script?.hashCode() ?: 0) - return result - } - - class Builder { private var language: String? = null fun setLanguage(language: String) : Builder { From 1bb77dd2fafb543e7454b6f5d94138301df87e22 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 18:16:05 +0100 Subject: [PATCH 36/98] remove Builder - unnecessary, just using constructor will do --- .../IDLocalizedFeatureCollection.kt | 18 ++++------- .../de.westnordost.osmfeatures/Locale.kt | 30 ------------------- .../IDLocalizedFeatureCollectionTest.kt | 5 ++-- 3 files changed, 8 insertions(+), 45 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt index 24a0357..32d08a5 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt @@ -78,25 +78,19 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : private fun getLocalizationFilename(locale: Locale?): String { /* we only want language+country+script of the locale, not anything else. So we construct it anew here */ - return Locale.Builder() - .setLanguage(locale?.language ?: Locale.ENGLISH.language) - .setRegion(locale?.region) - .setScript(locale?.script) - .build() + return Locale(locale?.language ?: "en", locale?.region, locale?.script) .languageTag + ".json" } private fun getLocaleComponents(locale: Locale?): List { val lang = locale?.language ?: "" - val country = locale?.country ?: "" - val script = locale?.script ?: "" + val region = locale?.region + val script = locale?.script val result: MutableList = ArrayList(4) result.add(Locale(lang)) - if (country.isNotEmpty()) result.add(Locale.Builder().setLanguage(lang).setRegion(country).build()) - if (script.isNotEmpty()) result.add(Locale.Builder().setLanguage(lang).setScript(script).build()) - if (country.isNotEmpty() && script.isNotEmpty()) result.add( - Locale.Builder().setLanguage(lang).setRegion(country).setScript(script).build() - ) + if (region != null) result.add(Locale(language = lang, region = region)) + if (script != null) result.add(Locale(language = lang, script = script)) + if (region != null && script != null) result.add(Locale(language = lang, region = region, script = script)) return result } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt index aacaf8a..ba552a5 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt @@ -38,34 +38,4 @@ data class Locale @JvmOverloads constructor( else -> "${language}-${script}-${region}" } } - - class Builder { - private var language: String? = null - fun setLanguage(language: String) : Builder { - this.language = language - return this - } - - private var region: String? = null - - fun setRegion(region: String?) : Builder { - this.region = region - return this - } - - private var script: String? = null - fun setScript(script: String?) : Builder { - - this.script = script - return this - } - - fun build(): Locale { - language?.let { - return Locale(it, region, script) - } - throw IllegalArgumentException("Language should not be empty") - } - - } } \ No newline at end of file diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt index 748a05a..1f049d8 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt @@ -130,10 +130,9 @@ class IDLocalizedFeatureCollectionTest { assertEquals("Brückle", c["yet/another/id", austria]?.name) // merging scripts - val cryllic: List = listOf(Locale.Builder().setLanguage("de").setScript("Cyrl").build()) + val cryllic = listOf(Locale("de", null, "Cyrl")) assertEquals("бацкхаус", c["some/id", cryllic]?.name) - val cryllicAustria: List = - listOf(Locale.Builder().setLanguage("de").setRegion("AT").setScript("Cyrl").build()) + val cryllicAustria = listOf(Locale("de", "AT","Cyrl")) assertEquals("бацкхусл", c["some/id", cryllicAustria]?.name) } From 7a91894acefc3b3788e1e2a1ea39aef16b57603f Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 18:24:02 +0100 Subject: [PATCH 37/98] simplify languageTag function --- .../kotlin/de.westnordost.osmfeatures/Locale.kt | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt index ba552a5..eadfba2 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt @@ -27,15 +27,7 @@ data class Locale @JvmOverloads constructor( val default: Locale? = null } - val country : String - get() = this.region.orEmpty() - - val languageTag : String by lazy { - when { - region == null && script == null -> language - region == null -> "${language}-${script}" - script == null -> "${language}-${region}" - else -> "${language}-${script}-${region}" - } - } + /** IETF language tag */ + val languageTag: String + get() = listOfNotNull(language, script, region).joinToString("-") } \ No newline at end of file From aa29df661d58d06af41d5aca1d7713a5929f3eb9 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 18:31:12 +0100 Subject: [PATCH 38/98] no need for Locale constants in production code --- .../de.westnordost.osmfeatures/Locale.kt | 16 ----- .../FeatureDictionaryTest.kt | 66 ++++++++++--------- .../IDLocalizedFeatureCollectionTest.kt | 15 +++-- .../IDPresetsTranslationJsonParserTest.kt | 2 +- 4 files changed, 46 insertions(+), 53 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt index eadfba2..a1f9ddc 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt @@ -8,22 +8,6 @@ data class Locale @JvmOverloads constructor( val script: String? = null ) { companion object { - val ENGLISH: Locale = Locale("en") - - val UK: Locale = Locale("en","UK") - - val US: Locale = Locale("en","US") - - val FRENCH: Locale = Locale("fr") - - val ITALIAN: Locale = Locale("it") - - val GERMAN: Locale = Locale("de") - - val GERMANY: Locale = Locale("de", "DE") - - val CHINESE: Locale = Locale("zh") - val default: Locale? = null } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt index f0bfbc2..da69bd9 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt @@ -7,6 +7,12 @@ import kotlin.test.assertEquals import kotlin.test.assertNull class FeatureDictionaryTest { + private val ENGLISH = Locale("en") + private val UK = Locale("en","UK") + private val ITALIAN = Locale("it") + private val GERMAN = Locale("de") + private val CHINESE = Locale("zh") + private val bakery: Feature = feature( // unlocalized shop=bakery "shop/bakery", mapOf("shop" to "bakery"), @@ -33,7 +39,7 @@ class FeatureDictionaryTest { 1.0f, mapOf(), false, - Locale.ITALIAN + ITALIAN ) private val ditsch: Feature = feature( // brand in DE for shop=bakery "shop/bakery/Ditsch", @@ -89,7 +95,7 @@ class FeatureDictionaryTest { 1.0f, mapOf(), false, - Locale.UK + UK ) private val car_dealer: Feature = feature( // German localized unspecific shop=car "shop/car", @@ -103,7 +109,7 @@ class FeatureDictionaryTest { 1.0f, mapOf(), false, - Locale.GERMAN + GERMAN ) private val second_hand_car_dealer: Feature = feature( // German localized shop=car with subtags "shop/car/second_hand", @@ -117,7 +123,7 @@ class FeatureDictionaryTest { 1.0f, mapOf(), false, - Locale.GERMAN + GERMAN ) private val scheisshaus: Feature = feature( // unsearchable feature "amenity/scheißhaus", @@ -327,14 +333,14 @@ class FeatureDictionaryTest { fun find_no_entry_because_wrong_locale() { val tags: Map = mapOf("shop" to "bakery") val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(emptyList(), dictionary.byTags(tags).forLocale(Locale.ITALIAN).find()) + assertEquals(emptyList(), dictionary.byTags(tags).forLocale(ITALIAN).find()) } @Test fun find_entry_because_fallback_locale() { val tags: Map = mapOf("shop" to "bakery") val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTags(tags).forLocale(Locale.ITALIAN, null).find() + val matches: List = dictionary.byTags(tags).forLocale(ITALIAN, null).find() assertEquals(listOf(bakery), matches) } @@ -374,8 +380,8 @@ class FeatureDictionaryTest { fun find_only_entries_with_given_locale() { val tags: Map = mapOf("shop" to "bakery") val dictionary: FeatureDictionary = dictionary(bakery, panetteria) - assertEquals(listOf(panetteria), dictionary.byTags(tags).forLocale(Locale.ITALIAN).find()) - assertEquals(emptyList(), dictionary.byTags(tags).forLocale(Locale.ENGLISH).find()) + assertEquals(listOf(panetteria), dictionary.byTags(tags).forLocale(ITALIAN).find()) + assertEquals(emptyList(), dictionary.byTags(tags).forLocale(ENGLISH).find()) assertEquals(listOf(bakery), dictionary.byTags(tags).forLocale(null as Locale?).find()) } @@ -415,7 +421,7 @@ class FeatureDictionaryTest { fun do_not_find_entry_with_too_specific_tags() { val tags: Map = mapOf("shop" to "car") val dictionary: FeatureDictionary = dictionary(car_dealer, second_hand_car_dealer) - val matches: List = dictionary.byTags(tags).forLocale(Locale.GERMAN, null).find() + val matches: List = dictionary.byTags(tags).forLocale(GERMAN, null).find() assertEquals(listOf(car_dealer), matches) } @@ -423,7 +429,7 @@ class FeatureDictionaryTest { fun find_entry_with_specific_tags() { val tags: Map = mapOf("shop" to "car", "second_hand" to "only") val dictionary: FeatureDictionary = dictionary(car_dealer, second_hand_car_dealer) - val matches: List = dictionary.byTags(tags).forLocale(Locale.GERMAN, null).find() + val matches: List = dictionary.byTags(tags).forLocale(GERMAN, null).find() assertEquals(listOf(second_hand_car_dealer), matches) } @@ -513,18 +519,18 @@ class FeatureDictionaryTest { @Test fun find_entry_by_term_brackets() { val dictionary: FeatureDictionary = dictionary(liquor_store) - assertEquals(listOf(liquor_store), dictionary.byTerm("Alcohol").forLocale(Locale.UK).find()) + assertEquals(listOf(liquor_store), dictionary.byTerm("Alcohol").forLocale(UK).find()) assertEquals( listOf(liquor_store), - dictionary.byTerm("Off licence (Alcohol Shop)").forLocale(Locale.UK).find() + dictionary.byTerm("Off licence (Alcohol Shop)").forLocale(UK).find() ) assertEquals( listOf(liquor_store), - dictionary.byTerm("Off Licence").forLocale(Locale.UK).find() + dictionary.byTerm("Off Licence").forLocale(UK).find() ) assertEquals( listOf(liquor_store), - dictionary.byTerm("Off Licence (Alco").forLocale(Locale.UK).find() + dictionary.byTerm("Off Licence (Alco").forLocale(UK).find() ) } @@ -545,7 +551,7 @@ class FeatureDictionaryTest { @Test fun find_multiple_entries_by_term() { val dictionary: FeatureDictionary = dictionary(second_hand_car_dealer, car_dealer) - val matches: List = dictionary.byTerm("auto").forLocale(Locale.GERMAN).find() + val matches: List = dictionary.byTerm("auto").forLocale(GERMAN).find() assertEqualsIgnoreOrder(listOf(second_hand_car_dealer, car_dealer), matches) } @@ -553,7 +559,7 @@ class FeatureDictionaryTest { fun find_multiple_entries_by_name_but_respect_limit() { val dictionary: FeatureDictionary = dictionary(second_hand_car_dealer, car_dealer) val matches: List = - dictionary.byTerm("auto").forLocale(Locale.GERMAN).limit(1).find() + dictionary.byTerm("auto").forLocale(GERMAN).limit(1).find() assertEquals(1, matches.size) } @@ -576,14 +582,14 @@ class FeatureDictionaryTest { @Test fun find_no_entry_by_term_because_wrong_locale() { val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(emptyList(), dictionary.byTerm("Bäck").forLocale(Locale.ITALIAN).find()) + assertEquals(emptyList(), dictionary.byTerm("Bäck").forLocale(ITALIAN).find()) } @Test fun find_entry_by_term_because_fallback_locale() { val dictionary: FeatureDictionary = dictionary(bakery) val matches: List = - dictionary.byTerm("Bäck").forLocale(Locale.ITALIAN, null).find() + dictionary.byTerm("Bäck").forLocale(ITALIAN, null).find() assertEquals(listOf(bakery), matches) } @@ -625,7 +631,7 @@ class FeatureDictionaryTest { @Test fun find_entry_by_tag_value() { val dictionary: FeatureDictionary = dictionary(panetteria) - val matches: List = dictionary.byTerm("bakery").forLocale(Locale.ITALIAN).find() + val matches: List = dictionary.byTerm("bakery").forLocale(ITALIAN).find() assertEquals(listOf(panetteria), matches) } @@ -640,23 +646,23 @@ class FeatureDictionaryTest { @Test fun find_no_entry_by_id_because_unlocalized_results_are_excluded() { val dictionary: FeatureDictionary = dictionary(bakery) - assertNull(dictionary.byId("shop/bakery").forLocale(Locale.ITALIAN).get()) + assertNull(dictionary.byId("shop/bakery").forLocale(ITALIAN).get()) } @Test fun find_entry_by_id() { val dictionary: FeatureDictionary = dictionary(bakery) assertEquals(bakery, dictionary.byId("shop/bakery").get()) - assertEquals(bakery, dictionary.byId("shop/bakery").forLocale(Locale.CHINESE, null).get()) + assertEquals(bakery, dictionary.byId("shop/bakery").forLocale(CHINESE, null).get()) } @Test fun find_localized_entry_by_id() { val dictionary: FeatureDictionary = dictionary(panetteria) - assertEquals(panetteria, dictionary.byId("shop/bakery").forLocale(Locale.ITALIAN).get()) + assertEquals(panetteria, dictionary.byId("shop/bakery").forLocale(ITALIAN).get()) assertEquals( panetteria, - dictionary.byId("shop/bakery").forLocale(Locale.ITALIAN, null).get() + dictionary.byId("shop/bakery").forLocale(ITALIAN, null).get() ) } @@ -704,23 +710,23 @@ class FeatureDictionaryTest { fun some_tests_with_real_data() { val featureCollection: LocalizedFeatureCollection = IDLocalizedFeatureCollection(LivePresetDataAccessAdapter()) - featureCollection.getAll(listOf(Locale.ENGLISH)) + featureCollection.getAll(listOf(ENGLISH)) val dictionary = FeatureDictionary(featureCollection, null) val matches: List = dictionary .byTags(mapOf("amenity" to "studio")) - .forLocale(Locale.ENGLISH) + .forLocale(ENGLISH) .find() assertEquals(1, matches.size) assertEquals("Studio", matches[0].name) val matches2: List = dictionary .byTags(mapOf("amenity" to "studio", "studio" to "audio")) - .forLocale(Locale.ENGLISH) + .forLocale(ENGLISH) .find() assertEquals(1, matches2.size) assertEquals("Recording Studio", matches2[0].name) val matches3: List = dictionary .byTerm("Chinese Res") - .forLocale(Locale.ENGLISH) + .forLocale(ENGLISH) .find() assertEquals(1, matches3.size) assertEquals("Chinese Restaurant", matches3[0].name) @@ -750,21 +756,21 @@ class FeatureDictionaryTest { val dictionary: FeatureDictionary = dictionary(lush) val byTags: List = dictionary .byTags(mapOf("brand:wikidata" to "Q1585448", "shop" to "cosmetics")) - .forLocale(Locale.GERMAN, null) + .forLocale(GERMAN, null) .inCountry("DE") .find() assertEquals(1, byTags.size) assertEquals(lush, byTags[0]) val byTerm: List = dictionary .byTerm("Lush") - .forLocale(Locale.GERMAN, null) + .forLocale(GERMAN, null) .inCountry("DE") .find() assertEquals(1, byTerm.size) assertEquals(lush, byTerm[0]) val byId: Feature? = dictionary .byId("shop/cosmetics/lush-a08666") - .forLocale(Locale.GERMAN, null) + .forLocale(GERMAN, null) .inCountry("DE") .get() assertEquals(lush, byId) diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt index 1f049d8..4d5711d 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt @@ -9,6 +9,9 @@ import okio.Path.Companion.toPath import kotlin.test.* class IDLocalizedFeatureCollectionTest { + private val ENGLISH = Locale("en") + private val GERMAN = Locale("de") + @Test fun features_not_found_produces_runtime_exception() { try { @@ -56,7 +59,7 @@ class IDLocalizedFeatureCollectionTest { assertEquals("test", c["yet/another/id", notLocalized]?.name) // getting English features - val english: List = listOf(Locale.ENGLISH) + val english: List = listOf(ENGLISH) val englishFeatures: Collection = c.getAll(english) assertEqualsIgnoreOrder(listOf("Bakery"), getNames(englishFeatures)) assertEquals("Bakery", c["some/id", english]?.name) @@ -65,7 +68,7 @@ class IDLocalizedFeatureCollectionTest { // getting Germany features // this also tests if the fallback from de-DE to de works if de-DE.json does not exist - val germany: List = listOf(Locale.GERMANY) + val germany: List = listOf(GERMANY) val germanyFeatures: Collection = c.getAll(germany) assertEqualsIgnoreOrder(listOf("Bäckerei", "Gullideckel"), getNames(germanyFeatures)) assertEquals("Bäckerei", c["some/id", germany]?.name) @@ -73,14 +76,14 @@ class IDLocalizedFeatureCollectionTest { assertNull(c["yet/another/id", germany]) // getting features through fallback chain - val locales: List = listOf(Locale.ENGLISH, Locale.GERMANY, null) + val locales: List = listOf(ENGLISH, GERMANY, null) val fallbackFeatures: Collection = c.getAll(locales) assertEqualsIgnoreOrder(listOf("Bakery", "Gullideckel", "test"), getNames(fallbackFeatures)) assertEquals("Bakery", c["some/id", locales]?.name) assertEquals("Gullideckel", c["another/id", locales]?.name) assertEquals("test", c["yet/another/id", locales]?.name) - assertEquals(Locale.ENGLISH, c["some/id", locales]?.locale) - assertEquals(Locale.GERMAN, c["another/id", locales]?.locale) + assertEquals(ENGLISH, c["some/id", locales]?.locale) + assertEquals(GERMAN, c["another/id", locales]?.locale) assertNull(c["yet/another/id", locales]?.locale) } @@ -111,7 +114,7 @@ class IDLocalizedFeatureCollectionTest { }) // standard case - no merging - val german: List = listOf(Locale.GERMAN) + val german: List = listOf(GERMAN) val germanFeatures: Collection = c.getAll(german) assertEqualsIgnoreOrder(listOf("Bäckerei", "Gullideckel"), getNames(germanFeatures)) assertEquals("Bäckerei", c["some/id", german]?.name) diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt index 48e269f..e904b0d 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -55,7 +55,7 @@ class IDPresetsTranslationJsonParserTest { val featureMap = HashMap(baseFeatures.associateBy { it.id }) IDPresetsTranslationJsonParser().parse( getSource(translationsFile), - Locale.ENGLISH, + Locale("en"), featureMap ) } catch (e: IOException) { From ecdb82c838a8603e43dff8819fdeac23254a465c Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 18:51:59 +0100 Subject: [PATCH 39/98] format --- .../kotlin/de.westnordost.osmfeatures/BaseFeature.kt | 5 ++--- .../kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt index a9d1c43..44a4f09 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt @@ -34,7 +34,6 @@ open class BaseFeature( override val locale: Locale? get() = null - override fun toString(): String { - return id - } + override fun toString(): String = + id } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt index b1de3b9..6656590 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt @@ -39,7 +39,6 @@ class LocalizedFeature( override val isSuggestion: Boolean get() = p.isSuggestion - override fun toString(): String { - return id - } + override fun toString(): String = + id } From 19f53881032a74ce48e09bec02188579f517df1c Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 18:52:15 +0100 Subject: [PATCH 40/98] fix documentation comment --- .../kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt index 0e5c07c..209b980 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt @@ -6,7 +6,7 @@ package de.westnordost.osmfeatures * It is threadsafe because it is immutable. * * For example for the string maps... - *
+ * ```
  * [
  *   #1 (amenity -> bicycle_parking),
  *   #2 (amenity -> bicycle_parking, bicycle_parking -> shed),
@@ -14,9 +14,9 @@ package de.westnordost.osmfeatures
  *   #4 (amenity -> taxi),
  *   #5 (shop -> supermarket),
  * ]
- * 
+ * ``` * ...the tree internally looks like this: - *
+ * ```
  * amenity ->
  *   bicycle_parking ->
  *     #1
@@ -31,7 +31,7 @@ package de.westnordost.osmfeatures
  *   supermarket ->
  *     #5
  * ...
-* 
+* ``` */ internal class ContainedMapTree(maps: Collection>, maxDepth: Int = 4, minContainerSize: Int = 4) { private val root: Node From 2502926ca96ebb69de30484ba5959d7c61dbc082 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 18:52:24 +0100 Subject: [PATCH 41/98] simplify constructor --- .../ContainedMapTree.kt | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt index 209b980..3b9c7c6 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt @@ -33,19 +33,19 @@ package de.westnordost.osmfeatures * ... * ``` */ -internal class ContainedMapTree(maps: Collection>, maxDepth: Int = 4, minContainerSize: Int = 4) { - private val root: Node - - /** Create this index with the given maps. - * - * The generated tree will have a max depth of maxDepth and another depth is not added to the - * tree if there are less than minContainerSize maps in one tree node. - */ - init { - var maxDepth = maxDepth - if (maxDepth < 0) maxDepth = 0 - root = buildTree(maps, emptyList(), maxDepth, minContainerSize) - } +internal class ContainedMapTree +/** Create this index with the given maps. + * + * The generated tree will have a max depth of maxDepth and another depth is not added to the + * tree if there are less than minContainerSize maps in one tree node. + */ + constructor( + maps: Collection>, + maxDepth: Int = 4, + minContainerSize: Int = 4 +) { + private val root: Node = + buildTree(maps, emptyList(), maxDepth.coerceAtLeast(0), minContainerSize) /** Get all maps whose entries are completely contained by the given map */ fun getAll(map: Map?): List> { From a9ac25ea216ca16da2b0222863c22681d11dcd03 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 19:21:28 +0100 Subject: [PATCH 42/98] make non-nullable --- .../ContainedMapTree.kt | 15 +++++----- .../FeatureTagsIndex.kt | 2 +- .../StartsWithStringTree.kt | 28 +++++++------------ 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt index 3b9c7c6..3c2126b 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt @@ -48,17 +48,14 @@ internal class ContainedMapTree buildTree(maps, emptyList(), maxDepth.coerceAtLeast(0), minContainerSize) /** Get all maps whose entries are completely contained by the given map */ - fun getAll(map: Map?): List> { - return root.getAll(map!!) - } + fun getAll(map: Map): List> = root.getAll(map) private class Node( /** key -> (value -> Node) */ - val children: Map>>?, maps: Collection> + val children: Map>>, + val maps: Collection> ) { - val maps: Collection>? = maps - - /** Get all maps whose entries are all contained by given map */ + /** Get all maps whose entries are all contained by the given map */ fun getAll(map: Map): List> { val result: MutableList> = ArrayList() if (children != null) { @@ -90,7 +87,9 @@ internal class ContainedMapTree maxDepth: Int, minContainerSize: Int ): Node { - if (previousKeys.size == maxDepth || maps.size < minContainerSize) return Node(null, maps) + if (previousKeys.size == maxDepth || maps.size < minContainerSize) { + return Node(emptyMap(), maps) + } val unsortedMaps: MutableSet> = HashSet(maps) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt index f2bb45b..8b2bc17 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt @@ -18,7 +18,7 @@ internal class FeatureTagsIndex(features: Iterable) { tree = ContainedMapTree(featureMap.keys) } - fun getAll(tags: Map?): List { + fun getAll(tags: Map): List { val result: MutableList = ArrayList() for (map in tree.getAll(tags)) { val fs: List? = featureMap[map] diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt index 451f161..1a87c21 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import kotlin.jvm.JvmOverloads - /** Index that makes finding strings that start with characters very efficient. * It sorts the strings into a tree structure with configurable depth. * @@ -26,22 +24,16 @@ internal class StartsWithStringTree * The generated tree will have a max depth of maxDepth and another depth is not added to the * tree if there are less than minContainerSize strings in one tree node. */ -@JvmOverloads constructor( +constructor( strings: Collection, maxDepth: Int = 16, minContainerSize: Int = 16 ) { - private val root: Node = buildTree( - strings, - 0, - maxDepth.coerceAtLeast(0), - minContainerSize.coerceAtLeast(1) - ) + private val root: Node = + buildTree(strings, 0, maxDepth.coerceAtLeast(0), minContainerSize.coerceAtLeast(1)) /** Get all strings which start with the given string */ - fun getAll(startsWith: String): List { - return root.getAll(startsWith, 0) - } + fun getAll(startsWith: String): List = root.getAll(startsWith, 0) private class Node(val children: Map, val strings: Collection) { @@ -50,9 +42,9 @@ internal class StartsWithStringTree if (startsWith.isEmpty()) return emptyList() val result = ArrayList() - for ((key, value) in children) { - if (startsWith.length <= offset || key == startsWith[offset]) { - result.addAll(value.getAll(startsWith, offset + 1)) + for ((char, childNode) in children) { + if (startsWith.length <= offset || char == startsWith[offset]) { + result.addAll(childNode.getAll(startsWith, offset + 1)) } } for (string in strings) { @@ -76,9 +68,9 @@ internal class StartsWithStringTree val stringsByCharacter = strings.groupedByNthCharacter(currentDepth) val children = HashMap(stringsByCharacter.size) - for ((key, value) in stringsByCharacter) { - val c = key ?: continue - val child = buildTree(value, currentDepth + 1, maxDepth, minContainerSize) + for ((char, stringsForChar) in stringsByCharacter) { + val c = char ?: continue + val child = buildTree(stringsForChar, currentDepth + 1, maxDepth, minContainerSize) children[c] = child } val remainingStrings = stringsByCharacter[null].orEmpty() From ca1b7cd52d909c342aa6b6a3d21ed37ced20ce85 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 19:22:24 +0100 Subject: [PATCH 43/98] make into extension functions for better readability plus, use getOrPut --- .../ContainedMapTree.kt | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt index 3c2126b..3699b34 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt @@ -93,7 +93,7 @@ internal class ContainedMapTree val unsortedMaps: MutableSet> = HashSet(maps) - val mapsByKey = getMapsByKey(maps, previousKeys) + val mapsByKey = maps.groupByEachKey(previousKeys) /* the map should be categorized by frequent keys first and least frequent keys last. */ val sortedByCountDesc: List>>> = ArrayList(mapsByKey.entries).sortedByDescending { it.value.size } @@ -105,7 +105,7 @@ internal class ContainedMapTree (mapsForKey as MutableList).retainAll(unsortedMaps) if (mapsForKey.isEmpty()) continue - val featuresByValue: Map>> = getMapsByKeyValue(key, mapsForKey) + val featuresByValue: Map>> = mapsForKey.groupByByKeyValue(key) val valueNodes: MutableMap> = HashMap(featuresByValue.size) for ((value, featuresForValue) in featuresByValue) { @@ -124,32 +124,30 @@ internal class ContainedMapTree return Node(result, ArrayList(unsortedMaps)) } - /** returns the given features grouped by the map entry value of the given key. */ - private fun getMapsByKeyValue(key: K, maps: Collection>): Map>> { + /** returns these maps grouped by the map entry value of the given key. */ + private fun Collection>.groupByByKeyValue( + key: K + ): Map>> { val result = HashMap>>() - for (map in maps) { - val value = map[key] - value?.let { - if (!result.containsKey(it)) result[it] = ArrayList() - result[it]?.add(map) - } + for (map in this) { + val value = map[key] ?: continue + result.getOrPut(value) { ArrayList() }.add(map) } return result } - /** returns the given maps grouped by each of their keys (except the given ones). */ - private fun getMapsByKey( - maps: Collection>, + /** returns these maps grouped by each of their keys (except the given ones). */ + private fun Collection>.groupByEachKey( excludeKeys: Collection ): Map>> { val result = HashMap>>() - for (map in maps) { - for (key in map.keys.filter { !excludeKeys.contains(it) }) { - if (!result.containsKey(key)) result[key] = ArrayList() - result[key]?.add(map) + for (map in this) { + for (key in map.keys) { + if (excludeKeys.contains(key)) continue + result.getOrPut(key) { ArrayList() }.add(map) } } return result } } -} +} \ No newline at end of file From 3428fff0f3fb23c9202b6ec385f7f579b7174cb9 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 19:22:54 +0100 Subject: [PATCH 44/98] use more descriptive variable names --- .../ContainedMapTree.kt | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt index 3699b34..1e638ca 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt @@ -57,23 +57,19 @@ internal class ContainedMapTree ) { /** Get all maps whose entries are all contained by the given map */ fun getAll(map: Map): List> { - val result: MutableList> = ArrayList() - if (children != null) { - for ((key, value) in children) { - if (map.containsKey(key)) { - for ((keyNode, node) in value) { - if (keyNode == map[key]) { - result.addAll(node.getAll(map)) - } + val result = ArrayList>() + for ((key, nodesByValue) in children) { + if (map.containsKey(key)) { + for ((value, node) in nodesByValue) { + if (value == map[key]) { + result.addAll(node.getAll(map)) } } } } - if (maps != null) { - for (m in maps) { - if (m.all { entry -> map[entry.key] == entry.value } ) { - result.add(m) - } + for (m in maps) { + if (m.all { entry -> map[entry.key] == entry.value } ) { + result.add(m) } } return result @@ -91,7 +87,7 @@ internal class ContainedMapTree return Node(emptyMap(), maps) } - val unsortedMaps: MutableSet> = HashSet(maps) + val unsortedMaps = HashSet>(maps) val mapsByKey = maps.groupByEachKey(previousKeys) From 5c2ce261bdecaf7e190f600cea0b7b1647da5808 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 19:30:31 +0100 Subject: [PATCH 45/98] rename some variables, remove unnecessary type declarations --- .../ContainedMapTree.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt index 1e638ca..758599a 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt @@ -87,12 +87,12 @@ internal class ContainedMapTree return Node(emptyMap(), maps) } - val unsortedMaps = HashSet>(maps) + val unsortedMaps = HashSet(maps) val mapsByKey = maps.groupByEachKey(previousKeys) /* the map should be categorized by frequent keys first and least frequent keys last. */ - val sortedByCountDesc: List>>> = ArrayList(mapsByKey.entries).sortedByDescending { it.value.size } + val sortedByCountDesc = ArrayList(mapsByKey.entries).sortedByDescending { it.value.size } val result = HashMap>>(mapsByKey.size) @@ -101,16 +101,16 @@ internal class ContainedMapTree (mapsForKey as MutableList).retainAll(unsortedMaps) if (mapsForKey.isEmpty()) continue - val featuresByValue: Map>> = mapsForKey.groupByByKeyValue(key) + val mapsByKeyValue = mapsForKey.groupByKeyValue(key) - val valueNodes: MutableMap> = HashMap(featuresByValue.size) - for ((value, featuresForValue) in featuresByValue) { - val previousKeysNow: MutableList = ArrayList(previousKeys) + val nodesByValue = HashMap>(mapsByKeyValue.size) + for ((value, nodes) in mapsByKeyValue) { + val previousKeysNow = ArrayList(previousKeys) previousKeysNow.add(key) - valueNodes[value] = buildTree(featuresForValue, previousKeysNow, maxDepth, minContainerSize) + nodesByValue[value] = buildTree(nodes, previousKeysNow, maxDepth, minContainerSize) } - result[key] = valueNodes + result[key] = nodesByValue for (map in mapsForKey) { unsortedMaps.remove(map) @@ -121,7 +121,7 @@ internal class ContainedMapTree } /** returns these maps grouped by the map entry value of the given key. */ - private fun Collection>.groupByByKeyValue( + private fun Collection>.groupByKeyValue( key: K ): Map>> { val result = HashMap>>() From 753ba34459a9b60005fa1617dc4f2ab15964811e Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 19:41:28 +0100 Subject: [PATCH 46/98] remove TestUtils object, assertEqualsIgnoreOrder will report more helpful message on fail --- .../de.westnordost.osmfeatures/ContainedMapTreeTest.kt | 1 - .../FeatureDictionaryTest.kt | 1 - .../de.westnordost.osmfeatures/FeatureTermIndexTest.kt | 1 - .../IDBrandPresetsFeatureCollectionTest.kt | 1 - .../IDLocalizedFeatureCollectionTest.kt | 1 - .../StartsWithStringTreeTest.kt | 1 - .../kotlin/de.westnordost.osmfeatures/TestUtils.kt | 10 ++++------ 7 files changed, 4 insertions(+), 12 deletions(-) diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt index 8ded874..8e3a3bb 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt @@ -1,7 +1,6 @@ package de.westnordost.osmfeatures import kotlin.test.Test -import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder import kotlin.test.assertEquals import kotlin.test.assertTrue diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt index da69bd9..e7e1f93 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt @@ -1,7 +1,6 @@ package de.westnordost.osmfeatures import kotlin.test.Test -import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder import kotlin.test.assertTrue import kotlin.test.assertEquals import kotlin.test.assertNull diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt index e915f27..0e2fed1 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt @@ -1,7 +1,6 @@ package de.westnordost.osmfeatures import kotlin.test.Test -import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder import kotlin.test.assertEquals import kotlin.test.assertTrue diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt index 23cd626..07dfdc9 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt @@ -3,7 +3,6 @@ package de.westnordost.osmfeatures import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.Test -import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder import okio.Source class IDBrandPresetsFeatureCollectionTest { diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt index 4d5711d..354af94 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt @@ -2,7 +2,6 @@ package de.westnordost.osmfeatures import okio.FileSystem import okio.Source -import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder import okio.FileNotFoundException import okio.IOException import okio.Path.Companion.toPath diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt index a69c1f1..7d943ac 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt @@ -1,6 +1,5 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.TestUtils.assertEqualsIgnoreOrder import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt index 2422972..f4732e2 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt @@ -1,9 +1,7 @@ package de.westnordost.osmfeatures -import kotlin.test.assertTrue +import kotlin.test.assertEquals -object TestUtils { - fun assertEqualsIgnoreOrder(a: Collection, b: Collection) { - assertTrue(a.size == b.size && a.containsAll(b)) - } -} +fun assertEqualsIgnoreOrder(a: Collection, b: Collection) { + assertEquals(a.toSet(), b.toSet()) +} \ No newline at end of file From 6e6fb3594c041bdd548bbbab8a3e06c9b785d057 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 19:49:43 +0100 Subject: [PATCH 47/98] remove some unnecessary type declarations --- .../ContainedMapTreeTest.kt | 35 ++++++++----------- .../StartsWithStringTreeTest.kt | 13 +++---- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt index 8e3a3bb..74acad1 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt @@ -7,20 +7,19 @@ import kotlin.test.assertTrue class ContainedMapTreeTest { @Test fun copes_with_empty_feature_collection() { - val t = tree(emptyList()) - assertTrue(t.getAll(mapOf("a" to "b")).isEmpty()) + assertTrue(tree().getAll(mapOf("a" to "b")).isEmpty()) } @Test fun find_single_map() { - val f1: Map = mapOf("a" to "b") - val t: ContainedMapTree = tree(listOf(f1)) + val f1 = mapOf("a" to "b") + val t = tree(f1) assertEquals(listOf(f1), t.getAll(mapOf("a" to "b", "c" to "d"))) } @Test - fun dont_find_single_map() { - val tree: ContainedMapTree = tree(listOf(mapOf("a" to "b"))) + fun do_not_find_single_map() { + val tree = tree(mapOf("a" to "b")) assertTrue(tree.getAll(mapOf()).isEmpty()) assertTrue(tree.getAll(mapOf("c" to "d")).isEmpty()) assertTrue(tree.getAll(mapOf("a" to "c")).isEmpty()) @@ -28,25 +27,21 @@ class ContainedMapTreeTest { @Test fun find_only_generic_map() { - val f1: Map = mapOf("a" to "b") - val f2: Map = mapOf("a" to "b", "c" to "d") - val tree: ContainedMapTree = tree(listOf(f1, f2)) + val f1 = mapOf("a" to "b") + val f2 = mapOf("a" to "b", "c" to "d") + val tree = tree(f1, f2) assertEquals(listOf(f1), tree.getAll(mapOf("a" to "b"))) } @Test fun find_map_with_one_match_and_with_several_matches() { - val f1: Map = mapOf("a" to "b") - val f2: Map = mapOf("a" to "b", "c" to "d") - val f3: Map = mapOf("a" to "b", "c" to "e") - val f4: Map = mapOf("a" to "b", "d" to "d") - val tree: ContainedMapTree = tree(listOf(f1, f2, f3, f4)) + val f1 = mapOf("a" to "b") + val f2 = mapOf("a" to "b", "c" to "d") + val f3 = mapOf("a" to "b", "c" to "e") + val f4 = mapOf("a" to "b", "d" to "d") + val tree = tree(f1, f2, f3, f4) assertEqualsIgnoreOrder(listOf(f1, f2), tree.getAll(mapOf("a" to "b", "c" to "d"))) } - - companion object { - private fun tree(items: Collection>): ContainedMapTree { - return ContainedMapTree(items) - } - } } + +private fun tree(vararg items: Map) = ContainedMapTree(items.toList()) diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt index 7d943ac..1c90d3a 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt @@ -8,21 +8,20 @@ class StartsWithStringTreeTest { @Test fun copes_with_empty_collection() { - val t = StartsWithStringTree(listOf()) - assertTrue(t.getAll("any").isEmpty()) + assertTrue(tree().getAll("any").isEmpty()) } @Test fun find_single_string() { - val t = StartsWithStringTree(listOf("anything")) + val t = tree("anything") assertEquals(listOf("anything"), t.getAll("a")) assertEquals(listOf("anything"), t.getAll("any")) assertEquals(listOf("anything"), t.getAll("anything")) } @Test - fun dont_find_single_string() { - val t = StartsWithStringTree(listOf("anything", "more", "etc")) + fun do_not_find_single_string() { + val t = tree("anything", "more", "etc") assertTrue(t.getAll("").isEmpty()) assertTrue(t.getAll("nything").isEmpty()) assertTrue(t.getAll("anything else").isEmpty()) @@ -30,7 +29,9 @@ class StartsWithStringTreeTest { @Test fun find_several_strings() { - val t = StartsWithStringTree(listOf("anything", "anybody", "anytime")) + val t = tree("anything", "anybody", "anytime") assertEqualsIgnoreOrder(listOf("anything", "anybody", "anytime"), t.getAll("any")) } } + +private fun tree(vararg strings: String) = StartsWithStringTree(strings.toList()) \ No newline at end of file From 7a956b9b5b1b85c729163449301781890baefa87 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Feb 2024 19:51:48 +0100 Subject: [PATCH 48/98] BaseFeature is immutable --- .../commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt index 44a4f09..d092d30 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt @@ -4,7 +4,7 @@ package de.westnordost.osmfeatures open class BaseFeature( override val id: String, override val tags: Map, - final override var geometry: List, + final override val geometry: List, private val _icon: String? = "", private val _imageURL: String? = "", private val _names: List, From 11d5088630387256e577439ac2c638d3fc3f9c86 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Feb 2024 00:47:42 +0100 Subject: [PATCH 49/98] remove unnecessary private vals from BaseFeature --- .../de.westnordost.osmfeatures/BaseFeature.kt | 55 +++++++------------ .../FeatureDictionaryTest.kt | 39 ++----------- 2 files changed, 25 insertions(+), 69 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt index d092d30..6fe4b71 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt @@ -1,39 +1,26 @@ package de.westnordost.osmfeatures /** Data class associated with the Feature interface. Represents a non-localized feature. */ -open class BaseFeature( - override val id: String, - override val tags: Map, - final override val geometry: List, - private val _icon: String? = "", - private val _imageURL: String? = "", - private val _names: List, - final override val terms: List, - final override val includeCountryCodes: List, - final override val excludeCountryCodes: List, - final override val isSearchable: Boolean, - final override val matchScore: Float, - final override val isSuggestion: Boolean, - final override val addTags: Map, - final override val removeTags: Map +data class BaseFeature( + override val id: String, + override val tags: Map, + override val geometry: List, + override val icon: String?, + override val imageURL: String?, + override val names: List, + override val terms: List, + override val includeCountryCodes: List, + override val excludeCountryCodes: List, + override val isSearchable: Boolean, + override val matchScore: Float, + override val isSuggestion: Boolean, + override val addTags: Map, + override val removeTags: Map ): Feature { - final override val canonicalNames: List = names.map { it.canonicalize() } - final override val canonicalTerms: List = terms.map { it.canonicalize() } + override val canonicalNames: List = names.map { it.canonicalize() } + override val canonicalTerms: List = terms.map { it.canonicalize() } - override val icon: String - get() = _icon ?: "" - - override val imageURL: String - get() = _imageURL ?: "" - - final override val names: List - get() = _names.ifEmpty { listOf("") } - - override val name: String - get() = names[0] - override val locale: Locale? - get() = null - - override fun toString(): String = - id -} + override val name: String get() = names[0] + override val locale: Locale? get() = null + override fun toString(): String = id +} \ No newline at end of file diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt index e7e1f93..d2edbfb 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt @@ -775,43 +775,12 @@ class FeatureDictionaryTest { assertEquals(lush, byId) } - internal class SuggestionFeature( - id: String, - tags: Map, - geometry: List, - icon: String?, - imageURL: String?, - names: List, - terms: List, - includeCountryCodes: List, - excludeCountryCodes: List, - searchable: Boolean, - matchScore: Float, - addTags: Map, - removeTags: Map - ) : BaseFeature( - id, - tags, - geometry, - icon, - imageURL, - names, - terms, - includeCountryCodes, - excludeCountryCodes, - searchable, - matchScore, - true, - addTags, - removeTags - ) - companion object { private val POINT: List = listOf(GeometryType.POINT) private fun dictionary(vararg entries: Feature): FeatureDictionary { return FeatureDictionary( - TestLocalizedFeatureCollection(entries.filterNot { it is SuggestionFeature }), - TestPerCountryFeatureCollection(entries.filterIsInstance()) + TestLocalizedFeatureCollection(entries.filterNot { it.isSuggestion }), + TestPerCountryFeatureCollection(entries.filter { it.isSuggestion }) ) } @@ -830,9 +799,9 @@ class FeatureDictionaryTest { locale: Locale? ): Feature { return if (isSuggestion) { - SuggestionFeature( + BaseFeature( id, tags, geometries, null, null, names, terms, countryCodes, - excludeCountryCodes, searchable, matchScore, addTags, mapOf() + excludeCountryCodes, searchable, matchScore, isSuggestion, addTags, mapOf() ) } else { val f = BaseFeature( From ce1c5a7217ad3a4ba1965760ca3672fccdca71a0 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Feb 2024 01:05:43 +0100 Subject: [PATCH 50/98] make synchronizedGetOrCreate an extension function --- .../CollectionUtils.kt | 22 ++++--- .../FeatureDictionary.kt | 61 ++++++------------- .../IDBrandPresetsFeatureCollection.kt | 8 +-- .../IDLocalizedFeatureCollection.kt | 20 +++--- 4 files changed, 41 insertions(+), 70 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt index dec00aa..75c95ac 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt @@ -1,14 +1,16 @@ package de.westnordost.osmfeatures -object CollectionUtils { - /** For the given map, get the value of the entry at the given key and if there is no - * entry yet, create it using the given create function thread-safely */ - fun synchronizedGetOrCreate(map: MutableMap, key: K, createFn: (K) -> V): V? { - synchronized(map) { - if (!map.containsKey(key)) { - map[key] = createFn(key) - } +/** For the given map, get the value of the entry at the given key and if there is no + * entry yet, create it using the given create function thread-safely */ +fun MutableMap.synchronizedGetOrCreate(key: K, createFn: (K) -> V): V { + synchronized(this) { + val value = get(key) + return if (value == null) { + val answer = createFn(key) + put(key, answer) + answer + } else { + value } - return map[key] } -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt index 1ad8ed4..8319e04 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt @@ -53,11 +53,11 @@ class FeatureDictionary internal constructor( if (tags.isEmpty()) return emptyList() val foundFeatures: MutableList = mutableListOf() if (isSuggestion == null || !isSuggestion) { - foundFeatures.addAll(getTagsIndex(locales)?.getAll(tags).orEmpty()) + foundFeatures.addAll(getTagsIndex(locales).getAll(tags)) } if (isSuggestion == null || isSuggestion) { val countryCodes = dissectCountryCode(countryCode) - foundFeatures.addAll(getBrandTagsIndex(countryCodes)?.getAll(tags).orEmpty()) + foundFeatures.addAll(getBrandTagsIndex(countryCodes).getAll(tags)) } foundFeatures.removeAll { feature: Feature -> !isFeatureMatchingParameters( @@ -167,7 +167,7 @@ class FeatureDictionary internal constructor( if (isSuggestion == null || !isSuggestion) { // a. matches with presets first val foundFeaturesByName: MutableList = - getNamesIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() + getNamesIndex(locales).getAll(canonicalSearch).toMutableList() foundFeaturesByName.removeAll { feature: Feature -> !isFeatureMatchingParameters( feature, @@ -186,7 +186,7 @@ class FeatureDictionary internal constructor( if (isSuggestion == null || isSuggestion) { // b. matches with brand names second val countryCodes = dissectCountryCode(countryCode) - val foundBrandFeatures = getBrandNamesIndex(countryCodes)?.getAll(canonicalSearch).orEmpty().toMutableList() + val foundBrandFeatures = getBrandNamesIndex(countryCodes).getAll(canonicalSearch).toMutableList() foundBrandFeatures.removeAll { feature: Feature -> !isFeatureMatchingParameters( feature, @@ -205,7 +205,7 @@ class FeatureDictionary internal constructor( } if (isSuggestion == null || !isSuggestion) { // c. matches with terms third - val foundFeaturesByTerm = getTermsIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() + val foundFeaturesByTerm = getTermsIndex(locales).getAll(canonicalSearch).toMutableList() foundFeaturesByTerm.removeAll { feature: Feature -> !isFeatureMatchingParameters( feature, @@ -232,7 +232,7 @@ class FeatureDictionary internal constructor( } if (isSuggestion == null || !isSuggestion) { // d. matches with tag values fourth - val foundFeaturesByTagValue = getTagValuesIndex(locales)?.getAll(canonicalSearch).orEmpty().toMutableList() + val foundFeaturesByTagValue = getTagValuesIndex(locales).getAll(canonicalSearch).toMutableList() foundFeaturesByTagValue.removeAll { feature: Feature -> !isFeatureMatchingParameters( feature, @@ -256,12 +256,8 @@ class FeatureDictionary internal constructor( //endregion //region Lazily get or create Indexes /** lazily get or create tags index for given locale(s) */ - private fun getTagsIndex(locales: List): FeatureTagsIndex? { - return CollectionUtils.synchronizedGetOrCreate( - tagsIndexes, - locales, - ::createTagsIndex - ) + private fun getTagsIndex(locales: List): FeatureTagsIndex { + return tagsIndexes.synchronizedGetOrCreate(locales, ::createTagsIndex) } private fun createTagsIndex(locales: List): FeatureTagsIndex { @@ -269,13 +265,8 @@ class FeatureDictionary internal constructor( } /** lazily get or create names index for given locale(s) */ - private fun getNamesIndex(locales: List): FeatureTermIndex? { - return CollectionUtils.synchronizedGetOrCreate( - namesIndexes, - locales, - ::createNamesIndex - ) - } + private fun getNamesIndex(locales: List): FeatureTermIndex = + namesIndexes.synchronizedGetOrCreate(locales, ::createNamesIndex) private fun createNamesIndex(locales: List): FeatureTermIndex { val features = featureCollection.getAll(locales) @@ -295,12 +286,8 @@ class FeatureDictionary internal constructor( } /** lazily get or create terms index for given locale(s) */ - private fun getTermsIndex(locales: List): FeatureTermIndex? { - return CollectionUtils.synchronizedGetOrCreate( - termsIndexes, - locales, - ::createTermsIndex - ) + private fun getTermsIndex(locales: List): FeatureTermIndex { + return termsIndexes.synchronizedGetOrCreate(locales, ::createTermsIndex) } private fun createTermsIndex(locales: List): FeatureTermIndex { @@ -311,12 +298,8 @@ class FeatureDictionary internal constructor( } /** lazily get or create tag values index */ - private fun getTagValuesIndex(locales: List): FeatureTermIndex? { - return CollectionUtils.synchronizedGetOrCreate( - tagValuesIndexes, - locales, - ::createTagValuesIndex - ) + private fun getTagValuesIndex(locales: List): FeatureTermIndex { + return tagValuesIndexes.synchronizedGetOrCreate(locales, ::createTagValuesIndex) } private fun createTagValuesIndex(locales: List): FeatureTermIndex { @@ -332,12 +315,8 @@ class FeatureDictionary internal constructor( } /** lazily get or create brand names index for country */ - private fun getBrandNamesIndex(countryCodes: List): FeatureTermIndex? { - return CollectionUtils.synchronizedGetOrCreate( - brandNamesIndexes, - countryCodes, - ::createBrandNamesIndex - ) + private fun getBrandNamesIndex(countryCodes: List): FeatureTermIndex { + return brandNamesIndexes.synchronizedGetOrCreate(countryCodes, ::createBrandNamesIndex) } private fun createBrandNamesIndex(countryCodes: List): FeatureTermIndex { @@ -351,12 +330,8 @@ class FeatureDictionary internal constructor( } /** lazily get or create tags index for the given countries */ - private fun getBrandTagsIndex(countryCodes: List): FeatureTagsIndex? { - return CollectionUtils.synchronizedGetOrCreate( - brandTagsIndexes, - countryCodes, - ::createBrandTagsIndex - ) + private fun getBrandTagsIndex(countryCodes: List): FeatureTagsIndex { + return brandTagsIndexes.synchronizedGetOrCreate(countryCodes, ::createBrandTagsIndex) } private fun createBrandTagsIndex(countryCodes: List): FeatureTagsIndex { diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt index 9b9feb4..6ddcaea 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt @@ -19,21 +19,21 @@ class IDBrandPresetsFeatureCollection internal constructor(private val fileAcces override fun getAll(countryCodes: List): Collection { val result: MutableMap = HashMap() for (cc in countryCodes) { - getOrLoadPerCountryFeatures(cc)?.let { result.putAll(it) } + result.putAll(getOrLoadPerCountryFeatures(cc)) } return result.values } override fun get(id: String, countryCodes: List): Feature? { for (countryCode in countryCodes) { - val result = getOrLoadPerCountryFeatures(countryCode)?.get(id) + val result = getOrLoadPerCountryFeatures(countryCode).get(id) if (result != null) return result } return null } - private fun getOrLoadPerCountryFeatures(countryCode: String?): LinkedHashMap? { - return CollectionUtils.synchronizedGetOrCreate(featuresByIdByCountryCode, countryCode) { this.loadPerCountryFeatures(it) } + private fun getOrLoadPerCountryFeatures(countryCode: String?): LinkedHashMap { + return featuresByIdByCountryCode.synchronizedGetOrCreate(countryCode, ::loadPerCountryFeatures) } private fun loadPerCountryFeatures(countryCode: String?): LinkedHashMap { diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt index 32d08a5..f753f46 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt @@ -26,8 +26,8 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : throw RuntimeException(e) } } - private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap? { - return CollectionUtils.synchronizedGetOrCreate(localizedFeatures, locales, ::loadLocalizedFeatures) + private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap { + return localizedFeatures.synchronizedGetOrCreate(locales, ::loadLocalizedFeatures) } private fun loadLocalizedFeatures(locales: List): LinkedHashMap { @@ -37,7 +37,7 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : val locale = it.previous() if (locale != null) { for (localeComponent in getLocaleComponents(locale)) { - getOrLoadLocalizedFeaturesList(localeComponent)?.let { it1 -> putAllFeatures(result, it1) } + putAllFeatures(result, getOrLoadLocalizedFeaturesList(localeComponent)) } } else { putAllFeatures(result, featuresById.values) @@ -46,14 +46,8 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : return result } - private fun getOrLoadLocalizedFeaturesList(locale: Locale): List? { - return CollectionUtils.synchronizedGetOrCreate( - localizedFeaturesList, locale - ) {locale: Locale? -> - loadLocalizedFeaturesList( - locale - ) - } + private fun getOrLoadLocalizedFeaturesList(locale: Locale): List { + return localizedFeaturesList.synchronizedGetOrCreate(locale, ::loadLocalizedFeaturesList) } private fun loadLocalizedFeaturesList(locale: Locale?): List { @@ -66,11 +60,11 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : } override fun getAll(locales: List): Collection { - return getOrLoadLocalizedFeatures(locales)?.values ?: emptyList() + return getOrLoadLocalizedFeatures(locales).values } override operator fun get(id: String, locales: List): Feature? { - return getOrLoadLocalizedFeatures(locales)?.get(id) + return getOrLoadLocalizedFeatures(locales)[id] } companion object { From e8968930c42267a7c884d7e2e8fa1ff8bf48b426 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Feb 2024 01:11:44 +0100 Subject: [PATCH 51/98] correct indentation --- .../FeatureDictionary.kt | 597 +++++++++--------- 1 file changed, 305 insertions(+), 292 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt index 8319e04..e4b4ad4 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt @@ -21,7 +21,9 @@ class FeatureDictionary internal constructor( getNamesIndex(listOf(default)) getTermsIndex(listOf(default)) } + //region Get by id + /** Find feature by id */ fun byId(id: String): QueryByIdBuilder { return QueryByIdBuilder(id) @@ -36,8 +38,11 @@ class FeatureDictionary internal constructor( } throw NullPointerException("brandFeatureCollection is null") } + //endregion + //region Query by tags + /** Find matches by a set of tags */ fun byTags(tags: Map): QueryByTagBuilder { return QueryByTagBuilder(tags) @@ -109,8 +114,11 @@ class FeatureDictionary internal constructor( } }) } + //endregion + //region Query by term + /** Find matches by given search word */ fun byTerm(term: String): QueryByTermBuilder { return QueryByTermBuilder(term) @@ -250,341 +258,346 @@ class FeatureDictionary internal constructor( result.addAll(foundFeaturesByTagValue) } } - return result.subList(0, min(limit.toDouble(), result.size.toDouble()).toInt()) - + return result.subList(0, min(limit.toDouble(), result.size.toDouble()).toInt()) } - //endregion - //region Lazily get or create Indexes - /** lazily get or create tags index for given locale(s) */ - private fun getTagsIndex(locales: List): FeatureTagsIndex { - return tagsIndexes.synchronizedGetOrCreate(locales, ::createTagsIndex) - } - - private fun createTagsIndex(locales: List): FeatureTagsIndex { - return FeatureTagsIndex(featureCollection.getAll(locales)) - } - - /** lazily get or create names index for given locale(s) */ - private fun getNamesIndex(locales: List): FeatureTermIndex = - namesIndexes.synchronizedGetOrCreate(locales, ::createNamesIndex) - - private fun createNamesIndex(locales: List): FeatureTermIndex { - val features = featureCollection.getAll(locales) - return FeatureTermIndex(features, FeatureTermIndex.Selector { feature: Feature -> - if (!feature.isSearchable) return@Selector emptyList() - val names: List = feature.canonicalNames - val result = ArrayList(names) - for (name in names) { - if (name.contains(" ")) { - result.addAll( - name.replace("[()]", "").split(" ") - ) - } - } - result - }) - } - /** lazily get or create terms index for given locale(s) */ - private fun getTermsIndex(locales: List): FeatureTermIndex { - return termsIndexes.synchronizedGetOrCreate(locales, ::createTermsIndex) - } + //endregion - private fun createTermsIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature -> - if (!feature.isSearchable) return@Selector emptyList() - feature.canonicalTerms - }) - } + //region Lazily get or create Indexes - /** lazily get or create tag values index */ - private fun getTagValuesIndex(locales: List): FeatureTermIndex { - return tagValuesIndexes.synchronizedGetOrCreate(locales, ::createTagValuesIndex) - } + /** lazily get or create tags index for given locale(s) */ + private fun getTagsIndex(locales: List): FeatureTagsIndex { + return tagsIndexes.synchronizedGetOrCreate(locales, ::createTagsIndex) + } - private fun createTagValuesIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature -> - if (!feature.isSearchable) return@Selector emptyList() + private fun createTagsIndex(locales: List): FeatureTagsIndex { + return FeatureTagsIndex(featureCollection.getAll(locales)) + } - val result: ArrayList = ArrayList(feature.tags.size) - for (tagValue in feature.tags.values) { - if (tagValue != "*") result.add(tagValue) + /** lazily get or create names index for given locale(s) */ + private fun getNamesIndex(locales: List): FeatureTermIndex = + namesIndexes.synchronizedGetOrCreate(locales, ::createNamesIndex) + + private fun createNamesIndex(locales: List): FeatureTermIndex { + val features = featureCollection.getAll(locales) + return FeatureTermIndex(features, FeatureTermIndex.Selector { feature: Feature -> + if (!feature.isSearchable) return@Selector emptyList() + val names: List = feature.canonicalNames + val result = ArrayList(names) + for (name in names) { + if (name.contains(" ")) { + result.addAll( + name.replace("[()]", "").split(" ") + ) + } } - return@Selector result - }) - } + result + }) + } - /** lazily get or create brand names index for country */ - private fun getBrandNamesIndex(countryCodes: List): FeatureTermIndex { - return brandNamesIndexes.synchronizedGetOrCreate(countryCodes, ::createBrandNamesIndex) - } + /** lazily get or create terms index for given locale(s) */ + private fun getTermsIndex(locales: List): FeatureTermIndex { + return termsIndexes.synchronizedGetOrCreate(locales, ::createTermsIndex) + } + + private fun createTermsIndex(locales: List): FeatureTermIndex { + return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature -> + if (!feature.isSearchable) return@Selector emptyList() + feature.canonicalTerms + }) + } + + /** lazily get or create tag values index */ + private fun getTagValuesIndex(locales: List): FeatureTermIndex { + return tagValuesIndexes.synchronizedGetOrCreate(locales, ::createTagValuesIndex) + } - private fun createBrandNamesIndex(countryCodes: List): FeatureTermIndex { - return if (brandFeatureCollection == null) { - FeatureTermIndex(emptyList(), null) - } else FeatureTermIndex( - brandFeatureCollection.getAll(countryCodes) - ) { - if (!it.isSearchable) emptyList() else it.canonicalNames + private fun createTagValuesIndex(locales: List): FeatureTermIndex { + return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature -> + if (!feature.isSearchable) return@Selector emptyList() + + val result: ArrayList = ArrayList(feature.tags.size) + for (tagValue in feature.tags.values) { + if (tagValue != "*") result.add(tagValue) } - } + return@Selector result + }) + } - /** lazily get or create tags index for the given countries */ - private fun getBrandTagsIndex(countryCodes: List): FeatureTagsIndex { - return brandTagsIndexes.synchronizedGetOrCreate(countryCodes, ::createBrandTagsIndex) - } + /** lazily get or create brand names index for country */ + private fun getBrandNamesIndex(countryCodes: List): FeatureTermIndex { + return brandNamesIndexes.synchronizedGetOrCreate(countryCodes, ::createBrandNamesIndex) + } - private fun createBrandTagsIndex(countryCodes: List): FeatureTagsIndex { - return if (brandFeatureCollection == null) { - FeatureTagsIndex(emptyList()) - } else FeatureTagsIndex(brandFeatureCollection.getAll(countryCodes)) + private fun createBrandNamesIndex(countryCodes: List): FeatureTermIndex { + return if (brandFeatureCollection == null) { + FeatureTermIndex(emptyList(), null) + } else FeatureTermIndex( + brandFeatureCollection.getAll(countryCodes) + ) { + if (!it.isSearchable) emptyList() else it.canonicalNames } + } - //endregion - //region Query builders - inner class QueryByIdBuilder(private val id: String) { - private var locale: List = listOf(default) - private var countryCode: String? = null + /** lazily get or create tags index for the given countries */ + private fun getBrandTagsIndex(countryCodes: List): FeatureTagsIndex { + return brandTagsIndexes.synchronizedGetOrCreate(countryCodes, ::createBrandTagsIndex) + } - private operator fun get(id: String, locales: List, countryCode: String?): Feature? { - return this@FeatureDictionary[id, locales, countryCode] - } + private fun createBrandTagsIndex(countryCodes: List): FeatureTagsIndex { + return if (brandFeatureCollection == null) { + FeatureTagsIndex(emptyList()) + } else FeatureTagsIndex(brandFeatureCollection.getAll(countryCodes)) + } - /** - * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * - * - * `null` means to include unlocalized results. - * - * - * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, - * i.e. unlocalized results are included by default. - */ - fun forLocale(vararg locales: Locale?): QueryByIdBuilder { - this.locale = locales.toList() - return this - } + //endregion - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByIdBuilder { - this.countryCode = countryCode - return this - } + //region Query builders - /** Returns the feature associated with the given id or `null` if it does not - * exist */ - fun get(): Feature? { - return this[id, locale, countryCode] - } + inner class QueryByIdBuilder(private val id: String) { + private var locale: List = listOf(default) + private var countryCode: String? = null + + private operator fun get(id: String, locales: List, countryCode: String?): Feature? { + return this@FeatureDictionary[id, locales, countryCode] } - inner class QueryByTagBuilder(private val tags: Map) { - private var geometryType: GeometryType? = null - private var locale: List = listOf(default) - private var suggestion: Boolean? = null - private var countryCode: String? = null + /** + * + *Sets the locale(s) in which to present the results. + * + * You can specify several locales in + * a row to each fall back to if a translation does not exist in the locale before that. + * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you + * wanted results preferredly in Catalan, but Spanish is also fine. + * + * + * `null` means to include unlocalized results. + * + * + * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, + * i.e. unlocalized results are included by default. + */ + fun forLocale(vararg locales: Locale?): QueryByIdBuilder { + this.locale = locales.toList() + return this + } - /** Sets for which geometry type to look. If not set or `null`, any will match. */ - fun forGeometry(geometryType: GeometryType): QueryByTagBuilder { - this.geometryType = geometryType - return this - } + /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the + * country/state the element is in. If not specified, will only return matches that are not + * county-specific. */ + fun inCountry(countryCode: String?): QueryByIdBuilder { + this.countryCode = countryCode + return this + } - /** - * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * - * - * `null` means to include unlocalized results. - * - * - * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, - * i.e. unlocalized results are included by default. - */ - fun forLocale(vararg locales: Locale?): QueryByTagBuilder { - this.locale = locales.toList() - return this - } + /** Returns the feature associated with the given id or `null` if it does not + * exist */ + fun get(): Feature? { + return this[id, locale, countryCode] + } + } - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByTagBuilder { - this.countryCode = countryCode - return this - } + inner class QueryByTagBuilder(private val tags: Map) { + private var geometryType: GeometryType? = null + private var locale: List = listOf(default) + private var suggestion: Boolean? = null + private var countryCode: String? = null - /** Set whether to only include suggestions (=true) or to not include suggestions (=false). - * Suggestions are brands, like 7-Eleven. */ - fun isSuggestion(suggestion: Boolean?): QueryByTagBuilder { - this.suggestion = suggestion - return this - } + /** Sets for which geometry type to look. If not set or `null`, any will match. */ + fun forGeometry(geometryType: GeometryType): QueryByTagBuilder { + this.geometryType = geometryType + return this + } - /** Returns a list of dictionary entries that match or an empty list if nothing is - * found.

In rare cases, a set of tags may match multiple primary features, such as for - * tag combinations like `shop=deli` + `amenity=cafe`, so, this is why - * it is a list. */ - fun find(): List { - return get(tags, geometryType, countryCode, suggestion, locale) - } + /** + * + *Sets the locale(s) in which to present the results. + * + * You can specify several locales in + * a row to each fall back to if a translation does not exist in the locale before that. + * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you + * wanted results preferredly in Catalan, but Spanish is also fine. + * + * + * `null` means to include unlocalized results. + * + * + * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, + * i.e. unlocalized results are included by default. + */ + fun forLocale(vararg locales: Locale?): QueryByTagBuilder { + this.locale = locales.toList() + return this } - inner class QueryByTermBuilder(private val term: String) { - private var geometryType: GeometryType? = null - private var locale: List = listOf(default) - private var suggestion: Boolean? = null - private var limit = 50 - private var countryCode: String? = null + /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the + * country/state the element is in. If not specified, will only return matches that are not + * county-specific. */ + fun inCountry(countryCode: String?): QueryByTagBuilder { + this.countryCode = countryCode + return this + } - /** Sets for which geometry type to look. If not set or `null`, any will match. */ - fun forGeometry(geometryType: GeometryType): QueryByTermBuilder { - this.geometryType = geometryType - return this - } + /** Set whether to only include suggestions (=true) or to not include suggestions (=false). + * Suggestions are brands, like 7-Eleven. */ + fun isSuggestion(suggestion: Boolean?): QueryByTagBuilder { + this.suggestion = suggestion + return this + } - /** - * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * - * - * `null` means to include unlocalized results. - * - * - * If nothing is specified, it defaults to `[Locale.getDefault()]`, i.e. - * unlocalized results are excluded by default. - */ - fun forLocale(vararg locales: Locale?): QueryByTermBuilder { - this.locale = locales.toList() - return this - } + /** Returns a list of dictionary entries that match or an empty list if nothing is + * found.

In rare cases, a set of tags may match multiple primary features, such as for + * tag combinations like `shop=deli` + `amenity=cafe`, so, this is why + * it is a list. */ + fun find(): List { + return get(tags, geometryType, countryCode, suggestion, locale) + } + } - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByTermBuilder { - this.countryCode = countryCode - return this - } + inner class QueryByTermBuilder(private val term: String) { + private var geometryType: GeometryType? = null + private var locale: List = listOf(default) + private var suggestion: Boolean? = null + private var limit = 50 + private var countryCode: String? = null + + /** Sets for which geometry type to look. If not set or `null`, any will match. */ + fun forGeometry(geometryType: GeometryType): QueryByTermBuilder { + this.geometryType = geometryType + return this + } - /** Set whether to only include suggestions (=true) or to not include suggestions (=false). - * Suggestions are brands, like 7-Eleven. */ - fun isSuggestion(suggestion: Boolean?): QueryByTermBuilder { - this.suggestion = suggestion - return this - } + /** + * + *Sets the locale(s) in which to present the results. + * + * You can specify several locales in + * a row to each fall back to if a translation does not exist in the locale before that. + * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you + * wanted results preferredly in Catalan, but Spanish is also fine. + * + * + * `null` means to include unlocalized results. + * + * + * If nothing is specified, it defaults to `[Locale.getDefault()]`, i.e. + * unlocalized results are excluded by default. + */ + fun forLocale(vararg locales: Locale?): QueryByTermBuilder { + this.locale = locales.toList() + return this + } - /** limit how many results to return at most. Default is 50, -1 for unlimited. */ - fun limit(limit: Int): QueryByTermBuilder { - this.limit = limit - return this - } + /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the + * country/state the element is in. If not specified, will only return matches that are not + * county-specific. */ + fun inCountry(countryCode: String?): QueryByTermBuilder { + this.countryCode = countryCode + return this + } - /** Returns a list of dictionary entries that match or an empty list if nothing is - * found.

- * Results are sorted mainly in this order: Matches with names, with brand names, then - * matches with terms (keywords). */ - fun find(): List { - return get(term, geometryType, countryCode, suggestion, limit, locale) - } - } //endregion - - companion object { - private val VALID_COUNTRY_CODE_REGEX = Regex("([A-Z]{2})(?:-([A-Z0-9]{1,3}))?") - /** Create a new FeatureDictionary which gets its data from the given directory. Optionally, - * a path to brand presets can be specified. */ - /** Create a new FeatureDictionary which gets its data from the given directory. */ - - fun create(presetsBasePath: String, brandPresetsBasePath: String? = null): FeatureDictionary { - val featureCollection: LocalizedFeatureCollection = - IDLocalizedFeatureCollection(FileSystemAccess(presetsBasePath)) - val brandsFeatureCollection: PerCountryFeatureCollection? = - if (brandPresetsBasePath != null) IDBrandPresetsFeatureCollection( - FileSystemAccess(brandPresetsBasePath) - ) else null - return FeatureDictionary(featureCollection, brandsFeatureCollection) - } + /** Set whether to only include suggestions (=true) or to not include suggestions (=false). + * Suggestions are brands, like 7-Eleven. */ + fun isSuggestion(suggestion: Boolean?): QueryByTermBuilder { + this.suggestion = suggestion + return this + } - //endregion - //region Utility / Filter functions - private fun getParentCategoryIds(id: String): Collection { - var currentId: String? = id - val result: MutableList = ArrayList() - do { - currentId = getParentId(currentId) - if (currentId != null) result.add(currentId) - } while (currentId != null) - return result - } + /** limit how many results to return at most. Default is 50, -1 for unlimited. */ + fun limit(limit: Int): QueryByTermBuilder { + this.limit = limit + return this + } - private fun getParentId(id: String?): String? { - val lastSlashIndex = id!!.lastIndexOf("/") - return if (lastSlashIndex == -1) null else id.substring(0, lastSlashIndex) - } + /** Returns a list of dictionary entries that match or an empty list if nothing is + * found.

+ * Results are sorted mainly in this order: Matches with names, with brand names, then + * matches with terms (keywords). */ + fun find(): List { + return get(term, geometryType, countryCode, suggestion, limit, locale) + } + } + //endregion - private fun isFeatureMatchingParameters( - feature: Feature, - geometry: GeometryType?, - countryCode: String? - ): Boolean { - if (geometry != null && !feature.geometry.contains(geometry)) return false - val include: List = feature.includeCountryCodes - val exclude: List = feature.excludeCountryCodes - if (include.isNotEmpty() || exclude.isNotEmpty()) { - if (countryCode == null) return false - if (include.isNotEmpty() && !matchesAnyCountryCode(countryCode, include)) return false - if (matchesAnyCountryCode(countryCode, exclude)) return false - } - return true - } + companion object { + private val VALID_COUNTRY_CODE_REGEX = Regex("([A-Z]{2})(?:-([A-Z0-9]{1,3}))?") + /** Create a new FeatureDictionary which gets its data from the given directory. Optionally, + * a path to brand presets can be specified. */ + /** Create a new FeatureDictionary which gets its data from the given directory. */ + + fun create(presetsBasePath: String, brandPresetsBasePath: String? = null): FeatureDictionary { + val featureCollection: LocalizedFeatureCollection = + IDLocalizedFeatureCollection(FileSystemAccess(presetsBasePath)) + val brandsFeatureCollection: PerCountryFeatureCollection? = + if (brandPresetsBasePath != null) IDBrandPresetsFeatureCollection( + FileSystemAccess(brandPresetsBasePath) + ) else null + return FeatureDictionary(featureCollection, brandsFeatureCollection) + } - private fun dissectCountryCode(countryCode: String?): List { - val result: MutableList = ArrayList() - // add default / international - result.add(null) - countryCode?.let { - val matcher = VALID_COUNTRY_CODE_REGEX.find(it) - if(matcher?.groups?.get(1) != null) { - result.add(matcher.groups[1]?.value) - - // add ISO 3166-1 alpha2 (e.g. "US") - if (matcher.groups.size != 2 && matcher.groups[2] != null) { - // add ISO 3166-2 (e.g. "US-NY") - result.add(countryCode) - } - } + //region Utility / Filter functions + private fun getParentCategoryIds(id: String): Collection { + var currentId: String? = id + val result: MutableList = ArrayList() + do { + currentId = getParentId(currentId) + if (currentId != null) result.add(currentId) + } while (currentId != null) + return result + } + + private fun getParentId(id: String?): String? { + val lastSlashIndex = id!!.lastIndexOf("/") + return if (lastSlashIndex == -1) null else id.substring(0, lastSlashIndex) + } + private fun isFeatureMatchingParameters( + feature: Feature, + geometry: GeometryType?, + countryCode: String? + ): Boolean { + if (geometry != null && !feature.geometry.contains(geometry)) return false + val include: List = feature.includeCountryCodes + val exclude: List = feature.excludeCountryCodes + if (include.isNotEmpty() || exclude.isNotEmpty()) { + if (countryCode == null) return false + if (include.isNotEmpty() && !matchesAnyCountryCode(countryCode, include)) return false + if (matchesAnyCountryCode(countryCode, exclude)) return false + } + return true + } + + private fun dissectCountryCode(countryCode: String?): List { + val result: MutableList = ArrayList() + // add default / international + result.add(null) + countryCode?.let { + val matcher = VALID_COUNTRY_CODE_REGEX.find(it) + if(matcher?.groups?.get(1) != null) { + result.add(matcher.groups[1]?.value) + + // add ISO 3166-1 alpha2 (e.g. "US") + if (matcher.groups.size != 2 && matcher.groups[2] != null) { + // add ISO 3166-2 (e.g. "US-NY") + result.add(countryCode) + } } - return result - } - private fun matchesAnyCountryCode(showOnly: String, featureCountryCodes: List): Boolean { - return featureCountryCodes.any { matchesCountryCode(showOnly, it) } - } - private fun matchesCountryCode(showOnly: String, featureCountryCode: String): Boolean { - return showOnly == featureCountryCode || showOnly.substring(0, 2) == featureCountryCode } + return result } - } + private fun matchesAnyCountryCode(showOnly: String, featureCountryCodes: List): Boolean { + return featureCountryCodes.any { matchesCountryCode(showOnly, it) } + } + + private fun matchesCountryCode(showOnly: String, featureCountryCode: String): Boolean { + return showOnly == featureCountryCode || showOnly.substring(0, 2) == featureCountryCode + } + //endregion + } +} \ No newline at end of file From fe5e4de9c2b9018c9899ba489ea61a3469893124 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Feb 2024 01:25:03 +0100 Subject: [PATCH 52/98] fixed indentation, removed unnecessary type declarations, use getOrPut --- .../ContainedMapTree.kt | 8 ++++--- .../FeatureTagsIndex.kt | 14 ++++++------ .../FeatureTermIndex.kt | 22 ++++++++++--------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt index 758599a..7f71436 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt @@ -121,13 +121,14 @@ internal class ContainedMapTree } /** returns these maps grouped by the map entry value of the given key. */ - private fun Collection>.groupByKeyValue( + private fun Collection>.groupByKeyValue( key: K ): Map>> { val result = HashMap>>() for (map in this) { val value = map[key] ?: continue - result.getOrPut(value) { ArrayList() }.add(map) + val group = result.getOrPut(value) { ArrayList() } + group.add(map) } return result } @@ -140,7 +141,8 @@ internal class ContainedMapTree for (map in this) { for (key in map.keys) { if (excludeKeys.contains(key)) continue - result.getOrPut(key) { ArrayList() }.add(map) + val group = result.getOrPut(key) { ArrayList() } + group.add(map) } } return result diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt index 8b2bc17..cae7d91 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt @@ -1,25 +1,25 @@ package de.westnordost.osmfeatures -/** Index that makes finding Features whose tags are completely contained by a given set of tags +/** + * Index that makes finding Features whose tags are completely contained by a given set of tags * very efficient. * * Based on ContainedMapTree data structure, see that class. */ -internal class FeatureTagsIndex(features: Iterable) { +internal class FeatureTagsIndex(features: Collection) { private val featureMap: MutableMap, MutableList> private val tree: ContainedMapTree init { - featureMap = HashMap() + featureMap = HashMap(features.size) for (feature in features) { - val map: Map = feature.tags - if (!featureMap.containsKey(map)) featureMap[map] = ArrayList(1) - featureMap[map]?.add(feature) + val map = featureMap.getOrPut(feature.tags) { ArrayList(1) } + map.add(feature) } tree = ContainedMapTree(featureMap.keys) } fun getAll(tags: Map): List { - val result: MutableList = ArrayList() + val result = ArrayList() for (map in tree.getAll(tags)) { val fs: List? = featureMap[map] if (fs != null) result.addAll(fs) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt index bed76b3..b6d6fd4 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt @@ -1,30 +1,32 @@ package de.westnordost.osmfeatures -/** Index that makes finding Features whose name/term/... starts with a given string very efficient. +/** + * Index that makes finding Features whose name/term/... starts with a given string very efficient. * * Based on the StartsWithStringTree data structure, see that class. */ class FeatureTermIndex(features: Collection, selector: Selector?) { - private val featureMap: MutableMap> = HashMap() + private val featureMap: MutableMap> private val tree: StartsWithStringTree init { - for (feature in features) { - val strings: Collection = selector?.getStrings(feature) ?: emptyList() - for (string in strings) { - if (!featureMap.containsKey(string)) featureMap[string] = ArrayList(1) - featureMap[string]?.add(feature) - } + featureMap = HashMap(features.size) + for (feature in features) { + val strings = selector?.getStrings(feature) ?: emptyList() + for (string in strings) { + val map = featureMap.getOrPut(string) { ArrayList(1) } + map.add(feature) } + } tree = StartsWithStringTree(featureMap.keys) } fun getAll(startsWith: String): List { - val result: MutableSet = HashSet() + val result = HashSet() for (string in tree.getAll(startsWith)) { val fs: List? = featureMap[string] if (fs != null) result.addAll(fs) } - return ArrayList(result) + return result.toList() } fun interface Selector { From 688d12513a20324cb85bd03970669f1f60fee21c Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Feb 2024 01:33:35 +0100 Subject: [PATCH 53/98] replace selector interface with function --- .../FeatureDictionary.kt | 45 +++++++++---------- .../FeatureTermIndex.kt | 8 +--- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt index e4b4ad4..38d409d 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt @@ -280,19 +280,19 @@ class FeatureDictionary internal constructor( private fun createNamesIndex(locales: List): FeatureTermIndex { val features = featureCollection.getAll(locales) - return FeatureTermIndex(features, FeatureTermIndex.Selector { feature: Feature -> - if (!feature.isSearchable) return@Selector emptyList() + return FeatureTermIndex(features) { feature: Feature -> + if (!feature.isSearchable) return@FeatureTermIndex emptyList() val names: List = feature.canonicalNames val result = ArrayList(names) - for (name in names) { - if (name.contains(" ")) { - result.addAll( - name.replace("[()]", "").split(" ") - ) - } + for (name in names) { + if (name.contains(" ")) { + result.addAll( + name.replace("[()]", "").split(" ") + ) } + } result - }) + } } /** lazily get or create terms index for given locale(s) */ @@ -301,10 +301,9 @@ class FeatureDictionary internal constructor( } private fun createTermsIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature -> - if (!feature.isSearchable) return@Selector emptyList() - feature.canonicalTerms - }) + return FeatureTermIndex(featureCollection.getAll(locales)) { feature: Feature -> + if (!feature.isSearchable) emptyList() else feature.canonicalTerms + } } /** lazily get or create tag values index */ @@ -313,15 +312,15 @@ class FeatureDictionary internal constructor( } private fun createTagValuesIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales), FeatureTermIndex.Selector { feature: Feature -> - if (!feature.isSearchable) return@Selector emptyList() + return FeatureTermIndex(featureCollection.getAll(locales)) { feature: Feature -> + if (!feature.isSearchable) return@FeatureTermIndex emptyList() - val result: ArrayList = ArrayList(feature.tags.size) + val result = ArrayList(feature.tags.size) for (tagValue in feature.tags.values) { if (tagValue != "*") result.add(tagValue) } - return@Selector result - }) + return@FeatureTermIndex result + } } /** lazily get or create brand names index for country */ @@ -331,11 +330,11 @@ class FeatureDictionary internal constructor( private fun createBrandNamesIndex(countryCodes: List): FeatureTermIndex { return if (brandFeatureCollection == null) { - FeatureTermIndex(emptyList(), null) - } else FeatureTermIndex( - brandFeatureCollection.getAll(countryCodes) - ) { - if (!it.isSearchable) emptyList() else it.canonicalNames + FeatureTermIndex(emptyList()) { emptyList() } + } else { + FeatureTermIndex(brandFeatureCollection.getAll(countryCodes)) { feature -> + if (!feature.isSearchable) emptyList() else feature.canonicalNames + } } } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt index b6d6fd4..12d4161 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt @@ -4,14 +4,14 @@ package de.westnordost.osmfeatures * Index that makes finding Features whose name/term/... starts with a given string very efficient. * * Based on the StartsWithStringTree data structure, see that class. */ -class FeatureTermIndex(features: Collection, selector: Selector?) { +class FeatureTermIndex(features: Collection, getStrings: (Feature) -> List) { private val featureMap: MutableMap> private val tree: StartsWithStringTree init { featureMap = HashMap(features.size) for (feature in features) { - val strings = selector?.getStrings(feature) ?: emptyList() + val strings = getStrings(feature) for (string in strings) { val map = featureMap.getOrPut(string) { ArrayList(1) } map.add(feature) @@ -28,8 +28,4 @@ class FeatureTermIndex(features: Collection, selector: Selector?) { } return result.toList() } - - fun interface Selector { - fun getStrings(feature: Feature): List - } } From f93f3a6453729bfe10ef90c2faf5533711573666 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Feb 2024 01:42:54 +0100 Subject: [PATCH 54/98] improve readability --- .../FeatureTagsIndexTest.kt | 55 ++++++------ .../FeatureTermIndexTest.kt | 88 ++++++++----------- 2 files changed, 60 insertions(+), 83 deletions(-) diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTagsIndexTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTagsIndexTest.kt index 583b23b..c77b142 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTagsIndexTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTagsIndexTest.kt @@ -7,15 +7,15 @@ import kotlin.test.assertTrue class FeatureTagsIndexTest { @Test fun copes_with_empty_collection() { - val index: FeatureTagsIndex = index() + val index = index() assertTrue(index.getAll(mapOf("a" to "b")).isEmpty()) } @Test fun get_two_features_with_same_tags() { - val f1: Feature = feature("a" to "b") - val f2: Feature = feature("a" to "b") - val index: FeatureTagsIndex = index(f1, f2) + val f1 = feature("a" to "b") + val f2 = feature("a" to "b") + val index = index(f1, f2) assertEquals( listOf(f1, f2), index.getAll(mapOf("a" to "b", "c" to "d")) @@ -24,36 +24,31 @@ class FeatureTagsIndexTest { @Test fun get_two_features_with_different_tags() { - val f1: Feature = feature("a" to "b") - val f2: Feature = feature("c" to "d") - val index: FeatureTagsIndex = index(f1, f2) + val f1 = feature("a" to "b") + val f2 = feature("c" to "d") + val index = index(f1, f2) assertEquals( listOf(f1, f2), index.getAll(mapOf("a" to "b", "c" to "d")) ) } +} - companion object { - private fun index(vararg features: Feature): FeatureTagsIndex { - return FeatureTagsIndex(features.toList()) - } +private fun index(vararg features: Feature) = FeatureTagsIndex(features.toList()) - private fun feature(vararg pairs: Pair): Feature { - return BaseFeature( - "id", - mapOf(*pairs), - listOf(GeometryType.POINT), - null, null, - listOf("name"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - false, - mapOf(), - mapOf() - ) - } - } -} +private fun feature(vararg pairs: Pair): Feature = BaseFeature( + "id", + mapOf(*pairs), + listOf(GeometryType.POINT), + null, + null, + listOf("name"), + listOf(), + listOf(), + listOf(), + true, + 1.0f, + false, + mapOf(), + mapOf() +) \ No newline at end of file diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt index 0e2fed1..a7efa99 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt @@ -7,76 +7,58 @@ import kotlin.test.assertTrue class FeatureTermIndexTest { @Test fun copes_with_empty_collection() { - val index: FeatureTermIndex = index() + val index = index() assertTrue(index.getAll("a").isEmpty()) } @Test fun get_one_features_with_same_term() { - val f1: Feature = feature("a", "b") - val f2: Feature = feature("c") - val index: FeatureTermIndex = index(f1, f2) - assertEqualsIgnoreOrder( - listOf(f1), - index.getAll("b") - ) + val f1 = feature("a", "b") + val f2 = feature("c") + val index = index(f1, f2) + assertEqualsIgnoreOrder(listOf(f1), index.getAll("b")) } @Test fun get_two_features_with_same_term() { - val f1: Feature = feature("a", "b") - val f2: Feature = feature("a", "c") - val index: FeatureTermIndex = index(f1, f2) - assertEqualsIgnoreOrder( - listOf(f1, f2), - index.getAll("a") - ) + val f1 = feature("a", "b") + val f2 = feature("a", "c") + val index = index(f1, f2) + assertEqualsIgnoreOrder(listOf(f1, f2), index.getAll("a")) } @Test fun get_two_features_with_different_terms() { - val f1: Feature = feature("anything") - val f2: Feature = feature("anybody") - val index: FeatureTermIndex = index(f1, f2) - assertEqualsIgnoreOrder( - listOf(f1, f2), - index.getAll("any") - ) - assertEqualsIgnoreOrder( - listOf(f1), - index.getAll("anyt") - ) + val f1 = feature("anything") + val f2 = feature("anybody") + val index = index(f1, f2) + assertEqualsIgnoreOrder(listOf(f1, f2), index.getAll("any")) + assertEqualsIgnoreOrder(listOf(f1), index.getAll("anyt")) } @Test - fun dont_get_one_feature_twice() { - val f1: Feature = feature("something", "someone") - val index: FeatureTermIndex = index(f1) + fun do_not_get_one_feature_twice() { + val f1 = feature("something", "someone") + val index = index(f1) assertEquals(listOf(f1), index.getAll("some")) } +} - companion object { - private fun index(vararg features: Feature): FeatureTermIndex { - return FeatureTermIndex(features.toList()) { feature: Feature -> feature.terms } - } +private fun index(vararg features: Feature) = FeatureTermIndex(features.toList()) { it.terms } - private fun feature(vararg terms: String): Feature { - return BaseFeature( - "id", - mapOf(), - listOf(GeometryType.POINT), - null, - null, - listOf("name"), - terms.toList(), - listOf(), - listOf(), - true, - 1.0f, - false, - mapOf(), - mapOf() - ) - } - } -} +private fun feature(vararg terms: String): Feature = BaseFeature( + "id", + mapOf(), + listOf(GeometryType.POINT), + null, + null, + listOf("name"), + terms.toList(), + listOf(), + listOf(), + true, + 1.0f, + false, + mapOf(), + mapOf() +) \ No newline at end of file From 9495b2f3c7970b0e6d140a7ebc87ad7d13b2f81a Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Feb 2024 20:31:07 +0100 Subject: [PATCH 55/98] improve readability, remove some unnecessary copying, operator get function with multiple parameters is weird --- .../FeatureDictionary.kt | 14 +- .../IDBrandPresetsFeatureCollection.kt | 34 ++-- .../IDLocalizedFeatureCollection.kt | 91 ++++----- .../LocalizedFeatureCollection.kt | 2 +- .../PerCountryFeatureCollection.kt | 4 +- .../IDBrandPresetsFeatureCollectionTest.kt | 50 ++--- .../IDLocalizedFeatureCollectionTest.kt | 175 ++++++++---------- .../TestLocalizedFeatureCollection.kt | 9 +- .../TestPerCountryFeatureCollection.kt | 32 +--- 9 files changed, 166 insertions(+), 245 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt index 38d409d..e150ef0 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt @@ -29,12 +29,12 @@ class FeatureDictionary internal constructor( return QueryByIdBuilder(id) } - private operator fun get(id: String, locales: List, countryCode: String?): Feature? { - val feature = featureCollection[id, locales] + private fun get(id: String, locales: List, countryCode: String?): Feature? { + val feature = featureCollection.get(id, locales) if (feature != null) return feature val countryCodes = dissectCountryCode(countryCode) brandFeatureCollection?.let { - return brandFeatureCollection[id, countryCodes] + return brandFeatureCollection.get(id, countryCodes) } throw NullPointerException("brandFeatureCollection is null") } @@ -48,7 +48,7 @@ class FeatureDictionary internal constructor( return QueryByTagBuilder(tags) } - private operator fun get( + private fun get( tags: Map, geometry: GeometryType?, countryCode: String?, @@ -124,7 +124,7 @@ class FeatureDictionary internal constructor( return QueryByTermBuilder(term) } - private operator fun get( + private fun get( search: String, geometry: GeometryType?, countryCode: String?, @@ -357,8 +357,8 @@ class FeatureDictionary internal constructor( private var locale: List = listOf(default) private var countryCode: String? = null - private operator fun get(id: String, locales: List, countryCode: String?): Feature? { - return this@FeatureDictionary[id, locales, countryCode] + private fun get(id: String, locales: List, countryCode: String?): Feature? { + return this@FeatureDictionary.get(id, locales, countryCode) } /** diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt index 6ddcaea..fe5d1e2 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt @@ -8,8 +8,10 @@ import okio.use * there is a presets.json which includes all the features. Additionally, it is possible to place * more files like e.g. presets-DE.json, presets-US-NY.json into the directory which will be loaded * lazily on demand */ -class IDBrandPresetsFeatureCollection internal constructor(private val fileAccess: FileAccessAdapter) : - PerCountryFeatureCollection { +internal class IDBrandPresetsFeatureCollection( + private val fileAccess: FileAccessAdapter +) : PerCountryFeatureCollection { + // countryCode -> featureId -> Feature private val featuresByIdByCountryCode: MutableMap> = LinkedHashMap(320) init { @@ -17,7 +19,7 @@ class IDBrandPresetsFeatureCollection internal constructor(private val fileAcces } override fun getAll(countryCodes: List): Collection { - val result: MutableMap = HashMap() + val result = HashMap() for (cc in countryCodes) { result.putAll(getOrLoadPerCountryFeatures(cc)) } @@ -26,40 +28,28 @@ class IDBrandPresetsFeatureCollection internal constructor(private val fileAcces override fun get(id: String, countryCodes: List): Feature? { for (countryCode in countryCodes) { - val result = getOrLoadPerCountryFeatures(countryCode).get(id) + val result = getOrLoadPerCountryFeatures(countryCode)[id] if (result != null) return result } return null } private fun getOrLoadPerCountryFeatures(countryCode: String?): LinkedHashMap { - return featuresByIdByCountryCode.synchronizedGetOrCreate(countryCode, ::loadPerCountryFeatures) - } - - private fun loadPerCountryFeatures(countryCode: String?): LinkedHashMap { - val features = loadFeatures(countryCode) - val featuresById = LinkedHashMap(features.size) - for (feature in features) { - featuresById[feature.id] = feature + return featuresByIdByCountryCode.synchronizedGetOrCreate(countryCode) { + loadFeatures(countryCode).associateByTo(LinkedHashMap()) { it.id } } - return featuresById } private fun loadFeatures(countryCode: String?): List { val filename = getPresetsFileName(countryCode) if (!fileAccess.exists(filename)) return emptyList() - fileAccess.open(filename).use { - return IDPresetsJsonParser(true).parse(it) + return fileAccess.open(filename).use { source -> + IDPresetsJsonParser(true).parse(source) } } companion object { - private fun getPresetsFileName(countryCode: String?): String { - return if (countryCode == null) { - "presets.json" - } else { - "presets-$countryCode.json" - } - } + private fun getPresetsFileName(countryCode: String?): String = + if (countryCode == null) "presets.json" else "presets-$countryCode.json" } } \ No newline at end of file diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt index f753f46..6f54a97 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt @@ -1,48 +1,60 @@ package de.westnordost.osmfeatures +import okio.use + /** Localized feature collection sourcing from iD presets defined in JSON. * * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that * there is a presets.json which includes all the features. The translations are expected to be * located in the same directory named like e.g. de.json, pt-BR.json etc. */ -class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : - LocalizedFeatureCollection { +class IDLocalizedFeatureCollection( + private val fileAccess: FileAccessAdapter +) : LocalizedFeatureCollection { + // featureId -> Feature private val featuresById: LinkedHashMap + + // locale -> localized feature private val localizedFeaturesList: MutableMap> = HashMap() + + // locales -> featureId -> Feature private val localizedFeatures: MutableMap, LinkedHashMap> = HashMap() init { - val features = loadFeatures() - featuresById = LinkedHashMap(features.associateBy { it.id }) + featuresById = loadFeatures().associateByTo(LinkedHashMap()) { it.id } + } + + override fun getAll(locales: List): Collection { + return getOrLoadLocalizedFeatures(locales).values + } + + override fun get(id: String, locales: List): Feature? { + return getOrLoadLocalizedFeatures(locales)[id] } private fun loadFeatures(): List { - try { - val source = fileAccess.open(FEATURES_FILE) - return IDPresetsJsonParser().parse(source) - } - catch (e: Exception) - { - throw RuntimeException(e) + return fileAccess.open(FEATURES_FILE).use { source -> + IDPresetsJsonParser().parse(source) } } + private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap { return localizedFeatures.synchronizedGetOrCreate(locales, ::loadLocalizedFeatures) } private fun loadLocalizedFeatures(locales: List): LinkedHashMap { val result = LinkedHashMap(featuresById.size) - val it = locales.listIterator(locales.size) - while (it.hasPrevious()) { - val locale = it.previous() + for (locale in locales.asReversed()) { if (locale != null) { - for (localeComponent in getLocaleComponents(locale)) { - putAllFeatures(result, getOrLoadLocalizedFeaturesList(localeComponent)) + for (localeComponent in locale.getComponents()) { + val features = getOrLoadLocalizedFeaturesList(localeComponent) + for (feature in features) { + result[feature.id] = feature + } } } else { - putAllFeatures(result, featuresById.values) + result.putAll(featuresById) } - } +} return result } @@ -51,47 +63,26 @@ class IDLocalizedFeatureCollection(private val fileAccess: FileAccessAdapter) : } private fun loadLocalizedFeaturesList(locale: Locale?): List { - val filename = getLocalizationFilename(locale) + val filename = if (locale != null) getLocalizationFilename(locale) else "en.json" if (!fileAccess.exists(filename)) return emptyList() - fileAccess.open(filename).use { source -> - return IDPresetsTranslationJsonParser().parse(source, locale, featuresById.toMap()) + return IDPresetsTranslationJsonParser().parse(source, locale, featuresById) } } - override fun getAll(locales: List): Collection { - return getOrLoadLocalizedFeatures(locales).values - } - - override operator fun get(id: String, locales: List): Feature? { - return getOrLoadLocalizedFeatures(locales)[id] - } - companion object { private const val FEATURES_FILE = "presets.json" - private fun getLocalizationFilename(locale: Locale?): String { - /* we only want language+country+script of the locale, not anything else. So we construct - it anew here */ - return Locale(locale?.language ?: "en", locale?.region, locale?.script) - .languageTag + ".json" - } - private fun getLocaleComponents(locale: Locale?): List { - val lang = locale?.language ?: "" - val region = locale?.region - val script = locale?.script - val result: MutableList = ArrayList(4) - result.add(Locale(lang)) - if (region != null) result.add(Locale(language = lang, region = region)) - if (script != null) result.add(Locale(language = lang, script = script)) - if (region != null && script != null) result.add(Locale(language = lang, region = region, script = script)) - return result - } + private fun getLocalizationFilename(locale: Locale): String = + locale.languageTag + ".json" - private fun putAllFeatures(map: MutableMap, features: Iterable) { - for (feature in features) { - map[feature.id] = feature - } + private fun Locale.getComponents(): List { + val result = ArrayList(4) + result.add(Locale(language)) + if (region != null) result.add(Locale(language, region = region)) + if (script != null) result.add(Locale(language, script = script)) + if (region != null && script != null) result.add(Locale(language, region = region, script = script)) + return result } } } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt index 06b13e0..b809c40 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt @@ -7,5 +7,5 @@ interface LocalizedFeatureCollection { /** Returns the feature with the given id in the given locale(s) or null if it has not been * found (for the given locale(s)) */ - operator fun get(id: String, locales: List): Feature? + fun get(id: String, locales: List): Feature? } \ No newline at end of file diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/PerCountryFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/PerCountryFeatureCollection.kt index 31cf103..1b64f99 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/PerCountryFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/PerCountryFeatureCollection.kt @@ -2,10 +2,10 @@ package de.westnordost.osmfeatures /** A collection of features grouped by country code */ interface PerCountryFeatureCollection { - /** Returns all features with the given country code */ + /** Returns all features with the given country codes. */ fun getAll(countryCodes: List): Collection /** Returns the feature with the given id with the given country code or null if it has not been * found */ - operator fun get(id: String, countryCodes: List): Feature? + fun get(id: String, countryCodes: List): Feature? } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt index 07dfdc9..9fb4ca2 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt @@ -9,47 +9,33 @@ class IDBrandPresetsFeatureCollectionTest { @Test fun load_brands() { val c = IDBrandPresetsFeatureCollection(object : FileAccessAdapter { - override fun exists(name: String): Boolean { - return name == "presets.json" - } - - override fun open(name: String): Source { - return getSource("brand_presets_min.json") - } + override fun exists(name: String): Boolean = name == "presets.json" + override fun open(name: String): Source = getSource("brand_presets_min.json") }) assertEqualsIgnoreOrder( listOf("Duckworths", "Megamall"), - getNames(c.getAll(listOf(null as String?))) + c.getAll(listOf(null)).map { it.name } ) - assertEquals("Duckworths", c["a/brand", listOf(null as String?)]?.name) - assertEquals("Megamall", c["another/brand", listOf(null as String?)]?.name) + assertEquals("Duckworths", c.get("a/brand", listOf(null))?.name) + assertEquals("Megamall", c.get("another/brand", listOf(null))?.name) + assertEquals(true, c.get("a/brand", listOf(null))?.isSuggestion) + assertEquals(true, c.get("another/brand", listOf(null))?.isSuggestion) } @Test fun load_brands_by_country() { val c = IDBrandPresetsFeatureCollection(object : FileAccessAdapter { - - override fun exists(name: String): Boolean { - return name == "presets-DE.json" - } - - override fun open(name: String): Source { - return getSource("brand_presets_min2.json") - } + override fun exists(name: String): Boolean = name == "presets-DE.json" + override fun open(name: String): Source = getSource("brand_presets_min2.json") }) - assertEqualsIgnoreOrder(listOf("Talespin"), getNames(c.getAll(listOf("DE")))) - assertEquals("Talespin", c["yet_another/brand", listOf("DE")]?.name) - c["yet_another/brand", listOf("DE")]?.isSuggestion?.let { assertTrue(it) } - } - - private fun getSource(file: String): Source { - val fileSystemAccess = FileSystemAccess("src/commonTest/resources") - return fileSystemAccess.open(file) - } - - companion object { - private fun getNames(features: Collection): Collection { - return features.map { it.name } - } + assertEqualsIgnoreOrder( + listOf("Talespin"), + c.getAll(listOf("DE")).map { it.name } + ) + assertEquals("Talespin", c.get("yet_another/brand", listOf("DE"))?.name) + assertEquals(true, c.get("yet_another/brand", listOf("DE"))?.isSuggestion) } } + +private fun getSource(file: String): Source = + FileSystemAccess("src/commonTest/resources").open(file) diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt index 354af94..8828895 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt @@ -1,152 +1,125 @@ package de.westnordost.osmfeatures -import okio.FileSystem import okio.Source -import okio.FileNotFoundException -import okio.IOException -import okio.Path.Companion.toPath import kotlin.test.* class IDLocalizedFeatureCollectionTest { - private val ENGLISH = Locale("en") - private val GERMAN = Locale("de") - @Test fun features_not_found_produces_runtime_exception() { - try { + assertFails { IDLocalizedFeatureCollection(object : FileAccessAdapter { - - override fun exists(name: String): Boolean { - return false - } - - @Override - @Throws(IOException::class) - override fun open(name: String): Source { - throw FileNotFoundException() - } + override fun exists(name: String): Boolean = false + override fun open(name: String): Source = throw Exception() }) - fail() - } catch (ignored: RuntimeException) { } } @Test fun load_features_and_two_localizations() { val c = IDLocalizedFeatureCollection(object : FileAccessAdapter { - @Override - override fun exists(name: String): Boolean { - return listOf("presets.json", "en.json", "de.json").contains(name) - } - - @Override - @Throws(IOException::class) - override fun open(name: String): Source { - if (name == "presets.json") return getSource("some_presets_min.json") - if (name == "en.json") return getSource("localizations_en.json") - if (name == "de.json") return getSource("localizations_de.json") - throw IOException("File not found") + override fun exists(name: String): Boolean = + name in listOf("presets.json", "en.json", "de.json") + + override fun open(name: String): Source = when (name) { + "presets.json" -> getSource("some_presets_min.json") + "en.json" -> getSource("localizations_en.json") + "de.json" -> getSource("localizations_de.json") + else -> throw Exception("File not found") } }) // getting non-localized features - val notLocalized: List = listOf(null as Locale?) - val notLocalizedFeatures: Collection = c.getAll(notLocalized) - assertEqualsIgnoreOrder(listOf("test", "test", "test"), getNames(notLocalizedFeatures)) - assertEquals("test", c["some/id", notLocalized]?.name) - assertEquals("test", c["another/id", notLocalized]?.name) - assertEquals("test", c["yet/another/id", notLocalized]?.name) + val notLocalized = listOf(null) + val notLocalizedFeatures = c.getAll(notLocalized) + assertEqualsIgnoreOrder(listOf("test", "test", "test"), notLocalizedFeatures.map { it.name }) + assertEquals("test", c.get("some/id", notLocalized)?.name) + assertEquals("test", c.get("another/id", notLocalized)?.name) + assertEquals("test", c.get("yet/another/id", notLocalized)?.name) // getting English features - val english: List = listOf(ENGLISH) - val englishFeatures: Collection = c.getAll(english) - assertEqualsIgnoreOrder(listOf("Bakery"), getNames(englishFeatures)) - assertEquals("Bakery", c["some/id", english]?.name) - assertNull(c["another/id", english]) - assertNull(c["yet/another/id", english]) + val english = listOf(ENGLISH) + val englishFeatures = c.getAll(english) + assertEqualsIgnoreOrder(listOf("Bakery"), englishFeatures.map { it.name }) + assertEquals("Bakery", c.get("some/id", english)?.name) + assertNull(c.get("another/id", english)) + assertNull(c.get("yet/another/id", english)) // getting Germany features // this also tests if the fallback from de-DE to de works if de-DE.json does not exist - val germany: List = listOf(GERMANY) - val germanyFeatures: Collection = c.getAll(germany) - assertEqualsIgnoreOrder(listOf("Bäckerei", "Gullideckel"), getNames(germanyFeatures)) - assertEquals("Bäckerei", c["some/id", germany]?.name) - assertEquals("Gullideckel", c["another/id", germany]?.name) - assertNull(c["yet/another/id", germany]) + val germany = listOf(GERMANY) + val germanyFeatures = c.getAll(germany) + assertEqualsIgnoreOrder(listOf("Bäckerei", "Gullideckel"), germanyFeatures.map { it.name }) + assertEquals("Bäckerei", c.get("some/id", germany)?.name) + assertEquals("Gullideckel", c.get("another/id", germany)?.name) + assertNull(c.get("yet/another/id", germany)) // getting features through fallback chain - val locales: List = listOf(ENGLISH, GERMANY, null) - val fallbackFeatures: Collection = c.getAll(locales) - assertEqualsIgnoreOrder(listOf("Bakery", "Gullideckel", "test"), getNames(fallbackFeatures)) - assertEquals("Bakery", c["some/id", locales]?.name) - assertEquals("Gullideckel", c["another/id", locales]?.name) - assertEquals("test", c["yet/another/id", locales]?.name) - assertEquals(ENGLISH, c["some/id", locales]?.locale) - assertEquals(GERMAN, c["another/id", locales]?.locale) - assertNull(c["yet/another/id", locales]?.locale) + val locales = listOf(ENGLISH, GERMANY, null) + val fallbackFeatures = c.getAll(locales) + assertEqualsIgnoreOrder( + listOf("Bakery", "Gullideckel", "test"), + fallbackFeatures.map { it.name } + ) + assertEquals("Bakery", c.get("some/id", locales)?.name) + assertEquals("Gullideckel", c.get("another/id", locales)?.name) + assertEquals("test", c.get("yet/another/id", locales)?.name) + assertEquals(ENGLISH, c.get("some/id", locales)?.locale) + assertEquals(GERMAN, c.get("another/id", locales)?.locale) + assertNull(c.get("yet/another/id", locales)?.locale) } @Test fun load_features_and_merge_localizations() { val c = IDLocalizedFeatureCollection(object : FileAccessAdapter { - @Override - override fun exists(name: String): Boolean { - return listOf( + override fun exists(name: String): Boolean = + name in listOf( "presets.json", "de-AT.json", "de.json", "de-Cyrl.json", "de-Cyrl-AT.json" - ).contains(name) - } - - @Override - @Throws(IOException::class) - override fun open(name: String): Source { - if (name == "presets.json") return getSource("some_presets_min.json") - if (name == "de-AT.json") return getSource("localizations_de-AT.json") - if (name == "de.json") return getSource("localizations_de.json") - if (name == "de-Cyrl-AT.json") return getSource("localizations_de-Cyrl-AT.json") - if (name == "de-Cyrl.json") return getSource("localizations_de-Cyrl.json") - throw IOException("File not found") + ) + + override fun open(name: String): Source = when (name) { + "presets.json" -> getSource("some_presets_min.json") + "de-AT.json" -> getSource("localizations_de-AT.json") + "de.json" -> getSource("localizations_de.json") + "de-Cyrl-AT.json" -> getSource("localizations_de-Cyrl-AT.json") + "de-Cyrl.json" -> getSource("localizations_de-Cyrl.json") + else -> throw Exception("File not found") } }) // standard case - no merging - val german: List = listOf(GERMAN) - val germanFeatures: Collection = c.getAll(german) - assertEqualsIgnoreOrder(listOf("Bäckerei", "Gullideckel"), getNames(germanFeatures)) - assertEquals("Bäckerei", c["some/id", german]?.name) - assertEquals("Gullideckel", c["another/id", german]?.name) - assertNull(c["yet/another/id", german]) + val german = listOf(GERMAN) + val germanFeatures = c.getAll(german) + assertEqualsIgnoreOrder(listOf("Bäckerei", "Gullideckel"), germanFeatures.map { it.name }) + assertEquals("Bäckerei", c.get("some/id", german)?.name) + assertEquals("Gullideckel", c.get("another/id", german)?.name) + assertNull(c.get("yet/another/id", german)) // merging de-AT and de - val austria: List = listOf(Locale("de", "AT")) - val austrianFeatures: Collection = c.getAll(austria) + val austria = listOf(Locale("de", "AT")) + val austrianFeatures = c.getAll(austria) assertEqualsIgnoreOrder( listOf("Backhusl", "Gullideckel", "Brückle"), - getNames(austrianFeatures) + austrianFeatures.map { it.name } ) - assertEquals("Backhusl", c["some/id", austria]?.name) - assertEquals("Gullideckel", c["another/id", austria]?.name) - assertEquals("Brückle", c["yet/another/id", austria]?.name) + assertEquals("Backhusl", c.get("some/id", austria)?.name) + assertEquals("Gullideckel", c.get("another/id", austria)?.name) + assertEquals("Brückle", c.get("yet/another/id", austria)?.name) // merging scripts - val cryllic = listOf(Locale("de", null, "Cyrl")) - assertEquals("бацкхаус", c["some/id", cryllic]?.name) - val cryllicAustria = listOf(Locale("de", "AT","Cyrl")) - assertEquals("бацкхусл", c["some/id", cryllicAustria]?.name) + val cryllic = listOf(Locale("de", script = "Cyrl")) + assertEquals("бацкхаус", c.get("some/id", cryllic)?.name) + val cryllicAustria = listOf(Locale("de", "AT", "Cyrl")) + assertEquals("бацкхусл", c.get("some/id", cryllicAustria)?.name) } +} - @kotlin.Throws(IOException::class) - private fun getSource(file: String): Source { - val resourcePath = "src/commonTest/resources/${file}".toPath() - return FileSystem.SYSTEM.source(resourcePath) - } +private fun getSource(file: String): Source = + FileSystemAccess("src/commonTest/resources").open(file) - companion object { - private fun getNames(features: Collection): Collection { - return features.map { it.name } - } - } -} +private val ENGLISH = Locale("en") +private val GERMAN = Locale("de") +private val GERMANY = Locale("de", "DE") \ No newline at end of file diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt index 2ff5c7c..17994b8 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt @@ -1,18 +1,13 @@ package de.westnordost.osmfeatures -class TestLocalizedFeatureCollection(features: List) : LocalizedFeatureCollection { - private val features: List - - init { - this.features = features - } +class TestLocalizedFeatureCollection(private val features: List) : LocalizedFeatureCollection { override fun getAll(locales: List): Collection { return features.filter { locales.contains(it.locale) } } - override operator fun get(id: String, locales: List): Feature? { + override fun get(id: String, locales: List): Feature? { val feature = features.find { it.id == id } return if (feature == null || (!locales.contains(feature.locale))) null else feature } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt index 36dc624..aada8a8 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt @@ -1,30 +1,16 @@ package de.westnordost.osmfeatures -class TestPerCountryFeatureCollection(features: List) : PerCountryFeatureCollection { - private val features: List - - init { - this.features = features - } +class TestPerCountryFeatureCollection(private val features: List) : PerCountryFeatureCollection { override fun getAll(countryCodes: List): Collection { - return features.filter { feature -> - !countryCodes.any { feature.excludeCountryCodes.contains(it) } - && countryCodes - .any { countryCode -> - feature.includeCountryCodes.contains(countryCode) || countryCode == null && feature.includeCountryCodes.isEmpty() - } - } + return features.filter { it.isAvailableIn(countryCodes) } } - override operator fun get(id: String, countryCodes: List): Feature? { - return features.find { feature -> - feature.id == id - && !countryCodes.any { feature.excludeCountryCodes.contains(it) } - && countryCodes.any { countryCode -> - feature.includeCountryCodes.contains(countryCode) || countryCode == null && feature.includeCountryCodes.isEmpty() - } - } - + override fun get(id: String, countryCodes: List): Feature? { + return features.find { it.isAvailableIn(countryCodes) } } -} + + private fun Feature.isAvailableIn(countryCodes: List): Boolean = + countryCodes.none { excludeCountryCodes.contains(it) } && + countryCodes.any { includeCountryCodes.contains(it) || it == null && includeCountryCodes.isEmpty() } +} \ No newline at end of file From a72e38315a304e86649e1172006669faf6d34721 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Feb 2024 20:31:30 +0100 Subject: [PATCH 56/98] fix LocalizedFeature --- .../kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt index 6656590..7bb3beb 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt @@ -20,9 +20,9 @@ class LocalizedFeature( get() = p.geometry override val name: String get() = names[0] - override val icon: String + override val icon: String? get() = p.icon - override val imageURL: String + override val imageURL: String? get() = p.imageURL override val includeCountryCodes: List get() = p.includeCountryCodes From db13f9ca79358dc94663bbd7db0d26837a2bb647 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Feb 2024 20:36:12 +0100 Subject: [PATCH 57/98] no need for assertEqualsIgnoreOrder helper function --- .../ContainedMapTreeTest.kt | 2 +- .../FeatureDictionaryTest.kt | 11 +++++++--- .../FeatureTermIndexTest.kt | 8 ++++---- .../IDBrandPresetsFeatureCollectionTest.kt | 12 +++++------ .../IDLocalizedFeatureCollectionTest.kt | 20 +++++++++---------- .../StartsWithStringTreeTest.kt | 2 +- .../de.westnordost.osmfeatures/TestUtils.kt | 7 ------- 7 files changed, 30 insertions(+), 32 deletions(-) delete mode 100644 library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt index 74acad1..3a7194c 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt @@ -40,7 +40,7 @@ class ContainedMapTreeTest { val f3 = mapOf("a" to "b", "c" to "e") val f4 = mapOf("a" to "b", "d" to "d") val tree = tree(f1, f2, f3, f4) - assertEqualsIgnoreOrder(listOf(f1, f2), tree.getAll(mapOf("a" to "b", "c" to "d"))) + assertEquals(setOf(f1, f2), tree.getAll(mapOf("a" to "b", "c" to "d")).toSet()) } } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt index d2edbfb..72724af 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt @@ -549,9 +549,14 @@ class FeatureDictionaryTest { @Test fun find_multiple_entries_by_term() { - val dictionary: FeatureDictionary = dictionary(second_hand_car_dealer, car_dealer) - val matches: List = dictionary.byTerm("auto").forLocale(GERMAN).find() - assertEqualsIgnoreOrder(listOf(second_hand_car_dealer, car_dealer), matches) + assertEquals( + setOf(second_hand_car_dealer, car_dealer), + dictionary(second_hand_car_dealer, car_dealer) + .byTerm("auto") + .forLocale(GERMAN) + .find() + .toSet() + ) } @Test diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt index a7efa99..386ed3d 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt @@ -16,7 +16,7 @@ class FeatureTermIndexTest { val f1 = feature("a", "b") val f2 = feature("c") val index = index(f1, f2) - assertEqualsIgnoreOrder(listOf(f1), index.getAll("b")) + assertEquals(setOf(f1), index.getAll("b").toSet()) } @Test @@ -24,7 +24,7 @@ class FeatureTermIndexTest { val f1 = feature("a", "b") val f2 = feature("a", "c") val index = index(f1, f2) - assertEqualsIgnoreOrder(listOf(f1, f2), index.getAll("a")) + assertEquals(setOf(f1, f2), index.getAll("a").toSet()) } @Test @@ -32,8 +32,8 @@ class FeatureTermIndexTest { val f1 = feature("anything") val f2 = feature("anybody") val index = index(f1, f2) - assertEqualsIgnoreOrder(listOf(f1, f2), index.getAll("any")) - assertEqualsIgnoreOrder(listOf(f1), index.getAll("anyt")) + assertEquals(setOf(f1, f2), index.getAll("any").toSet()) + assertEquals(setOf(f1), index.getAll("anyt").toSet()) } @Test diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt index 9fb4ca2..b6c0021 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt @@ -12,9 +12,9 @@ class IDBrandPresetsFeatureCollectionTest { override fun exists(name: String): Boolean = name == "presets.json" override fun open(name: String): Source = getSource("brand_presets_min.json") }) - assertEqualsIgnoreOrder( - listOf("Duckworths", "Megamall"), - c.getAll(listOf(null)).map { it.name } + assertEquals( + setOf("Duckworths", "Megamall"), + c.getAll(listOf(null)).map { it.name }.toSet() ) assertEquals("Duckworths", c.get("a/brand", listOf(null))?.name) assertEquals("Megamall", c.get("another/brand", listOf(null))?.name) @@ -28,9 +28,9 @@ class IDBrandPresetsFeatureCollectionTest { override fun exists(name: String): Boolean = name == "presets-DE.json" override fun open(name: String): Source = getSource("brand_presets_min2.json") }) - assertEqualsIgnoreOrder( - listOf("Talespin"), - c.getAll(listOf("DE")).map { it.name } + assertEquals( + setOf("Talespin"), + c.getAll(listOf("DE")).map { it.name }.toSet() ) assertEquals("Talespin", c.get("yet_another/brand", listOf("DE"))?.name) assertEquals(true, c.get("yet_another/brand", listOf("DE"))?.isSuggestion) diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt index 8828895..72193ef 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt @@ -31,7 +31,7 @@ class IDLocalizedFeatureCollectionTest { // getting non-localized features val notLocalized = listOf(null) val notLocalizedFeatures = c.getAll(notLocalized) - assertEqualsIgnoreOrder(listOf("test", "test", "test"), notLocalizedFeatures.map { it.name }) + assertEquals(setOf("test", "test", "test"), notLocalizedFeatures.map { it.name }.toSet()) assertEquals("test", c.get("some/id", notLocalized)?.name) assertEquals("test", c.get("another/id", notLocalized)?.name) assertEquals("test", c.get("yet/another/id", notLocalized)?.name) @@ -39,7 +39,7 @@ class IDLocalizedFeatureCollectionTest { // getting English features val english = listOf(ENGLISH) val englishFeatures = c.getAll(english) - assertEqualsIgnoreOrder(listOf("Bakery"), englishFeatures.map { it.name }) + assertEquals(setOf("Bakery"), englishFeatures.map { it.name }.toSet()) assertEquals("Bakery", c.get("some/id", english)?.name) assertNull(c.get("another/id", english)) assertNull(c.get("yet/another/id", english)) @@ -48,7 +48,7 @@ class IDLocalizedFeatureCollectionTest { // this also tests if the fallback from de-DE to de works if de-DE.json does not exist val germany = listOf(GERMANY) val germanyFeatures = c.getAll(germany) - assertEqualsIgnoreOrder(listOf("Bäckerei", "Gullideckel"), germanyFeatures.map { it.name }) + assertEquals(setOf("Bäckerei", "Gullideckel"), germanyFeatures.map { it.name }.toSet()) assertEquals("Bäckerei", c.get("some/id", germany)?.name) assertEquals("Gullideckel", c.get("another/id", germany)?.name) assertNull(c.get("yet/another/id", germany)) @@ -56,9 +56,9 @@ class IDLocalizedFeatureCollectionTest { // getting features through fallback chain val locales = listOf(ENGLISH, GERMANY, null) val fallbackFeatures = c.getAll(locales) - assertEqualsIgnoreOrder( - listOf("Bakery", "Gullideckel", "test"), - fallbackFeatures.map { it.name } + assertEquals( + setOf("Bakery", "Gullideckel", "test"), + fallbackFeatures.map { it.name }.toSet() ) assertEquals("Bakery", c.get("some/id", locales)?.name) assertEquals("Gullideckel", c.get("another/id", locales)?.name) @@ -93,7 +93,7 @@ class IDLocalizedFeatureCollectionTest { // standard case - no merging val german = listOf(GERMAN) val germanFeatures = c.getAll(german) - assertEqualsIgnoreOrder(listOf("Bäckerei", "Gullideckel"), germanFeatures.map { it.name }) + assertEquals(setOf("Bäckerei", "Gullideckel"), germanFeatures.map { it.name }.toSet()) assertEquals("Bäckerei", c.get("some/id", german)?.name) assertEquals("Gullideckel", c.get("another/id", german)?.name) assertNull(c.get("yet/another/id", german)) @@ -101,9 +101,9 @@ class IDLocalizedFeatureCollectionTest { // merging de-AT and de val austria = listOf(Locale("de", "AT")) val austrianFeatures = c.getAll(austria) - assertEqualsIgnoreOrder( - listOf("Backhusl", "Gullideckel", "Brückle"), - austrianFeatures.map { it.name } + assertEquals( + setOf("Backhusl", "Gullideckel", "Brückle"), + austrianFeatures.map { it.name }.toSet() ) assertEquals("Backhusl", c.get("some/id", austria)?.name) assertEquals("Gullideckel", c.get("another/id", austria)?.name) diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt index 1c90d3a..f946cbb 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt @@ -30,7 +30,7 @@ class StartsWithStringTreeTest { @Test fun find_several_strings() { val t = tree("anything", "anybody", "anytime") - assertEqualsIgnoreOrder(listOf("anything", "anybody", "anytime"), t.getAll("any")) + assertEquals(setOf("anything", "anybody", "anytime"), t.getAll("any").toSet()) } } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt deleted file mode 100644 index f4732e2..0000000 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestUtils.kt +++ /dev/null @@ -1,7 +0,0 @@ -package de.westnordost.osmfeatures - -import kotlin.test.assertEquals - -fun assertEqualsIgnoreOrder(a: Collection, b: Collection) { - assertEquals(a.toSet(), b.toSet()) -} \ No newline at end of file From c5934c215e23a1a82926a81c50c8fc139c59fe64 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Feb 2024 20:36:45 +0100 Subject: [PATCH 58/98] fix operator access --- .../kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt index e150ef0..185fc50 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt @@ -393,7 +393,7 @@ class FeatureDictionary internal constructor( /** Returns the feature associated with the given id or `null` if it does not * exist */ fun get(): Feature? { - return this[id, locale, countryCode] + return get(id, locale, countryCode) } } From 74a313f6424dff39c861ae113375f573a0e42884 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 28 Feb 2024 00:22:54 +0100 Subject: [PATCH 59/98] clean FeatureDictionary --- .../FeatureDictionary.kt | 483 ++++------ .../FeatureTermIndex.kt | 10 +- .../FeatureDictionaryTest.kt | 871 ++++++++---------- .../FeatureTermIndexTest.kt | 10 +- 4 files changed, 571 insertions(+), 803 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt index 185fc50..8cbd166 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt @@ -1,8 +1,6 @@ package de.westnordost.osmfeatures import de.westnordost.osmfeatures.Locale.Companion.default -import kotlin.math.min -import kotlin.text.Regex class FeatureDictionary internal constructor( private val featureCollection: LocalizedFeatureCollection, @@ -24,19 +22,16 @@ class FeatureDictionary internal constructor( //region Get by id - /** Find feature by id */ - fun byId(id: String): QueryByIdBuilder { - return QueryByIdBuilder(id) - } + /** Find feature by id */ + fun byId(id: String) = QueryByIdBuilder(id) - private fun get(id: String, locales: List, countryCode: String?): Feature? { - val feature = featureCollection.get(id, locales) - if (feature != null) return feature - val countryCodes = dissectCountryCode(countryCode) - brandFeatureCollection?.let { - return brandFeatureCollection.get(id, countryCodes) - } - throw NullPointerException("brandFeatureCollection is null") + private fun getById( + id: String, + locales: List = listOf(default), + countryCode: String? = null + ): Feature? { + return featureCollection.get(id, locales) + ?: brandFeatureCollection?.get(id, dissectCountryCode(countryCode)) } //endregion @@ -44,18 +39,17 @@ class FeatureDictionary internal constructor( //region Query by tags /** Find matches by a set of tags */ - fun byTags(tags: Map): QueryByTagBuilder { - return QueryByTagBuilder(tags) - } + fun byTags(tags: Map) = QueryByTagBuilder(tags) - private fun get( + private fun getByTags( tags: Map, - geometry: GeometryType?, - countryCode: String?, - isSuggestion: Boolean?, - locales: List + geometry: GeometryType? = null, + locales: List = listOf(default), + countryCode: String? = null, + isSuggestion: Boolean? = null ): List { if (tags.isEmpty()) return emptyList() + val foundFeatures: MutableList = mutableListOf() if (isSuggestion == null || !isSuggestion) { foundFeatures.addAll(getTagsIndex(locales).getAll(tags)) @@ -64,54 +58,45 @@ class FeatureDictionary internal constructor( val countryCodes = dissectCountryCode(countryCode) foundFeatures.addAll(getBrandTagsIndex(countryCodes).getAll(tags)) } - foundFeatures.removeAll { feature: Feature -> - !isFeatureMatchingParameters( - feature, - geometry, - countryCode - ) - } + foundFeatures.removeAll { !it.matches(geometry, countryCode) } + if (foundFeatures.size > 1) { // only return of each category the most specific thing. I.e. will return - // McDonalds only instead of McDonalds,Fast-Food Restaurant,Amenity - val removeIds: MutableSet = HashSet() + // McDonald's only instead of McDonald's,Fast-Food Restaurant,Amenity + val removeIds = HashSet() for (feature in foundFeatures) { removeIds.addAll(getParentCategoryIds(feature.id)) } if (removeIds.isNotEmpty()) { - foundFeatures.removeAll { feature: Feature -> - removeIds.contains( - feature.id - ) - } + foundFeatures.removeAll { it.id in removeIds } } } - return foundFeatures.sortedWith(object : Comparator { - override fun compare(a: Feature, b: Feature): Int { - // 1. features with more matching tags first - val tagOrder: Int = b.tags.size - a.tags.size - if (tagOrder != 0) { - return tagOrder - } - - // 2. if search is not limited by locale, return matches not limited by locale first - if (locales.size == 1 && locales[0] == null) { - val localeOrder = - ((if (b.includeCountryCodes.isEmpty() && b.excludeCountryCodes.isEmpty()) 1 else 0) - - if (a.includeCountryCodes.isEmpty() && a.excludeCountryCodes - .isEmpty() - ) 1 else 0) - if (localeOrder != 0) return localeOrder - } + return foundFeatures.sortedWith(Comparator { a: Feature, b: Feature -> + // 1. features with more matching tags first + val tagOrder = b.tags.size - a.tags.size + if (tagOrder != 0) { + return@Comparator tagOrder + } - // 3. features with more matching tags in addTags first - // https://github.com/openstreetmap/iD/issues/7927 - val numberOfMatchedAddTags = - (tags.entries.count { b.addTags.entries.contains(it) } - - tags.entries.count { a.addTags.entries.contains(it) }) - if (numberOfMatchedAddTags != 0) return numberOfMatchedAddTags - return (100 * b.matchScore - 100 * a.matchScore).toInt() + // 2. if search is not limited by locale, return matches not limited by locale first + if (locales.size == 1 && locales[0] == null) { + val localeOrder = ( + (b.includeCountryCodes.isEmpty() && b.excludeCountryCodes.isEmpty()).toInt() + - (a.includeCountryCodes.isEmpty() && a.excludeCountryCodes.isEmpty()).toInt() + ) + if (localeOrder != 0) return@Comparator localeOrder } + + // 3. features with more matching tags in addTags first + // https://github.com/openstreetmap/iD/issues/7927 + val numberOfMatchedAddTags = ( + tags.entries.count { it in b.addTags.entries } + - tags.entries.count { it in a.addTags.entries } + ) + if (numberOfMatchedAddTags != 0) return@Comparator numberOfMatchedAddTags + + // 4. features with higher matchScore first + return@Comparator (100 * b.matchScore - 100 * a.matchScore).toInt() }) } @@ -120,145 +105,80 @@ class FeatureDictionary internal constructor( //region Query by term /** Find matches by given search word */ - fun byTerm(term: String): QueryByTermBuilder { - return QueryByTermBuilder(term) - } + fun byTerm(term: String) = QueryByTermBuilder(term) - private fun get( + private fun getByTerm( search: String, geometry: GeometryType?, + locales: List, countryCode: String?, - isSuggestion: Boolean?, - limit: Int, - locales: List - ): MutableList { + isSuggestion: Boolean? + ): Sequence { val canonicalSearch = search.canonicalize() + val sortNames = Comparator { a: Feature, b: Feature -> // 1. exact matches first - val exactMatchOrder = - ((if (b.names.find { n: String? -> n == search } != null - ) 1 else 0) - - if (a.names.find { n: String? -> n == search } != null - ) 1 else 0) + val exactMatchOrder = ( + (b.names.any { it == search }).toInt() + - (a.names.any { it == search }).toInt() + ) if (exactMatchOrder != 0) return@Comparator exactMatchOrder // 2. exact matches case and diacritics insensitive first - val cExactMatchOrder = - ((if (b.canonicalNames.find { n: String? -> n == canonicalSearch } != null - ) 1 else 0) - - if (a.canonicalNames.find { n: String? -> n == canonicalSearch } != null - ) 1 else 0) + val cExactMatchOrder = ( + (b.canonicalNames.any { it == canonicalSearch }).toInt() + - (a.canonicalNames.any { it == canonicalSearch }).toInt() + ) if (cExactMatchOrder != 0) return@Comparator cExactMatchOrder // 3. starts-with matches in string first - val startsWithOrder = - ((if (b.canonicalNames.find { n: String? -> - n!!.startsWith( - canonicalSearch - ) - } != null - ) 1 else 0) - - if (a.canonicalNames.find { n: String? -> - n!!.startsWith( - canonicalSearch - ) - } != null - ) 1 else 0) + val startsWithOrder = ( + (b.canonicalNames.any { it.startsWith(canonicalSearch) }).toInt() + - (a.canonicalNames.any { it.startsWith(canonicalSearch) }).toInt() + ) if (startsWithOrder != 0) return@Comparator startsWithOrder // 4. features with higher matchScore first - val matchScoreOrder: Int = (100 * b.matchScore - 100 * a.matchScore).toInt() + val matchScoreOrder = (100 * b.matchScore - 100 * a.matchScore).toInt() if (matchScoreOrder != 0) return@Comparator matchScoreOrder - a.name.length - b.name.length - } - val result: MutableList = ArrayList() - if (isSuggestion == null || !isSuggestion) { - // a. matches with presets first - val foundFeaturesByName: MutableList = - getNamesIndex(locales).getAll(canonicalSearch).toMutableList() - foundFeaturesByName.removeAll { feature: Feature -> - !isFeatureMatchingParameters( - feature, - geometry, - countryCode - ) - } - result.addAll(foundFeaturesByName.sortedWith(sortNames)) - // if limit is reached, can return earlier (performance improvement) - if (limit > 0 && result.size >= limit) return result.subList( - 0, - min(limit.toDouble(), result.size.toDouble()).toInt() - ) + // 5. shorter names first + return@Comparator a.name.length - b.name.length } - if (isSuggestion == null || isSuggestion) { - // b. matches with brand names second - val countryCodes = dissectCountryCode(countryCode) - val foundBrandFeatures = getBrandNamesIndex(countryCodes).getAll(canonicalSearch).toMutableList() - foundBrandFeatures.removeAll { feature: Feature -> - !isFeatureMatchingParameters( - feature, - geometry, - countryCode - ) - } - result.addAll(foundBrandFeatures.sortedWith(sortNames)) - - // if limit is reached, can return earlier (performance improvement) - if (limit > 0 && result.size >= limit) return result.subList( - 0, - min(limit.toDouble(), result.size.toDouble()).toInt() - ) + val sortMatchScore = Comparator { a: Feature, b: Feature -> + (100 * b.matchScore - 100 * a.matchScore).toInt() } - if (isSuggestion == null || !isSuggestion) { - // c. matches with terms third - val foundFeaturesByTerm = getTermsIndex(locales).getAll(canonicalSearch).toMutableList() - foundFeaturesByTerm.removeAll { feature: Feature -> - !isFeatureMatchingParameters( - feature, - geometry, - countryCode + + return sequence { + if (isSuggestion == null || !isSuggestion) { + // a. matches with presets first + yieldAll( + getNamesIndex(locales).getAll(canonicalSearch).sortedWith(sortNames) ) } - if (foundFeaturesByTerm.isNotEmpty()) { - val alreadyFoundFeatures: Set = HashSet(result) - foundFeaturesByTerm.removeAll { feature: Feature -> - alreadyFoundFeatures.contains( - feature - ) - } - + if (isSuggestion == null || isSuggestion) { + // b. matches with brand names second + val countryCodes = dissectCountryCode(countryCode) + yieldAll( + getBrandNamesIndex(countryCodes).getAll(canonicalSearch).sortedWith(sortNames) + ) } - result.addAll(foundFeaturesByTerm.sortedWith { a: Feature, b: Feature -> (100 * b.matchScore - 100 * a.matchScore).toInt() }) - - // if limit is reached, can return earlier (performance improvement) - if (limit > 0 && result.size >= limit) return result.subList( - 0, - min(limit.toDouble(), result.size.toDouble()).toInt() - ) - } - if (isSuggestion == null || !isSuggestion) { - // d. matches with tag values fourth - val foundFeaturesByTagValue = getTagValuesIndex(locales).getAll(canonicalSearch).toMutableList() - foundFeaturesByTagValue.removeAll { feature: Feature -> - !isFeatureMatchingParameters( - feature, - geometry, - countryCode + if (isSuggestion == null || !isSuggestion) { + // c. matches with terms third + yieldAll( + getTermsIndex(locales).getAll(canonicalSearch).sortedWith(sortMatchScore) ) } - if (foundFeaturesByTagValue.isNotEmpty()) { - val alreadyFoundFeatures: Set = HashSet(result) - foundFeaturesByTagValue.removeAll { feature: Feature -> - alreadyFoundFeatures.contains( - feature - ) - } - result.addAll(foundFeaturesByTagValue) + if (isSuggestion == null || !isSuggestion) { + // d. matches with tag values fourth + yieldAll( + getTagValuesIndex(locales).getAll(canonicalSearch).sortedWith(sortMatchScore) + ) } } - return result.subList(0, min(limit.toDouble(), result.size.toDouble()).toInt()) + .distinct() + .filter { it.matches(geometry, countryCode) } } //endregion @@ -280,18 +200,8 @@ class FeatureDictionary internal constructor( private fun createNamesIndex(locales: List): FeatureTermIndex { val features = featureCollection.getAll(locales) - return FeatureTermIndex(features) { feature: Feature -> - if (!feature.isSearchable) return@FeatureTermIndex emptyList() - val names: List = feature.canonicalNames - val result = ArrayList(names) - for (name in names) { - if (name.contains(" ")) { - result.addAll( - name.replace("[()]", "").split(" ") - ) - } - } - result + return FeatureTermIndex(features) { feature -> + feature.getSearchableNames().toList() } } @@ -301,7 +211,7 @@ class FeatureDictionary internal constructor( } private fun createTermsIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales)) { feature: Feature -> + return FeatureTermIndex(featureCollection.getAll(locales)) { feature -> if (!feature.isSearchable) emptyList() else feature.canonicalTerms } } @@ -312,14 +222,9 @@ class FeatureDictionary internal constructor( } private fun createTagValuesIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales)) { feature: Feature -> + return FeatureTermIndex(featureCollection.getAll(locales)) { feature -> if (!feature.isSearchable) return@FeatureTermIndex emptyList() - - val result = ArrayList(feature.tags.size) - for (tagValue in feature.tags.values) { - if (tagValue != "*") result.add(tagValue) - } - return@FeatureTermIndex result + return@FeatureTermIndex feature.tags.values.filter { it != "*" } } } @@ -346,7 +251,9 @@ class FeatureDictionary internal constructor( private fun createBrandTagsIndex(countryCodes: List): FeatureTagsIndex { return if (brandFeatureCollection == null) { FeatureTagsIndex(emptyList()) - } else FeatureTagsIndex(brandFeatureCollection.getAll(countryCodes)) + } else { + FeatureTagsIndex(brandFeatureCollection.getAll(countryCodes)) + } } //endregion @@ -357,23 +264,16 @@ class FeatureDictionary internal constructor( private var locale: List = listOf(default) private var countryCode: String? = null - private fun get(id: String, locales: List, countryCode: String?): Feature? { - return this@FeatureDictionary.get(id, locales, countryCode) - } - /** + * Sets the locale(s) in which to present the results. * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * + * You can specify several locales in a row to each fall back to if a translation does not + * exist in the locale before that. For example + * `[new Locale("ca", "ES"), new Locale("es","ES")]` + * if you prefer results in Catalan, but Spanish is also fine. * * `null` means to include unlocalized results. * - * * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, * i.e. unlocalized results are included by default. */ @@ -390,11 +290,8 @@ class FeatureDictionary internal constructor( return this } - /** Returns the feature associated with the given id or `null` if it does not - * exist */ - fun get(): Feature? { - return get(id, locale, countryCode) - } + /** Returns the feature associated with the given id or `null` if it does not exist */ + fun get(): Feature? = getById(id, locale, countryCode) } inner class QueryByTagBuilder(private val tags: Map) { @@ -410,18 +307,15 @@ class FeatureDictionary internal constructor( } /** + * Sets the locale(s) in which to present the results. * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * + * You can specify several locales in a row to each fall back to if a translation does not + * exist in the locale before that. For example + * `[new Locale("ca", "ES"), new Locale("es","ES")]` + * if you prefer results in Catalan, but Spanish is also fine. * * `null` means to include unlocalized results. * - * * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, * i.e. unlocalized results are included by default. */ @@ -449,9 +343,7 @@ class FeatureDictionary internal constructor( * found.

In rare cases, a set of tags may match multiple primary features, such as for * tag combinations like `shop=deli` + `amenity=cafe`, so, this is why * it is a list. */ - fun find(): List { - return get(tags, geometryType, countryCode, suggestion, locale) - } + fun find(): List = getByTags(tags, geometryType, locale, countryCode, suggestion) } inner class QueryByTermBuilder(private val term: String) { @@ -468,20 +360,17 @@ class FeatureDictionary internal constructor( } /** + * Sets the locale(s) in which to present the results. * - *Sets the locale(s) in which to present the results. - * - * You can specify several locales in - * a row to each fall back to if a translation does not exist in the locale before that. - * For example `[new Locale("ca", "ES"), new Locale("es","ES")]` if you - * wanted results preferredly in Catalan, but Spanish is also fine. - * + * You can specify several locales in a row to each fall back to if a translation does not + * exist in the locale before that. For example + * `[new Locale("ca", "ES"), new Locale("es","ES")]` + * if you prefer results in Catalan, but Spanish is also fine. * * `null` means to include unlocalized results. * - * - * If nothing is specified, it defaults to `[Locale.getDefault()]`, i.e. - * unlocalized results are excluded by default. + * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, + * i.e. unlocalized results are included by default. */ fun forLocale(vararg locales: Locale?): QueryByTermBuilder { this.locale = locales.toList() @@ -513,90 +402,76 @@ class FeatureDictionary internal constructor( * found.

* Results are sorted mainly in this order: Matches with names, with brand names, then * matches with terms (keywords). */ - fun find(): List { - return get(term, geometryType, countryCode, suggestion, limit, locale) - } + fun find(): List = + getByTerm(term, geometryType, locale, countryCode, suggestion).take(limit).toList() } //endregion companion object { - private val VALID_COUNTRY_CODE_REGEX = Regex("([A-Z]{2})(?:-([A-Z0-9]{1,3}))?") + /** Create a new FeatureDictionary which gets its data from the given directory. Optionally, * a path to brand presets can be specified. */ /** Create a new FeatureDictionary which gets its data from the given directory. */ - - fun create(presetsBasePath: String, brandPresetsBasePath: String? = null): FeatureDictionary { - val featureCollection: LocalizedFeatureCollection = - IDLocalizedFeatureCollection(FileSystemAccess(presetsBasePath)) - val brandsFeatureCollection: PerCountryFeatureCollection? = - if (brandPresetsBasePath != null) IDBrandPresetsFeatureCollection( - FileSystemAccess(brandPresetsBasePath) - ) else null - return FeatureDictionary(featureCollection, brandsFeatureCollection) - } - - //region Utility / Filter functions - - private fun getParentCategoryIds(id: String): Collection { - var currentId: String? = id - val result: MutableList = ArrayList() - do { - currentId = getParentId(currentId) - if (currentId != null) result.add(currentId) - } while (currentId != null) - return result - } - - private fun getParentId(id: String?): String? { - val lastSlashIndex = id!!.lastIndexOf("/") - return if (lastSlashIndex == -1) null else id.substring(0, lastSlashIndex) - } - - private fun isFeatureMatchingParameters( - feature: Feature, - geometry: GeometryType?, - countryCode: String? - ): Boolean { - if (geometry != null && !feature.geometry.contains(geometry)) return false - val include: List = feature.includeCountryCodes - val exclude: List = feature.excludeCountryCodes - if (include.isNotEmpty() || exclude.isNotEmpty()) { - if (countryCode == null) return false - if (include.isNotEmpty() && !matchesAnyCountryCode(countryCode, include)) return false - if (matchesAnyCountryCode(countryCode, exclude)) return false - } - return true - } - - private fun dissectCountryCode(countryCode: String?): List { - val result: MutableList = ArrayList() - // add default / international - result.add(null) - countryCode?.let { - val matcher = VALID_COUNTRY_CODE_REGEX.find(it) - if(matcher?.groups?.get(1) != null) { - result.add(matcher.groups[1]?.value) - - // add ISO 3166-1 alpha2 (e.g. "US") - if (matcher.groups.size != 2 && matcher.groups[2] != null) { - // add ISO 3166-2 (e.g. "US-NY") - result.add(countryCode) - } + fun create(presetsBasePath: String, brandPresetsBasePath: String? = null) = + FeatureDictionary( + featureCollection = IDLocalizedFeatureCollection(FileSystemAccess(presetsBasePath)), + brandFeatureCollection = brandPresetsBasePath?.let { + IDBrandPresetsFeatureCollection(FileSystemAccess(brandPresetsBasePath)) } + ) + } +} + +//region Utility / Filter functions + +private fun Feature.matches(geometry: GeometryType?, countryCode: String?): Boolean { + if (geometry != null && !this.geometry.contains(geometry)) return false + if (includeCountryCodes.isNotEmpty() || excludeCountryCodes.isNotEmpty()) { + if (countryCode == null) return false + if ( + includeCountryCodes.isNotEmpty() && + !isInCountryCodes(countryCode, includeCountryCodes) + ) return false + if (isInCountryCodes(countryCode, excludeCountryCodes)) return false + } + return true +} - - } - return result - } - - private fun matchesAnyCountryCode(showOnly: String, featureCountryCodes: List): Boolean { - return featureCountryCodes.any { matchesCountryCode(showOnly, it) } +private fun Feature.getSearchableNames(): Sequence = sequence { + if (!isSearchable) return@sequence + yieldAll(canonicalNames) + for (name in canonicalNames) { + if (name.contains(" ")) { + yieldAll(name.replace("[()]", "").split(" ")) } + } +} - private fun matchesCountryCode(showOnly: String, featureCountryCode: String): Boolean { - return showOnly == featureCountryCode || showOnly.substring(0, 2) == featureCountryCode - } +private fun isInCountryCodes(countryCode: String, countryCodes: List): Boolean = + countryCode in countryCodes || countryCode.substringBefore('-') in countryCodes.map { it } - //endregion +private fun getParentCategoryIds(id: String): Sequence = sequence { + var lastIndex = id.length + while (true) { + lastIndex = id.lastIndexOf('/', lastIndex - 1) + if (lastIndex == -1) break + yield(id.substring(0, lastIndex)) + } +} + +private fun dissectCountryCode(countryCode: String?): List = buildList { + // add default / international + add(null) + if (countryCode != null) { + // add ISO 3166-1 alpha2 (e.g. "US") + val alpha2 = countryCode.substringBefore('-') + add(alpha2) + // add ISO 3166-2 (e.g. "US-NY") + if (alpha2 != countryCode) add(countryCode) } -} \ No newline at end of file +} + +private fun Boolean.toInt(): Int = + if (this) 1 else 0 + +//endregion \ No newline at end of file diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt index 12d4161..f45481b 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt @@ -5,14 +5,12 @@ package de.westnordost.osmfeatures * * Based on the StartsWithStringTree data structure, see that class. */ class FeatureTermIndex(features: Collection, getStrings: (Feature) -> List) { - private val featureMap: MutableMap> + private val featureMap: MutableMap> = HashMap(features.size) private val tree: StartsWithStringTree init { - featureMap = HashMap(features.size) for (feature in features) { - val strings = getStrings(feature) - for (string in strings) { + for (string in getStrings(feature)) { val map = featureMap.getOrPut(string) { ArrayList(1) } map.add(feature) } @@ -20,12 +18,12 @@ class FeatureTermIndex(features: Collection, getStrings: (Feature) -> L tree = StartsWithStringTree(featureMap.keys) } - fun getAll(startsWith: String): List { + fun getAll(startsWith: String): Set { val result = HashSet() for (string in tree.getAll(startsWith)) { val fs: List? = featureMap[string] if (fs != null) result.addAll(fs) } - return result.toList() + return result } } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt index 72724af..29458ea 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt @@ -6,451 +6,334 @@ import kotlin.test.assertEquals import kotlin.test.assertNull class FeatureDictionaryTest { - private val ENGLISH = Locale("en") - private val UK = Locale("en","UK") - private val ITALIAN = Locale("it") - private val GERMAN = Locale("de") - private val CHINESE = Locale("zh") - - private val bakery: Feature = feature( // unlocalized shop=bakery - "shop/bakery", - mapOf("shop" to "bakery"), - POINT, - listOf("Bäckerei"), - listOf("Brot"), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null + + + private val bakery = feature( // unlocalized shop=bakery + id = "shop/bakery", + tags = mapOf("shop" to "bakery"), + names = listOf("Bäckerei"), + terms = listOf("Brot") ) - private val panetteria: Feature = feature( // localized shop=bakery - "shop/bakery", - mapOf("shop" to "bakery"), - POINT, - listOf("Panetteria"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - ITALIAN + private val panetteria = feature( // localized shop=bakery + id = "shop/bakery", + tags = mapOf("shop" to "bakery"), + names = listOf("Panetteria"), + locale = ITALIAN ) - private val ditsch: Feature = feature( // brand in DE for shop=bakery - "shop/bakery/Ditsch", - mapOf("shop" to "bakery", "name" to "Ditsch"), - POINT, - listOf("Ditsch"), - listOf(), - listOf("DE", "AT"), - listOf("AT-9"), - true, - 1.0f, - mapOf("wikipedia" to "de:Brezelb%C3%A4ckerei_Ditsch", "brand" to "Ditsch"), - true, - null + private val ditsch = feature( // brand in DE for shop=bakery + id = "shop/bakery/Ditsch", + tags = mapOf("shop" to "bakery", "name" to "Ditsch"), + names = listOf("Ditsch"), + countryCodes = listOf("DE", "AT"), + excludeCountryCodes = listOf("AT-9"), + addTags = mapOf("wikipedia" to "de:Brezelb%C3%A4ckerei_Ditsch", "brand" to "Ditsch"), + isSuggestion = true ) - private val ditschRussian: Feature = feature( // brand in RU for shop=bakery - "shop/bakery/Дитсч", - mapOf("shop" to "bakery", "name" to "Ditsch"), - POINT, - listOf("Дитсч"), - listOf(), - listOf("RU", "UA-43"), - listOf(), - true, - 1.0f, - mapOf("wikipedia" to "de:Brezelb%C3%A4ckerei_Ditsch", "brand" to "Дитсч"), - true, - null + private val ditschRussian = feature( // brand in RU for shop=bakery + id = "shop/bakery/Дитсч", + tags = mapOf("shop" to "bakery", "name" to "Ditsch"), + names = listOf("Дитсч"), + countryCodes = listOf("RU", "UA-43"), + addTags = mapOf("wikipedia" to "de:Brezelb%C3%A4ckerei_Ditsch", "brand" to "Дитсч"), + isSuggestion = true ) - private val ditschInternational: Feature = feature( // brand everywhere for shop=bakery - "shop/bakery/Ditsh", - mapOf("shop" to "bakery", "name" to "Ditsch"), - POINT, - listOf("Ditsh"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf("wikipedia" to "de:Brezelb%C3%A4ckerei_Ditsch"), - true, - null + private val ditschInternational = feature( // brand everywhere for shop=bakery + id = "shop/bakery/Ditsh", + tags = mapOf("shop" to "bakery", "name" to "Ditsch"), + names = listOf("Ditsh"), + addTags = mapOf("wikipedia" to "de:Brezelb%C3%A4ckerei_Ditsch"), + isSuggestion = true ) - private val liquor_store: Feature = feature( // English localized unspecific shop=alcohol - "shop/alcohol", - mapOf("shop" to "alcohol"), - POINT, - listOf("Off licence (Alcohol shop)"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - UK + private val liquor_store = feature( // English localized unspecific shop=alcohol + id = "shop/alcohol", + tags = mapOf("shop" to "alcohol"), + names = listOf("Off licence (Alcohol shop)"), + locale = UK ) - private val car_dealer: Feature = feature( // German localized unspecific shop=car - "shop/car", - mapOf("shop" to "car"), - POINT, - listOf("Autohändler"), - listOf("auto"), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - GERMAN + private val car_dealer = feature( // German localized unspecific shop=car + id = "shop/car", + tags = mapOf("shop" to "car"), + names = listOf("Autohändler"), + terms = listOf("auto"), + locale = GERMAN ) - private val second_hand_car_dealer: Feature = feature( // German localized shop=car with subtags - "shop/car/second_hand", - mapOf("shop" to "car", "second_hand" to "only"), - POINT, - listOf("Gebrauchtwagenhändler"), - listOf("auto"), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - GERMAN + private val second_hand_car_dealer = feature( // German localized shop=car with subtags + id = "shop/car/second_hand", + tags = mapOf("shop" to "car", "second_hand" to "only"), + names = listOf("Gebrauchtwagenhändler"), + terms = listOf("auto"), + locale = GERMAN ) - private val scheisshaus: Feature = feature( // unsearchable feature - "amenity/scheißhaus", - mapOf("amenity" to "scheißhaus"), - POINT, - listOf("Scheißhaus"), - listOf(), - listOf(), - listOf(), - false, // <--- not searchable! - 1.0f, - mapOf(), - false, - null + private val scheisshaus = feature( // unsearchable feature + id = "amenity/scheißhaus", + tags = mapOf("amenity" to "scheißhaus"), + names = listOf("Scheißhaus"), + searchable = false ) - private val bank: Feature = feature( // unlocalized shop=bank (Bank) - "amenity/bank", - mapOf("amenity" to "bank"), - POINT, - listOf("Bank"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null + private val bank = feature( // unlocalized shop=bank (Bank) + id = "amenity/bank", + tags = mapOf("amenity" to "bank"), + names = listOf("Bank") ) - private val bench: Feature = feature( // unlocalized amenity=bench (PARKbank) - "amenity/bench", - mapOf("amenity" to "bench"), - POINT, - listOf("Parkbank"), - listOf("Bank"), - listOf(), - listOf(), - true, - 5.0f, - mapOf(), - false, - null + private val bench = feature( // unlocalized amenity=bench (PARKbank) + id = "amenity/bench", + tags = mapOf("amenity" to "bench"), + names = listOf("Parkbank"), + terms = listOf("Bank"), + matchScore = 5.0f ) - private val casino: Feature = feature( // unlocalized amenity=casino (SPIELbank) - "amenity/casino", - mapOf("amenity" to "casino"), - POINT, - listOf("Spielbank"), - listOf("Kasino"), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null + private val casino = feature( // unlocalized amenity=casino (SPIELbank) + id = "amenity/casino", + tags = mapOf("amenity" to "casino"), + names = listOf("Spielbank"), + terms = listOf("Kasino") ) - private val atm: Feature = feature( // unlocalized amenity=atm (BankOMAT) - "amenity/atm", - mapOf("amenity" to "atm"), - POINT, - listOf("Bankomat"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null + private val atm = feature( // unlocalized amenity=atm (BankOMAT) + id = "amenity/atm", + tags = mapOf("amenity" to "atm"), + names = listOf("Bankomat") ) - private val stock_exchange: Feature = - feature( // unlocalized amenity=stock_exchange (has "Banking" as term) - "amenity/stock_exchange", - mapOf("amenity" to "stock_exchange"), - POINT, - listOf("Börse"), - listOf("Banking"), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null - ) - private val bank_of_america: Feature = feature( // Brand of a amenity=bank (has "Bank" in name) - "amenity/bank/Bank of America", - mapOf("amenity" to "bank", "name" to "Bank of America"), - POINT, - listOf("Bank of America"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - true, - null + private val stock_exchange = feature( // unlocalized amenity=stock_exchange (has "Banking" as term) + id = "amenity/stock_exchange", + tags = mapOf("amenity" to "stock_exchange"), + names = listOf("Börse"), + terms = listOf("Banking"), ) - private val bank_of_liechtenstein: Feature = - feature( // Brand of a amenity=bank (has "Bank" in name), but low matchScore - "amenity/bank/Bank of Liechtenstein", - mapOf("amenity" to "bank", "name" to "Bank of Liechtenstein"), - POINT, - listOf("Bank of Liechtenstein"), - listOf(), - listOf(), - listOf(), - true, - 0.2f, - mapOf(), - true, - null - ) - private val deutsche_bank: Feature = - feature( // Brand of a amenity=bank (does not start with "Bank" in name) - "amenity/bank/Deutsche Bank", - mapOf("amenity" to "bank", "name" to "Deutsche Bank"), - POINT, - listOf("Deutsche Bank"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - true, - null - ) - private val baenk: Feature = - feature( // amenity=bänk, to see if diacritics match non-strictly ("a" finds "ä") - "amenity/bänk", - mapOf("amenity" to "bänk"), - POINT, - listOf("Bänk"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null - ) - private val bad_bank: Feature = - feature( // amenity=bank with subtags that has "Bank" in name but it is not the first word - "amenity/bank/bad", - mapOf("amenity" to "bank", "goodity" to "bad"), - POINT, - listOf("Bad Bank"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null - ) - private val thieves_guild: Feature = feature( // only has "bank" in an alias - "amenity/thieves_guild", - mapOf("amenity" to "thieves_guild"), - POINT, - listOf("Diebesgilde", "Bankräuberausbildungszentrum"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null + private val bank_of_america = feature( // Brand of a amenity=bank (has "Bank" in name) + id = "amenity/bank/Bank of America", + tags = mapOf("amenity" to "bank", "name" to "Bank of America"), + names = listOf("Bank of America"), + isSuggestion = true + ) + private val bank_of_liechtenstein = feature( // Brand of a amenity=bank (has "Bank" in name), but low matchScore + id = "amenity/bank/Bank of Liechtenstein", + tags = mapOf("amenity" to "bank", "name" to "Bank of Liechtenstein"), + names = listOf("Bank of Liechtenstein"), + matchScore = 0.2f, + isSuggestion = true, + ) + private val deutsche_bank = feature( // Brand of a amenity=bank (does not start with "Bank" in name) + id = "amenity/bank/Deutsche Bank", + tags = mapOf("amenity" to "bank", "name" to "Deutsche Bank"), + names = listOf("Deutsche Bank"), + isSuggestion = true + ) + private val baenk = feature( // amenity=bänk, to see if diacritics match non-strictly ("a" finds "ä") + id = "amenity/bänk", + tags = mapOf("amenity" to "bänk"), + names = listOf("Bänk"), + ) + private val bad_bank = feature( // amenity=bank with subtags that has "Bank" in name but it is not the first word + id = "amenity/bank/bad", + tags = mapOf("amenity" to "bank", "goodity" to "bad"), + names = listOf("Bad Bank") + ) + private val thieves_guild = feature( // only has "bank" in an alias + id = "amenity/thieves_guild", + tags = mapOf("amenity" to "thieves_guild"), + names = listOf("Diebesgilde", "Bankräuberausbildungszentrum"), + ) + private val miniature_train_shop = feature( // feature whose name consists of several words + id = "shop/miniature_train", + tags = mapOf("shop" to "miniature_train"), + names = listOf("Miniature Train Shop"), ) - private val miniature_train_shop: Feature = - feature( // feature whose name consists of several words - "shop/miniature_train", - mapOf("shop" to "miniature_train"), - POINT, - listOf("Miniature Train Shop"), - listOf(), - listOf(), - listOf(), - true, - 1.0f, - mapOf(), - false, - null - ) //region by tags + @Test fun find_no_entry_by_tags() { - val tags: Map = mapOf("shop" to "supermarket") - val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(emptyList(), dictionary.byTags(tags).find()) + assertEquals( + emptyList(), + dictionary(bakery).byTags(mapOf("shop" to "supermarket")).find() + ) } @Test fun find_no_entry_because_wrong_geometry() { - val tags: Map = mapOf("shop" to "bakery") - val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(emptyList(), dictionary.byTags(tags).forGeometry(GeometryType.RELATION).find()) + assertEquals( + emptyList(), + dictionary(bakery) + .byTags(mapOf("shop" to "bakery")) + .forGeometry(GeometryType.RELATION) + .find() + ) } @Test fun find_no_entry_because_wrong_locale() { - val tags: Map = mapOf("shop" to "bakery") - val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(emptyList(), dictionary.byTags(tags).forLocale(ITALIAN).find()) + assertEquals( + emptyList(), + dictionary(bakery) + .byTags(mapOf("shop" to "bakery")) + .forLocale(ITALIAN) + .find() + ) } @Test fun find_entry_because_fallback_locale() { - val tags: Map = mapOf("shop" to "bakery") - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTags(tags).forLocale(ITALIAN, null).find() - assertEquals(listOf(bakery), matches) + assertEquals( + listOf(bakery), + dictionary(bakery) + .byTags(mapOf("shop" to "bakery")) + .forLocale(ITALIAN, null) + .find() + ) } @Test fun find_entry_by_tags() { - val tags: Map = mapOf("shop" to "bakery") - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTags(tags).find() - assertEquals(listOf(bakery), matches) + assertEquals( + listOf(bakery), + dictionary(bakery).byTags(mapOf("shop" to "bakery")).find() + ) } @Test fun find_non_searchable_entry_by_tags() { - val tags: Map = mapOf("amenity" to "scheißhaus") - val dictionary: FeatureDictionary = dictionary(scheisshaus) - val matches: List = dictionary.byTags(tags).find() - assertEquals(listOf(scheisshaus), matches) + assertEquals( + listOf(scheisshaus), + dictionary(scheisshaus).byTags(mapOf("amenity" to "scheißhaus")).find() + ) } @Test fun find_entry_by_tags_correct_geometry() { - val tags: Map = mapOf("shop" to "bakery") - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTags(tags).forGeometry(GeometryType.POINT).find() - assertEquals(listOf(bakery), matches) + assertEquals( + listOf(bakery), + dictionary(bakery) + .byTags(mapOf("shop" to "bakery")) + .forGeometry(GeometryType.POINT) + .find() + ) } @Test fun find_brand_entry_by_tags() { - val tags: Map = mapOf("shop" to "bakery", "name" to "Ditsch") - val dictionary: FeatureDictionary = dictionary(bakery, ditsch) - val matches: List = dictionary.byTags(tags).inCountry("DE").find() - assertEquals(listOf(ditsch), matches) + assertEquals( + listOf(ditsch), + dictionary(bakery, ditsch) + .byTags(mapOf("shop" to "bakery", "name" to "Ditsch")) + .inCountry("DE") + .find() + ) } @Test fun find_only_entries_with_given_locale() { - val tags: Map = mapOf("shop" to "bakery") - val dictionary: FeatureDictionary = dictionary(bakery, panetteria) - assertEquals(listOf(panetteria), dictionary.byTags(tags).forLocale(ITALIAN).find()) - assertEquals(emptyList(), dictionary.byTags(tags).forLocale(ENGLISH).find()) - assertEquals(listOf(bakery), dictionary.byTags(tags).forLocale(null as Locale?).find()) + val tags = mapOf("shop" to "bakery") + val dictionary = dictionary(bakery, panetteria) + + assertEquals( + listOf(panetteria), + dictionary.byTags(tags).forLocale(ITALIAN).find() + ) + assertEquals( + emptyList(), + dictionary.byTags(tags).forLocale(ENGLISH).find() + ) + assertEquals( + listOf(bakery), + dictionary.byTags(tags).forLocale(null).find() + ) } @Test fun find_only_brands_finds_no_normal_entries() { - val tags: Map = mapOf("shop" to "bakery", "name" to "Ditsch") - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTags(tags).isSuggestion(true).find() - assertEquals(0, matches.size) + assertEquals( + 0, + dictionary(bakery) + .byTags(mapOf("shop" to "bakery", "name" to "Ditsch")) + .isSuggestion(true) + .find().size + ) } @Test fun find_no_brands_finds_only_normal_entries() { - val tags: Map = mapOf("shop" to "bakery", "name" to "Ditsch") - val dictionary: FeatureDictionary = dictionary(bakery, ditsch) - val matches: List = dictionary.byTags(tags).isSuggestion(false).find() - assertEquals(listOf(bakery), matches) + assertEquals( + listOf(bakery), + dictionary(bakery, ditsch) + .byTags(mapOf("shop" to "bakery", "name" to "Ditsch")) + .isSuggestion(false) + .find() + ) } @Test fun find_multiple_brands_sorts_by_locale() { - val tags: Map = mapOf("shop" to "bakery", "name" to "Ditsch") - val dictionary: FeatureDictionary = dictionary(ditschRussian, ditschInternational, ditsch) - val matches: List = dictionary.byTags(tags).forLocale(null).find() - assertEquals(ditschInternational, matches[0]) + assertEquals( + ditschInternational, + dictionary(ditschRussian, ditschInternational, ditsch) + .byTags(mapOf("shop" to "bakery", "name" to "Ditsch")) + .forLocale(null) + .find() + .first() + ) } @Test fun find_multiple_entries_by_tags() { - val tags: Map = mapOf("shop" to "bakery", "amenity" to "bank") - val dictionary: FeatureDictionary = dictionary(bakery, bank) - val matches: List = dictionary.byTags(tags).find() - assertEquals(2, matches.size) + assertEquals( + 2, + dictionary(bakery, bank) + .byTags(mapOf("shop" to "bakery", "amenity" to "bank")) + .find() + .size + ) } @Test fun do_not_find_entry_with_too_specific_tags() { - val tags: Map = mapOf("shop" to "car") - val dictionary: FeatureDictionary = dictionary(car_dealer, second_hand_car_dealer) - val matches: List = dictionary.byTags(tags).forLocale(GERMAN, null).find() - assertEquals(listOf(car_dealer), matches) + assertEquals( + listOf(car_dealer), + dictionary(car_dealer, second_hand_car_dealer) + .byTags(mapOf("shop" to "car")) + .forLocale(GERMAN, null) + .find() + ) } @Test fun find_entry_with_specific_tags() { - val tags: Map = mapOf("shop" to "car", "second_hand" to "only") - val dictionary: FeatureDictionary = dictionary(car_dealer, second_hand_car_dealer) - val matches: List = dictionary.byTags(tags).forLocale(GERMAN, null).find() - assertEquals(listOf(second_hand_car_dealer), matches) + assertEquals( + listOf(second_hand_car_dealer), + dictionary(car_dealer, second_hand_car_dealer) + .byTags(mapOf("shop" to "car", "second_hand" to "only")) + .forLocale(GERMAN, null) + .find() + ) } //endregion + //region by name + @Test fun find_no_entry_by_name() { - val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(emptyList(), dictionary.byTerm("Supermarkt").find()) + assertEquals(emptyList(), dictionary(bakery).byTerm("Supermarkt").find()) } @Test fun find_no_entry_by_name_because_wrong_geometry() { - val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(emptyList(), dictionary.byTerm("Bäckerei").forGeometry(GeometryType.LINE).find()) + assertEquals( + emptyList(), + dictionary(bakery).byTerm("Bäckerei").forGeometry(GeometryType.LINE).find() + ) } @Test fun find_no_entry_by_name_because_wrong_country() { - val dictionary: FeatureDictionary = dictionary(ditsch, ditschRussian) - assertEquals(emptyList(), dictionary.byTerm("Ditsch").find()) - assertEquals(emptyList(), dictionary.byTerm("Ditsch").inCountry("FR").find()) // not in France + val dictionary = dictionary(ditsch, ditschRussian) + assertEquals( + emptyList(), + dictionary.byTerm("Ditsch").find() + ) + assertEquals( + emptyList(), + dictionary.byTerm("Ditsch").inCountry("FR").find() + ) // not in France assertEquals( emptyList(), dictionary.byTerm("Ditsch").inCountry("AT-9").find() @@ -463,29 +346,34 @@ class FeatureDictionaryTest { @Test fun find_no_non_searchable_entry_by_name() { - val dictionary: FeatureDictionary = dictionary(scheisshaus) - assertEquals(emptyList(), dictionary.byTerm("Scheißhaus").find()) + assertEquals(emptyList(), dictionary(scheisshaus).byTerm("Scheißhaus").find()) } @Test fun find_entry_by_name() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTerm("Bäckerei").forLocale(null as Locale?).find() - assertEquals(listOf(bakery), matches) + assertEquals( + listOf(bakery), + dictionary(bakery) + .byTerm("Bäckerei") + .forLocale(null) + .find()) } @Test fun find_entry_by_name_with_correct_geometry() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = - dictionary.byTerm("Bäckerei").forLocale(null as Locale?).forGeometry(GeometryType.POINT) + assertEquals( + listOf(bakery), + dictionary(bakery) + .byTerm("Bäckerei") + .forLocale(null) + .forGeometry(GeometryType.POINT) .find() - assertEquals(listOf(bakery), matches) + ) } @Test fun find_entry_by_name_with_correct_country() { - val dictionary: FeatureDictionary = dictionary(ditsch, ditschRussian, bakery) + val dictionary = dictionary(ditsch, ditschRussian, bakery) assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("DE").find()) assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("DE-TH").find()) assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("AT").find()) @@ -496,29 +384,26 @@ class FeatureDictionaryTest { @Test fun find_entry_by_name_case_insensitive() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTerm("BÄCkErEI").forLocale(null as Locale?).find() - assertEquals(listOf(bakery), matches) + assertEquals(listOf(bakery), dictionary(bakery).byTerm("BÄCkErEI").forLocale(null).find()) } @Test fun find_entry_by_name_diacritics_insensitive() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTerm("Backérèi").forLocale(null as Locale?).find() - assertEquals(listOf(bakery), matches) + assertEquals(listOf(bakery), dictionary(bakery).byTerm("Backérèi").forLocale(null).find()) } @Test fun find_entry_by_term() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTerm("bro").forLocale(null as Locale?).find() - assertEquals(listOf(bakery), matches) + assertEquals(listOf(bakery), dictionary(bakery).byTerm("bro").forLocale(null).find()) } @Test fun find_entry_by_term_brackets() { - val dictionary: FeatureDictionary = dictionary(liquor_store) - assertEquals(listOf(liquor_store), dictionary.byTerm("Alcohol").forLocale(UK).find()) + val dictionary = dictionary(liquor_store) + assertEquals( + listOf(liquor_store), + dictionary.byTerm("Alcohol").forLocale(UK).find() + ) assertEquals( listOf(liquor_store), dictionary.byTerm("Off licence (Alcohol Shop)").forLocale(UK).find() @@ -535,16 +420,12 @@ class FeatureDictionaryTest { @Test fun find_entry_by_term_case_insensitive() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTerm("BRO").forLocale(null as Locale?).find() - assertEquals(listOf(bakery), matches) + assertEquals(listOf(bakery), dictionary(bakery).byTerm("BRO").forLocale(null).find()) } @Test fun find_entry_by_term_diacritics_insensitive() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = dictionary.byTerm("bró").forLocale(null as Locale?).find() - assertEquals(listOf(bakery), matches) + assertEquals(listOf(bakery), dictionary(bakery).byTerm("bró").forLocale(null).find()) } @Test @@ -561,45 +442,61 @@ class FeatureDictionaryTest { @Test fun find_multiple_entries_by_name_but_respect_limit() { - val dictionary: FeatureDictionary = dictionary(second_hand_car_dealer, car_dealer) - val matches: List = - dictionary.byTerm("auto").forLocale(GERMAN).limit(1).find() - assertEquals(1, matches.size) + assertEquals( + 1, + dictionary(second_hand_car_dealer, car_dealer) + .byTerm("auto") + .forLocale(GERMAN) + .limit(1) + .find() + .size + ) } @Test fun find_only_brands_by_name_finds_no_normal_entries() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = - dictionary.byTerm("Bäckerei").forLocale(null as Locale?).isSuggestion(true).find() - assertEquals(0, matches.size) + assertEquals( + 0, + dictionary(bakery) + .byTerm("Bäckerei") + .forLocale(null) + .isSuggestion(true) + .find() + .size + ) } @Test fun find_no_brands_by_name_finds_only_normal_entries() { - val dictionary: FeatureDictionary = dictionary(bank, bank_of_america) - val matches: List = - dictionary.byTerm("Bank").forLocale(null as Locale?).isSuggestion(false).find() - assertEquals(listOf(bank), matches) + assertEquals( + listOf(bank), + dictionary(bank, bank_of_america) + .byTerm("Bank") + .forLocale(null) + .isSuggestion(false) + .find() + ) } @Test fun find_no_entry_by_term_because_wrong_locale() { - val dictionary: FeatureDictionary = dictionary(bakery) - assertEquals(emptyList(), dictionary.byTerm("Bäck").forLocale(ITALIAN).find()) + assertEquals( + emptyList(), + dictionary(bakery).byTerm("Bäck").forLocale(ITALIAN).find() + ) } @Test fun find_entry_by_term_because_fallback_locale() { - val dictionary: FeatureDictionary = dictionary(bakery) - val matches: List = - dictionary.byTerm("Bäck").forLocale(ITALIAN, null).find() - assertEquals(listOf(bakery), matches) + assertEquals( + listOf(bakery), + dictionary(bakery).byTerm("Bäck").forLocale(ITALIAN, null).find() + ) } @Test fun find_multi_word_brand_feature() { - val dictionary: FeatureDictionary = dictionary(deutsche_bank) + val dictionary = dictionary(deutsche_bank) assertEquals(listOf(deutsche_bank), dictionary.byTerm("Deutsche Ba").find()) assertEquals(listOf(deutsche_bank), dictionary.byTerm("Deut").find()) // by-word only for non-brand features @@ -608,62 +505,66 @@ class FeatureDictionaryTest { @Test fun find_multi_word_feature() { - val dictionary: FeatureDictionary = dictionary(miniature_train_shop) + val dictionary = dictionary(miniature_train_shop) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("mini").forLocale(null as Locale?).find() + dictionary.byTerm("mini").forLocale(null).find() ) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("train").forLocale(null as Locale?).find() + dictionary.byTerm("train").forLocale(null).find() ) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("shop").forLocale(null as Locale?).find() + dictionary.byTerm("shop").forLocale(null).find() ) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("Miniature Trai").forLocale(null as Locale?).find() + dictionary.byTerm("Miniature Trai").forLocale(null).find() ) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("Miniature Train Shop").forLocale(null as Locale?).find() + dictionary.byTerm("Miniature Train Shop").forLocale(null).find() ) - assertTrue(dictionary.byTerm("Train Sho").forLocale(null as Locale?).find().isEmpty()) + assertTrue(dictionary.byTerm("Train Sho").forLocale(null).find().isEmpty()) } @Test fun find_entry_by_tag_value() { - val dictionary: FeatureDictionary = dictionary(panetteria) - val matches: List = dictionary.byTerm("bakery").forLocale(ITALIAN).find() - assertEquals(listOf(panetteria), matches) + assertEquals( + listOf(panetteria), + dictionary(panetteria).byTerm("bakery").forLocale(ITALIAN).find() + ) } //endregion + //region by id + @Test fun find_no_entry_by_id() { - val dictionary: FeatureDictionary = dictionary(bakery) - assertNull(dictionary.byId("amenity/hospital").get()) + assertNull(dictionary(bakery).byId("amenity/hospital").get()) } @Test fun find_no_entry_by_id_because_unlocalized_results_are_excluded() { - val dictionary: FeatureDictionary = dictionary(bakery) - assertNull(dictionary.byId("shop/bakery").forLocale(ITALIAN).get()) + assertNull(dictionary(bakery).byId("shop/bakery").forLocale(ITALIAN).get()) } @Test fun find_entry_by_id() { - val dictionary: FeatureDictionary = dictionary(bakery) + val dictionary = dictionary(bakery) assertEquals(bakery, dictionary.byId("shop/bakery").get()) assertEquals(bakery, dictionary.byId("shop/bakery").forLocale(CHINESE, null).get()) } @Test fun find_localized_entry_by_id() { - val dictionary: FeatureDictionary = dictionary(panetteria) - assertEquals(panetteria, dictionary.byId("shop/bakery").forLocale(ITALIAN).get()) + val dictionary = dictionary(panetteria) + assertEquals( + panetteria, + dictionary.byId("shop/bakery").forLocale(ITALIAN).get() + ) assertEquals( panetteria, dictionary.byId("shop/bakery").forLocale(ITALIAN, null).get() @@ -672,7 +573,7 @@ class FeatureDictionaryTest { @Test fun find_no_entry_by_id_because_wrong_country() { - val dictionary: FeatureDictionary = dictionary(ditsch) + val dictionary = dictionary(ditsch) assertNull(dictionary.byId("shop/bakery/Ditsch").get()) assertNull(dictionary.byId("shop/bakery/Ditsch").inCountry("IT").get()) assertNull(dictionary.byId("shop/bakery/Ditsch").inCountry("AT-9").get()) @@ -680,19 +581,19 @@ class FeatureDictionaryTest { @Test fun find_entry_by_id_in_country() { - val dictionary: FeatureDictionary = dictionary(ditsch) + val dictionary = dictionary(ditsch) assertEquals(ditsch, dictionary.byId("shop/bakery/Ditsch").inCountry("AT").get()) assertEquals(ditsch, dictionary.byId("shop/bakery/Ditsch").inCountry("DE").get()) } //endregion + @Test fun find_by_term_sorts_result_in_correct_order() { - val dictionary: FeatureDictionary = dictionary( + val dictionary = dictionary( casino, baenk, bad_bank, stock_exchange, bank_of_liechtenstein, bank, bench, atm, bank_of_america, deutsche_bank, thieves_guild ) - val matches: List = dictionary.byTerm("Bank").forLocale(null as Locale?).find() assertEquals( listOf( bank, // exact name matches @@ -706,29 +607,33 @@ class FeatureDictionaryTest { stock_exchange // found word in terms - lower matchScore // casino, // not included: "Spielbank" does not start with "bank" // deutsche_bank // not included: "Deutsche Bank" does not start with "bank" and is a brand - ), matches + ), + dictionary.byTerm("Bank").forLocale(null).find() ) } @Test fun some_tests_with_real_data() { - val featureCollection: LocalizedFeatureCollection = - IDLocalizedFeatureCollection(LivePresetDataAccessAdapter()) + val featureCollection = IDLocalizedFeatureCollection(LivePresetDataAccessAdapter()) featureCollection.getAll(listOf(ENGLISH)) + val dictionary = FeatureDictionary(featureCollection, null) - val matches: List = dictionary + + val matches = dictionary .byTags(mapOf("amenity" to "studio")) .forLocale(ENGLISH) .find() assertEquals(1, matches.size) assertEquals("Studio", matches[0].name) - val matches2: List = dictionary + + val matches2 = dictionary .byTags(mapOf("amenity" to "studio", "studio" to "audio")) .forLocale(ENGLISH) .find() assertEquals(1, matches2.size) assertEquals("Recording Studio", matches2[0].name) - val matches3: List = dictionary + + val matches3 = dictionary .byTerm("Chinese Res") .forLocale(ENGLISH) .find() @@ -738,87 +643,77 @@ class FeatureDictionaryTest { @Test fun issue19() { - val lush: Feature = feature( - "shop/cosmetics/lush-a08666", - mapOf("brand:wikidata" to "Q1585448", "shop" to "cosmetics"), - listOf(GeometryType.POINT, GeometryType.AREA), - listOf("Lush"), - listOf("lush"), - listOf(), - listOf(), - true, - 2.0f, - mapOf( + val lush = feature( + id = "shop/cosmetics/lush-a08666", + tags = mapOf("brand:wikidata" to "Q1585448", "shop" to "cosmetics"), + geometries = listOf(GeometryType.POINT, GeometryType.AREA), + names = listOf("Lush"), + terms = listOf("lush"), + matchScore = 2.0f, + addTags = mapOf( "brand" to "Lush", "brand:wikidata" to "Q1585448", "name" to "Lush", "shop" to "cosmetics" ), - true, - null + isSuggestion = true, ) - val dictionary: FeatureDictionary = dictionary(lush) - val byTags: List = dictionary + + val dictionary = dictionary(lush) + + val byTags = dictionary .byTags(mapOf("brand:wikidata" to "Q1585448", "shop" to "cosmetics")) .forLocale(GERMAN, null) .inCountry("DE") .find() assertEquals(1, byTags.size) assertEquals(lush, byTags[0]) - val byTerm: List = dictionary + + val byTerm = dictionary .byTerm("Lush") .forLocale(GERMAN, null) .inCountry("DE") .find() assertEquals(1, byTerm.size) assertEquals(lush, byTerm[0]) - val byId: Feature? = dictionary + + val byId = dictionary .byId("shop/cosmetics/lush-a08666") .forLocale(GERMAN, null) .inCountry("DE") .get() assertEquals(lush, byId) } - - companion object { - private val POINT: List = listOf(GeometryType.POINT) - private fun dictionary(vararg entries: Feature): FeatureDictionary { - return FeatureDictionary( - TestLocalizedFeatureCollection(entries.filterNot { it.isSuggestion }), - TestPerCountryFeatureCollection(entries.filter { it.isSuggestion }) - ) - } - - private fun feature( - id: String, - tags: Map, - geometries: List, - names: List, - terms: List, - countryCodes: List, - excludeCountryCodes: List, - searchable: Boolean, - matchScore: Float, - addTags: Map, - isSuggestion: Boolean, - locale: Locale? - ): Feature { - return if (isSuggestion) { - BaseFeature( - id, tags, geometries, null, null, names, terms, countryCodes, - excludeCountryCodes, searchable, matchScore, isSuggestion, addTags, mapOf() - ) - } else { - val f = BaseFeature( - id, tags, geometries, null, null, names, terms, countryCodes, - excludeCountryCodes, searchable, matchScore, false, addTags, mapOf() - ) - if (locale != null) { - LocalizedFeature(f, locale, f.names, f.terms) - } else { - f - } - } - } - } } + +private val ENGLISH = Locale("en") +private val UK = Locale("en","UK") +private val ITALIAN = Locale("it") +private val GERMAN = Locale("de") +private val CHINESE = Locale("zh") + +private fun dictionary(vararg entries: Feature) = FeatureDictionary( + TestLocalizedFeatureCollection(entries.filterNot { it.isSuggestion }), + TestPerCountryFeatureCollection(entries.filter { it.isSuggestion }) +) + +private fun feature( + id: String, + tags: Map, + geometries: List = listOf(GeometryType.POINT), + names: List, + terms: List = listOf(), + countryCodes: List = listOf(), + excludeCountryCodes: List = listOf(), + searchable: Boolean = true, + matchScore: Float = 1.0f, + addTags: Map = mapOf(), + isSuggestion: Boolean = false, + locale: Locale? = null +): Feature { + val f = BaseFeature( + id, tags, geometries, null, null, names, terms, countryCodes, + excludeCountryCodes, searchable, matchScore, isSuggestion, addTags, mapOf() + ) + return if (locale != null) LocalizedFeature(f, locale, f.names, f.terms) else f +} \ No newline at end of file diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt index 386ed3d..19de03b 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt @@ -16,7 +16,7 @@ class FeatureTermIndexTest { val f1 = feature("a", "b") val f2 = feature("c") val index = index(f1, f2) - assertEquals(setOf(f1), index.getAll("b").toSet()) + assertEquals(setOf(f1), index.getAll("b")) } @Test @@ -24,7 +24,7 @@ class FeatureTermIndexTest { val f1 = feature("a", "b") val f2 = feature("a", "c") val index = index(f1, f2) - assertEquals(setOf(f1, f2), index.getAll("a").toSet()) + assertEquals(setOf(f1, f2), index.getAll("a")) } @Test @@ -32,15 +32,15 @@ class FeatureTermIndexTest { val f1 = feature("anything") val f2 = feature("anybody") val index = index(f1, f2) - assertEquals(setOf(f1, f2), index.getAll("any").toSet()) - assertEquals(setOf(f1), index.getAll("anyt").toSet()) + assertEquals(setOf(f1, f2), index.getAll("any")) + assertEquals(setOf(f1), index.getAll("anyt")) } @Test fun do_not_get_one_feature_twice() { val f1 = feature("something", "someone") val index = index(f1) - assertEquals(listOf(f1), index.getAll("some")) + assertEquals(setOf(f1), index.getAll("some")) } } From 6060392b2df76efdbfc3c6480306082f4dd7a348 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 28 Feb 2024 00:36:24 +0100 Subject: [PATCH 60/98] move JVM specific tests to jvmTest --- .../FeatureDictionaryTest.kt | 29 --------------- .../LivePresetDataAccessAdapter.kt | 22 ----------- .../FeatureDictionaryTest.kt | 37 +++++++++++++++++++ .../IDPresetsJsonParserTest.kt | 9 ++--- .../IDPresetsTranslationJsonParserTest.kt | 18 +++------ .../LivePresetDataAccessAdapter.kt | 18 +++++++++ 6 files changed, 64 insertions(+), 69 deletions(-) delete mode 100644 library/src/commonTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt create mode 100644 library/src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt create mode 100644 library/src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt index 29458ea..e475ba0 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt @@ -612,35 +612,6 @@ class FeatureDictionaryTest { ) } - @Test - fun some_tests_with_real_data() { - val featureCollection = IDLocalizedFeatureCollection(LivePresetDataAccessAdapter()) - featureCollection.getAll(listOf(ENGLISH)) - - val dictionary = FeatureDictionary(featureCollection, null) - - val matches = dictionary - .byTags(mapOf("amenity" to "studio")) - .forLocale(ENGLISH) - .find() - assertEquals(1, matches.size) - assertEquals("Studio", matches[0].name) - - val matches2 = dictionary - .byTags(mapOf("amenity" to "studio", "studio" to "audio")) - .forLocale(ENGLISH) - .find() - assertEquals(1, matches2.size) - assertEquals("Recording Studio", matches2[0].name) - - val matches3 = dictionary - .byTerm("Chinese Res") - .forLocale(ENGLISH) - .find() - assertEquals(1, matches3.size) - assertEquals("Chinese Restaurant", matches3[0].name) - } - @Test fun issue19() { val lush = feature( diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt deleted file mode 100644 index 97eed20..0000000 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt +++ /dev/null @@ -1,22 +0,0 @@ -package de.westnordost.osmfeatures - -import okio.IOException -import java.net.URL -import okio.source - -class LivePresetDataAccessAdapter : FileAccessAdapter { - - override fun exists(name: String): Boolean { - return listOf("presets.json", "de.json", "en.json", "en-GB.json").contains(name) - } - - @Throws(IOException::class) - override fun open(name: String): okio.Source { - val url: URL = if (name == "presets.json") { - URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - } else { - URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/$name") - } - return url.openStream().source() - } -} diff --git a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt new file mode 100644 index 0000000..616a87b --- /dev/null +++ b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt @@ -0,0 +1,37 @@ +package de.westnordost.osmfeatures + +import kotlin.test.assertEquals +import kotlin.test.Test + +class FeatureDictionaryTest { + @Test + fun some_tests_with_real_data() { + val featureCollection = IDLocalizedFeatureCollection(LivePresetDataAccessAdapter()) + featureCollection.getAll(listOf(ENGLISH)) + + val dictionary = FeatureDictionary(featureCollection, null) + + val matches = dictionary + .byTags(mapOf("amenity" to "studio")) + .forLocale(ENGLISH) + .find() + assertEquals(1, matches.size) + assertEquals("Studio", matches[0].name) + + val matches2 = dictionary + .byTags(mapOf("amenity" to "studio", "studio" to "audio")) + .forLocale(ENGLISH) + .find() + assertEquals(1, matches2.size) + assertEquals("Recording Studio", matches2[0].name) + + val matches3 = dictionary + .byTerm("Chinese Res") + .forLocale(ENGLISH) + .find() + assertEquals(1, matches3.size) + assertEquals("Chinese Restaurant", matches3[0].name) + } +} + +private val ENGLISH = Locale("en") diff --git a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt index 2465c72..b0681b8 100644 --- a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt +++ b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt @@ -1,18 +1,15 @@ package de.westnordost.osmfeatures import kotlin.test.Test -import okio.IOException import okio.source import kotlin.test.assertTrue import java.net.URL -class IDPresetsJsonParserJVMTest { +class IDPresetsJsonParserTest { @Test - @Throws(IOException::class) fun parse_some_real_data() { - val url = - URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - val features: List = IDPresetsJsonParser().parse(url.openStream().source()) + val url = URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") + val features = IDPresetsJsonParser().parse(url.openStream().source()) // should not crash etc assertTrue(features.size > 1000) } diff --git a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt index 6887d09..cd0beef 100644 --- a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -1,26 +1,20 @@ package de.westnordost.osmfeatures -import okio.IOException -import java.net.URISyntaxException import java.net.URL import okio.source import kotlin.test.assertTrue import kotlin.test.Test -class IDPresetsTranslationJsonParserJVMTest { +class IDPresetsTranslationJsonParserTest { @Test - @Throws(IOException::class, URISyntaxException::class) fun parse_some_real_data() { - val url = - URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - val features: List = - IDPresetsJsonParser().parse(url.openConnection().getInputStream().source()) + val url = URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") + val features = IDPresetsJsonParser().parse(url.openConnection().getInputStream().source()) val featureMap = HashMap(features.associateBy { it.id }) - val rawTranslationsURL = - URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json") - val translatedFeatures: List = IDPresetsTranslationJsonParser().parse( + val rawTranslationsURL = URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json") + val translatedFeatures = IDPresetsTranslationJsonParser().parse( rawTranslationsURL.openStream().source(), - Locale.GERMAN, + Locale("de", "DE"), featureMap ) diff --git a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt new file mode 100644 index 0000000..d25c752 --- /dev/null +++ b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt @@ -0,0 +1,18 @@ +package de.westnordost.osmfeatures + +import java.net.URL +import okio.source + +class LivePresetDataAccessAdapter : FileAccessAdapter { + + override fun exists(name: String): Boolean = + name in listOf("presets.json", "de.json", "en.json", "en-GB.json") + + override fun open(name: String): okio.Source { + val url = URL(when(name) { + "presets.json" -> "https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json" + else -> "https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/$name" + }) + return url.openStream().source() + } +} From 95bad318ffcdc874a204c06c71f266706e3de4e4 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 28 Feb 2024 01:21:26 +0100 Subject: [PATCH 61/98] remove Locale data class, use string instead --- .../de.westnordost.osmfeatures/BaseFeature.kt | 2 +- .../de.westnordost.osmfeatures/Feature.kt | 2 +- .../FeatureDictionary.kt | 83 +++++++++---------- .../IDLocalizedFeatureCollection.kt | 43 +++++----- .../IDPresetsTranslationJsonParser.kt | 4 +- .../de.westnordost.osmfeatures/Locale.kt | 16 +--- .../LocalizedFeature.kt | 2 +- .../LocalizedFeatureCollection.kt | 4 +- .../FeatureDictionaryTest.kt | 60 ++++++-------- .../IDLocalizedFeatureCollectionTest.kt | 24 +++--- .../IDPresetsTranslationJsonParserTest.kt | 2 +- .../TestLocalizedFeatureCollection.kt | 4 +- .../de.westnordost.osmfeatures/Locale.kt | 8 ++ .../de.westnordost.osmfeatures/Locale.kt | 6 ++ .../FeatureDictionaryTest.kt | 10 +-- .../IDPresetsTranslationJsonParserTest.kt | 2 +- 16 files changed, 131 insertions(+), 141 deletions(-) create mode 100644 library/src/iosMain/kotlin/de.westnordost.osmfeatures/Locale.kt create mode 100644 library/src/jvmMain/kotlin/de.westnordost.osmfeatures/Locale.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt index 6fe4b71..6e39c42 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt @@ -21,6 +21,6 @@ data class BaseFeature( override val canonicalTerms: List = terms.map { it.canonicalize() } override val name: String get() = names[0] - override val locale: Locale? get() = null + override val locale: String? get() = null override fun toString(): String = id } \ No newline at end of file diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt index 343c5d9..88d405c 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt @@ -23,5 +23,5 @@ interface Feature { val canonicalNames: List val canonicalTerms: List val isSuggestion: Boolean - val locale: Locale? + val locale: String? } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt index 8cbd166..9a5742c 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt @@ -1,23 +1,23 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.Locale.Companion.default - class FeatureDictionary internal constructor( private val featureCollection: LocalizedFeatureCollection, private val brandFeatureCollection: PerCountryFeatureCollection? ) { private val brandNamesIndexes: MutableMap, FeatureTermIndex> = HashMap() private val brandTagsIndexes: MutableMap, FeatureTagsIndex> = HashMap() - private val tagsIndexes: MutableMap, FeatureTagsIndex> = HashMap() - private val namesIndexes: MutableMap, FeatureTermIndex> = HashMap() - private val termsIndexes: MutableMap, FeatureTermIndex> = HashMap() - private val tagValuesIndexes: MutableMap, FeatureTermIndex> = HashMap() + + // locale list -> index + private val tagsIndexes: MutableMap, FeatureTagsIndex> = HashMap() + private val namesIndexes: MutableMap, FeatureTermIndex> = HashMap() + private val termsIndexes: MutableMap, FeatureTermIndex> = HashMap() + private val tagValuesIndexes: MutableMap, FeatureTermIndex> = HashMap() init { // build indices for default locale - getTagsIndex(listOf(default, null)) - getNamesIndex(listOf(default)) - getTermsIndex(listOf(default)) + getTagsIndex(listOf(defaultLocale(), null)) + getNamesIndex(listOf(defaultLocale())) + getTermsIndex(listOf(defaultLocale())) } //region Get by id @@ -27,7 +27,7 @@ class FeatureDictionary internal constructor( private fun getById( id: String, - locales: List = listOf(default), + locales: List = listOf(defaultLocale()), countryCode: String? = null ): Feature? { return featureCollection.get(id, locales) @@ -44,7 +44,7 @@ class FeatureDictionary internal constructor( private fun getByTags( tags: Map, geometry: GeometryType? = null, - locales: List = listOf(default), + locales: List = listOf(defaultLocale()), countryCode: String? = null, isSuggestion: Boolean? = null ): List { @@ -110,7 +110,7 @@ class FeatureDictionary internal constructor( private fun getByTerm( search: String, geometry: GeometryType?, - locales: List, + locales: List, countryCode: String?, isSuggestion: Boolean? ): Sequence { @@ -186,19 +186,19 @@ class FeatureDictionary internal constructor( //region Lazily get or create Indexes /** lazily get or create tags index for given locale(s) */ - private fun getTagsIndex(locales: List): FeatureTagsIndex { + private fun getTagsIndex(locales: List): FeatureTagsIndex { return tagsIndexes.synchronizedGetOrCreate(locales, ::createTagsIndex) } - private fun createTagsIndex(locales: List): FeatureTagsIndex { + private fun createTagsIndex(locales: List): FeatureTagsIndex { return FeatureTagsIndex(featureCollection.getAll(locales)) } /** lazily get or create names index for given locale(s) */ - private fun getNamesIndex(locales: List): FeatureTermIndex = + private fun getNamesIndex(locales: List): FeatureTermIndex = namesIndexes.synchronizedGetOrCreate(locales, ::createNamesIndex) - private fun createNamesIndex(locales: List): FeatureTermIndex { + private fun createNamesIndex(locales: List): FeatureTermIndex { val features = featureCollection.getAll(locales) return FeatureTermIndex(features) { feature -> feature.getSearchableNames().toList() @@ -206,22 +206,22 @@ class FeatureDictionary internal constructor( } /** lazily get or create terms index for given locale(s) */ - private fun getTermsIndex(locales: List): FeatureTermIndex { + private fun getTermsIndex(locales: List): FeatureTermIndex { return termsIndexes.synchronizedGetOrCreate(locales, ::createTermsIndex) } - private fun createTermsIndex(locales: List): FeatureTermIndex { + private fun createTermsIndex(locales: List): FeatureTermIndex { return FeatureTermIndex(featureCollection.getAll(locales)) { feature -> if (!feature.isSearchable) emptyList() else feature.canonicalTerms } } /** lazily get or create tag values index */ - private fun getTagValuesIndex(locales: List): FeatureTermIndex { + private fun getTagValuesIndex(locales: List): FeatureTermIndex { return tagValuesIndexes.synchronizedGetOrCreate(locales, ::createTagValuesIndex) } - private fun createTagValuesIndex(locales: List): FeatureTermIndex { + private fun createTagValuesIndex(locales: List): FeatureTermIndex { return FeatureTermIndex(featureCollection.getAll(locales)) { feature -> if (!feature.isSearchable) return@FeatureTermIndex emptyList() return@FeatureTermIndex feature.tags.values.filter { it != "*" } @@ -261,23 +261,22 @@ class FeatureDictionary internal constructor( //region Query builders inner class QueryByIdBuilder(private val id: String) { - private var locale: List = listOf(default) + private var locale: List = listOf(defaultLocale()) private var countryCode: String? = null /** - * Sets the locale(s) in which to present the results. + * Sets the locale(s) in which to present the results as IETF language tags * * You can specify several locales in a row to each fall back to if a translation does not - * exist in the locale before that. For example - * `[new Locale("ca", "ES"), new Locale("es","ES")]` - * if you prefer results in Catalan, but Spanish is also fine. + * exist in the locale before that. For example `["ca-ES","es-ES"]` if you prefer results + * in Catalan, but Spanish is also fine. * * `null` means to include unlocalized results. * - * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, + * If nothing is specified, it defaults to `[, null]`, * i.e. unlocalized results are included by default. */ - fun forLocale(vararg locales: Locale?): QueryByIdBuilder { + fun forLocale(vararg locales: String?): QueryByIdBuilder { this.locale = locales.toList() return this } @@ -296,7 +295,7 @@ class FeatureDictionary internal constructor( inner class QueryByTagBuilder(private val tags: Map) { private var geometryType: GeometryType? = null - private var locale: List = listOf(default) + private var locale: List = listOf(defaultLocale()) private var suggestion: Boolean? = null private var countryCode: String? = null @@ -307,19 +306,18 @@ class FeatureDictionary internal constructor( } /** - * Sets the locale(s) in which to present the results. + * Sets the locale(s) in which to present the results as IETF language tags * * You can specify several locales in a row to each fall back to if a translation does not - * exist in the locale before that. For example - * `[new Locale("ca", "ES"), new Locale("es","ES")]` - * if you prefer results in Catalan, but Spanish is also fine. + * exist in the locale before that. For example `["ca-ES","es-ES"]` if you prefer results + * in Catalan, but Spanish is also fine. * * `null` means to include unlocalized results. * - * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, + * If nothing is specified, it defaults to `[, null]`, * i.e. unlocalized results are included by default. */ - fun forLocale(vararg locales: Locale?): QueryByTagBuilder { + fun forLocale(vararg locales: String?): QueryByTagBuilder { this.locale = locales.toList() return this } @@ -340,7 +338,9 @@ class FeatureDictionary internal constructor( } /** Returns a list of dictionary entries that match or an empty list if nothing is - * found.

In rare cases, a set of tags may match multiple primary features, such as for + * found. + * + * In rare cases, a set of tags may match multiple primary features, such as for * tag combinations like `shop=deli` + `amenity=cafe`, so, this is why * it is a list. */ fun find(): List = getByTags(tags, geometryType, locale, countryCode, suggestion) @@ -348,7 +348,7 @@ class FeatureDictionary internal constructor( inner class QueryByTermBuilder(private val term: String) { private var geometryType: GeometryType? = null - private var locale: List = listOf(default) + private var locale: List = listOf(defaultLocale()) private var suggestion: Boolean? = null private var limit = 50 private var countryCode: String? = null @@ -360,19 +360,18 @@ class FeatureDictionary internal constructor( } /** - * Sets the locale(s) in which to present the results. + * Sets the locale(s) in which to present the results as IETF language tags * * You can specify several locales in a row to each fall back to if a translation does not - * exist in the locale before that. For example - * `[new Locale("ca", "ES"), new Locale("es","ES")]` - * if you prefer results in Catalan, but Spanish is also fine. + * exist in the locale before that. For example `["ca-ES","es-ES"]` if you prefer results + * in Catalan, but Spanish is also fine. * * `null` means to include unlocalized results. * - * If nothing is specified, it defaults to `[Locale.getDefault(), null]`, + * If nothing is specified, it defaults to `[, null]`, * i.e. unlocalized results are included by default. */ - fun forLocale(vararg locales: Locale?): QueryByTermBuilder { + fun forLocale(vararg locales: String?): QueryByTermBuilder { this.locale = locales.toList() return this } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt index 6f54a97..244f8a6 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt @@ -14,20 +14,20 @@ class IDLocalizedFeatureCollection( private val featuresById: LinkedHashMap // locale -> localized feature - private val localizedFeaturesList: MutableMap> = HashMap() + private val localizedFeaturesList: MutableMap> = HashMap() // locales -> featureId -> Feature - private val localizedFeatures: MutableMap, LinkedHashMap> = HashMap() + private val localizedFeatures: MutableMap, LinkedHashMap> = HashMap() init { featuresById = loadFeatures().associateByTo(LinkedHashMap()) { it.id } } - override fun getAll(locales: List): Collection { + override fun getAll(locales: List): Collection { return getOrLoadLocalizedFeatures(locales).values } - override fun get(id: String, locales: List): Feature? { + override fun get(id: String, locales: List): Feature? { return getOrLoadLocalizedFeatures(locales)[id] } @@ -37,15 +37,15 @@ class IDLocalizedFeatureCollection( } } - private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap { + private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap { return localizedFeatures.synchronizedGetOrCreate(locales, ::loadLocalizedFeatures) } - private fun loadLocalizedFeatures(locales: List): LinkedHashMap { + private fun loadLocalizedFeatures(locales: List): LinkedHashMap { val result = LinkedHashMap(featuresById.size) for (locale in locales.asReversed()) { if (locale != null) { - for (localeComponent in locale.getComponents()) { + for (localeComponent in locale.getLocaleComponents()) { val features = getOrLoadLocalizedFeaturesList(localeComponent) for (feature in features) { result[feature.id] = feature @@ -58,11 +58,11 @@ class IDLocalizedFeatureCollection( return result } - private fun getOrLoadLocalizedFeaturesList(locale: Locale): List { + private fun getOrLoadLocalizedFeaturesList(locale: String): List { return localizedFeaturesList.synchronizedGetOrCreate(locale, ::loadLocalizedFeaturesList) } - private fun loadLocalizedFeaturesList(locale: Locale?): List { + private fun loadLocalizedFeaturesList(locale: String?): List { val filename = if (locale != null) getLocalizationFilename(locale) else "en.json" if (!fileAccess.exists(filename)) return emptyList() fileAccess.open(filename).use { source -> @@ -73,16 +73,19 @@ class IDLocalizedFeatureCollection( companion object { private const val FEATURES_FILE = "presets.json" - private fun getLocalizationFilename(locale: Locale): String = - locale.languageTag + ".json" - - private fun Locale.getComponents(): List { - val result = ArrayList(4) - result.add(Locale(language)) - if (region != null) result.add(Locale(language, region = region)) - if (script != null) result.add(Locale(language, script = script)) - if (region != null && script != null) result.add(Locale(language, region = region, script = script)) - return result - } + private fun getLocalizationFilename(locale: String): String = "$locale.json" } } + +private fun String.getLocaleComponents(): Sequence = sequence { + val components = split('-') + val language = components.first() + yield(language) + if (components.size == 1) return@sequence + + val others = components.subList(1, components.size) + for (other in others) { + yield(listOf(language, other).joinToString("-")) + } + yield(this@getLocaleComponents) +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt index 99ad27a..f3dfc9d 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt @@ -13,7 +13,7 @@ import okio.Source class IDPresetsTranslationJsonParser { fun parse( - source: Source, locale: Locale?, baseFeatures: Map + source: Source, locale: String?, baseFeatures: Map ): List { val decodedObject = createFromSource(source) val languageKey: String = decodedObject.entries.iterator().next().key @@ -48,7 +48,7 @@ class IDPresetsTranslationJsonParser { return ArrayList(localizedFeatures.values) } - private fun parseFeature(feature: BaseFeature?, locale: Locale?, localization: JsonObject): LocalizedFeature? { + private fun parseFeature(feature: BaseFeature?, locale: String?, localization: JsonObject): LocalizedFeature? { if (feature == null) return null val name = localization["name"]?.jsonPrimitive?.content diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt index a1f9ddc..525d01b 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt @@ -1,17 +1,3 @@ package de.westnordost.osmfeatures -import kotlin.jvm.JvmOverloads - -data class Locale @JvmOverloads constructor( - val language: String, - val region: String? = null, - val script: String? = null -) { - companion object { - val default: Locale? = null - } - - /** IETF language tag */ - val languageTag: String - get() = listOfNotNull(language, script, region).joinToString("-") -} \ No newline at end of file +internal expect fun defaultLocale(): String \ No newline at end of file diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt index 7bb3beb..0b3f09b 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt @@ -5,7 +5,7 @@ package de.westnordost.osmfeatures * I.e. the name and terms are specified in the given locale. */ class LocalizedFeature( private val p: BaseFeature, - override val locale: Locale?, + override val locale: String?, override val names: List, override val terms: List ) : Feature { diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt index b809c40..d5f2e61 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt @@ -3,9 +3,9 @@ package de.westnordost.osmfeatures /** A localized collection of features */ interface LocalizedFeatureCollection { /** Returns all features in the given locale(s). */ - fun getAll(locales: List): Collection + fun getAll(locales: List): Collection /** Returns the feature with the given id in the given locale(s) or null if it has not been * found (for the given locale(s)) */ - fun get(id: String, locales: List): Feature? + fun get(id: String, locales: List): Feature? } \ No newline at end of file diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt index e475ba0..d5e7bef 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt @@ -18,7 +18,7 @@ class FeatureDictionaryTest { id = "shop/bakery", tags = mapOf("shop" to "bakery"), names = listOf("Panetteria"), - locale = ITALIAN + locale = "it" ) private val ditsch = feature( // brand in DE for shop=bakery id = "shop/bakery/Ditsch", @@ -48,21 +48,21 @@ class FeatureDictionaryTest { id = "shop/alcohol", tags = mapOf("shop" to "alcohol"), names = listOf("Off licence (Alcohol shop)"), - locale = UK + locale = "en-GB" ) private val car_dealer = feature( // German localized unspecific shop=car id = "shop/car", tags = mapOf("shop" to "car"), names = listOf("Autohändler"), terms = listOf("auto"), - locale = GERMAN + locale = "de" ) private val second_hand_car_dealer = feature( // German localized shop=car with subtags id = "shop/car/second_hand", tags = mapOf("shop" to "car", "second_hand" to "only"), names = listOf("Gebrauchtwagenhändler"), terms = listOf("auto"), - locale = GERMAN + locale = "de" ) private val scheisshaus = feature( // unsearchable feature id = "amenity/scheißhaus", @@ -166,7 +166,7 @@ class FeatureDictionaryTest { emptyList(), dictionary(bakery) .byTags(mapOf("shop" to "bakery")) - .forLocale(ITALIAN) + .forLocale("it") .find() ) } @@ -177,7 +177,7 @@ class FeatureDictionaryTest { listOf(bakery), dictionary(bakery) .byTags(mapOf("shop" to "bakery")) - .forLocale(ITALIAN, null) + .forLocale("it", null) .find() ) } @@ -227,11 +227,11 @@ class FeatureDictionaryTest { assertEquals( listOf(panetteria), - dictionary.byTags(tags).forLocale(ITALIAN).find() + dictionary.byTags(tags).forLocale("it").find() ) assertEquals( emptyList(), - dictionary.byTags(tags).forLocale(ENGLISH).find() + dictionary.byTags(tags).forLocale("en").find() ) assertEquals( listOf(bakery), @@ -290,7 +290,7 @@ class FeatureDictionaryTest { listOf(car_dealer), dictionary(car_dealer, second_hand_car_dealer) .byTags(mapOf("shop" to "car")) - .forLocale(GERMAN, null) + .forLocale("de", null) .find() ) } @@ -301,7 +301,7 @@ class FeatureDictionaryTest { listOf(second_hand_car_dealer), dictionary(car_dealer, second_hand_car_dealer) .byTags(mapOf("shop" to "car", "second_hand" to "only")) - .forLocale(GERMAN, null) + .forLocale("de", null) .find() ) } @@ -402,19 +402,19 @@ class FeatureDictionaryTest { val dictionary = dictionary(liquor_store) assertEquals( listOf(liquor_store), - dictionary.byTerm("Alcohol").forLocale(UK).find() + dictionary.byTerm("Alcohol").forLocale("en-GB").find() ) assertEquals( listOf(liquor_store), - dictionary.byTerm("Off licence (Alcohol Shop)").forLocale(UK).find() + dictionary.byTerm("Off licence (Alcohol Shop)").forLocale("en-GB").find() ) assertEquals( listOf(liquor_store), - dictionary.byTerm("Off Licence").forLocale(UK).find() + dictionary.byTerm("Off Licence").forLocale("en-GB").find() ) assertEquals( listOf(liquor_store), - dictionary.byTerm("Off Licence (Alco").forLocale(UK).find() + dictionary.byTerm("Off Licence (Alco").forLocale("en-GB").find() ) } @@ -434,7 +434,7 @@ class FeatureDictionaryTest { setOf(second_hand_car_dealer, car_dealer), dictionary(second_hand_car_dealer, car_dealer) .byTerm("auto") - .forLocale(GERMAN) + .forLocale("de") .find() .toSet() ) @@ -446,7 +446,7 @@ class FeatureDictionaryTest { 1, dictionary(second_hand_car_dealer, car_dealer) .byTerm("auto") - .forLocale(GERMAN) + .forLocale("de") .limit(1) .find() .size @@ -482,7 +482,7 @@ class FeatureDictionaryTest { fun find_no_entry_by_term_because_wrong_locale() { assertEquals( emptyList(), - dictionary(bakery).byTerm("Bäck").forLocale(ITALIAN).find() + dictionary(bakery).byTerm("Bäck").forLocale("it").find() ) } @@ -490,7 +490,7 @@ class FeatureDictionaryTest { fun find_entry_by_term_because_fallback_locale() { assertEquals( listOf(bakery), - dictionary(bakery).byTerm("Bäck").forLocale(ITALIAN, null).find() + dictionary(bakery).byTerm("Bäck").forLocale("it", null).find() ) } @@ -533,7 +533,7 @@ class FeatureDictionaryTest { fun find_entry_by_tag_value() { assertEquals( listOf(panetteria), - dictionary(panetteria).byTerm("bakery").forLocale(ITALIAN).find() + dictionary(panetteria).byTerm("bakery").forLocale("it").find() ) } @@ -548,14 +548,14 @@ class FeatureDictionaryTest { @Test fun find_no_entry_by_id_because_unlocalized_results_are_excluded() { - assertNull(dictionary(bakery).byId("shop/bakery").forLocale(ITALIAN).get()) + assertNull(dictionary(bakery).byId("shop/bakery").forLocale("it").get()) } @Test fun find_entry_by_id() { val dictionary = dictionary(bakery) assertEquals(bakery, dictionary.byId("shop/bakery").get()) - assertEquals(bakery, dictionary.byId("shop/bakery").forLocale(CHINESE, null).get()) + assertEquals(bakery, dictionary.byId("shop/bakery").forLocale("zh", null).get()) } @Test @@ -563,11 +563,11 @@ class FeatureDictionaryTest { val dictionary = dictionary(panetteria) assertEquals( panetteria, - dictionary.byId("shop/bakery").forLocale(ITALIAN).get() + dictionary.byId("shop/bakery").forLocale("it").get() ) assertEquals( panetteria, - dictionary.byId("shop/bakery").forLocale(ITALIAN, null).get() + dictionary.byId("shop/bakery").forLocale("it", null).get() ) } @@ -634,7 +634,7 @@ class FeatureDictionaryTest { val byTags = dictionary .byTags(mapOf("brand:wikidata" to "Q1585448", "shop" to "cosmetics")) - .forLocale(GERMAN, null) + .forLocale("de", null) .inCountry("DE") .find() assertEquals(1, byTags.size) @@ -642,7 +642,7 @@ class FeatureDictionaryTest { val byTerm = dictionary .byTerm("Lush") - .forLocale(GERMAN, null) + .forLocale("de", null) .inCountry("DE") .find() assertEquals(1, byTerm.size) @@ -650,19 +650,13 @@ class FeatureDictionaryTest { val byId = dictionary .byId("shop/cosmetics/lush-a08666") - .forLocale(GERMAN, null) + .forLocale("de", null) .inCountry("DE") .get() assertEquals(lush, byId) } } -private val ENGLISH = Locale("en") -private val UK = Locale("en","UK") -private val ITALIAN = Locale("it") -private val GERMAN = Locale("de") -private val CHINESE = Locale("zh") - private fun dictionary(vararg entries: Feature) = FeatureDictionary( TestLocalizedFeatureCollection(entries.filterNot { it.isSuggestion }), TestPerCountryFeatureCollection(entries.filter { it.isSuggestion }) @@ -680,7 +674,7 @@ private fun feature( matchScore: Float = 1.0f, addTags: Map = mapOf(), isSuggestion: Boolean = false, - locale: Locale? = null + locale: String? = null ): Feature { val f = BaseFeature( id, tags, geometries, null, null, names, terms, countryCodes, diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt index 72193ef..5dccd1a 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt @@ -29,7 +29,7 @@ class IDLocalizedFeatureCollectionTest { }) // getting non-localized features - val notLocalized = listOf(null) + val notLocalized = listOf(null) val notLocalizedFeatures = c.getAll(notLocalized) assertEquals(setOf("test", "test", "test"), notLocalizedFeatures.map { it.name }.toSet()) assertEquals("test", c.get("some/id", notLocalized)?.name) @@ -37,7 +37,7 @@ class IDLocalizedFeatureCollectionTest { assertEquals("test", c.get("yet/another/id", notLocalized)?.name) // getting English features - val english = listOf(ENGLISH) + val english = listOf("en") val englishFeatures = c.getAll(english) assertEquals(setOf("Bakery"), englishFeatures.map { it.name }.toSet()) assertEquals("Bakery", c.get("some/id", english)?.name) @@ -46,7 +46,7 @@ class IDLocalizedFeatureCollectionTest { // getting Germany features // this also tests if the fallback from de-DE to de works if de-DE.json does not exist - val germany = listOf(GERMANY) + val germany = listOf("de-DE") val germanyFeatures = c.getAll(germany) assertEquals(setOf("Bäckerei", "Gullideckel"), germanyFeatures.map { it.name }.toSet()) assertEquals("Bäckerei", c.get("some/id", germany)?.name) @@ -54,7 +54,7 @@ class IDLocalizedFeatureCollectionTest { assertNull(c.get("yet/another/id", germany)) // getting features through fallback chain - val locales = listOf(ENGLISH, GERMANY, null) + val locales = listOf("en", "de-DE", null) val fallbackFeatures = c.getAll(locales) assertEquals( setOf("Bakery", "Gullideckel", "test"), @@ -63,8 +63,8 @@ class IDLocalizedFeatureCollectionTest { assertEquals("Bakery", c.get("some/id", locales)?.name) assertEquals("Gullideckel", c.get("another/id", locales)?.name) assertEquals("test", c.get("yet/another/id", locales)?.name) - assertEquals(ENGLISH, c.get("some/id", locales)?.locale) - assertEquals(GERMAN, c.get("another/id", locales)?.locale) + assertEquals("en", c.get("some/id", locales)?.locale) + assertEquals("de", c.get("another/id", locales)?.locale) assertNull(c.get("yet/another/id", locales)?.locale) } @@ -91,7 +91,7 @@ class IDLocalizedFeatureCollectionTest { }) // standard case - no merging - val german = listOf(GERMAN) + val german = listOf("de") val germanFeatures = c.getAll(german) assertEquals(setOf("Bäckerei", "Gullideckel"), germanFeatures.map { it.name }.toSet()) assertEquals("Bäckerei", c.get("some/id", german)?.name) @@ -99,7 +99,7 @@ class IDLocalizedFeatureCollectionTest { assertNull(c.get("yet/another/id", german)) // merging de-AT and de - val austria = listOf(Locale("de", "AT")) + val austria = listOf("de-AT") val austrianFeatures = c.getAll(austria) assertEquals( setOf("Backhusl", "Gullideckel", "Brückle"), @@ -110,16 +110,12 @@ class IDLocalizedFeatureCollectionTest { assertEquals("Brückle", c.get("yet/another/id", austria)?.name) // merging scripts - val cryllic = listOf(Locale("de", script = "Cyrl")) + val cryllic = listOf("de-Cyrl") assertEquals("бацкхаус", c.get("some/id", cryllic)?.name) - val cryllicAustria = listOf(Locale("de", "AT", "Cyrl")) + val cryllicAustria = listOf("de-Cyrl-AT")) assertEquals("бацкхусл", c.get("some/id", cryllicAustria)?.name) } } private fun getSource(file: String): Source = FileSystemAccess("src/commonTest/resources").open(file) - -private val ENGLISH = Locale("en") -private val GERMAN = Locale("de") -private val GERMANY = Locale("de", "DE") \ No newline at end of file diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt index e904b0d..e6483e1 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -55,7 +55,7 @@ class IDPresetsTranslationJsonParserTest { val featureMap = HashMap(baseFeatures.associateBy { it.id }) IDPresetsTranslationJsonParser().parse( getSource(translationsFile), - Locale("en"), + "en", featureMap ) } catch (e: IOException) { diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt index 17994b8..1e06d25 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt @@ -3,11 +3,11 @@ package de.westnordost.osmfeatures class TestLocalizedFeatureCollection(private val features: List) : LocalizedFeatureCollection { - override fun getAll(locales: List): Collection { + override fun getAll(locales: List): Collection { return features.filter { locales.contains(it.locale) } } - override fun get(id: String, locales: List): Feature? { + override fun get(id: String, locales: List): Feature? { val feature = features.find { it.id == id } return if (feature == null || (!locales.contains(feature.locale))) null else feature } diff --git a/library/src/iosMain/kotlin/de.westnordost.osmfeatures/Locale.kt b/library/src/iosMain/kotlin/de.westnordost.osmfeatures/Locale.kt new file mode 100644 index 0000000..35de84a --- /dev/null +++ b/library/src/iosMain/kotlin/de.westnordost.osmfeatures/Locale.kt @@ -0,0 +1,8 @@ +package de.westnordost.osmfeatures + +import platform.Foundation.NSLocale +import platform.Foundation.currentLocale +import platform.Foundation.localeIdentifier + +internal actual fun defaultLocale(): String = + NSLocale.currentLocale.localeIdentifier \ No newline at end of file diff --git a/library/src/jvmMain/kotlin/de.westnordost.osmfeatures/Locale.kt b/library/src/jvmMain/kotlin/de.westnordost.osmfeatures/Locale.kt new file mode 100644 index 0000000..28b0c44 --- /dev/null +++ b/library/src/jvmMain/kotlin/de.westnordost.osmfeatures/Locale.kt @@ -0,0 +1,6 @@ +package de.westnordost.osmfeatures + +import java.util.Locale + +internal actual fun defaultLocale(): String = + Locale.getDefault().toLanguageTag() \ No newline at end of file diff --git a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt index 616a87b..802c0f4 100644 --- a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt +++ b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt @@ -7,31 +7,29 @@ class FeatureDictionaryTest { @Test fun some_tests_with_real_data() { val featureCollection = IDLocalizedFeatureCollection(LivePresetDataAccessAdapter()) - featureCollection.getAll(listOf(ENGLISH)) + featureCollection.getAll(listOf("en")) val dictionary = FeatureDictionary(featureCollection, null) val matches = dictionary .byTags(mapOf("amenity" to "studio")) - .forLocale(ENGLISH) + .forLocale("en") .find() assertEquals(1, matches.size) assertEquals("Studio", matches[0].name) val matches2 = dictionary .byTags(mapOf("amenity" to "studio", "studio" to "audio")) - .forLocale(ENGLISH) + .forLocale("en") .find() assertEquals(1, matches2.size) assertEquals("Recording Studio", matches2[0].name) val matches3 = dictionary .byTerm("Chinese Res") - .forLocale(ENGLISH) + .forLocale("en") .find() assertEquals(1, matches3.size) assertEquals("Chinese Restaurant", matches3[0].name) } } - -private val ENGLISH = Locale("en") diff --git a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt index cd0beef..4288fc1 100644 --- a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -14,7 +14,7 @@ class IDPresetsTranslationJsonParserTest { val rawTranslationsURL = URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json") val translatedFeatures = IDPresetsTranslationJsonParser().parse( rawTranslationsURL.openStream().source(), - Locale("de", "DE"), + "de-DE", featureMap ) From 4de0ab721ffac0e3dbf3ff0e5c0565e036f536ab Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 28 Feb 2024 01:40:01 +0100 Subject: [PATCH 62/98] make LocalizedFeature a data class --- .../kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt index 7bb3beb..5b64235 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt @@ -3,7 +3,7 @@ package de.westnordost.osmfeatures /** Data class associated with the Feature interface. Represents a localized feature. * * I.e. the name and terms are specified in the given locale. */ -class LocalizedFeature( +data class LocalizedFeature( private val p: BaseFeature, override val locale: Locale?, override val names: List, From 7b409abd20ba9d6581d7a58c9a86e615fd35ac60 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 28 Feb 2024 01:42:22 +0100 Subject: [PATCH 63/98] make classes that should be internal internal --- .../kotlin/de.westnordost.osmfeatures/CollectionUtils.kt | 2 +- .../kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt | 3 +-- .../kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt | 2 +- .../de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt | 2 +- .../de.westnordost.osmfeatures/LocalizedFeatureCollection.kt | 2 +- .../de.westnordost.osmfeatures/PerCountryFeatureCollection.kt | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt index 75c95ac..f1a894a 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt @@ -2,7 +2,7 @@ package de.westnordost.osmfeatures /** For the given map, get the value of the entry at the given key and if there is no * entry yet, create it using the given create function thread-safely */ -fun MutableMap.synchronizedGetOrCreate(key: K, createFn: (K) -> V): V { +internal fun MutableMap.synchronizedGetOrCreate(key: K, createFn: (K) -> V): V { synchronized(this) { val value = get(key) return if (value == null) { diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt index cae7d91..7331f76 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt @@ -6,11 +6,10 @@ package de.westnordost.osmfeatures * * Based on ContainedMapTree data structure, see that class. */ internal class FeatureTagsIndex(features: Collection) { - private val featureMap: MutableMap, MutableList> + private val featureMap: MutableMap, MutableList> = HashMap(features.size) private val tree: ContainedMapTree init { - featureMap = HashMap(features.size) for (feature in features) { val map = featureMap.getOrPut(feature.tags) { ArrayList(1) } map.add(feature) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt index f45481b..c4576bb 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt @@ -4,7 +4,7 @@ package de.westnordost.osmfeatures * Index that makes finding Features whose name/term/... starts with a given string very efficient. * * Based on the StartsWithStringTree data structure, see that class. */ -class FeatureTermIndex(features: Collection, getStrings: (Feature) -> List) { +internal class FeatureTermIndex(features: Collection, getStrings: (Feature) -> List) { private val featureMap: MutableMap> = HashMap(features.size) private val tree: StartsWithStringTree diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt index 6f54a97..0d7b775 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt @@ -7,7 +7,7 @@ import okio.use * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that * there is a presets.json which includes all the features. The translations are expected to be * located in the same directory named like e.g. de.json, pt-BR.json etc. */ -class IDLocalizedFeatureCollection( +internal class IDLocalizedFeatureCollection( private val fileAccess: FileAccessAdapter ) : LocalizedFeatureCollection { // featureId -> Feature diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt index b809c40..9ff61a8 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt @@ -1,7 +1,7 @@ package de.westnordost.osmfeatures /** A localized collection of features */ -interface LocalizedFeatureCollection { +internal interface LocalizedFeatureCollection { /** Returns all features in the given locale(s). */ fun getAll(locales: List): Collection diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/PerCountryFeatureCollection.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/PerCountryFeatureCollection.kt index 1b64f99..6bce4c4 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/PerCountryFeatureCollection.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/PerCountryFeatureCollection.kt @@ -1,7 +1,7 @@ package de.westnordost.osmfeatures /** A collection of features grouped by country code */ -interface PerCountryFeatureCollection { +internal interface PerCountryFeatureCollection { /** Returns all features with the given country codes. */ fun getAll(countryCodes: List): Collection From b68f5c42e8a8ec45ab9d91b436455ce41ff70503 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Mon, 4 Mar 2024 19:50:12 +0100 Subject: [PATCH 64/98] use ktor --- gradle/libs.versions.toml | 3 ++ library/build.gradle.kts | 3 ++ .../IDPresetsJsonParser.kt | 6 ++- .../IDPresetsTranslationJsonParser.kt | 15 ++++--- .../de.westnordost.osmfeatures/JsonUtils.kt | 6 +-- .../IDPresetsJsonParserTest.kt | 25 ++++++++++++ .../IDPresetsTranslationJsonParserTest.kt | 40 +++++++++++++++++++ .../IDPresetsJsonParserTest.kt | 16 -------- .../IDPresetsTranslationJsonParserTest.kt | 24 ----------- 9 files changed, 89 insertions(+), 49 deletions(-) delete mode 100644 library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt delete mode 100644 library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0c895a3..58cbafc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,12 +4,15 @@ kotlin = "1.9.22" kotlinx-serialization-json = "1.6.0" normalize = "1.0.5" okio = "3.6.0" +ktor = "2.3.8" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } normalize = { module = "com.doist.x:normalize", version.ref = "normalize" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } +ktor = { module = "io.ktor:ktor-client-core", version.ref = "ktor"} +ktor-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor"} [plugins] diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 076d9d3..032f305 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -25,10 +25,13 @@ kotlin { } commonTest.dependencies { implementation(libs.kotlin.test) + implementation(libs.ktor) + implementation(libs.ktor.cio) } jvmTest.dependencies { implementation(libs.kotlin.test) + implementation(libs.ktor) } } } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParser.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParser.kt index f2c06f7..5a84c8f 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParser.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParser.kt @@ -25,6 +25,10 @@ class IDPresetsJsonParser { val decodedObject = Json.decodeFromString(sink.readUtf8()) return decodedObject.mapNotNull { (key, value) -> parseFeature(key, value.jsonObject)} } + fun parse(content: String): List { + val decodedObject = Json.decodeFromString(content) + return decodedObject.mapNotNull { (key, value) -> parseFeature(key, value.jsonObject)} + } private fun parseFeature(id: String, p: JsonObject): BaseFeature? { val tags = parseStringMap(p["tags"]?.jsonObject) @@ -91,7 +95,7 @@ class IDPresetsJsonParser { val result: MutableList = ArrayList(list.size) for (item in list) { // for example a lat,lon pair to denote a location with radius. Not supported. - val cc = item.uppercase().intern() + val cc = item.uppercase() // don't need this, 001 stands for "whole world" if (cc == "001") continue // ISO-3166-2 codes are supported but not m49 code such as "150" or geojsons like "city_national_bank_fl.geojson" diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt index 99ad27a..3b4d975 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt @@ -1,6 +1,6 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.JsonUtils.createFromSource +import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive @@ -15,7 +15,13 @@ class IDPresetsTranslationJsonParser { fun parse( source: Source, locale: Locale?, baseFeatures: Map ): List { - val decodedObject = createFromSource(source) + val content = JsonUtils.getContent(source) + return parse(content, locale, baseFeatures) + } + fun parse( + content: String, locale: Locale?, baseFeatures: Map + ): List { + val decodedObject = Json.decodeFromString(content) val languageKey: String = decodedObject.entries.iterator().next().key val languageObject = decodedObject[languageKey] ?: return emptyList() @@ -25,9 +31,8 @@ class IDPresetsTranslationJsonParser { ?: return emptyList() val localizedFeatures: MutableMap = HashMap(presetsObject.size) presetsObject.entries.forEach { (key, value) -> - val id = key.intern() - val f = parseFeature(baseFeatures[id], locale, value.jsonObject) - if (f != null) localizedFeatures[id] = f + val f = parseFeature(baseFeatures[key], locale, value.jsonObject) + if (f != null) localizedFeatures[key] = f } for (baseFeature in baseFeatures.values) { val names = baseFeature.names diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/JsonUtils.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/JsonUtils.kt index d342d04..90e2894 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/JsonUtils.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/JsonUtils.kt @@ -12,16 +12,16 @@ internal object JsonUtils { fun parseStringMap(map: JsonObject?): Map { if (map == null) return HashMap(1) - return map.map { (key, value) -> key.intern() to value.jsonPrimitive.content}.toMap().toMutableMap() + return map.map { (key, value) -> key to value.jsonPrimitive.content}.toMap().toMutableMap() } // this is only necessary because Android uses some old version of org.json where // new JSONObject(new JSONTokener(inputStream)) is not defined... - fun createFromSource(source: Source): JsonObject { + fun getContent(source: Source): String { val sink = Buffer() source.buffer().readAll(sink) - return Json.decodeFromString(sink.readUtf8()) + return sink.readUtf8() } } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt index 719dca7..3b6ce55 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt @@ -1,11 +1,18 @@ package de.westnordost.osmfeatures +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.request.get +import io.ktor.client.statement.HttpResponse +import io.ktor.client.statement.bodyAsText +import kotlinx.coroutines.runBlocking import okio.Source import kotlin.test.Test import okio.IOException import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +import kotlin.test.fail class IDPresetsJsonParserTest { @Test @@ -72,6 +79,24 @@ class IDPresetsJsonParserTest { assertTrue(features.isEmpty()) } + @Test + fun parse_some_real_data() { + + val client = HttpClient(CIO) + runBlocking { + val httpResponse: HttpResponse = client.get("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") + if (httpResponse.status.value in 200..299) { + val body = httpResponse.bodyAsText() + val features = IDPresetsJsonParser().parse(body) + // should not crash etc + assertTrue(features.size > 1000) + } + else { + fail("Unable to retrieve response") + } + } + } + private fun parse(file: String): List { return try { IDPresetsJsonParser().parse(getSource(file)) diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt index e904b0d..a867ecc 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -5,6 +5,13 @@ import okio.IOException import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.Test +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.request.get +import io.ktor.client.statement.HttpResponse +import io.ktor.client.statement.bodyAsText +import kotlinx.coroutines.runBlocking +import kotlin.test.fail class IDPresetsTranslationJsonParserTest { @Test @@ -46,6 +53,39 @@ class IDPresetsTranslationJsonParserTest { assertEquals("bar", feature?.name) assertEquals(listOf("bar", "one", "two", "three"), feature?.names) assertEquals(listOf("a", "b"), feature?.terms) + } + + @Test + fun parse_some_real_data() { + val client = HttpClient(CIO) + runBlocking { + val httpResponse: HttpResponse = client.get("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") + if (httpResponse.status.value in 200..299) { + val body = httpResponse.bodyAsText() + val features = IDPresetsJsonParser().parse(body) + val featureMap = HashMap(features.associateBy { it.id }) + val translationHttpResponse: HttpResponse = client.get("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json") + if (translationHttpResponse.status.value in 200..299) { + val translatedFeatures = IDPresetsTranslationJsonParser().parse( + translationHttpResponse.bodyAsText(), + Locale("de", "DE"), + featureMap + ) + // should not crash etc + assertTrue(translatedFeatures.size > 1000) + } + else { + fail("Unable to retrieve translation Http response") + } + } + else { + fail("Unable to retrieve response") + } + } + + + + } private fun parse(presetsFile: String, translationsFile: String): List { diff --git a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt deleted file mode 100644 index b0681b8..0000000 --- a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package de.westnordost.osmfeatures - -import kotlin.test.Test -import okio.source -import kotlin.test.assertTrue -import java.net.URL - -class IDPresetsJsonParserTest { - @Test - fun parse_some_real_data() { - val url = URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - val features = IDPresetsJsonParser().parse(url.openStream().source()) - // should not crash etc - assertTrue(features.size > 1000) - } -} diff --git a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt deleted file mode 100644 index cd0beef..0000000 --- a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package de.westnordost.osmfeatures - -import java.net.URL -import okio.source -import kotlin.test.assertTrue -import kotlin.test.Test - -class IDPresetsTranslationJsonParserTest { - @Test - fun parse_some_real_data() { - val url = URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - val features = IDPresetsJsonParser().parse(url.openConnection().getInputStream().source()) - val featureMap = HashMap(features.associateBy { it.id }) - val rawTranslationsURL = URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json") - val translatedFeatures = IDPresetsTranslationJsonParser().parse( - rawTranslationsURL.openStream().source(), - Locale("de", "DE"), - featureMap - ) - - // should not crash etc - assertTrue(translatedFeatures.size > 1000) - } -} From 4ae4615124567af87f79421a04a23553c1912eac Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Mon, 4 Mar 2024 20:11:08 +0100 Subject: [PATCH 65/98] use ktor --- .../IDPresetsTranslationJsonParser.kt | 2 +- .../IDLocalizedFeatureCollectionTest.kt | 2 +- .../IDPresetsTranslationJsonParserTest.kt | 2 +- .../IDPresetsTranslationJsonParserTest.kt | 24 ------------------- 4 files changed, 3 insertions(+), 27 deletions(-) delete mode 100644 library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt index 1f18533..0bb1907 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt @@ -19,7 +19,7 @@ class IDPresetsTranslationJsonParser { return parse(content, locale, baseFeatures) } fun parse( - content: String, locale: Locale?, baseFeatures: Map + content: String, locale: String?, baseFeatures: Map ): List { val decodedObject = Json.decodeFromString(content) val languageKey: String = decodedObject.entries.iterator().next().key diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt index 5dccd1a..d56a29c 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt @@ -112,7 +112,7 @@ class IDLocalizedFeatureCollectionTest { // merging scripts val cryllic = listOf("de-Cyrl") assertEquals("бацкхаус", c.get("some/id", cryllic)?.name) - val cryllicAustria = listOf("de-Cyrl-AT")) + val cryllicAustria = listOf("de-Cyrl-AT") assertEquals("бацкхусл", c.get("some/id", cryllicAustria)?.name) } } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt index 7a4e28b..08b2d17 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -68,7 +68,7 @@ class IDPresetsTranslationJsonParserTest { if (translationHttpResponse.status.value in 200..299) { val translatedFeatures = IDPresetsTranslationJsonParser().parse( translationHttpResponse.bodyAsText(), - Locale("de", "DE"), + "de-DE", featureMap ) // should not crash etc diff --git a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt deleted file mode 100644 index 4288fc1..0000000 --- a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package de.westnordost.osmfeatures - -import java.net.URL -import okio.source -import kotlin.test.assertTrue -import kotlin.test.Test - -class IDPresetsTranslationJsonParserTest { - @Test - fun parse_some_real_data() { - val url = URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - val features = IDPresetsJsonParser().parse(url.openConnection().getInputStream().source()) - val featureMap = HashMap(features.associateBy { it.id }) - val rawTranslationsURL = URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json") - val translatedFeatures = IDPresetsTranslationJsonParser().parse( - rawTranslationsURL.openStream().source(), - "de-DE", - featureMap - ) - - // should not crash etc - assertTrue(translatedFeatures.size > 1000) - } -} From 293ba52c0d93f7eabbd2d43b942208c51fe8c346 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 5 Mar 2024 00:30:27 +0100 Subject: [PATCH 66/98] solve failing test --- .../de.westnordost.osmfeatures/BaseFeature.kt | 1 - .../de.westnordost.osmfeatures/Feature.kt | 2 +- .../IDPresetsJsonParser.kt | 6 +-- .../LocalizedFeature.kt | 41 ++++++------------- .../IDPresetsJsonParserTest.kt | 4 +- 5 files changed, 18 insertions(+), 36 deletions(-) diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt index 6e39c42..77d8d71 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt @@ -20,7 +20,6 @@ data class BaseFeature( override val canonicalNames: List = names.map { it.canonicalize() } override val canonicalTerms: List = terms.map { it.canonicalize() } - override val name: String get() = names[0] override val locale: String? get() = null override fun toString(): String = id } \ No newline at end of file diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt index 88d405c..e1945a9 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt @@ -7,7 +7,7 @@ interface Feature { val id: String val tags: Map val geometry: List - val name: String + val name: String get() = names[0] val icon: String? val imageURL: String? diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParser.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParser.kt index 5a84c8f..13ad491 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParser.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParser.kt @@ -44,13 +44,11 @@ class IDPresetsJsonParser { ) { GeometryType.valueOf(((it as JsonPrimitive).content).uppercase()) } - val name = p["name"]?.jsonPrimitive?.content + val name = p["name"]?.jsonPrimitive?.content ?: "" val icon = p["icon"]?.jsonPrimitive?.content val imageURL = p["imageURL"]?.jsonPrimitive?.content val names = parseList(p["aliases"]?.jsonArray) { it.jsonPrimitive.content }.toMutableList() - if(name != null) { - names.add(0, name) - } + names.add(0, name) val terms = parseList(p["terms"]?.jsonArray) { it.jsonPrimitive.content } val locationSet = p["locationSet"]?.jsonObject diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt index ec36895..cb3bb7b 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt @@ -12,33 +12,18 @@ data class LocalizedFeature( override val canonicalNames: List = names.map { it.canonicalize() } override val canonicalTerms: List = terms.map { it.canonicalize() } - override val id: String - get() = p.id - override val tags: Map - get() = p.tags - override val geometry: List - get() = p.geometry - override val name: String - get() = names[0] - override val icon: String? - get() = p.icon - override val imageURL: String? - get() = p.imageURL - override val includeCountryCodes: List - get() = p.includeCountryCodes - override val excludeCountryCodes: List - get() = p.excludeCountryCodes - override val isSearchable: Boolean - get() = p.isSearchable - override val matchScore: Float - get() = p.matchScore - override val addTags: Map - get() = p.addTags - override val removeTags: Map - get() = p.removeTags - override val isSuggestion: Boolean - get() = p.isSuggestion + override val id: String get() = p.id + override val tags: Map get() = p.tags + override val geometry: List get() = p.geometry + override val icon: String? get() = p.icon + override val imageURL: String? get() = p.imageURL + override val includeCountryCodes: List get() = p.includeCountryCodes + override val excludeCountryCodes: List get() = p.excludeCountryCodes + override val isSearchable: Boolean get() = p.isSearchable + override val matchScore: Float get() = p.matchScore + override val addTags: Map get() = p.addTags + override val removeTags: Map get() = p.removeTags + override val isSuggestion: Boolean get() = p.isSuggestion - override fun toString(): String = - id + override fun toString(): String = id } diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt index 3b6ce55..bbfec29 100644 --- a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt +++ b/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt @@ -55,8 +55,8 @@ class IDPresetsJsonParserTest { assertTrue(feature.includeCountryCodes.isEmpty()) assertTrue(feature.excludeCountryCodes.isEmpty()) assertEquals("", feature.name) - assertEquals("", feature.icon) - assertEquals("", feature.imageURL) + assertEquals(null, feature.icon) + assertEquals(null, feature.imageURL) assertEquals(1, feature.names.size) assertTrue(feature.terms.isEmpty()) assertEquals(1.0f, feature.matchScore, 0.001f) From 23ba6518b011fc07ded7c7b2eb4a51fa9551a445 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Sat, 9 Mar 2024 00:51:24 +0100 Subject: [PATCH 67/98] synchronized map using `lazy` operator --- .../Locale.android.kt | 5 +++++ .../CollectionUtils.kt | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 library/src/androidMain/kotlin/de.westnordost.osmfeatures/Locale.android.kt diff --git a/library/src/androidMain/kotlin/de.westnordost.osmfeatures/Locale.android.kt b/library/src/androidMain/kotlin/de.westnordost.osmfeatures/Locale.android.kt new file mode 100644 index 0000000..f4517d8 --- /dev/null +++ b/library/src/androidMain/kotlin/de.westnordost.osmfeatures/Locale.android.kt @@ -0,0 +1,5 @@ +package de.westnordost.osmfeatures + +import java.util.Locale + +internal actual fun defaultLocale(): String = Locale.getDefault().toLanguageTag() \ No newline at end of file diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt index f1a894a..978097d 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt +++ b/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt @@ -3,14 +3,15 @@ package de.westnordost.osmfeatures /** For the given map, get the value of the entry at the given key and if there is no * entry yet, create it using the given create function thread-safely */ internal fun MutableMap.synchronizedGetOrCreate(key: K, createFn: (K) -> V): V { - synchronized(this) { - val value = get(key) - return if (value == null) { - val answer = createFn(key) - put(key, answer) - answer - } else { - value + val value by lazy { + val value = get(key) + if (value == null) { + val answer = createFn(key) + put(key, answer) + answer + } else { + value + } } - } + return value } From 52fad29be177327a865899244891a737060527c8 Mon Sep 17 00:00:00 2001 From: Ercalvez Date: Sat, 23 Mar 2024 14:46:53 +0100 Subject: [PATCH 68/98] de.westnordost.osmfeatures -> de/westnordost/osmfeatures --- gradle/libs.versions.toml | 2 ++ library/build.gradle.kts | 1 + .../osmfeatures}/Locale.android.kt | 0 .../westnordost/osmfeatures}/BaseFeature.kt | 0 .../osmfeatures}/CollectionUtils.kt | 22 ++++++++++--------- .../osmfeatures}/ContainedMapTree.kt | 0 .../westnordost/osmfeatures}/Feature.kt | 0 .../osmfeatures}/FeatureDictionary.kt | 7 ++++++ .../osmfeatures}/FeatureTagsIndex.kt | 0 .../osmfeatures}/FeatureTermIndex.kt | 0 .../osmfeatures}/FileAccessAdapter.kt | 0 .../osmfeatures}/FileSystemAccess.kt | 0 .../westnordost/osmfeatures}/GeometryType.kt | 0 .../IDBrandPresetsFeatureCollection.kt | 0 .../IDLocalizedFeatureCollection.kt | 0 .../osmfeatures}/IDPresetsJsonParser.kt | 0 .../IDPresetsTranslationJsonParser.kt | 0 .../westnordost/osmfeatures}/JsonUtils.kt | 0 .../westnordost/osmfeatures}/Locale.kt | 0 .../osmfeatures}/LocalizedFeature.kt | 0 .../LocalizedFeatureCollection.kt | 0 .../PerCountryFeatureCollection.kt | 0 .../osmfeatures}/StartsWithStringTree.kt | 0 .../westnordost/osmfeatures}/StringUtils.kt | 0 .../osmfeatures}/ContainedMapTreeTest.kt | 0 .../osmfeatures}/FeatureDictionaryTest.kt | 0 .../osmfeatures}/FeatureTagsIndexTest.kt | 0 .../osmfeatures}/FeatureTermIndexTest.kt | 0 .../IDBrandPresetsFeatureCollectionTest.kt | 0 .../IDLocalizedFeatureCollectionTest.kt | 0 .../osmfeatures}/IDPresetsJsonParserTest.kt | 0 .../IDPresetsTranslationJsonParserTest.kt | 0 .../westnordost/osmfeatures}/JsonUtilsTest.kt | 0 .../osmfeatures}/StartsWithStringTreeTest.kt | 0 .../SynchronizedFeatureDictionaryTest.kt | 4 ++++ .../TestLocalizedFeatureCollection.kt | 0 .../TestPerCountryFeatureCollection.kt | 0 .../osmfeatures/FileSystemAccess.kt | 0 .../westnordost/osmfeatures}/Locale.kt | 0 .../osmfeatures}/FileSystemAccess.kt | 0 .../westnordost/osmfeatures}/Locale.kt | 0 41 files changed, 26 insertions(+), 10 deletions(-) rename library/src/androidMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/Locale.android.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/BaseFeature.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/CollectionUtils.kt (52%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/ContainedMapTree.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/Feature.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/FeatureDictionary.kt (99%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/FeatureTagsIndex.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/FeatureTermIndex.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/FileAccessAdapter.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/FileSystemAccess.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/GeometryType.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/IDBrandPresetsFeatureCollection.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/IDLocalizedFeatureCollection.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/IDPresetsJsonParser.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/IDPresetsTranslationJsonParser.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/JsonUtils.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/Locale.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/LocalizedFeature.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/LocalizedFeatureCollection.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/PerCountryFeatureCollection.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/StartsWithStringTree.kt (100%) rename library/src/commonMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/StringUtils.kt (100%) rename library/src/commonTest/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/ContainedMapTreeTest.kt (100%) rename library/src/commonTest/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/FeatureDictionaryTest.kt (100%) rename library/src/commonTest/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/FeatureTagsIndexTest.kt (100%) rename library/src/commonTest/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/FeatureTermIndexTest.kt (100%) rename library/src/commonTest/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/IDBrandPresetsFeatureCollectionTest.kt (100%) rename library/src/commonTest/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/IDLocalizedFeatureCollectionTest.kt (100%) rename library/src/commonTest/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/IDPresetsJsonParserTest.kt (100%) rename library/src/commonTest/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/IDPresetsTranslationJsonParserTest.kt (100%) rename library/src/commonTest/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/JsonUtilsTest.kt (100%) rename library/src/commonTest/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/StartsWithStringTreeTest.kt (100%) create mode 100644 library/src/commonTest/kotlin/de/westnordost/osmfeatures/SynchronizedFeatureDictionaryTest.kt rename library/src/commonTest/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/TestLocalizedFeatureCollection.kt (100%) rename library/src/commonTest/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/TestPerCountryFeatureCollection.kt (100%) rename library/src/iosMain/kotlin/{ => de/westnordost}/osmfeatures/FileSystemAccess.kt (100%) rename library/src/iosMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/Locale.kt (100%) rename library/src/jvmMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/FileSystemAccess.kt (100%) rename library/src/jvmMain/kotlin/{de.westnordost.osmfeatures => de/westnordost/osmfeatures}/Locale.kt (100%) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 58cbafc..1c2cd23 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,12 +2,14 @@ agp = "8.2.1" kotlin = "1.9.22" kotlinx-serialization-json = "1.6.0" +kotlinx-coroutines = "1.8.0" normalize = "1.0.5" okio = "3.6.0" ktor = "2.3.8" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } normalize = { module = "com.doist.x:normalize", version.ref = "normalize" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 032f305..d80aba3 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -22,6 +22,7 @@ kotlin { implementation(libs.okio) implementation(libs.kotlinx.serialization.json) implementation(libs.normalize) + implementation(libs.kotlinx.coroutines) } commonTest.dependencies { implementation(libs.kotlin.test) diff --git a/library/src/androidMain/kotlin/de.westnordost.osmfeatures/Locale.android.kt b/library/src/androidMain/kotlin/de/westnordost/osmfeatures/Locale.android.kt similarity index 100% rename from library/src/androidMain/kotlin/de.westnordost.osmfeatures/Locale.android.kt rename to library/src/androidMain/kotlin/de/westnordost/osmfeatures/Locale.android.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/BaseFeature.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/BaseFeature.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/BaseFeature.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt similarity index 52% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt index 978097d..2d6a8c1 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/CollectionUtils.kt +++ b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt @@ -1,17 +1,19 @@ package de.westnordost.osmfeatures +import kotlinx.coroutines.runBlocking + /** For the given map, get the value of the entry at the given key and if there is no * entry yet, create it using the given create function thread-safely */ internal fun MutableMap.synchronizedGetOrCreate(key: K, createFn: (K) -> V): V { - val value by lazy { - val value = get(key) - if (value == null) { - val answer = createFn(key) - put(key, answer) - answer - } else { - value - } + val value by lazy { + val value = get(key) + if (value == null) { + val answer = createFn(key) + put(key, answer) + answer + } else { + value } - return value + } + return value } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/ContainedMapTree.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/ContainedMapTree.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/ContainedMapTree.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/Feature.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/Feature.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/Feature.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt similarity index 99% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt index 9a5742c..6bfdcf4 100644 --- a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureDictionary.kt +++ b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt @@ -1,5 +1,9 @@ package de.westnordost.osmfeatures +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout + class FeatureDictionary internal constructor( private val featureCollection: LocalizedFeatureCollection, private val brandFeatureCollection: PerCountryFeatureCollection? @@ -223,6 +227,9 @@ class FeatureDictionary internal constructor( private fun createTagValuesIndex(locales: List): FeatureTermIndex { return FeatureTermIndex(featureCollection.getAll(locales)) { feature -> + runBlocking { + delay(2000) + } if (!feature.isSearchable) return@FeatureTermIndex emptyList() return@FeatureTermIndex feature.tags.values.filter { it != "*" } } diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTagsIndex.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTagsIndex.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTagsIndex.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTermIndex.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/FeatureTermIndex.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTermIndex.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileAccessAdapter.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileAccessAdapter.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/GeometryType.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/GeometryType.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/GeometryType.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/GeometryType.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollection.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollection.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParser.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParser.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParser.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/JsonUtils.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/JsonUtils.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/Locale.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/Locale.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/Locale.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeature.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeature.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeature.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/LocalizedFeatureCollection.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/PerCountryFeatureCollection.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/PerCountryFeatureCollection.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/StartsWithStringTree.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/StartsWithStringTree.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/StartsWithStringTree.kt diff --git a/library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/StringUtils.kt similarity index 100% rename from library/src/commonMain/kotlin/de.westnordost.osmfeatures/StringUtils.kt rename to library/src/commonMain/kotlin/de/westnordost/osmfeatures/StringUtils.kt diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/ContainedMapTreeTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de.westnordost.osmfeatures/ContainedMapTreeTest.kt rename to library/src/commonTest/kotlin/de/westnordost/osmfeatures/ContainedMapTreeTest.kt diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt rename to library/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTagsIndexTest.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureTagsIndexTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTagsIndexTest.kt rename to library/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureTagsIndexTest.kt diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureTermIndexTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de.westnordost.osmfeatures/FeatureTermIndexTest.kt rename to library/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureTermIndexTest.kt diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDBrandPresetsFeatureCollectionTest.kt rename to library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDLocalizedFeatureCollectionTest.kt rename to library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsJsonParserTest.kt rename to library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de.westnordost.osmfeatures/IDPresetsTranslationJsonParserTest.kt rename to library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/JsonUtilsTest.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/JsonUtilsTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de.westnordost.osmfeatures/JsonUtilsTest.kt rename to library/src/commonTest/kotlin/de/westnordost/osmfeatures/JsonUtilsTest.kt diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/StartsWithStringTreeTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de.westnordost.osmfeatures/StartsWithStringTreeTest.kt rename to library/src/commonTest/kotlin/de/westnordost/osmfeatures/StartsWithStringTreeTest.kt diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/SynchronizedFeatureDictionaryTest.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/SynchronizedFeatureDictionaryTest.kt new file mode 100644 index 0000000..40a7540 --- /dev/null +++ b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/SynchronizedFeatureDictionaryTest.kt @@ -0,0 +1,4 @@ +package de.westnordost.osmfeatures + +class SynchronizedFeatureDictionaryTest { +} \ No newline at end of file diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt similarity index 100% rename from library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestLocalizedFeatureCollection.kt rename to library/src/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt diff --git a/library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.kt similarity index 100% rename from library/src/commonTest/kotlin/de.westnordost.osmfeatures/TestPerCountryFeatureCollection.kt rename to library/src/commonTest/kotlin/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.kt diff --git a/library/src/iosMain/kotlin/osmfeatures/FileSystemAccess.kt b/library/src/iosMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt similarity index 100% rename from library/src/iosMain/kotlin/osmfeatures/FileSystemAccess.kt rename to library/src/iosMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt diff --git a/library/src/iosMain/kotlin/de.westnordost.osmfeatures/Locale.kt b/library/src/iosMain/kotlin/de/westnordost/osmfeatures/Locale.kt similarity index 100% rename from library/src/iosMain/kotlin/de.westnordost.osmfeatures/Locale.kt rename to library/src/iosMain/kotlin/de/westnordost/osmfeatures/Locale.kt diff --git a/library/src/jvmMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt b/library/src/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt similarity index 100% rename from library/src/jvmMain/kotlin/de.westnordost.osmfeatures/FileSystemAccess.kt rename to library/src/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt diff --git a/library/src/jvmMain/kotlin/de.westnordost.osmfeatures/Locale.kt b/library/src/jvmMain/kotlin/de/westnordost/osmfeatures/Locale.kt similarity index 100% rename from library/src/jvmMain/kotlin/de.westnordost.osmfeatures/Locale.kt rename to library/src/jvmMain/kotlin/de/westnordost/osmfeatures/Locale.kt From fc67633ad8a415f35cf8138c72054a013f0445bc Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 10 May 2024 16:21:59 +0200 Subject: [PATCH 69/98] move android-specific stuff --- library-android/.gitignore | 1 - library-android/build.gradle | 113 ------------------ .../osmfeatures/AndroidFeatureDictionary.kt | 27 ----- .../osmfeatures/AssetManagerAccess.kt | 20 ---- .../osmfeatures/AssetManagerAccess.kt | 17 +-- .../osmfeatures/FeatureDictionary.kt | 21 ++++ 6 files changed, 30 insertions(+), 169 deletions(-) delete mode 100644 library-android/.gitignore delete mode 100644 library-android/build.gradle delete mode 100644 library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.kt delete mode 100644 library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.kt create mode 100644 library/src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt diff --git a/library-android/.gitignore b/library-android/.gitignore deleted file mode 100644 index 3543521..0000000 --- a/library-android/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/library-android/build.gradle b/library-android/build.gradle deleted file mode 100644 index d16916a..0000000 --- a/library-android/build.gradle +++ /dev/null @@ -1,113 +0,0 @@ -plugins { - id "com.android.library" - id "maven-publish" - id 'signing' -} - -version = "5.2" -group "de.westnordost" - -repositories { - mavenLocal() - mavenCentral() - google() -} - -dependencies { - // api (project(':library')) { - api ('de.westnordost:osmfeatures:5.2') { - // it's already included in Android - exclude group: 'org.json', module: 'json' - } -} - -android { - compileSdkVersion 32 - defaultConfig { - minSdkVersion 9 - targetSdkVersion 32 - versionCode 1 - versionName "1" - } - - compileOptions { - sourceCompatibility 1.8 - targetCompatibility 1.8 - } -} - -tasks.register('sourcesJar', Jar) { - from android.sourceSets.main.java.srcDirs - classifier = 'sources' -} - -tasks.register('javadoc', Javadoc) { - source = android.sourceSets.main.java.srcDirs - failOnError = false - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) -} - -tasks.register('javadocJar', Jar) { - dependsOn javadoc - from javadoc.destinationDir - classifier = 'javadoc' -} - -publishing { - publications { - mavenJava(MavenPublication) { - artifact("$buildDir/outputs/aar/library-android-release.aar") - artifact sourcesJar - artifact javadocJar - artifactId "osmfeatures-android" - pom { - name = 'osmfeatures-android' - description = 'Android library to translate OSM tags to and from localized names.' - url = 'https://github.com/westnordost/osmfeatures' - scm { - connection = 'https://github.com/westnordost/osmfeatures.git' - developerConnection = 'https://github.com/westnordost/osmfeatures.git' - url = 'https://github.com/westnordost/osmfeatures' - } - licenses { - license { - name = 'The Apache License, Version 2.0' - url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - developers { - developer { - id = 'westnordost' - name = 'Tobias Zwick' - email = 'osm@westnordost.de' - } - } - withXml { - def dependenciesNode = asNode().appendNode('dependencies') - configurations.api.allDependencies.each { - if (it.name != 'unspecified') { - def dependencyNode = dependenciesNode.appendNode('dependency') - dependencyNode.appendNode('groupId', group) - dependencyNode.appendNode('artifactId', it.name) - dependencyNode.appendNode('version', it.version) - } - } - } - } - } - } - repositories { - maven { - name = "mavenCentral" - url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" - credentials { - username = ossrhUsername - password = ossrhPassword - } - } - } -} - -signing { - sign publishing.publications.mavenJava -} \ No newline at end of file diff --git a/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.kt b/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.kt deleted file mode 100644 index 9a58b2f..0000000 --- a/library-android/src/main/java/de/westnordost/osmfeatures/AndroidFeatureDictionary.kt +++ /dev/null @@ -1,27 +0,0 @@ -package de.westnordost.osmfeatures - -import android.content.res.AssetManager - -object AndroidFeatureDictionary { - /** Create a new FeatureDictionary which gets its data from the given directory in the app's asset folder. */ - fun create(assetManager: AssetManager, presetsBasePath: String): FeatureDictionary { - return create(assetManager, presetsBasePath, null) - } - - /** Create a new FeatureDictionary which gets its data from the given directory in the app's - * asset folder. Optionally, the path to the brand presets can be specified. */ - fun create( - assetManager: AssetManager, - presetsBasePath: String, - brandPresetsBasePath: String? - ): FeatureDictionary { - val featureCollection: LocalizedFeatureCollection = - IDLocalizedFeatureCollection(AssetManagerAccess(assetManager, presetsBasePath)) - - val brandsFeatureCollection: PerCountryFeatureCollection? = if (brandPresetsBasePath != null - ) IDBrandPresetsFeatureCollection(AssetManagerAccess(assetManager, brandPresetsBasePath)) - else null - - return FeatureDictionary(featureCollection, brandsFeatureCollection) - } -} \ No newline at end of file diff --git a/library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.kt b/library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.kt deleted file mode 100644 index aad7d89..0000000 --- a/library-android/src/main/java/de/westnordost/osmfeatures/AssetManagerAccess.kt +++ /dev/null @@ -1,20 +0,0 @@ -package de.westnordost.osmfeatures - -import android.content.res.AssetManager -import java.io.File - -internal class AssetManagerAccess(assetManager: AssetManager, private val basePath: String) : FileAccessAdapter { - private val assetManager: AssetManager = assetManager - - override fun exists(name: String): Boolean { - val files: Array = assetManager.list(basePath) ?: return false - for (file in files) { - if (file == name) return true - } - return false - } - - override fun open(name: String): okio.Source { - return assetManager.open(basePath + File.separator + name) - } -} \ No newline at end of file diff --git a/library/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt b/library/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt index 589d089..121c716 100644 --- a/library/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt +++ b/library/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt @@ -5,13 +5,14 @@ import okio.Source import okio.source import java.io.File -class AssetManagerAccess(private val assetManager: AssetManager, private val basePath: String): FileAccessAdapter { - override fun exists(name: String): Boolean { - val files: Array = assetManager.list(basePath) ?: return false - return files.contains(name) - } +internal class AssetManagerAccess( + private val assetManager: AssetManager, + private val basePath: String +): FileAccessAdapter { - override fun open(name: String): Source { - return assetManager.open(basePath + File.separator + name).source() - } + override fun exists(name: String): Boolean = + assetManager.list(basePath)?.contains(name) ?: false + + override fun open(name: String): Source = + assetManager.open(basePath + File.separator + name).source() } \ No newline at end of file diff --git a/library/src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt b/library/src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt new file mode 100644 index 0000000..e3d601c --- /dev/null +++ b/library/src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt @@ -0,0 +1,21 @@ +package de.westnordost.osmfeatures + +import android.content.res.AssetManager + +/** Create a new FeatureDictionary which gets its data from the given directory in the app's + * asset folder. Optionally, the path to the brand presets can be specified. */ +@JvmOverloads +fun create( + assetManager: AssetManager, + presetsBasePath: String, + brandPresetsBasePath: String? = null +): FeatureDictionary { + val featureCollection = + IDLocalizedFeatureCollection(AssetManagerAccess(assetManager, presetsBasePath)) + + val brandsFeatureCollection = brandPresetsBasePath?.let { + IDBrandPresetsFeatureCollection(AssetManagerAccess(assetManager, brandPresetsBasePath)) + } + + return FeatureDictionary(featureCollection, brandsFeatureCollection) +} From b806f01bb226819bde917a9aebd0c6722461e777 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 10 May 2024 17:35:04 +0200 Subject: [PATCH 70/98] gradle shenanigans --- build.gradle.kts | 83 --------- gradle.properties | 23 +-- gradle/libs.versions.toml | 22 --- gradle/wrapper/gradle-wrapper.properties | 2 +- library/build.gradle.kts | 163 +++++++++++------- library/settings.gradle.kts | 0 .../osmfeatures/FeatureDictionary.kt | 20 +-- .../osmfeatures/CollectionUtils.kt | 2 - .../osmfeatures/FeatureDictionary.kt | 7 - .../osmfeatures/FileSystemAccess.kt | 1 + .../osmfeatures/IDPresetsJsonParserTest.kt | 3 - .../IDPresetsTranslationJsonParserTest.kt | 43 ++--- settings.gradle.kts | 4 +- 13 files changed, 136 insertions(+), 237 deletions(-) delete mode 100644 build.gradle.kts delete mode 100644 gradle/libs.versions.toml delete mode 100644 library/settings.gradle.kts diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index f8886a7..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,83 +0,0 @@ -//import groovy.json.JsonSlurper -//import java.io.File -//import java.net.URL -//import java.util.Locale -// -//plugins { -// `java` -//} -// -//repositories { -// mavenCentral() -// google() -//} -// -//val downloadPresets by tasks.registering { -// doLast { -// val targetDir = "$projectDir/presets" -// val presetsUrl = URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") -// val contentsUrl = URL("https://api.github.com/repos/openstreetmap/id-tagging-schema/contents/dist/translations") -// -// File("$targetDir/presets.json").outputStream().use { it.write(presetsUrl.openStream().readBytes()) } -// -// val slurper = JsonSlurper() -// val contents = slurper.parse(contentsUrl, "UTF-8") as List> -// contents.forEach { -// if (it["type"] == "file") { -// val language = it["name"].toString().substringBeforeLast(".") -// val translationsUrl = URL(it["download_url"].toString()) -// val javaLanguage = bcp47LanguageTagToJavaLanguageTag(language) -// File("$targetDir/${javaLanguage}.json").outputStream().use { os -> -// os.write(translationsUrl.openStream().readBytes()) -// } -// } -// } -// } -//} -// -//fun bcp47LanguageTagToJavaLanguageTag(bcp47: String): String { -// val locale = Locale.forLanguageTag(bcp47) -// var result = locale.language -// if (locale.country.isNotEmpty()) result += "-" + locale.country -// return result -//} - -//publishing { -// repositories { -// maven { -// url = uri("https://github.com/westnordost/osmfeatures") -// } -// } -// publications { -// create("mavenJava") { -// groupId = "de.westnordost" -// artifactId = "osmfeatures" -// version = "5.2" -// from(components["java"]) -// -// pom { -// name.value("osmfeatures") -// description.value("Java library to translate OSM tags to and from localized names.") -// url.value("https://github.com/westnordost/osmfeatures") -// scm { -// connection.value("https://github.com/westnordost/osmfeatures.git") -// developerConnection.value("https://github.com/westnordost/osmfeatures.git") -// url.value("https://github.com/westnordost/osmfeatures") -// } -// licenses { -// license { -// name.value("The Apache License, Version 2.0") -// url.value("http://www.apache.org/licenses/LICENSE-2.0.txt") -// } -// } -// developers { -// developer { -// id.value("westnordost") -// name.value("Tobias Zwick") -// email.value("osm@westnordost.de") -// } -// } -// } -// } -// } -//} diff --git a/gradle.properties b/gradle.properties index b9e23db..0f76a4e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,22 +1,7 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m -Dfile.encoding=UTF-8 - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true - -systemProp.file.encoding=utf-8 +# Gradle +org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" +org.gradle.caching=true +org.gradle.configuration-cache=true signing.keyId=08A11DBC signing.password= diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml deleted file mode 100644 index 1c2cd23..0000000 --- a/gradle/libs.versions.toml +++ /dev/null @@ -1,22 +0,0 @@ -[versions] -agp = "8.2.1" -kotlin = "1.9.22" -kotlinx-serialization-json = "1.6.0" -kotlinx-coroutines = "1.8.0" -normalize = "1.0.5" -okio = "3.6.0" -ktor = "2.3.8" - -[libraries] -kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } -kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } -kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } -normalize = { module = "com.doist.x:normalize", version.ref = "normalize" } -okio = { module = "com.squareup.okio:okio", version.ref = "okio" } -ktor = { module = "io.ktor:ktor-client-core", version.ref = "ktor"} -ktor-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor"} - - -[plugins] -android-library = { id = "com.android.library", version.ref = "agp" } -kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 40f4f2e..35dec86 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Dec 22 21:31:41 CET 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library/build.gradle.kts b/library/build.gradle.kts index d80aba3..537a98f 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -1,38 +1,55 @@ plugins { - alias(libs.plugins.kotlin.multiplatform) - alias(libs.plugins.android.library) + kotlin("multiplatform") version "1.9.24" + id("com.android.library") version "8.2.0" + id("org.jetbrains.dokka") version "1.9.20" + + id("maven-publish") + id("signing") } kotlin { + group = "de.westnordost" + version = "6.0" + jvm() androidTarget { + publishLibraryVariants("release") compilations.all { kotlinOptions { jvmTarget = "1.8" } } } + linuxX64() + linuxArm64() + mingwX64() + + macosX64() + macosArm64() + iosSimulatorArm64() + iosX64() iosArm64() - sourceSets { + applyDefaultHierarchyTemplate() - commonMain.dependencies { - //noinspection UseTomlInstead - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") - implementation(libs.okio) - implementation(libs.kotlinx.serialization.json) - implementation(libs.normalize) - implementation(libs.kotlinx.coroutines) + sourceSets { + commonMain { + dependencies { + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation("com.squareup.okio:okio:3.7.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + implementation("com.doist.x:normalize:1.0.5") + } } - commonTest.dependencies { - implementation(libs.kotlin.test) - implementation(libs.ktor) - implementation(libs.ktor.cio) + commonTest { + dependencies { + implementation(kotlin("test")) + } } - jvmTest.dependencies { - implementation(libs.kotlin.test) - implementation(libs.ktor) + jvmTest { + dependencies { + } } } } @@ -40,51 +57,73 @@ kotlin { android { namespace = "de.westnordost.osmfeatures" - compileSdk = 34 + compileSdk = 33 defaultConfig { - minSdk = 24 + minSdk = 9 + } +} + +val javadocJar = tasks.register("javadocJar") { + archiveClassifier.set("javadoc") + from(tasks.dokkaHtml) +} + +publishing { + publications { + withType { + artifactId = rootProject.name + if (name != "kotlinMultiplatform") "-$name" else "" + artifact(javadocJar) + + pom { + name.set("osmfeatures") + description.set("Java library to translate OSM tags to and from localized names.") + url.set("https://github.com/westnordost/osmfeatures") + + licenses { + license { + name.set("The Apache License, Version 2.0") + url.set("https://raw.githubusercontent.com/westnordost/osmfeatures/master/LICENSE") + } + } + issueManagement { + system.set("GitHub") + url.set("https://github.com/westnordost/osmfeatures/issues") + } + scm { + connection.set("https://github.com/westnordost/osmfeatures.git") + url.set("https://github.com/westnordost/osmfeatures") + } + developers { + developer { + id.set("westnordost") + name.set("Tobias Zwick") + email.set("osm@westnordost.de") + } + } + } + } + } + repositories { + maven { + name = "oss" + url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") + credentials { + val ossrhUsername: String by project + val ossrhPassword: String by project + username = ossrhUsername + password = ossrhPassword + } + } } } -//publishing { -// repositories { -// maven { -// url = uri("https://github.com/westnordost/osmfeatures") -// } -// } -// publications { -// create("mavenJava") { -// groupId = "de.westnordost" -// artifactId = "osmfeatures" -// version = "5.2" -// from(components["java"]) -// -// pom { -// name.value("osmfeatures") -// description.value("Java library to translate OSM tags to and from localized names.") -// url.value("https://github.com/westnordost/osmfeatures") -// scm { -// connection.value("https://github.com/westnordost/osmfeatures.git") -// developerConnection.value("https://github.com/westnordost/osmfeatures.git") -// url.value("https://github.com/westnordost/osmfeatures") -// } -// licenses { -// license { -// name.value("The Apache License, Version 2.0") -// url.value("http://www.apache.org/licenses/LICENSE-2.0.txt") -// } -// } -// developers { -// developer { -// id.value("westnordost") -// name.value("Tobias Zwick") -// email.value("osm@westnordost.de") -// } -// } -// } -// } -// } -//} -// -//signing { -// sign(publishing.publications["mavenJava"]) -//} \ No newline at end of file + +signing { + sign(publishing.publications) +} + + +// FIXME - workaround for https://github.com/gradle/gradle/issues/26091 +val signingTasks = tasks.withType() +tasks.withType().configureEach { + mustRunAfter(signingTasks) +} \ No newline at end of file diff --git a/library/settings.gradle.kts b/library/settings.gradle.kts deleted file mode 100644 index e69de29..0000000 diff --git a/library/src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt b/library/src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt index e3d601c..7b90e27 100644 --- a/library/src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt +++ b/library/src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt @@ -5,17 +5,15 @@ import android.content.res.AssetManager /** Create a new FeatureDictionary which gets its data from the given directory in the app's * asset folder. Optionally, the path to the brand presets can be specified. */ @JvmOverloads -fun create( +fun FeatureDictionary.Companion.create( assetManager: AssetManager, presetsBasePath: String, brandPresetsBasePath: String? = null -): FeatureDictionary { - val featureCollection = - IDLocalizedFeatureCollection(AssetManagerAccess(assetManager, presetsBasePath)) - - val brandsFeatureCollection = brandPresetsBasePath?.let { - IDBrandPresetsFeatureCollection(AssetManagerAccess(assetManager, brandPresetsBasePath)) - } - - return FeatureDictionary(featureCollection, brandsFeatureCollection) -} +) = FeatureDictionary( + featureCollection = + IDLocalizedFeatureCollection(AssetManagerAccess(assetManager, presetsBasePath)), + brandFeatureCollection = + brandPresetsBasePath?.let { + IDBrandPresetsFeatureCollection(AssetManagerAccess(assetManager, brandPresetsBasePath)) + } +) diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt index 2d6a8c1..6f0830f 100644 --- a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt +++ b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import kotlinx.coroutines.runBlocking - /** For the given map, get the value of the entry at the given key and if there is no * entry yet, create it using the given create function thread-safely */ internal fun MutableMap.synchronizedGetOrCreate(key: K, createFn: (K) -> V): V { diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt index 6bfdcf4..9a5742c 100644 --- a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt +++ b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt @@ -1,9 +1,5 @@ package de.westnordost.osmfeatures -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout - class FeatureDictionary internal constructor( private val featureCollection: LocalizedFeatureCollection, private val brandFeatureCollection: PerCountryFeatureCollection? @@ -227,9 +223,6 @@ class FeatureDictionary internal constructor( private fun createTagValuesIndex(locales: List): FeatureTermIndex { return FeatureTermIndex(featureCollection.getAll(locales)) { feature -> - runBlocking { - delay(2000) - } if (!feature.isSearchable) return@FeatureTermIndex emptyList() return@FeatureTermIndex feature.tags.values.filter { it != "*" } } diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt index 0231012..eaac29e 100644 --- a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt +++ b/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt @@ -1,4 +1,5 @@ package de.westnordost.osmfeatures + import okio.FileSystem import okio.Path.Companion.toPath import okio.Source diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt index bbfec29..edd1651 100644 --- a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt +++ b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt @@ -5,7 +5,6 @@ import io.ktor.client.engine.cio.CIO import io.ktor.client.request.get import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText -import kotlinx.coroutines.runBlocking import okio.Source import kotlin.test.Test import okio.IOException @@ -83,7 +82,6 @@ class IDPresetsJsonParserTest { fun parse_some_real_data() { val client = HttpClient(CIO) - runBlocking { val httpResponse: HttpResponse = client.get("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") if (httpResponse.status.value in 200..299) { val body = httpResponse.bodyAsText() @@ -94,7 +92,6 @@ class IDPresetsJsonParserTest { else { fail("Unable to retrieve response") } - } } private fun parse(file: String): List { diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt index 08b2d17..7e0cb21 100644 --- a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ b/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -10,7 +10,6 @@ import io.ktor.client.engine.cio.* import io.ktor.client.request.get import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText -import kotlinx.coroutines.runBlocking import kotlin.test.fail class IDPresetsTranslationJsonParserTest { @@ -58,34 +57,28 @@ class IDPresetsTranslationJsonParserTest { @Test fun parse_some_real_data() { val client = HttpClient(CIO) - runBlocking { - val httpResponse: HttpResponse = client.get("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - if (httpResponse.status.value in 200..299) { - val body = httpResponse.bodyAsText() - val features = IDPresetsJsonParser().parse(body) - val featureMap = HashMap(features.associateBy { it.id }) - val translationHttpResponse: HttpResponse = client.get("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json") - if (translationHttpResponse.status.value in 200..299) { - val translatedFeatures = IDPresetsTranslationJsonParser().parse( - translationHttpResponse.bodyAsText(), - "de-DE", - featureMap - ) - // should not crash etc - assertTrue(translatedFeatures.size > 1000) - } - else { - fail("Unable to retrieve translation Http response") - } + val httpResponse: HttpResponse = client.get("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") + if (httpResponse.status.value in 200..299) { + val body = httpResponse.bodyAsText() + val features = IDPresetsJsonParser().parse(body) + val featureMap = HashMap(features.associateBy { it.id }) + val translationHttpResponse: HttpResponse = client.get("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json") + if (translationHttpResponse.status.value in 200..299) { + val translatedFeatures = IDPresetsTranslationJsonParser().parse( + translationHttpResponse.bodyAsText(), + "de-DE", + featureMap + ) + // should not crash etc + assertTrue(translatedFeatures.size > 1000) } else { - fail("Unable to retrieve response") + fail("Unable to retrieve translation Http response") } } - - - - + else { + fail("Unable to retrieve response") + } } private fun parse(presetsFile: String, translationsFile: String): List { diff --git a/settings.gradle.kts b/settings.gradle.kts index 7cba528..ec53475 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,16 +3,16 @@ pluginManagement { google() gradlePluginPortal() mavenCentral() - } } dependencyResolutionManagement { repositories { google() + gradlePluginPortal() mavenCentral() } } -rootProject.name = "de.westnordost.osmfeatures" +rootProject.name = "osmfeatures" include(":library") From 6234dd318b197095671ae1bd7188cebaa74e8417 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 10 May 2024 17:48:01 +0200 Subject: [PATCH 71/98] move up --- library/build.gradle.kts => build.gradle.kts | 0 library/.gitignore | 1 - settings.gradle.kts | 1 - .../kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt | 0 .../kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt | 0 .../kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt | 0 .../kotlin/de/westnordost/osmfeatures/Locale.android.kt | 0 .../kotlin/de/westnordost/osmfeatures/BaseFeature.kt | 0 .../kotlin/de/westnordost/osmfeatures/CollectionUtils.kt | 0 .../kotlin/de/westnordost/osmfeatures/ContainedMapTree.kt | 3 ++- .../commonMain/kotlin/de/westnordost/osmfeatures/Feature.kt | 0 .../kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt | 0 .../kotlin/de/westnordost/osmfeatures/FeatureTagsIndex.kt | 0 .../kotlin/de/westnordost/osmfeatures/FeatureTermIndex.kt | 0 .../kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt | 0 .../kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt | 0 .../kotlin/de/westnordost/osmfeatures/GeometryType.kt | 0 .../westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt | 0 .../de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt | 0 .../kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt | 0 .../westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt | 0 .../commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt | 0 .../commonMain/kotlin/de/westnordost/osmfeatures/Locale.kt | 0 .../kotlin/de/westnordost/osmfeatures/LocalizedFeature.kt | 0 .../de/westnordost/osmfeatures/LocalizedFeatureCollection.kt | 0 .../de/westnordost/osmfeatures/PerCountryFeatureCollection.kt | 0 .../kotlin/de/westnordost/osmfeatures/StartsWithStringTree.kt | 3 ++- .../kotlin/de/westnordost/osmfeatures/StringUtils.kt | 0 .../kotlin/de/westnordost/osmfeatures/ContainedMapTreeTest.kt | 0 .../kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt | 0 .../kotlin/de/westnordost/osmfeatures/FeatureTagsIndexTest.kt | 0 .../kotlin/de/westnordost/osmfeatures/FeatureTermIndexTest.kt | 0 .../osmfeatures/IDBrandPresetsFeatureCollectionTest.kt | 0 .../osmfeatures/IDLocalizedFeatureCollectionTest.kt | 0 .../de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt | 0 .../osmfeatures/IDPresetsTranslationJsonParserTest.kt | 0 .../kotlin/de/westnordost/osmfeatures/JsonUtilsTest.kt | 0 .../de/westnordost/osmfeatures/StartsWithStringTreeTest.kt | 0 .../osmfeatures/SynchronizedFeatureDictionaryTest.kt | 0 .../westnordost/osmfeatures/TestLocalizedFeatureCollection.kt | 0 .../westnordost/osmfeatures/TestPerCountryFeatureCollection.kt | 0 .../src => src}/commonTest/resources/brand_presets_min.json | 0 .../src => src}/commonTest/resources/brand_presets_min2.json | 0 {library/src => src}/commonTest/resources/localizations.json | 0 .../src => src}/commonTest/resources/localizations_de-AT.json | 0 .../commonTest/resources/localizations_de-Cyrl-AT.json | 0 .../commonTest/resources/localizations_de-Cyrl.json | 0 .../src => src}/commonTest/resources/localizations_de.json | 0 .../src => src}/commonTest/resources/localizations_en.json | 0 .../src => src}/commonTest/resources/localizations_min.json | 0 {library/src => src}/commonTest/resources/one_preset_full.json | 0 {library/src => src}/commonTest/resources/one_preset_min.json | 0 .../resources/one_preset_unsupported_location_set.json | 0 .../src => src}/commonTest/resources/one_preset_wildcard.json | 0 .../commonTest/resources/one_preset_with_placeholder_name.json | 0 .../src => src}/commonTest/resources/some_presets_min.json | 0 .../kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt | 0 .../iosMain/kotlin/de/westnordost/osmfeatures/Locale.kt | 0 .../kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt | 0 .../jvmMain/kotlin/de/westnordost/osmfeatures/Locale.kt | 0 .../kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt | 0 .../de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt | 0 62 files changed, 4 insertions(+), 4 deletions(-) rename library/build.gradle.kts => build.gradle.kts (100%) delete mode 100644 library/.gitignore rename {library/src => src}/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt (100%) rename {library/src => src}/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt (100%) rename {library/src => src}/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt (100%) rename {library/src => src}/androidMain/kotlin/de/westnordost/osmfeatures/Locale.android.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/BaseFeature.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/ContainedMapTree.kt (98%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/Feature.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTagsIndex.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTermIndex.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/GeometryType.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/Locale.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeature.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt (100%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/StartsWithStringTree.kt (97%) rename {library/src => src}/commonMain/kotlin/de/westnordost/osmfeatures/StringUtils.kt (100%) rename {library/src => src}/commonTest/kotlin/de/westnordost/osmfeatures/ContainedMapTreeTest.kt (100%) rename {library/src => src}/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt (100%) rename {library/src => src}/commonTest/kotlin/de/westnordost/osmfeatures/FeatureTagsIndexTest.kt (100%) rename {library/src => src}/commonTest/kotlin/de/westnordost/osmfeatures/FeatureTermIndexTest.kt (100%) rename {library/src => src}/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt (100%) rename {library/src => src}/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt (100%) rename {library/src => src}/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt (100%) rename {library/src => src}/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt (100%) rename {library/src => src}/commonTest/kotlin/de/westnordost/osmfeatures/JsonUtilsTest.kt (100%) rename {library/src => src}/commonTest/kotlin/de/westnordost/osmfeatures/StartsWithStringTreeTest.kt (100%) rename {library/src => src}/commonTest/kotlin/de/westnordost/osmfeatures/SynchronizedFeatureDictionaryTest.kt (100%) rename {library/src => src}/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt (100%) rename {library/src => src}/commonTest/kotlin/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.kt (100%) rename {library/src => src}/commonTest/resources/brand_presets_min.json (100%) rename {library/src => src}/commonTest/resources/brand_presets_min2.json (100%) rename {library/src => src}/commonTest/resources/localizations.json (100%) rename {library/src => src}/commonTest/resources/localizations_de-AT.json (100%) rename {library/src => src}/commonTest/resources/localizations_de-Cyrl-AT.json (100%) rename {library/src => src}/commonTest/resources/localizations_de-Cyrl.json (100%) rename {library/src => src}/commonTest/resources/localizations_de.json (100%) rename {library/src => src}/commonTest/resources/localizations_en.json (100%) rename {library/src => src}/commonTest/resources/localizations_min.json (100%) rename {library/src => src}/commonTest/resources/one_preset_full.json (100%) rename {library/src => src}/commonTest/resources/one_preset_min.json (100%) rename {library/src => src}/commonTest/resources/one_preset_unsupported_location_set.json (100%) rename {library/src => src}/commonTest/resources/one_preset_wildcard.json (100%) rename {library/src => src}/commonTest/resources/one_preset_with_placeholder_name.json (100%) rename {library/src => src}/commonTest/resources/some_presets_min.json (100%) rename {library/src => src}/iosMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt (100%) rename {library/src => src}/iosMain/kotlin/de/westnordost/osmfeatures/Locale.kt (100%) rename {library/src => src}/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt (100%) rename {library/src => src}/jvmMain/kotlin/de/westnordost/osmfeatures/Locale.kt (100%) rename {library/src => src}/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt (100%) rename {library/src => src}/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt (100%) diff --git a/library/build.gradle.kts b/build.gradle.kts similarity index 100% rename from library/build.gradle.kts rename to build.gradle.kts diff --git a/library/.gitignore b/library/.gitignore deleted file mode 100644 index 3543521..0000000 --- a/library/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/settings.gradle.kts b/settings.gradle.kts index ec53475..f8a8536 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,4 +15,3 @@ dependencyResolutionManagement { } rootProject.name = "osmfeatures" -include(":library") diff --git a/library/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt b/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt similarity index 100% rename from library/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt rename to src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt diff --git a/library/src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt b/src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt similarity index 100% rename from library/src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt rename to src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt diff --git a/library/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt b/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt similarity index 100% rename from library/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt rename to src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt diff --git a/library/src/androidMain/kotlin/de/westnordost/osmfeatures/Locale.android.kt b/src/androidMain/kotlin/de/westnordost/osmfeatures/Locale.android.kt similarity index 100% rename from library/src/androidMain/kotlin/de/westnordost/osmfeatures/Locale.android.kt rename to src/androidMain/kotlin/de/westnordost/osmfeatures/Locale.android.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/BaseFeature.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/BaseFeature.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/BaseFeature.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/BaseFeature.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/ContainedMapTree.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/ContainedMapTree.kt similarity index 98% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/ContainedMapTree.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/ContainedMapTree.kt index 7f71436..d6b5433 100644 --- a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/ContainedMapTree.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/ContainedMapTree.kt @@ -48,7 +48,8 @@ internal class ContainedMapTree buildTree(maps, emptyList(), maxDepth.coerceAtLeast(0), minContainerSize) /** Get all maps whose entries are completely contained by the given map */ - fun getAll(map: Map): List> = root.getAll(map) + fun getAll(map: Map): List> = + root.getAll(map) private class Node( /** key -> (value -> Node) */ diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/Feature.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/Feature.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/Feature.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/Feature.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTagsIndex.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTagsIndex.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTagsIndex.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTagsIndex.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTermIndex.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTermIndex.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTermIndex.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTermIndex.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/GeometryType.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/GeometryType.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/GeometryType.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/GeometryType.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/Locale.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/Locale.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/Locale.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/Locale.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeature.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeature.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeature.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeature.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/PerCountryFeatureCollection.kt diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/StartsWithStringTree.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/StartsWithStringTree.kt similarity index 97% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/StartsWithStringTree.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/StartsWithStringTree.kt index 1a87c21..01f466c 100644 --- a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/StartsWithStringTree.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/StartsWithStringTree.kt @@ -33,7 +33,8 @@ constructor( buildTree(strings, 0, maxDepth.coerceAtLeast(0), minContainerSize.coerceAtLeast(1)) /** Get all strings which start with the given string */ - fun getAll(startsWith: String): List = root.getAll(startsWith, 0) + fun getAll(startsWith: String): List = + root.getAll(startsWith, 0) private class Node(val children: Map, val strings: Collection) { diff --git a/library/src/commonMain/kotlin/de/westnordost/osmfeatures/StringUtils.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/StringUtils.kt similarity index 100% rename from library/src/commonMain/kotlin/de/westnordost/osmfeatures/StringUtils.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/StringUtils.kt diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/ContainedMapTreeTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/ContainedMapTreeTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de/westnordost/osmfeatures/ContainedMapTreeTest.kt rename to src/commonTest/kotlin/de/westnordost/osmfeatures/ContainedMapTreeTest.kt diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt rename to src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureTagsIndexTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureTagsIndexTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureTagsIndexTest.kt rename to src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureTagsIndexTest.kt diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureTermIndexTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureTermIndexTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureTermIndexTest.kt rename to src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureTermIndexTest.kt diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt rename to src/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt rename to src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt rename to src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt rename to src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/JsonUtilsTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/JsonUtilsTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de/westnordost/osmfeatures/JsonUtilsTest.kt rename to src/commonTest/kotlin/de/westnordost/osmfeatures/JsonUtilsTest.kt diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/StartsWithStringTreeTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/StartsWithStringTreeTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de/westnordost/osmfeatures/StartsWithStringTreeTest.kt rename to src/commonTest/kotlin/de/westnordost/osmfeatures/StartsWithStringTreeTest.kt diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/SynchronizedFeatureDictionaryTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/SynchronizedFeatureDictionaryTest.kt similarity index 100% rename from library/src/commonTest/kotlin/de/westnordost/osmfeatures/SynchronizedFeatureDictionaryTest.kt rename to src/commonTest/kotlin/de/westnordost/osmfeatures/SynchronizedFeatureDictionaryTest.kt diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt similarity index 100% rename from library/src/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt rename to src/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt diff --git a/library/src/commonTest/kotlin/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.kt similarity index 100% rename from library/src/commonTest/kotlin/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.kt rename to src/commonTest/kotlin/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.kt diff --git a/library/src/commonTest/resources/brand_presets_min.json b/src/commonTest/resources/brand_presets_min.json similarity index 100% rename from library/src/commonTest/resources/brand_presets_min.json rename to src/commonTest/resources/brand_presets_min.json diff --git a/library/src/commonTest/resources/brand_presets_min2.json b/src/commonTest/resources/brand_presets_min2.json similarity index 100% rename from library/src/commonTest/resources/brand_presets_min2.json rename to src/commonTest/resources/brand_presets_min2.json diff --git a/library/src/commonTest/resources/localizations.json b/src/commonTest/resources/localizations.json similarity index 100% rename from library/src/commonTest/resources/localizations.json rename to src/commonTest/resources/localizations.json diff --git a/library/src/commonTest/resources/localizations_de-AT.json b/src/commonTest/resources/localizations_de-AT.json similarity index 100% rename from library/src/commonTest/resources/localizations_de-AT.json rename to src/commonTest/resources/localizations_de-AT.json diff --git a/library/src/commonTest/resources/localizations_de-Cyrl-AT.json b/src/commonTest/resources/localizations_de-Cyrl-AT.json similarity index 100% rename from library/src/commonTest/resources/localizations_de-Cyrl-AT.json rename to src/commonTest/resources/localizations_de-Cyrl-AT.json diff --git a/library/src/commonTest/resources/localizations_de-Cyrl.json b/src/commonTest/resources/localizations_de-Cyrl.json similarity index 100% rename from library/src/commonTest/resources/localizations_de-Cyrl.json rename to src/commonTest/resources/localizations_de-Cyrl.json diff --git a/library/src/commonTest/resources/localizations_de.json b/src/commonTest/resources/localizations_de.json similarity index 100% rename from library/src/commonTest/resources/localizations_de.json rename to src/commonTest/resources/localizations_de.json diff --git a/library/src/commonTest/resources/localizations_en.json b/src/commonTest/resources/localizations_en.json similarity index 100% rename from library/src/commonTest/resources/localizations_en.json rename to src/commonTest/resources/localizations_en.json diff --git a/library/src/commonTest/resources/localizations_min.json b/src/commonTest/resources/localizations_min.json similarity index 100% rename from library/src/commonTest/resources/localizations_min.json rename to src/commonTest/resources/localizations_min.json diff --git a/library/src/commonTest/resources/one_preset_full.json b/src/commonTest/resources/one_preset_full.json similarity index 100% rename from library/src/commonTest/resources/one_preset_full.json rename to src/commonTest/resources/one_preset_full.json diff --git a/library/src/commonTest/resources/one_preset_min.json b/src/commonTest/resources/one_preset_min.json similarity index 100% rename from library/src/commonTest/resources/one_preset_min.json rename to src/commonTest/resources/one_preset_min.json diff --git a/library/src/commonTest/resources/one_preset_unsupported_location_set.json b/src/commonTest/resources/one_preset_unsupported_location_set.json similarity index 100% rename from library/src/commonTest/resources/one_preset_unsupported_location_set.json rename to src/commonTest/resources/one_preset_unsupported_location_set.json diff --git a/library/src/commonTest/resources/one_preset_wildcard.json b/src/commonTest/resources/one_preset_wildcard.json similarity index 100% rename from library/src/commonTest/resources/one_preset_wildcard.json rename to src/commonTest/resources/one_preset_wildcard.json diff --git a/library/src/commonTest/resources/one_preset_with_placeholder_name.json b/src/commonTest/resources/one_preset_with_placeholder_name.json similarity index 100% rename from library/src/commonTest/resources/one_preset_with_placeholder_name.json rename to src/commonTest/resources/one_preset_with_placeholder_name.json diff --git a/library/src/commonTest/resources/some_presets_min.json b/src/commonTest/resources/some_presets_min.json similarity index 100% rename from library/src/commonTest/resources/some_presets_min.json rename to src/commonTest/resources/some_presets_min.json diff --git a/library/src/iosMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt b/src/iosMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt similarity index 100% rename from library/src/iosMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt rename to src/iosMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt diff --git a/library/src/iosMain/kotlin/de/westnordost/osmfeatures/Locale.kt b/src/iosMain/kotlin/de/westnordost/osmfeatures/Locale.kt similarity index 100% rename from library/src/iosMain/kotlin/de/westnordost/osmfeatures/Locale.kt rename to src/iosMain/kotlin/de/westnordost/osmfeatures/Locale.kt diff --git a/library/src/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt b/src/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt similarity index 100% rename from library/src/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt rename to src/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt diff --git a/library/src/jvmMain/kotlin/de/westnordost/osmfeatures/Locale.kt b/src/jvmMain/kotlin/de/westnordost/osmfeatures/Locale.kt similarity index 100% rename from library/src/jvmMain/kotlin/de/westnordost/osmfeatures/Locale.kt rename to src/jvmMain/kotlin/de/westnordost/osmfeatures/Locale.kt diff --git a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt similarity index 100% rename from library/src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt rename to src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt diff --git a/library/src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt b/src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt similarity index 100% rename from library/src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt rename to src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt From 56fbda081ddc61a34115a693d39e23694b86214f Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 10 May 2024 17:51:15 +0200 Subject: [PATCH 72/98] remove actually not supported platforms --- build.gradle.kts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 537a98f..0105ff3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,12 +20,6 @@ kotlin { } } } - linuxX64() - linuxArm64() - mingwX64() - - macosX64() - macosArm64() iosSimulatorArm64() iosX64() iosArm64() From 6fe7b1a4a1a71563a3d2bff91e9738ca4b17433b Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 10 May 2024 18:23:19 +0200 Subject: [PATCH 73/98] formatting --- .../osmfeatures/FileSystemAccess.android.kt | 6 ++ .../osmfeatures/FileSystemAccess.kt | 7 -- .../westnordost/osmfeatures/Locale.android.kt | 3 +- .../osmfeatures/FeatureTagsIndex.kt | 5 +- .../osmfeatures/FeatureTermIndex.kt | 5 +- .../osmfeatures/FileAccessAdapter.kt | 1 - .../osmfeatures/FileSystemAccess.kt | 15 ++-- .../IDBrandPresetsFeatureCollection.kt | 8 +- .../IDLocalizedFeatureCollection.kt | 2 +- .../osmfeatures/IDPresetsJsonParser.kt | 86 +++++++++---------- .../IDPresetsTranslationJsonParser.kt | 31 +++---- .../de/westnordost/osmfeatures/JsonUtils.kt | 1 + .../osmfeatures/StartsWithStringTree.kt | 4 +- .../osmfeatures/FeatureDictionaryTest.kt | 1 - .../osmfeatures/FileSystemAccess.kt | 5 +- 15 files changed, 84 insertions(+), 96 deletions(-) create mode 100644 src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.android.kt delete mode 100644 src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt diff --git a/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.android.kt b/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.android.kt new file mode 100644 index 0000000..bdfa875 --- /dev/null +++ b/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.android.kt @@ -0,0 +1,6 @@ +package de.westnordost.osmfeatures + +import okio.FileSystem + +actual fun fileSystem(): FileSystem = + FileSystem.SYSTEM \ No newline at end of file diff --git a/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt b/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt deleted file mode 100644 index 28bfbce..0000000 --- a/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt +++ /dev/null @@ -1,7 +0,0 @@ -package de.westnordost.osmfeatures - -import okio.FileSystem - -actual fun fileSystem(): FileSystem { - return FileSystem.SYSTEM -} \ No newline at end of file diff --git a/src/androidMain/kotlin/de/westnordost/osmfeatures/Locale.android.kt b/src/androidMain/kotlin/de/westnordost/osmfeatures/Locale.android.kt index f4517d8..28b0c44 100644 --- a/src/androidMain/kotlin/de/westnordost/osmfeatures/Locale.android.kt +++ b/src/androidMain/kotlin/de/westnordost/osmfeatures/Locale.android.kt @@ -2,4 +2,5 @@ package de.westnordost.osmfeatures import java.util.Locale -internal actual fun defaultLocale(): String = Locale.getDefault().toLanguageTag() \ No newline at end of file +internal actual fun defaultLocale(): String = + Locale.getDefault().toLanguageTag() \ No newline at end of file diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTagsIndex.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTagsIndex.kt index 7331f76..7cc34ed 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTagsIndex.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTagsIndex.kt @@ -6,7 +6,8 @@ package de.westnordost.osmfeatures * * Based on ContainedMapTree data structure, see that class. */ internal class FeatureTagsIndex(features: Collection) { - private val featureMap: MutableMap, MutableList> = HashMap(features.size) + // tags -> list of features + private val featureMap = HashMap, MutableList>(features.size) private val tree: ContainedMapTree init { @@ -20,7 +21,7 @@ internal class FeatureTagsIndex(features: Collection) { fun getAll(tags: Map): List { val result = ArrayList() for (map in tree.getAll(tags)) { - val fs: List? = featureMap[map] + val fs = featureMap[map] if (fs != null) result.addAll(fs) } return result diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTermIndex.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTermIndex.kt index c4576bb..75a5691 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTermIndex.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureTermIndex.kt @@ -5,7 +5,8 @@ package de.westnordost.osmfeatures * * Based on the StartsWithStringTree data structure, see that class. */ internal class FeatureTermIndex(features: Collection, getStrings: (Feature) -> List) { - private val featureMap: MutableMap> = HashMap(features.size) + // name/term/... -> features + private val featureMap = HashMap>(features.size) private val tree: StartsWithStringTree init { @@ -21,7 +22,7 @@ internal class FeatureTermIndex(features: Collection, getStrings: (Feat fun getAll(startsWith: String): Set { val result = HashSet() for (string in tree.getAll(startsWith)) { - val fs: List? = featureMap[string] + val fs = featureMap[string] if (fs != null) result.addAll(fs) } return result diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt index f5ff383..bf30892 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt @@ -3,7 +3,6 @@ package de.westnordost.osmfeatures import okio.Source interface FileAccessAdapter { - fun exists(name: String): Boolean fun open(name: String): Source diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt index eaac29e..a28e926 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt @@ -8,15 +8,16 @@ expect fun fileSystem(): FileSystem class FileSystemAccess(private val basePath: String) : FileAccessAdapter { private val fs: FileSystem = fileSystem() + init { - fs.metadataOrNull(basePath.toPath())?.let { require(it.isDirectory) { "$basePath is not a directory" } } + fs.metadataOrNull(basePath.toPath())?.let { + require(it.isDirectory) { "$basePath is not a directory" } + } } - override fun exists(name: String): Boolean { + override fun exists(name: String): Boolean = + fs.exists(("$basePath/$name").toPath()) - return fs.exists(("$basePath/$name").toPath()) - } - override fun open(name: String): Source { - return fs.source(("$basePath/$name".toPath())) - } + override fun open(name: String): Source = + fs.source(("$basePath/$name".toPath())) } diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt index fe5d1e2..859c854 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt @@ -12,7 +12,7 @@ internal class IDBrandPresetsFeatureCollection( private val fileAccess: FileAccessAdapter ) : PerCountryFeatureCollection { // countryCode -> featureId -> Feature - private val featuresByIdByCountryCode: MutableMap> = LinkedHashMap(320) + private val featuresByIdByCountryCode = LinkedHashMap>(320) init { getOrLoadPerCountryFeatures(null) @@ -48,8 +48,6 @@ internal class IDBrandPresetsFeatureCollection( } } - companion object { - private fun getPresetsFileName(countryCode: String?): String = - if (countryCode == null) "presets.json" else "presets-$countryCode.json" - } + private fun getPresetsFileName(countryCode: String?): String = + if (countryCode == null) "presets.json" else "presets-$countryCode.json" } \ No newline at end of file diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt index c9ee050..82148b7 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt @@ -54,7 +54,7 @@ internal class IDLocalizedFeatureCollection( } else { result.putAll(featuresById) } -} + } return result } diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt index 13ad491..5c1e8c0 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt @@ -10,14 +10,7 @@ import okio.buffer /** Parses this file * [...](https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json) * into map of id -> Feature. */ -class IDPresetsJsonParser { - private var isSuggestion = false - - constructor() - - constructor(isSuggestion: Boolean) { - this.isSuggestion = isSuggestion - } +class IDPresetsJsonParser(private var isSuggestion: Boolean = false) { fun parse(source: Source): List { val sink = Buffer() source.buffer().readAll(sink) @@ -25,6 +18,7 @@ class IDPresetsJsonParser { val decodedObject = Json.decodeFromString(sink.readUtf8()) return decodedObject.mapNotNull { (key, value) -> parseFeature(key, value.jsonObject)} } + fun parse(content: String): List { val decodedObject = Json.decodeFromString(content) return decodedObject.mapNotNull { (key, value) -> parseFeature(key, value.jsonObject)} @@ -39,9 +33,8 @@ class IDPresetsJsonParser { // also dropping features with empty tags (generic point, line, relation) if (tags.isEmpty()) return null - val geometry = parseList( - p["geometry"]?.jsonArray - ) { GeometryType.valueOf(((it as JsonPrimitive).content).uppercase()) + val geometry = parseList(p["geometry"]?.jsonArray) { + GeometryType.valueOf(((it as JsonPrimitive).content).uppercase()) } val name = p["name"]?.jsonPrimitive?.content ?: "" @@ -64,48 +57,49 @@ class IDPresetsJsonParser { excludeCountryCodes = ArrayList(0) } - val searchable = p["searchable"]?.jsonPrimitive?.booleanOrNull?: true - val matchScore = p["matchScore"]?.jsonPrimitive?.floatOrNull?: 1.0f - val addTags = p["addTags"]?.let { parseStringMap(it.jsonObject)}?: tags - val removeTags = p["removeTags"]?.let { parseStringMap(it.jsonObject)}?: addTags + val searchable = p["searchable"]?.jsonPrimitive?.booleanOrNull ?: true + val matchScore = p["matchScore"]?.jsonPrimitive?.floatOrNull ?: 1.0f + val addTags = p["addTags"]?.let { parseStringMap(it.jsonObject)} ?: tags + val removeTags = p["removeTags"]?.let { parseStringMap(it.jsonObject)} ?: addTags return BaseFeature( - id, - tags, - geometry, - icon, imageURL, - names, - terms, - includeCountryCodes, - excludeCountryCodes, - searchable, matchScore, isSuggestion, - addTags, - removeTags + id = id, + tags = tags, + geometry = geometry, + icon = icon, + imageURL = imageURL, + names = names, + terms = terms, + includeCountryCodes = includeCountryCodes, + excludeCountryCodes = excludeCountryCodes, + isSearchable = searchable, + matchScore = matchScore, + isSuggestion = isSuggestion, + addTags = addTags, + removeTags = removeTags ) } - companion object { - private fun parseCountryCodes(jsonList: JsonArray?): List? { - if(jsonList?.any { it is JsonArray } == true) { - return null - } - val list = parseList(jsonList) { it.jsonPrimitive.content } - val result: MutableList = ArrayList(list.size) - for (item in list) { - // for example a lat,lon pair to denote a location with radius. Not supported. - val cc = item.uppercase() - // don't need this, 001 stands for "whole world" - if (cc == "001") continue - // ISO-3166-2 codes are supported but not m49 code such as "150" or geojsons like "city_national_bank_fl.geojson" - if (!cc.matches("[A-Z]{2}(-[A-Z0-9]{1,3})?".toRegex())) return null - result.add(cc) - } - return result + private fun parseCountryCodes(jsonList: JsonArray?): List? { + if(jsonList?.any { it is JsonArray } == true) { + return null } - - private fun anyKeyOrValueContainsWildcard(map: Map): Boolean { - return map.any { (key, value) -> key.contains("*") || value.contains("*")} + val list = parseList(jsonList) { it.jsonPrimitive.content } + val result = ArrayList(list.size) + for (item in list) { + // for example a lat,lon pair to denote a location with radius. Not supported. + val cc = item.uppercase() + // don't need this, 001 stands for "whole world" + if (cc == "001") continue + // ISO-3166-2 codes are supported but not m49 code such as "150" or geojsons like "city_national_bank_fl.geojson" + if (!cc.matches("[A-Z]{2}(-[A-Z0-9]{1,3})?".toRegex())) return null + result.add(cc) } + return result + } + + private fun anyKeyOrValueContainsWildcard(map: Map): Boolean { + return map.any { (key, value) -> key.contains("*") || value.contains("*")} } } diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt index 0bb1907..6667f90 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt @@ -22,14 +22,14 @@ class IDPresetsTranslationJsonParser { content: String, locale: String?, baseFeatures: Map ): List { val decodedObject = Json.decodeFromString(content) - val languageKey: String = decodedObject.entries.iterator().next().key + val languageKey = decodedObject.entries.iterator().next().key val languageObject = decodedObject[languageKey] ?: return emptyList() val presetsContainerObject = languageObject.jsonObject["presets"] ?: return emptyList() val presetsObject = presetsContainerObject.jsonObject["presets"]?.jsonObject ?: return emptyList() - val localizedFeatures: MutableMap = HashMap(presetsObject.size) + val localizedFeatures = HashMap(presetsObject.size) presetsObject.entries.forEach { (key, value) -> val f = parseFeature(baseFeatures[key], locale, value.jsonObject) if (f != null) localizedFeatures[key] = f @@ -71,23 +71,20 @@ class IDPresetsTranslationJsonParser { terms.removeAll(names) return LocalizedFeature( - feature, - locale, - names, - terms + p = feature, + locale = locale, + names = names, + terms = terms ) } +} +private fun parseCommaSeparatedList(str: String?): Array { + if (str.isNullOrEmpty()) return emptyArray() + return str.split("\\s*,+\\s*".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() +} - companion object { - fun parseCommaSeparatedList(str: String?): Array { - if (str.isNullOrEmpty()) return emptyArray() - return str.split("\\s*,+\\s*".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - } - - fun parseNewlineSeparatedList(str: String?): Array { - if (str.isNullOrEmpty()) return emptyArray() - return str.split("\\s*[\\r\\n]+\\s*".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - } - } +private fun parseNewlineSeparatedList(str: String?): Array { + if (str.isNullOrEmpty()) return emptyArray() + return str.split("\\s*[\\r\\n]+\\s*".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() } \ No newline at end of file diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt index 90e2894..bd6a10f 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt @@ -4,6 +4,7 @@ import kotlinx.serialization.json.* import okio.Buffer import okio.Source import okio.buffer + internal object JsonUtils { fun parseList(array: JsonArray?, t: (JsonElement) -> T): List { diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/StartsWithStringTree.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/StartsWithStringTree.kt index 01f466c..76fd57d 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/StartsWithStringTree.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/StartsWithStringTree.kt @@ -81,9 +81,7 @@ constructor( /** returns the given strings grouped by their nth character. Strings whose length is shorter * or equal to nth go into the "null" group. */ - private fun Collection.groupedByNthCharacter( - nth: Int - ): Map> { + private fun Collection.groupedByNthCharacter(nth: Int): Map> { val result = HashMap>() for (string in this) { val c = if (string.length > nth) string[nth] else null diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt index d5e7bef..68a9cf0 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt @@ -7,7 +7,6 @@ import kotlin.test.assertNull class FeatureDictionaryTest { - private val bakery = feature( // unlocalized shop=bakery id = "shop/bakery", tags = mapOf("shop" to "bakery"), diff --git a/src/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt b/src/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt index 28bfbce..bdfa875 100644 --- a/src/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt +++ b/src/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt @@ -2,6 +2,5 @@ package de.westnordost.osmfeatures import okio.FileSystem -actual fun fileSystem(): FileSystem { - return FileSystem.SYSTEM -} \ No newline at end of file +actual fun fileSystem(): FileSystem = + FileSystem.SYSTEM \ No newline at end of file From a2dec4f7e66d0c8037696008a33e049d5aee6603 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 10 May 2024 19:31:33 +0200 Subject: [PATCH 74/98] replace okio with kotlinx-io --- build.gradle.kts | 5 ++- .../osmfeatures/AssetManagerAccess.kt | 9 +++--- .../osmfeatures/FileSystemAccess.android.kt | 6 ---- .../osmfeatures/FeatureDictionary.kt | 24 ++++++++------ .../osmfeatures/FileSystemAccess.kt | 25 ++++++++------- .../IDBrandPresetsFeatureCollection.kt | 5 ++- .../IDLocalizedFeatureCollection.kt | 6 ++-- .../osmfeatures/IDPresetsJsonParser.kt | 21 +++++------- .../IDPresetsTranslationJsonParser.kt | 23 +++++++------ .../de/westnordost/osmfeatures/JsonUtils.kt | 20 ++++-------- ...essAdapter.kt => ResourceAccessAdapter.kt} | 4 +-- .../IDBrandPresetsFeatureCollectionTest.kt | 15 ++++----- .../IDLocalizedFeatureCollectionTest.kt | 31 +++++++++--------- .../osmfeatures/IDPresetsJsonParserTest.kt | 23 ++++++------- .../IDPresetsTranslationJsonParserTest.kt | 32 ++++++++----------- .../osmfeatures/FileSystemAccess.kt | 7 ---- .../osmfeatures/FileSystemAccess.kt | 6 ---- .../LivePresetDataAccessAdapter.kt | 2 +- 18 files changed, 116 insertions(+), 148 deletions(-) delete mode 100644 src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.android.kt rename src/commonMain/kotlin/de/westnordost/osmfeatures/{FileAccessAdapter.kt => ResourceAccessAdapter.kt} (65%) delete mode 100644 src/iosMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt delete mode 100644 src/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt diff --git a/build.gradle.kts b/build.gradle.kts index 0105ff3..2177717 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,8 +29,7 @@ kotlin { sourceSets { commonMain { dependencies { - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") - implementation("com.squareup.okio:okio:3.7.0") + implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.3.4") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") implementation("com.doist.x:normalize:1.0.5") } @@ -53,7 +52,7 @@ android { namespace = "de.westnordost.osmfeatures" compileSdk = 33 defaultConfig { - minSdk = 9 + minSdk = 21 } } diff --git a/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt b/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt index 121c716..ca34aa6 100644 --- a/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt +++ b/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt @@ -1,18 +1,19 @@ package de.westnordost.osmfeatures import android.content.res.AssetManager -import okio.Source -import okio.source +import kotlinx.io.Source +import kotlinx.io.asSource +import kotlinx.io.buffered import java.io.File internal class AssetManagerAccess( private val assetManager: AssetManager, private val basePath: String -): FileAccessAdapter { +): ResourceAccessAdapter { override fun exists(name: String): Boolean = assetManager.list(basePath)?.contains(name) ?: false override fun open(name: String): Source = - assetManager.open(basePath + File.separator + name).source() + assetManager.open(basePath + File.separator + name).asSource().buffered() } \ No newline at end of file diff --git a/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.android.kt b/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.android.kt deleted file mode 100644 index bdfa875..0000000 --- a/src/androidMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.android.kt +++ /dev/null @@ -1,6 +0,0 @@ -package de.westnordost.osmfeatures - -import okio.FileSystem - -actual fun fileSystem(): FileSystem = - FileSystem.SYSTEM \ No newline at end of file diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt index 9a5742c..989fafa 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt @@ -1,5 +1,7 @@ package de.westnordost.osmfeatures +import kotlinx.io.files.FileSystem + class FeatureDictionary internal constructor( private val featureCollection: LocalizedFeatureCollection, private val brandFeatureCollection: PerCountryFeatureCollection? @@ -408,16 +410,20 @@ class FeatureDictionary internal constructor( companion object { - /** Create a new FeatureDictionary which gets its data from the given directory. Optionally, - * a path to brand presets can be specified. */ - /** Create a new FeatureDictionary which gets its data from the given directory. */ - fun create(presetsBasePath: String, brandPresetsBasePath: String? = null) = - FeatureDictionary( - featureCollection = IDLocalizedFeatureCollection(FileSystemAccess(presetsBasePath)), - brandFeatureCollection = brandPresetsBasePath?.let { - IDBrandPresetsFeatureCollection(FileSystemAccess(brandPresetsBasePath)) + /** Create a new FeatureDictionary which gets its data from the given directory. + * Optionally, a path to brand presets can be specified. */ + fun create( + fileSystem: FileSystem, + presetsBasePath: String, + brandPresetsBasePath: String? = null + ) = FeatureDictionary( + featureCollection = + IDLocalizedFeatureCollection(FileSystemAccess(fileSystem, presetsBasePath)), + brandFeatureCollection = + brandPresetsBasePath?.let { + IDBrandPresetsFeatureCollection(FileSystemAccess(fileSystem, brandPresetsBasePath)) } - ) + ) } } diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt index a28e926..d5ece28 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt @@ -1,23 +1,24 @@ package de.westnordost.osmfeatures -import okio.FileSystem -import okio.Path.Companion.toPath -import okio.Source +import kotlinx.io.Source +import kotlinx.io.buffered +import kotlinx.io.files.FileSystem +import kotlinx.io.files.Path -expect fun fileSystem(): FileSystem - -class FileSystemAccess(private val basePath: String) : FileAccessAdapter { - private val fs: FileSystem = fileSystem() +class FileSystemAccess( + private val fileSystem: FileSystem, + private val basePath: String +) : ResourceAccessAdapter { init { - fs.metadataOrNull(basePath.toPath())?.let { - require(it.isDirectory) { "$basePath is not a directory" } - } + val metadata = fileSystem.metadataOrNull(Path(basePath)) + require(metadata != null) { "$basePath does not exist" } + require(metadata.isDirectory) { "$basePath is not a directory" } } override fun exists(name: String): Boolean = - fs.exists(("$basePath/$name").toPath()) + fileSystem.exists(Path(basePath, name)) override fun open(name: String): Source = - fs.source(("$basePath/$name".toPath())) + fileSystem.source(Path(basePath, name)).buffered() } diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt index 859c854..267c154 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import okio.use - /** Non-localized feature collection sourcing from (NSI) iD presets defined in JSON. * * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that @@ -9,7 +7,7 @@ import okio.use * more files like e.g. presets-DE.json, presets-US-NY.json into the directory which will be loaded * lazily on demand */ internal class IDBrandPresetsFeatureCollection( - private val fileAccess: FileAccessAdapter + private val fileAccess: ResourceAccessAdapter ) : PerCountryFeatureCollection { // countryCode -> featureId -> Feature private val featuresByIdByCountryCode = LinkedHashMap>(320) @@ -40,6 +38,7 @@ internal class IDBrandPresetsFeatureCollection( } } + @OptIn(ExperimentalStdlibApi::class) private fun loadFeatures(countryCode: String?): List { val filename = getPresetsFileName(countryCode) if (!fileAccess.exists(filename)) return emptyList() diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt index 82148b7..6b44f20 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt @@ -1,14 +1,12 @@ package de.westnordost.osmfeatures -import okio.use - /** Localized feature collection sourcing from iD presets defined in JSON. * * The base path is defined via the given FileAccessAdapter. In the base path, it is expected that * there is a presets.json which includes all the features. The translations are expected to be * located in the same directory named like e.g. de.json, pt-BR.json etc. */ internal class IDLocalizedFeatureCollection( - private val fileAccess: FileAccessAdapter + private val fileAccess: ResourceAccessAdapter ) : LocalizedFeatureCollection { // featureId -> Feature private val featuresById: LinkedHashMap @@ -31,6 +29,7 @@ internal class IDLocalizedFeatureCollection( return getOrLoadLocalizedFeatures(locales)[id] } + @OptIn(ExperimentalStdlibApi::class) private fun loadFeatures(): List { return fileAccess.open(FEATURES_FILE).use { source -> IDPresetsJsonParser().parse(source) @@ -62,6 +61,7 @@ internal class IDLocalizedFeatureCollection( return localizedFeaturesList.synchronizedGetOrCreate(locale, ::loadLocalizedFeaturesList) } + @OptIn(ExperimentalStdlibApi::class) private fun loadLocalizedFeaturesList(locale: String?): List { val filename = if (locale != null) getLocalizationFilename(locale) else "en.json" if (!fileAccess.exists(filename)) return emptyList() diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt index 5c1e8c0..fbb773c 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt @@ -2,27 +2,22 @@ package de.westnordost.osmfeatures import de.westnordost.osmfeatures.JsonUtils.parseList import de.westnordost.osmfeatures.JsonUtils.parseStringMap +import kotlinx.io.Source import kotlinx.serialization.json.* -import okio.Buffer -import okio.Source -import okio.buffer /** Parses this file * [...](https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json) * into map of id -> Feature. */ class IDPresetsJsonParser(private var isSuggestion: Boolean = false) { - fun parse(source: Source): List { - val sink = Buffer() - source.buffer().readAll(sink) - val decodedObject = Json.decodeFromString(sink.readUtf8()) - return decodedObject.mapNotNull { (key, value) -> parseFeature(key, value.jsonObject)} - } + fun parse(source: Source): List = + parse(Json.decodeFromSource(source)) - fun parse(content: String): List { - val decodedObject = Json.decodeFromString(content) - return decodedObject.mapNotNull { (key, value) -> parseFeature(key, value.jsonObject)} - } + fun parse(content: String): List = + parse(Json.decodeFromString(content)) + + private fun parse(json: JsonObject): List = + json.mapNotNull { (key, value) -> parseFeature(key, value.jsonObject) } private fun parseFeature(id: String, p: JsonObject): BaseFeature? { val tags = parseStringMap(p["tags"]?.jsonObject) diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt index 6667f90..6e39e4e 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt @@ -1,10 +1,10 @@ package de.westnordost.osmfeatures +import kotlinx.io.Source import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive -import okio.Source /** Parses a file from * https://github.com/openstreetmap/id-tagging-schema/tree/main/dist/translations @@ -12,18 +12,21 @@ import okio.Source */ class IDPresetsTranslationJsonParser { - fun parse( - source: Source, locale: String?, baseFeatures: Map - ): List { - val content = JsonUtils.getContent(source) - return parse(content, locale, baseFeatures) - } fun parse( content: String, locale: String?, baseFeatures: Map + ): List = + parse(Json.decodeFromString(content), locale, baseFeatures) + + fun parse( + source: Source, locale: String?, baseFeatures: Map + ): List = + parse(Json.decodeFromSource(source), locale, baseFeatures) + + private fun parse( + json: JsonObject, locale: String?, baseFeatures: Map ): List { - val decodedObject = Json.decodeFromString(content) - val languageKey = decodedObject.entries.iterator().next().key - val languageObject = decodedObject[languageKey] + val languageKey = json.entries.iterator().next().key + val languageObject = json[languageKey] ?: return emptyList() val presetsContainerObject = languageObject.jsonObject["presets"] ?: return emptyList() diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt index bd6a10f..fb05bf6 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt @@ -1,9 +1,8 @@ package de.westnordost.osmfeatures +import kotlinx.io.Source +import kotlinx.io.readString import kotlinx.serialization.json.* -import okio.Buffer -import okio.Source -import okio.buffer internal object JsonUtils { @@ -15,14 +14,9 @@ internal object JsonUtils { if (map == null) return HashMap(1) return map.map { (key, value) -> key to value.jsonPrimitive.content}.toMap().toMutableMap() } - - // this is only necessary because Android uses some old version of org.json where - // new JSONObject(new JSONTokener(inputStream)) is not defined... - - fun getContent(source: Source): String { - val sink = Buffer() - source.buffer().readAll(sink) - - return sink.readUtf8() - } } + +// TODO This can hopefully be replaced with a function from kotlinx-serialization soon +@OptIn(ExperimentalStdlibApi::class) +internal inline fun Json.decodeFromSource(source: Source): T = + decodeFromString(source.use { it.readString() }) \ No newline at end of file diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/ResourceAccessAdapter.kt similarity index 65% rename from src/commonMain/kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt rename to src/commonMain/kotlin/de/westnordost/osmfeatures/ResourceAccessAdapter.kt index bf30892..584aa9a 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FileAccessAdapter.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/ResourceAccessAdapter.kt @@ -1,8 +1,8 @@ package de.westnordost.osmfeatures -import okio.Source +import kotlinx.io.Source -interface FileAccessAdapter { +interface ResourceAccessAdapter { fun exists(name: String): Boolean fun open(name: String): Source diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt index b6c0021..b94cf1d 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt @@ -1,16 +1,15 @@ package de.westnordost.osmfeatures +import kotlinx.io.Source import kotlin.test.assertEquals -import kotlin.test.assertTrue import kotlin.test.Test -import okio.Source class IDBrandPresetsFeatureCollectionTest { @Test fun load_brands() { - val c = IDBrandPresetsFeatureCollection(object : FileAccessAdapter { - override fun exists(name: String): Boolean = name == "presets.json" - override fun open(name: String): Source = getSource("brand_presets_min.json") + val c = IDBrandPresetsFeatureCollection(object : ResourceAccessAdapter { + override fun exists(name: String) = name == "presets.json" + override fun open(name: String) = getSource("brand_presets_min.json") }) assertEquals( setOf("Duckworths", "Megamall"), @@ -24,9 +23,9 @@ class IDBrandPresetsFeatureCollectionTest { @Test fun load_brands_by_country() { - val c = IDBrandPresetsFeatureCollection(object : FileAccessAdapter { - override fun exists(name: String): Boolean = name == "presets-DE.json" - override fun open(name: String): Source = getSource("brand_presets_min2.json") + val c = IDBrandPresetsFeatureCollection(object : ResourceAccessAdapter { + override fun exists(name: String) = name == "presets-DE.json" + override fun open(name: String) = getSource("brand_presets_min2.json") }) assertEquals( setOf("Talespin"), diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt index d56a29c..4246455 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt @@ -1,26 +1,26 @@ package de.westnordost.osmfeatures -import okio.Source +import kotlinx.io.Source import kotlin.test.* class IDLocalizedFeatureCollectionTest { @Test fun features_not_found_produces_runtime_exception() { assertFails { - IDLocalizedFeatureCollection(object : FileAccessAdapter { - override fun exists(name: String): Boolean = false - override fun open(name: String): Source = throw Exception() + IDLocalizedFeatureCollection(object : ResourceAccessAdapter { + override fun exists(name: String) = false + override fun open(name: String) = throw Exception() }) } } @Test fun load_features_and_two_localizations() { - val c = IDLocalizedFeatureCollection(object : FileAccessAdapter { - override fun exists(name: String): Boolean = + val c = IDLocalizedFeatureCollection(object : ResourceAccessAdapter { + override fun exists(name: String) = name in listOf("presets.json", "en.json", "de.json") - override fun open(name: String): Source = when (name) { + override fun open(name: String) = when (name) { "presets.json" -> getSource("some_presets_min.json") "en.json" -> getSource("localizations_en.json") "de.json" -> getSource("localizations_de.json") @@ -70,15 +70,14 @@ class IDLocalizedFeatureCollectionTest { @Test fun load_features_and_merge_localizations() { - val c = IDLocalizedFeatureCollection(object : FileAccessAdapter { - override fun exists(name: String): Boolean = - name in listOf( - "presets.json", - "de-AT.json", - "de.json", - "de-Cyrl.json", - "de-Cyrl-AT.json" - ) + val c = IDLocalizedFeatureCollection(object : ResourceAccessAdapter { + override fun exists(name: String) = name in listOf( + "presets.json", + "de-AT.json", + "de.json", + "de-Cyrl.json", + "de-Cyrl-AT.json" + ) override fun open(name: String): Source = when (name) { "presets.json" -> getSource("some_presets_min.json") diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt index edd1651..d75dedf 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt @@ -5,9 +5,11 @@ import io.ktor.client.engine.cio.CIO import io.ktor.client.request.get import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText -import okio.Source +import kotlinx.io.Source +import kotlinx.io.buffered +import kotlinx.io.files.Path +import kotlinx.io.files.SystemFileSystem import kotlin.test.Test -import okio.IOException import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -94,17 +96,10 @@ class IDPresetsJsonParserTest { } } - private fun parse(file: String): List { - return try { - IDPresetsJsonParser().parse(getSource(file)) - } catch (e: IOException) { - throw RuntimeException() - } - } + private fun parse(file: String): List = + read(file) { IDPresetsJsonParser().parse(it) } - @Throws(IOException::class) - private fun getSource(file: String): Source { - val fileSystemAccess = FileSystemAccess("src/commonTest/resources") - return fileSystemAccess.open(file) - } + @OptIn(ExperimentalStdlibApi::class) + private fun read(file: String, block: (Source) -> R): R = + SystemFileSystem.source(Path("src/commonTest/resources", file)).buffered().use { block(it) } } diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt index 7e0cb21..9b7feab 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import okio.Source -import okio.IOException import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.Test @@ -10,6 +8,11 @@ import io.ktor.client.engine.cio.* import io.ktor.client.request.get import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText +import kotlinx.io.IOException +import kotlinx.io.Source +import kotlinx.io.buffered +import kotlinx.io.files.Path +import kotlinx.io.files.SystemFileSystem import kotlin.test.fail class IDPresetsTranslationJsonParserTest { @@ -82,23 +85,16 @@ class IDPresetsTranslationJsonParserTest { } private fun parse(presetsFile: String, translationsFile: String): List { - return try { - val baseFeatures: List = - IDPresetsJsonParser().parse(getSource(presetsFile)) - val featureMap = HashMap(baseFeatures.associateBy { it.id }) - IDPresetsTranslationJsonParser().parse( - getSource(translationsFile), - "en", - featureMap - ) - } catch (e: IOException) { - throw RuntimeException() + val baseFeatures = read(presetsFile) { + IDPresetsJsonParser().parse(it) + }.associateBy { it.id } + + return read(translationsFile) { + IDPresetsTranslationJsonParser().parse(it, "en", baseFeatures) } } - @Throws(IOException::class) - private fun getSource(file: String): Source { - val fileSystemAccess = FileSystemAccess("src/commonTest/resources") - return fileSystemAccess.open(file) - } + @OptIn(ExperimentalStdlibApi::class) + private fun read(file: String, block: (Source) -> R): R = + SystemFileSystem.source(Path("src/commonTest/resources", file)).buffered().use { block(it) } } diff --git a/src/iosMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt b/src/iosMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt deleted file mode 100644 index 28bfbce..0000000 --- a/src/iosMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt +++ /dev/null @@ -1,7 +0,0 @@ -package de.westnordost.osmfeatures - -import okio.FileSystem - -actual fun fileSystem(): FileSystem { - return FileSystem.SYSTEM -} \ No newline at end of file diff --git a/src/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt b/src/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt deleted file mode 100644 index bdfa875..0000000 --- a/src/jvmMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt +++ /dev/null @@ -1,6 +0,0 @@ -package de.westnordost.osmfeatures - -import okio.FileSystem - -actual fun fileSystem(): FileSystem = - FileSystem.SYSTEM \ No newline at end of file diff --git a/src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt b/src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt index d25c752..eb0d90e 100644 --- a/src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt +++ b/src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt @@ -3,7 +3,7 @@ package de.westnordost.osmfeatures import java.net.URL import okio.source -class LivePresetDataAccessAdapter : FileAccessAdapter { +class LivePresetDataAccessAdapter : ResourceAccessAdapter { override fun exists(name: String): Boolean = name in listOf("presets.json", "de.json", "en.json", "en-GB.json") From 5b9c5fc0dcd9b028e2bcf73cfafcf764798d9210 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 10 May 2024 20:20:46 +0200 Subject: [PATCH 75/98] correct synchronization / lazynization --- .../osmfeatures/CollectionUtils.kt | 17 ---- .../osmfeatures/FeatureDictionary.kt | 80 ++++++++----------- .../IDBrandPresetsFeatureCollection.kt | 13 ++- .../IDLocalizedFeatureCollection.kt | 29 +++---- 4 files changed, 53 insertions(+), 86 deletions(-) delete mode 100644 src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt deleted file mode 100644 index 6f0830f..0000000 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/CollectionUtils.kt +++ /dev/null @@ -1,17 +0,0 @@ -package de.westnordost.osmfeatures - -/** For the given map, get the value of the entry at the given key and if there is no - * entry yet, create it using the given create function thread-safely */ -internal fun MutableMap.synchronizedGetOrCreate(key: K, createFn: (K) -> V): V { - val value by lazy { - val value = get(key) - if (value == null) { - val answer = createFn(key) - put(key, answer) - answer - } else { - value - } - } - return value -} diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt index 989fafa..170a6c0 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt @@ -6,14 +6,14 @@ class FeatureDictionary internal constructor( private val featureCollection: LocalizedFeatureCollection, private val brandFeatureCollection: PerCountryFeatureCollection? ) { - private val brandNamesIndexes: MutableMap, FeatureTermIndex> = HashMap() - private val brandTagsIndexes: MutableMap, FeatureTagsIndex> = HashMap() + private val brandNamesIndexes = HashMap, Lazy>() + private val brandTagsIndexes = HashMap, Lazy>() // locale list -> index - private val tagsIndexes: MutableMap, FeatureTagsIndex> = HashMap() - private val namesIndexes: MutableMap, FeatureTermIndex> = HashMap() - private val termsIndexes: MutableMap, FeatureTermIndex> = HashMap() - private val tagValuesIndexes: MutableMap, FeatureTermIndex> = HashMap() + private val tagsIndexes = HashMap, Lazy>() + private val namesIndexes = HashMap, Lazy>() + private val termsIndexes = HashMap, Lazy>() + private val tagValuesIndexes = HashMap, Lazy>() init { // build indices for default locale @@ -188,75 +188,66 @@ class FeatureDictionary internal constructor( //region Lazily get or create Indexes /** lazily get or create tags index for given locale(s) */ - private fun getTagsIndex(locales: List): FeatureTagsIndex { - return tagsIndexes.synchronizedGetOrCreate(locales, ::createTagsIndex) - } + private fun getTagsIndex(locales: List): FeatureTagsIndex = + tagsIndexes.getOrPut(locales) { lazy { createTagsIndex(locales) } }.value - private fun createTagsIndex(locales: List): FeatureTagsIndex { - return FeatureTagsIndex(featureCollection.getAll(locales)) - } + private fun createTagsIndex(locales: List): FeatureTagsIndex = + FeatureTagsIndex(featureCollection.getAll(locales)) /** lazily get or create names index for given locale(s) */ private fun getNamesIndex(locales: List): FeatureTermIndex = - namesIndexes.synchronizedGetOrCreate(locales, ::createNamesIndex) + namesIndexes.getOrPut(locales) { lazy { createNamesIndex(locales) } }.value - private fun createNamesIndex(locales: List): FeatureTermIndex { - val features = featureCollection.getAll(locales) - return FeatureTermIndex(features) { feature -> + private fun createNamesIndex(locales: List): FeatureTermIndex = + FeatureTermIndex(featureCollection.getAll(locales)) { feature -> feature.getSearchableNames().toList() } - } /** lazily get or create terms index for given locale(s) */ - private fun getTermsIndex(locales: List): FeatureTermIndex { - return termsIndexes.synchronizedGetOrCreate(locales, ::createTermsIndex) - } + private fun getTermsIndex(locales: List): FeatureTermIndex = + termsIndexes.getOrPut(locales) { lazy { createTermsIndex(locales) } }.value - private fun createTermsIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales)) { feature -> + private fun createTermsIndex(locales: List): FeatureTermIndex = + FeatureTermIndex(featureCollection.getAll(locales)) { feature -> if (!feature.isSearchable) emptyList() else feature.canonicalTerms } - } /** lazily get or create tag values index */ - private fun getTagValuesIndex(locales: List): FeatureTermIndex { - return tagValuesIndexes.synchronizedGetOrCreate(locales, ::createTagValuesIndex) - } - - private fun createTagValuesIndex(locales: List): FeatureTermIndex { - return FeatureTermIndex(featureCollection.getAll(locales)) { feature -> - if (!feature.isSearchable) return@FeatureTermIndex emptyList() - return@FeatureTermIndex feature.tags.values.filter { it != "*" } + private fun getTagValuesIndex(locales: List): FeatureTermIndex = + tagValuesIndexes.getOrPut(locales) { lazy { createTagValuesIndex(locales) } }.value + + private fun createTagValuesIndex(locales: List): FeatureTermIndex = + FeatureTermIndex(featureCollection.getAll(locales)) { feature -> + if (!feature.isSearchable) { + emptyList() + } else { + feature.tags.values.filter { it != "*" } + } } - } /** lazily get or create brand names index for country */ - private fun getBrandNamesIndex(countryCodes: List): FeatureTermIndex { - return brandNamesIndexes.synchronizedGetOrCreate(countryCodes, ::createBrandNamesIndex) - } + private fun getBrandNamesIndex(countryCodes: List): FeatureTermIndex = + brandNamesIndexes.getOrPut(countryCodes) { lazy { createBrandNamesIndex(countryCodes) } }.value - private fun createBrandNamesIndex(countryCodes: List): FeatureTermIndex { - return if (brandFeatureCollection == null) { + private fun createBrandNamesIndex(countryCodes: List): FeatureTermIndex = + if (brandFeatureCollection == null) { FeatureTermIndex(emptyList()) { emptyList() } } else { FeatureTermIndex(brandFeatureCollection.getAll(countryCodes)) { feature -> if (!feature.isSearchable) emptyList() else feature.canonicalNames } } - } /** lazily get or create tags index for the given countries */ - private fun getBrandTagsIndex(countryCodes: List): FeatureTagsIndex { - return brandTagsIndexes.synchronizedGetOrCreate(countryCodes, ::createBrandTagsIndex) - } + private fun getBrandTagsIndex(countryCodes: List): FeatureTagsIndex = + brandTagsIndexes.getOrPut(countryCodes) { lazy { createBrandTagsIndex(countryCodes) } }.value - private fun createBrandTagsIndex(countryCodes: List): FeatureTagsIndex { - return if (brandFeatureCollection == null) { + private fun createBrandTagsIndex(countryCodes: List): FeatureTagsIndex = + if (brandFeatureCollection == null) { FeatureTagsIndex(emptyList()) } else { FeatureTagsIndex(brandFeatureCollection.getAll(countryCodes)) } - } //endregion @@ -409,7 +400,6 @@ class FeatureDictionary internal constructor( //endregion companion object { - /** Create a new FeatureDictionary which gets its data from the given directory. * Optionally, a path to brand presets can be specified. */ fun create( diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt index 267c154..18ee9eb 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollection.kt @@ -9,8 +9,8 @@ package de.westnordost.osmfeatures internal class IDBrandPresetsFeatureCollection( private val fileAccess: ResourceAccessAdapter ) : PerCountryFeatureCollection { - // countryCode -> featureId -> Feature - private val featuresByIdByCountryCode = LinkedHashMap>(320) + // countryCode -> lazy { featureId -> Feature } + private val featuresByIdByCountryCode = LinkedHashMap>>(320) init { getOrLoadPerCountryFeatures(null) @@ -32,11 +32,10 @@ internal class IDBrandPresetsFeatureCollection( return null } - private fun getOrLoadPerCountryFeatures(countryCode: String?): LinkedHashMap { - return featuresByIdByCountryCode.synchronizedGetOrCreate(countryCode) { - loadFeatures(countryCode).associateByTo(LinkedHashMap()) { it.id } - } - } + private fun getOrLoadPerCountryFeatures(countryCode: String?): LinkedHashMap = + featuresByIdByCountryCode.getOrPut(countryCode) { + lazy { loadFeatures(countryCode).associateByTo(LinkedHashMap()) { it.id } } + }.value @OptIn(ExperimentalStdlibApi::class) private fun loadFeatures(countryCode: String?): List { diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt index 6b44f20..e36f30a 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt @@ -11,11 +11,11 @@ internal class IDLocalizedFeatureCollection( // featureId -> Feature private val featuresById: LinkedHashMap - // locale -> localized feature - private val localizedFeaturesList: MutableMap> = HashMap() + // locale -> lazy { localized features } + private val localizedFeaturesList = HashMap>>() - // locales -> featureId -> Feature - private val localizedFeatures: MutableMap, LinkedHashMap> = HashMap() + // locales -> lazy { featureId -> Feature } + private val localizedFeatures = HashMap, Lazy>>() init { featuresById = loadFeatures().associateByTo(LinkedHashMap()) { it.id } @@ -30,15 +30,11 @@ internal class IDLocalizedFeatureCollection( } @OptIn(ExperimentalStdlibApi::class) - private fun loadFeatures(): List { - return fileAccess.open(FEATURES_FILE).use { source -> - IDPresetsJsonParser().parse(source) - } - } + private fun loadFeatures(): List = + fileAccess.open(FEATURES_FILE).use { IDPresetsJsonParser().parse(it) } - private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap { - return localizedFeatures.synchronizedGetOrCreate(locales, ::loadLocalizedFeatures) - } + private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap = + localizedFeatures.getOrPut(locales) { lazy { loadLocalizedFeatures(locales) } }.value private fun loadLocalizedFeatures(locales: List): LinkedHashMap { val result = LinkedHashMap(featuresById.size) @@ -57,16 +53,15 @@ internal class IDLocalizedFeatureCollection( return result } - private fun getOrLoadLocalizedFeaturesList(locale: String): List { - return localizedFeaturesList.synchronizedGetOrCreate(locale, ::loadLocalizedFeaturesList) - } + private fun getOrLoadLocalizedFeaturesList(locale: String): List = + localizedFeaturesList.getOrPut(locale) { lazy { loadLocalizedFeaturesList(locale) } }.value @OptIn(ExperimentalStdlibApi::class) private fun loadLocalizedFeaturesList(locale: String?): List { val filename = if (locale != null) getLocalizationFilename(locale) else "en.json" if (!fileAccess.exists(filename)) return emptyList() - fileAccess.open(filename).use { source -> - return IDPresetsTranslationJsonParser().parse(source, locale, featuresById) + return fileAccess.open(filename).use { source -> + IDPresetsTranslationJsonParser().parse(source, locale, featuresById) } } From 0fec5c7201629d9826fcc1d924e8c2248f86262c Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 10 May 2024 20:21:00 +0200 Subject: [PATCH 76/98] Classes from kotlinx-io-core are on the interface --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2177717..eabba37 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,7 +29,7 @@ kotlin { sourceSets { commonMain { dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.3.4") + api("org.jetbrains.kotlinx:kotlinx-io-core:0.3.4") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") implementation("com.doist.x:normalize:1.0.5") } From 46bc9307a251c3d8a0440e1f4afa827a069ea95b Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 10 May 2024 20:21:28 +0200 Subject: [PATCH 77/98] add entry for changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70391b7..bdfbf10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +# 6.0.0 + +- The library is now a Kotlin Multiplatform library. This is a breaking API change. + # 5.2 - Add support placeholders for preset names (breaking change in [v5.0.0](https://github.com/ideditor/schema-builder/blob/main/CHANGELOG.md#510) of iD presets schema) From b7ecd80c76f3a4a18ec39f3007dc76a20906ff54 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 10 May 2024 23:28:45 +0200 Subject: [PATCH 78/98] fix tests --- build.gradle.kts | 14 ++-- .../osmfeatures/FeatureDictionary.kt | 33 ++++---- .../osmfeatures/FeatureDictionaryTest.kt | 29 +++++++ .../IDBrandPresetsFeatureCollectionTest.kt | 8 +- .../IDLocalizedFeatureCollectionTest.kt | 22 +++--- .../osmfeatures/IDPresetsJsonParserTest.kt | 53 ++++++------- .../IDPresetsTranslationJsonParserTest.kt | 79 ++++++++----------- .../LivePresetDataAccessAdapter.kt | 31 ++++++++ .../SynchronizedFeatureDictionaryTest.kt | 4 - .../TestLocalizedFeatureCollection.kt | 10 +-- .../TestPerCountryFeatureCollection.kt | 18 +++-- .../de/westnordost/osmfeatures/TestUtils.kt | 13 +++ .../FeatureDictionaryTest.kt | 35 -------- .../LivePresetDataAccessAdapter.kt | 18 ----- 14 files changed, 180 insertions(+), 187 deletions(-) create mode 100644 src/commonTest/kotlin/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.kt delete mode 100644 src/commonTest/kotlin/de/westnordost/osmfeatures/SynchronizedFeatureDictionaryTest.kt create mode 100644 src/commonTest/kotlin/de/westnordost/osmfeatures/TestUtils.kt delete mode 100644 src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt delete mode 100644 src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt diff --git a/build.gradle.kts b/build.gradle.kts index eabba37..f16d1db 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,19 +29,23 @@ kotlin { sourceSets { commonMain { dependencies { + // multiplatform file access api("org.jetbrains.kotlinx:kotlinx-io-core:0.3.4") + // parsing the preset.json implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + // for stripping diacritics correctly implementation("com.doist.x:normalize:1.0.5") } } commonTest { dependencies { implementation(kotlin("test")) - } - } - - jvmTest { - dependencies { + // we are pulling some current preset json from the iD preset repo to see if parsing + // does at least not crash and return something + implementation("io.ktor:ktor-client-core:2.3.11") + implementation("io.ktor:ktor-client-cio:2.3.11") + // ktor-client is a suspending API, so we need coroutines too + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") } } } diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt index 170a6c0..f131308 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt @@ -29,12 +29,11 @@ class FeatureDictionary internal constructor( private fun getById( id: String, - locales: List = listOf(defaultLocale()), + locales: List? = null, countryCode: String? = null - ): Feature? { - return featureCollection.get(id, locales) + ): Feature? = + featureCollection.get(id, locales ?: listOf(defaultLocale(), null)) ?: brandFeatureCollection?.get(id, dissectCountryCode(countryCode)) - } //endregion @@ -46,15 +45,17 @@ class FeatureDictionary internal constructor( private fun getByTags( tags: Map, geometry: GeometryType? = null, - locales: List = listOf(defaultLocale()), + locales: List? = null, countryCode: String? = null, isSuggestion: Boolean? = null ): List { if (tags.isEmpty()) return emptyList() - val foundFeatures: MutableList = mutableListOf() + val localesOrDefault = locales ?: listOf(defaultLocale(), null) + + val foundFeatures = mutableListOf() if (isSuggestion == null || !isSuggestion) { - foundFeatures.addAll(getTagsIndex(locales).getAll(tags)) + foundFeatures.addAll(getTagsIndex(localesOrDefault).getAll(tags)) } if (isSuggestion == null || isSuggestion) { val countryCodes = dissectCountryCode(countryCode) @@ -81,7 +82,7 @@ class FeatureDictionary internal constructor( } // 2. if search is not limited by locale, return matches not limited by locale first - if (locales.size == 1 && locales[0] == null) { + if (localesOrDefault.size == 1 && localesOrDefault[0] == null) { val localeOrder = ( (b.includeCountryCodes.isEmpty() && b.excludeCountryCodes.isEmpty()).toInt() - (a.includeCountryCodes.isEmpty() && a.excludeCountryCodes.isEmpty()).toInt() @@ -112,12 +113,14 @@ class FeatureDictionary internal constructor( private fun getByTerm( search: String, geometry: GeometryType?, - locales: List, + locales: List?, countryCode: String?, isSuggestion: Boolean? ): Sequence { val canonicalSearch = search.canonicalize() + val localesOrDefault = locales ?: listOf(defaultLocale(), null) + val sortNames = Comparator { a: Feature, b: Feature -> // 1. exact matches first val exactMatchOrder = ( @@ -156,7 +159,7 @@ class FeatureDictionary internal constructor( if (isSuggestion == null || !isSuggestion) { // a. matches with presets first yieldAll( - getNamesIndex(locales).getAll(canonicalSearch).sortedWith(sortNames) + getNamesIndex(localesOrDefault).getAll(canonicalSearch).sortedWith(sortNames) ) } if (isSuggestion == null || isSuggestion) { @@ -169,13 +172,13 @@ class FeatureDictionary internal constructor( if (isSuggestion == null || !isSuggestion) { // c. matches with terms third yieldAll( - getTermsIndex(locales).getAll(canonicalSearch).sortedWith(sortMatchScore) + getTermsIndex(localesOrDefault).getAll(canonicalSearch).sortedWith(sortMatchScore) ) } if (isSuggestion == null || !isSuggestion) { // d. matches with tag values fourth yieldAll( - getTagValuesIndex(locales).getAll(canonicalSearch).sortedWith(sortMatchScore) + getTagValuesIndex(localesOrDefault).getAll(canonicalSearch).sortedWith(sortMatchScore) ) } } @@ -254,7 +257,7 @@ class FeatureDictionary internal constructor( //region Query builders inner class QueryByIdBuilder(private val id: String) { - private var locale: List = listOf(defaultLocale()) + private var locale: List? = null private var countryCode: String? = null /** @@ -288,7 +291,7 @@ class FeatureDictionary internal constructor( inner class QueryByTagBuilder(private val tags: Map) { private var geometryType: GeometryType? = null - private var locale: List = listOf(defaultLocale()) + private var locale: List? = null private var suggestion: Boolean? = null private var countryCode: String? = null @@ -341,7 +344,7 @@ class FeatureDictionary internal constructor( inner class QueryByTermBuilder(private val term: String) { private var geometryType: GeometryType? = null - private var locale: List = listOf(defaultLocale()) + private var locale: List? = null private var suggestion: Boolean? = null private var limit = 50 private var countryCode: String? = null diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt index 68a9cf0..8b81bea 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt @@ -654,6 +654,35 @@ class FeatureDictionaryTest { .get() assertEquals(lush, byId) } + + @Test + fun some_tests_with_real_data() { + val featureCollection = IDLocalizedFeatureCollection(LivePresetDataAccessAdapter()) + featureCollection.getAll(listOf("en")) + + val dictionary = FeatureDictionary(featureCollection, null) + + val matches = dictionary + .byTags(mapOf("amenity" to "studio")) + .forLocale("en") + .find() + assertEquals(1, matches.size) + assertEquals("Studio", matches[0].name) + + val matches2 = dictionary + .byTags(mapOf("amenity" to "studio", "studio" to "audio")) + .forLocale("en") + .find() + assertEquals(1, matches2.size) + assertEquals("Recording Studio", matches2[0].name) + + val matches3 = dictionary + .byTerm("Chinese Res") + .forLocale("en") + .find() + assertEquals(1, matches3.size) + assertEquals("Chinese Restaurant", matches3[0].name) + } } private fun dictionary(vararg entries: Feature) = FeatureDictionary( diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt index b94cf1d..7f9a359 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDBrandPresetsFeatureCollectionTest.kt @@ -1,6 +1,5 @@ package de.westnordost.osmfeatures -import kotlinx.io.Source import kotlin.test.assertEquals import kotlin.test.Test @@ -9,7 +8,7 @@ class IDBrandPresetsFeatureCollectionTest { fun load_brands() { val c = IDBrandPresetsFeatureCollection(object : ResourceAccessAdapter { override fun exists(name: String) = name == "presets.json" - override fun open(name: String) = getSource("brand_presets_min.json") + override fun open(name: String) = resource("brand_presets_min.json") }) assertEquals( setOf("Duckworths", "Megamall"), @@ -25,7 +24,7 @@ class IDBrandPresetsFeatureCollectionTest { fun load_brands_by_country() { val c = IDBrandPresetsFeatureCollection(object : ResourceAccessAdapter { override fun exists(name: String) = name == "presets-DE.json" - override fun open(name: String) = getSource("brand_presets_min2.json") + override fun open(name: String) = resource("brand_presets_min2.json") }) assertEquals( setOf("Talespin"), @@ -35,6 +34,3 @@ class IDBrandPresetsFeatureCollectionTest { assertEquals(true, c.get("yet_another/brand", listOf("DE"))?.isSuggestion) } } - -private fun getSource(file: String): Source = - FileSystemAccess("src/commonTest/resources").open(file) diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt index 4246455..bd50985 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt @@ -1,6 +1,5 @@ package de.westnordost.osmfeatures -import kotlinx.io.Source import kotlin.test.* class IDLocalizedFeatureCollectionTest { @@ -21,9 +20,9 @@ class IDLocalizedFeatureCollectionTest { name in listOf("presets.json", "en.json", "de.json") override fun open(name: String) = when (name) { - "presets.json" -> getSource("some_presets_min.json") - "en.json" -> getSource("localizations_en.json") - "de.json" -> getSource("localizations_de.json") + "presets.json" -> resource("some_presets_min.json") + "en.json" -> resource("localizations_en.json") + "de.json" -> resource("localizations_de.json") else -> throw Exception("File not found") } }) @@ -79,12 +78,12 @@ class IDLocalizedFeatureCollectionTest { "de-Cyrl-AT.json" ) - override fun open(name: String): Source = when (name) { - "presets.json" -> getSource("some_presets_min.json") - "de-AT.json" -> getSource("localizations_de-AT.json") - "de.json" -> getSource("localizations_de.json") - "de-Cyrl-AT.json" -> getSource("localizations_de-Cyrl-AT.json") - "de-Cyrl.json" -> getSource("localizations_de-Cyrl.json") + override fun open(name: String) = when (name) { + "presets.json" -> resource("some_presets_min.json") + "de-AT.json" -> resource("localizations_de-AT.json") + "de.json" -> resource("localizations_de.json") + "de-Cyrl-AT.json" -> resource("localizations_de-Cyrl-AT.json") + "de-Cyrl.json" -> resource("localizations_de-Cyrl.json") else -> throw Exception("File not found") } }) @@ -115,6 +114,3 @@ class IDLocalizedFeatureCollectionTest { assertEquals("бацкхусл", c.get("some/id", cryllicAustria)?.name) } } - -private fun getSource(file: String): Source = - FileSystemAccess("src/commonTest/resources").open(file) diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt index d75dedf..ad6ff39 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt @@ -3,24 +3,20 @@ package de.westnordost.osmfeatures import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import io.ktor.client.request.get -import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText -import kotlinx.io.Source -import kotlinx.io.buffered -import kotlinx.io.files.Path -import kotlinx.io.files.SystemFileSystem +import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -import kotlin.test.fail class IDPresetsJsonParserTest { @Test fun load_features_only() { - val features: List = parse("one_preset_full.json") + val features = parseResource("one_preset_full.json") assertEquals(1, features.size) - val feature: Feature = features[0] + + val feature = features[0] assertEquals("some/id", feature.id) assertEquals(mapOf("a" to "b", "c" to "d"), feature.tags) assertEquals( @@ -30,7 +26,8 @@ class IDPresetsJsonParserTest { GeometryType.LINE, GeometryType.AREA, GeometryType.RELATION - ), feature.geometry + ), + feature.geometry ) assertEquals(listOf("DE", "GB"), feature.includeCountryCodes) assertEquals(listOf("IT"), feature.excludeCountryCodes) @@ -47,9 +44,10 @@ class IDPresetsJsonParserTest { @Test fun load_features_only_defaults() { - val features: List = parse("one_preset_min.json") + val features = parseResource("one_preset_min.json") assertEquals(1, features.size) - val feature: Feature = features[0] + + val feature = features[0] assertEquals("some/id", feature.id) assertEquals(mapOf("a" to "b", "c" to "d"), feature.tags) assertEquals(listOf(GeometryType.POINT), feature.geometry) @@ -68,7 +66,7 @@ class IDPresetsJsonParserTest { @Test fun load_features_unsupported_location_set() { - val features: List = parse("one_preset_unsupported_location_set.json") + val features = parseResource("one_preset_unsupported_location_set.json") assertEquals(2, features.size) assertEquals("some/ok", features[0].id) assertEquals("another/ok", features[1].id) @@ -76,30 +74,23 @@ class IDPresetsJsonParserTest { @Test fun load_features_no_wildcards() { - val features: List = parse("one_preset_wildcard.json") + val features = parseResource("one_preset_wildcard.json") assertTrue(features.isEmpty()) } @Test - fun parse_some_real_data() { + fun parse_some_real_data() = runBlocking { + val client = HttpClient(CIO) { expectSuccess = true } - val client = HttpClient(CIO) - val httpResponse: HttpResponse = client.get("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - if (httpResponse.status.value in 200..299) { - val body = httpResponse.bodyAsText() - val features = IDPresetsJsonParser().parse(body) - // should not crash etc - assertTrue(features.size > 1000) - } - else { - fail("Unable to retrieve response") - } - } + val presets = client + .get("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") + .bodyAsText() - private fun parse(file: String): List = - read(file) { IDPresetsJsonParser().parse(it) } + val features = IDPresetsJsonParser().parse(presets) + // should not crash etc + assertTrue(features.size > 1000) + } - @OptIn(ExperimentalStdlibApi::class) - private fun read(file: String, block: (Source) -> R): R = - SystemFileSystem.source(Path("src/commonTest/resources", file)).buffered().use { block(it) } + private fun parseResource(file: String): List = + useResource(file) { IDPresetsJsonParser().parse(it) } } diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt index 9b7feab..a2ef8ed 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParserTest.kt @@ -6,21 +6,16 @@ import kotlin.test.Test import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.request.get -import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText -import kotlinx.io.IOException -import kotlinx.io.Source -import kotlinx.io.buffered -import kotlinx.io.files.Path -import kotlinx.io.files.SystemFileSystem -import kotlin.test.fail +import kotlinx.coroutines.runBlocking class IDPresetsTranslationJsonParserTest { @Test fun load_features_and_localization() { - val features: List = parse("one_preset_min.json", "localizations.json") + val features = parseResource("one_preset_min.json", "localizations.json") assertEquals(1, features.size) - val feature: Feature = features[0] + + val feature = features[0] assertEquals("some/id", feature.id) assertEquals(mapOf("a" to "b", "c" to "d"), feature.tags) assertEquals(listOf(GeometryType.POINT), feature.geometry) @@ -31,10 +26,10 @@ class IDPresetsTranslationJsonParserTest { @Test fun load_features_and_localization_defaults() { - val features: List = - parse("one_preset_min.json", "localizations_min.json") + val features = parseResource("one_preset_min.json", "localizations_min.json") assertEquals(1, features.size) - val feature: Feature = features[0] + + val feature = features[0] assertEquals("some/id", feature.id) assertEquals(mapOf("a" to "b", "c" to "d"), feature.tags) assertEquals(listOf(GeometryType.POINT), feature.geometry) @@ -44,11 +39,11 @@ class IDPresetsTranslationJsonParserTest { @Test fun load_features_and_localization_with_placeholder_name() { - val features: List = - parse("one_preset_with_placeholder_name.json", "localizations.json") - val featuresById = HashMap(features.associateBy { it.id }) + val features = parseResource("one_preset_with_placeholder_name.json", "localizations.json") + val featuresById = features.associateBy { it.id } assertEquals(2, features.size) - val feature: Feature? = featuresById["some/id-dingsdongs"] + + val feature = featuresById["some/id-dingsdongs"] assertEquals("some/id-dingsdongs", feature?.id) assertEquals(mapOf("a" to "b", "c" to "d"), feature?.tags) assertEquals(listOf(GeometryType.POINT), feature?.geometry) @@ -58,43 +53,33 @@ class IDPresetsTranslationJsonParserTest { } @Test - fun parse_some_real_data() { - val client = HttpClient(CIO) - val httpResponse: HttpResponse = client.get("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - if (httpResponse.status.value in 200..299) { - val body = httpResponse.bodyAsText() - val features = IDPresetsJsonParser().parse(body) - val featureMap = HashMap(features.associateBy { it.id }) - val translationHttpResponse: HttpResponse = client.get("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json") - if (translationHttpResponse.status.value in 200..299) { - val translatedFeatures = IDPresetsTranslationJsonParser().parse( - translationHttpResponse.bodyAsText(), - "de-DE", - featureMap - ) - // should not crash etc - assertTrue(translatedFeatures.size > 1000) - } - else { - fail("Unable to retrieve translation Http response") - } - } - else { - fail("Unable to retrieve response") - } + fun parse_some_real_data() = runBlocking { + val client = HttpClient(CIO) { expectSuccess = true } + + val presets = client + .get("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") + .bodyAsText() + + val features = IDPresetsJsonParser() + .parse(presets) + .associateBy { it.id } + + val translations = client + .get("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/de.json") + .bodyAsText() + + val translatedFeatures = IDPresetsTranslationJsonParser().parse(translations, "de-DE", features) + // should not crash etc + assertTrue(translatedFeatures.size > 1000) } - private fun parse(presetsFile: String, translationsFile: String): List { - val baseFeatures = read(presetsFile) { + private fun parseResource(presetsFile: String, translationsFile: String): List { + val baseFeatures = useResource(presetsFile) { IDPresetsJsonParser().parse(it) }.associateBy { it.id } - return read(translationsFile) { + return useResource(translationsFile) { IDPresetsTranslationJsonParser().parse(it, "en", baseFeatures) } } - - @OptIn(ExperimentalStdlibApi::class) - private fun read(file: String, block: (Source) -> R): R = - SystemFileSystem.source(Path("src/commonTest/resources", file)).buffered().use { block(it) } } diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.kt new file mode 100644 index 0000000..866b62e --- /dev/null +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.kt @@ -0,0 +1,31 @@ +package de.westnordost.osmfeatures + +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import kotlinx.coroutines.runBlocking +import kotlinx.io.Buffer +import kotlinx.io.Source + +class LivePresetDataAccessAdapter : ResourceAccessAdapter { + + private val client = HttpClient(CIO) { expectSuccess = true } + private val baseUrl = "https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist" + + override fun exists(name: String): Boolean = + name in listOf("presets.json", "de.json", "en.json", "en-GB.json") + + override fun open(name: String): Source { + val url = when(name) { + "presets.json" -> "$baseUrl/presets.json" + else -> "$baseUrl/translations/$name" + } + + // TODO will be able to use Source directly in ktor v3 + val jsonBytes = runBlocking { client.get(url).readBytes() } + val buffer = Buffer() + buffer.write(jsonBytes) + return buffer + } +} diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/SynchronizedFeatureDictionaryTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/SynchronizedFeatureDictionaryTest.kt deleted file mode 100644 index 40a7540..0000000 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/SynchronizedFeatureDictionaryTest.kt +++ /dev/null @@ -1,4 +0,0 @@ -package de.westnordost.osmfeatures - -class SynchronizedFeatureDictionaryTest { -} \ No newline at end of file diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt index 1e06d25..a4f1a8d 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt @@ -3,12 +3,12 @@ package de.westnordost.osmfeatures class TestLocalizedFeatureCollection(private val features: List) : LocalizedFeatureCollection { - override fun getAll(locales: List): Collection { - return features.filter { locales.contains(it.locale) } - } + override fun getAll(locales: List): Collection = + features.filter { locales.contains(it.locale) } override fun get(id: String, locales: List): Feature? { - val feature = features.find { it.id == id } - return if (feature == null || (!locales.contains(feature.locale))) null else feature + val feature = features.find { it.id == id } ?: return null + if (!locales.contains(feature.locale)) return null + return feature } } \ No newline at end of file diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.kt index aada8a8..8d9e6f2 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/TestPerCountryFeatureCollection.kt @@ -2,15 +2,17 @@ package de.westnordost.osmfeatures class TestPerCountryFeatureCollection(private val features: List) : PerCountryFeatureCollection { - override fun getAll(countryCodes: List): Collection { - return features.filter { it.isAvailableIn(countryCodes) } - } + override fun getAll(countryCodes: List): Collection = + features.filter { it.isAvailableIn(countryCodes) } - override fun get(id: String, countryCodes: List): Feature? { - return features.find { it.isAvailableIn(countryCodes) } - } + override fun get(id: String, countryCodes: List): Feature? = + features.find { it.isAvailableIn(countryCodes) } private fun Feature.isAvailableIn(countryCodes: List): Boolean = - countryCodes.none { excludeCountryCodes.contains(it) } && - countryCodes.any { includeCountryCodes.contains(it) || it == null && includeCountryCodes.isEmpty() } + countryCodes.none { + excludeCountryCodes.contains(it) + } && + countryCodes.any { + includeCountryCodes.contains(it) || it == null && includeCountryCodes.isEmpty() + } } \ No newline at end of file diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/TestUtils.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/TestUtils.kt new file mode 100644 index 0000000..fedb958 --- /dev/null +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/TestUtils.kt @@ -0,0 +1,13 @@ +package de.westnordost.osmfeatures + +import kotlinx.io.Source +import kotlinx.io.buffered +import kotlinx.io.files.Path +import kotlinx.io.files.SystemFileSystem + +@OptIn(ExperimentalStdlibApi::class) +fun useResource(file: String, block: (Source) -> R): R = + resource(file).use { block(it) } + +fun resource(file: String): Source = + SystemFileSystem.source(Path("src/commonTest/resources", file)).buffered() \ No newline at end of file diff --git a/src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt b/src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt deleted file mode 100644 index 802c0f4..0000000 --- a/src/jvmTest/kotlin/de.westnordost.osmfeatures/FeatureDictionaryTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -package de.westnordost.osmfeatures - -import kotlin.test.assertEquals -import kotlin.test.Test - -class FeatureDictionaryTest { - @Test - fun some_tests_with_real_data() { - val featureCollection = IDLocalizedFeatureCollection(LivePresetDataAccessAdapter()) - featureCollection.getAll(listOf("en")) - - val dictionary = FeatureDictionary(featureCollection, null) - - val matches = dictionary - .byTags(mapOf("amenity" to "studio")) - .forLocale("en") - .find() - assertEquals(1, matches.size) - assertEquals("Studio", matches[0].name) - - val matches2 = dictionary - .byTags(mapOf("amenity" to "studio", "studio" to "audio")) - .forLocale("en") - .find() - assertEquals(1, matches2.size) - assertEquals("Recording Studio", matches2[0].name) - - val matches3 = dictionary - .byTerm("Chinese Res") - .forLocale("en") - .find() - assertEquals(1, matches3.size) - assertEquals("Chinese Restaurant", matches3[0].name) - } -} diff --git a/src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt b/src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt deleted file mode 100644 index eb0d90e..0000000 --- a/src/jvmTest/kotlin/de.westnordost.osmfeatures/LivePresetDataAccessAdapter.kt +++ /dev/null @@ -1,18 +0,0 @@ -package de.westnordost.osmfeatures - -import java.net.URL -import okio.source - -class LivePresetDataAccessAdapter : ResourceAccessAdapter { - - override fun exists(name: String): Boolean = - name in listOf("presets.json", "de.json", "en.json", "en-GB.json") - - override fun open(name: String): okio.Source { - val url = URL(when(name) { - "presets.json" -> "https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json" - else -> "https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/translations/$name" - }) - return url.openStream().source() - } -} From 83168950b550fffb614d63768d85f7bf7a5cefc5 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 10 May 2024 23:30:06 +0200 Subject: [PATCH 79/98] should be internal --- .../kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt | 2 +- .../westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt index fbb773c..582470b 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt @@ -8,7 +8,7 @@ import kotlinx.serialization.json.* /** Parses this file * [...](https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json) * into map of id -> Feature. */ -class IDPresetsJsonParser(private var isSuggestion: Boolean = false) { +internal class IDPresetsJsonParser(private var isSuggestion: Boolean = false) { fun parse(source: Source): List = parse(Json.decodeFromSource(source)) diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt index 6e39e4e..c423867 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt @@ -10,7 +10,7 @@ import kotlinx.serialization.json.jsonPrimitive * https://github.com/openstreetmap/id-tagging-schema/tree/main/dist/translations * , given the base features are already parsed. */ -class IDPresetsTranslationJsonParser { +internal class IDPresetsTranslationJsonParser { fun parse( content: String, locale: String?, baseFeatures: Map From 6d1c037a554b7645df57991a4f131de4b0ad79ca Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 10 May 2024 23:32:51 +0200 Subject: [PATCH 80/98] fix android build --- .../{FeatureDictionary.kt => FeatureDictionary.android.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/androidMain/kotlin/de/westnordost/osmfeatures/{FeatureDictionary.kt => FeatureDictionary.android.kt} (100%) diff --git a/src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt b/src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.android.kt similarity index 100% rename from src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt rename to src/androidMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.android.kt From 4b0b7a8ab83cb5c39e6802143bb1d3e8e3fe643b Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sat, 11 May 2024 00:07:03 +0200 Subject: [PATCH 81/98] update readme --- CHANGELOG.md | 2 +- README.md | 106 +++++++++++++++++++++------------------------------ 2 files changed, 44 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdfbf10..63cca85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 6.0.0 -- The library is now a Kotlin Multiplatform library. This is a breaking API change. +The library is now a Kotlin Multiplatform library. This is a breaking API change. There is also no separate artifact for Android anymore. # 5.2 diff --git a/README.md b/README.md index 2680557..c9ac74e 100644 --- a/README.md +++ b/README.md @@ -1,105 +1,85 @@ # osmfeatures -A dictionary of OSM map features, accessible by terms and by tags, for Java and Android. +A Kotlin multiplatform dictionary of OSM map features, accessible by terms and by tags. Supported platforms are Android, JVM and iOS. -Requires Java 8. +Due to heavy use of indices, it is very fast. -## Copyright and License - -© 2019-2022 Tobias Zwick. This library is released under the terms of the [Apache License Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt). - -## Installation +It is currently used in [StreetComplete](https://github.com/streetcomplete/streetcomplete). -Add [`de.westnordost:osmfeatures:5.2`](https://mvnrepository.com/artifact/de.westnordost/osmfeatures/5.2) as a Maven dependency or download the jar from there. +## Copyright and License -For Android, use [`de.westnordost:osmfeatures-android:5.2`](https://mvnrepository.com/artifact/de.westnordost/osmfeatures-android/5.2). +© 2019-2024 Tobias Zwick. This library is released under the terms of the Apache License Version 2.0. ## Usage +Add [de.westnordost:osmfeatures:6.0.0](https://mvnrepository.com/artifact/de.westnordost/osmfeatures/5.2) as a Maven dependency or download the jar from there. + ### Get the data The data for the dictionary is not maintained in this repository. -It actually uses the [preset data from iD](https://github.com/openstreetmap/id-tagging-schema/blob/main/dist/presets.json) and [the translations of the presets](https://github.com/openstreetmap/id-tagging-schema/tree/main/dist/translations). -Just dump all the translations and the presets.json into the same directory. -Do not forget to give attribution to iD since you are using their data. - -If you use gradle as your build tool, the easiest way to get this data is to put this task into your `build.gradle` and either execute this task manually from time to time or make the build process depend on it (by adding `preBuild.dependsOn(downloadPresets)`): - -```groovy -import groovy.json.JsonSlurper - -task downloadPresets { - doLast { - def targetDir = "$projectDir/path/to/where/the/data/should/go" - def presetsUrl = new URL("https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json") - def contentsUrl = new URL("https://api.github.com/repos/openstreetmap/id-tagging-schema/contents/dist/translations") - - new File("$targetDir/presets.json").withOutputStream { it << presetsUrl.openStream() } - - def slurper = new JsonSlurper() - slurper.parse(contentsUrl, "UTF-8").each { - if(it.type == "file") { - def filename = it.name.substring(0, it.name.indexOf(".")) - def javaLanguageTag = Locale.forLanguageTag(filename.replace('@','-')).toLanguageTag() - def translationsUrl = new URL(it.download_url) - new File("$targetDir/${javaLanguageTag}.json").withOutputStream { it << translationsUrl.openStream() } - } - } - } -} -``` +It actually uses the [preset data from iD](https://github.com/openstreetmap/id-tagging-schema/blob/main/dist/presets.json), [its translations](https://github.com/openstreetmap/id-tagging-schema/tree/main/dist/translations) +and optionally additionally the brand preset data from the [name suggestion index](https://github.com/osmlab/name-suggestion-index). +Each are © iD contributors, licensed under the ISC license. + -In StreetComplete (app that uses this library), there is [UpdatePresetsTask.kt](https://github.com/streetcomplete/StreetComplete/blob/master/buildSrc/src/main/java/UpdatePresetsTask.kt) to do this. It's longer but it is maintained and takes care of some more edge cases. Also, take note of [UpdateNsiPresetsTask.kt](https://github.com/streetcomplete/StreetComplete/blob/master/buildSrc/src/main/java/UpdateNsiPresetsTask.kt) which fetches the [name suggestion index presets](https://github.com/osmlab/name-suggestion-index) (=brands). +So, just dump all the translations and the presets.json into the same directory. To be always +up-to-date, it is advisable to have an automatic build task that fetches the current version of the +presets from the repository. + +The app for which this library was developed (StreetComplete), uses the following tasks: +- [UpdatePresetsTask.kt](https://github.com/streetcomplete/StreetComplete/blob/master/buildSrc/src/main/java/UpdatePresetsTask.kt) to download presets and selected translations +- [UpdateNsiPresetsTask.kt](https://github.com/streetcomplete/StreetComplete/blob/master/buildSrc/src/main/java/UpdateNsiPresetsTask.kt) to download the brand presets from the [name suggestion index](https://github.com/osmlab/name-suggestion-index) ### Initialize dictionary -Point the dictionary to the directory where the data is located (see above). Use `FeatureDictionary` as a singleton. -```java -FeatureDictionary dictionary = FeatureDictionary.create("path/to/data")); +Point the dictionary to the directory where the data is located (see above). Use `FeatureDictionary` as a singleton, as initialization takes a moment (loading files, building indices). +```kotlin +val dictionary = FeatureDictionary.create(fileSystem, "path/to/data") ``` For Android, use -```java -FeatureDictionary dictionary = AndroidFeatureDictionary.create(assetManager, "path/within/assets/folder/to/data")); +```kotlin +val dictionary = FeatureDictionary.create(assetManager, "path/within/assets/folder/to/data") ``` -If brand features from the [name suggestion index](https://github.com/osmlab/name-suggestion-index) (see last paragraph in the previous "Get the data" section) should be included in the dictionary, you can specify the path to these presets as a further parameter. These will be loaded on-demand depending on for which countries you search for. +If brand features from the [name suggestion index](https://github.com/osmlab/name-suggestion-index) should be included in the dictionary, you can specify the path to these presets as a third parameter. These will be loaded on-demand depending on for which countries you search for. ### Find matches by tags -```java -List matches = dictionary - .byTags(Map.of("amenity", "bench")) // look for features that have the given tags + +```kotlin +val matches: List = dictionary + .byTags(mapOf("amenity" to "bench")) // look for features that have the given tags .forGeometry(GeometryType.POINT) // limit the search to features that may be points - .forLocale(Locale.GERMAN) // show results in German only, don't fall back to English or unlocalized results - .find(); + .forLocale("de") // show results in German only, don't fall back to English or unlocalized results + .find() -// prints "Parkbank" (or something like this) or index out of bounds exception -// if no preset for amenity=bench exists that is localized to German -println(matches.get(0).getName()); +// prints "Parkbank" (or something like this) +// or null if no preset for amenity=bench exists that is localized to German +println(matches[0]?.getName()) ``` ### Find matches by search word -```java -List matches = dictionary +```kotlin +val matches: List = dictionary .byTerm("Bank") // look for features matching "Bank" .forGeometry(GeometryType.AREA) // limit the search to features that may be areas - .forLocale(Locale.GERMAN, null) // show results in German or fall back to unlocalized results + .forLocale("de", null) // show results in German or fall back to unlocalized results // (brand features are usually not localized) .inCountry("DE") // also include things (brands) that only exist in Germany .limit(10) // return at most 10 entries - .find(); + .find() // result list will have matches with at least amenity=bank, but not amenity=bench because it is a point-feature +// if the dictionary contains also brand presets, e.g. "Deutsche Bank" will certainly also be amongst the results ``` ### Find by id -```java -Feature feature = dictionary +```kotlin +val match: Feature? = dictionary .byId("amenity/bank") - .forLocale(Locale.GERMAN, // show results in German, otherwise fall back to English etc. - Locale.ENGLISH, - null) + .forLocale("de", "en-US", null) // show results in German, otherwise fall back to American + // English or otherwise unlocalized results .inCountry("DE") // also include things (brands) that only exist in Germany - .get(); + .get() ``` From 2004dc1b5117128b20ba824f488dfd1c8ccd7c76 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sat, 11 May 2024 01:44:06 +0200 Subject: [PATCH 82/98] prettify parsers --- .../osmfeatures/IDPresetsJsonParser.kt | 49 +++++++------- .../IDPresetsTranslationJsonParser.kt | 67 ++++++++----------- .../de/westnordost/osmfeatures/JsonUtils.kt | 12 ---- .../westnordost/osmfeatures/JsonUtilsTest.kt | 23 ------- 4 files changed, 52 insertions(+), 99 deletions(-) delete mode 100644 src/commonTest/kotlin/de/westnordost/osmfeatures/JsonUtilsTest.kt diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt index 582470b..2c29d3d 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt @@ -1,7 +1,5 @@ package de.westnordost.osmfeatures -import de.westnordost.osmfeatures.JsonUtils.parseList -import de.westnordost.osmfeatures.JsonUtils.parseStringMap import kotlinx.io.Source import kotlinx.serialization.json.* @@ -20,7 +18,7 @@ internal class IDPresetsJsonParser(private var isSuggestion: Boolean = false) { json.mapNotNull { (key, value) -> parseFeature(key, value.jsonObject) } private fun parseFeature(id: String, p: JsonObject): BaseFeature? { - val tags = parseStringMap(p["tags"]?.jsonObject) + val tags = p["tags"]?.jsonObject?.mapValues { it.value.jsonPrimitive.content }.orEmpty() // drop features with * in key or value of tags (for now), because they never describe // a concrete thing, but some category of things. // TODO maybe drop this limitation @@ -28,34 +26,34 @@ internal class IDPresetsJsonParser(private var isSuggestion: Boolean = false) { // also dropping features with empty tags (generic point, line, relation) if (tags.isEmpty()) return null - val geometry = parseList(p["geometry"]?.jsonArray) { - GeometryType.valueOf(((it as JsonPrimitive).content).uppercase()) - } + val geometry = p["geometry"]?.jsonArray?.map { + GeometryType.valueOf(it.jsonPrimitive.content.uppercase()) + }.orEmpty() - val name = p["name"]?.jsonPrimitive?.content ?: "" - val icon = p["icon"]?.jsonPrimitive?.content - val imageURL = p["imageURL"]?.jsonPrimitive?.content - val names = parseList(p["aliases"]?.jsonArray) { it.jsonPrimitive.content }.toMutableList() - names.add(0, name) - val terms = parseList(p["terms"]?.jsonArray) { it.jsonPrimitive.content } + val name = p["name"]?.jsonPrimitive?.contentOrNull ?: "" + val icon = p["icon"]?.jsonPrimitive?.contentOrNull + val imageURL = p["imageURL"]?.jsonPrimitive?.contentOrNull + val names = buildList { + add(name) + addAll(p["aliases"]?.jsonArray?.map { it.jsonPrimitive.content }.orEmpty()) + } + val terms = p["terms"]?.jsonArray?.map { it.jsonPrimitive.content }.orEmpty() val locationSet = p["locationSet"]?.jsonObject val includeCountryCodes: List? val excludeCountryCodes: List? if (locationSet != null) { - includeCountryCodes = parseCountryCodes(locationSet["include"]?.jsonArray) - if (includeCountryCodes == null) return null - excludeCountryCodes = parseCountryCodes(locationSet["exclude"]?.jsonArray) - if (excludeCountryCodes == null) return null + includeCountryCodes = locationSet["include"]?.jsonArray?.parseCountryCodes() ?: return null + excludeCountryCodes = locationSet["exclude"]?.jsonArray?.parseCountryCodes() ?: return null } else { - includeCountryCodes = ArrayList(0) - excludeCountryCodes = ArrayList(0) + includeCountryCodes = emptyList() + excludeCountryCodes = emptyList() } val searchable = p["searchable"]?.jsonPrimitive?.booleanOrNull ?: true val matchScore = p["matchScore"]?.jsonPrimitive?.floatOrNull ?: 1.0f - val addTags = p["addTags"]?.let { parseStringMap(it.jsonObject)} ?: tags - val removeTags = p["removeTags"]?.let { parseStringMap(it.jsonObject)} ?: addTags + val addTags = p["addTags"]?.jsonObject?.mapValues { it.value.jsonPrimitive.content } ?: tags + val removeTags = p["removeTags"]?.jsonObject?.mapValues { it.value.jsonPrimitive.content } ?: addTags return BaseFeature( id = id, @@ -75,14 +73,13 @@ internal class IDPresetsJsonParser(private var isSuggestion: Boolean = false) { ) } - private fun parseCountryCodes(jsonList: JsonArray?): List? { - if(jsonList?.any { it is JsonArray } == true) { - return null - } - val list = parseList(jsonList) { it.jsonPrimitive.content } + private fun JsonArray.parseCountryCodes(): List? { + // for example a lat,lon pair to denote a location with radius. Not supported. + if (any { it is JsonArray }) return null + + val list = map { it.jsonPrimitive.content } val result = ArrayList(list.size) for (item in list) { - // for example a lat,lon pair to denote a location with radius. Not supported. val cc = item.uppercase() // don't need this, 001 stands for "whole world" if (cc == "001") continue diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt index c423867..40f7313 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt @@ -1,17 +1,14 @@ package de.westnordost.osmfeatures import kotlinx.io.Source -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.* /** Parses a file from * https://github.com/openstreetmap/id-tagging-schema/tree/main/dist/translations * , given the base features are already parsed. */ internal class IDPresetsTranslationJsonParser { - + // TODO locale nullable does not make sense?! fun parse( content: String, locale: String?, baseFeatures: Map ): List = @@ -25,53 +22,57 @@ internal class IDPresetsTranslationJsonParser { private fun parse( json: JsonObject, locale: String?, baseFeatures: Map ): List { - val languageKey = json.entries.iterator().next().key - val languageObject = json[languageKey] - ?: return emptyList() - val presetsContainerObject = languageObject.jsonObject["presets"] - ?: return emptyList() - val presetsObject = presetsContainerObject.jsonObject["presets"]?.jsonObject + val translations = json.jsonObject.entries.firstOrNull()?.value?.jsonObject + val presetsObject = translations + ?.get("presets")?.jsonObject + ?.get("presets")?.jsonObject ?: return emptyList() + val localizedFeatures = HashMap(presetsObject.size) presetsObject.entries.forEach { (key, value) -> val f = parseFeature(baseFeatures[key], locale, value.jsonObject) if (f != null) localizedFeatures[key] = f } + for (baseFeature in baseFeatures.values) { val names = baseFeature.names if (names.isEmpty()) continue - val name = names[0] + val name = names.first() val isPlaceholder = name.startsWith("{") && name.endsWith("}") if (!isPlaceholder) continue val placeholderId = name.substring(1, name.length - 1) val localizedFeature = localizedFeatures[placeholderId] ?: continue localizedFeatures[baseFeature.id] = LocalizedFeature( - baseFeature, - locale, - localizedFeature.names, - localizedFeature.terms + p = baseFeature, + locale = locale, + names = localizedFeature.names, + terms = localizedFeature.terms ) } - return ArrayList(localizedFeatures.values) + return localizedFeatures.values.toList() } private fun parseFeature(feature: BaseFeature?, locale: String?, localization: JsonObject): LocalizedFeature? { if (feature == null) return null - val name = localization["name"]?.jsonPrimitive?.content - if (name.isNullOrEmpty()) return null + val name = localization["name"]?.jsonPrimitive?.contentOrNull.orEmpty() - val namesArray = parseNewlineSeparatedList(localization["aliases"]?.jsonPrimitive?.content) - val names: MutableList = ArrayList(namesArray.size + 1) - names.addAll(namesArray) - names.remove(name) - names.add(0, name) + val aliases = localization["aliases"]?.jsonPrimitive?.content + .orEmpty() + .lineSequence() - val termsArray = parseCommaSeparatedList(localization["terms"]?.jsonPrimitive?.content) - val terms: MutableList = ArrayList(termsArray.size) - terms.addAll(termsArray) - terms.removeAll(names) + val names = (sequenceOf(name) + aliases) + .map { it.trim() } + .filter { it.isNotEmpty() } + .toList() + + val terms = localization["terms"]?.jsonPrimitive?.content + .orEmpty() + .splitToSequence(",") + .map { it.trim() } + .filter { it.isNotEmpty() } + .toList() return LocalizedFeature( p = feature, @@ -81,13 +82,3 @@ internal class IDPresetsTranslationJsonParser { ) } } - -private fun parseCommaSeparatedList(str: String?): Array { - if (str.isNullOrEmpty()) return emptyArray() - return str.split("\\s*,+\\s*".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() -} - -private fun parseNewlineSeparatedList(str: String?): Array { - if (str.isNullOrEmpty()) return emptyArray() - return str.split("\\s*[\\r\\n]+\\s*".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() -} \ No newline at end of file diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt index fb05bf6..4514b7c 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt @@ -4,18 +4,6 @@ import kotlinx.io.Source import kotlinx.io.readString import kotlinx.serialization.json.* -internal object JsonUtils { - - fun parseList(array: JsonArray?, t: (JsonElement) -> T): List { - return array?.mapNotNull { item -> t(item) }.orEmpty() - } - - fun parseStringMap(map: JsonObject?): Map { - if (map == null) return HashMap(1) - return map.map { (key, value) -> key to value.jsonPrimitive.content}.toMap().toMutableMap() - } -} - // TODO This can hopefully be replaced with a function from kotlinx-serialization soon @OptIn(ExperimentalStdlibApi::class) internal inline fun Json.decodeFromSource(source: Source): T = diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/JsonUtilsTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/JsonUtilsTest.kt deleted file mode 100644 index 6b146cc..0000000 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/JsonUtilsTest.kt +++ /dev/null @@ -1,23 +0,0 @@ -package de.westnordost.osmfeatures - -import kotlinx.serialization.json.JsonArray -import kotlin.test.assertEquals -import kotlin.test.Test -import de.westnordost.osmfeatures.JsonUtils.parseList -import de.westnordost.osmfeatures.JsonUtils.parseStringMap - -class JsonUtilsTest { - @Test - fun parseList_with_null_json_array() { - assertEquals(0, parseList(null) { obj -> obj }.size) - } - - @Test - fun parseList_with_empty_json_array() { - assertEquals(0, parseList(JsonArray(listOf())) { obj -> obj }.size) - } - @Test - fun parseStringMap_with_null_json_map() { - assertEquals(0, parseStringMap(null).size) - } -} From 0c99a57ae2f99e00f330191fff436e438be7b19f Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sat, 11 May 2024 01:53:51 +0200 Subject: [PATCH 83/98] more parser stuff --- .../osmfeatures/IDPresetsJsonParser.kt | 41 +++++++++---------- .../IDPresetsTranslationJsonParser.kt | 9 ++-- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt index 2c29d3d..d54202c 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.json.* /** Parses this file * [...](https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json) * into map of id -> Feature. */ -internal class IDPresetsJsonParser(private var isSuggestion: Boolean = false) { +internal class IDPresetsJsonParser(private val isSuggestion: Boolean = false) { fun parse(source: Source): List = parse(Json.decodeFromSource(source)) @@ -22,7 +22,7 @@ internal class IDPresetsJsonParser(private var isSuggestion: Boolean = false) { // drop features with * in key or value of tags (for now), because they never describe // a concrete thing, but some category of things. // TODO maybe drop this limitation - if (anyKeyOrValueContainsWildcard(tags)) return null + if (tags.anyKeyOrValueContainsWildcard()) return null // also dropping features with empty tags (generic point, line, relation) if (tags.isEmpty()) return null @@ -72,27 +72,24 @@ internal class IDPresetsJsonParser(private var isSuggestion: Boolean = false) { removeTags = removeTags ) } +} - private fun JsonArray.parseCountryCodes(): List? { - // for example a lat,lon pair to denote a location with radius. Not supported. - if (any { it is JsonArray }) return null - - val list = map { it.jsonPrimitive.content } - val result = ArrayList(list.size) - for (item in list) { - val cc = item.uppercase() - // don't need this, 001 stands for "whole world" - if (cc == "001") continue - // ISO-3166-2 codes are supported but not m49 code such as "150" or geojsons like "city_national_bank_fl.geojson" - if (!cc.matches("[A-Z]{2}(-[A-Z0-9]{1,3})?".toRegex())) return null - result.add(cc) - } - return result - } - - private fun anyKeyOrValueContainsWildcard(map: Map): Boolean { - return map.any { (key, value) -> key.contains("*") || value.contains("*")} +private fun JsonArray.parseCountryCodes(): List? { + // for example a lat,lon pair to denote a location with radius. Not supported. + if (any { it is JsonArray }) return null + + val list = map { it.jsonPrimitive.content } + val result = ArrayList(list.size) + for (item in list) { + val cc = item.uppercase() + // don't need this, 001 stands for "whole world" + if (cc == "001") continue + // ISO-3166-2 codes are supported but not m49 code such as "150" or geojsons like "city_national_bank_fl.geojson" + if (!cc.matches("[A-Z]{2}(-[A-Z0-9]{1,3})?".toRegex())) return null + result.add(cc) } + return result } - +private fun Map.anyKeyOrValueContainsWildcard(): Boolean = + any { (key, value) -> key.contains("*") || value.contains("*") } \ No newline at end of file diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt index 40f7313..e9c9cff 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt @@ -23,21 +23,18 @@ internal class IDPresetsTranslationJsonParser { json: JsonObject, locale: String?, baseFeatures: Map ): List { val translations = json.jsonObject.entries.firstOrNull()?.value?.jsonObject - val presetsObject = translations ?.get("presets")?.jsonObject ?.get("presets")?.jsonObject ?: return emptyList() - val localizedFeatures = HashMap(presetsObject.size) - presetsObject.entries.forEach { (key, value) -> + val localizedFeatures = HashMap(translations.size) + translations.entries.forEach { (key, value) -> val f = parseFeature(baseFeatures[key], locale, value.jsonObject) if (f != null) localizedFeatures[key] = f } for (baseFeature in baseFeatures.values) { - val names = baseFeature.names - if (names.isEmpty()) continue - val name = names.first() + val name = baseFeature.names.firstOrNull() ?: continue val isPlaceholder = name.startsWith("{") && name.endsWith("}") if (!isPlaceholder) continue val placeholderId = name.substring(1, name.length - 1) From aeaee489e75dece196170636e9828c7ab152989e Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sat, 11 May 2024 01:56:38 +0200 Subject: [PATCH 84/98] remove todo --- .../de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt index e9c9cff..b37ef62 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt @@ -8,7 +8,6 @@ import kotlinx.serialization.json.* * , given the base features are already parsed. */ internal class IDPresetsTranslationJsonParser { - // TODO locale nullable does not make sense?! fun parse( content: String, locale: String?, baseFeatures: Map ): List = From 71b71436c0d70681c6f36c24e569d8db6662f22e Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 13 May 2024 14:30:16 +0200 Subject: [PATCH 85/98] add test also for brand data --- .../osmfeatures/IDPresetsJsonParserTest.kt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt index ad6ff39..95597e3 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt @@ -79,7 +79,7 @@ class IDPresetsJsonParserTest { } @Test - fun parse_some_real_data() = runBlocking { + fun parse_real_data() = runBlocking { val client = HttpClient(CIO) { expectSuccess = true } val presets = client @@ -91,6 +91,19 @@ class IDPresetsJsonParserTest { assertTrue(features.size > 1000) } + @Test + fun parse_real_brand_data() = runBlocking { + val client = HttpClient(CIO) { expectSuccess = true } + + val presets = client + .get("https://github.com/osmlab/name-suggestion-index/raw/main/dist/presets/nsi-id-presets.json") + .bodyAsText() + + val features = IDPresetsJsonParser().parse(presets) + // should not crash etc + assertTrue(features.size > 1000) + } + private fun parseResource(file: String): List = useResource(file) { IDPresetsJsonParser().parse(it) } } From ab9037e9df1041c1d0f0cc046f43213fa910f8f1 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 13 May 2024 15:10:20 +0200 Subject: [PATCH 86/98] remove unnecessary map --- .../kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt index f131308..bc694a5 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt @@ -446,7 +446,8 @@ private fun Feature.getSearchableNames(): Sequence = sequence { } private fun isInCountryCodes(countryCode: String, countryCodes: List): Boolean = - countryCode in countryCodes || countryCode.substringBefore('-') in countryCodes.map { it } + countryCode in countryCodes || + countryCode.substringBefore('-') in countryCodes private fun getParentCategoryIds(id: String): Sequence = sequence { var lastIndex = id.length From 29bbe6c8b873db6e6eaa1c58f5ca3b3638392b54 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 13 May 2024 17:37:36 +0200 Subject: [PATCH 87/98] don't create a new regex every time that function is called --- .../kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt index d54202c..6b317a8 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt @@ -74,6 +74,8 @@ internal class IDPresetsJsonParser(private val isSuggestion: Boolean = false) { } } +private val ISO3166_2 = Regex("[A-Z]{2}(-[A-Z0-9]{1,3})?") + private fun JsonArray.parseCountryCodes(): List? { // for example a lat,lon pair to denote a location with radius. Not supported. if (any { it is JsonArray }) return null @@ -85,7 +87,7 @@ private fun JsonArray.parseCountryCodes(): List? { // don't need this, 001 stands for "whole world" if (cc == "001") continue // ISO-3166-2 codes are supported but not m49 code such as "150" or geojsons like "city_national_bank_fl.geojson" - if (!cc.matches("[A-Z]{2}(-[A-Z0-9]{1,3})?".toRegex())) return null + if (!cc.matches(ISO3166_2)) return null result.add(cc) } return result From dad3413570ea3d2afd63d24ec8523456e33e585d Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 13 May 2024 17:52:49 +0200 Subject: [PATCH 88/98] naming --- .../de/westnordost/osmfeatures/{Locale.kt => Locale.ios.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/iosMain/kotlin/de/westnordost/osmfeatures/{Locale.kt => Locale.ios.kt} (100%) diff --git a/src/iosMain/kotlin/de/westnordost/osmfeatures/Locale.kt b/src/iosMain/kotlin/de/westnordost/osmfeatures/Locale.ios.kt similarity index 100% rename from src/iosMain/kotlin/de/westnordost/osmfeatures/Locale.kt rename to src/iosMain/kotlin/de/westnordost/osmfeatures/Locale.ios.kt From 89f893a61650f5de9edf814ad1802f16397de6bf Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 13 May 2024 18:32:21 +0200 Subject: [PATCH 89/98] rename file --- .../de/westnordost/osmfeatures/{Locale.kt => Locale.jvm.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/jvmMain/kotlin/de/westnordost/osmfeatures/{Locale.kt => Locale.jvm.kt} (100%) diff --git a/src/jvmMain/kotlin/de/westnordost/osmfeatures/Locale.kt b/src/jvmMain/kotlin/de/westnordost/osmfeatures/Locale.jvm.kt similarity index 100% rename from src/jvmMain/kotlin/de/westnordost/osmfeatures/Locale.kt rename to src/jvmMain/kotlin/de/westnordost/osmfeatures/Locale.jvm.kt From 7354d42de1f94999897794e8948030990eac9476 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 13 May 2024 19:13:11 +0200 Subject: [PATCH 90/98] add test and fix --- .../osmfeatures/IDPresetsJsonParser.kt | 27 ++++++++++--------- .../osmfeatures/IDPresetsJsonParserTest.kt | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt index 6b317a8..e12d7c9 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.json.* /** Parses this file * [...](https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.json) - * into map of id -> Feature. */ + * into list of Features. */ internal class IDPresetsJsonParser(private val isSuggestion: Boolean = false) { fun parse(source: Source): List = @@ -14,8 +14,11 @@ internal class IDPresetsJsonParser(private val isSuggestion: Boolean = false) { fun parse(content: String): List = parse(Json.decodeFromString(content)) - private fun parse(json: JsonObject): List = - json.mapNotNull { (key, value) -> parseFeature(key, value.jsonObject) } + private fun parse(json: JsonObject): List { + // the presets in the nsi presets are one level down (in preset object) + val root = json["presets"]?.jsonObject ?: json + return root.mapNotNull { (key, value) -> parseFeature(key, value.jsonObject) } + } private fun parseFeature(id: String, p: JsonObject): BaseFeature? { val tags = p["tags"]?.jsonObject?.mapValues { it.value.jsonPrimitive.content }.orEmpty() @@ -39,16 +42,14 @@ internal class IDPresetsJsonParser(private val isSuggestion: Boolean = false) { } val terms = p["terms"]?.jsonArray?.map { it.jsonPrimitive.content }.orEmpty() - val locationSet = p["locationSet"]?.jsonObject - val includeCountryCodes: List? - val excludeCountryCodes: List? - if (locationSet != null) { - includeCountryCodes = locationSet["include"]?.jsonArray?.parseCountryCodes() ?: return null - excludeCountryCodes = locationSet["exclude"]?.jsonArray?.parseCountryCodes() ?: return null - } else { - includeCountryCodes = emptyList() - excludeCountryCodes = emptyList() - } + val include = p["locationSet"]?.jsonObject?.get("include")?.jsonArray + val exclude = p["locationSet"]?.jsonObject?.get("exclude")?.jsonArray + val includeCountryCodes: List = + if (include != null) include.parseCountryCodes() ?: return null + else emptyList() + val excludeCountryCodes: List = + if (exclude != null) exclude.parseCountryCodes() ?: return null + else emptyList() val searchable = p["searchable"]?.jsonPrimitive?.booleanOrNull ?: true val matchScore = p["matchScore"]?.jsonPrimitive?.floatOrNull ?: 1.0f diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt index 95597e3..ced6e77 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParserTest.kt @@ -101,7 +101,7 @@ class IDPresetsJsonParserTest { val features = IDPresetsJsonParser().parse(presets) // should not crash etc - assertTrue(features.size > 1000) + assertTrue(features.size > 20000) } private fun parseResource(file: String): List = From 4fdd7a2b00fdc95e721b3f33951817ab6ea13dd0 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 13 May 2024 19:55:06 +0200 Subject: [PATCH 91/98] declare io exception can be thrown --- .../kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt | 2 ++ .../kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt | 2 ++ .../kotlin/de/westnordost/osmfeatures/ResourceAccessAdapter.kt | 2 ++ .../de/westnordost/osmfeatures/LivePresetDataAccessAdapter.kt | 2 ++ src/commonTest/kotlin/de/westnordost/osmfeatures/TestUtils.kt | 2 ++ 5 files changed, 10 insertions(+) diff --git a/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt b/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt index ca34aa6..59b6e9c 100644 --- a/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt +++ b/src/androidMain/kotlin/de/westnordost/osmfeatures/AssetManagerAccess.kt @@ -1,6 +1,7 @@ package de.westnordost.osmfeatures import android.content.res.AssetManager +import kotlinx.io.IOException import kotlinx.io.Source import kotlinx.io.asSource import kotlinx.io.buffered @@ -14,6 +15,7 @@ internal class AssetManagerAccess( override fun exists(name: String): Boolean = assetManager.list(basePath)?.contains(name) ?: false + @Throws(IOException::class) override fun open(name: String): Source = assetManager.open(basePath + File.separator + name).asSource().buffered() } \ No newline at end of file diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt index d5ece28..9e94d41 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/FileSystemAccess.kt @@ -1,5 +1,6 @@ package de.westnordost.osmfeatures +import kotlinx.io.IOException import kotlinx.io.Source import kotlinx.io.buffered import kotlinx.io.files.FileSystem @@ -19,6 +20,7 @@ class FileSystemAccess( override fun exists(name: String): Boolean = fileSystem.exists(Path(basePath, name)) + @Throws(IOException::class) override fun open(name: String): Source = fileSystem.source(Path(basePath, name)).buffered() } diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/ResourceAccessAdapter.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/ResourceAccessAdapter.kt index 584aa9a..d71f1ba 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/ResourceAccessAdapter.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/ResourceAccessAdapter.kt @@ -1,9 +1,11 @@ package de.westnordost.osmfeatures +import kotlinx.io.IOException import kotlinx.io.Source interface ResourceAccessAdapter { fun exists(name: String): Boolean + @Throws(IOException::class) fun open(name: String): Source } \ No newline at end of file diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.kt index 866b62e..3d33385 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/LivePresetDataAccessAdapter.kt @@ -6,6 +6,7 @@ import io.ktor.client.request.* import io.ktor.client.statement.* import kotlinx.coroutines.runBlocking import kotlinx.io.Buffer +import kotlinx.io.IOException import kotlinx.io.Source class LivePresetDataAccessAdapter : ResourceAccessAdapter { @@ -16,6 +17,7 @@ class LivePresetDataAccessAdapter : ResourceAccessAdapter { override fun exists(name: String): Boolean = name in listOf("presets.json", "de.json", "en.json", "en-GB.json") + @Throws(IOException::class) override fun open(name: String): Source { val url = when(name) { "presets.json" -> "$baseUrl/presets.json" diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/TestUtils.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/TestUtils.kt index fedb958..2da523c 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/TestUtils.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/TestUtils.kt @@ -1,5 +1,6 @@ package de.westnordost.osmfeatures +import kotlinx.io.IOException import kotlinx.io.Source import kotlinx.io.buffered import kotlinx.io.files.Path @@ -9,5 +10,6 @@ import kotlinx.io.files.SystemFileSystem fun useResource(file: String, block: (Source) -> R): R = resource(file).use { block(it) } +@Throws(IOException::class) fun resource(file: String): Source = SystemFileSystem.source(Path("src/commonTest/resources", file)).buffered() \ No newline at end of file From 5b82c63aafc949695ad6c2c89bfd04bceebc13b0 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 14 May 2024 01:04:49 +0200 Subject: [PATCH 92/98] make getters public --- .../osmfeatures/FeatureDictionary.kt | 293 +++++++++--------- .../osmfeatures/IDPresetsJsonParser.kt | 5 +- 2 files changed, 152 insertions(+), 146 deletions(-) diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt index bc694a5..0773e9a 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt @@ -24,29 +24,90 @@ class FeatureDictionary internal constructor( //region Get by id - /** Find feature by id */ + /** Builder to find a feature by id. See [getById] */ fun byId(id: String) = QueryByIdBuilder(id) - private fun getById( + /** + * Returns the feature associated with the given id or `null` if it does not exist + * + * @param id + * feature id + * + * @param locales + * Optional. List of IETF language tags of languages in which the result should be localized. + * + * Several languages can be specified to each fall back to if a translation does not exist in + * the locale before that. For example, specify `listOf("ca-ES","es", null)` if results in + * Catalan are preferred, Spanish is also fine or otherwise use unlocalized results (`null`). + * + * Defaults to `listOf(, null)`, i.e. unlocalized results are + * included by default. (Brand features are usually not localized.) + * + * @param country + * Optional. ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of + * the country/state the element is in. + * If `null`, will only return matches that are *not* county-specific. + * */ + fun getById( id: String, locales: List? = null, - countryCode: String? = null + country: String? = null ): Feature? = featureCollection.get(id, locales ?: listOf(defaultLocale(), null)) - ?: brandFeatureCollection?.get(id, dissectCountryCode(countryCode)) + ?: brandFeatureCollection?.get(id, dissectCountryCode(country)) //endregion //region Query by tags - /** Find matches by a set of tags */ + /** Builder to find matches by a set of tags. See [getByTags] */ fun byTags(tags: Map) = QueryByTagBuilder(tags) - private fun getByTags( + /** + * Search for features by a set of tags. + * + * @param tags feature tags + * + * @param geometry + * Optional. If not `null`, only returns features that match the given geometry type. + * + * @param locales + * Optional. List of IETF language tags of languages in which the result should be localized. + * + * Several languages can be specified to each fall back to if a translation does not exist in + * the locale before that. For example, specify `listOf("ca-ES","es", null)` if results in + * Catalan are preferred, Spanish is also fine or otherwise use unlocalized results (`null`). + * + * Defaults to `listOf(, null)`, i.e. unlocalized results are + * included by default. (Brand features are usually not localized.) + * + * @param country + * Optional. ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of + * the country/state the element is in. + * If `null`, will only return matches that are *not* county-specific. + * + * @param isSuggestion + * Optional. `true` to *only* include suggestions, `false` to *not* include suggestions + * or `null` to include any in the result. + * Suggestions are brands, like 7-Eleven, Santander etc. + * + * @return + * A list of dictionary entries that match the given tags or an empty list if nothing is found. + * + * For a set of tags that match a less specific feature and a more specific feature, only the + * more specific feature is returned. + * E.g. `amenity=doctors` + `healthcare:speciality=cardiology` matches *only* a Cardiologist, + * not a Doctor's Office in general. + * + * In rare cases, a set of tags may match multiple primary features, such as for + * tag combinations like `shop=deli` + `amenity=cafe`. This somewhat frowned upon tagging + * practice is the only reason why this method returns a list. + * */ + fun getByTags( tags: Map, - geometry: GeometryType? = null, locales: List? = null, - countryCode: String? = null, + country: String? = null, + geometry: GeometryType? = null, isSuggestion: Boolean? = null ): List { if (tags.isEmpty()) return emptyList() @@ -58,10 +119,10 @@ class FeatureDictionary internal constructor( foundFeatures.addAll(getTagsIndex(localesOrDefault).getAll(tags)) } if (isSuggestion == null || isSuggestion) { - val countryCodes = dissectCountryCode(countryCode) + val countryCodes = dissectCountryCode(country) foundFeatures.addAll(getBrandTagsIndex(countryCodes).getAll(tags)) } - foundFeatures.removeAll { !it.matches(geometry, countryCode) } + foundFeatures.removeAll { !it.matches(geometry, country) } if (foundFeatures.size > 1) { // only return of each category the most specific thing. I.e. will return @@ -107,15 +168,50 @@ class FeatureDictionary internal constructor( //region Query by term - /** Find matches by given search word */ + /** Builder to find matches by given search word. See [getByTerm] */ fun byTerm(term: String) = QueryByTermBuilder(term) - private fun getByTerm( + /** + * Search for features by a search term. + * + * @param search The search term + * + * @param geometry + * Optional. If not `null`, only returns features that match the given geometry type. + * + * @param locales + * Optional. List of IETF language tags of languages in which the result should be localized. + * + * Several languages can be specified to each fall back to if a translation does not exist in + * the locale before that. For example, specify `listOf("ca-ES","es", null)` if results in + * Catalan are preferred, Spanish is also fine or otherwise use unlocalized results (`null`). + * + * Defaults to `listOf(, null)`, i.e. unlocalized results are + * included by default. (Brand features are usually not localized.) + * + * @param country + * Optional. ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of + * the country/state the element is in. + * If `null`, will only return matches that are *not* county-specific. + * + * @param isSuggestion + * Optional. `true` to *only* include suggestions, `false` to *not* include suggestions + * or `null` to include any in the result. + * Suggestions are brands, like 7-Eleven, Santander etc. + * + * @return + * A sequence of dictionary entries that match the search, or an empty sequence list if nothing + * is found. + * + * Results are broadly sorted in this order: Matches with names, then with brand names, then + * with terms (keywords), then with tag values. + * */ + fun getByTerm( search: String, - geometry: GeometryType?, - locales: List?, - countryCode: String?, - isSuggestion: Boolean? + locales: List? = null, + country: String? = null, + geometry: GeometryType? = null, + isSuggestion: Boolean? = null ): Sequence { val canonicalSearch = search.canonicalize() @@ -164,7 +260,7 @@ class FeatureDictionary internal constructor( } if (isSuggestion == null || isSuggestion) { // b. matches with brand names second - val countryCodes = dissectCountryCode(countryCode) + val countryCodes = dissectCountryCode(country) yieldAll( getBrandNamesIndex(countryCodes).getAll(canonicalSearch).sortedWith(sortNames) ) @@ -183,7 +279,7 @@ class FeatureDictionary internal constructor( } } .distinct() - .filter { it.matches(geometry, countryCode) } + .filter { it.matches(geometry, country) } } //endregion @@ -257,148 +353,59 @@ class FeatureDictionary internal constructor( //region Query builders inner class QueryByIdBuilder(private val id: String) { - private var locale: List? = null - private var countryCode: String? = null - - /** - * Sets the locale(s) in which to present the results as IETF language tags - * - * You can specify several locales in a row to each fall back to if a translation does not - * exist in the locale before that. For example `["ca-ES","es-ES"]` if you prefer results - * in Catalan, but Spanish is also fine. - * - * `null` means to include unlocalized results. - * - * If nothing is specified, it defaults to `[, null]`, - * i.e. unlocalized results are included by default. - */ - fun forLocale(vararg locales: String?): QueryByIdBuilder { - this.locale = locales.toList() - return this - } + private var locales: List? = null + private var country: String? = null - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByIdBuilder { - this.countryCode = countryCode - return this - } + fun forLocale(vararg locales: String?): QueryByIdBuilder = + apply { this.locales = locales.toList() } - /** Returns the feature associated with the given id or `null` if it does not exist */ - fun get(): Feature? = getById(id, locale, countryCode) + fun inCountry(country: String?): QueryByIdBuilder = + apply { this.country = country } + + fun get(): Feature? = getById(id, locales, country) } inner class QueryByTagBuilder(private val tags: Map) { - private var geometryType: GeometryType? = null - private var locale: List? = null - private var suggestion: Boolean? = null - private var countryCode: String? = null - - /** Sets for which geometry type to look. If not set or `null`, any will match. */ - fun forGeometry(geometryType: GeometryType): QueryByTagBuilder { - this.geometryType = geometryType - return this - } + private var geometry: GeometryType? = null + private var locales: List? = null + private var isSuggestion: Boolean? = null + private var country: String? = null - /** - * Sets the locale(s) in which to present the results as IETF language tags - * - * You can specify several locales in a row to each fall back to if a translation does not - * exist in the locale before that. For example `["ca-ES","es-ES"]` if you prefer results - * in Catalan, but Spanish is also fine. - * - * `null` means to include unlocalized results. - * - * If nothing is specified, it defaults to `[, null]`, - * i.e. unlocalized results are included by default. - */ - fun forLocale(vararg locales: String?): QueryByTagBuilder { - this.locale = locales.toList() - return this - } + fun forGeometry(geometry: GeometryType): QueryByTagBuilder = + apply { this.geometry = geometry } - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByTagBuilder { - this.countryCode = countryCode - return this - } + fun forLocale(vararg locales: String?): QueryByTagBuilder = + apply { this.locales = locales.toList() } - /** Set whether to only include suggestions (=true) or to not include suggestions (=false). - * Suggestions are brands, like 7-Eleven. */ - fun isSuggestion(suggestion: Boolean?): QueryByTagBuilder { - this.suggestion = suggestion - return this - } + fun inCountry(country: String?): QueryByTagBuilder = + apply { this.country = country } - /** Returns a list of dictionary entries that match or an empty list if nothing is - * found. - * - * In rare cases, a set of tags may match multiple primary features, such as for - * tag combinations like `shop=deli` + `amenity=cafe`, so, this is why - * it is a list. */ - fun find(): List = getByTags(tags, geometryType, locale, countryCode, suggestion) + fun isSuggestion(isSuggestion: Boolean?): QueryByTagBuilder = + apply { this.isSuggestion = isSuggestion } + + fun find(): List = getByTags(tags, locales, country, geometry, isSuggestion) } inner class QueryByTermBuilder(private val term: String) { - private var geometryType: GeometryType? = null - private var locale: List? = null - private var suggestion: Boolean? = null - private var limit = 50 - private var countryCode: String? = null - - /** Sets for which geometry type to look. If not set or `null`, any will match. */ - fun forGeometry(geometryType: GeometryType): QueryByTermBuilder { - this.geometryType = geometryType - return this - } + private var geometry: GeometryType? = null + private var locales: List? = null + private var isSuggestion: Boolean? = null + private var country: String? = null - /** - * Sets the locale(s) in which to present the results as IETF language tags - * - * You can specify several locales in a row to each fall back to if a translation does not - * exist in the locale before that. For example `["ca-ES","es-ES"]` if you prefer results - * in Catalan, but Spanish is also fine. - * - * `null` means to include unlocalized results. - * - * If nothing is specified, it defaults to `[, null]`, - * i.e. unlocalized results are included by default. - */ - fun forLocale(vararg locales: String?): QueryByTermBuilder { - this.locale = locales.toList() - return this - } + fun forGeometry(geometryType: GeometryType): QueryByTermBuilder = + apply { this.geometry = geometryType } - /** the ISO 3166-1 alpha-2 country code (e.g. "US") or the ISO 3166-2 (e.g. "US-NY") of the - * country/state the element is in. If not specified, will only return matches that are not - * county-specific. */ - fun inCountry(countryCode: String?): QueryByTermBuilder { - this.countryCode = countryCode - return this - } + fun forLocale(vararg locales: String?): QueryByTermBuilder = + apply { this.locales = locales.toList() } - /** Set whether to only include suggestions (=true) or to not include suggestions (=false). - * Suggestions are brands, like 7-Eleven. */ - fun isSuggestion(suggestion: Boolean?): QueryByTermBuilder { - this.suggestion = suggestion - return this - } + fun inCountry(countryCode: String?): QueryByTermBuilder = + apply { this.country = countryCode } - /** limit how many results to return at most. Default is 50, -1 for unlimited. */ - fun limit(limit: Int): QueryByTermBuilder { - this.limit = limit - return this - } + fun isSuggestion(suggestion: Boolean?): QueryByTermBuilder = + apply { this.isSuggestion = suggestion } - /** Returns a list of dictionary entries that match or an empty list if nothing is - * found.

- * Results are sorted mainly in this order: Matches with names, with brand names, then - * matches with terms (keywords). */ - fun find(): List = - getByTerm(term, geometryType, locale, countryCode, suggestion).take(limit).toList() + fun find(): Sequence = + getByTerm(term, locales, country, geometry, isSuggestion) } //endregion diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt index e12d7c9..4e319db 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsJsonParser.kt @@ -24,7 +24,6 @@ internal class IDPresetsJsonParser(private val isSuggestion: Boolean = false) { val tags = p["tags"]?.jsonObject?.mapValues { it.value.jsonPrimitive.content }.orEmpty() // drop features with * in key or value of tags (for now), because they never describe // a concrete thing, but some category of things. - // TODO maybe drop this limitation if (tags.anyKeyOrValueContainsWildcard()) return null // also dropping features with empty tags (generic point, line, relation) if (tags.isEmpty()) return null @@ -44,10 +43,10 @@ internal class IDPresetsJsonParser(private val isSuggestion: Boolean = false) { val include = p["locationSet"]?.jsonObject?.get("include")?.jsonArray val exclude = p["locationSet"]?.jsonObject?.get("exclude")?.jsonArray - val includeCountryCodes: List = + val includeCountryCodes = if (include != null) include.parseCountryCodes() ?: return null else emptyList() - val excludeCountryCodes: List = + val excludeCountryCodes = if (exclude != null) exclude.parseCountryCodes() ?: return null else emptyList() From 2f5c4e415ca7128498cd13b1cab15f2be2f9cf20 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 14 May 2024 01:16:54 +0200 Subject: [PATCH 93/98] update readme --- README.md | 54 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index c9ac74e..91c33df 100644 --- a/README.md +++ b/README.md @@ -44,14 +44,17 @@ val dictionary = FeatureDictionary.create(assetManager, "path/within/assets/fold If brand features from the [name suggestion index](https://github.com/osmlab/name-suggestion-index) should be included in the dictionary, you can specify the path to these presets as a third parameter. These will be loaded on-demand depending on for which countries you search for. +Translations will also be loaded on demand when first querying features using a certain locale. + ### Find matches by tags ```kotlin -val matches: List = dictionary - .byTags(mapOf("amenity" to "bench")) // look for features that have the given tags - .forGeometry(GeometryType.POINT) // limit the search to features that may be points - .forLocale("de") // show results in German only, don't fall back to English or unlocalized results - .find() +val matches = dictionary.getByTags( + tags = mapOf("amenity" to "bench"), // look for features that have the given tags + locales = listOf("de"), // show results in German only, don't fall back to English or unlocalized results + geometry = GeometryType.POINT, // limit the search to features that may be points +) + // prints "Parkbank" (or something like this) // or null if no preset for amenity=bench exists that is localized to German @@ -61,25 +64,38 @@ println(matches[0]?.getName()) ### Find matches by search word ```kotlin -val matches: List = dictionary - .byTerm("Bank") // look for features matching "Bank" - .forGeometry(GeometryType.AREA) // limit the search to features that may be areas - .forLocale("de", null) // show results in German or fall back to unlocalized results +val matches = dictionary.getByTerm( + term = "Bank", // look for features matching "Bank" + locales = listOf("de", null), // show results in German or fall back to unlocalized results // (brand features are usually not localized) - .inCountry("DE") // also include things (brands) that only exist in Germany - .limit(10) // return at most 10 entries - .find() -// result list will have matches with at least amenity=bank, but not amenity=bench because it is a point-feature + country = "DE", // also include things (brands) that only exist in Germany + geometry = GeometryType.AREA, // limit the search to features that may be areas +) +// result sequence will have matches with at least amenity=bank, but not amenity=bench because it is a point-feature // if the dictionary contains also brand presets, e.g. "Deutsche Bank" will certainly also be amongst the results ``` ### Find by id ```kotlin -val match: Feature? = dictionary - .byId("amenity/bank") - .forLocale("de", "en-US", null) // show results in German, otherwise fall back to American - // English or otherwise unlocalized results - .inCountry("DE") // also include things (brands) that only exist in Germany - .get() +val match = dictionary.getById( + id = "amenity/bank", + locales = listOf("de", "en-US", null), // show results in German, otherwise fall back to American + // English or otherwise unlocalized results + country = "DE", // also include things (brands) that only exist in Germany +) ``` + +### Builders + +For a more convenient interface on Java, the above functions continue to be available as builders, +e.g. + +```java +List matches = dictionary + .byTags(Map.of("amenity", "bench")) + .forGeometry(GeometryType.POINT) + .forLocale("de") + .find(); +``` + From cc75a24c20dcfc7294a36d418eca426614b211b5 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 14 May 2024 01:29:56 +0200 Subject: [PATCH 94/98] fix building names and terms index --- .../kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt index 0773e9a..4852d81 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt @@ -18,8 +18,8 @@ class FeatureDictionary internal constructor( init { // build indices for default locale getTagsIndex(listOf(defaultLocale(), null)) - getNamesIndex(listOf(defaultLocale())) - getTermsIndex(listOf(defaultLocale())) + getNamesIndex(listOf(defaultLocale(), null)) + getTermsIndex(listOf(defaultLocale(), null)) } //region Get by id From 3e4715a9e89f8d28dbd9e730283220e35084c762 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 14 May 2024 01:53:41 +0200 Subject: [PATCH 95/98] fix tests --- .../de/westnordost/osmfeatures/JsonUtils.kt | 2 +- .../osmfeatures/FeatureDictionaryTest.kt | 120 ++++++++++-------- 2 files changed, 67 insertions(+), 55 deletions(-) diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt index 4514b7c..45cada9 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt @@ -4,7 +4,7 @@ import kotlinx.io.Source import kotlinx.io.readString import kotlinx.serialization.json.* -// TODO This can hopefully be replaced with a function from kotlinx-serialization soon +// TODO This can hopefully be replaced with a function from kotlinx-serialization soon that streams the data @OptIn(ExperimentalStdlibApi::class) internal inline fun Json.decodeFromSource(source: Source): T = decodeFromString(source.use { it.readString() }) \ No newline at end of file diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt index 8b81bea..d45aa02 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt @@ -311,14 +311,17 @@ class FeatureDictionaryTest { @Test fun find_no_entry_by_name() { - assertEquals(emptyList(), dictionary(bakery).byTerm("Supermarkt").find()) + assertEquals( + emptyList(), + dictionary(bakery).byTerm("Supermarkt").find().toList() + ) } @Test fun find_no_entry_by_name_because_wrong_geometry() { assertEquals( emptyList(), - dictionary(bakery).byTerm("Bäckerei").forGeometry(GeometryType.LINE).find() + dictionary(bakery).byTerm("Bäckerei").forGeometry(GeometryType.LINE).find().toList() ) } @@ -327,25 +330,28 @@ class FeatureDictionaryTest { val dictionary = dictionary(ditsch, ditschRussian) assertEquals( emptyList(), - dictionary.byTerm("Ditsch").find() + dictionary.byTerm("Ditsch").find().toList() ) assertEquals( emptyList(), - dictionary.byTerm("Ditsch").inCountry("FR").find() + dictionary.byTerm("Ditsch").inCountry("FR").find().toList() ) // not in France assertEquals( emptyList(), - dictionary.byTerm("Ditsch").inCountry("AT-9").find() + dictionary.byTerm("Ditsch").inCountry("AT-9").find().toList() ) // in all of AT but not Vienna assertEquals( emptyList(), - dictionary.byTerm("Дитсч").inCountry("UA").find() + dictionary.byTerm("Дитсч").inCountry("UA").find().toList() ) // only on the Krim } @Test fun find_no_non_searchable_entry_by_name() { - assertEquals(emptyList(), dictionary(scheisshaus).byTerm("Scheißhaus").find()) + assertEquals( + emptyList(), + dictionary(scheisshaus).byTerm("Scheißhaus").find().toList() + ) } @Test @@ -355,7 +361,9 @@ class FeatureDictionaryTest { dictionary(bakery) .byTerm("Bäckerei") .forLocale(null) - .find()) + .find() + .toList() + ) } @Test @@ -367,33 +375,43 @@ class FeatureDictionaryTest { .forLocale(null) .forGeometry(GeometryType.POINT) .find() + .toList() ) } @Test fun find_entry_by_name_with_correct_country() { val dictionary = dictionary(ditsch, ditschRussian, bakery) - assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("DE").find()) - assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("DE-TH").find()) - assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("AT").find()) - assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("AT-5").find()) - assertEquals(listOf(ditschRussian), dictionary.byTerm("Дитсч").inCountry("UA-43").find()) - assertEquals(listOf(ditschRussian), dictionary.byTerm("Дитсч").inCountry("RU-KHA").find()) + assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("DE").find().toList()) + assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("DE-TH").find().toList()) + assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("AT").find().toList()) + assertEquals(listOf(ditsch), dictionary.byTerm("Ditsch").inCountry("AT-5").find().toList()) + assertEquals(listOf(ditschRussian), dictionary.byTerm("Дитсч").inCountry("UA-43").find().toList()) + assertEquals(listOf(ditschRussian), dictionary.byTerm("Дитсч").inCountry("RU-KHA").find().toList()) } @Test fun find_entry_by_name_case_insensitive() { - assertEquals(listOf(bakery), dictionary(bakery).byTerm("BÄCkErEI").forLocale(null).find()) + assertEquals( + listOf(bakery), + dictionary(bakery).byTerm("BÄCkErEI").forLocale(null).find().toList() + ) } @Test fun find_entry_by_name_diacritics_insensitive() { - assertEquals(listOf(bakery), dictionary(bakery).byTerm("Backérèi").forLocale(null).find()) + assertEquals( + listOf(bakery), + dictionary(bakery).byTerm("Backérèi").forLocale(null).find().toList() + ) } @Test fun find_entry_by_term() { - assertEquals(listOf(bakery), dictionary(bakery).byTerm("bro").forLocale(null).find()) + assertEquals( + listOf(bakery), + dictionary(bakery).byTerm("bro").forLocale(null).find().toList() + ) } @Test @@ -401,30 +419,36 @@ class FeatureDictionaryTest { val dictionary = dictionary(liquor_store) assertEquals( listOf(liquor_store), - dictionary.byTerm("Alcohol").forLocale("en-GB").find() + dictionary.byTerm("Alcohol").forLocale("en-GB").find().toList() ) assertEquals( listOf(liquor_store), - dictionary.byTerm("Off licence (Alcohol Shop)").forLocale("en-GB").find() + dictionary.byTerm("Off licence (Alcohol Shop)").forLocale("en-GB").find().toList() ) assertEquals( listOf(liquor_store), - dictionary.byTerm("Off Licence").forLocale("en-GB").find() + dictionary.byTerm("Off Licence").forLocale("en-GB").find().toList() ) assertEquals( listOf(liquor_store), - dictionary.byTerm("Off Licence (Alco").forLocale("en-GB").find() + dictionary.byTerm("Off Licence (Alco").forLocale("en-GB").find().toList() ) } @Test fun find_entry_by_term_case_insensitive() { - assertEquals(listOf(bakery), dictionary(bakery).byTerm("BRO").forLocale(null).find()) + assertEquals( + listOf(bakery), + dictionary(bakery).byTerm("BRO").forLocale(null).find().toList() + ) } @Test fun find_entry_by_term_diacritics_insensitive() { - assertEquals(listOf(bakery), dictionary(bakery).byTerm("bró").forLocale(null).find()) + assertEquals( + listOf(bakery), + dictionary(bakery).byTerm("bró").forLocale(null).find().toList() + ) } @Test @@ -439,19 +463,6 @@ class FeatureDictionaryTest { ) } - @Test - fun find_multiple_entries_by_name_but_respect_limit() { - assertEquals( - 1, - dictionary(second_hand_car_dealer, car_dealer) - .byTerm("auto") - .forLocale("de") - .limit(1) - .find() - .size - ) - } - @Test fun find_only_brands_by_name_finds_no_normal_entries() { assertEquals( @@ -461,7 +472,7 @@ class FeatureDictionaryTest { .forLocale(null) .isSuggestion(true) .find() - .size + .count() ) } @@ -474,6 +485,7 @@ class FeatureDictionaryTest { .forLocale(null) .isSuggestion(false) .find() + .toList() ) } @@ -481,7 +493,7 @@ class FeatureDictionaryTest { fun find_no_entry_by_term_because_wrong_locale() { assertEquals( emptyList(), - dictionary(bakery).byTerm("Bäck").forLocale("it").find() + dictionary(bakery).byTerm("Bäck").forLocale("it").find().toList() ) } @@ -489,17 +501,17 @@ class FeatureDictionaryTest { fun find_entry_by_term_because_fallback_locale() { assertEquals( listOf(bakery), - dictionary(bakery).byTerm("Bäck").forLocale("it", null).find() + dictionary(bakery).byTerm("Bäck").forLocale("it", null).find().toList() ) } @Test fun find_multi_word_brand_feature() { val dictionary = dictionary(deutsche_bank) - assertEquals(listOf(deutsche_bank), dictionary.byTerm("Deutsche Ba").find()) - assertEquals(listOf(deutsche_bank), dictionary.byTerm("Deut").find()) + assertEquals(listOf(deutsche_bank), dictionary.byTerm("Deutsche Ba").find().toList()) + assertEquals(listOf(deutsche_bank), dictionary.byTerm("Deut").find().toList()) // by-word only for non-brand features - assertTrue(dictionary.byTerm("Ban").find().isEmpty()) + assertEquals(0, dictionary.byTerm("Ban").find().count()) } @Test @@ -507,32 +519,32 @@ class FeatureDictionaryTest { val dictionary = dictionary(miniature_train_shop) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("mini").forLocale(null).find() + dictionary.byTerm("mini").forLocale(null).find().toList() ) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("train").forLocale(null).find() + dictionary.byTerm("train").forLocale(null).find().toList() ) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("shop").forLocale(null).find() + dictionary.byTerm("shop").forLocale(null).find().toList() ) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("Miniature Trai").forLocale(null).find() + dictionary.byTerm("Miniature Trai").forLocale(null).find().toList() ) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("Miniature Train Shop").forLocale(null).find() + dictionary.byTerm("Miniature Train Shop").forLocale(null).find().toList() ) - assertTrue(dictionary.byTerm("Train Sho").forLocale(null).find().isEmpty()) + assertEquals(0, dictionary.byTerm("Train Sho").forLocale(null).find().count()) } @Test fun find_entry_by_tag_value() { assertEquals( listOf(panetteria), - dictionary(panetteria).byTerm("bakery").forLocale("it").find() + dictionary(panetteria).byTerm("bakery").forLocale("it").find().toList() ) } @@ -607,7 +619,7 @@ class FeatureDictionaryTest { // casino, // not included: "Spielbank" does not start with "bank" // deutsche_bank // not included: "Deutsche Bank" does not start with "bank" and is a brand ), - dictionary.byTerm("Bank").forLocale(null).find() + dictionary.byTerm("Bank").forLocale(null).find().toList() ) } @@ -644,8 +656,8 @@ class FeatureDictionaryTest { .forLocale("de", null) .inCountry("DE") .find() - assertEquals(1, byTerm.size) - assertEquals(lush, byTerm[0]) + assertEquals(1, byTerm.count()) + assertEquals(lush, byTerm.first()) val byId = dictionary .byId("shop/cosmetics/lush-a08666") @@ -680,8 +692,8 @@ class FeatureDictionaryTest { .byTerm("Chinese Res") .forLocale("en") .find() - assertEquals(1, matches3.size) - assertEquals("Chinese Restaurant", matches3[0].name) + assertEquals(1, matches3.count()) + assertEquals("Chinese Restaurant", matches3.first().name) } } From b6f3e0c9015b33de26fc5ee4f8f5a6712ef459b5 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 14 May 2024 02:19:40 +0200 Subject: [PATCH 96/98] add link to ticket to monitor --- src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt index 45cada9..f1f44bf 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/JsonUtils.kt @@ -5,6 +5,7 @@ import kotlinx.io.readString import kotlinx.serialization.json.* // TODO This can hopefully be replaced with a function from kotlinx-serialization soon that streams the data +// monitor https://github.com/Kotlin/kotlinx.serialization/issues/253 @OptIn(ExperimentalStdlibApi::class) internal inline fun Json.decodeFromSource(source: Source): T = decodeFromString(source.use { it.readString() }) \ No newline at end of file From e58ce9429db7ea40292aa8dfd9c524c14c883371 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 14 May 2024 18:17:08 +0200 Subject: [PATCH 97/98] is is more correct to use "language" rather than "locale" --- CHANGELOG.md | 2 +- README.md | 16 +-- ...{Locale.android.kt => Language.android.kt} | 2 +- .../de/westnordost/osmfeatures/BaseFeature.kt | 2 +- .../de/westnordost/osmfeatures/Feature.kt | 2 +- .../osmfeatures/FeatureDictionary.kt | 118 +++++++++--------- .../IDLocalizedFeatureCollection.kt | 42 +++---- .../IDPresetsTranslationJsonParser.kt | 18 +-- .../de/westnordost/osmfeatures/Language.kt | 3 + .../de/westnordost/osmfeatures/Locale.kt | 3 - .../osmfeatures/LocalizedFeature.kt | 4 +- .../osmfeatures/LocalizedFeatureCollection.kt | 10 +- .../osmfeatures/FeatureDictionaryTest.kt | 109 ++++++++-------- .../IDLocalizedFeatureCollectionTest.kt | 16 +-- .../TestLocalizedFeatureCollection.kt | 8 +- .../{Locale.ios.kt => Language.ios.kt} | 2 +- .../{Locale.jvm.kt => Language.jvm.kt} | 2 +- 17 files changed, 179 insertions(+), 180 deletions(-) rename src/androidMain/kotlin/de/westnordost/osmfeatures/{Locale.android.kt => Language.android.kt} (67%) create mode 100644 src/commonMain/kotlin/de/westnordost/osmfeatures/Language.kt delete mode 100644 src/commonMain/kotlin/de/westnordost/osmfeatures/Locale.kt rename src/iosMain/kotlin/de/westnordost/osmfeatures/{Locale.ios.kt => Language.ios.kt} (80%) rename src/jvmMain/kotlin/de/westnordost/osmfeatures/{Locale.jvm.kt => Language.jvm.kt} (67%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63cca85..238419b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -# 6.0.0 +# 6.0 The library is now a Kotlin Multiplatform library. This is a breaking API change. There is also no separate artifact for Android anymore. diff --git a/README.md b/README.md index 91c33df..88fe8b6 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ It is currently used in [StreetComplete](https://github.com/streetcomplete/stree ## Usage -Add [de.westnordost:osmfeatures:6.0.0](https://mvnrepository.com/artifact/de.westnordost/osmfeatures/5.2) as a Maven dependency or download the jar from there. +Add [de.westnordost:osmfeatures:6.0](https://mvnrepository.com/artifact/de.westnordost/osmfeatures/6.0) as a Maven dependency or download the jar from there. ### Get the data @@ -44,14 +44,14 @@ val dictionary = FeatureDictionary.create(assetManager, "path/within/assets/fold If brand features from the [name suggestion index](https://github.com/osmlab/name-suggestion-index) should be included in the dictionary, you can specify the path to these presets as a third parameter. These will be loaded on-demand depending on for which countries you search for. -Translations will also be loaded on demand when first querying features using a certain locale. +Translations will also be loaded on demand when first querying features using a certain language. ### Find matches by tags ```kotlin val matches = dictionary.getByTags( tags = mapOf("amenity" to "bench"), // look for features that have the given tags - locales = listOf("de"), // show results in German only, don't fall back to English or unlocalized results + languages = listOf("de"), // show results in German only, don't fall back to English or unlocalized results geometry = GeometryType.POINT, // limit the search to features that may be points ) @@ -66,7 +66,7 @@ println(matches[0]?.getName()) ```kotlin val matches = dictionary.getByTerm( term = "Bank", // look for features matching "Bank" - locales = listOf("de", null), // show results in German or fall back to unlocalized results + languages = listOf("de", null), // show results in German or fall back to unlocalized results // (brand features are usually not localized) country = "DE", // also include things (brands) that only exist in Germany geometry = GeometryType.AREA, // limit the search to features that may be areas @@ -80,9 +80,9 @@ val matches = dictionary.getByTerm( ```kotlin val match = dictionary.getById( id = "amenity/bank", - locales = listOf("de", "en-US", null), // show results in German, otherwise fall back to American - // English or otherwise unlocalized results - country = "DE", // also include things (brands) that only exist in Germany + languages = listOf("de", "en-US", null), // show results in German, otherwise fall back to American + // English or otherwise unlocalized results + country = "DE", // also include things (brands) that only exist in Germany ) ``` @@ -95,7 +95,7 @@ e.g. List matches = dictionary .byTags(Map.of("amenity", "bench")) .forGeometry(GeometryType.POINT) - .forLocale("de") + .inLanguage("de") .find(); ``` diff --git a/src/androidMain/kotlin/de/westnordost/osmfeatures/Locale.android.kt b/src/androidMain/kotlin/de/westnordost/osmfeatures/Language.android.kt similarity index 67% rename from src/androidMain/kotlin/de/westnordost/osmfeatures/Locale.android.kt rename to src/androidMain/kotlin/de/westnordost/osmfeatures/Language.android.kt index 28b0c44..ea538cc 100644 --- a/src/androidMain/kotlin/de/westnordost/osmfeatures/Locale.android.kt +++ b/src/androidMain/kotlin/de/westnordost/osmfeatures/Language.android.kt @@ -2,5 +2,5 @@ package de.westnordost.osmfeatures import java.util.Locale -internal actual fun defaultLocale(): String = +internal actual fun defaultLanguage(): String = Locale.getDefault().toLanguageTag() \ No newline at end of file diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/BaseFeature.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/BaseFeature.kt index 77d8d71..ef17bf9 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/BaseFeature.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/BaseFeature.kt @@ -20,6 +20,6 @@ data class BaseFeature( override val canonicalNames: List = names.map { it.canonicalize() } override val canonicalTerms: List = terms.map { it.canonicalize() } - override val locale: String? get() = null + override val language: String? get() = null override fun toString(): String = id } \ No newline at end of file diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/Feature.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/Feature.kt index e1945a9..cc84823 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/Feature.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/Feature.kt @@ -23,5 +23,5 @@ interface Feature { val canonicalNames: List val canonicalTerms: List val isSuggestion: Boolean - val locale: String? + val language: String? } diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt index 4852d81..3c60332 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt @@ -9,17 +9,17 @@ class FeatureDictionary internal constructor( private val brandNamesIndexes = HashMap, Lazy>() private val brandTagsIndexes = HashMap, Lazy>() - // locale list -> index + // language list -> index private val tagsIndexes = HashMap, Lazy>() private val namesIndexes = HashMap, Lazy>() private val termsIndexes = HashMap, Lazy>() private val tagValuesIndexes = HashMap, Lazy>() init { - // build indices for default locale - getTagsIndex(listOf(defaultLocale(), null)) - getNamesIndex(listOf(defaultLocale(), null)) - getTermsIndex(listOf(defaultLocale(), null)) + // build indices for default language + getTagsIndex(listOf(defaultLanguage(), null)) + getNamesIndex(listOf(defaultLanguage(), null)) + getTermsIndex(listOf(defaultLanguage(), null)) } //region Get by id @@ -33,14 +33,14 @@ class FeatureDictionary internal constructor( * @param id * feature id * - * @param locales + * @param languages * Optional. List of IETF language tags of languages in which the result should be localized. * * Several languages can be specified to each fall back to if a translation does not exist in - * the locale before that. For example, specify `listOf("ca-ES","es", null)` if results in + * the language before that. For example, specify `listOf("ca-ES","es", null)` if results in * Catalan are preferred, Spanish is also fine or otherwise use unlocalized results (`null`). * - * Defaults to `listOf(, null)`, i.e. unlocalized results are + * Defaults to `listOf(, null)`, i.e. unlocalized results are * included by default. (Brand features are usually not localized.) * * @param country @@ -50,10 +50,10 @@ class FeatureDictionary internal constructor( * */ fun getById( id: String, - locales: List? = null, + languages: List? = null, country: String? = null ): Feature? = - featureCollection.get(id, locales ?: listOf(defaultLocale(), null)) + featureCollection.get(id, languages ?: listOf(defaultLanguage(), null)) ?: brandFeatureCollection?.get(id, dissectCountryCode(country)) //endregion @@ -71,14 +71,14 @@ class FeatureDictionary internal constructor( * @param geometry * Optional. If not `null`, only returns features that match the given geometry type. * - * @param locales + * @param languages * Optional. List of IETF language tags of languages in which the result should be localized. * * Several languages can be specified to each fall back to if a translation does not exist in - * the locale before that. For example, specify `listOf("ca-ES","es", null)` if results in + * the language before that. For example, specify `listOf("ca-ES","es", null)` if results in * Catalan are preferred, Spanish is also fine or otherwise use unlocalized results (`null`). * - * Defaults to `listOf(, null)`, i.e. unlocalized results are + * Defaults to `listOf(, null)`, i.e. unlocalized results are * included by default. (Brand features are usually not localized.) * * @param country @@ -105,18 +105,18 @@ class FeatureDictionary internal constructor( * */ fun getByTags( tags: Map, - locales: List? = null, + languages: List? = null, country: String? = null, geometry: GeometryType? = null, isSuggestion: Boolean? = null ): List { if (tags.isEmpty()) return emptyList() - val localesOrDefault = locales ?: listOf(defaultLocale(), null) + val languagesOrDefault = languages ?: listOf(defaultLanguage(), null) val foundFeatures = mutableListOf() if (isSuggestion == null || !isSuggestion) { - foundFeatures.addAll(getTagsIndex(localesOrDefault).getAll(tags)) + foundFeatures.addAll(getTagsIndex(languagesOrDefault).getAll(tags)) } if (isSuggestion == null || isSuggestion) { val countryCodes = dissectCountryCode(country) @@ -142,13 +142,13 @@ class FeatureDictionary internal constructor( return@Comparator tagOrder } - // 2. if search is not limited by locale, return matches not limited by locale first - if (localesOrDefault.size == 1 && localesOrDefault[0] == null) { - val localeOrder = ( + // 2. if search is not limited by language, return matches not limited by language first + if (languagesOrDefault.size == 1 && languagesOrDefault[0] == null) { + val languageOrder = ( (b.includeCountryCodes.isEmpty() && b.excludeCountryCodes.isEmpty()).toInt() - (a.includeCountryCodes.isEmpty() && a.excludeCountryCodes.isEmpty()).toInt() ) - if (localeOrder != 0) return@Comparator localeOrder + if (languageOrder != 0) return@Comparator languageOrder } // 3. features with more matching tags in addTags first @@ -179,14 +179,14 @@ class FeatureDictionary internal constructor( * @param geometry * Optional. If not `null`, only returns features that match the given geometry type. * - * @param locales + * @param languages * Optional. List of IETF language tags of languages in which the result should be localized. * * Several languages can be specified to each fall back to if a translation does not exist in - * the locale before that. For example, specify `listOf("ca-ES","es", null)` if results in + * the language before that. For example, specify `listOf("ca-ES","es", null)` if results in * Catalan are preferred, Spanish is also fine or otherwise use unlocalized results (`null`). * - * Defaults to `listOf(, null)`, i.e. unlocalized results are + * Defaults to `listOf(, null)`, i.e. unlocalized results are * included by default. (Brand features are usually not localized.) * * @param country @@ -208,14 +208,14 @@ class FeatureDictionary internal constructor( * */ fun getByTerm( search: String, - locales: List? = null, + languages: List? = null, country: String? = null, geometry: GeometryType? = null, isSuggestion: Boolean? = null ): Sequence { val canonicalSearch = search.canonicalize() - val localesOrDefault = locales ?: listOf(defaultLocale(), null) + val languagesOrDefault = languages ?: listOf(defaultLanguage(), null) val sortNames = Comparator { a: Feature, b: Feature -> // 1. exact matches first @@ -255,7 +255,7 @@ class FeatureDictionary internal constructor( if (isSuggestion == null || !isSuggestion) { // a. matches with presets first yieldAll( - getNamesIndex(localesOrDefault).getAll(canonicalSearch).sortedWith(sortNames) + getNamesIndex(languagesOrDefault).getAll(canonicalSearch).sortedWith(sortNames) ) } if (isSuggestion == null || isSuggestion) { @@ -268,13 +268,13 @@ class FeatureDictionary internal constructor( if (isSuggestion == null || !isSuggestion) { // c. matches with terms third yieldAll( - getTermsIndex(localesOrDefault).getAll(canonicalSearch).sortedWith(sortMatchScore) + getTermsIndex(languagesOrDefault).getAll(canonicalSearch).sortedWith(sortMatchScore) ) } if (isSuggestion == null || !isSuggestion) { // d. matches with tag values fourth yieldAll( - getTagValuesIndex(localesOrDefault).getAll(canonicalSearch).sortedWith(sortMatchScore) + getTagValuesIndex(languagesOrDefault).getAll(canonicalSearch).sortedWith(sortMatchScore) ) } } @@ -286,37 +286,37 @@ class FeatureDictionary internal constructor( //region Lazily get or create Indexes - /** lazily get or create tags index for given locale(s) */ - private fun getTagsIndex(locales: List): FeatureTagsIndex = - tagsIndexes.getOrPut(locales) { lazy { createTagsIndex(locales) } }.value + /** lazily get or create tags index for given language(s) */ + private fun getTagsIndex(languages: List): FeatureTagsIndex = + tagsIndexes.getOrPut(languages) { lazy { createTagsIndex(languages) } }.value - private fun createTagsIndex(locales: List): FeatureTagsIndex = - FeatureTagsIndex(featureCollection.getAll(locales)) + private fun createTagsIndex(languages: List): FeatureTagsIndex = + FeatureTagsIndex(featureCollection.getAll(languages)) - /** lazily get or create names index for given locale(s) */ - private fun getNamesIndex(locales: List): FeatureTermIndex = - namesIndexes.getOrPut(locales) { lazy { createNamesIndex(locales) } }.value + /** lazily get or create names index for given language(s) */ + private fun getNamesIndex(languages: List): FeatureTermIndex = + namesIndexes.getOrPut(languages) { lazy { createNamesIndex(languages) } }.value - private fun createNamesIndex(locales: List): FeatureTermIndex = - FeatureTermIndex(featureCollection.getAll(locales)) { feature -> + private fun createNamesIndex(languages: List): FeatureTermIndex = + FeatureTermIndex(featureCollection.getAll(languages)) { feature -> feature.getSearchableNames().toList() } - /** lazily get or create terms index for given locale(s) */ - private fun getTermsIndex(locales: List): FeatureTermIndex = - termsIndexes.getOrPut(locales) { lazy { createTermsIndex(locales) } }.value + /** lazily get or create terms index for given language(s) */ + private fun getTermsIndex(languages: List): FeatureTermIndex = + termsIndexes.getOrPut(languages) { lazy { createTermsIndex(languages) } }.value - private fun createTermsIndex(locales: List): FeatureTermIndex = - FeatureTermIndex(featureCollection.getAll(locales)) { feature -> + private fun createTermsIndex(languages: List): FeatureTermIndex = + FeatureTermIndex(featureCollection.getAll(languages)) { feature -> if (!feature.isSearchable) emptyList() else feature.canonicalTerms } /** lazily get or create tag values index */ - private fun getTagValuesIndex(locales: List): FeatureTermIndex = - tagValuesIndexes.getOrPut(locales) { lazy { createTagValuesIndex(locales) } }.value + private fun getTagValuesIndex(languages: List): FeatureTermIndex = + tagValuesIndexes.getOrPut(languages) { lazy { createTagValuesIndex(languages) } }.value - private fun createTagValuesIndex(locales: List): FeatureTermIndex = - FeatureTermIndex(featureCollection.getAll(locales)) { feature -> + private fun createTagValuesIndex(languages: List): FeatureTermIndex = + FeatureTermIndex(featureCollection.getAll(languages)) { feature -> if (!feature.isSearchable) { emptyList() } else { @@ -353,29 +353,29 @@ class FeatureDictionary internal constructor( //region Query builders inner class QueryByIdBuilder(private val id: String) { - private var locales: List? = null + private var languages: List? = null private var country: String? = null - fun forLocale(vararg locales: String?): QueryByIdBuilder = - apply { this.locales = locales.toList() } + fun inLanguage(vararg languages: String?): QueryByIdBuilder = + apply { this.languages = languages.toList() } fun inCountry(country: String?): QueryByIdBuilder = apply { this.country = country } - fun get(): Feature? = getById(id, locales, country) + fun get(): Feature? = getById(id, languages, country) } inner class QueryByTagBuilder(private val tags: Map) { private var geometry: GeometryType? = null - private var locales: List? = null + private var languages: List? = null private var isSuggestion: Boolean? = null private var country: String? = null fun forGeometry(geometry: GeometryType): QueryByTagBuilder = apply { this.geometry = geometry } - fun forLocale(vararg locales: String?): QueryByTagBuilder = - apply { this.locales = locales.toList() } + fun inLanguage(vararg languages: String?): QueryByTagBuilder = + apply { this.languages = languages.toList() } fun inCountry(country: String?): QueryByTagBuilder = apply { this.country = country } @@ -383,20 +383,20 @@ class FeatureDictionary internal constructor( fun isSuggestion(isSuggestion: Boolean?): QueryByTagBuilder = apply { this.isSuggestion = isSuggestion } - fun find(): List = getByTags(tags, locales, country, geometry, isSuggestion) + fun find(): List = getByTags(tags, languages, country, geometry, isSuggestion) } inner class QueryByTermBuilder(private val term: String) { private var geometry: GeometryType? = null - private var locales: List? = null + private var languages: List? = null private var isSuggestion: Boolean? = null private var country: String? = null fun forGeometry(geometryType: GeometryType): QueryByTermBuilder = apply { this.geometry = geometryType } - fun forLocale(vararg locales: String?): QueryByTermBuilder = - apply { this.locales = locales.toList() } + fun inLanguage(vararg languages: String?): QueryByTermBuilder = + apply { this.languages = languages.toList() } fun inCountry(countryCode: String?): QueryByTermBuilder = apply { this.country = countryCode } @@ -405,7 +405,7 @@ class FeatureDictionary internal constructor( apply { this.isSuggestion = suggestion } fun find(): Sequence = - getByTerm(term, locales, country, geometry, isSuggestion) + getByTerm(term, languages, country, geometry, isSuggestion) } //endregion diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt index e36f30a..ddb1b88 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollection.kt @@ -11,37 +11,37 @@ internal class IDLocalizedFeatureCollection( // featureId -> Feature private val featuresById: LinkedHashMap - // locale -> lazy { localized features } + // language -> lazy { localized features } private val localizedFeaturesList = HashMap>>() - // locales -> lazy { featureId -> Feature } + // languages -> lazy { featureId -> Feature } private val localizedFeatures = HashMap, Lazy>>() init { featuresById = loadFeatures().associateByTo(LinkedHashMap()) { it.id } } - override fun getAll(locales: List): Collection { - return getOrLoadLocalizedFeatures(locales).values + override fun getAll(languages: List): Collection { + return getOrLoadLocalizedFeatures(languages).values } - override fun get(id: String, locales: List): Feature? { - return getOrLoadLocalizedFeatures(locales)[id] + override fun get(id: String, languages: List): Feature? { + return getOrLoadLocalizedFeatures(languages)[id] } @OptIn(ExperimentalStdlibApi::class) private fun loadFeatures(): List = fileAccess.open(FEATURES_FILE).use { IDPresetsJsonParser().parse(it) } - private fun getOrLoadLocalizedFeatures(locales: List): LinkedHashMap = - localizedFeatures.getOrPut(locales) { lazy { loadLocalizedFeatures(locales) } }.value + private fun getOrLoadLocalizedFeatures(languages: List): LinkedHashMap = + localizedFeatures.getOrPut(languages) { lazy { loadLocalizedFeatures(languages) } }.value - private fun loadLocalizedFeatures(locales: List): LinkedHashMap { + private fun loadLocalizedFeatures(languages: List): LinkedHashMap { val result = LinkedHashMap(featuresById.size) - for (locale in locales.asReversed()) { - if (locale != null) { - for (localeComponent in locale.getLocaleComponents()) { - val features = getOrLoadLocalizedFeaturesList(localeComponent) + for (language in languages.asReversed()) { + if (language != null) { + for (languageComponent in language.getLanguageComponents()) { + val features = getOrLoadLocalizedFeaturesList(languageComponent) for (feature in features) { result[feature.id] = feature } @@ -53,26 +53,26 @@ internal class IDLocalizedFeatureCollection( return result } - private fun getOrLoadLocalizedFeaturesList(locale: String): List = - localizedFeaturesList.getOrPut(locale) { lazy { loadLocalizedFeaturesList(locale) } }.value + private fun getOrLoadLocalizedFeaturesList(language: String): List = + localizedFeaturesList.getOrPut(language) { lazy { loadLocalizedFeaturesList(language) } }.value @OptIn(ExperimentalStdlibApi::class) - private fun loadLocalizedFeaturesList(locale: String?): List { - val filename = if (locale != null) getLocalizationFilename(locale) else "en.json" + private fun loadLocalizedFeaturesList(language: String?): List { + val filename = if (language != null) getLocalizationFilename(language) else "en.json" if (!fileAccess.exists(filename)) return emptyList() return fileAccess.open(filename).use { source -> - IDPresetsTranslationJsonParser().parse(source, locale, featuresById) + IDPresetsTranslationJsonParser().parse(source, language, featuresById) } } companion object { private const val FEATURES_FILE = "presets.json" - private fun getLocalizationFilename(locale: String): String = "$locale.json" + private fun getLocalizationFilename(language: String): String = "$language.json" } } -private fun String.getLocaleComponents(): Sequence = sequence { +private fun String.getLanguageComponents(): Sequence = sequence { val components = split('-') val language = components.first() yield(language) @@ -82,5 +82,5 @@ private fun String.getLocaleComponents(): Sequence = sequence { for (other in others) { yield(listOf(language, other).joinToString("-")) } - yield(this@getLocaleComponents) + yield(this@getLanguageComponents) } \ No newline at end of file diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt index b37ef62..3c7b715 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/IDPresetsTranslationJsonParser.kt @@ -9,17 +9,17 @@ import kotlinx.serialization.json.* */ internal class IDPresetsTranslationJsonParser { fun parse( - content: String, locale: String?, baseFeatures: Map + content: String, language: String?, baseFeatures: Map ): List = - parse(Json.decodeFromString(content), locale, baseFeatures) + parse(Json.decodeFromString(content), language, baseFeatures) fun parse( - source: Source, locale: String?, baseFeatures: Map + source: Source, language: String?, baseFeatures: Map ): List = - parse(Json.decodeFromSource(source), locale, baseFeatures) + parse(Json.decodeFromSource(source), language, baseFeatures) private fun parse( - json: JsonObject, locale: String?, baseFeatures: Map + json: JsonObject, language: String?, baseFeatures: Map ): List { val translations = json.jsonObject.entries.firstOrNull()?.value?.jsonObject ?.get("presets")?.jsonObject @@ -28,7 +28,7 @@ internal class IDPresetsTranslationJsonParser { val localizedFeatures = HashMap(translations.size) translations.entries.forEach { (key, value) -> - val f = parseFeature(baseFeatures[key], locale, value.jsonObject) + val f = parseFeature(baseFeatures[key], language, value.jsonObject) if (f != null) localizedFeatures[key] = f } @@ -40,7 +40,7 @@ internal class IDPresetsTranslationJsonParser { val localizedFeature = localizedFeatures[placeholderId] ?: continue localizedFeatures[baseFeature.id] = LocalizedFeature( p = baseFeature, - locale = locale, + language = language, names = localizedFeature.names, terms = localizedFeature.terms ) @@ -49,7 +49,7 @@ internal class IDPresetsTranslationJsonParser { return localizedFeatures.values.toList() } - private fun parseFeature(feature: BaseFeature?, locale: String?, localization: JsonObject): LocalizedFeature? { + private fun parseFeature(feature: BaseFeature?, language: String?, localization: JsonObject): LocalizedFeature? { if (feature == null) return null val name = localization["name"]?.jsonPrimitive?.contentOrNull.orEmpty() @@ -72,7 +72,7 @@ internal class IDPresetsTranslationJsonParser { return LocalizedFeature( p = feature, - locale = locale, + language = language, names = names, terms = terms ) diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/Language.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/Language.kt new file mode 100644 index 0000000..03fe9f4 --- /dev/null +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/Language.kt @@ -0,0 +1,3 @@ +package de.westnordost.osmfeatures + +internal expect fun defaultLanguage(): String \ No newline at end of file diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/Locale.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/Locale.kt deleted file mode 100644 index 525d01b..0000000 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/Locale.kt +++ /dev/null @@ -1,3 +0,0 @@ -package de.westnordost.osmfeatures - -internal expect fun defaultLocale(): String \ No newline at end of file diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeature.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeature.kt index cb3bb7b..2228d67 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeature.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeature.kt @@ -2,10 +2,10 @@ package de.westnordost.osmfeatures /** Data class associated with the Feature interface. Represents a localized feature. * - * I.e. the name and terms are specified in the given locale. */ + * I.e. the name and terms are specified in the given language. */ data class LocalizedFeature( private val p: BaseFeature, - override val locale: String?, + override val language: String?, override val names: List, override val terms: List ) : Feature { diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt index a8d8710..b95d5f6 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/LocalizedFeatureCollection.kt @@ -2,10 +2,10 @@ package de.westnordost.osmfeatures /** A localized collection of features */ internal interface LocalizedFeatureCollection { - /** Returns all features in the given locale(s). */ - fun getAll(locales: List): Collection + /** Returns all features in the given IETF language tag(s). */ + fun getAll(languages: List): Collection - /** Returns the feature with the given id in the given locale(s) or null if it has not been - * found (for the given locale(s)) */ - fun get(id: String, locales: List): Feature? + /** Returns the feature with the given id in the given IETF language tag(s) or null if it has + * not been found (for the given IETF language tag(s)) */ + fun get(id: String, languages: List): Feature? } \ No newline at end of file diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt index d45aa02..f266446 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/FeatureDictionaryTest.kt @@ -1,7 +1,6 @@ package de.westnordost.osmfeatures import kotlin.test.Test -import kotlin.test.assertTrue import kotlin.test.assertEquals import kotlin.test.assertNull @@ -17,7 +16,7 @@ class FeatureDictionaryTest { id = "shop/bakery", tags = mapOf("shop" to "bakery"), names = listOf("Panetteria"), - locale = "it" + language = "it" ) private val ditsch = feature( // brand in DE for shop=bakery id = "shop/bakery/Ditsch", @@ -47,21 +46,21 @@ class FeatureDictionaryTest { id = "shop/alcohol", tags = mapOf("shop" to "alcohol"), names = listOf("Off licence (Alcohol shop)"), - locale = "en-GB" + language = "en-GB" ) private val car_dealer = feature( // German localized unspecific shop=car id = "shop/car", tags = mapOf("shop" to "car"), names = listOf("Autohändler"), terms = listOf("auto"), - locale = "de" + language = "de" ) private val second_hand_car_dealer = feature( // German localized shop=car with subtags id = "shop/car/second_hand", tags = mapOf("shop" to "car", "second_hand" to "only"), names = listOf("Gebrauchtwagenhändler"), terms = listOf("auto"), - locale = "de" + language = "de" ) private val scheisshaus = feature( // unsearchable feature id = "amenity/scheißhaus", @@ -160,23 +159,23 @@ class FeatureDictionaryTest { } @Test - fun find_no_entry_because_wrong_locale() { + fun find_no_entry_because_wrong_language() { assertEquals( emptyList(), dictionary(bakery) .byTags(mapOf("shop" to "bakery")) - .forLocale("it") + .inLanguage("it") .find() ) } @Test - fun find_entry_because_fallback_locale() { + fun find_entry_because_fallback_language() { assertEquals( listOf(bakery), dictionary(bakery) .byTags(mapOf("shop" to "bakery")) - .forLocale("it", null) + .inLanguage("it", null) .find() ) } @@ -220,21 +219,21 @@ class FeatureDictionaryTest { } @Test - fun find_only_entries_with_given_locale() { + fun find_only_entries_with_given_language() { val tags = mapOf("shop" to "bakery") val dictionary = dictionary(bakery, panetteria) assertEquals( listOf(panetteria), - dictionary.byTags(tags).forLocale("it").find() + dictionary.byTags(tags).inLanguage("it").find() ) assertEquals( emptyList(), - dictionary.byTags(tags).forLocale("en").find() + dictionary.byTags(tags).inLanguage("en").find() ) assertEquals( listOf(bakery), - dictionary.byTags(tags).forLocale(null).find() + dictionary.byTags(tags).inLanguage(null).find() ) } @@ -261,12 +260,12 @@ class FeatureDictionaryTest { } @Test - fun find_multiple_brands_sorts_by_locale() { + fun find_multiple_brands_sorts_by_language() { assertEquals( ditschInternational, dictionary(ditschRussian, ditschInternational, ditsch) .byTags(mapOf("shop" to "bakery", "name" to "Ditsch")) - .forLocale(null) + .inLanguage(null) .find() .first() ) @@ -289,7 +288,7 @@ class FeatureDictionaryTest { listOf(car_dealer), dictionary(car_dealer, second_hand_car_dealer) .byTags(mapOf("shop" to "car")) - .forLocale("de", null) + .inLanguage("de", null) .find() ) } @@ -300,7 +299,7 @@ class FeatureDictionaryTest { listOf(second_hand_car_dealer), dictionary(car_dealer, second_hand_car_dealer) .byTags(mapOf("shop" to "car", "second_hand" to "only")) - .forLocale("de", null) + .inLanguage("de", null) .find() ) } @@ -360,7 +359,7 @@ class FeatureDictionaryTest { listOf(bakery), dictionary(bakery) .byTerm("Bäckerei") - .forLocale(null) + .inLanguage(null) .find() .toList() ) @@ -372,7 +371,7 @@ class FeatureDictionaryTest { listOf(bakery), dictionary(bakery) .byTerm("Bäckerei") - .forLocale(null) + .inLanguage(null) .forGeometry(GeometryType.POINT) .find() .toList() @@ -394,7 +393,7 @@ class FeatureDictionaryTest { fun find_entry_by_name_case_insensitive() { assertEquals( listOf(bakery), - dictionary(bakery).byTerm("BÄCkErEI").forLocale(null).find().toList() + dictionary(bakery).byTerm("BÄCkErEI").inLanguage(null).find().toList() ) } @@ -402,7 +401,7 @@ class FeatureDictionaryTest { fun find_entry_by_name_diacritics_insensitive() { assertEquals( listOf(bakery), - dictionary(bakery).byTerm("Backérèi").forLocale(null).find().toList() + dictionary(bakery).byTerm("Backérèi").inLanguage(null).find().toList() ) } @@ -410,7 +409,7 @@ class FeatureDictionaryTest { fun find_entry_by_term() { assertEquals( listOf(bakery), - dictionary(bakery).byTerm("bro").forLocale(null).find().toList() + dictionary(bakery).byTerm("bro").inLanguage(null).find().toList() ) } @@ -419,19 +418,19 @@ class FeatureDictionaryTest { val dictionary = dictionary(liquor_store) assertEquals( listOf(liquor_store), - dictionary.byTerm("Alcohol").forLocale("en-GB").find().toList() + dictionary.byTerm("Alcohol").inLanguage("en-GB").find().toList() ) assertEquals( listOf(liquor_store), - dictionary.byTerm("Off licence (Alcohol Shop)").forLocale("en-GB").find().toList() + dictionary.byTerm("Off licence (Alcohol Shop)").inLanguage("en-GB").find().toList() ) assertEquals( listOf(liquor_store), - dictionary.byTerm("Off Licence").forLocale("en-GB").find().toList() + dictionary.byTerm("Off Licence").inLanguage("en-GB").find().toList() ) assertEquals( listOf(liquor_store), - dictionary.byTerm("Off Licence (Alco").forLocale("en-GB").find().toList() + dictionary.byTerm("Off Licence (Alco").inLanguage("en-GB").find().toList() ) } @@ -439,7 +438,7 @@ class FeatureDictionaryTest { fun find_entry_by_term_case_insensitive() { assertEquals( listOf(bakery), - dictionary(bakery).byTerm("BRO").forLocale(null).find().toList() + dictionary(bakery).byTerm("BRO").inLanguage(null).find().toList() ) } @@ -447,7 +446,7 @@ class FeatureDictionaryTest { fun find_entry_by_term_diacritics_insensitive() { assertEquals( listOf(bakery), - dictionary(bakery).byTerm("bró").forLocale(null).find().toList() + dictionary(bakery).byTerm("bró").inLanguage(null).find().toList() ) } @@ -457,7 +456,7 @@ class FeatureDictionaryTest { setOf(second_hand_car_dealer, car_dealer), dictionary(second_hand_car_dealer, car_dealer) .byTerm("auto") - .forLocale("de") + .inLanguage("de") .find() .toSet() ) @@ -469,7 +468,7 @@ class FeatureDictionaryTest { 0, dictionary(bakery) .byTerm("Bäckerei") - .forLocale(null) + .inLanguage(null) .isSuggestion(true) .find() .count() @@ -482,7 +481,7 @@ class FeatureDictionaryTest { listOf(bank), dictionary(bank, bank_of_america) .byTerm("Bank") - .forLocale(null) + .inLanguage(null) .isSuggestion(false) .find() .toList() @@ -490,18 +489,18 @@ class FeatureDictionaryTest { } @Test - fun find_no_entry_by_term_because_wrong_locale() { + fun find_no_entry_by_term_because_wrong_language() { assertEquals( emptyList(), - dictionary(bakery).byTerm("Bäck").forLocale("it").find().toList() + dictionary(bakery).byTerm("Bäck").inLanguage("it").find().toList() ) } @Test - fun find_entry_by_term_because_fallback_locale() { + fun find_entry_by_term_because_fallback_language() { assertEquals( listOf(bakery), - dictionary(bakery).byTerm("Bäck").forLocale("it", null).find().toList() + dictionary(bakery).byTerm("Bäck").inLanguage("it", null).find().toList() ) } @@ -519,32 +518,32 @@ class FeatureDictionaryTest { val dictionary = dictionary(miniature_train_shop) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("mini").forLocale(null).find().toList() + dictionary.byTerm("mini").inLanguage(null).find().toList() ) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("train").forLocale(null).find().toList() + dictionary.byTerm("train").inLanguage(null).find().toList() ) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("shop").forLocale(null).find().toList() + dictionary.byTerm("shop").inLanguage(null).find().toList() ) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("Miniature Trai").forLocale(null).find().toList() + dictionary.byTerm("Miniature Trai").inLanguage(null).find().toList() ) assertEquals( listOf(miniature_train_shop), - dictionary.byTerm("Miniature Train Shop").forLocale(null).find().toList() + dictionary.byTerm("Miniature Train Shop").inLanguage(null).find().toList() ) - assertEquals(0, dictionary.byTerm("Train Sho").forLocale(null).find().count()) + assertEquals(0, dictionary.byTerm("Train Sho").inLanguage(null).find().count()) } @Test fun find_entry_by_tag_value() { assertEquals( listOf(panetteria), - dictionary(panetteria).byTerm("bakery").forLocale("it").find().toList() + dictionary(panetteria).byTerm("bakery").inLanguage("it").find().toList() ) } @@ -559,14 +558,14 @@ class FeatureDictionaryTest { @Test fun find_no_entry_by_id_because_unlocalized_results_are_excluded() { - assertNull(dictionary(bakery).byId("shop/bakery").forLocale("it").get()) + assertNull(dictionary(bakery).byId("shop/bakery").inLanguage("it").get()) } @Test fun find_entry_by_id() { val dictionary = dictionary(bakery) assertEquals(bakery, dictionary.byId("shop/bakery").get()) - assertEquals(bakery, dictionary.byId("shop/bakery").forLocale("zh", null).get()) + assertEquals(bakery, dictionary.byId("shop/bakery").inLanguage("zh", null).get()) } @Test @@ -574,11 +573,11 @@ class FeatureDictionaryTest { val dictionary = dictionary(panetteria) assertEquals( panetteria, - dictionary.byId("shop/bakery").forLocale("it").get() + dictionary.byId("shop/bakery").inLanguage("it").get() ) assertEquals( panetteria, - dictionary.byId("shop/bakery").forLocale("it", null).get() + dictionary.byId("shop/bakery").inLanguage("it", null).get() ) } @@ -619,7 +618,7 @@ class FeatureDictionaryTest { // casino, // not included: "Spielbank" does not start with "bank" // deutsche_bank // not included: "Deutsche Bank" does not start with "bank" and is a brand ), - dictionary.byTerm("Bank").forLocale(null).find().toList() + dictionary.byTerm("Bank").inLanguage(null).find().toList() ) } @@ -645,7 +644,7 @@ class FeatureDictionaryTest { val byTags = dictionary .byTags(mapOf("brand:wikidata" to "Q1585448", "shop" to "cosmetics")) - .forLocale("de", null) + .inLanguage("de", null) .inCountry("DE") .find() assertEquals(1, byTags.size) @@ -653,7 +652,7 @@ class FeatureDictionaryTest { val byTerm = dictionary .byTerm("Lush") - .forLocale("de", null) + .inLanguage("de", null) .inCountry("DE") .find() assertEquals(1, byTerm.count()) @@ -661,7 +660,7 @@ class FeatureDictionaryTest { val byId = dictionary .byId("shop/cosmetics/lush-a08666") - .forLocale("de", null) + .inLanguage("de", null) .inCountry("DE") .get() assertEquals(lush, byId) @@ -676,21 +675,21 @@ class FeatureDictionaryTest { val matches = dictionary .byTags(mapOf("amenity" to "studio")) - .forLocale("en") + .inLanguage("en") .find() assertEquals(1, matches.size) assertEquals("Studio", matches[0].name) val matches2 = dictionary .byTags(mapOf("amenity" to "studio", "studio" to "audio")) - .forLocale("en") + .inLanguage("en") .find() assertEquals(1, matches2.size) assertEquals("Recording Studio", matches2[0].name) val matches3 = dictionary .byTerm("Chinese Res") - .forLocale("en") + .inLanguage("en") .find() assertEquals(1, matches3.count()) assertEquals("Chinese Restaurant", matches3.first().name) @@ -714,11 +713,11 @@ private fun feature( matchScore: Float = 1.0f, addTags: Map = mapOf(), isSuggestion: Boolean = false, - locale: String? = null + language: String? = null ): Feature { val f = BaseFeature( id, tags, geometries, null, null, names, terms, countryCodes, excludeCountryCodes, searchable, matchScore, isSuggestion, addTags, mapOf() ) - return if (locale != null) LocalizedFeature(f, locale, f.names, f.terms) else f + return if (language != null) LocalizedFeature(f, language, f.names, f.terms) else f } \ No newline at end of file diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt index bd50985..7979990 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/IDLocalizedFeatureCollectionTest.kt @@ -53,18 +53,18 @@ class IDLocalizedFeatureCollectionTest { assertNull(c.get("yet/another/id", germany)) // getting features through fallback chain - val locales = listOf("en", "de-DE", null) - val fallbackFeatures = c.getAll(locales) + val languages = listOf("en", "de-DE", null) + val fallbackFeatures = c.getAll(languages) assertEquals( setOf("Bakery", "Gullideckel", "test"), fallbackFeatures.map { it.name }.toSet() ) - assertEquals("Bakery", c.get("some/id", locales)?.name) - assertEquals("Gullideckel", c.get("another/id", locales)?.name) - assertEquals("test", c.get("yet/another/id", locales)?.name) - assertEquals("en", c.get("some/id", locales)?.locale) - assertEquals("de", c.get("another/id", locales)?.locale) - assertNull(c.get("yet/another/id", locales)?.locale) + assertEquals("Bakery", c.get("some/id", languages)?.name) + assertEquals("Gullideckel", c.get("another/id", languages)?.name) + assertEquals("test", c.get("yet/another/id", languages)?.name) + assertEquals("en", c.get("some/id", languages)?.language) + assertEquals("de", c.get("another/id", languages)?.language) + assertNull(c.get("yet/another/id", languages)?.language) } @Test diff --git a/src/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt b/src/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt index a4f1a8d..9f6f94a 100644 --- a/src/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt +++ b/src/commonTest/kotlin/de/westnordost/osmfeatures/TestLocalizedFeatureCollection.kt @@ -3,12 +3,12 @@ package de.westnordost.osmfeatures class TestLocalizedFeatureCollection(private val features: List) : LocalizedFeatureCollection { - override fun getAll(locales: List): Collection = - features.filter { locales.contains(it.locale) } + override fun getAll(languages: List): Collection = + features.filter { languages.contains(it.language) } - override fun get(id: String, locales: List): Feature? { + override fun get(id: String, languages: List): Feature? { val feature = features.find { it.id == id } ?: return null - if (!locales.contains(feature.locale)) return null + if (!languages.contains(feature.language)) return null return feature } } \ No newline at end of file diff --git a/src/iosMain/kotlin/de/westnordost/osmfeatures/Locale.ios.kt b/src/iosMain/kotlin/de/westnordost/osmfeatures/Language.ios.kt similarity index 80% rename from src/iosMain/kotlin/de/westnordost/osmfeatures/Locale.ios.kt rename to src/iosMain/kotlin/de/westnordost/osmfeatures/Language.ios.kt index 35de84a..3b5a089 100644 --- a/src/iosMain/kotlin/de/westnordost/osmfeatures/Locale.ios.kt +++ b/src/iosMain/kotlin/de/westnordost/osmfeatures/Language.ios.kt @@ -4,5 +4,5 @@ import platform.Foundation.NSLocale import platform.Foundation.currentLocale import platform.Foundation.localeIdentifier -internal actual fun defaultLocale(): String = +internal actual fun defaultLanguage(): String = NSLocale.currentLocale.localeIdentifier \ No newline at end of file diff --git a/src/jvmMain/kotlin/de/westnordost/osmfeatures/Locale.jvm.kt b/src/jvmMain/kotlin/de/westnordost/osmfeatures/Language.jvm.kt similarity index 67% rename from src/jvmMain/kotlin/de/westnordost/osmfeatures/Locale.jvm.kt rename to src/jvmMain/kotlin/de/westnordost/osmfeatures/Language.jvm.kt index 28b0c44..ea538cc 100644 --- a/src/jvmMain/kotlin/de/westnordost/osmfeatures/Locale.jvm.kt +++ b/src/jvmMain/kotlin/de/westnordost/osmfeatures/Language.jvm.kt @@ -2,5 +2,5 @@ package de.westnordost.osmfeatures import java.util.Locale -internal actual fun defaultLocale(): String = +internal actual fun defaultLanguage(): String = Locale.getDefault().toLanguageTag() \ No newline at end of file From 6572300b4284be26d5e38f7597e1439963a10750 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 14 May 2024 18:41:37 +0200 Subject: [PATCH 98/98] builders should only be constructable from the methods of FeatureDictionary --- .../kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt index 3c60332..d97f190 100644 --- a/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt +++ b/src/commonMain/kotlin/de/westnordost/osmfeatures/FeatureDictionary.kt @@ -352,7 +352,7 @@ class FeatureDictionary internal constructor( //region Query builders - inner class QueryByIdBuilder(private val id: String) { + inner class QueryByIdBuilder internal constructor(private val id: String) { private var languages: List? = null private var country: String? = null @@ -365,7 +365,7 @@ class FeatureDictionary internal constructor( fun get(): Feature? = getById(id, languages, country) } - inner class QueryByTagBuilder(private val tags: Map) { + inner class QueryByTagBuilder internal constructor(private val tags: Map) { private var geometry: GeometryType? = null private var languages: List? = null private var isSuggestion: Boolean? = null @@ -386,7 +386,7 @@ class FeatureDictionary internal constructor( fun find(): List = getByTags(tags, languages, country, geometry, isSuggestion) } - inner class QueryByTermBuilder(private val term: String) { + inner class QueryByTermBuilder internal constructor(private val term: String) { private var geometry: GeometryType? = null private var languages: List? = null private var isSuggestion: Boolean? = null