Skip to content

Commit

Permalink
Search feature: refactor, add translations
Browse files Browse the repository at this point in the history
  • Loading branch information
pietervdvn committed Sep 11, 2024
1 parent b349293 commit bd3bddc
Show file tree
Hide file tree
Showing 21 changed files with 499 additions and 507 deletions.
14 changes: 12 additions & 2 deletions langs/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -397,11 +397,21 @@
"save": "Save",
"screenToSmall": "Open <i>{theme}</i> in a new window",
"search": {
"activeFilters": "Active filters",
"clearFilters": "Clear filters",
"deleteSearchHistory": "Delete location history",
"deleteThemeHistory": "Delete earlier visited themes",
"editSearchSyncSettings": "Edit sync settings",
"editThemeSync": "Edit sync settings",
"error": "Something went wrong…",
"instructions": "Use the search bar above to search for locations, filters or other thematic maps",
"locations": "Locations",
"nothing": "Nothing found…",
"nothingFor": "No results found for {term}",
"otherMaps": "Other maps",
"pickFilter": "Pick a filter",
"recentThemes": "Recently visited maps",
"recents": "Recent searches",
"recents": "Recently seen places",
"search": "Search a location",
"searchShort": "Search…",
"searching": "Searching…"
Expand Down Expand Up @@ -877,4 +887,4 @@
"startsWithQ": "A wikidata identifier starts with Q and is followed by a number"
}
}
}
}
14 changes: 13 additions & 1 deletion langs/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -333,9 +333,21 @@
"save": "Opslaan",
"screenToSmall": "Open {theme} in een nieuw venster",
"search": {
"activeFilters": "Actieve filters",
"clearFilters": "Verwijder filters",
"deleteSearchHistory": "Verwijder geschiedenis",
"deleteThemeHistory": "Verwijder geschiedenis",
"editSearchSyncSettings": "Stel je geschiedenis-voorkeuren in",
"editThemeSync": "Stel je geschiedenis-voorkeuren in",
"error": "Niet gelukt…",
"instructions": "Gebruik de zoekbalk om locaties, filters of om andere kaarten te zoeken",
"locations": "Plaatsen",
"nothing": "Niets gevonden…",
"search": "Zoek naar een locatie",
"otherMaps": "Andere kaarten",
"pickFilter": "Kies een filter",
"recentThemes": "Recent bezochte kaarten",
"recents": "Recent bekeken plaatsen",
"search": "Zoek naar een locatie, filter of kaart",
"searchShort": "Zoek…",
"searching": "Aan het zoeken…"
},
Expand Down
8 changes: 4 additions & 4 deletions src/Logic/Search/CombinedSearcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import GeocodingProvider, { SearchResult, GeocodingOptions, GeocodeResult } from
import { Utils } from "../../Utils"
import { Store, Stores } from "../UIEventSource"

export default class CombinedSearcher implements GeocodingProvider <GeocodeResult> {
private _providers: ReadonlyArray<GeocodingProvider<GeocodeResult>>
private _providersWithSuggest: ReadonlyArray<GeocodingProvider<GeocodeResult>>
export default class CombinedSearcher implements GeocodingProvider {
private _providers: ReadonlyArray<GeocodingProvider>
private _providersWithSuggest: ReadonlyArray<GeocodingProvider>

constructor(...providers: ReadonlyArray<GeocodingProvider<GeocodeResult>>) {
constructor(...providers: ReadonlyArray<GeocodingProvider>) {
this._providers = Utils.NoNull(providers)
this._providersWithSuggest = this._providers.filter(pr => pr.suggest !== undefined)
}
Expand Down
2 changes: 1 addition & 1 deletion src/Logic/Search/CoordinateSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ImmutableStore, Store } from "../UIEventSource"
/**
* A simple search-class which interprets possible locations
*/
export default class CoordinateSearch implements GeocodingProvider<GeocodeResult> {
export default class CoordinateSearch implements GeocodingProvider {
private static readonly latLonRegexes: ReadonlyArray<RegExp> = [
/^(-?[0-9]+\.[0-9]+)[ ,;/\\]+(-?[0-9]+\.[0-9]+)/,
/lat[:=]? *['"]?(-?[0-9]+\.[0-9]+)['"]?[ ,;&]+lon[:=]? *['"]?(-?[0-9]+\.[0-9]+)['"]?/,
Expand Down
33 changes: 12 additions & 21 deletions src/Logic/Search/FilterSearch.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { ImmutableStore, Store } from "../UIEventSource"
import GeocodingProvider, { FilterPayload, FilterResult, GeocodingOptions, SearchResult } from "./GeocodingProvider"
import { SpecialVisualizationState } from "../../UI/SpecialVisualization"
import { Utils } from "../../Utils"
import Locale from "../../UI/i18n/Locale"
import Constants from "../../Models/Constants"
import FilterConfig, { FilterConfigOption } from "../../Models/ThemeConfig/FilterConfig"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"

export type FilterSearchResult = { option: FilterConfigOption, filter: FilterConfig, layer: LayerConfig, index: number }


/**
* Searches matching filters
*/
export default class FilterSearch implements GeocodingProvider {
export default class FilterSearch {
private readonly _state: SpecialVisualizationState

constructor(state: SpecialVisualizationState) {
this._state = state
}

async search(query: string): Promise<SearchResult[]> {
return this.searchDirectly(query)
}
public searchDirectly(query: string): FilterResult[] {
public search(query: string): FilterSearchResult[] {
if (query.length === 0) {
return []
}
Expand All @@ -28,7 +28,7 @@ export default class FilterSearch implements GeocodingProvider {
}
return query
}).filter(q => q.length > 0)
const possibleFilters: FilterResult[] = []
const possibleFilters: FilterSearchResult[] = []
for (const layer of this._state.layout.layers) {
if (!Array.isArray(layer.filters)) {
continue
Expand Down Expand Up @@ -61,30 +61,21 @@ export default class FilterSearch implements GeocodingProvider {
if (levehnsteinD > 0.25) {
continue
}
possibleFilters.push(<FilterResult>{
category: "filter",
osm_id: layer.id + "/" + filter.id + "/" + i,
payload: {
possibleFilters.push({
option, layer, filter, index:
i,
},
})
}
}
}
return possibleFilters
}

suggest(query: string, options?: GeocodingOptions): Store<SearchResult[]> {
return new ImmutableStore(this.searchDirectly(query))
}


/**
* Create a random list of filters
*/
getSuggestions(): FilterPayload[] {
const result: FilterPayload[] = []
getSuggestions(): FilterSearchResult[] {
const result: FilterSearchResult[] = []
for (const [id, filteredLayer] of this._state.layerState.filteredLayers) {
if (!Array.isArray(filteredLayer.layerDef.filters)) {
continue
Expand All @@ -93,7 +84,7 @@ export default class FilterSearch implements GeocodingProvider {
continue
}
for (const filter of filteredLayer.layerDef.filters) {
const singleFilterResults: FilterPayload[] = []
const singleFilterResults: FilterSearchResult[] = []
for (let i = 0; i < Math.min(filter.options.length, 5); i++) {
const option = filter.options[i]
if (option.osmTags === undefined) {
Expand Down
14 changes: 3 additions & 11 deletions src/Logic/Search/GeocodingProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { Store } from "../UIEventSource"
import * as search from "../../assets/generated/layers/search.json"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
import FilterConfig, { FilterConfigOption } from "../../Models/ThemeConfig/FilterConfig"
import { MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
import { GeoOperations } from "../GeoOperations"

export type GeocodingCategory =
Expand Down Expand Up @@ -44,30 +42,24 @@ export type GeocodeResult = {
payload?: object,
source?: string
}
export type FilterPayload = { option: FilterConfigOption, filter: FilterConfig, layer: LayerConfig, index: number }
export type FilterResult = { category: "filter", osm_id: string, payload: FilterPayload }
export type LayerResult = {category: "layer", osm_id: string, payload: LayerConfig}
export type SearchResult =
| FilterResult
| { category: "theme", osm_id: string, payload: MinimalLayoutInformation }
| LayerResult
| GeocodeResult

export interface GeocodingOptions {
bbox?: BBox
}


export default interface GeocodingProvider<T extends SearchResult = SearchResult> {
export default interface GeocodingProvider {


search(query: string, options?: GeocodingOptions): Promise<T[]>
search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]>

/**
* @param query
* @param options
*/
suggest?(query: string, options?: GeocodingOptions): Store<T[]>
suggest?(query: string, options?: GeocodingOptions): Store<GeocodeResult[]>
}

export type ReverseGeocodingResult = Feature<Geometry, {
Expand Down
44 changes: 21 additions & 23 deletions src/Logic/Search/LayerSearch.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,41 @@
import GeocodingProvider, { GeocodingOptions, LayerResult, SearchResult } from "./GeocodingProvider"
import { SpecialVisualizationState } from "../../UI/SpecialVisualization"
import MoreScreen from "../../UI/BigComponents/MoreScreen"
import { ImmutableStore, Store } from "../UIEventSource"
import Constants from "../../Models/Constants"
import SearchUtils from "./SearchUtils"
import ThemeSearch from "./ThemeSearch"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"

export default class LayerSearch implements GeocodingProvider<LayerResult> {
export default class LayerSearch {

private readonly _state: SpecialVisualizationState
private readonly _suggestionLimit: number
private readonly _layerWhitelist : Set<string>
constructor(state: SpecialVisualizationState, suggestionLimit: number) {
constructor(state: SpecialVisualizationState) {
this._state = state
this._layerWhitelist = new Set(state.layout.layers.map(l => l.id).filter(id => Constants.added_by_default.indexOf(<any> id) < 0))
this._suggestionLimit = suggestionLimit
}

async search(query: string): Promise<LayerResult[]> {
return this.searchWrapped(query, 99)
}

suggest(query: string, options?: GeocodingOptions): Store<LayerResult[]> {
return new ImmutableStore(this.searchWrapped(query, this._suggestionLimit ?? 4))
static scoreLayers(query: string, layerWhitelist?: Set<string>): Record<string, number> {
const result: Record<string, number> = {}
for (const id in ThemeSearch.officialThemes.layers) {
if(layerWhitelist !== undefined && !layerWhitelist.has(id)){
continue
}
const keywords = ThemeSearch.officialThemes.layers[id]
const distance = SearchUtils.scoreKeywords(query, keywords)
result[id] = distance
}
return result
}


private searchWrapped(query: string, limit: number): LayerResult[] {
return this.searchDirect(query, limit)
}

public searchDirect(query: string, limit: number): LayerResult[] {
public search(query: string, limit: number): LayerConfig[] {
if (query.length < 1) {
return []
}
const scores = MoreScreen.scoreLayers(query, this._layerWhitelist)
const asList:(LayerResult & {score:number})[] = []
const scores = LayerSearch.scoreLayers(query, this._layerWhitelist)
const asList:({layer: LayerConfig, score:number})[] = []
for (const layer in scores) {
asList.push({
category: "layer",
payload: this._state.layout.getLayer(layer),
osm_id: layer,
layer: this._state.layout.getLayer(layer),
score: scores[layer]
})
}
Expand All @@ -47,6 +44,7 @@ export default class LayerSearch implements GeocodingProvider<LayerResult> {
return asList
.filter(sorted => sorted.score < 2)
.slice(0, limit)
.map(l => l.layer)
}


Expand Down
2 changes: 1 addition & 1 deletion src/Logic/Search/OpenStreetMapIdSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import GeocodingProvider, { GeocodingOptions, GeocodeResult } from "./GeocodingP
import { OsmId } from "../../Models/OsmFeature"
import { SpecialVisualizationState } from "../../UI/SpecialVisualization"

export default class OpenStreetMapIdSearch implements GeocodingProvider<GeocodeResult> {
export default class OpenStreetMapIdSearch implements GeocodingProvider {
private static readonly regex = /((https?:\/\/)?(www.)?(osm|openstreetmap).org\/)?(n|node|w|way|r|relation)[/ ]?([0-9]+)/

private static readonly types: Readonly<Record<string, "node" | "way" | "relation">> = {
Expand Down
75 changes: 75 additions & 0 deletions src/Logic/Search/SearchUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import Locale from "../../UI/i18n/Locale"
import { Utils } from "../../Utils"
import ThemeSearch from "./ThemeSearch"

export default class SearchUtils {


/** Applies special search terms, such as 'studio', 'osmcha', ...
* Returns 'false' if nothing is matched.
* Doesn't return control flow if a match is found (navigates to another page in this case)
*/
public static applySpecialSearch(searchTerm: string, ) {
searchTerm = searchTerm.toLowerCase()
if (!searchTerm) {
return false
}
if (searchTerm === "personal") {
window.location.href = ThemeSearch.createUrlFor({ id: "personal" }, undefined)
}
if (searchTerm === "bugs" || searchTerm === "issues") {
window.location.href = "https://github.com/pietervdvn/MapComplete/issues"
}
if (searchTerm === "source") {
window.location.href = "https://github.com/pietervdvn/MapComplete"
}
if (searchTerm === "docs") {
window.location.href = "https://github.com/pietervdvn/MapComplete/tree/develop/Docs"
}
if (searchTerm === "osmcha" || searchTerm === "stats") {
window.location.href = Utils.OsmChaLinkFor(7)
}
if (searchTerm === "studio") {
window.location.href = "./studio.html"
}
return false

}


/**
* Searches for the smallest distance in words; will split both the query and the terms
*
* SearchUtils.scoreKeywords("drinking water", {"en": ["A layer with drinking water points"]}, "en") // => 0
* SearchUtils.scoreKeywords("waste", {"en": ["A layer with drinking water points"]}, "en") // => 2
*
*/
public static scoreKeywords(query: string, keywords: Record<string, string[]> | string[], language?: string): number {
if(!keywords){
return Infinity
}
language ??= Locale.language.data
const queryParts = query.trim().split(" ").map(q => Utils.simplifyStringForSearch(q))
let terms: string[]
if (Array.isArray(keywords)) {
terms = keywords
} else {
terms = (keywords[language] ?? []).concat(keywords["*"])
}
const termsAll = Utils.NoNullInplace(terms).flatMap(t => t.split(" "))

let distanceSummed = 0
for (let i = 0; i < queryParts.length; i++) {
const q = queryParts[i]
let minDistance: number = 99
for (const term of termsAll) {
const d = Utils.levenshteinDistance(q, Utils.simplifyStringForSearch(term))
if (d < minDistance) {
minDistance = d
}
}
distanceSummed += minDistance
}
return distanceSummed
}
}
Loading

0 comments on commit bd3bddc

Please sign in to comment.