Har du inte läst gårdagens inlägg så börja med det först.
Här tänker jag gå in på lite av koden bakom webbsidan som skall hjälpa mig att mäta in olika objekt i efterhand. Du kommer inte att kunna återskapa sidan enbart genom att läsa denna artikel, men mot slutet finns det länkar till såväl alla filer som till en ”live” version av sidan som du kan prova med dina egna panoraman.
Jag har två panoraman med GPS-taggar som jag skapat i Photosynth. Jag har modifierat dessa så att norr är i bildernas skarv (ungefär) och nu är jag klar att läsa in och bearbeta dessa (Båda dessa finns bland de filer du kan ladda hem längre ner).
Sidan består i princip av fem fält:
- Första panoramat
- Andra panoramat
- Koordinater för panoraman och vinklar till utpekade objekt
- Beräknade koordinater för objekt samt objektnamn
- Lista med sparade objekt
För att visa panoraman så använder jag två olika metoder. Bilder laddas in och visas med LeafletJS, som ju egentligen skall användas för att visa kartor, men vinklar är vinklar oavsett om det är koordinater eller kompassriktningar.
Det ser däremot inte så snyggt ut så jag använder även Pannellum för att granska dessa panoraman separat om man så vill. Detta är dock inget som är viktigt för funktionen.
LeafletJS läses in med en SCRIPT-tagg i huvudet på sidan:
<script type="text/javascript" src="leaflet.js"></script>
Platshållarna för panoramat skapar jag precis som om det hade varit en webbkarta med en DIV-tag som får id map1 och map2. Mest för att jag var lat och inte ändrade till exempelvis panorama1 och panorama2.
<div id="map1"></div>
Jag samlar alla mina javaskript mot slutet i dokumentet under en egen SCRIPT-tagg.
<script> var map1 = L.map('map1', { crs: L.CRS.EPSG4326, continuousWorld: true, maxZoom: 5 }).setView([0,180], 2); // Här kommer det mera som beskrivs längre ner på sidan... </script>
Panoramat (eller kartan) initieras med ett skriptkommando (ovan). Jag lägger till att jag vill att projektionen skall vara WGS84 i stället för PseudoMercator eftersom detta passar mina ekvirektilinjära panoraman bättre. Lite olika inställningar för zoom används för att man inte skall zooma in för mycket, vilket jag har märkt kan ställa till med problem.
När man klickar på ”Bläddra” så skickas valt filnamn över till skriptet. Detta är dock utan sökväg vilket är en säkerhetsrisk, vilket i sin tur betyder att panoramabilder måste finnas på samma sökväg som html-dokumentet, eller på en sökväg som man får hårdkoda i skriptet, eller på en webbserver.
När man har filnamnet till panoramat så lägger man in det som ett ImageOverlay:
var lager1 = new L.ImageOverlay("panorama.png", [[-45,0],[45,360]]); map1.addLayer(lager1);
Det jag gör ovan är att läsa in den bild som skall synas när sidan startar i varje kartfönster. Längre fram använder jag en annan kod för att läsa in nya panoraman.
För att läsa GPS informationen i EXIF så använder jag ExifJS. På samma sätt som för Leaflet lägger jag därför till en hänvisning till ”exif.js” i huvudet på sidan.
Koordinaterna är i grader, minuter och sekunder vilket inte passar så detta måste räknas om till decimala grader. Detta kommer dock inte att fungera att räkna på så sedan måste decimala grader omvandlas till projicerade. Detta gör jag med ännu ett javaskript nämligen Proj4JS, som även det läggs in på samma sätt som de tidigare.
Inläsning av koordinater i SWEREF99TM går sedan till enligt nedan:
document.getElementById("panorama1").onchange = function(e) { // När utpekad fil ändras så körs skriptet EXIF.getData(e.target.files[0], function() { // Använd ExifJS för att läsa in GPS data var exifLat = EXIF.getTag(this, "GPSLatitude"); var exifLon = EXIF.getTag(this, "GPSLongitude"); var exifLatRef = EXIF.getTag(this, "GPSLatitudeRef") || "N"; var exifLonRef = EXIF.getTag(this, "GPSLongitudeRef") || "E"; var lat = (exifLat[0] + exifLat[1]/60 + exifLat[2]/3600) * (exifLatRef == "N" ? 1 : -1); // Gör om koordinater till decimala grader +/- var lon = (exifLon[0] + exifLon[1]/60 + exifLon[2]/3600) * (exifLonRef == "E" ? 1 : -1); // Använd Proj4JS för att göra om decimala grader till SWEREF99TM. var UTM = proj4('EPSG:4326', "+proj=utm +zone=33 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs", [lon, lat]); // Vill du använda ett annat koordinatsystem så är det texten som börjar med "+proj" som skall ändras. var easting = parseFloat(UTM[0]).toFixed(1); // Avrunda koordinaterna till en decimal var northing = parseFloat(UTM[1]).toFixed(1); document.getElementById("east1").value = easting; // Skriv ut koordinaterna i rätt fält document.getElementById("north1").value = northing; }); map1.removeLayer(lager1); // Ta bort det befintliga panoramat lager1 = new L.imageOverlay(document.getElementById("panorama1").value, [[-90,0],[90,360]]); // Skapa ett nytt panorama från utpekad fil map1.addLayer(lager1); // Lägg in panoramat i kartan var dok = document.getElementById("panorama1").value; // Hämra filnamnet (strular med exempelvis Chrome) document.getElementById("link1").href = "pannellum.htm?panorama="+dok+"&autoLoad=true&author=GEO SG&title="+dok; // Skapa länk till Pannellum för att visa panoramat i stor bild. }
Det var en lite längre kod, men vi tar den lite stegvis. Funktionen körs så fort filnamnet som står i ”panorama1”, vilket är en text som finns jämte bläddra-knappen.
Exif frågan körs mot filobjektet som finns i den överlämnade variabeln ”e”. Detta filobjekt ”this” används sedan för att läsa ut latitud, longitud och referens för N/S samt E/W.
Omvandlingen till decimala grader kombineras med en funktion som sätter koordinaterna som negativa om de är på södra halvklotet eller om de är i väst.
Proj4JS anropas och tilldelar en variabel ett nytt värde med proj4(ursprungssystem, destinationssystem, koordinat), där WGS84 är inbyggt och därför kan refereras till ganska enkelt. SWEREF99TM får man dock ange med en längre textsträng. Denna hittar man enkelt i exempelvis QGIS. Mitt skript kommer alltså endast att fungera för omvandling till SWEREF99TM, så om någon vill att destinationssystemet skall vara något annat så blir det till att ändra här.
Resultatet skrivs till olika fält på sidan genom att referera till document.getElementById(”id-namn”).value, vilket jag gör genomgående för att såväl läsa som skriva data dynamiskt.
Om du inte är så hemma i javaskript så är du säkert fundersam över alla hakparenteser ( [0] ). Dessa används helt enkelt när variablerna är ”listor” med data och siffran innanför hakparentesen är helt enkelt ett index för dessa listor så att rätt värde kan användas.
När EXIF är utvunnet så skall lagret läggas till och det görs genom att först ta bort det gamla lagret. Sedan läser skriptet filnamnet från det fält som står jämte knappen på sidan. Detta strular i en del webbläsare så jag skall nog göra om detta till en global variabel i stället.
Jag använder filnamnet inte bara till att läsa in panoramat i kartfönstret, utan även för att skapa en länk till pannellum.htm så att det går att granska panoramat lite bättre om man så önskar. Pannellum kräver normalt att sidan finns på en webbserver, men jag har upptäckt att det fungerar fint i exempelvis Firefox (ver 37) direkt i en vanlig katalog.
Klicka i bilderna
Det går att zooma och panorera i de inlästa panoramabilderna och det finns en speciell funktion som anropas när man klickar på en specifik punkt:
function onMapClick1(e) { document.getElementById("v1").value = e.latlng.lng.toFixed(1); // Skriv ut longituden, vilket är det samma som vinkeln från norr i vinkelfältet smalVinkel(); // Anropa funktion som kontrollerar vinkelskillnaden var popup1 = L.popup() // Skapa en popup vid den klickade positionen .setLatLng(e.latlng) .setContent("Bäring: " + e.latlng.lng.toFixed(1)) .openOn(map1); }
Longituden hämtas avrundat till en decimal ur muspositionen med e.latlng.lng.toFixed(1). Latituden bryr jag mig inte om eftersom jag tänker mäta höjd eller z-värde.
smalVinkel() är en funktion för att testa om vinkeln till objektet från de båda bilderna är så liten eller stor att det finns risk att positionen blir väldigt dålig.
Förutom att skriva vinkeln till ett fält på sidan så läggs det ut i en popup-ruta. Denna är även bra för att hålla reda på var man senast klickade i kartan.
Räkna koordinater och skapa objekt
När man så har alla koordinater och vinklar så kan man använda de formler som jag gick igenom igår för att räkna fram objektets koordinat.
Jag tänkte inte lista denna kod här utan är du intresserad så kan du granska hela källkoden i stället.
När man sedan klickar på ”spara” så körs en sista funktion som sätter ihop en textsträng med koordinater och det namn man valt för objektet och lägger till detta i listan längst ned på sidan.
Listan kan man sedan kopiera och klistra in i ett textdokumet och spara som ”csv-fil”.
I gränssnittet så klickar man på objektet i båda bilderna, klickar ”beräkna”, skriver in ett namn på objektet och klickar på ”spara” för att lägga till i listan.
I QGIS (eller valfritt GIS) så kan man sedan öppna filen och jobba vidare med dessa data, eller bara kontrollera hur rätt eller fel det blev.
Sammanfattning
Innan du får länken till sidan så du kan testa själv så tänkte jag avsluta med några kommentarer.
Appen i telefonen fungerade ganska bra, men det blev betydligt mycket bättre när jag använde noggrannare metoder för att sätta samman dessa panoraman. Det börjar även dyka upp skapligt billiga kameror som kan fånga ett 360 graders panorama på ett enda ögonblick, vilket jag antar kommer att förbättra panoramats kvalité.
Positioneringen i telefonen är svår att granska och det bidrar till att ytterligare ge lite osäkerhet i resultatet. Med bättre noggrannhet i positioneringen så blir sannolikt positionen på inmätta punkter också bättre. Kanske kan använda Nätverks DGPS när detta släpps fritt nästa år?
Noggrannheten i inmätta punkter blir inte bättre än vad GPS och panorama är och att jämföra med exempelvis skannande laser eller professionellt kalibrerade objektiv är inte rättvist. Detta sagt så är jag faktiskt väldigt nöjd med resultatet och jag har redan en liten lista med sådant som jag vill förbättra när jag får lite tid över nästa gång.
Vill du prova själv så kan du antingen hämta allt du behöver i en zip-fil inklusive mina exempel, eller prova ”live” via länken nedan. För att det skall fungera ”live” så laddar du bara två filer (fil1, fil2) och använder dessa, servern struntar nästan i vad du har för filer och använder de som heter likadant på servern, men för att exif skall fungera så måste du ha filerna lokalt (återstår som sagt en del skriptande). Observera att du måste ha en modern webbläsare och när jag jobbat med koden har jag framför allt använt Firefox, men det verkar som att Chrome och Internet Explorer fungerar också.
http://geosupportsystem.altervista.org/pangeox/
Om du föredrar att titta på en film som demo så går det också bra:
(Jag testar nya saker också i en ”master” version)
Klas! Great application!
What about get measures from coupled panoramas?
Have a look: http://www.cyclomedia.com/en/applications/#measuring
and this is the related application: http://www.cyclomedia.com/en/products/software/#globespotter
Working on a simple measurement tool.
Any progress can be found on http://geosupportsystem.altervista.org/pangeox/master.html
It will not be very accurate until you move from javascript to OpenCV, but I never intended it to be either.