Merge pull request #1360 from twpayne/vector-api-igc

[vector-api] ol.format.IGC
This commit is contained in:
Tom Payne
2013-12-17 03:39:43 -08:00
14 changed files with 50125 additions and 163 deletions

View File

@@ -1,135 +0,0 @@
goog.require('ol.Attribution');
goog.require('ol.Map');
goog.require('ol.RendererHint');
goog.require('ol.View2D');
goog.require('ol.control');
goog.require('ol.control.ScaleLine');
goog.require('ol.control.ScaleLineUnits');
goog.require('ol.geom.LineString');
goog.require('ol.geom.Point');
goog.require('ol.layer.Tile');
goog.require('ol.layer.Vector');
goog.require('ol.proj');
goog.require('ol.shape');
goog.require('ol.source.GeoJSON');
goog.require('ol.source.TileWMS');
goog.require('ol.style.Stroke');
goog.require('ol.style.Style');
var projection = ol.proj.configureProj4jsProjection({
code: 'EPSG:21781',
extent: [485869.5728, 76443.1884, 837076.5648, 299941.7864]
});
var vectorSource = new ol.source.GeoJSON({
defaultProjection: projection,
url: 'data/mtbland.geojson'
});
var styleArray = [new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'rgba(128,0,128,0.8)',
lineCap: 'round',
lineJoin: 'round',
width: 3
})
})];
var extent = [420000, 30000, 900000, 350000];
var layers = [
new ol.layer.Tile({
source: new ol.source.TileWMS({
url: 'http://wms.geo.admin.ch/',
crossOrigin: 'anonymous',
attributions: [new ol.Attribution({
html: '© ' +
'<a href="http://www.geo.admin.ch/internet/geoportal/' +
'en/home.html">' +
'Pixelmap 1:1000000 / geo.admin.ch</a>'
})],
params: {
'LAYERS': 'ch.swisstopo.pixelkarte-farbe-pk1000.noscale',
'FORMAT': 'image/jpeg'
},
extent: extent
})
}),
new ol.layer.Vector({
source: vectorSource,
styleFunction: function(feature, resolution) {
return styleArray;
}
})
];
var map = new ol.Map({
controls: ol.control.defaults().extend([
new ol.control.ScaleLine({
units: ol.control.ScaleLineUnits.METRIC
})
]),
layers: layers,
renderer: ol.RendererHint.CANVAS,
target: 'map',
view: new ol.View2D({
projection: projection,
center: [660000, 190000],
extent: extent,
zoom: 2
})
});
var point = null;
var line = null;
var displaySnap = function(coordinate) {
var closestFeature = vectorSource.getClosestFeatureToCoordinate(coordinate);
if (closestFeature === null) {
point = null;
line = null;
} else {
var geometry = closestFeature.getGeometry();
var closestPoint = geometry.getClosestPoint(coordinate);
if (point === null) {
point = new ol.geom.Point(closestPoint);
} else {
point.setCoordinates(closestPoint);
}
if (line === null) {
line = new ol.geom.LineString([coordinate, closestPoint]);
} else {
line.setCoordinates([coordinate, closestPoint]);
}
}
map.requestRenderFrame();
};
$(map.getViewport()).on('mousemove', function(evt) {
var coordinate = map.getEventCoordinate(evt.originalEvent);
displaySnap(coordinate);
});
map.on('singleclick', function(evt) {
var coordinate = evt.getCoordinate();
displaySnap(coordinate);
});
var imageStyle = ol.shape.renderCircle(5, null, new ol.style.Stroke({
color: 'rgba(255,0,0,0.9)',
width: 1
}));
var strokeStyle = new ol.style.Stroke({
color: 'rgba(255,0,0,0.9)',
width: 1
});
map.on('postcompose', function(evt) {
var render = evt.getRender();
if (point !== null) {
render.setImageStyle(imageStyle);
render.drawPointGeometry(point);
}
if (line !== null) {
render.setFillStrokeStyle(null, strokeStyle);
render.drawLineStringGeometry(line);
}
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -8,7 +8,7 @@
<link rel="stylesheet" href="../resources/bootstrap/css/bootstrap.min.css" type="text/css">
<link rel="stylesheet" href="../resources/layout.css" type="text/css">
<link rel="stylesheet" href="../resources/bootstrap/css/bootstrap-responsive.min.css" type="text/css">
<title>Complex geometry example</title>
<title>IGC example</title>
</head>
<body>
@@ -30,13 +30,18 @@
<div class="row-fluid">
<div class="span12">
<h4 id="title">Complex geometry example</h4>
<p id="shortdesc">Example of rendering a complex geometry. Zoom into the purple lines to see more detail.</p>
<div class="span4">
<h4 id="title">IGC example</h4>
<p id="shortdesc">Example of tracks recorded from multiple paraglider flights on the same day, read from an IGC file. The five tracks contain a total of 49,707 unique coordinates. Zoom in to see more detail. The background layer is from <a href="http://www.opencyclemap.org/">OpenCycleMap</a>.</p>
<div id="docs">
<p>See the <a href="complex-geometry.js" target="_blank">complex-geometry.js source</a> to see how this is done.</p>
<p>See the <a href="igc.js" target="_blank">igc.js source</a> to see how this is done.</p>
</div>
<div id="tags">complex-geometry, closest-feature, igc, opencyclemap</div>
</div>
<div class="span4 offset4">
<div id="info" class="alert alert-success">
&nbsp;
</div>
<div id="tags">vector, simplification</div>
</div>
</div>
@@ -44,9 +49,7 @@
</div>
<script src="jquery.min.js" type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/proj4js/1.1.0/proj4js-compressed.js" type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/proj4js/1.1.0/defs/EPSG21781.js" type="text/javascript"></script>
<script src="loader.js?id=complex-geometry" type="text/javascript"></script>
<script src="loader.js?id=igc" type="text/javascript"></script>
<script src="../resources/example-behaviour.js" type="text/javascript"></script>
</body>

149
examples/igc.js Normal file
View File

@@ -0,0 +1,149 @@
goog.require('ol.Attribution');
goog.require('ol.Map');
goog.require('ol.RendererHint');
goog.require('ol.View2D');
goog.require('ol.format.IGC');
goog.require('ol.geom.LineString');
goog.require('ol.geom.Point');
goog.require('ol.layer.Tile');
goog.require('ol.layer.Vector');
goog.require('ol.proj');
goog.require('ol.shape');
goog.require('ol.source.OSM');
goog.require('ol.source.Vector');
goog.require('ol.style.Stroke');
goog.require('ol.style.Style');
var tracklogs = [
'data/igc/Clement-Latour.igc',
'data/igc/Damien-de-Baenst.igc',
'data/igc/Sylvain-Dhonneur.igc',
'data/igc/Tom-Payne.igc',
'data/igc/Ulrich-Prinz.igc'
];
var colors = {
'Clement Latour': 'rgba(0, 0, 255, 0.7)',
'Damien de Baesnt': 'rgba(0, 215, 255, 0.7)',
'Sylvain Dhonneur': 'rgba(0, 165, 255, 0.7)',
'Tom Payne': 'rgba(0, 255, 255, 0.7)',
'Ulrich Prinz': 'rgba(0, 215, 255, 0.7)'
};
var styleCache = {};
var styleFunction = function(feature, resolution) {
var color = colors[feature.get('PLT')];
var styleArray = styleCache[color];
if (!styleArray) {
styleArray = [new ol.style.Style({
stroke: new ol.style.Stroke({
color: color,
lineCap: 'round',
lineJoin: 'round',
width: 3
})
})];
styleCache[color] = styleArray;
}
return styleArray;
};
var vectorSource = new ol.source.Vector();
var map = new ol.Map({
layers: [
new ol.layer.Tile({
source: new ol.source.OSM({
attributions: [
new ol.Attribution({
html: 'All maps &copy; ' +
'<a href="http://www.opencyclemap.org/">OpenCycleMap</a>'
}),
ol.source.OSM.DATA_ATTRIBUTION
],
url: 'http://{a-c}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png'
})
}),
new ol.layer.Vector({
source: vectorSource,
styleFunction: styleFunction
})
],
renderer: ol.RendererHint.CANVAS,
target: 'map',
view: new ol.View2D({
center: [703365.7089403362, 5714629.865071137],
zoom: 9
})
});
var transform = ol.proj.getTransform('EPSG:4326', 'EPSG:3857');
var i, ii;
for (i = 0, ii = tracklogs.length; i < ii; ++i) {
$.get(tracklogs[i], function(data) {
var format = new ol.format.IGC();
var feature = format.readFeature(data);
feature.getGeometry().transform(transform);
vectorSource.addFeature(feature);
});
}
var point = null;
var line = null;
var displaySnap = function(coordinate) {
var closestFeature = vectorSource.getClosestFeatureToCoordinate(coordinate);
var info = document.getElementById('info');
if (closestFeature === null) {
point = null;
line = null;
info.innerHTML = '&nbsp;';
} else {
info.innerHTML = closestFeature.get('PLT');
var geometry = closestFeature.getGeometry();
var closestPoint = geometry.getClosestPoint(coordinate);
if (point === null) {
point = new ol.geom.Point(closestPoint);
} else {
point.setCoordinates(closestPoint);
}
if (line === null) {
line = new ol.geom.LineString([coordinate, closestPoint]);
} else {
line.setCoordinates([coordinate, closestPoint]);
}
}
map.requestRenderFrame();
};
$(map.getViewport()).on('mousemove', function(evt) {
var coordinate = map.getEventCoordinate(evt.originalEvent);
displaySnap(coordinate);
});
map.on('singleclick', function(evt) {
var coordinate = evt.getCoordinate();
displaySnap(coordinate);
});
var imageStyle = ol.shape.renderCircle(5, null, new ol.style.Stroke({
color: 'rgba(255,0,0,0.9)',
width: 1
}));
var strokeStyle = new ol.style.Stroke({
color: 'rgba(255,0,0,0.9)',
width: 1
});
map.on('postcompose', function(evt) {
var render = evt.getRender();
if (point !== null) {
render.setImageStyle(imageStyle);
render.drawPointGeometry(point);
}
if (line !== null) {
render.setFillStrokeStyle(null, strokeStyle);
render.drawLineStringGeometry(line);
}
});

View File

@@ -254,6 +254,12 @@
* @property {ol.proj.ProjectionLike} defaultProjection Default projection.
*/
/**
* @typedef {Object} olx.format.IGCOptions
* @property {ol.format.IGCZ|undefined} altitudeMode Altitude mode.
* Possible values are `barometric`, `gps`, and `none`. Default is `none`.
*/
/**
* @typedef {Object} olx.interaction.DoubleClickZoomOptions
* @property {number|undefined} duration Animation duration in milliseconds. Default is `250`.
@@ -494,6 +500,14 @@
* @property {string|undefined} url URL.
*/
/**
* @typedef {Object} olx.source.IGCOptions
* @property {ol.format.IGCZ|undefined} altitudeMode Altitude mode.
* Possible values are `barometric`, `gps`, and `none`. Default is `none`.
* @property {string|undefined} text Text.
* @property {string|undefined} url URL.
*/
/**
* @typedef {Object} olx.source.MapGuideOptions
* @property {string|undefined} url The mapagent url.

View File

@@ -0,0 +1 @@
@exportSymbol ol.format.IGC

154
src/ol/format/igcformat.js Normal file
View File

@@ -0,0 +1,154 @@
goog.provide('ol.format.IGC');
goog.require('goog.asserts');
goog.require('goog.string');
goog.require('goog.string.newlines');
goog.require('ol.Feature');
goog.require('ol.format.Text');
goog.require('ol.geom.LineString');
goog.require('ol.proj');
/**
* @enum {string}
*/
ol.format.IGCZ = {
BAROMETRIC: 'barometric',
GPS: 'gps',
NONE: 'none'
};
/**
* @constructor
* @extends {ol.format.Text}
* @param {olx.format.IGCOptions=} opt_options Options.
*/
ol.format.IGC = function(opt_options) {
var options = goog.isDef(opt_options) ? opt_options : {};
goog.base(this);
/**
* @private
* @type {ol.format.IGCZ}
*/
this.altitudeMode_ = goog.isDef(options.altitudeMode) ?
options.altitudeMode : ol.format.IGCZ.NONE;
};
goog.inherits(ol.format.IGC, ol.format.Text);
/**
* @const {RegExp}
* @private
*/
ol.format.IGC.B_RECORD_RE_ =
/^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/;
/**
* @const {RegExp}
* @private
*/
ol.format.IGC.H_RECORD_RE_ = /^H.([A-Z]{3}).*?:(.*)/;
/**
* @const {RegExp}
* @private
*/
ol.format.IGC.HFDTE_RECORD_RE_ = /^HFDTE(\d{2})(\d{2})(\d{2})/;
/**
* @inheritDoc
*/
ol.format.IGC.prototype.readFeatureFromText = function(text) {
var altitudeMode = this.altitudeMode_;
var lines = goog.string.newlines.splitLines(text);
/** @type {Object.<string, string>} */
var properties = {};
var flatCoordinates = [];
var year = 2000;
var month = 1;
var day = 1;
var i, ii;
for (i = 0, ii = lines.length; i < ii; ++i) {
var line = lines[i];
var m;
if (line.charAt(0) == 'B') {
m = ol.format.IGC.B_RECORD_RE_.exec(line);
if (m) {
var hour = parseInt(m[1], 10);
var minute = parseInt(m[2], 10);
var second = parseInt(m[3], 10);
var y = parseInt(m[4], 10) + parseInt(m[5], 10) / 60000;
if (m[6] == 'S') {
y = -y;
}
var x = parseInt(m[7], 10) + parseInt(m[8], 10) / 60000;
if (m[9] == 'W') {
x = -x;
}
flatCoordinates.push(x, y);
if (altitudeMode != ol.format.IGCZ.NONE) {
var z;
if (altitudeMode == ol.format.IGCZ.GPS) {
z = parseInt(m[11], 10);
} else if (altitudeMode == ol.format.IGCZ.BAROMETRIC) {
z = parseInt(m[12], 10);
} else {
goog.asserts.fail();
z = 0;
}
flatCoordinates.push(z);
}
var date = new Date(year, month, day, hour, minute, second, 0);
flatCoordinates.push(date.getTime() / 1000);
}
} else if (line.charAt(0) == 'H') {
m = ol.format.IGC.H_RECORD_RE_.exec(line);
if (m) {
properties[m[1]] = goog.string.trim(m[2]);
m = ol.format.IGC.HFDTE_RECORD_RE_.exec(line);
if (m) {
year = 2000 + parseInt(m[1], 10);
month = parseInt(m[2], 10);
day = parseInt(m[3], 10);
}
}
}
}
var lineString = new ol.geom.LineString(null);
var layout = altitudeMode == ol.format.IGCZ.NONE ?
ol.geom.GeometryLayout.XYM : ol.geom.GeometryLayout.XYZM;
lineString.setFlatCoordinates(layout, flatCoordinates);
var feature = new ol.Feature(lineString);
feature.setValues(properties);
return feature;
};
/**
* @inheritDoc
*/
ol.format.IGC.prototype.readFeaturesFromText = function(text) {
var feature = this.readFeatureFromText(text);
if (!goog.isNull(feature)) {
return [feature];
} else {
return [];
}
};
/**
* @inheritDoc
*/
ol.format.IGC.prototype.readProjectionFromText = function(text) {
return ol.proj.get('EPSG:4326');
};

View File

@@ -0,0 +1 @@
@exportSymbol ol.source.IGC

View File

@@ -0,0 +1,26 @@
goog.provide('ol.source.IGC');
goog.require('ol.format.IGC');
goog.require('ol.source.VectorFile');
/**
* @constructor
* @extends {ol.source.VectorFile}
* @param {olx.source.IGCOptions=} opt_options Options.
*/
ol.source.IGC = function(opt_options) {
var options = goog.isDef(opt_options) ? opt_options : {};
goog.base(this, {
format: new ol.format.IGC({
altitudeMode: options.altitudeMode
}),
text: options.text,
url: options.url
});
};
goog.inherits(ol.source.IGC, ol.source.VectorFile);