geojs 1.9.4 → 1.10.2

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.9.4",
3
+ "version": "1.10.2",
4
4
  "description": "JavaScript Geo Visualization and Analysis Library",
5
5
  "homepage": "https://github.com/OpenGeoscience/geojs",
6
6
  "license": "Apache-2.0",
@@ -27,6 +27,7 @@
27
27
  },
28
28
  "optionalDependencies": {
29
29
  "d3": "^3.5.17",
30
+ "glslang-validator-prebuilt-predownloaded": "^0.0.2",
30
31
  "hammerjs": "^2.0.8",
31
32
  "vtk.js": "*"
32
33
  },
@@ -53,7 +54,6 @@
53
54
  "express": "^4.17.2",
54
55
  "fs-extra": "^10.0.0",
55
56
  "glob": "^8.0.1",
56
- "glslang-validator-prebuilt": "^0.0.7",
57
57
  "imports-loader": "^4.0.0",
58
58
  "jaguarjs-jsdoc": "^1.0.2",
59
59
  "jasmine-core": "^4.0.0",
@@ -135,7 +135,7 @@
135
135
  "build-website-tutorials": "node tutorials/build-website.js && webpack --config webpack-website-tutorials.config.js",
136
136
  "lint": "eslint --cache . --max-warnings=0",
137
137
  "puglint": "pug-lint src examples",
138
- "glsllint": "find . \\( -name '*.frag' \\) -exec sh -c 'for n; do python scripts/preprocess_glsl.py \"$n\" | node_modules/glslang-validator-prebuilt/bin/glslangValidator --stdin -S frag || exit 1; done' sh {} \\+ && find . \\( -name '*.vert' \\) -exec sh -c 'for n; do python scripts/preprocess_glsl.py \"$n\" | node_modules/glslang-validator-prebuilt/bin/glslangValidator --stdin -S vert || exit 1; done' sh {} \\+",
138
+ "glsllint": "chmod +x node_modules/glslang-validator-prebuilt-predownloaded/bin/glslangValidator.linux; find . \\( -name '*.frag' \\) -exec sh -c 'for n; do python scripts/preprocess_glsl.py \"$n\" | node_modules/glslang-validator-prebuilt-predownloaded/bin/glslangValidator.linux --stdin -S frag || exit 1; done' sh {} \\+ && find . \\( -name '*.vert' \\) -exec sh -c 'for n; do python scripts/preprocess_glsl.py \"$n\" | node_modules/glslang-validator-prebuilt-predownloaded/bin/glslangValidator.linux --stdin -S vert || exit 1; done' sh {} \\+",
139
139
  "test-headless": "GEOJS_TEST_CASE=tests/test-unit.js karma start karma-cov.conf.js --single-run --browsers ChromeHeadlessTouch,FirefoxHeadlessTouch",
140
140
  "test-headless-all": "GEOJS_TEST_CASE=tests/test-unit.js karma start karma-cov.conf.js --single-run --browsers ChromeHeadlessTouch,FirefoxHeadlessTouch",
141
141
  "test-headed": "GEOJS_TEST_CASE=tests/test-headed.js karma start karma-cov.conf.js --single-run --browsers ChromeFull",
package/src/action.js CHANGED
@@ -17,7 +17,8 @@ var geo_action = {
17
17
  zoomselect: 'geo_action_zoomselect',
18
18
 
19
19
  // annotation actions -- some are also added by the registry
20
- annotation_edit_handle: 'geo_annotation_edit_handle'
20
+ annotation_edit_handle: 'geo_annotation_edit_handle',
21
+ annotation_cursor: 'geo_annotation_cursor'
21
22
  };
22
23
 
23
24
  module.exports = geo_action;
@@ -15,7 +15,8 @@ var annotationState = {
15
15
  create: 'create',
16
16
  done: 'done',
17
17
  highlight: 'highlight',
18
- edit: 'edit'
18
+ edit: 'edit',
19
+ cursor: 'cursor'
19
20
  };
20
21
 
21
22
  var annotationActionOwner = 'annotationAction';
@@ -153,10 +154,13 @@ var annotation = function (type, args) {
153
154
 
154
155
  /**
155
156
  * Assign a new id to this annotation.
157
+ *
158
+ * @returns {this}
156
159
  */
157
160
  this.newId = function () {
158
161
  annotationId += 1;
159
162
  m_id = annotationId;
163
+ return m_this;
160
164
  };
161
165
 
162
166
  /**
@@ -308,6 +312,27 @@ var annotation = function (type, args) {
308
312
  return m_this;
309
313
  };
310
314
 
315
+ this._cursorHandleMousemove = function (evt) {
316
+ m_this.layer()._handleMouseMoveModifiers(evt);
317
+ const center = m_this._cursorCenter;
318
+ const delta = {
319
+ x: evt.mapgcs.x - center.x,
320
+ y: evt.mapgcs.y - center.y
321
+ };
322
+ if (delta.x || delta.y) {
323
+ const curPts = m_this._coordinates();
324
+ var pts = m_this._coordinatesMapFunc(curPts, function (elem) {
325
+ return {x: elem.x + delta.x, y: elem.y + delta.y};
326
+ });
327
+ m_this._coordinates(pts);
328
+ m_this._cursorCenter = evt.mapgcs;
329
+ m_this.modified();
330
+ m_this.draw();
331
+ return true;
332
+ }
333
+ return false;
334
+ };
335
+
311
336
  /**
312
337
  * Get or set the state of this annotation.
313
338
  *
@@ -328,6 +353,17 @@ var annotation = function (type, args) {
328
353
  annotation: m_this
329
354
  });
330
355
  }
356
+ if (m_this.layer()) {
357
+ m_this.layer().geoOff(geo_event.mousemove, m_this._cursorHandleMousemove);
358
+ }
359
+ switch (m_state) {
360
+ case annotationState.cursor:
361
+ m_this._cursorCenter = util.centerFromPerimeter(m_this._coordinates());
362
+ if (m_this.layer()) {
363
+ m_this.layer().geoOn(geo_event.mousemove, m_this._cursorHandleMousemove);
364
+ }
365
+ break;
366
+ }
331
367
  }
332
368
  return m_this;
333
369
  };
@@ -356,6 +392,18 @@ var annotation = function (type, args) {
356
392
  owner: annotationActionOwner,
357
393
  input: 'pan'
358
394
  }];
395
+ case annotationState.cursor:
396
+ return [{
397
+ action: geo_action.annotation_cursor,
398
+ name: 'annotation cursor',
399
+ owner: annotationActionOwner,
400
+ input: 'pan'
401
+ }, {
402
+ action: geo_action.annotation_cursor,
403
+ name: 'annotation cursor',
404
+ owner: annotationActionOwner,
405
+ input: 'left'
406
+ }];
359
407
  default:
360
408
  return [];
361
409
  }
@@ -479,7 +527,7 @@ var annotation = function (type, args) {
479
527
  /* For style objects, re-extend them without recursion. This allows
480
528
  * setting colors without an opacity field, for instance. */
481
529
  ['style', 'createStyle', 'editStyle', 'editHandleStyle', 'labelStyle',
482
- 'highlightStyle'
530
+ 'highlightStyle', 'cursorStyle'
483
531
  ].forEach(function (key) {
484
532
  if (arg1[key] !== undefined) {
485
533
  $.extend(m_options[key], arg1[key]);
@@ -528,8 +576,8 @@ var annotation = function (type, args) {
528
576
  * current style with the values in the specified object.
529
577
  * @param {*} [arg2] If `arg1` is a string, the new value for that style.
530
578
  * @param {string} [styleType='style'] The name of the style type, such as
531
- * `createStyle`, `editStyle`, `editHandleStyle`, `labelStyle`, or
532
- * `highlightStyle`.
579
+ * `createStyle`, `editStyle`, `editHandleStyle`, `labelStyle`,
580
+ * `highlightStyle`, or `cursorStyle`.
533
581
  * @returns {object|this} Either the entire style object, the value of a
534
582
  * specific style, or the current class instance.
535
583
  */
@@ -578,12 +626,18 @@ var annotation = function (type, args) {
578
626
  * @instance
579
627
  */
580
628
  /**
581
- * Calls {@link geo.annotation#style} with `styleType='highlightHandleStyle'`.
582
- * @function highlightHandleStyle
629
+ * Calls {@link geo.annotation#style} with `styleType='highlightStyle'`.
630
+ * @function highlightStyle
583
631
  * @memberof geo.annotation
584
632
  * @instance
585
633
  */
586
- ['createStyle', 'editStyle', 'editHandleStyle', 'labelStyle', 'highlightStyle'
634
+ /**
635
+ * Calls {@link geo.annotation#style} with `styleType='cursorStyle'`.
636
+ * @function cursorStyle
637
+ * @memberof geo.annotation
638
+ * @instance
639
+ */
640
+ ['createStyle', 'editStyle', 'editHandleStyle', 'labelStyle', 'highlightStyle', 'cursorStyle'
587
641
  ].forEach(function (styleType) {
588
642
  m_this[styleType] = function (arg1, arg2) {
589
643
  return m_this.style(arg1, arg2, styleType);
@@ -608,6 +662,10 @@ var annotation = function (type, args) {
608
662
  return $.extend({}, m_options.style, m_options.editStyle,
609
663
  m_options[state + 'Style']);
610
664
  }
665
+ if (state === annotationState.cursor) {
666
+ return $.extend({}, m_options.style, m_options.editStyle,
667
+ m_options.createStyle, m_options[state + 'Style']);
668
+ }
611
669
  return m_options[state + 'Style'] || m_options.style || {};
612
670
  };
613
671
 
@@ -3,6 +3,7 @@ const inherit = require('../inherit');
3
3
  const registerAnnotation = require('../registry').registerAnnotation;
4
4
  const lineFeature = require('../lineFeature');
5
5
  const polygonFeature = require('../polygonFeature');
6
+ const util = require('../util');
6
7
 
7
8
  const annotation = require('./annotation').annotation;
8
9
  const annotationState = require('./annotation').state;
@@ -61,6 +62,9 @@ var polygonAnnotation = function (args) {
61
62
  }
62
63
  return m_this.options('vertices')[i];
63
64
  }
65
+ },
66
+ cursorStyle: {
67
+ position: util.identityFunction
64
68
  }
65
69
  }, args);
66
70
  args.vertices = args.vertices || args.coordinates || [];
@@ -332,6 +336,13 @@ polygonAnnotation.defaults = $.extend({}, annotation.defaults, {
332
336
  stroke: false,
333
337
  strokeColor: {r: 0, g: 0, b: 1}
334
338
  },
339
+ cursorStyle: {
340
+ closed: true,
341
+ fillColor: {r: 0.3, g: 0.3, b: 0.3},
342
+ fillOpacity: 0.25,
343
+ stroke: true,
344
+ strokeColor: {r: 0, g: 0, b: 1}
345
+ },
335
346
  allowBooleanOperations: true
336
347
  });
337
348
 
@@ -184,6 +184,15 @@ var annotationLayer = function (arg) {
184
184
  }
185
185
  }
186
186
  break;
187
+ case m_this.modes.cursor:
188
+ m_this.currentAnnotation._cursorHandleMousemove(evt.mouse);
189
+ update = m_this.currentAnnotation.processAction(evt);
190
+ m_this.geoTrigger(geo_event.annotation.cursor_action, {
191
+ annotation: m_this.currentAnnotation,
192
+ operation: m_this.currentBooleanOperation(),
193
+ evt: evt
194
+ });
195
+ break;
187
196
  default:
188
197
  update = m_this.currentAnnotation.processAction(evt);
189
198
  break;
@@ -261,7 +270,7 @@ var annotationLayer = function (arg) {
261
270
  * @param {geo.event} evt The mouse move or click event.
262
271
  */
263
272
  this._handleMouseMoveModifiers = function (evt) {
264
- if (m_this.mode() !== m_this.modes.edit && m_this.currentAnnotation.options('allowBooleanOperations') && m_this.currentAnnotation._coordinates().length < 2) {
273
+ if (m_this.mode() !== m_this.modes.edit && m_this.currentAnnotation.options('allowBooleanOperations') && (m_this.currentAnnotation._coordinates().length < 2 || m_this.mode() === m_this.modes.cursor)) {
265
274
  if (evt.modifiers) {
266
275
  const mod = (evt.modifiers.shift ? 's' : '') + (evt.modifiers.ctrl ? 'c' : '') + (evt.modifiers.meta || evt.modifiers.alt ? 'a' : '');
267
276
  if (m_this._currentBooleanClass === m_this._booleanClasses[mod]) {
@@ -392,6 +401,13 @@ var annotationLayer = function (arg) {
392
401
  update = m_this.currentAnnotation.mouseClick(evt);
393
402
  m_this._updateFromEvent(update);
394
403
  retrigger = !m_this.mode();
404
+ if (m_this.mode() === m_this.modes.cursor) {
405
+ m_this.geoTrigger(geo_event.annotation.cursor_click, {
406
+ annotation: m_this.currentAnnotation,
407
+ operation: m_this.currentBooleanOperation(),
408
+ evt: evt
409
+ });
410
+ }
395
411
  } else if (!m_this.mode() && !m_this.currentAnnotation && m_this.options('clickToEdit')) {
396
412
  var highlighted = m_this.annotations().filter(function (ann) {
397
413
  return ann.state() === geo_annotation.state.highlight;
@@ -591,7 +607,8 @@ var annotationLayer = function (arg) {
591
607
 
592
608
  /* A list of special modes */
593
609
  this.modes = {
594
- edit: 'edit'
610
+ edit: 'edit',
611
+ cursor: 'cursor'
595
612
  };
596
613
 
597
614
  /* Keys are short-hand for preferred event modifiers. Values are classes to
@@ -608,11 +625,13 @@ var annotationLayer = function (arg) {
608
625
  *
609
626
  * @param {string|null} [arg] `undefined` to get the current mode, `null` to
610
627
  * stop creating/editing, `this.modes.edit` (`'edit'`) plus an annotation
611
- * to switch to edit mode, or the name of the type of annotation to
612
- * create. Available annotations can listed via
628
+ * to switch to edit mode, `this.modes.cursor` plus an annotation to
629
+ * switch to using the annotation as a cursor, or the name of the type of
630
+ * annotation to create. Available annotations can listed via
613
631
  * {@link geo.listAnnotations}.
614
- * @param {geo.annotation} [editAnnotation] If `arg === this.modes.edit`,
615
- * this is the annotation that should be edited.
632
+ * @param {geo.annotation} [editAnnotation] If `arg === this.modes.edit` or
633
+ * `arg === this.modes.cursor`, this is the annotation that should be
634
+ * edited or used.
616
635
  * @param {object} [options] Additional options to pass when creating an
617
636
  * annotation.
618
637
  * @returns {string|null|this} The current mode or the layer.
@@ -622,11 +641,14 @@ var annotationLayer = function (arg) {
622
641
  if (arg === undefined) {
623
642
  return m_mode;
624
643
  }
625
- if (arg !== m_mode || (arg === m_this.modes.edit && editAnnotation !== m_this.editAnnotation) || (arg !== m_this.modes.edit && arg && !m_this.currentAnnotation)) {
644
+ if (arg !== m_mode ||
645
+ ((arg === m_this.modes.edit || arg === m_this.modes.cursor) && editAnnotation !== m_this.editAnnotation) ||
646
+ (arg !== m_this.modes.edit && arg !== m_this.modes.cursor && arg && !m_this.currentAnnotation)
647
+ ) {
626
648
  var createAnnotation, actions,
627
649
  mapNode = m_this.map().node(), oldMode = m_mode;
628
650
  m_mode = arg;
629
- mapNode.toggleClass('annotation-input', !!(m_mode && m_mode !== m_this.modes.edit));
651
+ mapNode.toggleClass('annotation-input', !!(m_mode && m_mode !== m_this.modes.edit && m_mode !== m_this.modes.cursor));
630
652
  if (!m_mode || m_mode === m_this.modes.edit) {
631
653
  Object.values(m_this._booleanClasses).forEach((c) => mapNode.toggleClass(c, false));
632
654
  m_this._currentBooleanClass = undefined;
@@ -649,6 +671,12 @@ var annotationLayer = function (arg) {
649
671
  m_this.modified();
650
672
  m_this.draw();
651
673
  break;
674
+ case geo_annotation.state.cursor:
675
+ m_this.currentAnnotation.state(geo_annotation.state.done);
676
+ m_this.modified();
677
+ m_this.draw();
678
+ m_this.map().node().toggleClass('annotation-cursor', false);
679
+ break;
652
680
  }
653
681
  m_this.currentAnnotation = null;
654
682
  }
@@ -656,6 +684,12 @@ var annotationLayer = function (arg) {
656
684
  m_this.currentAnnotation = editAnnotation;
657
685
  m_this.currentAnnotation.state(geo_annotation.state.edit);
658
686
  m_this.modified();
687
+ } else if (m_mode === m_this.modes.cursor) {
688
+ m_this.currentAnnotation = editAnnotation;
689
+ m_this.currentAnnotation.state(geo_annotation.state.cursor);
690
+ m_this.modified();
691
+ m_this.map().node().toggleClass('annotation-cursor', true);
692
+ actions = m_this.currentAnnotation.actions(geo_annotation.state.cursor);
659
693
  } else if (registry.registries.annotations[m_mode]) {
660
694
  createAnnotation = registry.registries.annotations[m_mode].func;
661
695
  }
@@ -669,13 +703,15 @@ var annotationLayer = function (arg) {
669
703
  m_this.currentAnnotation = createAnnotation(options);
670
704
  m_this.addAnnotation(m_this.currentAnnotation, null);
671
705
  actions = m_this.currentAnnotation.actions(geo_annotation.state.create);
706
+ }
707
+ if (actions) {
672
708
  $.each(actions, function (idx, action) {
673
709
  m_this.map().interactor().addAction(action);
674
710
  });
675
711
  }
676
712
  m_this.geoTrigger(geo_event.annotation.mode, {
677
713
  mode: m_mode, oldMode: oldMode});
678
- if (oldMode === m_this.modes.edit) {
714
+ if (oldMode === m_this.modes.edit || oldMode === m_this.modes.cursor) {
679
715
  m_this.modified();
680
716
  }
681
717
  }
@@ -138,6 +138,14 @@ var canvas_quadFeature = function (arg) {
138
138
 
139
139
  var oldAlpha = context2d.globalAlpha;
140
140
  var opacity = oldAlpha;
141
+ var nearestPixel = m_this.nearestPixel();
142
+ if (nearestPixel !== undefined) {
143
+ if (nearestPixel !== true && util.isNonNullFinite(nearestPixel)) {
144
+ const curZoom = m_this.layer().map().zoom();
145
+ nearestPixel = curZoom >= nearestPixel;
146
+ }
147
+ context2d.imageSmoothingEnabled = !nearestPixel;
148
+ }
141
149
  $.each([m_quads.imgQuads, m_quads.vidQuads], function (listidx, quadlist) {
142
150
  if (!quadlist) {
143
151
  return;
@@ -193,6 +201,7 @@ var canvas_quadFeature = function (arg) {
193
201
  context2d.globalAlpha = oldAlpha;
194
202
  }
195
203
  context2d.setTransform(1, 0, 0, 1, 0, 0);
204
+ context2d.imageSmoothingEnabled = true;
196
205
  };
197
206
 
198
207
  /**
@@ -69,6 +69,7 @@ var canvas_tileLayer = function () {
69
69
  */
70
70
  this._update = function (request) {
71
71
  s_update.call(m_this, request);
72
+ m_quadFeature.nearestPixel(m_this.nearestPixel());
72
73
  m_this._addBaseQuadToTiles(m_quadFeature, m_tiles);
73
74
  return m_this;
74
75
  };
package/src/event.js CHANGED
@@ -706,4 +706,32 @@ geo_event.annotation.mode = 'geo_annotation_mode';
706
706
  */
707
707
  geo_event.annotation.boolean = 'geo_annotation_boolean';
708
708
 
709
+ /**
710
+ * Triggered when an annotation is in cursor mode and the mouse is clicked.
711
+ *
712
+ * @event geo.event.annotation.cursor_click
713
+ * @type {geo.event.base}
714
+ * @property {geo.annotation} annotation The annotation that is being operated
715
+ * on.
716
+ * @property {string} operation The operation being performed.
717
+ * @property {boolean} [cancel] If the handle sets this to false, don't apply
718
+ * the operation to the annotation layer.
719
+ * @property {object} event The triggering event.
720
+ */
721
+ geo_event.annotation.cursor_click = 'geo_annotation_cursor_click';
722
+
723
+ /**
724
+ * Triggered when an annotation is in cursor mode and an action occurs.
725
+ *
726
+ * @event geo.event.annotation.cursor_action
727
+ * @type {geo.event.base}
728
+ * @property {geo.annotation} annotation The annotation that is being operated
729
+ * on.
730
+ * @property {string} operation The operation being performed.
731
+ * @property {boolean} [cancel] If the handle sets this to false, don't apply
732
+ * the operation to the annotation layer.
733
+ * @property {object} event The triggering event.
734
+ */
735
+ geo_event.annotation.cursor_action = 'geo_annotation_cursor_action';
736
+
709
737
  module.exports = geo_event;
package/src/main.styl CHANGED
@@ -58,6 +58,17 @@
58
58
  &.annotation-xor
59
59
  cursor embedurl("./css/cursor-crosshair-xor.svg") 12 12,crosshair
60
60
 
61
+ &.annotation-cursor
62
+ cursor crosshair
63
+ &.annotation-intersect
64
+ cursor embedurl("./css/cursor-crosshair-intersect.svg") 12 12,crosshair
65
+ &.annotation-difference
66
+ cursor embedurl("./css/cursor-crosshair-difference.svg") 12 12,crosshair
67
+ &.annotation-union
68
+ cursor embedurl("./css/cursor-crosshair-union.svg") 12 12,crosshair
69
+ &.annotation-xor
70
+ cursor embedurl("./css/cursor-crosshair-xor.svg") 12 12,crosshair
71
+
61
72
  &.highlight-focus
62
73
  &:after
63
74
  content ""
package/src/map.js CHANGED
@@ -407,18 +407,22 @@ var map = function (arg) {
407
407
  * center.
408
408
  * @param {boolean} [ignoreDiscreteZoom] If `true`, ignore the discreteZoom
409
409
  * option when determining the new view.
410
+ * @param {boolean} [ignoreClampBounds] If `true`, ignore the clampBounds
411
+ * option when determining the new view.
410
412
  * @returns {number|this}
411
413
  * @fires geo.event.zoom
412
414
  * @fires geo.event.pan
413
415
  */
414
- this.zoom = function (val, origin, ignoreDiscreteZoom) {
416
+ this.zoom = function (val, origin, ignoreDiscreteZoom, ignoreClampBounds) {
415
417
  if (val === undefined) {
416
418
  return m_zoom;
417
419
  }
418
420
  var evt, bounds;
419
421
  /* If we are zooming around a point, ignore the clamp bounds */
420
422
  var aroundPoint = (origin && (origin.mapgcs || origin.geo) && origin.map);
421
- var ignoreClampBounds = aroundPoint;
423
+ if (aroundPoint) {
424
+ ignoreClampBounds = true;
425
+ }
422
426
 
423
427
  /* The ignoreDiscreteZoom flag is intended to allow non-integer zoom values
424
428
  * during animation. */
@@ -446,7 +450,7 @@ var map = function (arg) {
446
450
  m_this.pan({x: origin.map.x - shifted.x, y: origin.map.y - shifted.y},
447
451
  ignoreDiscreteZoom, true);
448
452
  } else {
449
- m_this.pan({x: 0, y: 0}, ignoreDiscreteZoom);
453
+ m_this.pan({x: 0, y: 0}, ignoreDiscreteZoom, ignoreClampBounds);
450
454
  }
451
455
  return m_this;
452
456
  };
@@ -1101,6 +1105,8 @@ var map = function (arg) {
1101
1105
  * a value based on what canceled a transition, `transition`: the current
1102
1106
  * transition that just completed, `next`: a boolean if another transition
1103
1107
  * follows immediately.
1108
+ * @param {boolean} [opts.endClamp=true] If `false`, the last center change
1109
+ * will not clamp to the bounds and zoom values.
1104
1110
  * @param {string|geo.transform|null} [gcs] Input gcs. `undefined` to use
1105
1111
  * the interface gcs, `null` to use the map gcs, or any other transform.
1106
1112
  * Applies only to `opts.center` and to converting zoom values to height,
@@ -1205,7 +1211,8 @@ var map = function (arg) {
1205
1211
  zCoord: opts.zCoord,
1206
1212
  done: opts.done,
1207
1213
  duration: opts.duration,
1208
- zoomOrigin: opts.zoomOrigin
1214
+ zoomOrigin: opts.zoomOrigin,
1215
+ endClamp: opts.endClamp
1209
1216
  };
1210
1217
 
1211
1218
  m_transition.interp = opts.interp([
@@ -1260,9 +1267,10 @@ var map = function (arg) {
1260
1267
  if (!next) {
1261
1268
  if (m_transition.end.center) {
1262
1269
  var needZoom = m_zoom !== m_this._fix_zoom(m_transition.end.zoom);
1263
- m_this.center(m_transition.end.center, null, needZoom, needZoom);
1270
+ var noEndClamp = needZoom || opts.endClamp === false;
1271
+ m_this.center(m_transition.end.center, null, noEndClamp, noEndClamp);
1264
1272
  }
1265
- m_this.zoom(m_transition.end.zoom, m_transition.zoomOrigin);
1273
+ m_this.zoom(m_transition.end.zoom, m_transition.zoomOrigin, opts.endClamp === false, opts.endClamp === false);
1266
1274
  m_this.rotation(fix_rotation(m_transition.end.rotation));
1267
1275
  }
1268
1276
 
@@ -1297,7 +1305,7 @@ var map = function (arg) {
1297
1305
  }, null, true, true);
1298
1306
  } else {
1299
1307
  m_center = m_this.gcsToWorld({x: p[0], y: p[1]}, null);
1300
- m_this.zoom(p[2], m_transition.zoomOrigin, true);
1308
+ m_this.zoom(p[2], m_transition.zoomOrigin, true, true);
1301
1309
  }
1302
1310
  m_this.rotation(p[3], undefined, true);
1303
1311
 
@@ -1956,6 +1964,9 @@ var map = function (arg) {
1956
1964
  return m_this;
1957
1965
  };
1958
1966
 
1967
+ /* Report the current version on the map object. */
1968
+ this._version = require('./version');
1969
+
1959
1970
  /**
1960
1971
  * Draw a layer image to a canvas context. The layer's opacity and transform
1961
1972
  * are applied. This is used as part of making a screenshot.
@@ -134,6 +134,7 @@ var mapInteractor = function (args) {
134
134
  m_boundKeys,
135
135
  m_touchHandler,
136
136
  m_state,
137
+ m_nextStateId = 0,
137
138
  m_queue,
138
139
  $node,
139
140
  m_selectionLayer = null,
@@ -1117,6 +1118,7 @@ var mapInteractor = function (args) {
1117
1118
  };
1118
1119
 
1119
1120
  // store the state object
1121
+ m_nextStateId += 1;
1120
1122
  m_state = {
1121
1123
  action: action,
1122
1124
  actionRecord: actionRecord,
@@ -1124,6 +1126,7 @@ var mapInteractor = function (args) {
1124
1126
  initialZoom: map.zoom(),
1125
1127
  initialRotation: map.rotation(),
1126
1128
  initialEventRotation: evt.rotation,
1129
+ stateId: m_nextStateId,
1127
1130
  delta: {x: 0, y: 0}
1128
1131
  };
1129
1132
 
@@ -96,6 +96,7 @@ var pixelmapLayer = function (arg) {
96
96
  this._init = function () {
97
97
  // Call super class init
98
98
  s_init.apply(m_this, arguments);
99
+ m_this.nearestPixel(true, true);
99
100
 
100
101
  const pixelmapArgs = {quadFeature: m_this.features()[0]};
101
102
  if (arg.style) {
@@ -47,6 +47,10 @@ var feature = require('./feature');
47
47
  * call `cacheUpdate` on the data item or for all data.
48
48
  * @property {geo.quadFeature.styleSpec} [style] Style object with default
49
49
  * style options.
50
+ * @property {boolean|number} [nearestPixel] If true, image quads are
51
+ * rendered with near-neighbor sampling. If false, with interpolated
52
+ * sampling. If a number, interpolate at that zoom level or below and
53
+ * nearest neighbor at that zoom level or above.
50
54
  */
51
55
 
52
56
  /**
@@ -106,6 +110,7 @@ var quadFeature = function (arg) {
106
110
  var m_this = this,
107
111
  s_init = this._init,
108
112
  m_cacheQuads,
113
+ m_nearestPixel = arg.nearestPixel,
109
114
  m_nextQuadId = 0,
110
115
  m_images = [],
111
116
  m_videos = [],
@@ -677,6 +682,25 @@ var quadFeature = function (arg) {
677
682
  return null;
678
683
  };
679
684
 
685
+ /**
686
+ * Get/Set nearestPixel value.
687
+ *
688
+ * @param {boolean|number} [val] If not specified, return the current value.
689
+ * If true, image quads are rendered with near-neighbor sampling. If
690
+ * false, with interpolated sampling. If a number, interpolate at that
691
+ * zoom level or below and nearest neighbor at that zoom level or above.
692
+ * @returns {boolean|number|this}
693
+ */
694
+ this.nearestPixel = function (val) {
695
+ if (val === undefined) {
696
+ return m_nearestPixel;
697
+ } else {
698
+ m_nearestPixel = val;
699
+ m_this.modified();
700
+ }
701
+ return m_this;
702
+ };
703
+
680
704
  /**
681
705
  * Initialize.
682
706
  *
package/src/tileLayer.js CHANGED
@@ -81,6 +81,10 @@ var featureLayer = require('./featureLayer');
81
81
  * any tile layers. If specified, this uses the quad defaults, so this is a
82
82
  * ``geo.quadFeature.position`` object with, typically, an ``image`` property
83
83
  * added to it. The quad positions are in the map gcs coordinates.
84
+ * @property {boolean|number} [nearestPixel] If true, image quads are
85
+ * rendered with near-neighbor sampling. If false, with interpolated
86
+ * sampling. If a number, interpolate at that zoom level or below and
87
+ * nearest neighbor at that zoom level or above.
84
88
  */
85
89
 
86
90
  /**
@@ -228,6 +232,7 @@ var tileLayer = function (arg) {
228
232
  m_reference,
229
233
  m_exited,
230
234
  m_lastBaseQuad,
235
+ m_nearestPixel = arg.nearestPixel,
231
236
  m_this = this;
232
237
 
233
238
  // copy the options into a private variable
@@ -1682,6 +1687,31 @@ var tileLayer = function (arg) {
1682
1687
  return m_this;
1683
1688
  };
1684
1689
 
1690
+ /**
1691
+ * Get/Set nearestPixel value.
1692
+ *
1693
+ * @param {boolean|number} [val] If not specified, return the current value.
1694
+ * If true, image quads are rendered with near-neighbor sampling. If
1695
+ * false, with interpolated sampling. If a number, interpolate at that
1696
+ * zoom level or below and nearest neighbor at that zoom level or above.
1697
+ * @param {boolean} [skipUpdate] If specifying val and this value is truthy,
1698
+ * don't update the layer or mark it as modified.
1699
+ * @returns {boolean|number|this}
1700
+ */
1701
+ this.nearestPixel = function (val, skipUpdate) {
1702
+ if (val === undefined) {
1703
+ return m_nearestPixel;
1704
+ }
1705
+ if (m_nearestPixel !== val) {
1706
+ m_nearestPixel = val;
1707
+ if (!skipUpdate) {
1708
+ m_this.modified();
1709
+ m_this._update();
1710
+ }
1711
+ }
1712
+ return m_this;
1713
+ };
1714
+
1685
1715
  /**
1686
1716
  * Get/set the baseQuad.
1687
1717
  *
@@ -134,7 +134,8 @@ var webgl_pixelmapFeature = function (arg) {
134
134
  m_quadFeature = m_this.layer().createFeature('quad', {
135
135
  selectionAPI: false,
136
136
  gcs: m_this.gcs(),
137
- visible: m_this.visible(undefined, true)
137
+ visible: m_this.visible(undefined, true),
138
+ nearestPixel: true
138
139
  });
139
140
  m_quadFeatureInit = false;
140
141
  }
@@ -153,11 +154,6 @@ var webgl_pixelmapFeature = function (arg) {
153
154
  prog.addUniform(lutHeight);
154
155
  };
155
156
  m_quadFeature._hookRenderImageQuads = (renderState, quads) => {
156
- quads.forEach((quad) => {
157
- if (quad.image && quad.texture && !quad.texture.nearestPixel()) {
158
- quad.texture.setNearestPixel(true);
159
- }
160
- });
161
157
  m_lookupTable.bind(renderState, quads);
162
158
  };
163
159
  if (m_quadFeatureInit === false) {
@@ -188,6 +184,7 @@ var webgl_pixelmapFeature = function (arg) {
188
184
 
189
185
  if (arg.quadFeature) {
190
186
  m_quadFeature = arg.quadFeature;
187
+ m_quadFeature.nearestPixel(true);
191
188
  }
192
189
  this._init(arg);
193
190
  return this;