From 9be1014757bae5c0d1714052b0ad080a29dabd30 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 2 Aug 2017 08:16:29 -0700 Subject: [PATCH 1/2] Type symbol directory and symbol_bucket.js --- src/data/bucket/symbol_bucket.js | 217 ++++++++++++++++++++++------- src/shaders/encode_attribute.js | 3 +- src/source/rtl_text_plugin.js | 2 +- src/style/style_layer.js | 4 +- src/symbol/anchor.js | 5 +- src/symbol/check_max_angle.js | 16 ++- src/symbol/clip_line.js | 15 +- src/symbol/collision_box.js | 22 +++ src/symbol/collision_feature.js | 58 +++++--- src/symbol/collision_tile.js | 49 +++---- src/symbol/get_anchors.js | 17 ++- src/symbol/glyph_source.js | 2 + src/symbol/mergelines.js | 15 +- src/symbol/projection.js | 77 +++++++--- src/symbol/quads.js | 80 +++++------ src/symbol/shaping.js | 232 ++++++++++++++++++++----------- src/symbol/sprite_atlas.js | 2 +- src/symbol/symbol_size.js | 26 +++- src/symbol/transform_text.js | 5 +- src/util/classify_rings.js | 9 +- 20 files changed, 583 insertions(+), 273 deletions(-) diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index 72b45522712..3b62fe43f65 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -5,12 +5,12 @@ const ArrayGroup = require('../array_group'); const BufferGroup = require('../buffer_group'); const createElementArrayType = require('../element_array_type'); const EXTENT = require('../extent'); -const packUint8ToFloat = require('../../shaders/encode_attribute').packUint8ToFloat; +const {packUint8ToFloat} = require('../../shaders/encode_attribute'); const Anchor = require('../../symbol/anchor'); const getAnchors = require('../../symbol/get_anchors'); const resolveTokens = require('../../util/token'); -const Quads = require('../../symbol/quads'); -const Shaping = require('../../symbol/shaping'); +const {getGlyphQuads, getIconQuads} = require('../../symbol/quads'); +const {shapeText, shapeIcon, WritingMode} = require('../../symbol/shaping'); const transformText = require('../../symbol/transform_text'); const mergeLines = require('../../symbol/mergelines'); const clipLine = require('../../symbol/clip_line'); @@ -24,27 +24,60 @@ const vectorTileFeatureTypes = require('@mapbox/vector-tile').VectorTileFeature. const createStructArrayType = require('../../util/struct_array'); const verticalizePunctuation = require('../../util/verticalize_punctuation'); -const shapeText = Shaping.shapeText; -const shapeIcon = Shaping.shapeIcon; -const WritingMode = Shaping.WritingMode; -const getGlyphQuads = Quads.getGlyphQuads; -const getIconQuads = Quads.getIconQuads; - import type {BucketParameters, IndexedFeature, PopulateParameters} from '../bucket'; import type {ProgramInterface} from '../program_configuration'; +import type CollisionBoxArray, {CollisionBox} from '../../symbol/collision_box'; +import type CollisionTile from '../../symbol/collision_tile'; +import type {StructArray} from '../../util/struct_array'; +import type StyleLayer from '../../style/style_layer'; +import type {Shaping, PositionedIcon} from '../../symbol/shaping'; +import type {SymbolQuad} from '../../symbol/quads'; type SymbolBucketParameters = BucketParameters & { sdfIcons: boolean, iconsNeedLinear: boolean, - fontstack: any, + fontstack: string, textSizeData: any, iconSizeData: any, - placedGlyphArray: any, - placedIconArray: any, - glyphOffsetArray: any, - lineVertexArray: any, + placedGlyphArray: StructArray, + placedIconArray: StructArray, + glyphOffsetArray: StructArray, + lineVertexArray: StructArray, } +type SymbolInstance = { + textBoxStartIndex: number, + textBoxEndIndex: number, + iconBoxStartIndex: number, + iconBoxEndIndex: number, + glyphQuads: Array, + iconQuads: Array, + textOffset: [number, number], + iconOffset: [number, number], + anchor: Anchor, + line: Array, + featureIndex: number, + featureProperties: Object, + writingModes: number, + textCollisionFeature?: {boxStartIndex: number, boxEndIndex: number}, + iconCollisionFeature?: {boxStartIndex: number, boxEndIndex: number} +}; + +export type SymbolFeature = { + text: string | void, + icon: string | void, + index: number, + sourceLayerIndex: number, + geometry: Array>, + properties: Object, + type: 'Point' | 'LineString' | 'Polygon' +}; + +type ShapedTextOrientations = { + '1'?: Shaping, + '2'?: Shaping +}; + const PlacedSymbolArray = createStructArrayType({ members: [ { type: 'Int16', name: 'anchorX' }, @@ -180,9 +213,13 @@ function addCollisionBoxVertex(layoutVertexArray, point, anchor, extrude, maxZoo * and icons needed (by this bucket and any others). When glyphs and icons * have been received, the WorkerTile creates a CollisionTile and invokes: * - * 3. SymbolBucket#prepare(stacks, icons) to perform text shaping and layout, populating `this.symbolInstances` and `this.collisionBoxArray`. + * 3. SymbolBucket#prepare(stacks, icons) to perform text shaping and layout, + * populating `this.symbolInstances` and `this.collisionBoxArray`. * - * 4. SymbolBucket#place(collisionTile): taking collisions into account, decide on which labels and icons to actually draw and at which scale, populating the vertex arrays (`this.arrays.glyph`, `this.arrays.icon`) and thus completing the parsing / buffer population process. + * 4. SymbolBucket#place(collisionTile): taking collisions into account, decide + * on which labels and icons to actually draw and at which scale, populating + * the vertex arrays (`this.arrays.glyph`, `this.arrays.icon`) and thus + * completing the parsing / buffer population process. * * The reason that `prepare` and `place` are separate methods is that * `prepare`, being independent of pitch and orientation, only needs to happen @@ -199,30 +236,29 @@ class SymbolBucket { }; static MAX_INSTANCES: number; + static addDynamicAttributes: typeof addDynamicAttributes; - static addDynamicAttributes: any; - - collisionBoxArray: any; + collisionBoxArray: CollisionBoxArray; zoom: number; overscaling: number; - layers: any; - index: any; + layers: Array; + index: number; sdfIcons: boolean; iconsNeedLinear: boolean; - fontstack: any; - symbolInterfaces: any; - buffers: any; + fontstack: string; + symbolInterfaces: typeof symbolInterfaces; + buffers: {[string]: BufferGroup}; textSizeData: any; iconSizeData: any; - placedGlyphArray: any; - placedIconArray: any; - glyphOffsetArray: any; - lineVertexArray: any; - features: any; - arrays: any; - symbolInstances: any; - tilePixelRatio: any; - compareText: any; + placedGlyphArray: StructArray; + placedIconArray: StructArray; + glyphOffsetArray: StructArray; + lineVertexArray: StructArray; + features: Array; + arrays: {glyph: ArrayGroup, icon: ArrayGroup, collisionBox: ArrayGroup}; + symbolInstances: Array; + tilePixelRatio: number; + compareText: {[string]: Array}; constructor(options: SymbolBucketParameters) { this.collisionBoxArray = options.collisionBoxArray; @@ -382,7 +418,7 @@ class SymbolBucket { if (this.buffers.icon) this.buffers.icon.destroy(); if (this.buffers.glyph) this.buffers.glyph.destroy(); if (this.buffers.collisionBox) this.buffers.collisionBox.destroy(); - this.buffers = null; + this.buffers = (null : any); } } @@ -410,11 +446,12 @@ class SymbolBucket { for (const feature of this.features) { let shapedTextOrientations; - if (feature.text) { - const allowsVerticalWritingMode = scriptDetection.allowsVerticalWritingMode(feature.text); + const text = feature.text; + if (text) { + const allowsVerticalWritingMode = scriptDetection.allowsVerticalWritingMode(text); const textOffset = this.layers[0].getLayoutValue('text-offset', {zoom: this.zoom}, feature.properties).map((t)=> t * oneEm); const spacing = this.layers[0].getLayoutValue('text-letter-spacing', {zoom: this.zoom}, feature.properties) * oneEm; - const spacingIfAllowed = scriptDetection.allowsLetterSpacing(feature.text) ? spacing : 0; + const spacingIfAllowed = scriptDetection.allowsLetterSpacing(text) ? spacing : 0; const textAnchor = this.layers[0].getLayoutValue('text-anchor', {zoom: this.zoom}, feature.properties); const textJustify = this.layers[0].getLayoutValue('text-justify', {zoom: this.zoom}, feature.properties); const maxWidth = layout['symbol-placement'] !== 'line' ? @@ -422,8 +459,26 @@ class SymbolBucket { 0; shapedTextOrientations = { - [WritingMode.horizontal]: shapeText(feature.text, stacks[fontstack], maxWidth, lineHeight, textAnchor, textJustify, spacingIfAllowed, textOffset, oneEm, WritingMode.horizontal), - [WritingMode.vertical]: allowsVerticalWritingMode && textAlongLine && shapeText(feature.text, stacks[fontstack], maxWidth, lineHeight, textAnchor, textJustify, spacingIfAllowed, textOffset, oneEm, WritingMode.vertical) + [WritingMode.horizontal]: shapeText(text, + stacks[fontstack], + maxWidth, + lineHeight, + textAnchor, + textJustify, + spacingIfAllowed, + textOffset, + oneEm, + WritingMode.horizontal), + [WritingMode.vertical]: allowsVerticalWritingMode && textAlongLine && shapeText(text, + stacks[fontstack], + maxWidth, + lineHeight, + textAnchor, + textJustify, + spacingIfAllowed, + textOffset, + oneEm, + WritingMode.vertical) }; } else { shapedTextOrientations = {}; @@ -462,7 +517,7 @@ class SymbolBucket { * source.) * @private */ - addFeature(feature: any, shapedTextOrientations: any, shapedIcon: any) { + addFeature(feature: SymbolFeature, shapedTextOrientations: ShapedTextOrientations, shapedIcon: PositionedIcon | void) { const layoutTextSize = this.layers[0].getLayoutValue('text-size', {zoom: this.zoom + 1}, feature.properties); const layoutIconSize = this.layers[0].getLayoutValue('icon-size', {zoom: this.zoom + 1}, feature.properties); @@ -558,7 +613,7 @@ class SymbolBucket { } } - anchorIsTooClose(text: any, repeatDistance: any, anchor: any) { + anchorIsTooClose(text: string, repeatDistance: number, anchor: Point) { const compareText = this.compareText; if (!(text in compareText)) { compareText[text] = []; @@ -576,7 +631,7 @@ class SymbolBucket { return false; } - place(collisionTile: any, showCollisionBoxes: any) { + place(collisionTile: CollisionTile, showCollisionBoxes: boolean) { // Calculate which labels can be shown and when they can be shown and // create the bufers used for rendering. @@ -715,7 +770,7 @@ class SymbolBucket { iconAlongLine, collisionTile.angle, symbolInstance.featureProperties, - null, + 0, symbolInstance.anchor, lineStartIndex, lineLength, @@ -729,7 +784,20 @@ class SymbolBucket { if (showCollisionBoxes) this.addToDebugBuffers(collisionTile); } - addSymbols(arrays: any, quads: any, scale: any, sizeVertex: any, keepUpright: any, lineOffset: any, alongLine: any, placementAngle: any, featureProperties: any, writingModes: any, labelAnchor: any, lineStartIndex: any, lineLength: any, placedSymbolArray: any) { + addSymbols(arrays: ArrayGroup, + quads: Array, + scale: number, + sizeVertex: any, + keepUpright: boolean, + lineOffset: [number, number], + alongLine: boolean, + placementAngle: number, + featureProperties: Object, + writingModes: number, + labelAnchor: Anchor, + lineStartIndex: number, + lineLength: number, + placedSymbolArray: StructArray) { const elementArray = arrays.elementArray; const layoutVertexArray = arrays.layoutVertexArray; const dynamicLayoutVertexArray = arrays.dynamicLayoutVertexArray; @@ -788,7 +856,7 @@ class SymbolBucket { arrays.populatePaintArrays(featureProperties); } - addToDebugBuffers(collisionTile: any) { + addToDebugBuffers(collisionTile: CollisionTile) { const arrays = this.arrays.collisionBox; const layoutVertexArray = arrays.layoutVertexArray; const elementArray = arrays.elementArray; @@ -805,7 +873,7 @@ class SymbolBucket { if (!feature) continue; for (let b = feature.boxStartIndex; b < feature.boxEndIndex; b++) { - const box = this.collisionBoxArray.get(b); + const box: CollisionBox = (this.collisionBoxArray.get(b): any); if (collisionTile.perspectiveRatio === 1 && box.maxScale < 1) { // These boxes aren't used on unpitched maps // See CollisionTile#insertCollisionFeature @@ -857,9 +925,26 @@ class SymbolBucket { * * @private */ - addSymbolInstance(anchor: any, line: any, shapedTextOrientations: any, shapedIcon: any, layer: any, addToBuffers: any, collisionBoxArray: any, featureIndex: any, sourceLayerIndex: any, bucketIndex: any, - textBoxScale: any, textPadding: any, textAlongLine: any, textOffset: any, - iconBoxScale: any, iconPadding: any, iconAlongLine: any, iconOffset: any, globalProperties: any, featureProperties: any) { + addSymbolInstance(anchor: Anchor, + line: Array, + shapedTextOrientations: ShapedTextOrientations, + shapedIcon: PositionedIcon | void, + layer: StyleLayer, + addToBuffers: boolean, + collisionBoxArray: CollisionBoxArray, + featureIndex: number, + sourceLayerIndex: number, + bucketIndex: number, + textBoxScale: number, + textPadding: number, + textAlongLine: boolean, + textOffset: [number, number], + iconBoxScale: number, + iconPadding: number, + iconAlongLine: boolean, + iconOffset: [number, number], + globalProperties: Object, + featureProperties: Object) { let textCollisionFeature, iconCollisionFeature; let iconQuads = []; @@ -871,7 +956,17 @@ class SymbolBucket { getGlyphQuads(anchor, shapedTextOrientations[writingMode], layer, textAlongLine, globalProperties, featureProperties) : []); - textCollisionFeature = new CollisionFeature(collisionBoxArray, line, anchor, featureIndex, sourceLayerIndex, bucketIndex, shapedTextOrientations[writingMode], textBoxScale, textPadding, textAlongLine, false); + textCollisionFeature = new CollisionFeature(collisionBoxArray, + line, + anchor, + featureIndex, + sourceLayerIndex, + bucketIndex, + shapedTextOrientations[writingMode], + textBoxScale, + textPadding, + textAlongLine, + false); } const textBoxStartIndex = textCollisionFeature ? textCollisionFeature.boxStartIndex : this.collisionBoxArray.length; @@ -883,14 +978,28 @@ class SymbolBucket { iconAlongLine, shapedTextOrientations[WritingMode.horizontal], globalProperties, featureProperties) : []; - iconCollisionFeature = new CollisionFeature(collisionBoxArray, line, anchor, featureIndex, sourceLayerIndex, bucketIndex, shapedIcon, iconBoxScale, iconPadding, iconAlongLine, true); + iconCollisionFeature = new CollisionFeature(collisionBoxArray, + line, + anchor, + featureIndex, + sourceLayerIndex, + bucketIndex, + shapedIcon, + iconBoxScale, + iconPadding, + iconAlongLine, + true); } const iconBoxStartIndex = iconCollisionFeature ? iconCollisionFeature.boxStartIndex : this.collisionBoxArray.length; const iconBoxEndIndex = iconCollisionFeature ? iconCollisionFeature.boxEndIndex : this.collisionBoxArray.length; - if (textBoxEndIndex > SymbolBucket.MAX_INSTANCES) util.warnOnce("Too many symbols being rendered in a tile. See https://github.com/mapbox/mapbox-gl-js/issues/2907"); - if (iconBoxEndIndex > SymbolBucket.MAX_INSTANCES) util.warnOnce("Too many glyphs being rendered in a tile. See https://github.com/mapbox/mapbox-gl-js/issues/2907"); + if (textBoxEndIndex > SymbolBucket.MAX_INSTANCES) { + util.warnOnce("Too many symbols being rendered in a tile. See https://github.com/mapbox/mapbox-gl-js/issues/2907"); + } + if (iconBoxEndIndex > SymbolBucket.MAX_INSTANCES) { + util.warnOnce("Too many glyphs being rendered in a tile. See https://github.com/mapbox/mapbox-gl-js/issues/2907"); + } const writingModes = ( (shapedTextOrientations[WritingMode.vertical] ? WritingMode.vertical : 0) | @@ -967,7 +1076,7 @@ function getSizeVertexData(layer, tileZoom, stopZoomLevels, sizeProperty, featur ) { // source function return [ - 10 * layer.getLayoutValue(sizeProperty, {}, featureProperties) + 10 * layer.getLayoutValue(sizeProperty, ({} : any), featureProperties) ]; } else if ( !layer.isLayoutValueZoomConstant(sizeProperty) && diff --git a/src/shaders/encode_attribute.js b/src/shaders/encode_attribute.js index d820461945e..f1e9a183502 100644 --- a/src/shaders/encode_attribute.js +++ b/src/shaders/encode_attribute.js @@ -1,3 +1,4 @@ +// @flow const util = require('../util/util'); @@ -8,7 +9,7 @@ const util = require('../util/util'); * * @private */ -exports.packUint8ToFloat = function pack(a, b) { +exports.packUint8ToFloat = function pack(a: number, b: number) { // coerce a and b to 8-bit ints a = util.clamp(Math.floor(a), 0, 255); b = util.clamp(Math.floor(b), 0, 255); diff --git a/src/source/rtl_text_plugin.js b/src/source/rtl_text_plugin.js index 3d6873b2caa..54b837d5d8c 100644 --- a/src/source/rtl_text_plugin.js +++ b/src/source/rtl_text_plugin.js @@ -49,4 +49,4 @@ module.exports.setRTLTextPlugin = function(pluginURL: string, callback: ErrorCal }; module.exports.applyArabicShaping = (null : ?Function); -module.exports.processBidirectionalText = (null : ?Function); +module.exports.processBidirectionalText = (null : ?(string, Array) => Array); diff --git a/src/style/style_layer.js b/src/style/style_layer.js index 42c8bda51df..9f55d8248bf 100644 --- a/src/style/style_layer.js +++ b/src/style/style_layer.js @@ -117,7 +117,7 @@ class StyleLayer extends Evented { ); } - getLayoutValue(name: string, globalProperties?: GlobalProperties, featureProperties?: FeatureProperties) { + getLayoutValue(name: string, globalProperties?: GlobalProperties, featureProperties?: FeatureProperties): any { const specification = this._layoutSpecifications[name]; const declaration = this._layoutDeclarations[name]; @@ -170,7 +170,7 @@ class StyleLayer extends Evented { } } - getPaintValue(name: string, globalProperties?: GlobalProperties, featureProperties?: FeatureProperties) { + getPaintValue(name: string, globalProperties?: GlobalProperties, featureProperties?: FeatureProperties): any { const specification = this._paintSpecifications[name]; const transition = this._paintTransitions[name]; diff --git a/src/symbol/anchor.js b/src/symbol/anchor.js index 47c61f33177..a0c124c57cb 100644 --- a/src/symbol/anchor.js +++ b/src/symbol/anchor.js @@ -1,9 +1,12 @@ +// @flow const Point = require('@mapbox/point-geometry'); class Anchor extends Point { + angle: any; + segment: number | void; - constructor(x, y, angle, segment) { + constructor(x: number, y: number, angle: number, segment?: number) { super(x, y); this.angle = angle; if (segment !== undefined) { diff --git a/src/symbol/check_max_angle.js b/src/symbol/check_max_angle.js index 783054a042a..556b50c0d9e 100644 --- a/src/symbol/check_max_angle.js +++ b/src/symbol/check_max_angle.js @@ -1,20 +1,24 @@ +// @flow module.exports = checkMaxAngle; +import type Point from '@mapbox/point-geometry'; +import type Anchor from './anchor'; + /** * Labels placed around really sharp angles aren't readable. Check if any * part of the potential label has a combined angle that is too big. * - * @param {Array} line - * @param {Anchor} anchor The point on the line around which the label is anchored. - * @param {number} labelLength The length of the label in geometry units. - * @param {number} windowSize The check fails if the combined angles within a part of the line that is `windowSize` long is too big. - * @param {number} maxAngle The maximum combined angle that any window along the label is allowed to have. + * @param line + * @param anchor The point on the line around which the label is anchored. + * @param labelLength The length of the label in geometry units. + * @param windowSize The check fails if the combined angles within a part of the line that is `windowSize` long is too big. + * @param maxAngle The maximum combined angle that any window along the label is allowed to have. * * @returns {boolean} whether the label should be placed * @private */ -function checkMaxAngle(line, anchor, labelLength, windowSize, maxAngle) { +function checkMaxAngle(line: Array, anchor: Anchor, labelLength: number, windowSize: number, maxAngle: number) { // horizontal labels always pass if (anchor.segment === undefined) return true; diff --git a/src/symbol/clip_line.js b/src/symbol/clip_line.js index 849095f649f..b4df5398233 100644 --- a/src/symbol/clip_line.js +++ b/src/symbol/clip_line.js @@ -1,3 +1,4 @@ +// @flow const Point = require('@mapbox/point-geometry'); @@ -6,15 +7,15 @@ module.exports = clipLine; /** * Returns the part of a multiline that intersects with the provided rectangular box. * - * @param {Array>} lines - * @param {number} x1 the left edge of the box - * @param {number} y1 the top edge of the box - * @param {number} x2 the right edge of the box - * @param {number} y2 the bottom edge of the box - * @returns {Array>} lines + * @param lines + * @param x1 the left edge of the box + * @param y1 the top edge of the box + * @param x2 the right edge of the box + * @param y2 the bottom edge of the box + * @returns lines * @private */ -function clipLine(lines, x1, y1, x2, y2) { +function clipLine(lines: Array>, x1: number, y1: number, x2: number, y2: number): Array> { const clippedLines = []; for (let l = 0; l < lines.length; l++) { diff --git a/src/symbol/collision_box.js b/src/symbol/collision_box.js index 505c6ff21f7..0bbc020be76 100644 --- a/src/symbol/collision_box.js +++ b/src/symbol/collision_box.js @@ -3,6 +3,28 @@ const createStructArrayType = require('../util/struct_array'); const Point = require('@mapbox/point-geometry'); +export type CollisionBox = { + anchorPoint: Point, + anchorPointX: number, + anchorPointY: number, + offsetX: number, + offsetY: number, + x1: number, + y1: number, + x2: number, + y2: number, + unadjustedMaxScale: number, + maxScale: number, + featureIndex: number, + sourceLayerIndex: number, + bucketIndex: number, + bbox0: number, + bbox1: number, + bbox2: number, + bbox3: number, + placementScale: number +}; + /** * A collision box represents an area of the map that that is covered by a * label. CollisionFeature uses one or more of these collision boxes to diff --git a/src/symbol/collision_feature.js b/src/symbol/collision_feature.js index a5a11c10073..fea021efaea 100644 --- a/src/symbol/collision_feature.js +++ b/src/symbol/collision_feature.js @@ -1,3 +1,8 @@ +// @flow + +import type CollisionBoxArray from './collision_box'; +import type Point from '@mapbox/point-geometry'; +import type Anchor from './anchor'; /** * A CollisionFeature represents the area of the tile covered by a single label. @@ -8,19 +13,30 @@ * @private */ class CollisionFeature { + boxStartIndex: number; + boxEndIndex: number; + /** * Create a CollisionFeature, adding its collision box data to the given collisionBoxArray in the process. * - * @param {Array} line The geometry the label is placed on. - * @param {Anchor} anchor The point along the line around which the label is anchored. - * @param {VectorTileFeature} feature The VectorTileFeature that this CollisionFeature was created for. - * @param {Array} layerIDs The IDs of the layers that this CollisionFeature is a part of. - * @param {Object} shaped The text or icon shaping results. - * @param {number} boxScale A magic number used to convert from glyph metrics units to geometry units. - * @param {number} padding The amount of padding to add around the label edges. - * @param {boolean} alignLine Whether the label is aligned with the line or the viewport. + * @param line The geometry the label is placed on. + * @param anchor The point along the line around which the label is anchored. + * @param shaped The text or icon shaping results. + * @param boxScale A magic number used to convert from glyph metrics units to geometry units. + * @param padding The amount of padding to add around the label edges. + * @param alignLine Whether the label is aligned with the line or the viewport. */ - constructor(collisionBoxArray, line, anchor, featureIndex, sourceLayerIndex, bucketIndex, shaped, boxScale, padding, alignLine, straight) { + constructor(collisionBoxArray: CollisionBoxArray, + line: Array, + anchor: Anchor, + featureIndex: number, + sourceLayerIndex: number, + bucketIndex: number, + shaped: Object, + boxScale: number, + padding: number, + alignLine: boolean, + straight: boolean) { const y1 = shaped.top * boxScale - padding; const y2 = shaped.bottom * boxScale + padding; const x1 = shaped.left * boxScale - padding; @@ -39,12 +55,12 @@ class CollisionFeature { if (straight) { // used for icon labels that are aligned with the line, but don't curve along it - const vector = line[anchor.segment + 1].sub(line[anchor.segment])._unit()._mult(length); + const vector = line[anchor.segment + 1].sub(line[(anchor.segment : any)])._unit()._mult(length); const straightLine = [anchor.sub(vector), anchor.add(vector)]; this._addLineCollisionBoxes(collisionBoxArray, straightLine, anchor, 0, length, height, featureIndex, sourceLayerIndex, bucketIndex); } else { // used for text labels that curve along a line - this._addLineCollisionBoxes(collisionBoxArray, line, anchor, anchor.segment, length, height, featureIndex, sourceLayerIndex, bucketIndex); + this._addLineCollisionBoxes(collisionBoxArray, line, anchor, (anchor.segment : any), length, height, featureIndex, sourceLayerIndex, bucketIndex); } } @@ -59,16 +75,20 @@ class CollisionFeature { /** * Create a set of CollisionBox objects for a line. * - * @param {Array} line - * @param {Anchor} anchor - * @param {number} labelLength The length of the label in geometry units. - * @param {Anchor} anchor The point along the line around which the label is anchored. - * @param {VectorTileFeature} feature The VectorTileFeature that this CollisionFeature was created for. - * @param {number} boxSize The size of the collision boxes that will be created. - * + * @param labelLength The length of the label in geometry units. + * @param anchor The point along the line around which the label is anchored. + * @param boxSize The size of the collision boxes that will be created. * @private */ - _addLineCollisionBoxes(collisionBoxArray, line, anchor, segment, labelLength, boxSize, featureIndex, sourceLayerIndex, bucketIndex) { + _addLineCollisionBoxes(collisionBoxArray: CollisionBoxArray, + line: Array, + anchor: Anchor, + segment: number, + labelLength: number, + boxSize: number, + featureIndex: number, + sourceLayerIndex: number, + bucketIndex: number) { const step = boxSize / 2; const nBoxes = Math.floor(labelLength / step); // We calculate line collision boxes out to 300% of what would normally be our diff --git a/src/symbol/collision_tile.js b/src/symbol/collision_tile.js index 19797dbaebb..019dd61d56e 100644 --- a/src/symbol/collision_tile.js +++ b/src/symbol/collision_tile.js @@ -3,9 +3,10 @@ const Point = require('@mapbox/point-geometry'); const EXTENT = require('../data/extent'); const Grid = require('grid-index'); - const intersectionTests = require('../util/intersection_tests'); +import type CollisionBoxArray, {CollisionBox} from './collision_box'; + export type SerializedCollisionTile = {| angle: number, pitch: number, @@ -23,8 +24,8 @@ export type SerializedCollisionTile = {| * @private */ class CollisionTile { - grid: any; - ignoredGrid: any; + grid: Grid; + ignoredGrid: Grid; perspectiveRatio: number; minScale: number; maxScale: number; @@ -35,9 +36,9 @@ class CollisionTile { rotationMatrix: [number, number, number, number]; reverseRotationMatrix: [number, number, number, number]; yStretch: number; - collisionBoxArray: any; - tempCollisionBox: any; - edges: Array; + collisionBoxArray: CollisionBoxArray; + tempCollisionBox: CollisionBox; + edges: Array; static deserialize(serialized: SerializedCollisionTile, collisionBoxArray: any) { return new CollisionTile( @@ -57,8 +58,8 @@ class CollisionTile { cameraToCenterDistance: number, cameraToTileDistance: number, collisionBoxArray: any, - grid: any = new Grid(EXTENT, 12, 6), - ignoredGrid: any = new Grid(EXTENT, 12, 0) + grid: Grid = new Grid(EXTENT, 12, 6), + ignoredGrid: Grid = new Grid(EXTENT, 12, 0) ) { this.angle = angle; this.pitch = pitch; @@ -141,13 +142,11 @@ class CollisionTile { /** * Find the scale at which the collisionFeature can be shown without * overlapping with other features. - * - * @param {CollisionFeature} collisionFeature - * @param allowOverlap - * @param avoidEdges * @private */ - placeCollisionFeature(collisionFeature: any, allowOverlap: boolean, avoidEdges: boolean): number { + placeCollisionFeature(collisionFeature: {boxStartIndex: number, boxEndIndex: number}, + allowOverlap: boolean, + avoidEdges: boolean): number { const collisionBoxArray = this.collisionBoxArray; let minPlacementScale = this.minScale; @@ -156,7 +155,7 @@ class CollisionTile { for (let b = collisionFeature.boxStartIndex; b < collisionFeature.boxEndIndex; b++) { - const box = collisionBoxArray.get(b); + const box: CollisionBox = (collisionBoxArray.get(b): any); const anchorPoint = box.anchorPoint._matMult(rotationMatrix); const x = anchorPoint.x; @@ -195,7 +194,7 @@ class CollisionTile { const blockingBoxes = this.grid.query(x1, y1, x2, y2); for (let i = 0; i < blockingBoxes.length; i++) { - const blocking = collisionBoxArray.get(blockingBoxes[i]); + const blocking: CollisionBox = (collisionBoxArray.get(blockingBoxes[i]): any); const blockingAnchorPoint = blocking.anchorPoint._matMult(rotationMatrix); minPlacementScale = this.getPlacementScale(minPlacementScale, anchorPoint, box, blockingAnchorPoint, blocking); @@ -240,7 +239,7 @@ class CollisionTile { return minPlacementScale; } - queryRenderedSymbols(queryGeometry: any, scale: number): Array { + queryRenderedSymbols(queryGeometry: Array>, scale: number): Array<*> { const sourceLayerFeatures = {}; const result = []; @@ -287,7 +286,7 @@ class CollisionTile { const roundedScale = Math.pow(2, Math.ceil(Math.log(perspectiveScale) / Math.LN2 * 10) / 10); for (let i = 0; i < features.length; i++) { - const blocking = collisionBoxArray.get(features[i]); + const blocking: CollisionBox = (collisionBoxArray.get(features[i]): any); const sourceLayer = blocking.sourceLayerIndex; const featureIndex = blocking.featureIndex; @@ -321,7 +320,11 @@ class CollisionTile { return result; } - getPlacementScale(minPlacementScale: number, anchorPoint: Point, box: any, blockingAnchorPoint: Point, blocking: any): number { + getPlacementScale(minPlacementScale: number, + anchorPoint: Point, + box: CollisionBox, + blockingAnchorPoint: Point, + blocking: CollisionBox): number { // Find the lowest scale at which the two boxes can fit side by side without overlapping. // Original algorithm: @@ -367,18 +370,16 @@ class CollisionTile { /** * Remember this collisionFeature and what scale it was placed at to block * later features from overlapping with it. - * - * @param {CollisionFeature} collisionFeature - * @param minPlacementScale - * @param ignorePlacement * @private */ - insertCollisionFeature(collisionFeature: any, minPlacementScale: number, ignorePlacement: boolean) { + insertCollisionFeature(collisionFeature: {boxStartIndex: number, boxEndIndex: number}, + minPlacementScale: number, + ignorePlacement: boolean) { const grid = ignorePlacement ? this.ignoredGrid : this.grid; const collisionBoxArray = this.collisionBoxArray; for (let k = collisionFeature.boxStartIndex; k < collisionFeature.boxEndIndex; k++) { - const box = collisionBoxArray.get(k); + const box: CollisionBox = (collisionBoxArray.get(k): any); box.placementScale = minPlacementScale; if (minPlacementScale < this.maxScale && (this.perspectiveRatio === 1 || box.maxScale >= 1)) { diff --git a/src/symbol/get_anchors.js b/src/symbol/get_anchors.js index ccd99bec258..66eb0321428 100644 --- a/src/symbol/get_anchors.js +++ b/src/symbol/get_anchors.js @@ -1,11 +1,23 @@ +// @flow const interpolate = require('../style-spec/util/interpolate'); const Anchor = require('../symbol/anchor'); const checkMaxAngle = require('./check_max_angle'); +import type Point from '@mapbox/point-geometry'; +import type {Shaping, PositionedIcon} from './shaping'; + module.exports = getAnchors; -function getAnchors(line, spacing, maxAngle, shapedText, shapedIcon, glyphSize, boxScale, overscaling, tileExtent) { +function getAnchors(line: Array, + spacing: number, + maxAngle: number, + shapedText: ?Shaping, + shapedIcon: ?PositionedIcon, + glyphSize: number, + boxScale: number, + overscaling: number, + tileExtent: number) { // Resample a line to get anchor points for labels and check that each // potential label passes text-max-angle check and has enough froom to fit @@ -77,7 +89,8 @@ function resample(line, offset, spacing, angleWindowSize, maxAngle, labelLength, if (x >= 0 && x < tileExtent && y >= 0 && y < tileExtent && markedDistance - halfLabelLength >= 0 && markedDistance + halfLabelLength <= lineLength) { - const anchor = new Anchor(x, y, angle, i)._round(); + const anchor = new Anchor(x, y, angle, i); + anchor._round(); if (!angleWindowSize || checkMaxAngle(line, anchor, labelLength, angleWindowSize, maxAngle)) { anchors.push(anchor); diff --git a/src/symbol/glyph_source.js b/src/symbol/glyph_source.js index 8da6ef09583..e25edba1c17 100644 --- a/src/symbol/glyph_source.js +++ b/src/symbol/glyph_source.js @@ -30,6 +30,8 @@ class SimpleGlyph { } } +export type {SimpleGlyph as SimpleGlyph}; + /** * A glyph source has a URL from which to load new glyphs and manages * GlyphAtlases in which to store glyphs used by the requested fontstacks diff --git a/src/symbol/mergelines.js b/src/symbol/mergelines.js index d417373c584..cf6d4fb8306 100644 --- a/src/symbol/mergelines.js +++ b/src/symbol/mergelines.js @@ -1,7 +1,10 @@ +// @flow -module.exports = function (features) { - const leftIndex = {}; - const rightIndex = {}; +import type {SymbolFeature} from '../data/bucket/symbol_bucket'; + +module.exports = function (features: Array) { + const leftIndex: {[string]: number} = {}; + const rightIndex: {[string]: number} = {}; const mergedFeatures = []; let mergedIndex = 0; @@ -10,7 +13,7 @@ module.exports = function (features) { mergedIndex++; } - function mergeFromRight(leftKey, rightKey, geom) { + function mergeFromRight(leftKey: string, rightKey: string, geom) { const i = rightIndex[leftKey]; delete rightIndex[leftKey]; rightIndex[rightKey] = i; @@ -20,7 +23,7 @@ module.exports = function (features) { return i; } - function mergeFromLeft(leftKey, rightKey, geom) { + function mergeFromLeft(leftKey: string, rightKey: string, geom) { const i = leftIndex[rightKey]; delete leftIndex[rightKey]; leftIndex[leftKey] = i; @@ -57,7 +60,7 @@ module.exports = function (features) { delete rightIndex[rightKey]; rightIndex[getKey(text, mergedFeatures[i].geometry, true)] = i; - mergedFeatures[j].geometry = null; + mergedFeatures[j].geometry = (null : any); } else if (leftKey in rightIndex) { // found mergeable line adjacent to the start of the current line, merge diff --git a/src/symbol/projection.js b/src/symbol/projection.js index 404336929d8..99ef1256513 100644 --- a/src/symbol/projection.js +++ b/src/symbol/projection.js @@ -1,14 +1,19 @@ +// @flow const Point = require('@mapbox/point-geometry'); -const mat4 = require('@mapbox/gl-matrix').mat4; -const vec4 = require('@mapbox/gl-matrix').vec4; +const {mat4, vec4} = require('@mapbox/gl-matrix'); const symbolSize = require('./symbol_size'); -const addDynamicAttributes = require('../data/bucket/symbol_bucket').addDynamicAttributes; +const {addDynamicAttributes} = require('../data/bucket/symbol_bucket'); + +import type Painter from '../render/painter'; +import type StyleLayer from '../style/style_layer'; +import type Transform from '../geo/transform'; +import type SymbolBucket from '../data/bucket/symbol_bucket'; module.exports = { - updateLineLabels: updateLineLabels, - getLabelPlaneMatrix: getLabelPlaneMatrix, - getGlCoordMatrix: getGlCoordMatrix + updateLineLabels, + getLabelPlaneMatrix, + getGlCoordMatrix }; /* @@ -59,7 +64,11 @@ module.exports = { /* * Returns a matrix for converting from tile units to the correct label coordinate space. */ -function getLabelPlaneMatrix(posMatrix, pitchWithMap, rotateWithMap, transform, pixelsToTileUnits) { +function getLabelPlaneMatrix(posMatrix: mat4, + pitchWithMap: boolean, + rotateWithMap: boolean, + transform: Transform, + pixelsToTileUnits: number) { const m = mat4.identity(new Float32Array(16)); if (pitchWithMap) { mat4.identity(m); @@ -78,7 +87,11 @@ function getLabelPlaneMatrix(posMatrix, pitchWithMap, rotateWithMap, transform, /* * Returns a matrix for converting from the correct label coordinate space to gl coords. */ -function getGlCoordMatrix(posMatrix, pitchWithMap, rotateWithMap, transform, pixelsToTileUnits) { +function getGlCoordMatrix(posMatrix: mat4, + pitchWithMap: boolean, + rotateWithMap: boolean, + transform: Transform, + pixelsToTileUnits: number) { const m = mat4.identity(new Float32Array(16)); if (pitchWithMap) { mat4.multiply(m, m, posMatrix); @@ -94,13 +107,16 @@ function getGlCoordMatrix(posMatrix, pitchWithMap, rotateWithMap, transform, pix return m; } -function project(point, matrix) { +function project(point: Point, matrix: mat4): Point { const pos = [point.x, point.y, 0, 1]; vec4.transformMat4(pos, pos, matrix); return new Point(pos[0] / pos[3], pos[1] / pos[3]); } -function isVisible(anchorPos, placementZoom, clippingBuffer, painter) { +function isVisible(anchorPos: [number, number, number, number], + placementZoom: number, + clippingBuffer: [number, number], + painter: Painter) { const x = anchorPos[0] / anchorPos[3]; const y = anchorPos[1] / anchorPos[3]; const inPaddedViewport = ( @@ -115,7 +131,16 @@ function isVisible(anchorPos, placementZoom, clippingBuffer, painter) { * Update the `dynamicLayoutVertexBuffer` for the buffer with the correct glyph positions for the current map view. * This is only run on labels that are aligned with lines. Horizontal labels are handled entirely in the shader. */ -function updateLineLabels(bucket, posMatrix, painter, isText, labelPlaneMatrix, glCoordMatrix, pitchWithMap, keepUpright, pixelsToTileUnits, layer) { +function updateLineLabels(bucket: SymbolBucket, + posMatrix: mat4, + painter: Painter, + isText: boolean, + labelPlaneMatrix: mat4, + glCoordMatrix: mat4, + pitchWithMap: boolean, + keepUpright: boolean, + pixelsToTileUnits: number, + layer: StyleLayer) { const sizeData = isText ? bucket.textSizeData : bucket.iconSizeData; const partiallyEvaluatedSize = symbolSize.evaluateSizeForZoom(sizeData, painter.transform, layer, isText); @@ -131,7 +156,7 @@ function updateLineLabels(bucket, posMatrix, painter, isText, labelPlaneMatrix, const placedSymbols = isText ? bucket.placedGlyphArray : bucket.placedIconArray; for (let s = 0; s < placedSymbols.length; s++) { - const symbol = placedSymbols.get(s); + const symbol: any = placedSymbols.get(s); const anchorPos = [symbol.anchorX, symbol.anchorY, 0, 1]; vec4.transformMat4(anchorPos, anchorPos, posMatrix); @@ -171,7 +196,18 @@ function updateLineLabels(bucket, posMatrix, painter, isText, labelPlaneMatrix, } } -function placeGlyphsAlongLine(symbol, fontSize, flip, keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, glyphOffsetArray, lineVertexArray, dynamicLayoutVertexArray, anchorPoint, projectionCache) { +function placeGlyphsAlongLine(symbol, + fontSize: number, + flip: boolean, + keepUpright: boolean, + posMatrix: mat4, + labelPlaneMatrix: mat4, + glCoordMatrix: mat4, + glyphOffsetArray: any, + lineVertexArray: any, + dynamicLayoutVertexArray, + anchorPoint: Point, + projectionCache: {[number]: Point}) { const fontScale = fontSize / 24; const lineOffsetX = symbol.lineOffsetX * fontSize; const lineOffsetY = symbol.lineOffsetY * fontSize; @@ -234,14 +270,23 @@ function placeGlyphsAlongLine(symbol, fontSize, flip, keepUpright, posMatrix, la } const placementZoom = symbol.placementZoom; - for (const glyph of placedGlyphs) { + for (const glyph: any of placedGlyphs) { addDynamicAttributes(dynamicLayoutVertexArray, glyph.point, glyph.angle, placementZoom); } return {}; } -function placeGlyphAlongLine(offsetX, lineOffsetX, lineOffsetY, flip, anchorPoint, anchorSegment, - lineStartIndex, lineEndIndex, lineVertexArray, labelPlaneMatrix, projectionCache) { +function placeGlyphAlongLine(offsetX: number, + lineOffsetX: number, + lineOffsetY: number, + flip: boolean, + anchorPoint: Point, + anchorSegment: number, + lineStartIndex: number, + lineEndIndex: number, + lineVertexArray: any, + labelPlaneMatrix: mat4, + projectionCache: {[number]: Point}) { const combinedOffsetX = flip ? offsetX - lineOffsetX : diff --git a/src/symbol/quads.js b/src/symbol/quads.js index 306c7576c43..22773bb0708 100644 --- a/src/symbol/quads.js +++ b/src/symbol/quads.js @@ -1,10 +1,14 @@ +// @flow const Point = require('@mapbox/point-geometry'); +import type Anchor from './anchor'; +import type {PositionedIcon, Shaping} from './shaping'; +import type StyleLayer from '../style/style_layer'; + module.exports = { - getIconQuads: getIconQuads, - getGlyphQuads: getGlyphQuads, - SymbolQuad: SymbolQuad + getIconQuads, + getGlyphQuads }; /** @@ -12,39 +16,40 @@ module.exports = { * * The zoom range the glyph can be shown is defined by minScale and maxScale. * - * @param {Point} tl The offset of the top left corner from the anchor. - * @param {Point} tr The offset of the top right corner from the anchor. - * @param {Point} bl The offset of the bottom left corner from the anchor. - * @param {Point} br The offset of the bottom right corner from the anchor. - * @param {Object} tex The texture coordinates. + * @param tl The offset of the top left corner from the anchor. + * @param tr The offset of the top right corner from the anchor. + * @param bl The offset of the bottom left corner from the anchor. + * @param br The offset of the bottom right corner from the anchor. + * @param tex The texture coordinates. * - * @class SymbolQuad * @private */ -function SymbolQuad(tl, tr, bl, br, tex, writingMode, glyphOffset) { - this.tl = tl; - this.tr = tr; - this.bl = bl; - this.br = br; - this.tex = tex; - this.writingMode = writingMode; - this.glyphOffset = glyphOffset; -} +export type SymbolQuad = { + tl: Point, + tr: Point, + bl: Point, + br: Point, + tex: { + x: number, + y: number, + w: number, + h: number + }, + writingMode: any | void, + glyphOffset: [number, number] +}; /** * Create the quads used for rendering an icon. - * - * @param {Anchor} anchor - * @param {PositionedIcon} shapedIcon - * @param {StyleLayer} layer - * @param {boolean} alongLine Whether the icon should be placed along the line. - * @param {Shaping} shapedText Shaping for corresponding text - * @param {Object} globalProperties - * @param {Object} featureProperties - * @returns {Array} * @private */ -function getIconQuads(anchor, shapedIcon, layer, alongLine, shapedText, globalProperties, featureProperties) { +function getIconQuads(anchor: Anchor, + shapedIcon: PositionedIcon, + layer: StyleLayer, + alongLine: boolean, + shapedText: Shaping, + globalProperties: Object, + featureProperties: Object): Array { const image = shapedIcon.image; const layout = layer.layout; @@ -111,22 +116,19 @@ function getIconQuads(anchor, shapedIcon, layer, alongLine, shapedText, globalPr h: image.textureRect.h + border * 2 }; - return [new SymbolQuad(tl, tr, bl, br, textureRect, undefined, [0, 0])]; + return [{tl, tr, bl, br, tex: textureRect, writingMode: undefined, glyphOffset: [0, 0]}]; } /** * Create the quads used for rendering a text label. - * - * @param {Anchor} anchor - * @param {Shaping} shaping - * @param {StyleLayer} layer - * @param {boolean} alongLine Whether the label should be placed along the line. - * @param {Object} globalProperties - * @param {Object} featureProperties - * @returns {Array} * @private */ -function getGlyphQuads(anchor, shaping, layer, alongLine, globalProperties, featureProperties) { +function getGlyphQuads(anchor: Anchor, + shaping: Shaping, + layer: StyleLayer, + alongLine: boolean, + globalProperties: Object, + featureProperties: Object): Array { const oneEm = 24; const textRotate = layer.getLayoutValue('text-rotate', globalProperties, featureProperties) * Math.PI / 180; @@ -184,7 +186,7 @@ function getGlyphQuads(anchor, shaping, layer, alongLine, globalProperties, feat br._matMult(matrix); } - quads.push(new SymbolQuad(tl, tr, bl, br, rect, shaping.writingMode, glyphOffset)); + quads.push({tl, tr, bl, br, tex: rect, writingMode: shaping.writingMode, glyphOffset}); } return quads; diff --git a/src/symbol/shaping.js b/src/symbol/shaping.js index 04cffd1e45d..225f49260c7 100644 --- a/src/symbol/shaping.js +++ b/src/symbol/shaping.js @@ -1,41 +1,47 @@ +// @flow const scriptDetection = require('../util/script_detection'); const verticalizePunctuation = require('../util/verticalize_punctuation'); const rtlTextPlugin = require('../source/rtl_text_plugin'); +import type {SpriteAtlasElement} from './sprite_atlas'; +import type {SimpleGlyph} from './glyph_source'; + const WritingMode = { horizontal: 1, vertical: 2 }; module.exports = { - shapeText: shapeText, - shapeIcon: shapeIcon, - WritingMode: WritingMode + shapeText, + shapeIcon, + WritingMode }; - // The position of a glyph relative to the text's anchor point. -function PositionedGlyph(codePoint, x, y, glyph, angle) { - this.codePoint = codePoint; - this.x = x; - this.y = y; - this.glyph = glyph || null; - this.angle = angle; -} +export type PositionedGlyph = { + codePoint: number, + x: number, + y: number, + glyph: SimpleGlyph, + angle: number +}; // A collection of positioned glyphs and some metadata -function Shaping(positionedGlyphs, text, top, bottom, left, right, writingMode) { - this.positionedGlyphs = positionedGlyphs; - this.text = text; - this.top = top; - this.bottom = bottom; - this.left = left; - this.right = right; - this.writingMode = writingMode; -} +export type Shaping = { + positionedGlyphs: Array, + text: string, + top: number, + bottom: number, + left: number, + right: number, + writingMode: 1 | 2 +}; + +type TextAnchor = 'center' | 'left' | 'right' | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; +type TextJustify = 'left' | 'center' | 'right'; -function breakLines(text, lineBreakPoints) { +function breakLines(text: string, lineBreakPoints: Array) { const lines = []; let start = 0; for (const lineBreak of lineBreakPoints) { @@ -49,21 +55,42 @@ function breakLines(text, lineBreakPoints) { return lines; } -function shapeText(text, glyphs, maxWidth, lineHeight, textAnchor, textJustify, spacing, translate, verticalHeight, writingMode) { +function shapeText(text: string, + glyphs: {[number]: SimpleGlyph}, + maxWidth: number, + lineHeight: number, + textAnchor: TextAnchor, + textJustify: TextJustify, + spacing: number, + translate: [number, number], + verticalHeight: number, + writingMode: 1 | 2): Shaping | false { let logicalInput = text.trim(); - if (writingMode === WritingMode.vertical) logicalInput = verticalizePunctuation(logicalInput); + if (writingMode === WritingMode.vertical) { + logicalInput = verticalizePunctuation(logicalInput); + } const positionedGlyphs = []; - const shaping = new Shaping(positionedGlyphs, logicalInput, translate[1], translate[1], translate[0], translate[0], writingMode); + const shaping = { + positionedGlyphs, + text: logicalInput, + top: translate[1], + bottom: translate[1], + left: translate[0], + right: translate[0], + writingMode + }; + + let lines: Array; - let lines; - if (rtlTextPlugin.processBidirectionalText) { - lines = rtlTextPlugin.processBidirectionalText(logicalInput, determineLineBreaks(logicalInput, spacing, maxWidth, glyphs)); + const {processBidirectionalText} = rtlTextPlugin; + if (processBidirectionalText) { + lines = processBidirectionalText(logicalInput, determineLineBreaks(logicalInput, spacing, maxWidth, glyphs)); } else { lines = breakLines(logicalInput, determineLineBreaks(logicalInput, spacing, maxWidth, glyphs)); } - shapeLines(shaping, glyphs, lines, lineHeight, textAnchor, textJustify, translate, writingMode, spacing, verticalHeight); + shapeLines(shaping, glyphs, lines, lineHeight, textAnchor, textJustify, writingMode, spacing, verticalHeight); if (!positionedGlyphs.length) return false; @@ -71,39 +98,42 @@ function shapeText(text, glyphs, maxWidth, lineHeight, textAnchor, textJustify, return shaping; } -const whitespace = { - 0x09: true, // tab - 0x0a: true, // newline - 0x0b: true, // vertical tab - 0x0c: true, // form feed - 0x0d: true, // carriage return - 0x20: true, // space +const whitespace: {[number]: boolean} = { + [0x09]: true, // tab + [0x0a]: true, // newline + [0x0b]: true, // vertical tab + [0x0c]: true, // form feed + [0x0d]: true, // carriage return + [0x20]: true, // space }; -const breakable = { - 0x0a: true, // newline - 0x20: true, // space - 0x26: true, // ampersand - 0x28: true, // left parenthesis - 0x29: true, // right parenthesis - 0x2b: true, // plus sign - 0x2d: true, // hyphen-minus - 0x2f: true, // solidus - 0xad: true, // soft hyphen - 0xb7: true, // middle dot - 0x200b: true, // zero-width space - 0x2010: true, // hyphen - 0x2013: true, // en dash - 0x2027: true // interpunct +const breakable: {[number]: boolean} = { + [0x0a]: true, // newline + [0x20]: true, // space + [0x26]: true, // ampersand + [0x28]: true, // left parenthesis + [0x29]: true, // right parenthesis + [0x2b]: true, // plus sign + [0x2d]: true, // hyphen-minus + [0x2f]: true, // solidus + [0xad]: true, // soft hyphen + [0xb7]: true, // middle dot + [0x200b]: true, // zero-width space + [0x2010]: true, // hyphen + [0x2013]: true, // en dash + [0x2027]: true // interpunct // Many other characters may be reasonable breakpoints // Consider "neutral orientation" characters at scriptDetection.charHasNeutralVerticalOrientation // See https://github.com/mapbox/mapbox-gl-js/issues/3658 }; -function determineAverageLineWidth(logicalInput, spacing, maxWidth, glyphs) { +function determineAverageLineWidth(logicalInput: string, + spacing: number, + maxWidth: number, + glyphs: {[number]: SimpleGlyph}) { let totalWidth = 0; - for (const index in logicalInput) { + for (let index = 0; index < logicalInput.length; index++) { const glyph = glyphs[logicalInput.charCodeAt(index)]; if (!glyph) continue; @@ -114,7 +144,10 @@ function determineAverageLineWidth(logicalInput, spacing, maxWidth, glyphs) { return totalWidth / lineCount; } -function calculateBadness(lineWidth, targetWidth, penalty, isLastBreak) { +function calculateBadness(lineWidth: number, + targetWidth: number, + penalty: number, + isLastBreak: boolean) { const raggedness = Math.pow(lineWidth - targetWidth, 2); if (isLastBreak) { // Favor finals lines shorter than average over longer than average @@ -128,7 +161,7 @@ function calculateBadness(lineWidth, targetWidth, penalty, isLastBreak) { return raggedness + Math.abs(penalty) * penalty; } -function calculatePenalty(codePoint, nextCodePoint) { +function calculatePenalty(codePoint: number, nextCodePoint: number) { let penalty = 0; // Force break on newline if (codePoint === 0x0a) { @@ -146,13 +179,25 @@ function calculatePenalty(codePoint, nextCodePoint) { return penalty; } -function evaluateBreak(breakIndex, breakX, targetWidth, potentialBreaks, penalty, isLastBreak) { +type Break = { + index: number, + x: number, + priorBreak: ?Break, + badness: number +}; + +function evaluateBreak(breakIndex: number, + breakX: number, + targetWidth: number, + potentialBreaks: Array, + penalty: number, + isLastBreak: boolean): Break { // We could skip evaluating breaks where the line length (breakX - priorBreak.x) > maxWidth // ...but in fact we allow lines longer than maxWidth (if there's no break points) // ...and when targetWidth and maxWidth are close, strictly enforcing maxWidth can give // more lopsided results. - let bestPriorBreak = null; + let bestPriorBreak: ?Break = null; let bestBreakBadness = calculateBadness(breakX, targetWidth, penalty, isLastBreak); for (const potentialBreak of potentialBreaks) { @@ -173,14 +218,17 @@ function evaluateBreak(breakIndex, breakX, targetWidth, potentialBreaks, penalty }; } -function leastBadBreaks(lastLineBreak) { +function leastBadBreaks(lastLineBreak: ?Break): Array { if (!lastLineBreak) { return []; } return leastBadBreaks(lastLineBreak.priorBreak).concat(lastLineBreak.index); } -function determineLineBreaks(logicalInput, spacing, maxWidth, glyphs) { +function determineLineBreaks(logicalInput: string, + spacing: number, + maxWidth: number, + glyphs: {[number]: SimpleGlyph}): Array { if (!maxWidth) return []; @@ -214,8 +262,6 @@ function determineLineBreaks(logicalInput, spacing, maxWidth, glyphs) { calculatePenalty(codePoint, logicalInput.charCodeAt(i + 1)), false)); } - - } return leastBadBreaks( @@ -228,7 +274,7 @@ function determineLineBreaks(logicalInput, spacing, maxWidth, glyphs) { true)); } -function getAnchorAlignment(textAnchor) { +function getAnchorAlignment(textAnchor: TextAnchor) { let horizontalAlign = 0.5, verticalAlign = 0.5; switch (textAnchor) { @@ -257,10 +303,18 @@ function getAnchorAlignment(textAnchor) { break; } - return { horizontalAlign: horizontalAlign, verticalAlign: verticalAlign }; + return { horizontalAlign, verticalAlign }; } -function shapeLines(shaping, glyphs, lines, lineHeight, textAnchor, textJustify, translate, writingMode, spacing, verticalHeight) { +function shapeLines(shaping: Shaping, + glyphs: {[number]: SimpleGlyph}, + lines: Array, + lineHeight: number, + textAnchor: TextAnchor, + textJustify: TextJustify, + writingMode: 1 | 2, + spacing: number, + verticalHeight: number) { // the y offset *should* be part of the font metadata const yOffset = -17; @@ -274,8 +328,8 @@ function shapeLines(shaping, glyphs, lines, lineHeight, textAnchor, textJustify, textJustify === 'right' ? 1 : textJustify === 'left' ? 0 : 0.5; - for (const i in lines) { - const line = lines[i].trim(); + for (let line of lines) { + line = line.trim(); if (!line.length) { y += lineHeight; // Still need a line feed after empty line @@ -290,10 +344,10 @@ function shapeLines(shaping, glyphs, lines, lineHeight, textAnchor, textJustify, if (!glyph) continue; if (!scriptDetection.charHasUprightVerticalOrientation(codePoint) || writingMode === WritingMode.horizontal) { - positionedGlyphs.push(new PositionedGlyph(codePoint, x, y, glyph, 0)); + positionedGlyphs.push({codePoint, x, y, glyph, angle: 0}); x += glyph.advance + spacing; } else { - positionedGlyphs.push(new PositionedGlyph(codePoint, x, 0, glyph, -Math.PI / 2)); + positionedGlyphs.push({codePoint, x, y: 0, glyph, angle: -Math.PI / 2}); x += verticalHeight + spacing; } } @@ -310,21 +364,24 @@ function shapeLines(shaping, glyphs, lines, lineHeight, textAnchor, textJustify, y += lineHeight; } - const anchorPosition = getAnchorAlignment(textAnchor); - - align(positionedGlyphs, justify, anchorPosition.horizontalAlign, anchorPosition.verticalAlign, maxLineLength, lineHeight, lines.length); + const {horizontalAlign, verticalAlign} = getAnchorAlignment(textAnchor); + align(positionedGlyphs, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, lines.length); // Calculate the bounding box const height = lines.length * lineHeight; - shaping.top += -anchorPosition.verticalAlign * height; + shaping.top += -verticalAlign * height; shaping.bottom = shaping.top + height; - shaping.left += -anchorPosition.horizontalAlign * maxLineLength; + shaping.left += -horizontalAlign * maxLineLength; shaping.right = shaping.left + maxLineLength; } // justify right = 1, left = 0, center = 0.5 -function justifyLine(positionedGlyphs, glyphs, start, end, justify) { +function justifyLine(positionedGlyphs: Array, + glyphs: {[number]: SimpleGlyph}, + start: number, + end: number, + justify: 1 | 0 | 0.5) { if (!justify) return; @@ -336,7 +393,13 @@ function justifyLine(positionedGlyphs, glyphs, start, end, justify) { } } -function align(positionedGlyphs, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, lineCount) { +function align(positionedGlyphs: Array, + justify: number, + horizontalAlign: number, + verticalAlign: number, + maxLineLength: number, + lineHeight: number, + lineCount: number) { const shiftX = (justify - horizontalAlign) * maxLineLength; const shiftY = (-verticalAlign * lineCount + 0.5) * lineHeight; @@ -346,21 +409,20 @@ function align(positionedGlyphs, justify, horizontalAlign, verticalAlign, maxLin } } -function shapeIcon(image, iconOffset) { +export type PositionedIcon = { + image: any, + top: number, + bottom: number, + left: number, + right: number +}; + +function shapeIcon(image: SpriteAtlasElement, iconOffset: [number, number]): PositionedIcon { const dx = iconOffset[0]; const dy = iconOffset[1]; const x1 = dx - image.displaySize[0] / 2; const x2 = x1 + image.displaySize[0]; const y1 = dy - image.displaySize[1] / 2; const y2 = y1 + image.displaySize[1]; - - return new PositionedIcon(image, y1, y2, x1, x2); -} - -function PositionedIcon(image, top, bottom, left, right) { - this.image = image; - this.top = top; - this.bottom = bottom; - this.left = left; - this.right = right; + return {image, top: y1, bottom: y2, left: x1, right: x2}; } diff --git a/src/symbol/sprite_atlas.js b/src/symbol/sprite_atlas.js index 2990078d6e6..cfbefc16fa3 100644 --- a/src/symbol/sprite_atlas.js +++ b/src/symbol/sprite_atlas.js @@ -31,7 +31,7 @@ type Image = { sdf: boolean }; -type SpriteAtlasElement = { +export type SpriteAtlasElement = { sdf: boolean, pixelRatio: number, isNativePixelRatio: boolean, diff --git a/src/symbol/symbol_size.js b/src/symbol/symbol_size.js index 9347fcb2f15..4ae46572532 100644 --- a/src/symbol/symbol_size.js +++ b/src/symbol/symbol_size.js @@ -1,16 +1,29 @@ +// @flow const interpolate = require('../style-spec/util/interpolate'); +const {interpolationFactor} = require('../style-spec/function'); const util = require('../util/util'); -const interpolationFactor = require('../style-spec/function').interpolationFactor; const assert = require('assert'); +import type StyleLayer from '../style/style_layer'; + module.exports = { - evaluateSizeForFeature: evaluateSizeForFeature, - evaluateSizeForZoom: evaluateSizeForZoom + evaluateSizeForFeature, + evaluateSizeForZoom }; +type SizeData = { + isFeatureConstant: boolean, + isZoomConstant: boolean, + functionBase: number, + coveringZoomRange: [number, number], + coveringStopValues: [number, number], + layoutSize: number +}; -function evaluateSizeForFeature(sizeData, partiallyEvaluatedSize, symbol) { +function evaluateSizeForFeature(sizeData: SizeData, + partiallyEvaluatedSize: { uSize: number, uSizeT: number }, + symbol: { lowerSize: number, upperSize: number}) { const part = partiallyEvaluatedSize; if (sizeData.isFeatureConstant) { return part.uSize; @@ -23,7 +36,10 @@ function evaluateSizeForFeature(sizeData, partiallyEvaluatedSize, symbol) { } } -function evaluateSizeForZoom(sizeData, tr, layer, isText) { +function evaluateSizeForZoom(sizeData: SizeData, + tr: { zoom: number }, + layer: StyleLayer, + isText: boolean) { const sizeUniforms = {}; if (!sizeData.isZoomConstant && !sizeData.isFeatureConstant) { // composite function diff --git a/src/symbol/transform_text.js b/src/symbol/transform_text.js index 40f6925c333..10a391a9403 100644 --- a/src/symbol/transform_text.js +++ b/src/symbol/transform_text.js @@ -1,7 +1,10 @@ +// @flow const rtlTextPlugin = require('../source/rtl_text_plugin'); -module.exports = function(text, layer, globalProperties, featureProperties) { +import type StyleLayer from '../style/style_layer'; + +module.exports = function(text: string, layer: StyleLayer, globalProperties: Object, featureProperties: Object) { const transform = layer.getLayoutValue('text-transform', globalProperties, featureProperties); if (transform === 'uppercase') { text = text.toLocaleUpperCase(); diff --git a/src/util/classify_rings.js b/src/util/classify_rings.js index 3db6e697df6..45a6e19ae64 100644 --- a/src/util/classify_rings.js +++ b/src/util/classify_rings.js @@ -1,9 +1,12 @@ +// @flow const quickselect = require('quickselect'); const calculateSignedArea = require('./util').calculateSignedArea; +import type Point from '@mapbox/point-geometry'; + // classifies an array of rings into polygons with outer rings and holes -module.exports = function classifyRings(rings, maxRings) { +module.exports = function classifyRings(rings: Array>, maxRings: number) { const len = rings.length; if (len <= 1) return [rings]; @@ -16,7 +19,7 @@ module.exports = function classifyRings(rings, maxRings) { const area = calculateSignedArea(rings[i]); if (area === 0) continue; - rings[i].area = Math.abs(area); + (rings[i] : any).area = Math.abs(area); if (ccw === undefined) ccw = area < 0; @@ -25,7 +28,7 @@ module.exports = function classifyRings(rings, maxRings) { polygon = [rings[i]]; } else { - polygon.push(rings[i]); + (polygon : any).push(rings[i]); } } if (polygon) polygons.push(polygon); From 6a0350d61592642eae062c9da6bb3c709afc0e19 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 2 Aug 2017 10:52:33 -0700 Subject: [PATCH 2/2] Type ui directory --- build/generate-flow-typed-style-spec.js | 12 +++++- flow-typed/geojson.js | 14 +++--- flow-typed/style-spec.js | 12 +++++- src/data/feature_index.js | 2 +- src/source/query_features.js | 2 +- src/source/source.js | 1 + src/style/style.js | 10 ++--- src/ui/bind_handlers.js | 35 ++++++++------- src/ui/camera.js | 4 +- src/ui/control/attribution_control.js | 47 +++++++++++++-------- src/ui/control/fullscreen_control.js | 42 +++++++++++------- src/ui/control/geolocate_control.js | 46 ++++++++++++-------- src/ui/control/logo_control.js | 11 +++-- src/ui/control/navigation_control.js | 29 ++++++++----- src/ui/control/scale_control.js | 14 ++++-- src/ui/handler/box_zoom.js | 28 ++++++++---- src/ui/handler/dblclick_zoom.js | 17 ++++++-- src/ui/handler/drag_pan.js | 32 +++++++++----- src/ui/handler/drag_rotate.js | 33 +++++++++++---- src/ui/handler/keyboard.js | 17 ++++++-- src/ui/handler/scroll_zoom.js | 26 +++++++++--- src/ui/handler/touch_zoom_rotate.js | 27 +++++++++--- src/ui/hash.js | 9 +++- src/ui/map.js | 54 ++++++++++++++++++------ src/ui/marker.js | 45 ++++++++++++-------- src/ui/popup.js | 53 ++++++++++++++--------- src/util/find_pole_of_inaccessibility.js | 19 ++++----- 27 files changed, 430 insertions(+), 211 deletions(-) diff --git a/build/generate-flow-typed-style-spec.js b/build/generate-flow-typed-style-spec.js index 19cb9ab8bad..a56db69e5c2 100644 --- a/build/generate-flow-typed-style-spec.js +++ b/build/generate-flow-typed-style-spec.js @@ -100,7 +100,17 @@ const layerTypes = Object.keys(spec.layer.type.values); fs.writeFileSync('flow-typed/style-spec.js', `// Generated code; do not edit. Edit build/generate-flow-typed-style-spec.js instead. declare type ColorSpecification = string; -declare type FilterSpecification = Array; + +declare type FilterSpecification = + | ['has', string] + | ['!has', string] + | ['==', string, string | number | boolean] + | ['!=', string, string | number | boolean] + | ['>', string, string | number | boolean] + | ['>=', string, string | number | boolean] + | ['<', string, string | number | boolean] + | ['<=', string, string | number | boolean] + | Array; // Can't type in, !in, all, any, none -- https://github.com/facebook/flow/issues/2443 declare type TransitionSpecification = { duration?: number, diff --git a/flow-typed/geojson.js b/flow-typed/geojson.js index 9ef1f732b8c..a16013c8b1f 100644 --- a/flow-typed/geojson.js +++ b/flow-typed/geojson.js @@ -1,14 +1,14 @@ -type Position = [number, number] | [number, number, number]; +type GeoJSONPosition = [number, number] | [number, number, number]; type Geometry = { type: T, coordinates: C } -declare type GeoJSONPoint = Geometry< 'Point', Position>; -declare type GeoJSONMultiPoint = Geometry<'MultiPoint', Array>; +declare type GeoJSONPoint = Geometry< 'Point', GeoJSONPosition>; +declare type GeoJSONMultiPoint = Geometry<'MultiPoint', Array>; -declare type GeoJSONLineString = Geometry< 'LineString', Array>; -declare type GeoJSONMultiLineString = Geometry<'MultiLineString', Array>>; +declare type GeoJSONLineString = Geometry< 'LineString', Array>; +declare type GeoJSONMultiLineString = Geometry<'MultiLineString', Array>>; -declare type GeoJSONPolygon = Geometry< 'Polygon', Array>>; -declare type GeoJSONMultiPolygon = Geometry<'MultiPolygon', Array>>>; +declare type GeoJSONPolygon = Geometry< 'Polygon', Array>>; +declare type GeoJSONMultiPolygon = Geometry<'MultiPolygon', Array>>>; declare type GeoJSONGeometry = | GeoJSONPoint diff --git a/flow-typed/style-spec.js b/flow-typed/style-spec.js index ace7d7f00e4..cbc621ee2e1 100644 --- a/flow-typed/style-spec.js +++ b/flow-typed/style-spec.js @@ -1,7 +1,17 @@ // Generated code; do not edit. Edit build/generate-flow-typed-style-spec.js instead. declare type ColorSpecification = string; -declare type FilterSpecification = Array; + +declare type FilterSpecification = + | ['has', string] + | ['!has', string] + | ['==', string, string | number | boolean] + | ['!=', string, string | number | boolean] + | ['>', string, string | number | boolean] + | ['>=', string, string | number | boolean] + | ['<', string, string | number | boolean] + | ['<=', string, string | number | boolean] + | Array; // Can't type in, !in, all, any, none -- https://github.com/facebook/flow/issues/2443 declare type TransitionSpecification = { duration?: number, diff --git a/src/data/feature_index.js b/src/data/feature_index.js index d541b00a7f7..b23ab33b6c4 100644 --- a/src/data/feature_index.js +++ b/src/data/feature_index.js @@ -41,7 +41,7 @@ type QueryParameters = { tileSize: number, queryGeometry: Array>, params: { - filter: any, + filter: FilterSpecification, layers: Array, } } diff --git a/src/source/query_features.js b/src/source/query_features.js index 462e6831ca2..0f2221b4faa 100644 --- a/src/source/query_features.js +++ b/src/source/query_features.js @@ -9,7 +9,7 @@ import type Coordinate from '../geo/coordinate'; exports.rendered = function(sourceCache: SourceCache, styleLayers: {[string]: StyleLayer}, queryGeometry: Array, - params: { filter: any, layers: Array }, + params: { filter: FilterSpecification, layers: Array }, zoom: number, bearing: number) { const tilesIn = sourceCache.tilesIn(queryGeometry); diff --git a/src/source/source.js b/src/source/source.js index 0a9d562a84e..4bfa9eeef23 100644 --- a/src/source/source.js +++ b/src/source/source.js @@ -45,6 +45,7 @@ export interface Source { minzoom: number, maxzoom: number, tileSize: number, + attribution?: string, roundZoom?: boolean, reparseOverscaled?: boolean, diff --git a/src/style/style.js b/src/style/style.js index 9acb23e46d3..f4ad99be03d 100644 --- a/src/style/style.js +++ b/src/style/style.js @@ -248,7 +248,7 @@ class Style extends Evented { return ids.map((id) => this._layers[id].serialize()); } - _applyClasses(classes?: Array, options: {}) { + _applyClasses(classes?: Array, options: ?{}) { if (!this._loaded) return; classes = classes || []; @@ -334,7 +334,7 @@ class Style extends Evented { /** * Apply queued style updates in a batch */ - update(classes: Array, options: {}) { + update(classes: Array, options: ?{}) { if (!this._changed) return; const updatedIds = Object.keys(this._updatedLayers); @@ -547,7 +547,7 @@ class Style extends Evented { * @param {StyleLayer|Object} layer * @param {string=} before ID of an existing layer to insert before */ - moveLayer(id: string, before: string) { + moveLayer(id: string, before?: string) { this._checkLoaded(); this._changed = true; @@ -650,7 +650,7 @@ class Style extends Evented { this._updateLayer(layer); } - setFilter(layerId: string, filter: any) { + setFilter(layerId: string, filter: FilterSpecification) { this._checkLoaded(); const layer = this.getLayer(layerId); @@ -868,7 +868,7 @@ class Style extends Evented { return this.light.getLight(); } - setLight(lightOptions: LightSpecification, transitionOptions: {}) { + setLight(lightOptions: LightSpecification, transitionOptions?: {}) { this._checkLoaded(); const light = this.light.getLight(); diff --git a/src/ui/bind_handlers.js b/src/ui/bind_handlers.js index 3bbce748123..827043f4465 100644 --- a/src/ui/bind_handlers.js +++ b/src/ui/bind_handlers.js @@ -1,7 +1,10 @@ +// @flow const DOM = require('../util/dom'); const Point = require('@mapbox/point-geometry'); +import type Map from './map'; + const handlers = { scrollZoom: require('./handler/scroll_zoom'), boxZoom: require('./handler/box_zoom'), @@ -12,7 +15,7 @@ const handlers = { touchZoomRotate: require('./handler/touch_zoom_rotate') }; -module.exports = function bindHandlers(map, options) { +module.exports = function bindHandlers(map: Map, options: {}) { const el = map.getCanvasContainer(); let contextMenuEvent = null; let mouseDown = false; @@ -20,9 +23,9 @@ module.exports = function bindHandlers(map, options) { let tapped = null; for (const name in handlers) { - map[name] = new handlers[name](map, options); + (map : any)[name] = new handlers[name](map, options); if (options.interactive && options[name]) { - map[name].enable(options[name]); + (map : any)[name].enable(options[name]); } } @@ -38,11 +41,11 @@ module.exports = function bindHandlers(map, options) { el.addEventListener('dblclick', onDblClick, false); el.addEventListener('contextmenu', onContextMenu, false); - function onMouseOut(e) { + function onMouseOut(e: MouseEvent) { fireMouseEvent('mouseout', e); } - function onMouseDown(e) { + function onMouseDown(e: MouseEvent) { map.stop(); startPos = DOM.mousePos(el, e); fireMouseEvent('mousedown', e); @@ -50,7 +53,7 @@ module.exports = function bindHandlers(map, options) { mouseDown = true; } - function onMouseUp(e) { + function onMouseUp(e: MouseEvent) { const rotating = map.dragRotate && map.dragRotate.isActive(); if (contextMenuEvent && !rotating) { @@ -63,18 +66,18 @@ module.exports = function bindHandlers(map, options) { fireMouseEvent('mouseup', e); } - function onMouseMove(e) { + function onMouseMove(e: MouseEvent) { if (map.dragPan && map.dragPan.isActive()) return; if (map.dragRotate && map.dragRotate.isActive()) return; - let target = e.toElement || e.target; + let target: any = e.toElement || e.target; while (target && target !== el) target = target.parentNode; if (target !== el) return; fireMouseEvent('mousemove', e); } - function onTouchStart(e) { + function onTouchStart(e: TouchEvent) { map.stop(); fireTouchEvent('touchstart', e); @@ -90,15 +93,15 @@ module.exports = function bindHandlers(map, options) { } } - function onTouchMove(e) { + function onTouchMove(e: TouchEvent) { fireTouchEvent('touchmove', e); } - function onTouchEnd(e) { + function onTouchEnd(e: TouchEvent) { fireTouchEvent('touchend', e); } - function onTouchCancel(e) { + function onTouchCancel(e: TouchEvent) { fireTouchEvent('touchcancel', e); } @@ -106,20 +109,20 @@ module.exports = function bindHandlers(map, options) { tapped = null; } - function onClick(e) { + function onClick(e: MouseEvent) { const pos = DOM.mousePos(el, e); - if (pos.equals(startPos)) { + if (pos.equals((startPos : any))) { fireMouseEvent('click', e); } } - function onDblClick(e) { + function onDblClick(e: MouseEvent) { fireMouseEvent('dblclick', e); e.preventDefault(); } - function onContextMenu(e) { + function onContextMenu(e: MouseEvent) { const rotating = map.dragRotate && map.dragRotate.isActive(); if (!mouseDown && !rotating) { // Windows: contextmenu fired on mouseup, so fire event now diff --git a/src/ui/camera.js b/src/ui/camera.js index bc4a4e5669c..6ffd8e0fa25 100644 --- a/src/ui/camera.js +++ b/src/ui/camera.js @@ -386,7 +386,7 @@ class Camera extends Evented { return 0; }), ["bottom", "left", "right", "top"])) { util.warnOnce("options.padding must be a positive number, or an Object with keys 'bottom', 'left', 'right', 'top'"); - return; + return this; } bounds = LngLatBounds.convert(bounds); @@ -410,7 +410,7 @@ class Camera extends Evented { if (scaleY < 0 || scaleX < 0) { util.warnOnce('Map cannot fit within canvas with the given bounds, padding, and/or offset.'); - return; + return this; } options.center = tr.unproject(nw.add(se).div(2)); diff --git a/src/ui/control/attribution_control.js b/src/ui/control/attribution_control.js index aff45dd4da0..b059befb2b7 100644 --- a/src/ui/control/attribution_control.js +++ b/src/ui/control/attribution_control.js @@ -1,8 +1,11 @@ +// @flow const DOM = require('../../util/dom'); const util = require('../../util/util'); const config = require('../../util/config'); +import type Map from '../map'; + /** * An `AttributionControl` control presents the map's [attribution information](https://www.mapbox.com/help/attribution/). * @@ -16,8 +19,14 @@ const config = require('../../util/config'); * })); */ class AttributionControl { - - constructor(options) { + options: any; + _map: Map; + _container: HTMLElement; + _editLink: ?HTMLAnchorElement; + styleId: string; + styleOwner: string; + + constructor(options: any) { this.options = options; util.bindAll([ @@ -31,7 +40,7 @@ class AttributionControl { return 'bottom-right'; } - onAdd(map) { + onAdd(map: Map) { const compact = this.options && this.options.compact; this._map = map; @@ -56,37 +65,39 @@ class AttributionControl { } onRemove() { - this._container.parentNode.removeChild(this._container); + DOM.remove(this._container); this._map.off('sourcedata', this._updateData); this._map.off('moveend', this._updateEditLink); this._map.off('resize', this._updateCompact); - this._map = undefined; + this._map = (undefined : any); } _updateEditLink() { - if (!this._editLink) this._editLink = this._container.querySelector('.mapbox-improve-map'); + let editLink = this._editLink; + if (!editLink) { + editLink = this._editLink = (this._container.querySelector('.mapbox-improve-map'): any); + } + const params = [ {key: "owner", value: this.styleOwner}, {key: "id", value: this.styleId}, {key: "access_token", value: config.ACCESS_TOKEN} ]; - if (this._editLink) { + if (editLink) { const paramString = params.reduce((acc, next, i) => { - if (next.value !== undefined) { + if (next.value) { acc += `${next.key}=${next.value}${i < params.length - 1 ? '&' : ''}`; } return acc; }, `?`); - this._editLink.href = `https://www.mapbox.com/feedback/${paramString}${this._map._hash ? this._map._hash.getHashString(true) : ''}`; - + editLink.href = `https://www.mapbox.com/feedback/${paramString}${this._map._hash ? this._map._hash.getHashString(true) : ''}`; } } - - _updateData(e) { + _updateData(e: any) { if (e && e.sourceDataType === 'metadata') { this._updateAttributions(); this._updateEditLink(); @@ -95,10 +106,10 @@ class AttributionControl { _updateAttributions() { if (!this._map.style) return; - let attributions = []; + let attributions: Array = []; if (this._map.style.stylesheet) { - const stylesheet = this._map.style.stylesheet; + const stylesheet: any = this._map.style.stylesheet; this.styleOwner = stylesheet.owner; this.styleId = stylesheet.id; } @@ -126,9 +137,11 @@ class AttributionControl { } _updateCompact() { - const compact = this._map.getCanvasContainer().offsetWidth <= 640; - - this._container.classList[compact ? 'add' : 'remove']('mapboxgl-compact'); + if (this._map.getCanvasContainer().offsetWidth <= 640) { + this._container.classList.add('mapboxgl-compact'); + } else { + this._container.classList.remove('mapboxgl-compact'); + } } } diff --git a/src/ui/control/fullscreen_control.js b/src/ui/control/fullscreen_control.js index 6d8d9642e67..061096ec11e 100644 --- a/src/ui/control/fullscreen_control.js +++ b/src/ui/control/fullscreen_control.js @@ -1,8 +1,11 @@ +// @flow const DOM = require('../../util/dom'); const util = require('../../util/util'); const window = require('../../util/window'); +import type Map from '../map'; + /** * A `FullscreenControl` control contains a button for toggling the map in and out of fullscreen mode. * @@ -13,6 +16,13 @@ const window = require('../../util/window'); */ class FullscreenControl { + _map: Map; + _mapContainer: HTMLElement; + _container: HTMLElement; + _fullscreen: boolean; + _fullscreenchange: string; + _fullscreenButton: HTMLElement; + _className: string; constructor() { this._fullscreen = false; @@ -32,7 +42,7 @@ class FullscreenControl { this._className = 'mapboxgl-ctrl'; } - onAdd(map) { + onAdd(map: Map) { this._map = map; this._mapContainer = this._map.getContainer(); this._container = DOM.create('div', `${this._className} mapboxgl-ctrl-group`); @@ -46,17 +56,17 @@ class FullscreenControl { } onRemove() { - this._container.parentNode.removeChild(this._container); - this._map = null; + DOM.remove(this._container); + this._map = (null : any); window.document.removeEventListener(this._fullscreenchange, this._changeIcon); } _checkFullscreenSupport() { return !!( window.document.fullscreenEnabled || - window.document.mozFullscreenEnabled || - window.document.msFullscreenEnabled || - window.document.webkitFullscreenEnabled + (window.document : any).mozFullscreenEnabled || + (window.document : any).msFullscreenEnabled || + (window.document : any).webkitFullscreenEnabled ); } @@ -75,9 +85,9 @@ class FullscreenControl { _changeIcon() { const fullscreenElement = window.document.fullscreenElement || - window.document.mozFullScreenElement || - window.document.webkitFullscreenElement || - window.document.msFullscreenElement; + (window.document : any).mozFullScreenElement || + (window.document : any).webkitFullscreenElement || + (window.document : any).msFullscreenElement; if ((fullscreenElement === this._mapContainer) !== this._fullscreen) { this._fullscreen = !this._fullscreen; @@ -89,22 +99,22 @@ class FullscreenControl { _onClickFullscreen() { if (this._isFullscreen()) { if (window.document.exitFullscreen) { - window.document.exitFullscreen(); + (window.document : any).exitFullscreen(); } else if (window.document.mozCancelFullScreen) { - window.document.mozCancelFullScreen(); + (window.document : any).mozCancelFullScreen(); } else if (window.document.msExitFullscreen) { - window.document.msExitFullscreen(); + (window.document : any).msExitFullscreen(); } else if (window.document.webkitCancelFullScreen) { - window.document.webkitCancelFullScreen(); + (window.document : any).webkitCancelFullScreen(); } } else if (this._mapContainer.requestFullscreen) { this._mapContainer.requestFullscreen(); } else if (this._mapContainer.mozRequestFullScreen) { - this._mapContainer.mozRequestFullScreen(); + (this._mapContainer : any).mozRequestFullScreen(); } else if (this._mapContainer.msRequestFullscreen) { - this._mapContainer.msRequestFullscreen(); + (this._mapContainer : any).msRequestFullscreen(); } else if (this._mapContainer.webkitRequestFullscreen) { - this._mapContainer.webkitRequestFullscreen(); + (this._mapContainer : any).webkitRequestFullscreen(); } } } diff --git a/src/ui/control/geolocate_control.js b/src/ui/control/geolocate_control.js index 583466cc6aa..e94ba7af152 100644 --- a/src/ui/control/geolocate_control.js +++ b/src/ui/control/geolocate_control.js @@ -1,3 +1,4 @@ +// @flow const Evented = require('../../util/evented'); const DOM = require('../../util/dom'); @@ -7,6 +8,8 @@ const assert = require('assert'); const LngLat = require('../../geo/lng_lat'); const Marker = require('../marker'); +import type Map from '../map'; + const defaultOptions = { positionOptions: { enableHighAccuracy: false, @@ -75,8 +78,18 @@ function checkGeolocationSupport(callback) { * })); */ class GeolocateControl extends Evented { - - constructor(options) { + _map: Map; + options: any; + _container: HTMLElement; + _dotElement: HTMLElement; + _geolocateButton: HTMLElement; + _geolocationWatchID: number; + _timeoutId: ?number; + _watchState: string; + _lastKnownPosition: any; + _userLocationDotMarker: Marker; + + constructor(options: any) { super(); this.options = util.extend({}, defaultOptions, options); @@ -91,7 +104,7 @@ class GeolocateControl extends Evented { ], this); } - onAdd(map) { + onAdd(map: Map) { this._map = map; this._container = DOM.create('div', `${className} ${className}-group`); checkGeolocationSupport(this._setupUI); @@ -102,7 +115,7 @@ class GeolocateControl extends Evented { // clear the geolocation watch if exists if (this._geolocationWatchID !== undefined) { window.navigator.geolocation.clearWatch(this._geolocationWatchID); - this._geolocationWatchID = undefined; + this._geolocationWatchID = (undefined : any); } // clear the marker from the map @@ -110,11 +123,11 @@ class GeolocateControl extends Evented { this._userLocationDotMarker.remove(); } - this._container.parentNode.removeChild(this._container); - this._map = undefined; + DOM.remove(this._container); + this._map = (undefined : any); } - _onSuccess(position) { + _onSuccess(position: Position) { if (this.options.trackUserLocation) { // keep a record of the position so that if the state is BACKGROUND and the user // clicks the button, we can move to ACTIVE_LOCK immediately without waiting for @@ -161,7 +174,7 @@ class GeolocateControl extends Evented { this._finish(); } - _updateCamera(position) { + _updateCamera(position: Position) { const center = new LngLat(position.coords.longitude, position.coords.latitude); const radius = position.coords.accuracy; @@ -170,7 +183,7 @@ class GeolocateControl extends Evented { }); } - _updateMarker(position) { + _updateMarker(position: ?Position) { if (position) { this._userLocationDotMarker.setLngLat([position.coords.longitude, position.coords.latitude]).addTo(this._map); } else { @@ -178,7 +191,7 @@ class GeolocateControl extends Evented { } } - _onError(error) { + _onError(error: PositionError) { if (this.options.trackUserLocation) { if (error.code === 1) { // PERMISSION_DENIED @@ -235,10 +248,9 @@ class GeolocateControl extends Evented { this._timeoutId = undefined; } - _setupUI(supported) { + _setupUI(supported: boolean) { if (supported === false) return; - this._container.addEventListener('contextmenu', - e => e.preventDefault()); + this._container.addEventListener('contextmenu', (e: MouseEvent) => e.preventDefault()); this._geolocateButton = DOM.create('button', `${className}-icon ${className}-geolocate`, this._container); @@ -246,7 +258,7 @@ class GeolocateControl extends Evented { this._geolocateButton.setAttribute('aria-label', 'Geolocate'); if (this.options.trackUserLocation) { - this._geolocateButton.setAttribute('aria-pressed', false); + this._geolocateButton.setAttribute('aria-pressed', 'false'); this._watchState = 'OFF'; } @@ -347,7 +359,7 @@ class GeolocateControl extends Evented { // enable watchPosition since watchState is not OFF and there is no watchPosition already running this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-waiting'); - this._geolocateButton.setAttribute('aria-pressed', true); + this._geolocateButton.setAttribute('aria-pressed', 'true'); this._geolocationWatchID = window.navigator.geolocation.watchPosition( this._onSuccess, this._onError, this.options.positionOptions); @@ -365,9 +377,9 @@ class GeolocateControl extends Evented { _clearWatch() { window.navigator.geolocation.clearWatch(this._geolocationWatchID); - this._geolocationWatchID = undefined; + this._geolocationWatchID = (undefined : any); this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-waiting'); - this._geolocateButton.setAttribute('aria-pressed', false); + this._geolocateButton.setAttribute('aria-pressed', 'false'); if (this.options.showUserLocation) { this._updateMarker(null); diff --git a/src/ui/control/logo_control.js b/src/ui/control/logo_control.js index f49ccdd878a..07b39ea6494 100644 --- a/src/ui/control/logo_control.js +++ b/src/ui/control/logo_control.js @@ -1,7 +1,10 @@ +// @flow const DOM = require('../../util/dom'); const util = require('../../util/util'); +import type Map from '../map'; + /** * A `LogoControl` is a control that adds the Mapbox watermark * to the map as required by the [terms of service](https://www.mapbox.com/tos/) for Mapbox @@ -12,12 +15,14 @@ const util = require('../../util/util'); **/ class LogoControl { + _map: Map; + _container: HTMLElement; constructor() { util.bindAll(['_updateLogo'], this); } - onAdd(map) { + onAdd(map: Map) { this._map = map; this._container = DOM.create('div', 'mapboxgl-ctrl'); const anchor = DOM.create('a', 'mapboxgl-ctrl-logo'); @@ -33,7 +38,7 @@ class LogoControl { } onRemove() { - this._container.parentNode.removeChild(this._container); + DOM.remove(this._container); this._map.off('sourcedata', this._updateLogo); } @@ -41,7 +46,7 @@ class LogoControl { return 'bottom-left'; } - _updateLogo(e) { + _updateLogo(e: any) { if (!e || e.sourceDataType === 'metadata') { this._container.style.display = this._logoRequired() ? 'block' : 'none'; } diff --git a/src/ui/control/navigation_control.js b/src/ui/control/navigation_control.js index f780c2b06f7..f341b78bbfe 100644 --- a/src/ui/control/navigation_control.js +++ b/src/ui/control/navigation_control.js @@ -1,8 +1,11 @@ +// @flow const DOM = require('../../util/dom'); const window = require('../../util/window'); const util = require('../../util/util'); +import type Map from '../map'; + const className = 'mapboxgl-ctrl'; /** @@ -16,6 +19,12 @@ const className = 'mapboxgl-ctrl'; * @see [Add a third party vector tile source](https://www.mapbox.com/mapbox-gl-js/example/third-party/) */ class NavigationControl { + _map: Map; + _container: HTMLElement; + _zoomInButton: HTMLElement; + _zoomOutButton: HTMLElement; + _compass: HTMLElement; + _compassArrow: HTMLElement; constructor() { util.bindAll([ @@ -28,7 +37,7 @@ class NavigationControl { this._compassArrow.style.transform = rotate; } - onAdd(map) { + onAdd(map: Map) { this._map = map; this._container = DOM.create('div', `${className} ${className}-group`, map.getContainer()); this._container.addEventListener('contextmenu', this._onContextMenu.bind(this)); @@ -40,8 +49,8 @@ class NavigationControl { this._compassArrow = DOM.create('span', `${className}-compass-arrow`, this._compass); this._compass.addEventListener('mousedown', this._onCompassDown.bind(this)); - this._onCompassMove = this._onCompassMove.bind(this); - this._onCompassUp = this._onCompassUp.bind(this); + + util.bindAll(['_onCompassMove', '_onCompassUp'], this); this._map.on('rotate', this._rotateCompassArrow); this._rotateCompassArrow(); @@ -50,16 +59,16 @@ class NavigationControl { } onRemove() { - this._container.parentNode.removeChild(this._container); + DOM.remove(this._container); this._map.off('rotate', this._rotateCompassArrow); - this._map = undefined; + this._map = (undefined : any); } - _onContextMenu(e) { + _onContextMenu(e: MouseEvent) { e.preventDefault(); } - _onCompassDown(e) { + _onCompassDown(e: MouseEvent) { if (e.button !== 0) return; DOM.disableDrag(); @@ -70,14 +79,14 @@ class NavigationControl { e.stopPropagation(); } - _onCompassMove(e) { + _onCompassMove(e: MouseEvent) { if (e.button !== 0) return; this._map.getCanvasContainer().dispatchEvent(copyMouseEvent(e)); e.stopPropagation(); } - _onCompassUp(e) { + _onCompassUp(e: MouseEvent) { if (e.button !== 0) return; window.document.removeEventListener('mousemove', this._onCompassMove); @@ -88,7 +97,7 @@ class NavigationControl { e.stopPropagation(); } - _createButton(className, ariaLabel, fn) { + _createButton(className: string, ariaLabel: string, fn: () => mixed) { const a = DOM.create('button', className, this._container); a.type = 'button'; a.setAttribute('aria-label', ariaLabel); diff --git a/src/ui/control/scale_control.js b/src/ui/control/scale_control.js index c18516311c8..99b03e3a78b 100644 --- a/src/ui/control/scale_control.js +++ b/src/ui/control/scale_control.js @@ -1,7 +1,10 @@ +// @flow const DOM = require('../../util/dom'); const util = require('../../util/util'); +import type Map from '../map'; + /** * A `ScaleControl` control displays the ratio of a distance on the map to the corresponding distance on the ground. * @@ -16,8 +19,11 @@ const util = require('../../util/util'); * })); */ class ScaleControl { + _map: Map; + _container: HTMLElement; + options: any; - constructor(options) { + constructor(options: any) { this.options = options; util.bindAll([ @@ -33,7 +39,7 @@ class ScaleControl { updateScale(this._map, this._container, this.options); } - onAdd(map) { + onAdd(map: Map) { this._map = map; this._container = DOM.create('div', 'mapboxgl-ctrl mapboxgl-ctrl-scale', map.getContainer()); @@ -44,9 +50,9 @@ class ScaleControl { } onRemove() { - this._container.parentNode.removeChild(this._container); + DOM.remove(this._container); this._map.off('move', this._onMove); - this._map = undefined; + this._map = (undefined : any); } } diff --git a/src/ui/handler/box_zoom.js b/src/ui/handler/box_zoom.js index f894f7c2153..0a2586f8521 100644 --- a/src/ui/handler/box_zoom.js +++ b/src/ui/handler/box_zoom.js @@ -1,9 +1,12 @@ +// @flow const DOM = require('../../util/dom'); const LngLatBounds = require('../../geo/lng_lat_bounds'); const util = require('../../util/util'); const window = require('../../util/window'); +import type Map from '../map'; + /** * The `BoxZoomHandler` allows the user to zoom the map to fit within a bounding box. * The bounding box is defined by clicking and holding `shift` while dragging the cursor. @@ -11,8 +14,15 @@ const window = require('../../util/window'); * @param {Map} map The Mapbox GL JS map to add the handler to. */ class BoxZoomHandler { - - constructor(map) { + _map: Map; + _el: HTMLElement; + _container: HTMLElement; + _enabled: boolean; + _active: boolean; + _startPos: any; + _box: HTMLElement; + + constructor(map: Map) { this._map = map; this._el = map.getCanvasContainer(); this._container = map.getContainer(); @@ -74,7 +84,7 @@ class BoxZoomHandler { this._enabled = false; } - _onMouseDown(e) { + _onMouseDown(e: MouseEvent) { if (!(e.shiftKey && e.button === 0)) return; window.document.addEventListener('mousemove', this._onMouseMove, false); @@ -86,7 +96,7 @@ class BoxZoomHandler { this._active = true; } - _onMouseMove(e) { + _onMouseMove(e: MouseEvent) { const p0 = this._startPos, p1 = DOM.mousePos(this._el, e); @@ -107,7 +117,7 @@ class BoxZoomHandler { this._box.style.height = `${maxY - minY}px`; } - _onMouseUp(e) { + _onMouseUp(e: MouseEvent) { if (e.button !== 0) return; const p0 = this._startPos, @@ -127,7 +137,7 @@ class BoxZoomHandler { } } - _onKeyDown(e) { + _onKeyDown(e: KeyboardEvent) { if (e.keyCode === 27) { this._finish(); this._fireEvent('boxzoomcancel', e); @@ -144,14 +154,14 @@ class BoxZoomHandler { this._container.classList.remove('mapboxgl-crosshair'); if (this._box) { - this._box.parentNode.removeChild(this._box); - this._box = null; + DOM.remove(this._box); + this._box = (null : any); } DOM.enableDrag(); } - _fireEvent(type, e) { + _fireEvent(type: string, e: Event) { return this._map.fire(type, { originalEvent: e }); } } diff --git a/src/ui/handler/dblclick_zoom.js b/src/ui/handler/dblclick_zoom.js index f6b6925e1d2..e7a90808625 100644 --- a/src/ui/handler/dblclick_zoom.js +++ b/src/ui/handler/dblclick_zoom.js @@ -1,3 +1,8 @@ +// @flow + +const util = require('../../util/util'); + +import type Map from '../map'; /** * The `DoubleClickZoomHandler` allows the user to zoom the map at a point by @@ -6,9 +11,15 @@ * @param {Map} map The Mapbox GL JS map to add the handler to. */ class DoubleClickZoomHandler { - constructor(map) { + _map: Map; + _enabled: boolean; + + constructor(map: Map) { this._map = map; - this._onDblClick = this._onDblClick.bind(this); + + util.bindAll([ + '_onDblClick', + ], this); } /** @@ -44,7 +55,7 @@ class DoubleClickZoomHandler { this._enabled = false; } - _onDblClick(e) { + _onDblClick(e: any) { this._map.zoomTo( this._map.getZoom() + (e.originalEvent.shiftKey ? -1 : 1), {around: e.lngLat}, diff --git a/src/ui/handler/drag_pan.js b/src/ui/handler/drag_pan.js index 20c6d7c98c5..ebd24d6154c 100644 --- a/src/ui/handler/drag_pan.js +++ b/src/ui/handler/drag_pan.js @@ -1,8 +1,12 @@ +// @flow const DOM = require('../../util/dom'); const util = require('../../util/util'); const window = require('../../util/window'); +import type Map from '../map'; +import type Point from '@mapbox/point-geometry'; + const inertiaLinearity = 0.3, inertiaEasing = util.bezier(0, 0, inertiaLinearity, 1), inertiaMaxSpeed = 1400, // px/s @@ -15,7 +19,15 @@ const inertiaLinearity = 0.3, * @param {Map} map The Mapbox GL JS map to add the handler to. */ class DragPanHandler { - constructor(map) { + _map: Map; + _el: HTMLElement; + _enabled: boolean; + _active: boolean; + _pos: Point; + _startPos: Point; + _inertia: Array<[number, Point]>; + + constructor(map: Map) { this._map = map; this._el = map.getCanvasContainer(); @@ -74,7 +86,7 @@ class DragPanHandler { this._enabled = false; } - _onDown(e) { + _onDown(e: MouseEvent | TouchEvent) { if (this._ignoreEvent(e)) return; if (this.isActive()) return; @@ -93,7 +105,7 @@ class DragPanHandler { this._inertia = [[Date.now(), this._pos]]; } - _onMove(e) { + _onMove(e: MouseEvent | TouchEvent) { if (this._ignoreEvent(e)) return; if (!this.isActive()) { @@ -120,7 +132,7 @@ class DragPanHandler { e.preventDefault(); } - _onUp(e) { + _onUp(e: MouseEvent | TouchEvent) { if (!this.isActive()) return; this._active = false; @@ -167,26 +179,26 @@ class DragPanHandler { }, { originalEvent: e }); } - _onMouseUp(e) { + _onMouseUp(e: MouseEvent) { if (this._ignoreEvent(e)) return; this._onUp(e); window.document.removeEventListener('mousemove', this._onMove); window.document.removeEventListener('mouseup', this._onMouseUp); - window.removeEventListener('blur', this._onMouseUp); + window.removeEventListener('blur', (this._onMouseUp : any)); } - _onTouchEnd(e) { + _onTouchEnd(e: TouchEvent) { if (this._ignoreEvent(e)) return; this._onUp(e); window.document.removeEventListener('touchmove', this._onMove); window.document.removeEventListener('touchend', this._onTouchEnd); } - _fireEvent(type, e) { + _fireEvent(type: string, e: Event) { return this._map.fire(type, { originalEvent: e }); } - _ignoreEvent(e) { + _ignoreEvent(e: any) { const map = this._map; if (map.boxZoom && map.boxZoom.isActive()) return true; @@ -197,7 +209,7 @@ class DragPanHandler { if (e.ctrlKey) return true; const buttons = 1, // left button button = 0; // left button - return (e.type === 'mousemove' ? e.buttons & buttons === 0 : e.button && e.button !== button); + return (e.type === 'mousemove' ? (e.buttons & buttons) === 0 : e.button && e.button !== button); } } diff --git a/src/ui/handler/drag_rotate.js b/src/ui/handler/drag_rotate.js index f6276badb52..b814b662e01 100644 --- a/src/ui/handler/drag_rotate.js +++ b/src/ui/handler/drag_rotate.js @@ -1,8 +1,12 @@ +// @flow const DOM = require('../../util/dom'); const util = require('../../util/util'); const window = require('../../util/window'); +import type Map from '../map'; +import type Point from '@mapbox/point-geometry'; + const inertiaLinearity = 0.25, inertiaEasing = util.bezier(0, 0, inertiaLinearity, 1), inertiaMaxSpeed = 180, // deg/s @@ -19,7 +23,18 @@ const inertiaLinearity = 0.25, * @param {bool} [options.pitchWithRotate=true] Control the map pitch in addition to the bearing */ class DragRotateHandler { - constructor(map, options) { + _map: Map; + _el: HTMLElement; + _enabled: boolean; + _active: boolean; + _bearingSnap: number; + _pitchWithRotate: boolean; + _pos: Point; + _startPos: Point; + _inertia: Array<[number, number]>; + _center: Point; + + constructor(map: Map, options: any) { this._map = map; this._el = map.getCanvasContainer(); this._bearingSnap = options.bearingSnap; @@ -75,7 +90,7 @@ class DragRotateHandler { this._enabled = false; } - _onDown(e) { + _onDown(e: MouseEvent) { if (this._ignoreEvent(e)) return; if (this.isActive()) return; @@ -92,7 +107,7 @@ class DragRotateHandler { e.preventDefault(); } - _onMove(e) { + _onMove(e: MouseEvent) { if (this._ignoreEvent(e)) return; if (!this.isActive()) { @@ -132,11 +147,11 @@ class DragRotateHandler { this._pos = p2; } - _onUp(e) { + _onUp(e: MouseEvent) { if (this._ignoreEvent(e)) return; window.document.removeEventListener('mousemove', this._onMove); window.document.removeEventListener('mouseup', this._onUp); - window.removeEventListener('blur', this._onUp); + window.removeEventListener('blur', (this._onUp : any)); if (!this.isActive()) return; @@ -197,11 +212,11 @@ class DragRotateHandler { }, { originalEvent: e }); } - _fireEvent(type, e) { + _fireEvent(type: string, e: Event) { return this._map.fire(type, { originalEvent: e }); } - _ignoreEvent(e) { + _ignoreEvent(e: any) { const map = this._map; if (map.boxZoom && map.boxZoom.isActive()) return true; @@ -212,14 +227,14 @@ class DragRotateHandler { const buttons = (e.ctrlKey ? 1 : 2), // ? ctrl+left button : right button button = (e.ctrlKey ? 0 : 2); // ? ctrl+left button : right button let eventButton = e.button; - if (typeof InstallTrigger !== 'undefined' && e.button === 2 && e.ctrlKey && + if (typeof window.InstallTrigger !== 'undefined' && e.button === 2 && e.ctrlKey && window.navigator.platform.toUpperCase().indexOf('MAC') >= 0) { // Fix for https://github.com/mapbox/mapbox-gl-js/issues/3131: // Firefox (detected by InstallTrigger) on Mac determines e.button = 2 when // using Control + left click eventButton = 0; } - return (e.type === 'mousemove' ? e.buttons & buttons === 0 : !this.isActive() && eventButton !== button); + return (e.type === 'mousemove' ? (e.buttons & buttons) === 0 : !this.isActive() && eventButton !== button); } } diff --git a/src/ui/handler/keyboard.js b/src/ui/handler/keyboard.js index 2bd8ed624b3..623c54a41e4 100644 --- a/src/ui/handler/keyboard.js +++ b/src/ui/handler/keyboard.js @@ -1,3 +1,8 @@ +// @flow + +const util = require('../../util/util'); + +import type Map from '../map'; const panStep = 100, bearingStep = 15, @@ -20,11 +25,17 @@ const panStep = 100, * @param {Map} map The Mapbox GL JS map to add the handler to. */ class KeyboardHandler { - constructor(map) { + _map: Map; + _el: HTMLElement; + _enabled: boolean; + + constructor(map: Map) { this._map = map; this._el = map.getCanvasContainer(); - this._onKeyDown = this._onKeyDown.bind(this); + util.bindAll([ + '_onKeyDown' + ], this); } /** @@ -60,7 +71,7 @@ class KeyboardHandler { this._enabled = false; } - _onKeyDown(e) { + _onKeyDown(e: KeyboardEvent) { if (e.altKey || e.ctrlKey || e.metaKey) return; let zoomDir = 0; diff --git a/src/ui/handler/scroll_zoom.js b/src/ui/handler/scroll_zoom.js index 2db5675f531..85741c5e07c 100644 --- a/src/ui/handler/scroll_zoom.js +++ b/src/ui/handler/scroll_zoom.js @@ -1,9 +1,13 @@ +// @flow const DOM = require('../../util/dom'); const util = require('../../util/util'); const browser = require('../../util/browser'); const window = require('../../util/window'); +import type Map from '../map'; +import type Point from '@mapbox/point-geometry'; + const ua = window.navigator.userAgent.toLowerCase(), firefox = ua.indexOf('firefox') !== -1, safari = ua.indexOf('safari') !== -1 && ua.indexOf('chrom') === -1; @@ -14,7 +18,17 @@ const ua = window.navigator.userAgent.toLowerCase(), * @param {Map} map The Mapbox GL JS map to add the handler to. */ class ScrollZoomHandler { - constructor(map) { + _map: Map; + _el: HTMLElement; + _enabled: boolean; + _aroundCenter: boolean; + _time: number; + _pos: Point; + _type: 'wheel' | 'trackpad' | null; + _lastValue: number; + _timeout: ?number; + + constructor(map: Map) { this._map = map; this._el = map.getCanvasContainer(); @@ -44,7 +58,7 @@ class ScrollZoomHandler { * @example * map.scrollZoom.enable({ around: 'center' }) */ - enable(options) { + enable(options: any) { if (this.isEnabled()) return; this._el.addEventListener('wheel', this._onWheel, false); this._el.addEventListener('mousewheel', this._onWheel, false); @@ -65,8 +79,8 @@ class ScrollZoomHandler { this._enabled = false; } - _onWheel(e) { - let value; + _onWheel(e: any) { + let value = 0; if (e.type === 'wheel') { value = e.deltaY; @@ -129,7 +143,7 @@ class ScrollZoomHandler { this._zoom(-this._lastValue); } - _zoom(delta, e) { + _zoom(delta: number, e?: Event) { if (delta === 0) return; const map = this._map; @@ -137,7 +151,7 @@ class ScrollZoomHandler { let scale = 2 / (1 + Math.exp(-Math.abs(delta / 100))); if (delta < 0 && scale !== 0) scale = 1 / scale; - const fromScale = map.ease ? map.ease.to : map.transform.scale, + const fromScale = map.ease ? (map.ease : any).to : map.transform.scale, targetZoom = map.transform.scaleZoom(fromScale * scale); map.zoomTo(targetZoom, { diff --git a/src/ui/handler/touch_zoom_rotate.js b/src/ui/handler/touch_zoom_rotate.js index 3fa1007cbbf..423291acaaa 100644 --- a/src/ui/handler/touch_zoom_rotate.js +++ b/src/ui/handler/touch_zoom_rotate.js @@ -1,8 +1,12 @@ +// @flow const DOM = require('../../util/dom'); const util = require('../../util/util'); const window = require('../../util/window'); +import type Map from '../map'; +import type Point from '@mapbox/point-geometry'; + const inertiaLinearity = 0.15, inertiaEasing = util.bezier(0, 0, inertiaLinearity, 1), inertiaDeceleration = 12, // scale / s^2 @@ -17,7 +21,18 @@ const inertiaLinearity = 0.15, * @param {Map} map The Mapbox GL JS map to add the handler to. */ class TouchZoomRotateHandler { - constructor(map) { + _map: Map; + _el: HTMLElement; + _enabled: boolean; + _aroundCenter: boolean; + _rotationDisabled: boolean; + _startVec: Point; + _startScale: number; + _startBearing: number; + _gestureIntent: 'rotate' | 'zoom' | void; + _inertia: Array<[number, number, Point]>; + + constructor(map: Map) { this._map = map; this._el = map.getCanvasContainer(); @@ -48,7 +63,7 @@ class TouchZoomRotateHandler { * @example * map.touchZoomRotate.enable({ around: 'center' }); */ - enable(options) { + enable(options: any) { if (this.isEnabled()) return; this._el.classList.add('mapboxgl-touch-zoom-rotate'); this._el.addEventListener('touchstart', this._onStart, false); @@ -91,7 +106,7 @@ class TouchZoomRotateHandler { this._rotationDisabled = false; } - _onStart(e) { + _onStart(e: TouchEvent) { if (e.touches.length !== 2) return; const p0 = DOM.mousePos(this._el, e.touches[0]), @@ -107,7 +122,7 @@ class TouchZoomRotateHandler { window.document.addEventListener('touchend', this._onEnd, false); } - _onMove(e) { + _onMove(e: TouchEvent) { if (e.touches.length !== 2) return; const p0 = DOM.mousePos(this._el, e.touches[0]), @@ -137,7 +152,7 @@ class TouchZoomRotateHandler { } } else { - const param = { duration: 0, around: map.unproject(p) }; + const param: Object = { duration: 0, around: map.unproject(p) }; if (this._gestureIntent === 'rotate') { param.bearing = this._startBearing + bearing; @@ -156,7 +171,7 @@ class TouchZoomRotateHandler { e.preventDefault(); } - _onEnd(e) { + _onEnd(e: TouchEvent) { window.document.removeEventListener('touchmove', this._onMove); window.document.removeEventListener('touchend', this._onEnd); this._drainInertiaBuffer(); diff --git a/src/ui/hash.js b/src/ui/hash.js index f78cb8565ef..f79a0e4dad4 100644 --- a/src/ui/hash.js +++ b/src/ui/hash.js @@ -1,7 +1,10 @@ +// @flow const util = require('../util/util'); const window = require('../util/window'); +import type Map from './map'; + /* * Adds the map's position to its page's location hash. * Passed as an option to the map object. @@ -9,6 +12,8 @@ const window = require('../util/window'); * @returns {Hash} `this` */ class Hash { + _map: Map; + constructor() { util.bindAll([ '_onHashChange', @@ -22,7 +27,7 @@ class Hash { * @param {Object} map * @returns {Hash} `this` */ - addTo(map) { + addTo(map: Map) { this._map = map; window.addEventListener('hashchange', this._onHashChange, false); this._map.on('moveend', this._updateHash); @@ -41,7 +46,7 @@ class Hash { return this; } - getHashString(mapFeedback) { + getHashString(mapFeedback?: boolean) { const center = this._map.getCenter(), zoom = Math.round(this._map.getZoom() * 100) / 100, precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)), diff --git a/src/ui/map.js b/src/ui/map.js index 2e2da2b9c39..9bb9df763fd 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -28,10 +28,22 @@ import type {LngLatBoundsLike} from '../geo/lng_lat_bounds'; import type {RequestParameters} from '../util/ajax'; import type {StyleOptions} from '../style/style'; +import type ScrollZoomHandler from './handler/scroll_zoom'; +import type BoxZoomHandler from './handler/box_zoom'; +import type DragRotateHandler from './handler/drag_rotate'; +import type DragPanHandler from './handler/drag_pan'; +import type KeyboardHandler from './handler/keyboard'; +import type DoubleClickZoomHandler from './handler/dblclick_zoom'; +import type TouchZoomRotateHandler from './handler/touch_zoom_rotate'; + +type ControlPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; + /* eslint-disable no-use-before-define */ type IControl = { - onAdd(map: Map): void; + onAdd(map: Map): HTMLElement; onRemove(map: Map): void; + + +getDefaultPosition?: () => ControlPosition; } /* eslint-enable no-use-before-define */ @@ -45,7 +57,7 @@ type MapOptions = { bearingSnap?: number, classes?: Array, attributionControl?: boolean, - logoPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right', + logoPosition?: ControlPosition, failIfMajorPerformanceCaveat?: boolean, preserveDrawingBuffer?: boolean, refreshExpiredTiles?: boolean, @@ -93,7 +105,15 @@ type MapEvent = | 'data' | 'dataloading' | 'rotate' - | 'pitch'; + | 'pitch' + | 'resize' + | 'error' + | 'data' + | 'styledata' + | 'sourcedata' + | 'dataloading' + | 'styledataloading' + | 'sourcedataloading'; const defaultMinZoom = 0; const defaultMaxZoom = 22; @@ -226,7 +246,7 @@ const defaultOptions = { * @see [Display a map](https://www.mapbox.com/mapbox-gl-js/examples/) */ class Map extends Camera { - style: any; + style: Style; painter: Painter; animationLoop: AnimationLoop; @@ -256,6 +276,14 @@ class Map extends Camera { _hash: Hash; _delegatedListeners: any; + scrollZoom: ScrollZoomHandler; + boxZoom: BoxZoomHandler; + dragRotate: DragRotateHandler; + dragPan: DragPanHandler; + keyboard: KeyboardHandler; + doubleClickZoom: DoubleClickZoomHandler; + touchZoomRotate: TouchZoomRotateHandler; + constructor(options: MapOptions) { options = util.extend({}, defaultOptions, options); @@ -363,7 +391,7 @@ class Map extends Camera { * @returns {Map} `this` * @see [Display map navigation controls](https://www.mapbox.com/mapbox-gl-js/example/navigation/) */ - addControl(control, position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right') { + addControl(control: IControl, position?: ControlPosition) { if (position === undefined && control.getDefaultPosition) { position = control.getDefaultPosition(); } @@ -1004,7 +1032,7 @@ class Map extends Camera { } if (!style) { - this.style = null; + this.style = (null : any); return this; } else if (style instanceof Style) { this.style = style; @@ -1055,7 +1083,7 @@ class Map extends Camera { * @see [Style circles using data-driven styling](https://www.mapbox.com/mapbox-gl-js/example/data-driven-circle-colors/) * @see [Set a point after Geocoder result](https://www.mapbox.com/mapbox-gl-js/example/point-from-geocoder-result/) */ - addSource(id: string, source: any) { + addSource(id: string, source: SourceSpecification) { this.style.addSource(id, source); this._update(true); return this; @@ -1156,7 +1184,7 @@ class Map extends Camera { image: HTMLImageElement | $ArrayBufferView, options?: {width: number, height: number, pixelRatio: number} ) { - this.style.spriteAtlas.addImage(name, image, options); + this.style.spriteAtlas.addImage(name, image, (options : any)); } /** @@ -1195,7 +1223,7 @@ class Map extends Camera { * @see [Add a vector tile source](https://www.mapbox.com/mapbox-gl-js/example/vector-source/) * @see [Add a WMS source](https://www.mapbox.com/mapbox-gl-js/example/wms/) */ - addLayer(layer: any, before?: string) { + addLayer(layer: LayerSpecification, before?: string) { this.style.addLayer(layer, before); this._update(true); return this; @@ -1255,7 +1283,7 @@ class Map extends Camera { * @see [Highlight features containing similar data](https://www.mapbox.com/mapbox-gl-js/example/query-similar-features/) * @see [Create a timeline animation](https://www.mapbox.com/mapbox-gl-js/example/timeline-animation/) */ - setFilter(layer: string, filter: Array) { + setFilter(layer: string, filter: FilterSpecification) { this.style.setFilter(layer, filter); this._update(true); return this; @@ -1350,11 +1378,11 @@ class Map extends Camera { /** * Sets the any combination of light values. * - * @param {Object} options Light properties to set. Must conform to the [Mapbox Style Specification](https://www.mapbox.com/mapbox-gl-style-spec/). + * @param light Light properties to set. Must conform to the [Mapbox Style Specification](https://www.mapbox.com/mapbox-gl-style-spec/). * @returns {Map} `this` */ - setLight(lightOptions: any) { - this.style.setLight(lightOptions); + setLight(light: LightSpecification) { + this.style.setLight(light); this._update(true); return this; } diff --git a/src/ui/marker.js b/src/ui/marker.js index a650d7daa5d..96809327238 100644 --- a/src/ui/marker.js +++ b/src/ui/marker.js @@ -1,14 +1,20 @@ +// @flow const DOM = require('../util/dom'); const LngLat = require('../geo/lng_lat'); const Point = require('@mapbox/point-geometry'); const smartWrap = require('../util/smart_wrap'); +const {bindAll} = require('../util/util'); + +import type Map from './map'; +import type Popup from './popup'; +import type {LngLatLike} from "../geo/lng_lat"; /** * Creates a marker component - * @param {HTMLElement=} element DOM element to use as a marker (creates a div element by default) - * @param {Object=} options - * @param {PointLike=} options.offset The offset in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. + * @param element DOM element to use as a marker (creates a div element by default) + * @param options + * @param options.offset The offset in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. * @example * var marker = new mapboxgl.Marker() * .setLngLat([30.5, 50.5]) @@ -16,12 +22,17 @@ const smartWrap = require('../util/smart_wrap'); * @see [Add custom icons with Markers](https://www.mapbox.com/mapbox-gl-js/example/custom-marker-icons/) */ class Marker { - - constructor(element, options) { + _map: Map; + _offset: Point; + _element: HTMLElement; + _popup: ?Popup; + _lngLat: LngLat; + _pos: Point; + + constructor(element: ?HTMLElement, options?: {offset: PointLike}) { this._offset = Point.convert(options && options.offset || [0, 0]); - this._update = this._update.bind(this); - this._onMapClick = this._onMapClick.bind(this); + bindAll(['_update', '_onMapClick'], this); if (!element) element = DOM.create('div'); element.classList.add('mapboxgl-marker'); @@ -35,7 +46,7 @@ class Marker { * @param {Map} map * @returns {Marker} `this` */ - addTo(map) { + addTo(map: Map) { this.remove(); this._map = map; map.getCanvasContainer().appendChild(this._element); @@ -63,7 +74,7 @@ class Marker { this._map.off('click', this._onMapClick); this._map.off('move', this._update); this._map.off('moveend', this._update); - this._map = null; + this._map = (null : any); } DOM.remove(this._element); if (this._popup) this._popup.remove(); @@ -85,12 +96,11 @@ class Marker { /** * Set the marker's geographical position and move it. - * @param {LngLat} lnglat * @returns {Marker} `this` */ - setLngLat(lnglat) { + setLngLat(lnglat: LngLatLike) { this._lngLat = LngLat.convert(lnglat); - this._pos = null; + this._pos = (null : any); if (this._popup) this._popup.setLngLat(this._lngLat); this._update(); return this; @@ -102,12 +112,11 @@ class Marker { /** * Binds a Popup to the Marker - * @param {Popup=} popup an instance of the `Popup` class. If undefined or null, any popup + * @param popup an instance of the `Popup` class. If undefined or null, any popup * set on this `Marker` instance is unset * @returns {Marker} `this` */ - - setPopup(popup) { + setPopup(popup: ?Popup) { if (this._popup) { this._popup.remove(); this._popup = null; @@ -124,7 +133,7 @@ class Marker { return this; } - _onMapClick(event) { + _onMapClick(event: any) { const targetElement = event.originalEvent.target; const element = this._element; @@ -153,7 +162,7 @@ class Marker { else popup.addTo(this._map); } - _update(e) { + _update(e: any) { if (!this._map) return; if (this._map.transform.renderWorldCopies) { @@ -162,7 +171,7 @@ class Marker { this._pos = this._map.project(this._lngLat) ._add(this._offset) - ._add({x: -this._element.offsetWidth / 2, y: -this._element.offsetHeight / 2}); + ._add(new Point(-this._element.offsetWidth / 2, -this._element.offsetHeight / 2)); // because rounding the coordinates at every `move` event causes stuttered zooming // we only round them when _update is called with `moveend` or when its called with diff --git a/src/ui/popup.js b/src/ui/popup.js index 7a19e94cbe6..e799c490186 100644 --- a/src/ui/popup.js +++ b/src/ui/popup.js @@ -1,3 +1,4 @@ +// @flow const util = require('../util/util'); const Evented = require('../util/evented'); @@ -7,11 +8,17 @@ const Point = require('@mapbox/point-geometry'); const window = require('../util/window'); const smartWrap = require('../util/smart_wrap'); +import type Map from './map'; +import type {LngLatLike} from '../geo/lng_lat'; + const defaultOptions = { closeButton: true, closeOnClick: true }; +export type Anchor = 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; +export type Offset = number | PointLike | {[Anchor]: PointLike}; + /** * A popup component. * @@ -53,7 +60,16 @@ const defaultOptions = { * @see [Display a popup on click](https://www.mapbox.com/mapbox-gl-js/example/popup-on-click/) */ class Popup extends Evented { - constructor(options) { + _map: Map; + options: any; + _content: HTMLElement; + _container: HTMLElement; + _closeButton: HTMLElement; + _tip: HTMLElement; + _lngLat: LngLat; + _pos: Point; + + constructor(options: any) { super(); this.options = util.extend(Object.create(defaultOptions), options); util.bindAll(['_update', '_onClickClose'], this); @@ -65,7 +81,7 @@ class Popup extends Evented { * @param {Map} map The Mapbox GL JS map to add the popup to. * @returns {Popup} `this` */ - addTo(map) { + addTo(map: Map) { this._map = map; this._map.on('move', this._update); if (this.options.closeOnClick) { @@ -91,12 +107,12 @@ class Popup extends Evented { * @returns {Popup} `this` */ remove() { - if (this._content && this._content.parentNode) { - this._content.parentNode.removeChild(this._content); + if (this._content) { + DOM.remove(this._content); } if (this._container) { - this._container.parentNode.removeChild(this._container); + DOM.remove(this._container); delete this._container; } @@ -136,12 +152,12 @@ class Popup extends Evented { /** * Sets the geographical location of the popup's anchor, and moves the popup to it. * - * @param {LngLatLike} lnglat The geographical location to set as the popup's anchor. + * @param lnglat The geographical location to set as the popup's anchor. * @returns {Popup} `this` */ - setLngLat(lnglat) { + setLngLat(lnglat: LngLatLike) { this._lngLat = LngLat.convert(lnglat); - this._pos = null; + this._pos = (null : any); this._update(); return this; } @@ -153,7 +169,7 @@ class Popup extends Evented { * so it cannot insert raw HTML. Use this method for security against XSS * if the popup content is user-provided. * - * @param {string} text Textual content for the popup. + * @param text Textual content for the popup. * @returns {Popup} `this` * @example * var popup = new mapboxgl.Popup() @@ -161,7 +177,7 @@ class Popup extends Evented { * .setText('Hello, world!') * .addTo(map); */ - setText(text) { + setText(text: string) { return this.setDOMContent(window.document.createTextNode(text)); } @@ -172,10 +188,10 @@ class Popup extends Evented { * used only with trusted content. Consider {@link Popup#setText} if * the content is an untrusted text string. * - * @param {string} html A string representing HTML content for the popup. + * @param html A string representing HTML content for the popup. * @returns {Popup} `this` */ - setHTML(html) { + setHTML(html: string) { const frag = window.document.createDocumentFragment(); const temp = window.document.createElement('body'); let child; @@ -192,7 +208,7 @@ class Popup extends Evented { /** * Sets the popup's content to the element provided as a DOM node. * - * @param {Node} htmlNode A DOM node to be used as content for the popup. + * @param htmlNode A DOM node to be used as content for the popup. * @returns {Popup} `this` * @example * // create an element with the popup content @@ -203,7 +219,7 @@ class Popup extends Evented { * .setDOMContent(div) * .addTo(map); */ - setDOMContent(htmlNode) { + setDOMContent(htmlNode: Node) { this._createContent(); this._content.appendChild(htmlNode); this._update(); @@ -296,8 +312,7 @@ class Popup extends Evented { } } -function normalizeOffset(offset) { - +function normalizeOffset(offset: ?Offset) { if (!offset) { return normalizeOffset(new Point(0, 0)); @@ -315,7 +330,7 @@ function normalizeOffset(offset) { 'right': new Point(-offset, 0) }; - } else if (isPointLike(offset)) { + } else if (offset instanceof Point || Array.isArray(offset)) { // input specifies a single offset to be applied to all positions const convertedOffset = Point.convert(offset); return { @@ -344,8 +359,4 @@ function normalizeOffset(offset) { } } -function isPointLike(input) { - return input instanceof Point || Array.isArray(input); -} - module.exports = Popup; diff --git a/src/util/find_pole_of_inaccessibility.js b/src/util/find_pole_of_inaccessibility.js index 886af07446f..f2f758d6a2b 100644 --- a/src/util/find_pole_of_inaccessibility.js +++ b/src/util/find_pole_of_inaccessibility.js @@ -1,3 +1,5 @@ +// @flow + const Queue = require('tinyqueue'); const Point = require('@mapbox/point-geometry'); const distToSegmentSquared = require('./intersection_tests').distToSegmentSquared; @@ -6,18 +8,15 @@ const distToSegmentSquared = require('./intersection_tests').distToSegmentSquare * Finds an approximation of a polygon's Pole Of Inaccessibiliy https://en.wikipedia.org/wiki/Pole_of_inaccessibility * This is a copy of http://github.com/mapbox/polylabel adapted to use Points * - * @param {Array>} List of polygon rings first item in array is the outer ring followed optionally by the list of holes, should be an element of the result of util/classify_rings - * @param {number} [precision=1] Specified in input coordinate units. If 0 returns after first run, if > 0 repeatedly narrows the search space until the radius of the area searched for the best pole is less than precision - * @param {bool} [debug=false] Print some statistics to the console during execution - * - * @returns {Point} Pole of Inaccessibiliy. + * @param polygonRings first item in array is the outer ring followed optionally by the list of holes, should be an element of the result of util/classify_rings + * @param precision Specified in input coordinate units. If 0 returns after first run, if > 0 repeatedly narrows the search space until the radius of the area searched for the best pole is less than precision + * @param debug Print some statistics to the console during execution + * @returns Pole of Inaccessibiliy. * @private */ -module.exports = function (polygonRings, precision, debug) { - precision = precision || 1.0; - +module.exports = function (polygonRings: Array>, precision?: number = 1, debug?: boolean = false): Point { // find the bounding box of the outer ring - let minX, minY, maxX, maxY; + let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; const outerRing = polygonRings[0]; for (let i = 0; i < outerRing.length; i++) { const p = outerRing[i]; @@ -35,7 +34,7 @@ module.exports = function (polygonRings, precision, debug) { // a priority queue of cells in order of their "potential" (max distance to polygon) const cellQueue = new Queue(null, compareMax); - if (cellSize === 0) return [minX, minY]; + if (cellSize === 0) return new Point(minX, minY); // cover polygon with initial cells for (let x = minX; x < maxX; x += cellSize) {