OL3 V3.1.1 to V3.8.2 broke ol.source.xyz from PouchDB - openlayers-3

I recently updated my Cordova mobile mapping app from OL3 V3.1.1 to V3.7.0 to V3.8.2.
Am using PouchDB to store off-line tiles, and with V3.1.1 tiles were visible.
Here is the code snippet:
OSM_bc_offline_pouchdb = new ol.layer.Tile({
//maxResolution: 5000,
//extent: BC,
//projection: spherical_mercator,
//crossOrigin: 'anonymous',
source: new ol.source.XYZ({
//adapted from: http://jsfiddle.net/gussy/LCNWC/
tileLoadFunction: function (imageTile, src) {
pouchTilesDB_osm_bc_baselayer.getAttachment(src, 'tile', function (err, res) {
if (err && err.error == 'not_found')
return;
//if(!res) return; // ?issue -> causes map refresh on movement to stop
imageTile.getImage().src = window.URL.createObjectURL(res);
});
},
tileUrlFunction: function (coordinate, projection) {
if (coordinate == null)
return undefined;
// OSM NW origin style URL
var z = coordinate[0];
var x = coordinate[1];
var y = coordinate[2];
var imgURL = ["tile", z, x, y].join('_');
return imgURL;
}
})
});
trails_mobileMap.addLayer(OSM_bc_offline_pouchdb);
OSM_bc_offline_pouchdb.setVisible(true);
Moving to both V3.7.0 and V3.8.2 causes the tiles to not display. Read the API and I'm missing why this would happen.
What in my code needs updating to work with OL-V3.8.2?
Thanks,
Peter

Your issue might be related to the changes to ol.TileCoord in OpenLayers 3.7.0. From the release notes:
Until now, the API exposed two different types of ol.TileCoord tile coordinates: internal ones that increase left to right and upward, and transformed ones that may increase downward, as defined by a transform function on the tile grid. With this change, the API now only exposes tile coordinates that increase left to right and upward.
Previously, tile grids created by OpenLayers either had their origin at the top-left or at the bottom-left corner of the extent. To make it easier for application developers to transform tile coordinates to the common XYZ tiling scheme, all tile grids that OpenLayers creates internally have their origin now at the top-left corner of the extent.
This change affects applications that configure a custom tileUrlFunction for an ol.source.Tile. Previously, the tileUrlFunction was called with rather unpredictable tile coordinates, depending on whether a tile coordinate transform took place before calling the tileUrlFunction. Now it is always called with OpenLayers tile coordinates. To transform these into the common XYZ tiling scheme, a custom tileUrlFunction has to change the y value (tile row) of the ol.TileCoord:
function tileUrlFunction = function(tileCoord, pixelRatio, projection){
var urlTemplate = '{z}/{x}/{y}';
return urlTemplate
.replace('{z}', tileCoord[0].toString())
.replace('{x}', tileCoord[1].toString())
.replace('{y}', (-tileCoord[2] - 1).toString());
}
If this is your issue, try changing your tileUrlFunction to
function (coordinate, projection) {
if (coordinate == null)
return undefined;
// OSM NW origin style URL
var z = coordinate[0];
var x = coordinate[1];
var y = (-coordinate[2] - 1);
var imgURL = ["tile", z, x, y].join('_');
return imgURL;
}

Related

OpenLayers - lock rotation of box or rectangle geometry while modifying

Openlayers provides useful functions for drawing boxes and rectangles and also has ol.geom.Geometry.prototype.rotate(angle, anchor) for rotating a geometry around a certain anchor. Is it possible to lock the rotation of a box/rectangle while modifying it?
Using the OpenLayers example located here to draw a box with a certain rotation to illustrate the point:
I would like the box/rectangle to maintain its rotation while still being able to drag the sides longer and shorter. Is there a simple way to achieve this?
Answering with the solution I came up with.
First of all, add the feature(s) to a ModifyInteraction so you are able to modify by dragging the corners of the feature.
this.modifyInteraction = new Modify({
deleteCondition: eventsCondition.never,
features: this.drawInteraction.features,
insertVertexCondition: eventsCondition.never,
});
this.map.addInteraction(this.modifyInteraction);
Also, add event handlers upon the events "modifystart" and "modifyend".
this.modifyInteraction.on("modifystart", this.modifyStartFunction);
this.modifyInteraction.on("modifyend", this.modifyEndFunction);
The functions for "modifystart" and "modifyend" look like this.
private modifyStartFunction(event) {
const features = event.features;
const feature = features.getArray()[0];
this.featureAtModifyStart = feature.clone();
this.draggedCornerAtModifyStart = "";
feature.on("change", this.changeFeatureFunction);
}
private modifyEndFunction(event) {
const features = event.features;
const feature = features.getArray()[0];
feature.un("change", this.changeFeatureFunction);
// removing and adding feature to force reindexing
// of feature's snappable edges in OpenLayers
this.drawInteraction.features.clear();
this.drawInteraction.features.push(feature);
this.dispatchRettighetModifyEvent(feature);
}
The changeFeatureFunction is below. This function is called for every single change which is done to the geometry as long as the user is still modifying/dragging one of the corners. Inside this function, I made another function to adjust the modified rectangle into a rectangle again. This "Rectanglify"-function moves the corners which are adjacent to the corner which was just moved by the user.
private changeFeatureFunction(event) {
let feature = event.target;
let geometry = feature.getGeometry();
// Removing change event temporarily to avoid infinite recursion
feature.un("change", this.changeFeatureFunction);
this.rectanglifyModifiedGeometry(geometry);
// Reenabling change event
feature.on("change", this.changeFeatureFunction);
}
Without going into too much detail, the rectanglify-function needs to
find rotation of geometry in radians
inversely rotate with radians * -1 (e.g. geometry.rotate(radians * (-1), anchor) )
update neighboring corners of the dragged corner (easier to do when we have a rectangle which is parallel to the x and y axes)
rotate back with the rotation we found in 1
--
In order to get the rotation of the rectangle, we can do this:
export function getRadiansFromRectangle(feature: Feature): number {
const coords = getCoordinates(feature);
const point1 = coords[0];
const point2 = coords[1];
const deltaY = (point2[1] as number) - (point1[1] as number);
const deltaX = (point2[0] as number) - (point1[0] as number);
return Math.atan2(deltaY, deltaX);
}

384x384px tile to diplay at 384x384px on map (retina)

I serve TMS tile with in two flavours: 256px or 384px through renderd option scale=1.5.
With Openlayers 3, the only way I found to display these 384px tiles their original size is to transform the canvas context like this:
map.getViewport().getElementsByTagName('canvas')[0].getContext("2d").setTransform(1.5, 0, 0, 1.5, -w, -h);
I think it's not the proper way to go, so what would be the right one?
I played a bit with a special ol.tilegrid but with no success, see here:
https://jsfiddle.net/yvecai/owwc5bo8/8/
The output I aim for is on the right map.
There is no need to create a special tile grid or to apply any canvas scaling. All you need to do is set the tilePixelRatio of the source properly, which would be 1.5 in your case:
source: new ol.source.XYZ({
url: "http://www5.opensnowmap.org/base_snow_map_high_dpi/{z}/{x}/{y}.png?debug",
attributions: [/* ... */],
tilePixelRatio: 1.5
})
Also note that your expectation of the result is wrong. I updated your fiddle to compare the standard 256px tiles (on the right) with the hidpi 384px tiles (on the left). If you are viewing the fiddle on a hidpi display, you'll notice the difference. https://jsfiddle.net/owwc5bo8/9/
To summarize:
If you want to display high-dpi tiles on a mobile device with good sharpness, use tilePixelRatio :
https://jsfiddle.net/owwc5bo8/9/
If you want to display tiles with a size differnet than 256x256, create a proper ol.tilegrid, and a proper ol.view:
https://jsfiddle.net/owwc5bo8/12/
var extent = ol.proj.get('EPSG:3857').getExtent();
var tileSizePixels = 384;
var tileSizeMtrs = ol.extent.getWidth(extent) / 384;
var resolutions = [];
for (var i = -1; i <= 20; i++) {
resolutions[i] = tileSizeMtrs / (Math.pow(2, i));
}
var tileGrid = new ol.tilegrid.TileGrid({
extent: extent,
resolutions: resolutions,
tileSize: [384, 384]
});
var center = ol.proj.fromLonLat(
ol.proj.toLonLat([2, 49], 'EPSG:4326'),
'EPSG:3857');
var zoom = 5;
var view1 = new ol.View({
center: center,
zoom: zoom,
maxResolution: 40075016.68557849 / 384
});

How can I change ol.source.OSM tile url based on level (z value)

We supply tiles for levels 0-9. So when the user goes to a zoom level 10 or higher I want the URL to change back to the default values of Open Street Map.
I've tried this and it almost works. When level 10 or higher is selected I change the URL using the ol.source.OSM.setURLs() function. But in some cases - not all - the image is still set to our local URL. I'm assuming this is some kind of caching issue but not sure.
$scope.tilesource = new ol.source.OSM({
url : '/'+$scope.tileRoot+'/tiles/{z}/{x}/{y}.png',
wrapX : false
});
var raster = new ol.layer.Tile({
source : $scope.tilesource
});
$scope.tilesource.on('tileloadstart', function(arg) {
//console.log(arg.tile.src_);
if ($scope.tileLevelsSupported.search(arg.tile.tileCoord[0]) == -1) {
$scope.tilesource.setUrls(["https://a.tile.openstreetmap.org/{z}/{x}/{y}.png", "https://b.tile.openstreetmap.org/{z}/{x}/{y}.png", "https://c.tile.openstreetmap.org/{z}/{x}/{y}.png"]);
} else {
$scope.tilesource.setUrl('/'+$scope.tileRoot+'/tiles/{z}/{x}/{y}.png');
}
});
I've tried several methods on OSM and Tile but have had no luck. On those instances when the Tile URL is wrong I get the File Not Found 404 error (expected), but then it corrects itself and the tile gets loaded.
Thanks in advance.
Instead of changing the URL, you could use two different layers with the minResolution and maxResolution options:
var raster = new ol.layer.Tile({
source : $scope.tilesource,
minResolution: 200,
maxResolution: 10000000
});
var osmLayer = new ol.layer.Tile({
source: new ol.source.OSM(),
minResolution: 0,
maxResolution: 200
});
When you zoom in from level 9 to 10, the raster layer will become invisible and the osm layer will appear.

OpenLayers 3 show/hide layers

I am creating an application which utilises a map created and managed by the OpenLayers 3 library. I want to be able to switch which layer is visible using the zoom level (i.e. zoom out to get an overview of countries, zoom in to get an overview of cities). There are three categories of layers (3 different zoom levels), and within each category there are 3 colours which the pins I am using could be (which are all separate layers as well) so in total there are 9 layers.
What I want is to develop the ability to filter which layers are displayed, which means showing/hiding the existing layers depending on which zoom level we are at.
There is some code to demonstrate how the map is generated and how one type of layer is generated but if there is more detail required please let me know. I don't believe there will be an issue with this, however.
function setUpMap(vectorLayers, $scope){
var view = new ol.View({
center: ol.proj.fromLonLat([2.808981, 46.609599]),
zoom: 4
});
map = new ol.Map({
target: 'map',
layers: vectorLayers,
overlays: [overlay],
view: view
});
view.on("change:resolution", function(e){
var oldValue = e.oldValue;
var newValue = e.target.get(e.key);
if (newValue > 35000){
if (oldValue < 35000)
//This is where I will show group 1
} else if (newValue > 10000){
if (oldValue < 10000 || oldValue > 35000)
//This is where I will show group 2
} else {
if (oldValue > 10000)
//This is where I will show group 3
}
});
addClickEventsToMapItems($scope);
}
I tried something like this and got no success:
function showLayer(whichLayer){
vectorLayers[1].setVisibility(false);
vectorLayers[2].setVisibility(false);
vectorLayers[3].setVisibility(false);
vectorLayers[whichLayer].setVisibility(true);
}
I am open to suggestions. Please let me know! :)
You can listen to the resolution:change event on your ol.Map instance:
map.getView().on('change:resolution', function (e) {
if (map.getView().getZoom() > 0) {
vector.setVisible(true);
}
if (map.getView().getZoom() > 1) {
vector.setVisible(false);
}
});
Example on Plunker: http://plnkr.co/edit/szSCMh6raZfHi9s6vzQX?p=preview
It's also worth to take a look at the minResolution and maxResolution options of ol.layer which can switch automaticly for you. But it works by using the view's resolution, not the zoomfactor:
http://openlayers.org/en/v3.2.1/examples/min-max-resolution.html
why dont you use minResolution/maxResolution parameters during vector layer initialasation, check the api here
If you dont know the resolution but you know only the scale, use the following function to get the resolution out of the scale
function getResolutionFromScale(scale){
var dpi = 25.4 / 0.28;
var units = map.getView().getProjection().getUnits();
var mpu = ol.proj.METERS_PER_UNIT[units];
var res = scale / (mpu * 39.37 * dpi);
return res;
}

Openlayers 3: Why forEachFeatureAtPixel only return one feature

I create a text box where I can put the coordinates X,Y then I move the pin to the location and find which roads are there.
The pin move, but forEachFeatureAtPixel only return one feature instead of four. Also the feature I got doesn't have NOMBRE property
I'm using Openlayer 3 and my WMS server map.
As you can see in MapInfo (picture right side) I got all 4 features. Horizontal 2x "Calle 6" and Vertical 2x "Calle 1 Norte", and have NOMBRE property.
I'm using the same x, y from mapinfo where all 4 feature instersect.
new info: My layer is ol.layer.Tile instead of ol.layer.Vector, maybe that is the problem (checking)
wmsLyr09 = new ol.layer.Tile({
source: wmsSource
});
// move the pin to new position
geometry.translate(deltaX, deltaY);
var coordinate = geometry.getCoordinates();
var pixel = map.getPixelFromCoordinate(coordinate);
console.log('pixel: ' + pixel);
var allFeaturesAtPixel = [];
map.forEachFeatureAtPixel(pixel, function (feature) {
allFeaturesAtPixel.push(feature);
});
// feature[len]:1
console.log("feature[len]:" + allFeaturesAtPixel.length);
//feature[Name]: undefined
feature= allFeaturesAtPixel[0];
console.log("feature[Name]: " + feature.get('NOMBRE'));
In your map you probably have two layers, the WMS tile layer and the vector layer for the pin. So, OpenLayers doesn't know about the road features. If you are calling forEachFeatureAtPixel you are getting the only feature OpenLayers knows about: the pin.
What you want to do is make a WFS GetFeature request to get the features at the current pin position. Take a look at this example: http://openlayers.org/en/master/examples/getfeatureinfo-tile.html

Resources