Skip to content

Commit

Permalink
poi search (#394)
Browse files Browse the repository at this point in the history
* poi search on every key stroke

* new autocomplete item, better usability

* fix poi search without location keywords

* proper marker Select

* change scale, not color

* keyboard support

* show popup and POI being used in route

* do not zoom if no location; fix bbox if single point; minor layout fix

* translation; move AddressParseResult into separate class and inject distanceFormat to reduce jest problems

* fetch more information from OSM

* minor fixes

* use overpass instead osm

* fetch on click; more i18n; minor fixes

* reorder

* one more exclusion

* added a few more POIs

* photon does not know public_transport:station (?)

* rename

* use overpass API

* use out center and let overpass do the work; fix a few bugs

* translate 'nearby'

* make 'AND NOT' possible to exclude military airports

* fix a few POI queries and i18n

* allow generic query in simplified 'photon' format

* workaround for initial search

* bug fix in merge

* support simple 'or' and 'and' queries with 'not'. use more overpass wizard query style

* special case for all value query

* avoid POI layer setup in most cases

* minor cosmetics

* simplify and avoid hack. UX is more clear too.

* formatting

* minor fix
  • Loading branch information
karussell authored Aug 12, 2024
1 parent 2670bcd commit b8b4cd5
Show file tree
Hide file tree
Showing 50 changed files with 1,142 additions and 71 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ This project uses
* the [codemirror](https://codemirror.net/) code editor for the custom model editor.
* many icons from Google's [open source font library](https://fonts.google.com/icons).
* many more open source projects - see the package.json

24 changes: 14 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"prettier": "2.8.8",
"source-map-loader": "^4.0.0",
"style-loader": "^3.3.1",
"ts-jest": "^29.0.5",
"ts-jest": "^29.1.4",
"ts-loader": "^9.4.2",
"typescript": "^5.1.3",
"webpack": "^5.75.0",
Expand Down
22 changes: 19 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getMapFeatureStore,
getMapOptionsStore,
getPathDetailsStore,
getPOIsStore,
getQueryStore,
getRouteStore,
getSettingsStore,
Expand Down Expand Up @@ -42,7 +43,8 @@ import PlainButton from '@/PlainButton'
import useAreasLayer from '@/layers/UseAreasLayer'
import useExternalMVTLayer from '@/layers/UseExternalMVTLayer'
import LocationButton from '@/map/LocationButton'
import { SettingsContext } from './contexts/SettingsContext'
import { SettingsContext } from '@/contexts/SettingsContext'
import usePOIsLayer from '@/layers/UsePOIsLayer'

export const POPUP_CONTAINER_ID = 'popup-container'
export const SIDEBAR_CONTENT_ID = 'sidebar-content'
Expand All @@ -56,6 +58,7 @@ export default function App() {
const [mapOptions, setMapOptions] = useState(getMapOptionsStore().state)
const [pathDetails, setPathDetails] = useState(getPathDetailsStore().state)
const [mapFeatures, setMapFeatures] = useState(getMapFeatureStore().state)
const [pois, setPOIs] = useState(getPOIsStore().state)

const map = getMap()

Expand All @@ -68,6 +71,7 @@ export default function App() {
const onMapOptionsChanged = () => setMapOptions(getMapOptionsStore().state)
const onPathDetailsChanged = () => setPathDetails(getPathDetailsStore().state)
const onMapFeaturesChanged = () => setMapFeatures(getMapFeatureStore().state)
const onPOIsChanged = () => setPOIs(getPOIsStore().state)

getSettingsStore().register(onSettingsChanged)
getQueryStore().register(onQueryChanged)
Expand All @@ -77,6 +81,7 @@ export default function App() {
getMapOptionsStore().register(onMapOptionsChanged)
getPathDetailsStore().register(onPathDetailsChanged)
getMapFeatureStore().register(onMapFeaturesChanged)
getPOIsStore().register(onPOIsChanged)

onQueryChanged()
onInfoChanged()
Expand All @@ -85,6 +90,7 @@ export default function App() {
onMapOptionsChanged()
onPathDetailsChanged()
onMapFeaturesChanged()
onPOIsChanged()

return () => {
getSettingsStore().register(onSettingsChanged)
Expand All @@ -95,6 +101,7 @@ export default function App() {
getMapOptionsStore().deregister(onMapOptionsChanged)
getPathDetailsStore().deregister(onPathDetailsChanged)
getMapFeatureStore().deregister(onMapFeaturesChanged)
getPOIsStore().deregister(onPOIsChanged)
}
}, [])

Expand All @@ -108,11 +115,19 @@ export default function App() {
usePathsLayer(map, route.routingResult.paths, route.selectedPath, query.queryPoints)
useQueryPointsLayer(map, query.queryPoints)
usePathDetailsLayer(map, pathDetails)
usePOIsLayer(map, pois)

const isSmallScreen = useMediaQuery({ query: '(max-width: 44rem)' })
return (
<SettingsContext.Provider value={settings}>
<div className={styles.appWrapper}>
<MapPopups map={map} pathDetails={pathDetails} mapFeatures={mapFeatures} />
<MapPopups
map={map}
pathDetails={pathDetails}
mapFeatures={mapFeatures}
poiState={pois}
query={query}
/>
<ContextMenu map={map} route={route} queryPoints={query.queryPoints} />
{isSmallScreen ? (
<SmallScreenLayout
Expand Down Expand Up @@ -177,7 +192,7 @@ function LargeScreenLayout({ query, route, map, error, mapOptions, encodedValues
drawAreas={drawAreas}
/>
)}
<Search points={query.queryPoints} />
<Search points={query.queryPoints} map={map} />
<div>{!error.isDismissed && <ErrorMessage error={error} />}</div>
<RoutingResults
info={route.routingResult.info}
Expand Down Expand Up @@ -224,6 +239,7 @@ function SmallScreenLayout({ query, route, map, error, mapOptions, encodedValues
error={error}
encodedValues={encodedValues}
drawAreas={drawAreas}
map={map}
/>
</div>
<div className={styles.smallScreenMap}>
Expand Down
5 changes: 4 additions & 1 deletion src/Converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ export function milliSecondsToText(ms: number) {
return (hourText ? hourText + ' ' : '') + minutes + ' min'
}

const distanceFormat = new Intl.NumberFormat(navigator.language, { maximumFractionDigits: 1 })
let distanceFormat: Intl.NumberFormat = new Intl.NumberFormat('en', { maximumFractionDigits: 1 })
export function setDistanceFormat(_distanceFormat: Intl.NumberFormat) {
distanceFormat = _distanceFormat
}

export function metersToText(meters: number, showDistanceInMiles: boolean, forceSmallUnits: boolean = false) {
if (showDistanceInMiles) {
Expand Down
44 changes: 19 additions & 25 deletions src/NavBar.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { coordinateToText } from '@/Converters'
import { Bbox } from '@/api/graphhopper'
import Dispatcher from '@/stores/Dispatcher'
import { ClearPoints, SelectMapLayer, SetBBox, SetQueryPoints, SetVehicleProfile } from '@/actions/Actions'
// import the window like this so that it can be mocked during testing
import { window } from '@/Window'
import QueryStore, {
Coordinate,
getBBoxFromCoord,
QueryPoint,
QueryPointType,
QueryStoreState,
} from '@/stores/QueryStore'
import QueryStore, { getBBoxFromCoord, QueryPoint, QueryPointType, QueryStoreState } from '@/stores/QueryStore'
import MapOptionsStore, { MapOptionsStoreState } from './stores/MapOptionsStore'
import { getApi } from '@/api/Api'
import { ApiImpl, getApi } from '@/api/Api'
import { AddressParseResult } from '@/pois/AddressParseResult'
import { getQueryStore } from '@/stores/Stores'

export default class NavBar {
private readonly queryStore: QueryStore
Expand Down Expand Up @@ -116,6 +111,19 @@ export default class NavBar {
if (parsedPoints.some(p => !p.isInitialized && p.queryText.length > 0)) {
const promises = parsedPoints.map(p => {
if (p.isInitialized) return Promise.resolve(p)
const result = AddressParseResult.parse(p.queryText, false)
if (result.hasPOIs() && result.location) {
// two stage POI search: 1. use extracted location to get coordinates 2. do reverse geocoding with this coordinates
return getApi()
.geocode(result.location, 'nominatim')
.then(res => {
if (res.hits.length == 0) return p
getApi()
.reverseGeocode(result.query, res.hits[0].extent)
.then(res => AddressParseResult.handleGeocodingResponse(res, result))
return p
})
}
return (
getApi()
.geocode(p.queryText, 'nominatim')
Expand All @@ -129,7 +137,7 @@ export default class NavBar {
}
})
// if the geocoding request fails we just keep the point as it is, just as if no results were found
.catch(() => Promise.resolve(p))
.catch(() => p)
)
})
const points = await Promise.all(promises)
Expand All @@ -151,7 +159,7 @@ export default class NavBar {
const bbox =
initializedPoints.length == 1
? getBBoxFromCoord(initializedPoints[0].coordinate)
: NavBar.getBBoxFromUrlPoints(initializedPoints.map(p => p.coordinate))
: ApiImpl.getBBoxPoints(initializedPoints.map(p => p.coordinate))
if (bbox) Dispatcher.dispatch(new SetBBox(bbox))
return Dispatcher.dispatch(new SetQueryPoints(points))
}
Expand All @@ -169,18 +177,4 @@ export default class NavBar {
this.mapStore.state
).toString()
}

private static getBBoxFromUrlPoints(urlPoints: Coordinate[]): Bbox | null {
const bbox: Bbox = urlPoints.reduce(
(res: Bbox, c) => [
Math.min(res[0], c.lng),
Math.min(res[1], c.lat),
Math.max(res[2], c.lng),
Math.max(res[3], c.lat),
],
[180, 90, -180, -90] as Bbox
)
// return null if the bbox is not valid, e.g. if no url points were given at all
return bbox[0] < bbox[2] && bbox[1] < bbox[3] ? bbox : null
}
}
17 changes: 17 additions & 0 deletions src/actions/Actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Action } from '@/stores/Dispatcher'
import { Coordinate, QueryPoint } from '@/stores/QueryStore'
import { ApiInfo, Bbox, Path, RoutingArgs, RoutingProfile, RoutingResult } from '@/api/graphhopper'
import { PathDetailsPoint } from '@/stores/PathDetailsStore'
import { POI } from '@/stores/POIsStore'
import { Settings } from '@/stores/SettingsStore'

export class InfoReceived implements Action {
Expand Down Expand Up @@ -246,3 +247,19 @@ export class UpdateSettings implements Action {
this.updatedSettings = updatedSettings
}
}

export class SelectPOI implements Action {
readonly selected: POI | null

constructor(selected: POI | null) {
this.selected = selected
}
}

export class SetPOIs implements Action {
readonly pois: POI[]

constructor(pois: POI[]) {
this.pois = pois
}
}
Loading

0 comments on commit b8b4cd5

Please sign in to comment.