geojs 1.6.6 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geojs",
3
- "version": "1.6.6",
3
+ "version": "1.7.0",
4
4
  "description": "JavaScript Geo Visualization and Analysis Library",
5
5
  "homepage": "https://github.com/OpenGeoscience/geojs",
6
6
  "license": "Apache-2.0",
@@ -4,6 +4,25 @@ var pixelmapFeature = require('../pixelmapFeature');
4
4
  var geo_event = require('../event');
5
5
  var util = require('../util');
6
6
 
7
+ /**
8
+ * Pixelmap feature information record.
9
+ *
10
+ * @typedef {object} geo.pixelmapFeature.info
11
+ * @property {number} width The width of the source image.
12
+ * @property {number} height The width of the source image.
13
+ * @property {CanvasRenderingContext2D} context The HTMLCanvasElement context
14
+ * used for handling the pixelmap.
15
+ * @property {ImageData} imageData The context's image data.
16
+ * @property {number[]} indices An array, one per pixel, of the index value in
17
+ * the image. This decodes the pixel value to the corresponding integer.
18
+ * @property number} area The number of pixels in the image. This is
19
+ * `width * height`.
20
+ * @property {object[]} mappedColors This has one entry for each distinct index
21
+ * value. Each entry has `first` and `last` with the first and last pixel
22
+ * locations where that index occurs. Note that last is the inclusive value
23
+ * of the location (so its maximum possible value is `size - 1`).
24
+ */
25
+
7
26
  /**
8
27
  * Create a new instance of class pixelmapFeature.
9
28
  *
@@ -116,11 +135,13 @@ var canvas_pixelmapFeature = function (arg) {
116
135
  updateFirst, updateLast = -1, update, prepared;
117
136
 
118
137
  if (!m_this.m_info) {
138
+ m_this.indexModified(undefined, 'clear');
119
139
  if (!m_this._preparePixelmap()) {
120
140
  return;
121
141
  }
122
142
  prepared = true;
123
143
  }
144
+ m_this.indexModified(undefined, 'clear');
124
145
  mappedColors = m_this.m_info.mappedColors;
125
146
  updateFirst = m_this.m_info.area;
126
147
  for (idx in mappedColors) {
package/src/index.js CHANGED
@@ -77,6 +77,7 @@ module.exports = $.extend({
77
77
  polygonFeature: require('./polygonFeature'),
78
78
  quadFeature: require('./quadFeature'),
79
79
  pixelmapFeature: require('./pixelmapFeature'),
80
+ pixelmapLayer: require('./pixelmapLayer'),
80
81
  renderer: require('./renderer'),
81
82
  sceneObject: require('./sceneObject'),
82
83
  textFeature: require('./textFeature'),
@@ -30,25 +30,6 @@ var util = require('./util');
30
30
  * transformations for those two triangles.
31
31
  */
32
32
 
33
- /**
34
- * Pixelmap feature information record.
35
- *
36
- * @typedef {object} geo.pixelmapFeature.info
37
- * @property {number} width The width of the source image.
38
- * @property {number} height The width of the source image.
39
- * @property {context} context The HTMLCanvasElement context used for handling
40
- * the pixelmap.
41
- * @property {ImageData} imageData The context's image data.
42
- * @property {number[]} indices An array, one per pixel, of the index value in
43
- * the image. This decodes the pixel value to the corresponding integer.
44
- * @property number} area The number of pixels in the image. This is
45
- * `width * height`.
46
- * @property {object[]} mappedColors This has one entry for each distinct index
47
- * value. Each entry has `first` and `last` with the first and last pixel
48
- * locations where that index occurs. Note that last is the inclusive value
49
- * of the location (so its maximum possible value is `size - 1`).
50
- */
51
-
52
33
  /**
53
34
  * Create a new instance of class pixelmapFeature
54
35
  *
@@ -72,6 +53,7 @@ var pixelmapFeature = function (arg) {
72
53
  */
73
54
  var m_this = this,
74
55
  s_update = this._update,
56
+ m_modifiedIndexRange,
75
57
  s_init = this._init;
76
58
 
77
59
  this.featureType = 'pixelmap';
@@ -134,6 +116,42 @@ var pixelmapFeature = function (arg) {
134
116
  return m_this;
135
117
  };
136
118
 
119
+ /**
120
+ * Mark that an index's data value (and hence its color) has changed without
121
+ * marking all of the data array as changed. If this function is called
122
+ * without any parameters, it clears the tracked changes.
123
+ *
124
+ * @param {number} [idx] The lowest data index that has changed. If
125
+ * `undefined`, return the current tracked changed range.
126
+ * @param {number|'clear'} [idx2] If an index was specified in `idx` and
127
+ * this is specified, the highest index (inclusive) that has changed. If
128
+ * returning the tracked changed range and this is `clear`, clear the
129
+ * tracked range.
130
+ * @returns {this|number[]} When returning a range, this is the lowest and
131
+ * highest index values that have changed (inclusive), so their range is
132
+ * `[0, data.length)`.
133
+ */
134
+ this.indexModified = function (idx, idx2) {
135
+ if (idx === undefined) {
136
+ const range = m_modifiedIndexRange;
137
+ if (idx2 === 'clear') {
138
+ m_modifiedIndexRange = undefined;
139
+ }
140
+ return range;
141
+ }
142
+ m_this.modified();
143
+ if (m_modifiedIndexRange === undefined) {
144
+ m_modifiedIndexRange = [idx, idx];
145
+ }
146
+ if (idx < m_modifiedIndexRange[0]) {
147
+ m_modifiedIndexRange[0] = idx;
148
+ }
149
+ if ((idx2 || idx) > m_modifiedIndexRange[1]) {
150
+ m_modifiedIndexRange[1] = (idx2 || idx);
151
+ }
152
+ return m_this;
153
+ };
154
+
137
155
  /**
138
156
  * Update.
139
157
  *
@@ -192,7 +210,6 @@ var pixelmapFeature = function (arg) {
192
210
  * drawing a quad, which can trigger a full layer update, which in turn
193
211
  * checks if this feature is built. Setting the build time avoids calling
194
212
  * this a second time. */
195
- m_this.buildTime().modified();
196
213
  if (!m_this.m_srcImage) {
197
214
  var src = m_this.style.get('url')();
198
215
  if (util.isReadyImage(src)) {
@@ -243,6 +260,7 @@ var pixelmapFeature = function (arg) {
243
260
  } else if (m_this.m_info) {
244
261
  m_this._computePixelmap();
245
262
  }
263
+ m_this.buildTime().modified();
246
264
  return m_this;
247
265
  };
248
266
 
@@ -282,6 +300,10 @@ var pixelmapFeature = function (arg) {
282
300
  }
283
301
  m_this.style(style);
284
302
  m_this.dataTime().modified();
303
+ if (arg.quadFeature) {
304
+ m_this.m_srcImage = true;
305
+ m_this._computePixelmap();
306
+ }
285
307
 
286
308
  return m_this;
287
309
  };
@@ -307,7 +329,9 @@ pixelmapFeature.create = function (layer, spec) {
307
329
 
308
330
  pixelmapFeature.capabilities = {
309
331
  /* core feature name -- support in any manner */
310
- feature: 'pixelmap'
332
+ feature: 'pixelmap',
333
+ /* support for image-based lookup */
334
+ lookup: 'pixelmap.lookup'
311
335
  };
312
336
 
313
337
  inherit(pixelmapFeature, feature);
@@ -0,0 +1,145 @@
1
+ var $ = require('jquery');
2
+ var inherit = require('./inherit');
3
+ var tileLayer = require('./tileLayer');
4
+ var registry = require('./registry');
5
+ var quadFeature = require('./quadFeature');
6
+ var pixelmapFeature = require('./pixelmapFeature');
7
+
8
+ /**
9
+ * Object specification for a pixelmap layer.
10
+ *
11
+ * @typedef {geo.tileLayer.spec} geo.pixelmapLayer.spec
12
+ * @extends {geo.tileLayer.spec}
13
+ * @property {geo.geoColor|function} [color] The color that should be used
14
+ * for each data element. Data elements correspond to the indices in the
15
+ * pixel map. If an index is larger than the number of data elements, it will
16
+ * be transparent. If there is more data than there are indices, it is
17
+ * ignored.
18
+ * @property {object} [style] An optional style object that could contain
19
+ * `color` or other style values.
20
+ * @property {array} [data] A new data array.
21
+ * @property {string} [crossDomain='anonymous'] Image CORS attribute. This is
22
+ * used for the `crossorigin` property when loading images.
23
+ */
24
+
25
+ /**
26
+ * Create a new instance of pixelmapLayer. This is a {@link geo.tileLayer} with
27
+ * an OSM url and attribution defaults and with the tiles centered on the
28
+ * origin.
29
+ *
30
+ * @class
31
+ * @alias geo.pixelmapLayer
32
+ * @extends geo.tileLayer
33
+ *
34
+ * @param {geo.pixelmapLayer.spec} [arg] Specification for the layer.
35
+ */
36
+ var pixelmapLayer = function (arg) {
37
+
38
+ var imageTile = require('./imageTile');
39
+
40
+ if (!(this instanceof pixelmapLayer)) {
41
+ return new pixelmapLayer(arg);
42
+ }
43
+ arg = arg || {};
44
+ arg = $.extend(
45
+ true,
46
+ {},
47
+ this.constructor.defaults,
48
+ arg);
49
+ tileLayer.call(this, arg);
50
+
51
+ var s_init = this._init,
52
+ m_pixelmapFeature,
53
+ m_this = this;
54
+
55
+ /**
56
+ * Returns an instantiated imageTile object with the given indices. This
57
+ * method always returns a new tile object. Use `_getTileCached` to use
58
+ * the caching layer.
59
+ *
60
+ * @param {object} index The tile index.
61
+ * @param {number} index.x
62
+ * @param {number} index.y
63
+ * @param {number} index.level
64
+ * @param {object} source The tile index used for constructing the url.
65
+ * @param {number} source.x
66
+ * @param {number} source.y
67
+ * @param {number} source.level
68
+ * @returns {geo.tile}
69
+ */
70
+ this._getTile = function (index, source) {
71
+ var urlParams = source || index;
72
+ return imageTile({
73
+ index: index,
74
+ size: {x: m_this._options.tileWidth, y: m_this._options.tileHeight},
75
+ queue: m_this._queue,
76
+ overlap: m_this._options.tileOverlap,
77
+ scale: m_this._options.tileScale,
78
+ url: m_this._options.url.call(
79
+ m_this, urlParams.x, urlParams.y, urlParams.level || 0,
80
+ m_this._options.subdomains),
81
+ crossDomain: m_this._options.crossDomain
82
+ });
83
+ };
84
+
85
+ /**
86
+ * Initialize.
87
+ *
88
+ * @returns {this} The current layer.
89
+ */
90
+ this._init = function () {
91
+ // Call super class init
92
+ s_init.apply(m_this, arguments);
93
+
94
+ const pixelmapArgs = {quadFeature: m_this.features()[0]};
95
+ if (arg.style) {
96
+ pixelmapArgs.style = arg.style;
97
+ }
98
+ if (arg.color) {
99
+ pixelmapArgs.color = arg.color;
100
+ }
101
+ m_pixelmapFeature = m_this.createFeature('pixelmap', pixelmapArgs);
102
+ if (arg.data) {
103
+ m_pixelmapFeature.data(arg.data);
104
+ }
105
+ m_this.style = m_pixelmapFeature.style;
106
+ m_this.data = m_pixelmapFeature.data;
107
+ m_this.indexModified = m_pixelmapFeature.indexModified;
108
+ const s_dataTimeModified = m_this.dataTime().modified;
109
+ m_this.dataTime().modified = () => {
110
+ m_pixelmapFeature.dataTime().modified();
111
+ return s_dataTimeModified();
112
+ };
113
+ ['modified', 'geoOn', 'geoOff', 'geoOnce'].forEach((funcName) => {
114
+ const superFunc = m_this[funcName];
115
+ m_this[funcName] = function () {
116
+ m_pixelmapFeature[funcName].apply(this, arguments);
117
+ return superFunc.apply(this, arguments);
118
+ };
119
+ });
120
+ return m_this;
121
+ };
122
+
123
+ return m_this;
124
+ };
125
+
126
+ /**
127
+ * This object contains the default options used to initialize the
128
+ * pixelmapLayer.
129
+ */
130
+ pixelmapLayer.defaults = $.extend({}, tileLayer.defaults, {
131
+ features: [quadFeature.capabilities.image, pixelmapFeature.capabilities.lookup],
132
+ tileOffset : function (level) {
133
+ var s = Math.pow(2, level - 1) * 256;
134
+ return {x: s, y: s};
135
+ },
136
+ url: ''
137
+ });
138
+
139
+ inherit(pixelmapLayer, tileLayer);
140
+ /* By default, ask to support image quads. If the user needs full
141
+ * reprojection, they will need to require the
142
+ * quadFeature.capabilities.imageFull feature */
143
+ registry.registerLayer('pixelmap', pixelmapLayer, [quadFeature.capabilities.image]);
144
+
145
+ module.exports = pixelmapLayer;
package/src/tileLayer.js CHANGED
@@ -676,7 +676,7 @@ var tileLayer = function (arg) {
676
676
  this._getTiles = function (maxLevel, bounds, sorted, onlyIfChanged) {
677
677
  var i, j, tiles = [], index, nTilesLevel,
678
678
  start, end, indexRange, source, center, changed = false, old, level,
679
- minLevel = (m_this._options.keepLower ? m_this._options.minLevel : maxLevel);
679
+ minLevel = (m_this._options.keepLower ? m_this._options.minLevel : Math.max(maxLevel, m_this._options.minLevel));
680
680
  if (maxLevel < minLevel) {
681
681
  maxLevel = minLevel;
682
682
  }
@@ -1532,7 +1532,8 @@ var tileLayer = function (arg) {
1532
1532
  * finished, purge all but the current layer. This is important for
1533
1533
  * semi-transparent layers. */
1534
1534
  if ((doneLoading || m_this._isCovered(tile)) &&
1535
- zoom !== tile.index.level) {
1535
+ zoom !== tile.index.level &&
1536
+ (zoom >= m_this._options.minLevel || tile.index.level !== m_this._options.minLevel)) {
1536
1537
  return true;
1537
1538
  }
1538
1539
  }
@@ -28,6 +28,7 @@ var webgl_pixelmapFeature = function (arg) {
28
28
  const fragmentShader = require('./pixelmapFeature.frag');
29
29
 
30
30
  var m_quadFeature,
31
+ m_quadFeatureInit,
31
32
  s_exit = this._exit,
32
33
  m_lookupTable,
33
34
  m_this = this;
@@ -46,25 +47,28 @@ var webgl_pixelmapFeature = function (arg) {
46
47
  this.pointSearch = function (geo, gcs) {
47
48
  if (m_quadFeature && m_this.m_info) {
48
49
  let result = m_quadFeature.pointSearch(geo, gcs);
49
- if (result.index.length === 1 &&
50
- result.extra && result.extra[result.index[0]].basis &&
51
- result.extra[result.index[0]]._quad &&
52
- result.extra[result.index[0]]._quad.image) {
53
- const img = result.extra[result.index[0]]._quad.image;
54
- const basis = result.extra[result.index[0]].basis;
55
- const x = Math.floor(basis.x * img.width);
56
- const y = Math.floor(basis.y * img.height);
57
- const canvas = document.createElement('canvas');
58
- canvas.width = canvas.height = 1;
59
- const context = canvas.getContext('2d');
60
- context.drawImage(img, x, y, 1, 1, 0, 0, 1, 1);
61
- const pixel = context.getImageData(0, 0, 1, 1).data;
62
- const idx = pixel[0] + pixel[1] * 256 + pixel[2] * 256 * 256;
63
- result = {
64
- index: [idx],
65
- found: [m_this.data()[idx]]
66
- };
67
- return result;
50
+ // use the last index by preference, since for tile layers, this is the
51
+ // topmosttile
52
+ let idxIdx = result.index.length - 1;
53
+ for (; idxIdx >= 0; idxIdx -= 1) {
54
+ if (result.extra[result.index[idxIdx]]._quad &&
55
+ result.extra[result.index[idxIdx]]._quad.image) {
56
+ const img = result.extra[result.index[idxIdx]]._quad.image;
57
+ const basis = result.extra[result.index[idxIdx]].basis;
58
+ const x = Math.floor(basis.x * img.width);
59
+ const y = Math.floor(basis.y * img.height);
60
+ const canvas = document.createElement('canvas');
61
+ canvas.width = canvas.height = 1;
62
+ const context = canvas.getContext('2d');
63
+ context.drawImage(img, x, y, 1, 1, 0, 0, 1, 1);
64
+ const pixel = context.getImageData(0, 0, 1, 1).data;
65
+ const idx = pixel[0] + pixel[1] * 256 + pixel[2] * 256 * 256;
66
+ result = {
67
+ index: [idx],
68
+ found: [m_this.data()[idx]]
69
+ };
70
+ return result;
71
+ }
68
72
  }
69
73
  }
70
74
  return {index: [], found: []};
@@ -78,34 +82,49 @@ var webgl_pixelmapFeature = function (arg) {
78
82
  var data = m_this.data() || [],
79
83
  colorFunc = m_this.style.get('color');
80
84
 
85
+ let indexRange = m_this.indexModified(undefined, 'clear');
86
+ let fullUpdate = m_this.dataTime().timestamp() >= m_this.buildTime().timestamp() || indexRange === undefined;
81
87
  if (!m_lookupTable) {
82
88
  m_lookupTable = lookupTable2D();
83
89
  m_lookupTable.setTextureUnit(1);
90
+ fullUpdate = true;
84
91
  }
85
92
  let clrLen = Math.max(1, data.length);
86
93
  const maxWidth = m_lookupTable.maxWidth();
87
94
  if (clrLen > maxWidth && clrLen % maxWidth) {
88
95
  clrLen += maxWidth - (clrLen % maxWidth);
89
96
  }
90
- const colors = new Uint8Array(clrLen * 4);
91
- data.forEach((d, i) => {
97
+
98
+ let colors;
99
+ if (!fullUpdate) {
100
+ colors = m_lookupTable.colorTable();
101
+ fullUpdate = colors.length !== clrLen * 4;
102
+ indexRange[0] = Math.max(0, indexRange[0]);
103
+ indexRange[1] = Math.min(data.length, indexRange[1] + 1);
104
+ }
105
+ if (fullUpdate) {
106
+ colors = new Uint8Array(clrLen * 4);
107
+ indexRange = [0, data.length];
108
+ }
109
+ for (let i = indexRange[0]; i < indexRange[1]; i += 1) {
110
+ const d = data[i];
92
111
  const color = util.convertColor(colorFunc.call(m_this, d, i));
93
112
  colors[i * 4] = color.r * 255;
94
113
  colors[i * 4 + 1] = color.g * 255;
95
114
  colors[i * 4 + 2] = color.b * 255;
96
115
  colors[i * 4 + 3] = color.a === undefined ? 255 : (color.a * 255);
97
- });
116
+ }
98
117
  m_this.m_info = {colors: colors};
99
118
  // check if colors haven't changed
100
119
  var oldcolors = m_lookupTable.colorTable();
101
120
  if (oldcolors && oldcolors.length === colors.length) {
102
- let idx = 0;
103
- for (; idx < colors.length; idx += 1) {
121
+ let idx = indexRange[0] * 4;
122
+ for (; idx < indexRange[1] * 4; idx += 1) {
104
123
  if (colors[idx] !== oldcolors[idx]) {
105
124
  break;
106
125
  }
107
126
  }
108
- if (idx === colors.length) {
127
+ if (idx === indexRange[1] * 4) {
109
128
  return;
110
129
  }
111
130
  }
@@ -117,6 +136,9 @@ var webgl_pixelmapFeature = function (arg) {
117
136
  gcs: m_this.gcs(),
118
137
  visible: m_this.visible(undefined, true)
119
138
  });
139
+ m_quadFeatureInit = false;
140
+ }
141
+ if (!m_quadFeatureInit) {
120
142
  m_this.dependentFeatures([m_quadFeature]);
121
143
  m_quadFeature.setShader('image_fragment', fragmentShader);
122
144
  m_quadFeature._hookBuild = (prog) => {
@@ -138,11 +160,14 @@ var webgl_pixelmapFeature = function (arg) {
138
160
  });
139
161
  m_lookupTable.bind(renderState, quads);
140
162
  };
141
- m_quadFeature.style({
142
- image: m_this.m_srcImage,
143
- position: m_this.style.get('position')})
144
- .data([{}])
145
- .draw();
163
+ if (m_quadFeatureInit === false) {
164
+ m_quadFeature.style({
165
+ image: m_this.m_srcImage,
166
+ position: m_this.style.get('position')})
167
+ .data([{}])
168
+ .draw();
169
+ }
170
+ m_quadFeatureInit = true;
146
171
  }
147
172
  };
148
173
 
@@ -161,6 +186,9 @@ var webgl_pixelmapFeature = function (arg) {
161
186
  return m_this;
162
187
  };
163
188
 
189
+ if (arg.quadFeature) {
190
+ m_quadFeature = arg.quadFeature;
191
+ }
164
192
  this._init(arg);
165
193
  return this;
166
194
  };
@@ -168,5 +196,8 @@ var webgl_pixelmapFeature = function (arg) {
168
196
  inherit(webgl_pixelmapFeature, pixelmapFeature);
169
197
 
170
198
  // Now register it
171
- registerFeature('webgl', 'pixelmap', webgl_pixelmapFeature);
199
+ var capabilities = {};
200
+ capabilities[pixelmapFeature.capabilities.lookup] = true;
201
+
202
+ registerFeature('webgl', 'pixelmap', webgl_pixelmapFeature, capabilities);
172
203
  module.exports = webgl_pixelmapFeature;
@@ -371,7 +371,9 @@ var webgl_quadFeature = function (arg) {
371
371
  return;
372
372
  }
373
373
  quad.texture.bind(renderState);
374
- if (context.getError() === context.OUT_OF_MEMORY) {
374
+ // only check if the context is out of memory when using modestly large
375
+ // textures. The check is slow.
376
+ if ((quad.image.width > 4096 || quad.image.height > 4096 || quad.image.width * quad.image.height > 4194304) && context.getError() === context.OUT_OF_MEMORY) {
375
377
  console.log('Insufficient GPU memory for texture');
376
378
  }
377
379
  if (quad.opacity !== opacity) {