Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[needs code review] Add ImageSource for VideoSource-like image sources. #1174

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions js/mapbox-gl.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ if (typeof window === 'undefined') {

mapboxgl.GeoJSONSource = require('./source/geojson_source');
mapboxgl.VideoSource = require('./source/video_source');
mapboxgl.ImageSource = require('./source/image_source');

mapboxgl.Style = require('./style/style');

Expand Down
141 changes: 141 additions & 0 deletions js/source/image_source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
'use strict';

var util = require('../util/util');
var Tile = require('./tile');
var LatLng = require('../geo/lat_lng');
var Point = require('point-geometry');
var Evented = require('../util/evented');
var ajax = require('../util/ajax');

module.exports = ImageSource;

/**
* Create an Image source instance given an options object
* @class ImageSource
* @param {Object} [options]
* @param {String} options.url A string URL of an image file
* @param {Array} options.coordinates lat, lng coordinates in order clockwise
* starting at the top left: tl, tr, br, bl
* @example
* var sourceObj = new mapboxgl.ImageSource({
* url: 'https://www.mapbox.com/images/foo.png',
* coordinates: [
* [39.18579907229748, -76.54335737228394],
* [39.1838364847587, -76.52803659439087],
* [39.17683392507606, -76.5295386314392],
* [39.17876344106642, -76.54520273208618]
* ]
* });
* map.addSource('some id', sourceObj); // add
* map.removeSource('some id'); // remove
*/
function ImageSource(options) {
this.coordinates = options.coordinates;

ajax.getImage(options.url, function(err, image) {
// @TODO handle errors via event.
if (err) return;

this.image = image;

this.image.addEventListener('load', function() {
this.map._rerender();
}.bind(this));

this._loaded = true;

if (this.map) {
this.createTile();
this.fire('change');
}
}.bind(this));
}

ImageSource.prototype = util.inherit(Evented, {
onAdd: function(map) {
this.map = map;
if (this.image) {
this.createTile();
}
},

/**
* Calculate which mercator tile is suitable for rendering the image in
* and create a buffer with the corner coordinates. These coordinates
* may be outside the tile, because raster tiles aren't clipped when rendering.
*/
createTile: function() {
var map = this.map;
var coords = this.coordinates.map(function(latlng) {
var loc = LatLng.convert(latlng);
return map.transform.locationCoordinate(loc).zoomTo(0);
});

var center = util.getCoordinatesCenter(coords);
var tileExtent = 4096;
var tileCoords = coords.map(function(coord) {
var zoomedCoord = coord.zoomTo(center.zoom);
return new Point(
Math.round((zoomedCoord.column - center.column) * tileExtent),
Math.round((zoomedCoord.row - center.row) * tileExtent));
});

var gl = map.painter.gl;
var maxInt16 = 32767;
var array = new Int16Array([
tileCoords[0].x, tileCoords[0].y, 0, 0,
tileCoords[1].x, tileCoords[1].y, maxInt16, 0,
tileCoords[3].x, tileCoords[3].y, 0, maxInt16,
tileCoords[2].x, tileCoords[2].y, maxInt16, maxInt16
]);

this.tile = new Tile();
this.tile.buckets = {};

this.tile.boundsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.tile.boundsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);

this.center = center;
},

loaded: function() {
return this.image && this.image.complete;
},

update: function() {
// noop
},

render: function(layers, painter) {
if (!this._loaded || !this.loaded()) return;

var c = this.center;
this.tile.calculateMatrices(c.zoom, c.column, c.row, this.map.transform, painter);

var gl = painter.gl;

if (!this.tile.texture) {
this.tile.texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this.tile.texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.image);
} else {
gl.bindTexture(gl.TEXTURE_2D, this.tile.texture);
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, this.image);
}

painter.drawLayers(layers, this.tile.posMatrix, this.tile);
},

/**
* An ImageSource doesn't have any vector features that could
* be selectable, so always return an empty array.
*/
featuresAt: function(point, params, callback) {
return callback(null, []);
}
});
3 changes: 2 additions & 1 deletion js/source/source.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ exports.create = function(source) {
vector: require('./vector_tile_source'),
raster: require('./raster_tile_source'),
geojson: require('./geojson_source'),
video: require('./video_source')
video: require('./video_source'),
image: require('./image_source')
};

for (var type in sources) {
Expand Down
20 changes: 1 addition & 19 deletions js/source/video_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ var Tile = require('./tile');
var LatLng = require('../geo/lat_lng');
var Point = require('point-geometry');
var Evented = require('../util/evented');
var Coordinate = require('../geo/coordinate');
var ajax = require('../util/ajax');

module.exports = VideoSource;
Expand Down Expand Up @@ -86,24 +85,7 @@ VideoSource.prototype = util.inherit(Evented, {
return map.transform.locationCoordinate(loc).zoomTo(0);
});

var minX = Infinity;
var minY = Infinity;
var maxX = -Infinity;
var maxY = -Infinity;

for (var i = 0; i < coords.length; i++) {
minX = Math.min(minX, coords[i].column);
minY = Math.min(minY, coords[i].row);
maxX = Math.max(maxX, coords[i].column);
maxY = Math.max(maxY, coords[i].row);
}

var dx = maxX - minX;
var dy = maxY - minY;
var dMax = Math.max(dx, dy);
var center = new Coordinate((minX + maxX) / 2, (minY + maxY) / 2, 0)
.zoomTo(Math.floor(-Math.log(dMax) / Math.LN2));

var center = util.getCoordinatesCenter(coords);
var tileExtent = 4096;
var tileCoords = coords.map(function(coord) {
var zoomedCoord = coord.zoomTo(center.zoom);
Expand Down
26 changes: 26 additions & 0 deletions js/util/util.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

var UnitBezier = require('unitbezier');
var Coordinate = require('../geo/coordinate');

/**
* Given a value `t` that varies between 0 and 1, return
Expand Down Expand Up @@ -317,3 +318,28 @@ exports.setOptions = function(obj, options) {
}
return obj.options;
};

/**
* Given a list of coordinates, get their center as a coordinate.
* @param {Array<Coordinate>} coords
* @returns {Coordinate} centerpoint
*/
exports.getCoordinatesCenter = function(coords) {
var minX = Infinity;
var minY = Infinity;
var maxX = -Infinity;
var maxY = -Infinity;

for (var i = 0; i < coords.length; i++) {
minX = Math.min(minX, coords[i].column);
minY = Math.min(minY, coords[i].row);
maxX = Math.max(maxX, coords[i].column);
maxY = Math.max(maxY, coords[i].row);
}

var dx = maxX - minX;
var dy = maxY - minY;
var dMax = Math.max(dx, dy);
return new Coordinate((minX + maxX) / 2, (minY + maxY) / 2, 0)
.zoomTo(Math.floor(-Math.log(dMax) / Math.LN2));
};