Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into overpass-search
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/services/__tests__/osmApi.test.ts
  • Loading branch information
zbycz committed Sep 30, 2023
2 parents 5c1df9d + 1b6af16 commit cdf2e28
Show file tree
Hide file tree
Showing 22 changed files with 595 additions and 151 deletions.
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@

Lets create a universal OpenStreetMap app for broad public. It should be as easy to use as Google Maps, including clickable POIs and editing capabilites. See also [SotM 2021 talk](https://github.com/zbycz/osmapp-talk).

- **production**: https://osmapp.org
- examples: [Empire State Building](https://osmapp.org/way/34633854#17.00/40.7483/-73.9864), [Prague Castle](https://osmapp.org/relation/3312247#17.00/50.0900/14.4000) or click just anything
- **master branch**: https://osmapp-git-master-zbycz.vercel.app/
- it is currently undergoing a [major refactoring to TailwindCSS](https://github.com/zbycz/osmapp/issues/165) thanks to [@Flohhhhh](https://github.com/Flohhhhh)
- until finished, some components would be MUI and some new design
- some functionality may be broken
- master branch: https://osmapp.org
- examples: [Empire State Building](https://osmapp.org/way/34633854#17.00/40.7483/-73.9864), [Prague Castle](https://osmapp.org/relation/3312247#17.00/50.0900/14.4000) or click just anything

## How to contribute 🐱‍💻

Expand All @@ -24,10 +20,11 @@ You may [add issues](https://github.com/zbycz/osmapp/issues) here on github, or
## Features 🗺 📱 🖥

- **clickable map** – poi, cities, localities, ponds (more coming soon)
- **info panel** – images from Wikipedia, Mapillary or Fody
- **info panel** – images from Wikipedia, Mapillary or Fody, line numbers on public transport stops
- **editing** – for anonymous users inserts a note
- **search engine** – try for example "Tesco, London"
- **vector maps** – with the possibility of tilting to 3D (drag the compass, or two fingers drag)
- **3D terrain** - tilt to 3D and then click terrain icon to show 3D terrain
- **tourist map** – from MapTiler: vector, including marked routes
- **layer switcher** – still basic, but you can add your own layers
- **mobile applications** – see [osmapp.org/install](https://osmapp.org/install)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"js-cookie": "^2.2.1",
"jss": "^10.6.0",
"lodash": "^4.17.21",
"maplibre-gl": "^1.14.0",
"maplibre-gl": "^3.3.1",
"next": "^13.4.3",
"next-cookies": "^2.0.3",
"next-pwa": "^5.2.21",
Expand Down
4 changes: 2 additions & 2 deletions src/components/FeaturePanel/Coordinates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ const LinkItem = ({ href, label }) => (

// Our map uses 512 tiles, so our zoom is "one less"
// https://wiki.openstreetmap.org/wiki/Zoom_levels#Mapbox_GL
const MAPBOXGL_ZOOM_DIFFERENCE = 1;
const MAPLIBREGL_ZOOM_DIFFERENCE = 1;

const useGetItems = ([lon, lat]: PositionBoth) => {
const { feature } = useFeatureContext();
const { view } = useMapStateContext();
const [ourZoom] = view;

const zoom = parseFloat(ourZoom) + MAPBOXGL_ZOOM_DIFFERENCE;
const zoom = parseFloat(ourZoom) + MAPLIBREGL_ZOOM_DIFFERENCE;
const zoomInt = Math.round(zoom);
const osmQuery = feature?.osmMeta?.id
? `${feature.osmMeta.type}/${feature.osmMeta.id}`
Expand Down
1 change: 1 addition & 0 deletions src/components/FeaturePanel/FeaturePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const featuredKeys = [
'contact:mobile',
'opening_hours',
'description',
'fhrs:id',
];

const FeaturePanel = () => {
Expand Down
6 changes: 5 additions & 1 deletion src/components/FeaturePanel/FeaturedTag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import WebsiteRenderer from './renderers/WebsiteRenderer';
import OpeningHoursRenderer from './renderers/OpeningHoursRenderer';
import PhoneRenderer from './renderers/PhoneRenderer';
import { EditIconButton } from './helpers/EditIconButton';
import { FoodHygieneRatingSchemeRenderer } from './renderers/FoodHygieneRatingScheme';

const Wrapper = styled.div`
position: relative;
Expand All @@ -29,13 +30,16 @@ const Value = styled.div`
`;

const DefaultRenderer = ({ v }) => v;
const renderers = {
const renderers: {
[key: string]: React.FC<{ v: string }>;
} = {
website: WebsiteRenderer,
'contact:website': WebsiteRenderer,
phone: PhoneRenderer,
'contact:phone': PhoneRenderer,
'contact:mobile': PhoneRenderer,
opening_hours: OpeningHoursRenderer,
'fhrs:id': FoodHygieneRatingSchemeRenderer,
};

export const FeaturedTag = ({ k, v, onEdit }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/FeaturePanel/IdSchemeFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const Table = styled.table`
th {
width: 140px;
max-width: 140px;
color: rgba(0, 0, 0, 0.54);
color: ${({ theme }) => theme.palette.text.secondary};
text-align: left;
font-weight: normal;
vertical-align: baseline;
Expand Down
48 changes: 47 additions & 1 deletion src/components/FeaturePanel/PublicTransport/LineNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,52 @@ interface LineNumberProps {
color: string;
}

/**
* A function to map a color name to hex. When a color is not found, it is returned as is, the same goes for hex colors.
* @param color The color to map to hex
* @returns The color in hex format, e.g. #ff0000
*/
function mapColorToHex(color: string) {
switch (color.toLowerCase()) {
case 'black':
return '#000000';
case 'gray':
case 'grey':
return '#808080';
case 'maroon':
return '#800000';
case 'olive':
return '#808000';
case 'green':
return '#008000';
case 'teal':
return '#008080';
case 'navy':
return '#000080';
case 'purple':
return '#800080';
case 'white':
return '#ffffff';
case 'silver':
return '#c0c0c0';
case 'red':
return '#ff0000';
case 'yellow':
return '#ffff00';
case 'lime':
return '#00ff00';
case 'aqua':
case 'cyan':
return '#00ffff';
case 'blue':
return '#0000ff';
case 'fuchsia':
case 'magenta':
return '#ff00ff';
default:
return color;
}
}
/**
* A function to determine whether the text color should be black or white
* @param hexBgColor The background color in hex format, e.g. #ff0000
Expand All @@ -26,7 +72,7 @@ export const LineNumber: React.FC<LineNumberProps> = ({ name, color }) => {
let bgcolor: string;
if (!color) bgcolor = darkmode ? '#898989' : '#dddddd';
// set the default color
else bgcolor = color.toLowerCase();
else bgcolor = mapColorToHex(color);

const divStyle: React.CSSProperties = {
backgroundColor: bgcolor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ export const PublicTransport: React.FC<PublicTransportProps> = ({ tags }) => {
const isPublicTransport =
Object.keys(tags).includes('public_transport') ||
tags.railway === 'station' ||
tags.railway === 'halt' ||
tags.railway === 'subway_entrance';
tags.railway === 'halt';

if (!isPublicTransport) {
return null;
Expand Down
10 changes: 3 additions & 7 deletions src/components/FeaturePanel/PublicTransport/requestRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,12 @@ export interface LineInformation {
export async function requestLines(
featureType: 'node' | 'way' | 'relation',
id: number,
radius = 150,
) {
// use the overpass api to request the lines in
const overpassQuery = `[out:csv(ref, colour; false; ';')];
${featureType}(${id})->.center;
(
node(around.center:${radius})["public_transport"="stop_position"];
nw(around.center:${radius})["highway"="bus_stop"];
nwr(around.center:${radius})["amenity"="ferry_terminal"];
) -> .stops;
${featureType}(${id});
rel(bn)["public_transport"="stop_area"];
node(r: "stop") -> .stops;
rel(bn.stops)["route"~"bus|train|tram|subway|light_rail|ferry|monorail"];
out;`;

Expand Down
105 changes: 105 additions & 0 deletions src/components/FeaturePanel/renderers/FoodHygieneRatingScheme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { useEffect, useState } from 'react';
import { Tooltip, Typography } from '@material-ui/core';
import RestaurantIcon from '@material-ui/icons/Restaurant';
import styled from 'styled-components';
import { getEstablishmentRatingValue } from '../../../services/fhrsApi';

const useLoadingState = () => {
const [rating, setRating] = useState<number>();
const [error, setError] = useState<string>();
const [loading, setLoading] = useState(true);

const finishRating = (payload) => {
setLoading(false);
setRating(payload);
};

const startRating = () => {
setLoading(true);
setRating(undefined);
setError(undefined);
};

const failRating = () => {
setError('Could not load rating');
setLoading(false);
};

return { rating, error, loading, startRating, finishRating, failRating };
};

const Wrapper = styled.div`
display: flex;
gap: 0.5rem;
.MuiRating-root {
margin-top: -2px;
}
`;

const RatingRound = styled.span`
border-radius: 50%;
background-color: #1a6500;
color: #fff;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
font-weight: bold;
position: relative;
top: -1px;
`;

export const FoodHygieneRatingSchemeRenderer = ({ v }) => {
const { rating, error, loading, startRating, finishRating, failRating } =
useLoadingState();

useEffect(() => {
const loadData = async () => {
startRating();
const ratingValue = await getEstablishmentRatingValue(v);
if (Number.isNaN(rating)) {
failRating();
}
finishRating(ratingValue);
};

loadData();
}, []);

if (loading) {
return (
<>
<span className="dotloader" />
<span className="dotloader" />
<span className="dotloader" />
</>
);
}

return (
<>
<RestaurantIcon fontSize="small" />
<Wrapper>
<Tooltip
arrow
interactive
title="Food Hygiene Rating Scheme (only in UK)"
placement="bottom-end"
>
<a
href={`https://ratings.food.gov.uk/business/${v}`}
className="colorInherit"
>
FHRS{' '}
{Number.isNaN(rating) || error ? (
<Typography color="textSecondary">
(Error while fetching rating)
</Typography>
) : (
<RatingRound>{rating}</RatingRound>
)}
</a>
</Tooltip>
</Wrapper>
</>
);
};
6 changes: 4 additions & 2 deletions src/components/Map/BrowserMap.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import 'maplibre-gl/dist/maplibre-gl.css';
import maplibregl from 'maplibre-gl';
import { useAddMapEvent, useMapEffect, useMobileMode } from '../helpers';
import { useMapStateContext } from '../utils/MapStateContext';
import { useFeatureContext } from '../utils/FeatureContext';
Expand All @@ -10,6 +9,8 @@ import { useUpdateViewOnMove } from './behaviour/useUpdateViewOnMove';
import { useUpdateStyle } from './behaviour/useUpdateStyle';
import { useInitMap } from './behaviour/useInitMap';
import { Translation } from '../../services/intl';
import { useToggleTerrainControl } from './behaviour/useToggleTerrainControl';
import { isWebglSupported } from './helpers';

const useOnMapLoaded = useAddMapEvent((map, onMapLoaded) => ({
eventType: 'load',
Expand All @@ -32,7 +33,7 @@ const NotSupportedMessage = () => (
// TODO https://cdn.klokantech.com/openmaptiles-language/v1.0/openmaptiles-language.js + use localized name in FeaturePanel

const BrowserMap = ({ onMapLoaded }) => {
if (!maplibregl.supported()) {
if (!isWebglSupported()) {
onMapLoaded();
return <NotSupportedMessage />;
}
Expand All @@ -46,6 +47,7 @@ const BrowserMap = ({ onMapLoaded }) => {
const { viewForMap, setViewFromMap, setBbox, activeLayers } =
useMapStateContext();
useUpdateViewOnMove(map, setViewFromMap, setBbox);
useToggleTerrainControl(map);
useUpdateMap(map, viewForMap);
useUpdateStyle(map, activeLayers);

Expand Down
38 changes: 38 additions & 0 deletions src/components/Map/behaviour/useToggleTerrainControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* eslint-disable no-underscore-dangle */
import maplibregl from 'maplibre-gl';
import { useAddMapEvent } from '../../helpers';

class OsmappTerrainControl extends maplibregl.TerrainControl {
_toggleTerrain = () => {
if (this._map.getTerrain()) {
this._map.setTerrain(null);
this._map.setMaxPitch(60);
} else {
this._map.setTerrain(this.options);
this._map.setMaxPitch(85);
}
this._updateTerrainIcon();
};
}

const terrainControl = new OsmappTerrainControl({
source: 'terrain',
exaggeration: 1,
});

let added = false;

export const useToggleTerrainControl = useAddMapEvent((map) => ({
eventType: 'move',
eventHandler: () => {
if (map.getPitch() > 0) {
if (!added) {
map.addControl(terrainControl);
added = true;
}
} else if (added) {
map.removeControl(terrainControl);
added = false;
}
},
}));
6 changes: 6 additions & 0 deletions src/components/Map/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ export const OSMAPP_SOURCES = {
'terrain-rgb': {
url: `https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=${apiKey}`,
type: 'raster-dem' as const,
tileSize: 256,
},
terrain: {
url: `https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=${apiKey}`,
type: 'raster-dem' as const,
tileSize: 256,
},
outdoor: {
url: `https://api.maptiler.com/tiles/outdoor/tiles.json?key=${apiKey}`,
Expand Down
Loading

0 comments on commit cdf2e28

Please sign in to comment.