OBS: En nyere versjon av eksempelet her er tilgjengelig på http://www.atlefren.net/post/2011/02/kartverket-openlayers/. Se gjerne på koden der!
OBSOBS (mai 2015): Denne bloggposten ble skrevet for 5 år siden, og mye har endra seg siden da. Det betyr at denne bloggposten er utdatert og koden ikke funker mer, men ta en titt på https://github.com/atlefren/kartverket-gpx
Du har sikkert en GPS, eller en smarttelefon med GPS innebygd? Du liker kanskje også å gå på tur og logge den? Men du synes ikke det er så forbanna kult å legge ut et track fra en tur i den norske villmark over Google Maps (la oss innse det) elendige turkart eller kornete satelittbilder?
Du ønsker kanskje ikke å vise frem noe slik som dette til turvenner? Du skulle heller ønske du kunne lagt inn ruta di over “indrefileten” av norske data? Nemlig kartverkets kart? Du har sikkert fått med deg at disse er tilgjengelige for gratis bruk for privatpersoner?
Du har kanskje til og med kommet så langt at du har fått satt opp OpenLayers med kartverkets WMS på din hjemmeside?
Men der stopper du kanskje? Du skjønner ikke helt hvordan du skal få lagt GPX fila di over kartet? Eller du får kanskje lasta den inn, men den havner helt riv, ruskende, galt avsted? Eller kanskje ser hele Norge helt på trynet ut? Frykt ikke, siden kongeriket Norge har fått meg utdanna som IKT-Geomatiker tenkte jeg jeg skulle prøve å hjelpe deg.
Jeg vet folk lurer på dette. Jeg så tidligere en tweet fra Martin Bekkelund om temaet, og når har også søkefraser som “turkart egne gpx-filer” begynnt å dukke opp i søkeloggene her på bloggen. Derfor tenkte jeg å yte en innsats.
Så, hva tenkte jeg å gå gjennom? Først av alt en liten intro til å sette opp OpenLayers med Kartverkets WMS i Openlayers. Så tenkte jeg å gå gjennom hvordan man får inn GPX-data i OpenLayers, før jeg forklarer magien bak transformasjoner og dermed får alt til å passe sammen.
Kartoppsett
Først må du ha opp et kart, det gjøres ved å gjøre ca følgende:
1. Lag en HTML-side med en div med høyde og bredde 100%. Gi denne diven en id
<html>
<head>
<title>Kart</title>
<!-- sett opp en div til kartet -->
<style type="text/css">
#map { width: 100%; height: 100%;}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>
2. Så må du legge inn OpenLayers js-biblioteket. Det greieste er å hente den fra serveren deres direkte.
Dette gjør du ved å legge til en <script>-tag i headeren:
<!-- last inn OpenLayers js -->
<script src="http://openlayers.org/api/OpenLayers.js"></script>
3. Så må vi skrive litt JavaScript selv. Vi ønsker å ha en metode som startes når siden listes, og vi velger å legge all javascript-koden direkte inn i en script-blokk i headeren: Da får vi følgende skjelett:
<html>
<head>
<title>Kart</title>
<!-- sett opp en div til kartet -->
<style type="text/css">
#map { width: 100%; height: 100%;}
</style>
<!-- last inn OpenLayers js -->
<script src="http://openlayers.org/api/OpenLayers.js"></script>
<script type="text/javascript">
//her kommer JS-koden vår
function init(){
}
</script>
</head>
<body onload="init()">
<div id="map"></div>
</body>
</html>
4. Da er grunnlaget lagt, vi har importert OpenLayers-biblioteket, og vi har en (foreløpig) tom init-funksjon. Da gjenstår det å lage et Map-objekt, et layer objekt og legge til litt kontroller.
Gjør vi dette blir js-blokka vår seende slik ut (med beskrivende kommentarer):
<script type="text/javascript">
//selve kartet
var map;
//her starter vi ting
function init() {
OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3; //reduserer problemet med "rosa" tiles (openlayers viser rosa tiles når den ikke får lastet en tile)
//sett opp kartet (EPSG:32633 er UTM sone 33N)
map = new OpenLayers.Map( 'map', {
projection: new OpenLayers.Projection('EPSG:32633'),
maxExtent: new OpenLayers.Bounds(-2500000.0,3500000.0,3045984.0,9045984.0),
units: "m",
maxResolution: 2708.0, // tilsvarer zoom level 3 (hele er 21664.0)
numZoomLevels: 18 // egentlig 21, men maxResolution tilsvarer zoom level 3 (følgelig er 0-3 skrudd av)
} );
//musenavigering
map.addControl(new OpenLayers.Control.MouseDefaults());
// Definer karttjenesten(e)
var topo2 = new OpenLayers.Layer.WMS(
"Topografisk norgeskart2","http://opencache.statkart.no/gatekeeper/gk/gk.open?",
{layers: 'topo2', format: 'image/png'},{attribution:''}
);
// Legg WMSen fra kartverket til kartet
map.addLayers([topo2]);
//sentrer kartet på besseggen
map.setCenter(new OpenLayers.LonLat(683589,7146336),0); //Besseggen
}
</script>
5. Har du kommet så langt skal du nå ha ett Norgeskart du kan zoome og panorere rundt i, slik som dette.
GPX-innlasting
Godt å vel det, men jeg ser jo fortsatt ikke noe track? Nei, det skal vi ta nå.
Første skritt er å lokalisere en .gpx-fil (min ser slik ut) og ligger i samme mappe som kartet mitt på webserveren.
En ting kan vi like godt notere oss først som sist: GPX filer er i et koordinatsystem kjent som WGS 84 Lat/Lon, mens kartet vårt er i WGS84 UTM Sone 33N. Derfor må vi transformere, men det kommer vi til. Først skal vi få lasta inn GPX-fila i JS-koden.
1. Innlasting. OpenLayers har støtte for GPX-formatet og støtte for å laste filer. I tilegg må vi ha et vector-layer å legge tracket vårt i.
Ved å legge inn dette får vi noe slikt som:
//openLayers GPX-leser
var format = new OpenLayers.Format.GPX();
//layeren vi hiver featurene fra GPX i
var featureLayer = null;
//selve kartet
var map;
//her starter vi ting
function init() {
//sett opp featurelayeren som en vector-layer
featureLayer = new OpenLayers.Layer.Vector("GPX");
//url til gpx-fila (må ligge på samme server)
var url = "track.gpx";
//OpenLayers har støtte for å lese inn filer
OpenLayers.loadURL(url, null, null, loadSuccess, loadFailure);
//sett opp kartet (EPSG:32633 er UTM sone 33N)
map = new OpenLayers.Map( 'map', {
projection: new OpenLayers.Projection('EPSG:32633'),
maxExtent: new OpenLayers.Bounds(-2500000.0,3500000.0,3045984.0,9045984.0),
units: "m",
maxResolution: 2708.0, // tilsvarer zoom level 3 (hele er 21664.0)
numZoomLevels: 18 // egentlig 21, men maxResolution tilsvarer zoom level 3 (følgelig er 0-3 skrudd av)
} );
//musenavigering
map.addControl(new OpenLayers.Control.MouseDefaults());
OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3; //reduserer problemet med "rosa" tiles (openlayers viser rosa tiles når den ikke får lastet en tile)
// Definer karttjenesten(e)
var topo2 = new OpenLayers.Layer.WMS(
"Topografisk norgeskart2","http://opencache.statkart.no/gatekeeper/gk/gk.open?",
{layers: 'topo2', format: 'image/png'},{attribution:''}
);
// Legg WMSen fra kartverket og featureLayeren vår til kartet
map.addLayers([topo2,featureLayer]);
//sentrer kartet på besseggen
map.setCenter(new OpenLayers.LonLat(683589,7146336),0); //Besseggen
}
//denne kalles når GPX-fila er lastet
function loadSuccess(request) {
//les responsen her som GPX
var features = format.read(request.responseText);
}
//denne kalles hvis/når noe går galt ved lesing av fila
function loadFailure(request) {
alert("Kunne ikke lese GPX-fila...");
}
Vi ser vi har gjort endel nye ting:
- Vi har definert et format og en featureLayer
- Vi kjører et kall til OpenLayers.loadURL
- Vi legger til et layer til til kartet
- Vi har en funksjon loadSuccess som laster GPX-fila over i et feature-array
Men vi kommer forsatt ikke så veldig langt.
Neste, logiske skritt, ville være å legge til featurene i featureLayeret og zoome inn på disse. Dvs å legge til litt i loadSuccess:
function loadSuccess(request) {
//les responsen her som GPX
var features = format.read(request.responseText);
//vi får da en samling features, legg disse til vector-layeret
featureLayer.addFeatures(features);
//zoom kartet, denne gangen til område de lastede featurene dekker
map.zoomToExtent(featureLayer.getDataExtent());
}
Men dette vil ikke fungere, det ser vi her. Problemet er, som nevnt før, at vi har to forskjellige koordinatsystem. Hadde kartet vært i WGS84 Lon/Lat hadde det fungert (men det vil vi ikke, da ser ikke kartet bra ut), hadde GPX-fila vært i WGS84 UTM Sone 33N ville det fungert (men det er aldri GPX-filer).
Dermed må vi transformere. Her kommer OpenLayers til kort, men den kan støtte seg på Proj4js. Du må laste ned kildekoden, og legge “lib/proj4js-compressed.js” et sted der javascriptet ditt når den, samt inkludere den på samme måte som med OpenLayers.js. Hvis du legger den i samme mappe legger du til:
<script src="proj4js-compressed.js"></script>
i headeren.
I tillegg må du definere for Proj4Js hvilke parametere som gjelder for WGS84 UTM Sone 33N, da den ikke kjenner det. Dette gjør du ved å legge inn følgende i begynnelsen av JavaScriptet:
//Proj4js definisjon for WGS84 UTM Sone 33N
Proj4js.defs["EPSG:32633"] = "+proj=utm +zone=33 +ellps=WGS84 +datum=WGS84 +units=m +no_defs";
(Denne definisjonen finner du under “Proj4js” på http://spatialreference.org/ref/epsg/32633/.)
Da er vi nesten i mål, nå gjenstår bare selve transformasjonen. Det vi må gjøre i loadSuccess er å loope gjennom alle featurene og transformere geometriene fra WGS84 LatLon (som har kode EPSG:4326) til WGS84 UTM Sone 33N (som har kode EPSG:32633). Dette gjør vi med transform-metoden til geometri-objektet, og funskjonen vår blir slik:
function loadSuccess(request) {
//les responsen her som GPX
var features = format.read(request.responseText);
//loop gjennom alle features og transformer fra WGS84 Lat/Lon (EPSG4326) til WGS84 UTM Sone 33N (EPSG:32633)
for(var i = 0; i < features.length; i++){
features[i].geometry.transform(new OpenLayers.Projection('EPSG:4326'), new OpenLayers.Projection('EPSG:32633'));
}
//vi får da en samling features, legg disse til vector-layeret
featureLayer.addFeatures(features);
//zoom kartet, denne gangen til område de lastede featurene dekker
map.zoomToExtent(featureLayer.getDataExtent());
}
Nå ser vi et spor slynge seg oppover Skogshorn, men det er litt blasst. Vi må endre hvordan det tegnes!
Flikking og pussing
Vi har nå ting på plass, men det ser kanskje litt sært ut? Vi kan mens vi er i gang fikse litt på utseendet på lina vår, via et StyleMap på vektor-layeret. Dette gjør vi ganske enkelt slik (og tegner linja med 2px grønn strek):
//angi styling av features
var styleMap = new OpenLayers.StyleMap(OpenLayers.Util.applyDefaults({
strokeColor: "green",
strokeWidth: 2},
OpenLayers.Feature.Vector.style["default"]));
//sett opp featurelayeren som en vector-layer
featureLayer = new OpenLayers.Layer.Vector("GPX", {styleMap: styleMap});
Og får dette. (For mer om styling, se OpenLayers.Feature.Vector).
Eller kanskje du foretrekker å bruke N50-kartet som bakgrunn, og la brukeren få velge? Mulighetene er mange!
Konklusjon
Dette var bare en quick-and-dirty gjennomgang av prinsippene, koden fungerer greit til enkel bruk, men den kan lett kombineres med opplastingsrutiner for å laste opp GPX-filer via f.eks. php, eller du kan utvide ting til å bruke en romlig database for å lagre GPS-tracket? Muligehtene er mange.
Og, skulle du oppdage feil eller mangler, eller har spørsmål er det bare å si fra!