Support formats that read ArrayBuffer sources

Drag-and-Drop support for formats that read arraybuffer sources
Add TextDecoder to polyfills
Add custom Drag-and-Drop examples for KMZ and MVT
Add sample download and KML styles option to existing example
Test arraybuffer and constructed formats
This commit is contained in:
mike-000
2020-11-06 16:22:34 +00:00
parent bdd343e87b
commit a6860118c7
17 changed files with 5280 additions and 34 deletions

View File

@@ -11,6 +11,7 @@
"GyroNorm": false,
"jsPDF": false,
"jsts": false,
"JSZip": false,
"mapboxgl": false,
"saveAs": false,
"toastr": false,

4661
examples/data/kml/states.kml Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
#info {
width: 100%;
height: 24rem;
overflow: scroll;
display: flex;
align-items: baseline;
border: 1px solid black;
justify-content: flex-start;
}

View File

@@ -0,0 +1,22 @@
---
layout: example.html
title: Custom Drag-and-Drop (KMZ)
shortdesc: Example of using the drag-and-drop interaction with a custom format to handle KMZ files.
docs: >
Example of using the drag-and-drop interaction with a custom format to handle KMZ files.
In addition to the formats used in the [Drag-and-Drop](drag-and-drop.html) example a custom format (subclassing KML) is used to handle KMZ files.
KML and icons must be extracted from the KMZ array buffer synchronously.
<a href="https://stuk.github.io/jszip/documentation/upgrade_guide.html" target="_blank">JSZip 2.x</a> is used as it has better browser compatibility and is simpler to code than the more recent <a href="https://github.com/ericvergnaud/jszip#readme" target="_blank">JSZip-sync</a>.
There is no projection transform support, so this will only work with data in EPSG:4326 and EPSG:3857.
tags: "drag-and-drop, kml, kmz"
resources:
- https://cdnjs.cloudflare.com/ajax/libs/jszip/2.6.1/jszip.min.js
---
<div id="map" class="map"></div>
<br />
<div>
<a id="download" download></a>
<button id="download-kmz">Download sample</button>
</div>
<br />
<div id="info">&nbsp;</div>

View File

@@ -0,0 +1,155 @@
import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js';
import {
DragAndDrop,
defaults as defaultInteractions,
} from '../src/ol/interaction.js';
import {GPX, GeoJSON, IGC, KML, TopoJSON} from '../src/ol/format.js';
import {OSM, Vector as VectorSource} from '../src/ol/source.js';
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';
// Create functions to extract KML and icons from KMZ array buffer,
// which must be done synchronously.
const zip = new JSZip();
function getKMLData(buffer) {
let kmlData;
zip.load(buffer);
const kmlFile = zip.file(/.kml$/i)[0];
if (kmlFile) {
kmlData = kmlFile.asText();
}
return kmlData;
}
function getKMLImage(href) {
let url = href;
let path = window.location.href;
path = path.slice(0, path.lastIndexOf('/') + 1);
if (href.indexOf(path) === 0) {
const regexp = new RegExp(href.replace(path, '') + '$', 'i');
const kmlFile = zip.file(regexp)[0];
if (kmlFile) {
url = URL.createObjectURL(new Blob([kmlFile.asArrayBuffer()]));
}
}
return url;
}
// Define a KMZ format class by subclassing ol/format/KML
class KMZ extends KML {
constructor(opt_options) {
const options = opt_options || {};
options.iconUrlFunction = getKMLImage;
super(options);
}
getType() {
return 'arraybuffer';
}
readFeature(source, options) {
const kmlData = getKMLData(source);
return super.readFeature(kmlData, options);
}
readFeatures(source, options) {
const kmlData = getKMLData(source);
return super.readFeatures(kmlData, options);
}
}
// Set up map with Drag and Drop interaction
const dragAndDropInteraction = new DragAndDrop({
formatConstructors: [KMZ, GPX, GeoJSON, IGC, KML, TopoJSON],
});
const map = new Map({
interactions: defaultInteractions().extend([dragAndDropInteraction]),
layers: [
new TileLayer({
source: new OSM(),
}),
],
target: 'map',
view: new View({
center: [0, 0],
zoom: 2,
}),
});
dragAndDropInteraction.on('addfeatures', function (event) {
const vectorSource = new VectorSource({
features: event.features,
});
map.addLayer(
new VectorLayer({
source: vectorSource,
})
);
map.getView().fit(vectorSource.getExtent());
});
const displayFeatureInfo = function (pixel) {
const features = [];
map.forEachFeatureAtPixel(pixel, function (feature) {
features.push(feature);
});
if (features.length > 0) {
const info = [];
let i, ii;
for (i = 0, ii = features.length; i < ii; ++i) {
const description =
features[i].get('description') ||
features[i].get('name') ||
features[i].get('_name') ||
features[i].get('layer');
if (description) {
info.push(description);
}
}
document.getElementById('info').innerHTML = info.join('<br/>') || '&nbsp';
} else {
document.getElementById('info').innerHTML = '&nbsp;';
}
};
map.on('pointermove', function (evt) {
if (evt.dragging) {
return;
}
const pixel = map.getEventPixel(evt.originalEvent);
displayFeatureInfo(pixel);
});
map.on('click', function (evt) {
displayFeatureInfo(evt.pixel);
});
// Sample data download
const link = document.getElementById('download');
function download(fullpath, filename) {
fetch(fullpath)
.then(function (response) {
return response.blob();
})
.then(function (blob) {
if (navigator.msSaveBlob) {
// link download attribuute does not work on MS browsers
navigator.msSaveBlob(blob, filename);
} else {
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
}
});
}
document.getElementById('download-kmz').addEventListener('click', function () {
download('data/kmz/iceland.kmz', 'iceland.kmz');
});

View File

@@ -0,0 +1,3 @@
.tileCoord input {
width: 60px;
}

View File

@@ -0,0 +1,23 @@
---
layout: example.html
title: Custom Drag-and-Drop (MVT preview)
shortdesc: Example of using the drag-and-drop interaction with a custom format to preview MVT tiles.
docs: >
Example of using the drag-and-drop interaction with a custom format to preview MVT tiles.
In addition to the formats used in the [Drag-and-Drop](drag-and-drop.html) example individual MVT tiles can be previewed.
There is no projection transform support, so this will only work with data in EPSG:4326 and EPSG:3857.
tags: "drag-and-drop, mvt"
---
<div id="map" class="map"></div>
<br />
<div class="tileCoord">
<a id="download" download></a>
<span>Tile coordinate&nbsp;&nbsp;</span>
<span>&nbsp;z: <input type="number" id="tileCoordZ" value="6" /></span>
<span>&nbsp;x: <input type="number" id="tileCoordX" value="30" /></span>
<span>&nbsp;y: <input type="number" id="tileCoordY" value="20" /></span>
<span>&nbsp;&nbsp;</span>
<button id="download-mvt">Download sample</button>
</div>
<br />
<div id="info">&nbsp;</div>

View File

@@ -0,0 +1,133 @@
import Feature from '../src/ol/Feature.js';
import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js';
import {
DragAndDrop,
defaults as defaultInteractions,
} from '../src/ol/interaction.js';
import {GPX, GeoJSON, IGC, KML, MVT, TopoJSON} from '../src/ol/format.js';
import {OSM, Vector as VectorSource} from '../src/ol/source.js';
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';
import {createXYZ} from '../src/ol/tilegrid.js';
// Define a custom MVT format as ol/format/MVT requires an extent
const tileCoordZ = document.getElementById('tileCoordZ');
const tileCoordX = document.getElementById('tileCoordX');
const tileCoordY = document.getElementById('tileCoordY');
class customMVT extends MVT {
constructor() {
super({featureClass: Feature});
}
readFeatures(source, options) {
options.extent = createXYZ().getTileCoordExtent([
parseInt(tileCoordZ.value),
parseInt(tileCoordX.value),
parseInt(tileCoordY.value),
]);
return super.readFeatures(source, options);
}
}
// Set up map with Drag and Drop interaction
const dragAndDropInteraction = new DragAndDrop({
formatConstructors: [customMVT, GPX, GeoJSON, IGC, KML, TopoJSON],
});
const map = new Map({
interactions: defaultInteractions().extend([dragAndDropInteraction]),
layers: [
new TileLayer({
source: new OSM(),
}),
],
target: 'map',
view: new View({
center: [0, 0],
zoom: 2,
}),
});
dragAndDropInteraction.on('addfeatures', function (event) {
const vectorSource = new VectorSource({
features: event.features,
});
map.addLayer(
new VectorLayer({
source: vectorSource,
})
);
map.getView().fit(vectorSource.getExtent());
});
const displayFeatureInfo = function (pixel) {
const features = [];
map.forEachFeatureAtPixel(pixel, function (feature) {
features.push(feature);
});
if (features.length > 0) {
const info = [];
let i, ii;
for (i = 0, ii = features.length; i < ii; ++i) {
const description =
features[i].get('name') ||
features[i].get('_name') ||
features[i].get('layer');
if (description) {
info.push(description);
}
}
document.getElementById('info').innerHTML = info.join(', ') || '&nbsp';
} else {
document.getElementById('info').innerHTML = '&nbsp;';
}
};
map.on('pointermove', function (evt) {
if (evt.dragging) {
return;
}
const pixel = map.getEventPixel(evt.originalEvent);
displayFeatureInfo(pixel);
});
map.on('click', function (evt) {
displayFeatureInfo(evt.pixel);
});
// Sample data download
const link = document.getElementById('download');
function download(fullpath, filename) {
fetch(fullpath)
.then(function (response) {
return response.blob();
})
.then(function (blob) {
if (navigator.msSaveBlob) {
// link download attribuute does not work on MS browsers
navigator.msSaveBlob(blob, filename);
} else {
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
}
});
}
document.getElementById('download-mvt').addEventListener('click', function () {
const fullpath =
'https://basemaps.arcgis.com/v1/arcgis/rest/services/World_Basemap/VectorTileServer/tile/' +
tileCoordZ.value +
'/' +
tileCoordY.value +
'/' +
tileCoordX.value +
'.pbf';
const filename =
tileCoordZ.value + '-' + tileCoordX.value + '-' + tileCoordY.value + '.mvt';
download(fullpath, filename);
});

View File

@@ -10,4 +10,20 @@ cloak:
value: Get your own API key at https://www.maptiler.com/cloud/
---
<div id="map" class="map"></div>
<div><input type="checkbox" id="extractstyles" checked /> Extract styles from KML</div>
<br />
<div>
<a id="download" download></a>
Download samples:&nbsp;&nbsp;
<button id="download-gpx">GPX</button>
&nbsp;
<button id="download-geojson">GeoJSON</button>
&nbsp;
<button id="download-igc">IGC</button>
&nbsp;
<button id="download-kml">KML</button>
&nbsp;
<button id="download-topojson">TopoJSON</button>
</div>
<br />
<div id="info">&nbsp;</div>

View File

@@ -1,24 +1,16 @@
import DragAndDrop from '../src/ol/interaction/DragAndDrop.js';
import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js';
import {
DragAndDrop,
defaults as defaultInteractions,
} from '../src/ol/interaction.js';
import {GPX, GeoJSON, IGC, KML, TopoJSON} from '../src/ol/format.js';
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';
import {Vector as VectorSource, XYZ} from '../src/ol/source.js';
const dragAndDropInteraction = new DragAndDrop({
formatConstructors: [GPX, GeoJSON, IGC, KML, TopoJSON],
});
const key = 'get_your_own_D6rA4zTHduk6KOKTXzGB';
const attributions =
'<a href="https://www.maptiler.com/copyright/" target="_blank">&copy; MapTiler</a> ' +
'<a href="https://www.openstreetmap.org/copyright" target="_blank">&copy; OpenStreetMap contributors</a>';
const map = new Map({
interactions: defaultInteractions().extend([dragAndDropInteraction]),
layers: [
new TileLayer({
source: new XYZ({
@@ -36,17 +28,39 @@ const map = new Map({
}),
});
dragAndDropInteraction.on('addfeatures', function (event) {
const vectorSource = new VectorSource({
features: event.features,
const extractStyles = document.getElementById('extractstyles');
let dragAndDropInteraction;
function setInteraction() {
if (dragAndDropInteraction) {
map.removeInteraction(dragAndDropInteraction);
}
dragAndDropInteraction = new DragAndDrop({
formatConstructors: [
GPX,
GeoJSON,
IGC,
// use constructed format to set options
new KML({extractStyles: extractStyles.checked}),
TopoJSON,
],
});
map.addLayer(
new VectorLayer({
source: vectorSource,
})
);
map.getView().fit(vectorSource.getExtent());
});
dragAndDropInteraction.on('addfeatures', function (event) {
const vectorSource = new VectorSource({
features: event.features,
});
map.addLayer(
new VectorLayer({
source: vectorSource,
})
);
map.getView().fit(vectorSource.getExtent());
});
map.addInteraction(dragAndDropInteraction);
}
setInteraction();
extractStyles.addEventListener('change', setInteraction);
const displayFeatureInfo = function (pixel) {
const features = [];
@@ -76,3 +90,48 @@ map.on('pointermove', function (evt) {
map.on('click', function (evt) {
displayFeatureInfo(evt.pixel);
});
// Sample data downloads
const link = document.getElementById('download');
function download(fullpath, filename) {
fetch(fullpath)
.then(function (response) {
return response.blob();
})
.then(function (blob) {
if (navigator.msSaveBlob) {
// link download attribuute does not work on MS browsers
navigator.msSaveBlob(blob, filename);
} else {
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
}
});
}
document.getElementById('download-gpx').addEventListener('click', function () {
download('data/gpx/fells_loop.gpx', 'fells_loop.gpx');
});
document
.getElementById('download-geojson')
.addEventListener('click', function () {
download('data/geojson/roads-seoul.geojson', 'roads-seoul.geojson');
});
document.getElementById('download-igc').addEventListener('click', function () {
download('data/igc/Ulrich-Prinz.igc', 'Ulrich-Prinz.igc');
});
document.getElementById('download-kml').addEventListener('click', function () {
download('data/kml/states.kml', 'states.kml');
});
document
.getElementById('download-topojson')
.addEventListener('click', function () {
download('data/topojson/fr-departments.json', 'fr-departments.json');
});

View File

@@ -9,7 +9,7 @@
function fetchResource(resource) {
return new Promise(function (resolve, reject) {
const isImage = /\.(png|jpe?g|gif|tiff|svg)$/.test(resource);
const isImage = /\.(png|jpe?g|gif|tiff|svg|kmz)$/.test(resource);
if (isImage) {
resolve ({
isBinary: true,

View File

@@ -68,7 +68,7 @@
<link rel="stylesheet" href="./css/ol.css" type="text/css">
<link rel="stylesheet" href="./resources/layout.css" type="text/css">
<script src="https://unpkg.com/elm-pep"></script>
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=fetch,requestAnimationFrame,Element.prototype.classList,URL"></script>
<script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=fetch,requestAnimationFrame,Element.prototype.classList,URL,TextDecoder"></script>
{{{ extraHead.local }}}
{{{ css.tag }}}
<title>{{ title }}</title>
@@ -194,7 +194,7 @@
&lt;!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer --&gt;
&lt;script src="https://unpkg.com/elm-pep"&gt;&lt;/script&gt;{{#if extraHead.remote}}
&lt;!-- The line below is only needed for old environments like Internet Explorer and Android 4.x --&gt;
&lt;script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=fetch,requestAnimationFrame,Element.prototype.classList,URL"&gt;&lt;/script&gt;
&lt;script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=fetch,requestAnimationFrame,Element.prototype.classList,URL,TextDecoder"&gt;&lt;/script&gt;
{{ indent extraHead.remote spaces=4 }}{{/if}}
&lt;style&gt;
.map {