stanleycas
stanleycas

Reputation: 21

Why does pinch to zoom not work in custom crs in leaflet?

I've setup a leaflet map with a custom CRS (EPSG:32633). The map generally works well, except that it freezes whenever I try to zoom the map using pinch to zoom on mobile devices.

The error occurring under pinch to zoom is "Uncaught Error: Invalid LatLng object: (NaN, NaN)".

Here's a jsfiddle that can be used to reproduce the problem:

http://jsfiddle.net/thL5bbnv/2/

Here's the code (which is also used in the jsfiddle):

// Defining  the projection system
L.Projection.UTM33 = {
  ZONE: 33,
  R_MINOR: 6356752.3142,
  R_MAJOR: 6378137,
  DEG_TO_RAD: Math.PI / 180,
  RAD_TO_DEG: 180 / Math.PI,

  bounds: L.bounds([-2500000, 3500000], [3045984, 9045984]),

  project: function(latlng) {
    var latRad = latlng.lat * this.DEG_TO_RAD,
      longRad = latlng.lng * this.DEG_TO_RAD,
      longOriginRad = (-183 + (6 * this.ZONE)) * this.DEG_TO_RAD,
      eccs = 1 - ((this.R_MINOR / this.R_MAJOR) * (this.R_MINOR / this.R_MAJOR)),
      k0 = 0.9996,
      eccps = eccs / (1 - eccs),
      n = this.R_MAJOR / Math.sqrt(1 - eccs * Math.sin(latRad) * Math.sin(latRad)),
      t = Math.tan(latRad) * Math.tan(latRad),
      c = eccps * Math.cos(latRad) * Math.cos(latRad),
      a = Math.cos(latRad) * (longRad - longOriginRad),
      m = this.R_MAJOR * ((1 - eccs / 4 - 3 * eccs * eccs / 64 - 5 * eccs * eccs * eccs / 256) * latRad - (3 * eccs / 8 + 3 * eccs * eccs / 32 + 45 * eccs * eccs * eccs / 1024) * Math.sin(2 * latRad) + (15 * eccs * eccs / 256 + 45 * eccs * eccs * eccs / 1024) * Math.sin(4 * latRad) - (35 * eccs * eccs * eccs / 3072) * Math.sin(6 * latRad)),
      x = k0 * n * (a + (1 - t + c) * a * a * a / 6 + (5 - 18 * t + t * t + 72 * c - 58 * eccps) * a * a * a * a * a / 120) + 500000.0,
      y = k0 * (m + n * Math.tan(latRad) * (a * a / 2 + (5 - t + 9 * c + 4 * c * c) * a * a * a * a / 24.0 + (61.0 - 58 * t + t * t + 600.0 * c - 330.0 * eccps) * a * a * a * a * a * a / 720));

    return new L.Point(x, y);
  },

  unproject: function(point) {
    var eccs = 1 - ((this.R_MINOR / this.R_MAJOR) * (this.R_MINOR / this.R_MAJOR)),
      e1 = (1 - Math.sqrt(1 - eccs)) / (1 + Math.sqrt(1 - eccs)),
      k0 = 0.9996,
      x = point.x - 500000,
      y = point.y,
      longOrigin = (this.ZONE - 1) * 6 - 180 + 3,
      eccps = (eccs) / (1 - eccs),
      m = y / k0,
      mu = m / (this.R_MAJOR * (1 - eccs / 4 - 3 * eccs * eccs / 64 - 5 * eccs * eccs * eccs / 256)),
      phi1Rad = (mu + (3 * e1 / 2 - 27 * e1 * e1 * e1 / 32) * Math.sin(2 * mu) + (21 * e1 * e1 / 16 - 55 * e1 * e1 * e1 * e1 / 32) * Math.sin(4 * mu) + (151 * e1 * e1 * e1 / 96) * Math.sin(6 * mu)),
      n1 = this.R_MAJOR / Math.sqrt(1 - eccs * Math.sin(phi1Rad) * Math.sin(phi1Rad)),
      t1 = Math.tan(phi1Rad) * Math.tan(phi1Rad),
      c1 = eccps * Math.cos(phi1Rad) * Math.cos(phi1Rad),
      r1 = this.R_MAJOR * (1 - eccs) / Math.pow(1 - eccs * Math.sin(phi1Rad) * Math.sin(phi1Rad), 1.5),
      d = x / (n1 * k0),
      lng = ((longOrigin * this.DEG_TO_RAD + ((d - (1 + 2 * t1 + c1) * d * d * d / 6 + (5 - 2 * c1 + 28 * t1 - 3 * c1 * c1 + 8 * eccps + 24 * t1 * t1) * d * d * d * d * d / 120) / Math.cos(phi1Rad))) * this.RAD_TO_DEG),
      lat = ((phi1Rad - (n1 * Math.tan(phi1Rad) / r1) * (d * d / 2 - (5 + 3 * t1 + 10 * c1 - 4 * c1 * c1 - 9 * eccps) * d * d * d * d / 24 + (61 + 90 * t1 + 298 * c1 + 45 * t1 * t1 - 252 * eccps - 3 * c1 * c1) * d * d * d * d * d * d / 720)) * this.RAD_TO_DEG);

    return new L.LatLng(lat, lng);
  }
};

// Defining the utm crs
L.CRS.EPSG32633 = L.extend({}, L.CRS.Earth, {
  code: "EPSG:32633",
  projection: L.Projection.UTM33,
  transformation: new L.Transformation(1, 2500000, -1, 9045984),
  scale: function(zoom) {
    return 1 / (21664 / Math.pow(2, zoom));
  }
});

// Creating map with the utm crs
var map = new L.map('map', {
  crs: L.CRS.EPSG32633,
  minZoom: 0,
  maxZoom: 15,
  zoomControl: true
}).on("load", function(e) {
    // Adding utm basemap
var layer = new L.TileLayer.WMS('https://opencache.statkart.no/gatekeeper/gk/gk.open', {
  layers: "topo2",
  format: "image/png",
  transparent: false,
  attribution: "© Kartverket"
});

layer.addTo(e.target);

}).setView([65.276178, 16.683775], 3);

UPDATE: Here's a stack trace for the error I'm getting:

Uncaught Error: Invalid LatLng object: (NaN, NaN)
    at o.LatLng (https://unpkg.com/[email protected]/dist/leaflet.js:5:14154)
    at Object.unproject (http://fiddle.jshell.net/thL5bbnv/2/show/:105:12)
    at Object.pointToLatLng (https://unpkg.com/[email protected]/dist/leaflet.js:5:18431)
    at e.unproject (https://unpkg.com/[email protected]/dist/leaflet.js:5:30126)
    at e.layerPointToLatLng (https://unpkg.com/[email protected]/dist/leaflet.js:5:30242)
    at e._fireDOMEvent (https://unpkg.com/[email protected]/dist/leaflet.js:6:3634)
    at e._handleDOMEvent (https://unpkg.com/[email protected]/dist/leaflet.js:6:3071)
    at HTMLDivElement.h (https://unpkg.com/[email protected]/dist/leaflet.js:6:11270)
    at e._simulateEvent (https://unpkg.com/[email protected]/dist/leaflet.js:8:31479)
    at e._onMove (https://unpkg.com/[email protected]/dist/leaflet.js:8:31242)o.LatLng @ leaflet.js:5unproject @ (index):105pointToLatLng @ leaflet.js:5unproject @ leaflet.js:5layerPointToLatLng @ leaflet.js:5_fireDOMEvent @ leaflet.js:6_handleDOMEvent @ leaflet.js:6h @ leaflet.js:6_simulateEvent @ leaflet.js:8_onMove @ leaflet.js:8h @ leaflet.js:6
leaflet.js:5 Uncaught Error: Invalid LatLng object: (NaN, NaN)(…)o.LatLng @ leaflet.js:5unproject @ (index):105pointToLatLng @ leaflet.js:5unproject @ leaflet.js:5layerPointToLatLng @ leaflet.js:5_fireDOMEvent @ leaflet.js:6_handleDOMEvent @ leaflet.js:6h @ leaflet.js:6_simulateEvent @ leaflet.js:8_onMove @ leaflet.js:8h @ leaflet.js:6

I'm trying to understand why pinch to zoom is failing, and what I can do to fix the problem. Does anyone know why pinch to zoom would result in a Invalid LatLng object: (NaN, NaN) when a custom projection is used?

Upvotes: 2

Views: 497

Answers (1)

IvanSanchez
IvanSanchez

Reputation: 19089

OK, so I have traced the problem down to a call to map.getScaleZoom():

debug screenshot

That function depends directly on the scale method of the map's CRS, which you have defined as

scale: function(zoom) {
   return 1 / (21664 / Math.pow(2, zoom));
}

Now, the scale method of the CRS must implement the inverse of the zoom method of the CRS. It says so in the Leaflet API documentation for L.CRS.

You're not defining a zoom method in L.CRS.EPSG32633, but...

L.CRS.EPSG32633 = L.extend({}, L.CRS.Earth, {

...you're using L.CRS.Earth.zoom() instead. So if scale(zoom(x)) is not 1 for any value of x, things will fail. First, a zoom level jumps from 1.01 to -19.4. Then, a LatLng gets a value of 1e+233. Then your projection code takes that and starts spewing out Infinity and NaN, and everything ends up in flames.

Fix the scale and zoom of methods of L.CRS.EPSG32633 so they are idempotent.


Regardless of the bug, I encourage you to use Proj4Leaflet instead of implementing projection code yourself. It's tested, it's maintained, and it supports all known EPSG projections and CRSs.

Upvotes: 2

Related Questions