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
+1 -1
View File
@@ -19,7 +19,7 @@ OpenLayers is available as [`ol` npm package](https://npmjs.com/package/ol), whi
By default, OpenLayers uses a performance optimized Canvas renderer. By default, OpenLayers uses a performance optimized Canvas renderer.
OpenLayers runs on all modern browsers that support [HTML5](https://html.spec.whatwg.org/multipage/) and [ECMAScript 5](http://www.ecma-international.org/ecma-262/5.1/). This includes Chrome, Firefox, Safari and Edge. For older browsers and platforms like Internet Explorer (down to version 9) and Android 4.x, [polyfills](http://polyfill.io), the application bundle needs to be transpiled (e.g. using [Babel](https://babeljs.io)) and bundled with polyfills for `requestAnimationFrame`, `Element.prototype.classList` and `URL`. OpenLayers runs on all modern browsers that support [HTML5](https://html.spec.whatwg.org/multipage/) and [ECMAScript 5](http://www.ecma-international.org/ecma-262/5.1/). This includes Chrome, Firefox, Safari and Edge. For older browsers and platforms like Internet Explorer (down to version 9) and Android 4.x, [polyfills](http://polyfill.io), the application bundle needs to be transpiled (e.g. using [Babel](https://babeljs.io)) and bundled with polyfills for `fetch`, `requestAnimationFrame`, `Element.prototype.classList`, `URL` and `TextDecoder`.
The library is intended for use on both desktop/laptop and mobile devices, and supports pointer and touch interactions. The library is intended for use on both desktop/laptop and mobile devices, and supports pointer and touch interactions.
+1
View File
@@ -11,6 +11,7 @@
"GyroNorm": false, "GyroNorm": false,
"jsPDF": false, "jsPDF": false,
"jsts": false, "jsts": false,
"JSZip": false,
"mapboxgl": false, "mapboxgl": false,
"saveAs": false, "saveAs": false,
"toastr": false, "toastr": false,
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
+9
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;
}
+22
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>
+155
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');
});
+3
View File
@@ -0,0 +1,3 @@
.tileCoord input {
width: 60px;
}
+23
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>
+133
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);
});
+16
View File
@@ -10,4 +10,20 @@ cloak:
value: Get your own API key at https://www.maptiler.com/cloud/ value: Get your own API key at https://www.maptiler.com/cloud/
--- ---
<div id="map" class="map"></div> <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> <div id="info">&nbsp;</div>
+78 -19
View File
@@ -1,24 +1,16 @@
import DragAndDrop from '../src/ol/interaction/DragAndDrop.js';
import Map from '../src/ol/Map.js'; import Map from '../src/ol/Map.js';
import View from '../src/ol/View.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 {GPX, GeoJSON, IGC, KML, TopoJSON} from '../src/ol/format.js';
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';
import {Vector as VectorSource, XYZ} from '../src/ol/source.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 key = 'get_your_own_D6rA4zTHduk6KOKTXzGB';
const attributions = const attributions =
'<a href="https://www.maptiler.com/copyright/" target="_blank">&copy; MapTiler</a> ' + '<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>'; '<a href="https://www.openstreetmap.org/copyright" target="_blank">&copy; OpenStreetMap contributors</a>';
const map = new Map({ const map = new Map({
interactions: defaultInteractions().extend([dragAndDropInteraction]),
layers: [ layers: [
new TileLayer({ new TileLayer({
source: new XYZ({ source: new XYZ({
@@ -36,17 +28,39 @@ const map = new Map({
}), }),
}); });
dragAndDropInteraction.on('addfeatures', function (event) { const extractStyles = document.getElementById('extractstyles');
const vectorSource = new VectorSource({ let dragAndDropInteraction;
features: event.features,
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( dragAndDropInteraction.on('addfeatures', function (event) {
new VectorLayer({ const vectorSource = new VectorSource({
source: vectorSource, features: event.features,
}) });
); map.addLayer(
map.getView().fit(vectorSource.getExtent()); new VectorLayer({
}); source: vectorSource,
})
);
map.getView().fit(vectorSource.getExtent());
});
map.addInteraction(dragAndDropInteraction);
}
setInteraction();
extractStyles.addEventListener('change', setInteraction);
const displayFeatureInfo = function (pixel) { const displayFeatureInfo = function (pixel) {
const features = []; const features = [];
@@ -76,3 +90,48 @@ map.on('pointermove', function (evt) {
map.on('click', function (evt) { map.on('click', function (evt) {
displayFeatureInfo(evt.pixel); 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');
});
+1 -1
View File
@@ -9,7 +9,7 @@
function fetchResource(resource) { function fetchResource(resource) {
return new Promise(function (resolve, reject) { 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) { if (isImage) {
resolve ({ resolve ({
isBinary: true, isBinary: true,
+2 -2
View File
@@ -68,7 +68,7 @@
<link rel="stylesheet" href="./css/ol.css" type="text/css"> <link rel="stylesheet" href="./css/ol.css" type="text/css">
<link rel="stylesheet" href="./resources/layout.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://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 }}} {{{ extraHead.local }}}
{{{ css.tag }}} {{{ css.tag }}}
<title>{{ title }}</title> <title>{{ title }}</title>
@@ -194,7 +194,7 @@
&lt;!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer --&gt; &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;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;!-- 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}} {{ indent extraHead.remote spaces=4 }}{{/if}}
&lt;style&gt; &lt;style&gt;
.map { .map {
+43 -8
View File
@@ -5,6 +5,7 @@
import Event from '../events/Event.js'; import Event from '../events/Event.js';
import EventType from '../events/EventType.js'; import EventType from '../events/EventType.js';
import FormatType from '../format/FormatType.js';
import Interaction from './Interaction.js'; import Interaction from './Interaction.js';
import {TRUE} from '../functions.js'; import {TRUE} from '../functions.js';
import {get as getProjection} from '../proj.js'; import {get as getProjection} from '../proj.js';
@@ -12,7 +13,8 @@ import {listen, unlistenByKey} from '../events.js';
/** /**
* @typedef {Object} Options * @typedef {Object} Options
* @property {Array<typeof import("../format/Feature.js").default>} [formatConstructors] Format constructors. * @property {Array<typeof import("../format/Feature.js").default|import("../format/Feature.js").default>} [formatConstructors] Format constructors
* (and/or formats pre-constructed with options).
* @property {import("../source/Vector.js").default} [source] Optional vector source where features will be added. If a source is provided * @property {import("../source/Vector.js").default} [source] Optional vector source where features will be added. If a source is provided
* all existing features will be removed and new features will be added when * all existing features will be removed and new features will be added when
* they are dropped on the target. If you want to add features to a vector * they are dropped on the target. If you want to add features to a vector
@@ -75,6 +77,11 @@ export class DragAndDropEvent extends Event {
/** /**
* @classdesc * @classdesc
* Handles input of vector data by drag and drop. * Handles input of vector data by drag and drop.
*
* Note that the DragAndDrop interaction uses the TextDecoder() constructor if the supplied
* combinnation of formats read both text string and ArrayBuffer sources. Older browsers such
* as IE which do not support this will need a TextDecoder polyfill to be loaded before use.
*
* @api * @api
* *
* @fires DragAndDropEvent * @fires DragAndDropEvent
@@ -92,11 +99,27 @@ class DragAndDrop extends Interaction {
/** /**
* @private * @private
* @type {Array<typeof import("../format/Feature.js").default>} * @type {boolean}
*/ */
this.formatConstructors_ = options.formatConstructors this.readAsBuffer_ = false;
/**
* @private
* @type {Array<import("../format/Feature.js").default>}
*/
this.formats_ = [];
const formatConstructors = options.formatConstructors
? options.formatConstructors ? options.formatConstructors
: []; : [];
for (let i = 0, ii = formatConstructors.length; i < ii; ++i) {
let format = formatConstructors[i];
if (typeof format === 'function') {
format = new format();
}
this.formats_.push(format);
this.readAsBuffer_ =
this.readAsBuffer_ || format.getType() === FormatType.ARRAY_BUFFER;
}
/** /**
* @private * @private
@@ -139,10 +162,18 @@ class DragAndDrop extends Interaction {
projection = view.getProjection(); projection = view.getProjection();
} }
const formatConstructors = this.formatConstructors_; let text;
for (let i = 0, ii = formatConstructors.length; i < ii; ++i) { const formats = this.formats_;
const format = new formatConstructors[i](); for (let i = 0, ii = formats.length; i < ii; ++i) {
const features = this.tryReadFeatures_(format, result, { const format = formats[i];
let input = result;
if (this.readAsBuffer_ && format.getType() !== FormatType.ARRAY_BUFFER) {
if (text === undefined) {
text = new TextDecoder().decode(result);
}
input = text;
}
const features = this.tryReadFeatures_(format, input, {
featureProjection: projection, featureProjection: projection,
}); });
if (features && features.length > 0) { if (features && features.length > 0) {
@@ -249,7 +280,11 @@ class DragAndDrop extends Interaction {
EventType.LOAD, EventType.LOAD,
this.handleResult_.bind(this, file) this.handleResult_.bind(this, file)
); );
reader.readAsText(file); if (this.readAsBuffer_) {
reader.readAsArrayBuffer(file);
} else {
reader.readAsText(file);
}
} }
} }
+131 -3
View File
@@ -2,6 +2,7 @@ import DragAndDrop from '../../../../src/ol/interaction/DragAndDrop.js';
import Event from '../../../../src/ol/events/Event.js'; import Event from '../../../../src/ol/events/Event.js';
import EventTarget from '../../../../src/ol/events/Target.js'; import EventTarget from '../../../../src/ol/events/Target.js';
import GeoJSON from '../../../../src/ol/format/GeoJSON.js'; import GeoJSON from '../../../../src/ol/format/GeoJSON.js';
import MVT from '../../../../src/ol/format/MVT.js';
import VectorSource from '../../../../src/ol/source/Vector.js'; import VectorSource from '../../../../src/ol/source/Vector.js';
import View from '../../../../src/ol/View.js'; import View from '../../../../src/ol/View.js';
@@ -29,8 +30,8 @@ where('FileReader').describe('ol.interaction.DragAndDrop', function () {
expect(interaction).to.be.an(DragAndDrop); expect(interaction).to.be.an(DragAndDrop);
}); });
it('sets formatConstructors on the instance', function () { it('sets formats on the instance', function () {
expect(interaction.formatConstructors_).to.have.length(1); expect(interaction.formats_).to.have.length(1);
}); });
it('accepts a source option', function () { it('accepts a source option', function () {
@@ -88,18 +89,28 @@ where('FileReader').describe('ol.interaction.DragAndDrop', function () {
describe('#handleDrop_', function () { describe('#handleDrop_', function () {
let OrigFileReader; let OrigFileReader;
let mockReadAsText;
let mockReadAsArrayBuffer;
beforeEach(function () { beforeEach(function () {
OrigFileReader = FileReader; OrigFileReader = FileReader;
mockReadAsText = false;
mockReadAsArrayBuffer = false;
class MockFileReader extends EventTarget { class MockFileReader extends EventTarget {
constructor() { constructor() {
super(...arguments); super(...arguments);
} }
readAsText(file) { readAsText(file) {
mockReadAsText = true;
this.result = file; this.result = file;
this.dispatchEvent('load'); this.dispatchEvent('load');
} }
readAsArrayBuffer(file) {
mockReadAsArrayBuffer = true;
this.result = new TextEncoder().encode(file).buffer;
this.dispatchEvent('load');
}
} }
FileReader = MockFileReader; FileReader = MockFileReader;
}); });
@@ -108,9 +119,11 @@ where('FileReader').describe('ol.interaction.DragAndDrop', function () {
FileReader = OrigFileReader; FileReader = OrigFileReader;
}); });
it('reads dropped files', function (done) { it('reads dropped files as text', function (done) {
interaction.on('addfeatures', function (evt) { interaction.on('addfeatures', function (evt) {
expect(evt.features.length).to.be(1); expect(evt.features.length).to.be(1);
expect(mockReadAsText).to.be(true);
expect(mockReadAsArrayBuffer).to.be(false);
done(); done();
}); });
interaction.setMap(map); interaction.setMap(map);
@@ -135,6 +148,121 @@ where('FileReader').describe('ol.interaction.DragAndDrop', function () {
expect(event.propagationStopped).to.be(true); expect(event.propagationStopped).to.be(true);
}); });
it('reads dropped files as arraybuffer', function (done) {
const drop = new DragAndDrop({
formatConstructors: [GeoJSON, MVT],
});
drop.setMap(map);
drop.on('addfeatures', function (evt) {
expect(evt.features.length).to.be(1);
expect(mockReadAsText).to.be(false);
expect(mockReadAsArrayBuffer).to.be(true);
done();
});
const event = new Event();
event.dataTransfer = {};
event.type = 'dragenter';
viewport.dispatchEvent(event);
event.type = 'dragover';
viewport.dispatchEvent(event);
event.type = 'drop';
event.dataTransfer.files = {
length: 1,
item: function () {
return JSON.stringify({
type: 'FeatureCollection',
features: [{type: 'Feature', id: '1'}],
});
},
};
viewport.dispatchEvent(event);
expect(event.dataTransfer.dropEffect).to.be('copy');
expect(event.propagationStopped).to.be(true);
});
it('reads using constructed formats', function (done) {
const drop = new DragAndDrop({
formatConstructors: [new GeoJSON()],
});
drop.setMap(map);
drop.on('addfeatures', function (evt) {
expect(evt.features.length).to.be(1);
expect(mockReadAsText).to.be(true);
expect(mockReadAsArrayBuffer).to.be(false);
done();
});
const event = new Event();
event.dataTransfer = {};
event.type = 'dragenter';
viewport.dispatchEvent(event);
event.type = 'dragover';
viewport.dispatchEvent(event);
event.type = 'drop';
event.dataTransfer.files = {
length: 1,
item: function () {
return JSON.stringify({
type: 'FeatureCollection',
features: [{type: 'Feature', id: '1'}],
});
},
};
viewport.dispatchEvent(event);
expect(event.dataTransfer.dropEffect).to.be('copy');
expect(event.propagationStopped).to.be(true);
});
it('reads using arraybuffer formats', function (done) {
class binaryGeoJSON extends GeoJSON {
constructor(options) {
super(options);
}
getType() {
return 'arraybuffer';
}
readFeatures(source, options) {
const data = new TextDecoder().decode(source);
return super.readFeatures(data, options);
}
}
const drop = new DragAndDrop({
formatConstructors: [binaryGeoJSON],
});
drop.setMap(map);
drop.on('addfeatures', function (evt) {
expect(evt.features.length).to.be(1);
expect(mockReadAsText).to.be(false);
expect(mockReadAsArrayBuffer).to.be(true);
done();
});
const event = new Event();
event.dataTransfer = {};
event.type = 'dragenter';
viewport.dispatchEvent(event);
event.type = 'dragover';
viewport.dispatchEvent(event);
event.type = 'drop';
event.dataTransfer.files = {
length: 1,
item: function () {
return JSON.stringify({
type: 'FeatureCollection',
features: [{type: 'Feature', id: '1'}],
});
},
};
viewport.dispatchEvent(event);
expect(event.dataTransfer.dropEffect).to.be('copy');
expect(event.propagationStopped).to.be(true);
});
it('adds dropped features to a source', function (done) { it('adds dropped features to a source', function (done) {
const source = new VectorSource(); const source = new VectorSource();
const drop = new DragAndDrop({ const drop = new DragAndDrop({