Skip to content

Commit

Permalink
Merge pull request #73 from r00tat/feature/place-search
Browse files Browse the repository at this point in the history
Geocoding von Adressen und Orten
  • Loading branch information
r00tat authored May 22, 2024
2 parents b38e895 + 553fa88 commit be93734
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 68 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"firebaseui": "^6.1.0",
"geofire-common": "^6.0.0",
"googleapis": "^137.1.0",
"haversine-distance": "^1.2.1",
"leaflet": "^1.9.4",
"leaflet-geometryutil": "^0.10.3",
"leaflet-rotatedmarker": "^0.2.0",
Expand Down
21 changes: 21 additions & 0 deletions src/common/osm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export interface OSMPlace {
place_id: number;
licence: string;
osm_type: string;
osm_id: number;
lat: string;
lon: string;
category?: string;
type?: string;
place_rank: number;
importance: number;
addresstype?: string;
name: string;
display_name: string;
boundingbox: [number, number, number, number];
distance?: number;
}

export interface PlacesResponse {
places?: OSMPlace[];
}
28 changes: 11 additions & 17 deletions src/components/Map/SearchButton.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import SearchIcon from "@mui/icons-material/Search";
import Box from "@mui/material/Box";
import Fab from "@mui/material/Fab";
import SearchIcon from "@mui/icons-material/Search";
import InputDialog from "../dialogs/InputDialog";
import { useCallback, useState } from "react";
import { useMap } from "react-leaflet";
import { defaultGeoPosition } from "../../common/geo";
import { OSMPlace, PlacesResponse } from "../../common/osm";
import useFirebaseLogin from "../../hooks/useFirebaseLogin";
import { places_v1 } from "googleapis/build/src/apis/places/v1";
import { FirecallItemMarker } from "../FirecallItems/elements/FirecallItemMarker";
import useFirecallItemAdd from "../../hooks/useFirecallItemAdd";
import { defaultGeoPosition } from "../../common/geo";
import { useMap } from "react-leaflet";
// import { searchPlace } from "../actions/maps/places";

interface PlacesResponse {
places?: places_v1.Schema$GoogleMapsPlacesV1Place[];
}
import InputDialog from "../dialogs/InputDialog";
import { FirecallItemMarker } from "../FirecallItems/elements/FirecallItemMarker";

function useSearchPlace() {
const { isSignedIn, user } = useFirebaseLogin();
Expand Down Expand Up @@ -44,14 +39,13 @@ function useAddPlace() {
const addFirecallItem = useFirecallItemAdd();
const map = useMap();
return useCallback(
async (query: string, place: places_v1.Schema$GoogleMapsPlacesV1Place) => {
async (query: string, place: OSMPlace) => {
const m = new FirecallItemMarker({
name: place.displayName?.text || query,
name: place.name || place.display_name || query,
type: "marker",
color: place.iconBackgroundColor || "blue",
lat: place.location?.latitude || defaultGeoPosition.lat,
lng: place.location?.longitude || defaultGeoPosition.lng,
beschreibung: `${place.formattedAddress}\n${place.googleMapsUri}`,
lat: Number.parseFloat(place.lat) || defaultGeoPosition.lat,
lng: Number.parseFloat(place.lon) || defaultGeoPosition.lng,
beschreibung: `${place.name}\n${place.display_name}\n${place.licence}`,
});

const item = await addFirecallItem(m.data());
Expand Down
58 changes: 32 additions & 26 deletions src/components/actions/maps/places.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,48 @@
import { google } from "googleapis";
import haversine from "haversine-distance";
import { defaultGeoPosition, GeoPosition } from "../../../common/geo";
import { OSMPlace } from "../../../common/osm";

export async function searchPlace(
query: string,
{
position,
maxResults = 3,
}: {
position?: GeoPosition;
maxResults?: number;
} = {}
) {
const auth = new google.auth.GoogleAuth({
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
const uri = `https://nominatim.openstreetmap.org/search?${new URLSearchParams(
{
q: `${query}, Österreich`,
format: "jsonv2",
limit: "10",
}
)}`;
// console.info(`uri: ${uri}`);
const result = await fetch(uri, {
headers: {
"User-Agent": "Hydrantenkarte https://hydrant.ffnd.at",
Accept: "application/json",
},
});

const places = google.places({ version: "v1", auth });
const bodyText = await result.text();
if (result.status !== 200) {
throw new Error(`Geocoding failed ${result.status} ${bodyText}`);
}

const result = (
await places.places.searchText({
fields:
// "places(id,formattedAddress,internationalPhoneNumber,location,googleMapsUri,iconBackgroundColor,displayName(text))",
"*",
console.info(`geocoding result: ${result.status} ${bodyText}`);
const results: OSMPlace[] = JSON.parse(bodyText);

requestBody: {
maxResultCount: 3,
languageCode: "de",
textQuery: query,
locationBias: {
circle: {
center: {
latitude: (position || defaultGeoPosition).lat,
longitude: (position || defaultGeoPosition).lng,
},
radius: 30000,
},
},
},
})
).data.places;
results.forEach(
(p) =>
(p.distance = haversine(
{ lat: Number.parseFloat(p.lat), lon: Number.parseFloat(p.lon) },
(position || defaultGeoPosition)?.toGeoObject()
))
);
results.sort((a, b) => (a.distance || 0) - (b.distance || 0));

return result;
return results.slice(0, maxResults);
}
20 changes: 12 additions & 8 deletions src/pages/api/places.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { ErrorResponse } from "./responses";
import userRequired from "../../server/auth/userRequired";
import { defaultGeoPosition, GeoPosition } from "../../common/geo";
import { PlacesResponse } from "../../common/osm";
import { searchPlace } from "../../components/actions/maps/places";
import { places_v1 } from "googleapis/build/src/apis/places/v1";

type PlacesResponse = {
places?: places_v1.Schema$GoogleMapsPlacesV1Place[];
};
import userRequired from "../../server/auth/userRequired";
import { ErrorResponse } from "./responses";

async function POST(
req: NextApiRequest,
Expand All @@ -19,7 +16,14 @@ async function POST(
if (!req.body.query) {
return res.status(400).json({ error: "Missing parameter query" });
}
const places = await searchPlace(req.body.query);
const pos = GeoPosition.fromLatLng([
req.body?.position?.lat || defaultGeoPosition.lat,
req.body?.position?.lng || defaultGeoPosition.lng,
]);
const places = await searchPlace(req.body.query, {
position: pos,
maxResults: req.body?.maxResults || 3,
});
return res.json({ places });
}

Expand Down
39 changes: 22 additions & 17 deletions src/worker/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { initializeApp } from 'firebase/app';
import { initializeApp } from "firebase/app";
import {
MessagePayload,
getMessaging,
onBackgroundMessage,
} from 'firebase/messaging/sw';
import { ChatMessage } from '../common/chat';
import { workboxSetup } from './wb';
} from "firebase/messaging/sw";
import { ChatMessage } from "../common/chat";
import { workboxSetup } from "./wb";

declare let self: ServiceWorkerGlobalScope;

Expand All @@ -15,10 +15,10 @@ declare let self: ServiceWorkerGlobalScope;
self.__WB_DISABLE_DEV_LOGS = true;

const firebaseConfig = JSON.parse(
process.env.NEXT_PUBLIC_FIREBASE_APIKEY || '{}'
process.env.NEXT_PUBLIC_FIREBASE_APIKEY || "{}"
);

const scope = 'sw:' + self.registration.scope.replace(/^.*\//, '');
const scope = "sw:" + self.registration.scope.replace(/^.*\//, "");

console.info(
`[${scope}] starting background service worker with scope ${self.registration.scope}!`
Expand All @@ -39,13 +39,13 @@ workboxSetup();

// console.info(`[${scope}] self.reg`, self.registration);

self.registration.addEventListener('updatefound', (ev) => {
self.registration.addEventListener("updatefound", (ev) => {
console.info(`[${scope}] update found! `, ev);
});

// self.registration.update();

addEventListener('message', (event) => {
addEventListener("message", (event) => {
console.log(
`[${scope}] Message from navigator received: ${JSON.stringify(event.data)}`
);
Expand All @@ -65,24 +65,24 @@ addEventListener('message', (event) => {
// }
});

addEventListener('notificationclick', (ev) => {
addEventListener("notificationclick", (ev) => {
const event = ev as NotificationEvent;
console.log('On notification click: ', event.action);
console.log("On notification click: ", event.action);
event.notification.close();

// This looks to see if the current is already open and
// focuses if it is
event.waitUntil(
self.clients
.matchAll({
type: 'window',
type: "window",
})
.then((clientList) => {
for (const client of clientList) {
if (client.url === '/chat' && 'focus' in client)
if (client.url === "/chat" && "focus" in client)
return client.focus();
}
if (self.clients.openWindow) return self.clients.openWindow('/chat');
if (self.clients.openWindow) return self.clients.openWindow("/chat");
})
);
});
Expand All @@ -92,6 +92,11 @@ console.info(`[${scope}] firebase messaging scope, starting messaging`);
initializeApp(firebaseConfig);

const messaging = getMessaging();

interface NotificationOptionsWithActions extends NotificationOptions {
actions?: { action: string; title: string }[];
}

// If you would like to customize notifications that are received in the
// background (Web app is closed or not in browser focus) then you should
// implement this optional method.
Expand All @@ -107,13 +112,13 @@ onBackgroundMessage(messaging, function (payload: MessagePayload) {
if (payload.data) {
const message: ChatMessage = payload.data as unknown as ChatMessage;
const notificationTitle = `Einsatz Chat: ${message.name || message.email}`;
const notificationOptions: NotificationOptions = {
const notificationOptions: NotificationOptionsWithActions = {
body: message.message,
icon: '/app-icon.png',
icon: "/app-icon.png",
actions: [
{
action: 'chat',
title: 'Open Chat',
action: "chat",
title: "Open Chat",
},
],
};
Expand Down

0 comments on commit be93734

Please sign in to comment.