From 7addf8678d2c7674711a1df70f12647b24743ca2 Mon Sep 17 00:00:00 2001 From: LorenMueller Date: Tue, 22 May 2018 15:36:27 -0700 Subject: [PATCH] Cleanup and commenting --- modules/services/streetside.js | 365 ++++++++++++--------------------- modules/svg/streetside.js | 108 +++++----- 2 files changed, 189 insertions(+), 284 deletions(-) diff --git a/modules/services/streetside.js b/modules/services/streetside.js index b408ab7a5..af54e45bc 100644 --- a/modules/services/streetside.js +++ b/modules/services/streetside.js @@ -7,54 +7,43 @@ import _isEmpty from 'lodash-es/isEmpty'; import _map from 'lodash-es/map'; import _some from 'lodash-es/some'; import _union from 'lodash-es/union'; - import { range as d3_range } from 'd3-array'; import { dispatch as d3_dispatch } from 'd3-dispatch'; - import { request as d3_request, json as d3_json } from 'd3-request'; - import { select as d3_select, selectAll as d3_selectAll } from 'd3-selection'; - import rbush from 'rbush'; - import { jsonpRequest } from '../util/jsonp_request'; import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile'; import { geoExtent } from '../geo'; import { utilDetect } from '../util/detect'; import { utilQsString, utilRebind } from '../util'; -var apibase = 'https://a.mapillary.com/v3/', - bubbleApi = 'https://dev.virtualearth.net/mapcontrol/HumanScaleServices/GetBubbles.ashx?', +var bubbleApi = 'https://dev.virtualearth.net/mapcontrol/HumanScaleServices/GetBubbles.ashx?', streetsideImagesApi = 'https://t.ssl.ak.tiles.virtualearth.net/tiles/', - appkey = 'An-VWpS-o_m7aV8Lxa0oR9cC3bxwdhdCYEGEFHMP9wyMbmRJFzWfMDD1z3-DXUuE', - streetsideViewerCss = 'pannellum-streetside/pannellum.css', - streetsideViewer = 'pannellum-streetside/pannellum.js', - viewercss = 'mapillary-js/mapillary.min.css', - viewerjs = 'mapillary-js/mapillary.js', - clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi', - maxResults = 1000, + bubbleAppKey = 'AuftgJsO0Xs8Ts4M1xZUQJQXJNsvmh3IV8DkNieCiy3tCwCUMq76-WpkrBtNAuEm', + acctId = '1402191', + pannellumViewerCss = 'pannellum-streetside/pannellum.css', + pannellumViewer = 'pannellum-streetside/pannellum.js', tileZoom = 16.5, dispatch = d3_dispatch('loadedBubbles'), - _mlyFallback = false, - _mlyCache, - _mlyClicks, - _mlySelectedImage, - _mlySignDefs, - _mlySignSprite, - _mlyViewer; - + _bubbleCache; +/** + * abortRequest(). + */ function abortRequest(i) { i.abort(); } - +/** + * nearNullIsland(). + */ function nearNullIsland(x, y, z) { if (z >= 7) { var center = Math.pow(2, z - 1), @@ -66,17 +55,9 @@ function nearNullIsland(x, y, z) { return false; } - -function maxPageAtZoom(z) { - if (z < 15) return 2; - if (z === 15) return 5; - if (z === 16) return 10; - if (z === 17) return 20; - if (z === 18) return 40; - if (z > 18) return 80; -} - - +/** + * localeTimeStamp(). + */ function localeTimestamp(s) { if (!s) return null; var detected = utilDetect(); @@ -90,15 +71,19 @@ function localeTimestamp(s) { return d.toLocaleString(detected.locale, options); } -// using d3.geo.tiles.js from lib, gets tile extents for the current -// map view extent +/** + * getTiles() returns array of d3 geo tiles. + * Using d3.geo.tiles.js from lib, gets tile extents for each grid tile in a grid created from + * an area around (and including) the current map view extents. + */ function getTiles(projection) { - console.log('getTiles()'); - //s is the current map scale - //z is the 'Level of Detail', or zoom-level, where Level 1 is far from the earth, and Level 23 is close to the ground. - //ts ('tile size') here is the formula for determining the width/height of the map in pixels, but with a modification. - // See 'Ground Resolution and Map Scale': //https://msdn.microsoft.com/en-us/library/bb259689.aspx. - // Here, by subtracting constant 'tileZoom' from z (the level), you end up with a much smaller value for the tile size (in pixels). + //console.log('getTiles()'); + + // s is the current map scale + // z is the 'Level of Detail', or zoom-level, where Level 1 is far from the earth, and Level 23 is close to the ground. + // ts ('tile size') here is the formula for determining the width/height of the map in pixels, but with a modification. + // See 'Ground Resolution and Map Scale': //https://msdn.microsoft.com/en-us/library/bb259689.aspx. + // As used here, by subtracting constant 'tileZoom' from z (the level), you end up with a much smaller value for the tile size (in pixels). var s = projection.scale() * 2 * Math.PI, z = Math.max(Math.log(s) / Math.log(2) - 8, 0), ts = 256 * Math.pow(2, z - tileZoom), @@ -111,7 +96,6 @@ function getTiles(projection) { .size(projection.clipExtent()[1]) .translate(projection.translate())() .map(function (tile) { - //console.log('d3_geoTile: ', tile); var x = tile[0] * ts - origin[0], y = tile[1] * ts - origin[1]; return { @@ -125,7 +109,9 @@ function getTiles(projection) { }); } - +/** + * loadTiles() wraps the process of generating tiles and then fetching image points for each tile. + */ function loadTiles(which, url, projection) { console.log('loadTiles() for: ', which); var s = projection.scale() * 2 * Math.PI, @@ -135,101 +121,80 @@ function loadTiles(which, url, projection) { var tiles = getTiles(projection).filter(function (t) { return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]); }); - console.log("loadTiles(), tiles = ", tiles); - - // which.inflight seems to always be undefined - _filter(which.inflight, function (v, k) { - var wanted = _find(tiles, function (tile) { return k === (tile.id + ',0'); }); - if (!wanted) delete which.inflight[k]; - return !wanted; - }).map(abortRequest); tiles.forEach(function (tile) { loadNextTilePage(which, currZoom, url, tile); }); } -// load data for the next tile page in line +/** + * loadNextTilePage() load data for the next tile page in line. + */ function loadNextTilePage(which, currZoom, url, tile) { console.log('loadNextTilePage()'); - var cache = _mlyCache[which], - //maxPages = maxPageAtZoom(currZoom), - nextPage = cache.nextPage[tile.id] || 0; - - var id = tile.id + ',' + String(nextPage); + var cache = _bubbleCache[which], + nextPage = cache.nextPage[tile.id] || 0, + id = tile.id + ',' + String(nextPage); + if (cache.loaded[id] || cache.inflight[id]) return; + cache.inflight[id] = getBubbles(url, tile, function(bubbles){ + console.log("GET Response - bubbles: ", bubbles); + cache.loaded[id] = true; + delete cache.inflight[id]; + if (!bubbles) return; - //console.log('id = ', id); - //console.log('cache.loaded[id]: ', cache.loaded[id]); - //console.log('cache.inflight[id]: ', cache.inflight[id]); - - if (cache.loaded[id] || cache.inflight[id]) return; + // [].shift() removes the first element, some statistics info, not a bubble point + bubbles.shift(); + console.log('bubbles.length', bubbles.length); + var features = bubbles.map(function (bubble) { + var loc = [bubble.lo, bubble.la]; + var d = { + loc: loc, + key: bubble.id, + ca: bubble.he, + captured_at: bubble.cd, + captured_by: "microsoft", + nbn: bubble.nbn, + pbn: bubble.pbn, + rn: bubble.rn, + pano: true + }; + var feature = { + geometry: { + coordinates: [bubble.lo, bubble.la], + type: "Point" + }, + properties: d, + type: "Feature" + }; + var bubbleId = bubble.id; + cache.points[bubbleId] = feature; + cache.forImageKey[bubbleId] = bubbleId; + return { + minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d + }; - cache.inflight[id] = getBubbles(url, tile, function(bubbles){ - console.log("GET Response - bubbles: ", bubbles); - cache.loaded[id] = true; - delete cache.inflight[id]; - if (!bubbles) return; - // remove first element, statistic info on request, not a bubble - bubbles.shift(); - console.log('bubbles.length', bubbles.length); - var features = bubbles.map(function (bubble) { - //console.log("bubble: ", bubble); - var loc = [bubble.lo, bubble.la]; - var d = { - loc: loc, - key: bubble.id, - ca: bubble.he, - captured_at: bubble.cd, - captured_by: "microsoft", - nbn: bubble.nbn, - pbn: bubble.pbn, - rn: bubble.rn, - pano: true - }; - var feature = { - geometry: { - coordinates: [bubble.lo, bubble.la], - type: "Point" - }, - properties: d, - type: "Feature" - }; - var bubbleId = bubble.id; - cache.points[bubbleId] = feature; - cache.forImageKey[bubbleId] = bubbleId; - // return false; // because no `d` data worth loading into an rbush - return { - minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d - }; - - }).filter(Boolean); - //console.log("bubble features: ", features); - cache.rtree.load(features); - //console.log('cache.nextPage[tile.id]', cache.nextPage[tile.id]); - if (which === 'bubbles'){ - dispatch.call('loadedBubbles'); - } - // if (bubbles.length === maxResults) { // more pages to load - // cache.nextPage[tile.id] = nextPage + 1; - // loadNextTilePage(which, currZoom, url, tile); - // } else { - // cache.nextPage[tile.id] = Infinity; // no more pages to load - // } - }); + }).filter(Boolean); + cache.rtree.load(features); + if (which === 'bubbles'){ + dispatch.call('loadedBubbles'); + } + }); } +/** + * getBubbles() handles the request to the server for a tile extent of 'bubbles' (streetside image locations). + */ function getBubbles(url, tile, callback) { - console.log('services - streetside - getBubbles()'); + //console.log('services - streetside - getBubbles()'); var rect = tile.extent.rectangle(); var urlForRequest = url + utilQsString({ n: rect[3], s: rect[1], e: rect[2], w: rect[0], - appkey: appkey, + appkey: bubbleAppKey, jsCallback: '{callback}' }); - console.log('url for request',urlForRequest); jsonpRequest(urlForRequest, function (data) { if (!data || data.error) { callback(null); @@ -239,26 +204,9 @@ function getBubbles(url, tile, callback) { }); } -// extract links to pages of API results -function parsePagination(links) { - return links.split(',').map(function (rel) { - var elements = rel.split(';'); - if (elements.length === 2) { - return [ - /<(.+)>/.exec(elements[0])[1], - /rel="(.+)"/.exec(elements[1])[1] - ]; - } else { - return ['', '']; - } - }).reduce(function (pagination, val) { - pagination[val[1]] = val[0]; - return pagination; - }, {}); -} - - -// partition viewport into `psize` x `psize` regions +/** + * partitionViewport() partition viewport into `psize` x `psize` regions. + */ function partitionViewport(psize, projection) { var dimensions = projection.clipExtent()[1]; psize = psize || 16; @@ -278,8 +226,9 @@ function partitionViewport(psize, projection) { return partitions; } - -// no more than `limit` results per partition. +/** + * searchLimited(). + */ function searchLimited(psize, limit, projection, rtree) { //console.log('services - streetside - searchLimited()'); limit = limit || 3; @@ -293,21 +242,6 @@ function searchLimited(psize, limit, projection, rtree) { .slice(0, limit) .map(function (d) { return d.data; }); })); - // console.timeEnd('previous'); - - // console.time('new'); - // results = partitions.reduce(function(result, extent) { - // var found = rtree.search(extent.bbox()) - // .map(function(d) { return d.data; }) - // .sort(function(a, b) { - // return a.loc[1] - b.loc[1]; - // // return a.key.localeCompare(b.key); - // }) - // .slice(0, limit); - - // return (found.length ? result.concat(found) : result); - // }, []); - // console.timeEnd('new'); return results; } @@ -315,17 +249,23 @@ function searchLimited(psize, limit, projection, rtree) { export default { - // initialize streetside + + /** + * init() initialize streetside. + */ init: function () { - if (!_mlyCache) { + if (!_bubbleCache) { this.reset(); } this.event = utilRebind(this, dispatch, 'on'); }, - // reset the cache + + /** + * reset() reset the cache. + */ reset: function () { - var cache = _mlyCache; + var cache = _bubbleCache; if (cache) { if (cache.bubbles && cache.bubbles.inflight) { @@ -333,33 +273,35 @@ export default { } } - _mlyCache = { + _bubbleCache = { bubbles: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush(), forImageKey: {}, points: {} } }; - - _mlySelectedImage = null; - _mlyClicks = []; }, - //called by update() in svg - services.js + /** + * bubbles() + */ bubbles: function (projection) { //console.log('services - streetside - bubbles()'); var psize = 32, limit = 3; - return searchLimited(psize, limit, projection, _mlyCache.bubbles.rtree); + return searchLimited(psize, limit, projection, _bubbleCache.bubbles.rtree); }, - - - // this is called a bunch of times repeatedly + /** + * loadBubbles() + */ loadBubbles: function (projection) { - //console.log('services - streetside - loadImages()'); + //console.log('services - streetside - loadBubbles()'); loadTiles('bubbles', bubbleApi, projection); }, - // create the streeside viewer + /** + * loadViewer() create the streeside viewer. + */ loadViewer: function (context) { //console.log('services - streetside - loadViewer()'); - // create ms-wrapper a photo wrapper class + + // create ms-wrapper, a photo wrapper class var wrap = d3_select('#photoviewer').selectAll('.ms-wrapper') .data([0]); @@ -375,19 +317,12 @@ export default { wrapEnter .append('div') .attr('id','viewer-streetside'); - //.attr('class','photo-viewer-streetside'); // inject div to support photo attribution into ms-wrapper wrapEnter .append('div') .attr('class', 'photo-attribution-streetside fillD'); - // // inject child div for the pannellum viewer - // var wrap2 = d3_select('#viewer-streetside-wrapper').selectAll('#streetside-viewer').data([0]); - // wrap2.enter() - // .append('div') - // .attr('id','viewer-streetside'); - // load streetside pannellum viewer css d3_select('head').selectAll('#streetside-viewercss') .data([0]) @@ -395,7 +330,7 @@ export default { .append('link') .attr('id', 'streetside-viewercss') .attr('rel', 'stylesheet') - .attr('href', context.asset(streetsideViewerCss)); + .attr('href', context.asset(pannellumViewerCss)); // load streetside pannellum viewer js d3_select('head').selectAll('#streetside-viewerjs') @@ -403,10 +338,12 @@ export default { .enter() .append('script') .attr('id', 'streetside-viewerjs') - .attr('src', context.asset(streetsideViewer)); + .attr('src', context.asset(pannellumViewer)); }, - + /** + * showViewer() + */ showViewer: function () { //console.log('services - streetside - showViewer()'); var wrap = d3_select('#photoviewer') @@ -422,21 +359,15 @@ export default { wrap .selectAll('.photo-wrapper.ms-wrapper') .classed('hide', false); - - //_mlyViewer.resize(); } return this; }, - + /** + * hideViewer() + */ hideViewer: function () { - _mlySelectedImage = null; - - if (!_mlyFallback && _mlyViewer) { - _mlyViewer.getComponent('sequence').stop(); - } - var viewer = d3_select('#photoviewer'); if (!viewer.empty()) viewer.datum(null); @@ -451,31 +382,13 @@ export default { return this.setStyles(null, true); }, - // Pass the image datum itself in `d` or the `imageKey` string. - // This allows images to be selected from places that dont have access - // to the full image datum (like the street signs layer or the js viewer) - selectImage: function (d, imageKey, fromViewer) { - //console.log('services - streetside - selectIamge(); d = ',d); - //console.log('services - streetside - selectIamge(); imageKey = ',imageKey); - //console.log('services - streetside - selectIamge(); fromViewer = ',fromViewer); - if (!d && imageKey) { - // If the user clicked on something that's not an image marker, we - // might get in here.. Cache lookup can fail, e.g. if the user - // clicked a streetsign, but images are loading slowly asynchronously. - // We'll try to carry on anyway if there is no datum. There just - // might be a delay before user sees detections, captured_at, etc. - d = _mlyCache.bubbles.forImageKey[imageKey]; - } - - _mlySelectedImage = d; + /** + * selectImage(). + */ + selectImage: function (d) { var viewer = d3_select('#photoviewer'); if (!viewer.empty()) viewer.datum(d); - imageKey = (d && d.key) || imageKey; - if (!fromViewer && imageKey) { - _mlyClicks.push(imageKey); - } - this.setStyles(null, true); var wrap = d3_select('#photoviewer .ms-wrapper'); @@ -511,15 +424,15 @@ export default { '&focus=photo&lat=' + d.loc[1] + '&lng=' + d.loc[0] + '&z=17') .text('Report a privacy concern with this image'); - //this.updateDetections(d); - var bubbleIdQuadKey = d.key.toString(4); var paddingNeeded = 16 - bubbleIdQuadKey.length; for (var i = 0; i < paddingNeeded ;i++) { bubbleIdQuadKey = "0" + bubbleIdQuadKey; } - var imgLocIdxArr = ['01','02','03','10','11','12']; //Order matters here: front=01, right=02, back=03, left=10 up=11,= down=12 + + //Order matters here: front=01, right=02, back=03, left=10 up=11,= down=12 + var imgLocIdxArr = ['01','02','03','10','11','12']; var imgUrlPrefix = streetsideImagesApi + 'hs' + bubbleIdQuadKey; var imgUrlSuffix = '.jpg?g=6338&n=z'; pannellum.viewer('viewer-streetside', { @@ -536,14 +449,12 @@ export default { "autoLoad": true }); } - ////console.log("clicked a streetside image: ", d); return this; }, - getSelectedImage: function () { - return _mlySelectedImage; - }, - + /** + * setStyles(). + */ setStyles: function (hovered, reset) { if (reset) { // reset all layers d3_selectAll('.viewfield-group') @@ -573,16 +484,10 @@ export default { return this; }, - + /** + * cache(). + */ cache: function () { - return _mlyCache; - }, - - - signDefs: function (_) { - if (!arguments.length) return _mlySignDefs; - _mlySignDefs = _; - return this; + return _bubbleCache; } - }; diff --git a/modules/svg/streetside.js b/modules/svg/streetside.js index 612f4135e..860efe09f 100644 --- a/modules/svg/streetside.js +++ b/modules/svg/streetside.js @@ -7,11 +7,13 @@ export function svgStreetside(projection, context, dispatch) { var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); var minZoom = 16; var minMarkerZoom = 16; - var minViewfieldZoom = 16; + var minViewfieldZoom = 19; var layer = d3_select(null); var _streetside; - + /** + * init(). + */ function init() { if (svgStreetside.initialized) return; // run once svgStreetside.enabled = false; @@ -19,7 +21,9 @@ export function svgStreetside(projection, context, dispatch) { console.log("svg: streetside initialized...."); } - + /** + * getService(). + */ function getService() { if (services.streetside && !_streetside) { _streetside = services.streetside; @@ -31,7 +35,9 @@ export function svgStreetside(projection, context, dispatch) { return _streetside; } - + /** + * showLayer(). + */ function showLayer() { console.log('svg - streetside - showLayer()'); var service = getService(); @@ -48,7 +54,9 @@ export function svgStreetside(projection, context, dispatch) { .on('end', function () { dispatch.call('change'); }); } - + /** + * hideLayer(). + */ function hideLayer() { var service = getService(); if (service) { @@ -64,44 +72,55 @@ export function svgStreetside(projection, context, dispatch) { .on('end', editOff); } - + /** + * editOn(). + */ function editOn() { layer.style('display', 'block'); } - + /** + * editOff(). + */ function editOff() { layer.selectAll('.viewfield-group').remove(); layer.style('display', 'none'); } - + /** + * click() Handles 'bubble' point click event. + */ function click(d) { - console.log("svg: map was clicked with streetside on, here is the passed object: ", d); + console.log("svg: map was clicked with streetside on. Passed obj: ", d); var service = getService(); if (!service) return; service .selectImage(d) - //.updateViewer(d.key, context); .showViewer(); context.map().centerEase(d.loc); } - + /** + * mouseover(). + */ function mouseover(d) { var service = getService(); if (service) service.setStyles(d); } - + /** + * mouseout(). + */ function mouseout() { var service = getService(); if (service) service.setStyles(null); } - + /** + * transform(). + */ function transform(d) { var t = svgPointTransform(projection)(d); if (d.ca) { @@ -110,42 +129,20 @@ export function svgStreetside(projection, context, dispatch) { return t; } - + /** + * update(). + */ function update() { console.log("svg - update()"); var viewer = d3_select('#photoviewer'); var selected = viewer.empty() ? undefined : viewer.datum(); - var z = ~~context.map().zoom(); - - //console.log('z = ', z); - //console.log('minMarkerZoom = ', minMarkerZoom); - var showMarkers = (z >= minMarkerZoom); var showViewfields = (z >= minViewfieldZoom); - var service = getService(); + // gets the features from service cache - //var sequences = (service ? service.sequences(projection) : []); - //var images = (service && showMarkers ? service.images(projection) : []); var bubbles = (service && showMarkers ? service.bubbles(projection) : []); - console.log("svg: update() bubbles", bubbles); - // console.log(" svg: update() images", images); - // var traces = layer.selectAll('.sequences').selectAll('.sequence') - // .data(sequences, function(d) { return d.properties.key; }); - - // // exit - // traces.exit() - // .remove(); - - // // enter/update - // traces = traces.enter() - // .append('path') - // .attr('class', 'sequence') - // .merge(traces) - // .attr('d', svgPath(projection).geojson); - - var groups = layer.selectAll('.markers').selectAll('.viewfield-group') .data(bubbles, function(d) { return d.key; }); @@ -171,7 +168,7 @@ export function svgStreetside(projection, context, dispatch) { .sort(function(a, b) { return (a === selected) ? 1 : (b === selected) ? -1 - : b.loc[1] - a.loc[1]; // sort Y + : b.loc[1] - a.loc[1]; }) .attr('transform', transform) .select('.viewfield-scale'); @@ -191,8 +188,10 @@ export function svgStreetside(projection, context, dispatch) { viewfields.exit() .remove(); - viewfields.enter() // viewfields may or may not be drawn... - .insert('path', 'circle') // but if they are, draw below the circles + // viewfields may or may not be drawn... + // but if they are, draw below the circles + viewfields.enter() + .insert('path', 'circle') .attr('class', 'viewfield') .attr('transform', 'scale(1.5,1.5),translate(-8, -13)') .attr('d', viewfieldPath); @@ -207,13 +206,16 @@ export function svgStreetside(projection, context, dispatch) { } } - //drawImages is the method that is returned (and that runs) everytime 'svgStreetside()' is called. - //'svgStreetside()' is called from index.js (ln) + /** + * drawImages() + * drawImages is the method that is returned (and that runs) everytime 'svgStreetside()' is called. + * 'svgStreetside()' is called from index.js + */ function drawImages(selection) { //console.log("svg - streetside - drawImages(); selection: ", selection); var enabled = svgStreetside.enabled, service = getService(); - //console.log("svg - streetside - drawImages(); svgStreetside enabled? ", enabled); + layer = selection.selectAll('.layer-streetside-images') .data(service ? [0] : []); @@ -225,10 +227,6 @@ export function svgStreetside(projection, context, dispatch) { .attr('class', 'layer-streetside-images') .style('display', enabled ? 'block' : 'none'); - // layerEnter - // .append('g') - // .attr('class', 'sequences'); - layerEnter .append('g') .attr('class', 'markers'); @@ -240,7 +238,6 @@ export function svgStreetside(projection, context, dispatch) { if (service && ~~context.map().zoom() >= minZoom) { editOn(); update(); - // console.log("svg: calling loadImages...."); service.loadBubbles(projection); } else { @@ -249,7 +246,9 @@ export function svgStreetside(projection, context, dispatch) { } } - + /** + * drawImages.enabled(). + */ drawImages.enabled = function(_) { //console.log('svg - streetside - drawImages.enabled()'); if (!arguments.length) return svgStreetside.enabled; @@ -263,12 +262,13 @@ export function svgStreetside(projection, context, dispatch) { return this; }; - + /** + * drawImages.supported(). + */ drawImages.supported = function() { return !!getService(); }; - - + init(); return drawImages;