问题
I have a KML element with no document tags and just tags like...(with some irrelevant elements removed with ...)
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<GroundOverlay>
<name>...</name>
<description>...</description>
<Icon><href>...</href></Icon>
<LatLonBox>
...
</LatLonBox>
</GroundOverlay>
</kml>
In OpenLayers, I can't get this to load without document tags around the GroundOverlay. I can however get a place mark that is the root node with no document tags to load fine.
Is there a way to a KML with a GroundOverlay root node into KML that doesn't have document tags? It seems to load fine in other renderers like Google Earth and Cesium.
回答1:
It is not supported in OpenLayers
https://github.com/openlayers/openlayers/issues/2941
回答2:
It is possible to use ground overlay tiles as the source for an OpenLayers layer. So far I have only done that by hard coding in the coordinates, tile sizes, tile paths, etc. manually read from the KML and letting OpenLayers calculate the correct projection, and although proj4 does allow rotated projections I have not yet sorted out the maths needed to handle rotations. I have a demo based on sample data from https://www.dwgwalking.co.uk/garminTryB4Buy.htm The demo http://mikenunn.16mb.com/demo/dwg-aracena-demo.htm uses east/west coordinates from the top row and north/south coordinates left column and ignores the very small rotation.
UPDATE
I've now developed this to a potential library function to parse a KML file for ground overlays and return a layer group of ImageStatic sources and layers. Rotation is also supported. The only required parameter is the KML document url. Supported options are attributions
and crossOrigin
.
Here's the previous multi-pverlay demo (with very small rotation) loaded directly from the KML document:
var tileLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
attributions: [
'Powered by Esri',
'Source: Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community'
],
attributionsCollapsible: false,
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
maxZoom: 23
})
});
var map = new ol.Map({
layers: [tileLayer],
target: 'map',
logo: false,
view: new ol.View({
center:[0, 0],
zoom: 2
})
});
var extent = ol.extent.createEmpty();
var group = kmlOverlay.loadUrl(
'https://www.mikenunn.net/data/dwg/aracena/doc.kml',
{ attributions: 'Copyright David Brawn <a href="https://www.dwgwalking.co.uk" target="_blank">www.dwgwalking.co.uk</a>' }
);
group.getLayers().on('add', function(evt) {
evt.element.once('change:source', function() {
if (evt.element.getSource && evt.element.getSource().getProjection) {
var imageProj = evt.element.getSource().getProjection();
ol.extent.extend(extent, ol.proj.transformExtent(imageProj.getExtent(), imageProj, map.getView().getProjection()));
map.getView().fit(extent);
}
});
});
group.setOpacity(0.7);
map.addLayer(group);
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.map {
width: 100%;
height: 100%;
}
<link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
<!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.5.0/proj4.js"></script>
<script>
kmlOverlay = function() {
function loadUrl ( url,
opt_options // attributions (defaults to undefined), crossOrigin (defaults to 'anonymous')
) {
var options = opt_options || {};
var crossOrigin = options.crossOrigin === undefined ? 'anonymous' : options.crossOrigin;
var group = new ol.layer.Group();
function addLayer(name, extent, url, rotation) { // function to maintain context during async img load
var imageLayer = new ol.layer.Image({
title: name
});
group.getLayers().push(imageLayer);
var imageSize = [];
var img = document.createElement('img');
img.onload = function() {
imageSize[0] = img.width;
imageSize[1] = img.height;
imageLayer.setSource(
source (
extent,
url,
rotation,
imageSize,
{ attributions: options.attributions, crossOrigin: crossOrigin }
)
);
};
img.crossOrigin = crossOrigin;
img.src = url;
}
var last = url.lastIndexOf('/') + 1;
path = url.slice(0, last);
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(xhr.responseText,'text/xml');
var elements = xmlDoc.getElementsByTagName('GroundOverlay');
for (var i=0; i<elements.length; i++) {
var name;
if (elements[i].getElementsByTagName('rotation').length > 0) {
name = elements[i].getElementsByTagName('name')[0].childNodes[0].nodeValue;
}
var href = elements[i].getElementsByTagName('href')[0].childNodes[0].nodeValue;
if (href.indexOf('http:') != 0 && href.indexOf('https:') != 0) {
href = path + href;
}
var north = Number(elements[i].getElementsByTagName('north')[0].childNodes[0].nodeValue);
var south = Number(elements[i].getElementsByTagName('south')[0].childNodes[0].nodeValue);
var east = Number(elements[i].getElementsByTagName('east')[0].childNodes[0].nodeValue);
var west = Number(elements[i].getElementsByTagName('west')[0].childNodes[0].nodeValue);
var rotation = 0;
if (elements[i].getElementsByTagName('rotation').length > 0) {
rotation = Number(elements[i].getElementsByTagName('rotation')[0].childNodes[0].nodeValue);
}
addLayer(name, [west, south, east, north], href, rotation);
}
}
xhr.send();
return group;
}
function source ( kmlExtent, // KMLs specify the extent the unrotated image would occupy
url,
rotation,
imageSize,
opt_options // attributions, crossOrigin (default to undefined)
) {
var options = opt_options || {};
// calculate latitude of true scale of equidistant cylindrical projection based on pixels per degree on each axis
proj4.defs('EPSG:' + url, '+proj=eqc +lat_ts=' +
(Math.acos((ol.extent.getHeight(kmlExtent)/imageSize[1])
/(ol.extent.getWidth(kmlExtent)/imageSize[0]))*180/Math.PI) +
' +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
if (ol.proj.proj4 && ol.proj.proj4.register) { ol.proj.proj4.register(proj4); } // if OL5 register proj4
// convert the extents to source projection coordinates
var projection = ol.proj.get('EPSG:' + url);
var projExtent = ol.proj.transformExtent(kmlExtent, 'EPSG:4326', projection);
var angle = -rotation * Math.PI/180;
function rotateTransform(coordinate) {
var point = new ol.geom.Point(coordinate);
point.rotate(angle, ol.extent.getCenter(projExtent));
return point.getCoordinates();
}
function normalTransform(coordinate) {
var point = new ol.geom.Point(coordinate);
point.rotate(-angle, ol.extent.getCenter(projExtent));
return point.getCoordinates();
}
var rotatedProjection = new ol.proj.Projection({
code: 'EPSG:' + url + ':rotation:' + rotation,
units: 'm',
extent: projExtent
});
ol.proj.addProjection(rotatedProjection);
ol.proj.addCoordinateTransforms('EPSG:4326', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, 'EPSG:4326', projection));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), projection, 'EPSG:4326');
}
);
ol.proj.addCoordinateTransforms('EPSG:3857', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, 'EPSG:3857', projection));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), projection, 'EPSG:3857');
}
);
return new ol.source.ImageStatic({
projection: rotatedProjection,
url: url,
imageExtent: projExtent,
attributions: options.attributions,
crossOrigin: options.crossOrigin
});
}
return {
"loadUrl" : loadUrl,
"source" : source
}
} ();
</script>
<div id="map" class="map"></div>
Here's a single overlay demo with very conspicuous rotation taken from https://renenyffenegger.ch/notes/tools/Google-Earth/kml/index
var tileLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
attributions: [
'Powered by Esri',
'Source: Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community'
],
//attributionsCollapsible: false,
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
maxZoom: 23
})
});
var map = new ol.Map({
layers: [tileLayer],
target: 'map',
logo: false,
view: new ol.View({
center:[0, 0],
zoom: 2
})
});
var extent = ol.extent.createEmpty();
var group = kmlOverlay.loadUrl(
'https://raw.githubusercontent.com/ReneNyffenegger/about-GoogleEarth/master/kml/GroundOverlay.kml'
);
group.getLayers().once('add', function(evt) {
evt.element.once('change:source', function() {
if (evt.element.getSource && evt.element.getSource().getProjection) {
var imageProj = evt.element.getSource().getProjection();
ol.extent.extend(extent, ol.proj.transformExtent(imageProj.getExtent(), imageProj, map.getView().getProjection()));
map.getView().fit(extent, { constrainResolution: false });
}
});
});
group.setOpacity(0.8);
map.addLayer(group);
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.map {
width: 100%;
height: 100%;
}
<link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
<!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.5.0/proj4.js"></script>
<script>
kmlOverlay = function() {
function loadUrl ( url,
opt_options // attributions (defaults to undefined), crossOrigin (defaults to 'anonymous')
) {
var options = opt_options || {};
var crossOrigin = options.crossOrigin === undefined ? 'anonymous' : options.crossOrigin;
var group = new ol.layer.Group();
function addLayer(name, extent, url, rotation) { // function to maintain context during async img load
var imageLayer = new ol.layer.Image({
title: name
});
group.getLayers().push(imageLayer);
var imageSize = [];
var img = document.createElement('img');
img.onload = function() {
imageSize[0] = img.width;
imageSize[1] = img.height;
imageLayer.setSource(
source (
extent,
url,
rotation,
imageSize,
{ attributions: options.attributions, crossOrigin: crossOrigin }
)
);
};
img.crossOrigin = crossOrigin;
img.src = url;
}
var last = url.lastIndexOf('/') + 1;
path = url.slice(0, last);
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(xhr.responseText,'text/xml');
var elements = xmlDoc.getElementsByTagName('GroundOverlay');
for (var i=0; i<elements.length; i++) {
var name;
if (elements[i].getElementsByTagName('rotation').length > 0) {
name = elements[i].getElementsByTagName('name')[0].childNodes[0].nodeValue;
}
var href = elements[i].getElementsByTagName('href')[0].childNodes[0].nodeValue;
if (href.indexOf('http:') != 0 && href.indexOf('https:') != 0) {
href = path + href;
}
var north = Number(elements[i].getElementsByTagName('north')[0].childNodes[0].nodeValue);
var south = Number(elements[i].getElementsByTagName('south')[0].childNodes[0].nodeValue);
var east = Number(elements[i].getElementsByTagName('east')[0].childNodes[0].nodeValue);
var west = Number(elements[i].getElementsByTagName('west')[0].childNodes[0].nodeValue);
var rotation = 0;
if (elements[i].getElementsByTagName('rotation').length > 0) {
rotation = Number(elements[i].getElementsByTagName('rotation')[0].childNodes[0].nodeValue);
}
addLayer(name, [west, south, east, north], href, rotation);
}
}
xhr.send();
return group;
}
function source ( kmlExtent, // KMLs specify the extent the unrotated image would occupy
url,
rotation,
imageSize,
opt_options // attributions, crossOrigin (default to undefined)
) {
var options = opt_options || {};
// calculate latitude of true scale of equidistant cylindrical projection based on pixels per degree on each axis
proj4.defs('EPSG:' + url, '+proj=eqc +lat_ts=' +
(Math.acos((ol.extent.getHeight(kmlExtent)/imageSize[1])
/(ol.extent.getWidth(kmlExtent)/imageSize[0]))*180/Math.PI) +
' +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
if (ol.proj.proj4 && ol.proj.proj4.register) { ol.proj.proj4.register(proj4); } // if OL5 register proj4
// convert the extents to source projection coordinates
var projection = ol.proj.get('EPSG:' + url);
var projExtent = ol.proj.transformExtent(kmlExtent, 'EPSG:4326', projection);
var angle = -rotation * Math.PI/180;
function rotateTransform(coordinate) {
var point = new ol.geom.Point(coordinate);
point.rotate(angle, ol.extent.getCenter(projExtent));
return point.getCoordinates();
}
function normalTransform(coordinate) {
var point = new ol.geom.Point(coordinate);
point.rotate(-angle, ol.extent.getCenter(projExtent));
return point.getCoordinates();
}
var rotatedProjection = new ol.proj.Projection({
code: 'EPSG:' + url + ':rotation:' + rotation,
units: 'm',
extent: projExtent
});
ol.proj.addProjection(rotatedProjection);
ol.proj.addCoordinateTransforms('EPSG:4326', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, 'EPSG:4326', projection));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), projection, 'EPSG:4326');
}
);
ol.proj.addCoordinateTransforms('EPSG:3857', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, 'EPSG:3857', projection));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), projection, 'EPSG:3857');
}
);
return new ol.source.ImageStatic({
projection: rotatedProjection,
url: url,
imageExtent: projExtent,
attributions: options.attributions,
crossOrigin: options.crossOrigin
});
}
return {
"loadUrl" : loadUrl,
"source" : source
}
} ();
</script>
<div id="map" class="map"></div>
来源:https://stackoverflow.com/questions/52082468/kml-openlayers-groundoverlay-element-that-doesnt-have-document-tags-not-loadi