Idee
Die Kartenkamera zeigt die aktuelle GPS-Position, die Adresse und die Uhrzeit auf einer Karte an.
Per Screenshot am Smartphone lassen sich so aussagekräftige Fotos von Schlaglöchern etc. gestalten.
Link/Demo
https://drittereihe.de/kartenkamera2.html
https://drittereihe.de/kartenkamera.html (alte Version)
V2024-09-14 Marker auf der Karte verschiebbar
V2025-06-19 Genauigkeit erhöht, Bibliotheken lokal, Button Screenshot mit Teilenfunktion
Realisiert mit ChatGPT
Quellcode
```js<!DOCTYPE html>
<!--
© 2025 ugh – eigener Code unter MIT-Lizenz.
Enthält Leaflet (BSD-2-Clause) und html2canvas (MIT).
-->
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Karten‑ & Kamera‑App mit Snapshot‑Funktion</title>
<!-- Leaflet CSS (lokal) -->
<link rel="stylesheet" href="leaflet/leaflet.css" />
<style>
/* ---------- Layout ---------- */
html, body {margin:0;height:100%;display:flex;flex-direction:column;font-family:sans-serif}
#map,#camera{flex:0 0 40%;height:40%;min-height:160px}
.leaflet-container{width:100%!important;height:100%!important}
#address{padding:10px;background:#f9f9f9;font-size:1.1em}
#inputField{width:95%;padding:10px;font-size:.9em;resize:vertical;min-height:60px}
video{width:100%;height:100%;object-fit:cover}
/* Buttons & Meldungen */
#snap{position:fixed;bottom:15px;right:15px;padding:10px 16px;border:none;border-radius:8px;box-shadow:0 2px 6px rgba(0,0,0,.2);background:#0284c7;color:#fff;font-size:1em;z-index:1000}
#snap:active{transform:scale(.97)}
#msg{position:fixed;top:10px;left:50%;transform:translateX(-50%);background:#f87171;color:#fff;padding:8px 12px;border-radius:6px;box-shadow:0 2px 6px rgba(0,0,0,.2);display:none;z-index:1001}
@media(max-width:600px){#inputField{font-size:.8em;min-height:100px}}
</style>
</head>
<body>
<div id="msg"></div>
<div id="map"></div>
<div id="address"><textarea id="inputField" placeholder="Tippe hier..."></textarea></div>
<div id="camera"><video id="video" autoplay playsinline></video></div>
<button id="snap">📸 Snapshot</button>
<!-- Leaflet JS – versuch erst lokal, fallback CDN -->
<script src="leaflet/leaflet.js"></script>
<script>
/* ---------- Utility ---------- */
const toast=t=>{const m=document.getElementById('msg');m.textContent=t;m.style.display='block';setTimeout(()=>m.style.display='none',4000)};
/* ---------- Map / Geolocation ---------- */
let map,marker,accuracyCircle;
const TARGET_ACC=30;
const FALLBACK=[51.163,10.447]; // Mitte DE
function buildMap(lat,lon,acc){
if(!map){
map=L.map('map').setView([lat,lon],18);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{attribution:'© OpenStreetMap contributors'}).addTo(map);
setTimeout(()=>map.invalidateSize(),100);
}
if(marker) map.removeLayer(marker);
if(accuracyCircle) map.removeLayer(accuracyCircle);
marker=L.marker([lat,lon],{draggable:true}).addTo(map);
accuracyCircle=L.circle([lat,lon],{radius:acc}).addTo(map);
marker.on('dragend',()=>{const {lat,lng}=marker.getLatLng();fillAddress(lat,lng);});
marker.bindPopup(`Genauigkeit: ±${acc.toFixed(0)} m`).openPopup();
map.fitBounds(accuracyCircle.getBounds());
fillAddress(lat,lon);
}
function locate(){
if(!navigator.geolocation){toast('Kein Geolocation‑Support – Karte ohne Standort.');return buildMap(...FALLBACK,5000);}
const opts={enableHighAccuracy:true,timeout:15000,maximumAge:0};
const id=navigator.geolocation.watchPosition(p=>{const {latitude:lat,longitude:lon,accuracy:acc}=p.coords;buildMap(lat,lon,acc);if(acc<=TARGET_ACC) navigator.geolocation.clearWatch(id);},e=>{console.warn(e);toast('Standort nicht verfügbar – Karte ohne Standort.');buildMap(...FALLBACK,5000);},opts);
setTimeout(()=>navigator.geolocation.clearWatch(id),60000);
}
/* ---------- Adresse ---------- */
function fillAddress(lat,lon){
const f=n=>n.toString().padStart(2,'0');const d=new Date();const dt=`${['So','Mo','Di','Mi','Do','Fr','Sa'][d.getDay()]}, ${f(d.getDate())}.${f(d.getMonth()+1)}.${d.getFullYear().toString().slice(-2)} ${f(d.getHours())}:${f(d.getMinutes())}`;
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}`)
.then(r=>r.json()).then(a=>{const ad=a.address;const adr=`${ad.road||''} ${ad.house_number||''}, ${ad.postcode||''} ${(ad.city||ad.town||ad.village||'')}${ad.suburb&&ad.suburb!==ad.city?' '+ad.suburb:''}`.trim();document.getElementById('inputField').value=`Datum/Uhrzeit: ${dt}\nAdresse: ${adr}\nLat.: ${lat}, Lon.: ${lon}`;})
.catch(()=>{document.getElementById('inputField').value=`Datum/Uhrzeit: ${dt}\nAdresse konnte nicht geladen werden.\nLat.: ${lat}, Lon.: ${lon}`;});
}
/* ---------- Kamera ---------- */
function startCamera(){const v=document.getElementById('video');const cam=f=>navigator.mediaDevices.getUserMedia({video:{facingMode:{exact:f}}});cam('environment').then(s=>v.srcObject=s).catch(()=>cam('user').then(s=>v.srcObject=s).catch(()=>toast('Keine Kamera')));}
/* ---------- Snapshot ---------- */
document.getElementById('snap').addEventListener('click',async()=>{
if(!window.html2canvas){await import('./libs/html2canvas.min.js');
}
const mapNode=document.getElementById('map');const addrNode=document.getElementById('address');const video=document.getElementById('video');
const mapCv=await html2canvas(mapNode,{useCORS:true,allowTaint:true});const addrCv=await html2canvas(addrNode);
const scale=mapCv.width/video.videoWidth;const vW=video.videoWidth*scale;const vH=video.videoHeight*scale;
const out=document.createElement('canvas');out.width=mapCv.width;out.height=mapCv.height+vH+addrCv.height;const ctx=out.getContext('2d');
ctx.drawImage(mapCv,0,0);ctx.drawImage(video,0,mapCv.height,vW,vH);ctx.drawImage(addrCv,0,mapCv.height+vH);
out.toBlob(async b=>{const file=`snapshot_${Date.now()}.png`;if(navigator.canShare&&navigator.canShare({files:[new File([b],file,{type:b.type})]})){await navigator.share({files:[new File([b],file,{type:b.type})],title:'Snapshot'});}else{const a=document.createElement('a');a.href=URL.createObjectURL(b);a.download=file;a.click();URL.revokeObjectURL(a.href);}},'image/png');
});
/* ---------- Loader: prüfe Leaflet & fallback ---------- */
window.addEventListener('load',()=>{
function init(){locate();startCamera();}
if(typeof L==='undefined'){
toast('Lokal konnte Leaflet nicht geladen werden – lade CDN Fallback …');
// CSS‑Fallback
const css=document.createElement('link');css.rel='stylesheet';css.href='https://unpkg.com/leaflet@1.9.3/dist/leaflet.css';document.head.appendChild(css);
// JS‑Fallback
const s=document.createElement('script');s.src='https://unpkg.com/leaflet@1.9.3/dist/leaflet.js';s.onload=init;s.onerror=()=>toast('Leaflet konnte weder lokal noch per CDN geladen werden.');document.head.appendChild(s);
}else init();
});
</script>
</body>
</html>
```
'