mirror of
https://github.com/Findus23/vienna-cycling-quality.git
synced 2024-09-09 04:13:48 +02:00
many custom changes
This commit is contained in:
parent
fef94fc6f4
commit
95c6c93ca7
17 changed files with 2654 additions and 968 deletions
3331
package-lock.json
generated
3331
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -13,7 +13,8 @@
|
|||
"@headlessui/react": "^1.7.18",
|
||||
"@nanostores/react": "^0.7.2",
|
||||
"@nanostores/router": "^0.13.0",
|
||||
"@turf/turf": "^6.5.0",
|
||||
"@turf/turf": "^7.0.0-alpha.114",
|
||||
"immutable": "^5.0.0-beta.5",
|
||||
"maplibre-gl": "^4.1.1",
|
||||
"pmtiles": "^3.0.5",
|
||||
"react": "^18.2.0",
|
||||
|
@ -22,14 +23,19 @@
|
|||
"tailwind-merge": "^2.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||
"@typescript-eslint/parser": "^7.2.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.6",
|
||||
"postcss": "^8.4.38",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.2.0"
|
||||
}
|
||||
|
|
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
2
publish.sh
Executable file
2
publish.sh
Executable file
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
rsync -rvzP ./dist/ lukas@lw1.at:/var/www/vienna-cycling-quality/ --fuzzy --delete-after -v
|
20
src/App.tsx
20
src/App.tsx
|
@ -1,23 +1,23 @@
|
|||
import * as turf from '@turf/turf'
|
||||
import type {LngLatBoundsLike} from 'react-map-gl/maplibre'
|
||||
import {CqiMap} from "./page_map/CqiMap.tsx";
|
||||
import "./base.css"
|
||||
|
||||
function App() {
|
||||
|
||||
const berlinInnenstadtBbox = [16.18278, 48.11833, 16.58, 48.32306] satisfies ReturnType<typeof turf.bbox>
|
||||
const maxBounds = turf.bbox(
|
||||
turf.buffer(turf.bboxPolygon(berlinInnenstadtBbox), 250, {
|
||||
units: 'meters',
|
||||
}),
|
||||
) as LngLatBoundsLike
|
||||
|
||||
const viennabbox = [16.18278, 48.11833, 16.58, 48.32306] as LngLatBoundsLike
|
||||
// const maxBounds = bbox(
|
||||
// buffer(bboxPolygon(berlinInnenstadtBbox), 250, {
|
||||
// units: 'meters',
|
||||
// }),
|
||||
// ) as LngLatBoundsLike
|
||||
// console.log(maxBounds)
|
||||
const minZoom = 10
|
||||
|
||||
return (
|
||||
<>
|
||||
<CqiMap maxBounds={maxBounds} minZoom={minZoom}/>
|
||||
<CqiMap maxBounds={viennabbox} minZoom={minZoom}/>
|
||||
<div className="absolute bottom-3 left-3 rounded bg-white/80 px-1 py-0.5 text-xs">
|
||||
Datenstand: 02.03.2024
|
||||
Datenstand: 24.03.2024
|
||||
</div>
|
||||
|
||||
</>
|
||||
|
|
|
@ -3,7 +3,7 @@ import maplibregl from 'maplibre-gl'
|
|||
import 'maplibre-gl/dist/maplibre-gl.css'
|
||||
import * as pmtiles from 'pmtiles'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Map, type ViewStateChangeEvent } from 'react-map-gl/maplibre'
|
||||
import {Layer, Map, Source, type ViewStateChangeEvent} from 'react-map-gl/maplibre'
|
||||
import {
|
||||
$clickedMapData,
|
||||
$mapLoaded,
|
||||
|
@ -14,6 +14,7 @@ import {
|
|||
type SearchParamBaseMap,
|
||||
} from './store'
|
||||
import { roundPositionForURL } from './utils/roundNumber'
|
||||
import * as Immutable from "immutable";
|
||||
|
||||
type Props = {
|
||||
initialViewState: MapSearchParam
|
||||
|
@ -56,6 +57,8 @@ export const BaseMap = ({ initialViewState, interactiveLayerIds, boxZoom, childr
|
|||
|
||||
const latLngZoom = paramMapParse(params.map)
|
||||
|
||||
const rasterAttribution='Data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>, <a href="http://viewfinderpanoramas.org/">SRTM</a>, <a href="https://portal.opentopography.org/datasetMetadata?otCollectionID=OT.032021.4326.2">NASADEM</a>, <a href="https://worldcover2021.esa.int">ESA WorldCover</a>; Maps © <a href="https://www.tracestrack.com/">Tracestrack</a>'
|
||||
|
||||
return (
|
||||
<Map
|
||||
initialViewState={{
|
||||
|
@ -65,7 +68,7 @@ export const BaseMap = ({ initialViewState, interactiveLayerIds, boxZoom, childr
|
|||
longitude: latLngZoom.longitude || initialViewState.longitude,
|
||||
}}
|
||||
// Style: https://cloud.maptiler.com/maps/dataviz/
|
||||
mapStyle="https://api.maptiler.com/maps/dataviz/style.json?key=0opClOQz7xpg46NzNSOo"
|
||||
// mapStyle="https://api.maptiler.com/maps/dataviz/style.json?key=0opClOQz7xpg46NzNSOo"
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
boxZoom={boxZoom || true}
|
||||
// hash
|
||||
|
@ -80,6 +83,16 @@ export const BaseMap = ({ initialViewState, interactiveLayerIds, boxZoom, childr
|
|||
onMouseLeave={() => setCursorStyle('grab')}
|
||||
onClick={(event) => $clickedMapData.set(event.features)}
|
||||
>
|
||||
<Source
|
||||
type="raster"
|
||||
tiles={['https://maps.lw1.at/tiles/1.0.0/tracestack/webmercator_hq/{z}/{x}/{y}.png']}
|
||||
tileSize={512}
|
||||
attribution={rasterAttribution}
|
||||
>
|
||||
<Layer type="raster"></Layer>
|
||||
|
||||
</Source>
|
||||
|
||||
{children}
|
||||
</Map>
|
||||
)
|
||||
|
|
57
src/base.css
Normal file
57
src/base.css
Normal file
|
@ -0,0 +1,57 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
/* https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
|
||||
https://lukechannings.com/blog/2021-06-09-does-safari-15-fix-the-vh-bug/ */
|
||||
.computed-h-screen {
|
||||
height: 100vh;
|
||||
height: calc((var(--vh, 1vh) * 100) - env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.img-thumbnail {
|
||||
@apply p-0.5;
|
||||
@apply border border-gray-200;
|
||||
@apply rounded-sm;
|
||||
}
|
||||
|
||||
.notice {
|
||||
@apply mb-3 p-4;
|
||||
@apply bg-gray-200;
|
||||
@apply rounded;
|
||||
@apply leading-normal;
|
||||
}
|
||||
|
||||
/* Overwrite defaults, which we cannot to inline since markdow makes that hard. */
|
||||
.prose .notice p {
|
||||
@apply m-0;
|
||||
}
|
||||
|
||||
.prose .notice > h2:first-child {
|
||||
@apply mt-0;
|
||||
}
|
||||
|
||||
.article-headline-spacing h1:not(:first-child) {
|
||||
@apply mt-10;
|
||||
}
|
||||
|
||||
.article-headline-spacing h1 {
|
||||
@apply mb-3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
line-height: 2.6666rem;
|
||||
|
||||
}
|
||||
|
||||
h1, p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
#overlay a {
|
||||
@apply text-emerald-600 hover:text-emerald-800 visited:text-emerald-600
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
|
|
|
@ -8,6 +8,7 @@ import { validAnzeigeValues, type SearchParamsCqiMap } from './storeCqi'
|
|||
import { interactiveLayerIdsByGroup } from './layers/layers'
|
||||
import { Overlay } from './Overlay'
|
||||
import {BaseMap} from "../BaseMap/BaseMap.tsx";
|
||||
import {MapInfo} from "./MapInfo.tsx";
|
||||
|
||||
type Props = {
|
||||
maxBounds: MapSearchParam['maxBounds']
|
||||
|
@ -40,6 +41,7 @@ export const CqiMap = ({ maxBounds, minZoom, maxZoom }: Props) => {
|
|||
<MapSourceCqi />
|
||||
<NavigationControl showCompass={false} position="top-right" />
|
||||
<MapInspector />
|
||||
<MapInfo />
|
||||
<Overlay />
|
||||
</BaseMap>
|
||||
)
|
||||
|
|
56
src/page_map/MapInfo.tsx
Normal file
56
src/page_map/MapInfo.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import {useState} from "react";
|
||||
|
||||
export const MapInfo = () => {
|
||||
// const clickedMapData = useStore($clickedMapData)
|
||||
//
|
||||
// if (!clickedMapData || !clickedMapData.length) return null
|
||||
const [displayed, setDisplayed] = useState(true);
|
||||
return (
|
||||
<div id="overlay" style={{
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
position: "absolute",
|
||||
display: displayed ? "flex" : "none",
|
||||
justifyContent: "center",
|
||||
// pointerEvents: "none",
|
||||
alignItems: "center",
|
||||
zIndex: 2000
|
||||
}} onClick={() => setDisplayed(false)}>
|
||||
<section
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="text-base z-50 overflow-y-auto rounded-lg bg-white border border-emerald-200 p-4 shadow-xl sm:inset-x-auto sm:inset-y-10 sm:right-5 sm:w-1/2">
|
||||
<h1 className="text-center">Cycling Quality Index<br/>Wien</h1>
|
||||
<p>
|
||||
Ein Radverkehrs-Qualitätsindex für Wien basierend auf
|
||||
<a href="https://www.openstreetmap.org/" target="_blank"> OSM</a>-Daten.
|
||||
</p>
|
||||
<p>Der Code für die Analyse und Visualisierung stammt vom
|
||||
<a href="https://www.osm-verkehrswende.org/cqi/" target="_blank"> www.osm-verkehrswende.org</a>-Team
|
||||
und wurde von
|
||||
mir nur
|
||||
<a href="https://github.com/Findus23/vienna-cycling-quality" target="_blank"> für Wien adaptiert</a>.
|
||||
Weitere Informationen zur Methodik findet man auch auf der
|
||||
<a href="https://www.osm-verkehrswende.org/cqi/" target="_blank"> osm-verkehrswende-Webseite </a>
|
||||
oder im <a
|
||||
href="https://www.osm-verkehrswende.org/cqi/posts/2024-01-01-cqi-fossgis-2024/"> FOSSGIS-Vortrag </a>
|
||||
von Alex Seidel.
|
||||
</p>
|
||||
<p>
|
||||
Alle Ergebnisse sind nur so gut wie die Rohdaten und fehlende Attribute (z.B. über Fahrbahnbreiten)
|
||||
können die Ergebnisse verfälschen.
|
||||
<a href="https://www.osm-verkehrswende.org/cqi/improve-data/"> Hier </a>
|
||||
gibt es Informationen, wie man zu den Daten beitragen kann.
|
||||
</p>
|
||||
<p>
|
||||
Über mich: <a href="https://lw1.at">lw1.at</a>
|
||||
</p>
|
||||
<p>
|
||||
Impressum: <a href="https://lw1.at/de/impressum/">lw1.at/de/impressum</a>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -93,6 +93,10 @@ export const MapInspector = () => {
|
|||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<details>
|
||||
<summary className="text-lg">Rohdaten</summary>
|
||||
<pre>{JSON.stringify(feature.properties, null, 2)}</pre>
|
||||
</details>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
|
|
@ -5,27 +5,26 @@ import { Layer, Source } from 'react-map-gl/maplibre'
|
|||
import { wrapFilterWithAll } from '../BaseMap/utils/wrapFilterWithAll'
|
||||
import { layerByGroups, layersSelected } from './layers/layers'
|
||||
import { $focus, type SearchParamsCqiMap } from './storeCqi'
|
||||
import {useMap} from "react-map-gl";
|
||||
|
||||
import {default as dataTiles} from "../assets/data.pmtiles";
|
||||
export const MapSourceCqi = () => {
|
||||
const params = useStore($searchParams) as SearchParamsCqiMap
|
||||
const focus = useStore($focus)
|
||||
const mapData = useStore($clickedMapData)
|
||||
const mapDataIds = mapData?.map((feature) => feature.properties?.id) ?? []
|
||||
|
||||
// Debugging:
|
||||
console.log(mapDataIds)
|
||||
const map = useMap()
|
||||
console.log(map.current?.getStyle())
|
||||
// // Debugging:
|
||||
// console.log(mapDataIds)
|
||||
// const map = useMap()
|
||||
// console.log(map.current?.getStyle())
|
||||
|
||||
const focusFilter = focus ? ['match', ['get', focus.key], focus.values, true, false] : null
|
||||
|
||||
const pmtilesUrl = "pmtiles://" + dataTiles
|
||||
return (
|
||||
<Source
|
||||
id="cqi"
|
||||
type="vector"
|
||||
// url="pmtiles://https://atlas-tiles.s3.eu-central-1.amazonaws.com/cycling_quality_index.pmtiles"
|
||||
url="pmtiles://http://localhost:5174/src/assets/out.pmtiles"
|
||||
url={pmtilesUrl}
|
||||
attribution="© OpenStreetMap"
|
||||
>
|
||||
{layersSelected.map((layer) => {
|
||||
|
|
6
src/vite-env.d.ts
vendored
6
src/vite-env.d.ts
vendored
|
@ -1 +1,7 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
// images
|
||||
declare module '*.pmtiles' {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
|
28
tailwind.config.js
Normal file
28
tailwind.config.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}',
|
||||
'./public/javascript/*.{js,ts}',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
zIndex: {
|
||||
1000: '1000',
|
||||
},
|
||||
},
|
||||
},
|
||||
corePlugins: {
|
||||
// Removes the opacity var() from Styles in order to get dev tools show the color.
|
||||
// https://twitter.com/adamwathan/status/1529596984235118595
|
||||
// https://tailwindcss.com/docs/configuration#core-plugins
|
||||
backdropOpacity: false, // The backdrop-opacity utilities like backdrop-opacity-50
|
||||
backgroundOpacity: false, // The background-color opacity utilities like bg-opacity-25
|
||||
borderOpacity: false, // The border-color opacity utilities like border-opacity-25
|
||||
divideOpacity: false, // The divide-opacity utilities like divide-opacity-50
|
||||
placeholderOpacity: false, // The placeholder color opacity utilities like placeholder-opacity-25
|
||||
ringOpacity: false, // The ring-opacity utilities like ring-opacity-50
|
||||
textOpacity: false, // The text-opacity utilities like text-opacity-50},
|
||||
// 'opacity': true, // The opacity utilities like opacity-50
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')],
|
||||
}
|
|
@ -4,4 +4,5 @@ import react from '@vitejs/plugin-react'
|
|||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
assetsInclude:["**/*.pmtiles"]
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue