{"version":3,"file":"tracking-BoMV-WlF.js","sources":["../../javascript/tracking/strings.js","../../javascript/tracking/data.js","../../javascript/tracking/const.js","../../javascript/tracking/state.js","../../javascript/tracking/options.js","../../javascript/tracking/preferences.js","../../javascript/tracking/user.js","../../javascript/tracking/devices.js","../../javascript/tracking/domNodes.js","../../javascript/tracking/log.js","../../javascript/tracking/leaflet.gleo/BlanketOverlay.js","../../javascript/tracking/gleo/src/glii/src/Attribute/AbstractAttributeSet.mjs","../../javascript/tracking/gleo/src/glii/src/constantNames.mjs","../../javascript/tracking/gleo/src/glii/src/GliiFactory.mjs","../../javascript/tracking/gleo/src/glii/src/util/typeMap.mjs","../../javascript/tracking/gleo/src/glii/src/util/parseGlslType.mjs","../../javascript/tracking/gleo/src/glii/src/Attribute/StridedTypedArrays.mjs","../../javascript/tracking/gleo/src/glii/src/Attribute/SingleAttribute.mjs","../../javascript/tracking/gleo/src/glii/src/Attribute/InterleavedAttributes.mjs","../../javascript/tracking/gleo/src/glii/src/Indices/SequentialIndices.mjs","../../javascript/tracking/gleo/src/glii/src/Indices/IndexBuffer.mjs","../../javascript/tracking/gleo/src/glii/src/Allocator.mjs","../../javascript/tracking/gleo/src/glii/src/Indices/SparseIndices.mjs","../../javascript/tracking/gleo/src/glii/src/Indices/SequentialSparseIndices.mjs","../../javascript/tracking/gleo/src/glii/src/Indices/TriangleIndices.mjs","../../javascript/tracking/gleo/src/glii/src/LoDAllocator.mjs","../../javascript/tracking/gleo/src/glii/src/Indices/LoDIndices.mjs","../../javascript/tracking/gleo/src/glii/src/Indices/WireframeTriangleIndices.mjs","../../javascript/tracking/gleo/src/glii/src/Indices/PointIndices.mjs","../../javascript/tracking/gleo/src/glii/src/util/reverseTypeMap.mjs","../../javascript/tracking/gleo/src/glii/src/FrameBuffer/RenderBuffer.mjs","../../javascript/tracking/gleo/src/glii/src/FrameBuffer/FrameBuffer.mjs","../../javascript/tracking/gleo/src/glii/src/Texture.mjs","../../javascript/tracking/gleo/src/glii/src/util/prettifyGlslError.mjs","../../javascript/tracking/gleo/src/glii/src/Program/WebGL1Program.mjs","../../javascript/tracking/gleo/src/glii/src/Program/MultiProgram.mjs","../../javascript/tracking/gleo/src/glii/src/WebGL1Clear.mjs","../../javascript/tracking/gleo/src/crs/knownCRSs.mjs","../../javascript/tracking/gleo/src/crs/projector.mjs","../../javascript/tracking/gleo/src/crs/BaseCRS.mjs","../../javascript/tracking/gleo/src/crs/OffsetCRS.mjs","../../javascript/tracking/gleo/src/geometry/RawGeometry.mjs","../../javascript/tracking/gleo/src/geometry/Geometry.mjs","../../javascript/tracking/gleo/src/dom/Evented.mjs","../../javascript/tracking/gleo/src/loaders/Loader.mjs","../../javascript/tracking/gleo/src/3rd-party/gl-matrix/common.mjs","../../javascript/tracking/gleo/src/3rd-party/gl-matrix/mat3.mjs","../../javascript/tracking/gleo/src/3rd-party/gl-matrix/vec3.mjs","../../javascript/tracking/gleo/src/dom/Dom.mjs","../../javascript/tracking/gleo/src/dom/GleoEventMixin.mjs","../../javascript/tracking/gleo/src/dom/GleoMouseEvent.mjs","../../javascript/tracking/gleo/src/dom/GleoPointerEvent.mjs","../../javascript/tracking/gleo/src/geometry/ExpandBox.mjs","../../javascript/tracking/gleo/src/geometry/DefaultGeometry.mjs","../../javascript/tracking/gleo/src/3rd-party/css-colour-parser.mjs","../../javascript/tracking/gleo/src/acetates/Acetate.mjs","../../javascript/tracking/gleo/src/Platina.mjs","../../javascript/tracking/gleo/src/crs/epsg4326.mjs","../../javascript/tracking/gleo/src/crs/epsg3857.mjs","../../javascript/tracking/leaflet.gleo/LeafletGleo.js","../../javascript/tracking/gleo/src/loaders/AbstractSymbolGroup.mjs","../../javascript/tracking/gleo/src/symbols/Symbol.mjs","../../javascript/tracking/gleo/src/loaders/SymbolGroup.mjs","../../javascript/tracking/gleo/src/geometry/LngLat.mjs","../../javascript/tracking/gleo/src/geometry/LatLng.mjs","../../javascript/tracking/gleo/src/symbols/ExtrudedPoint.mjs","../../javascript/tracking/gleo/src/util/imagePromise.mjs","../../javascript/tracking/gleo/src/util/imagifyFetchResponse.mjs","../../javascript/tracking/gleo/src/acetates/AcetateVertices.mjs","../../javascript/tracking/gleo/src/acetates/AcetateInteractive.mjs","../../javascript/tracking/gleo/src/acetates/AcetateExtrudedPoint.mjs","../../javascript/tracking/gleo/src/3rd-party/shelf-pack.mjs","../../javascript/tracking/gleo/src/symbols/Sprite.mjs","../../javascript/tracking/gleo/src/symbols/TintedSprite.mjs","../../javascript/tracking/map-gleo.js","../../javascript/tracking/map-items.js","../../javascript/tracking/assets.js","../../javascript/tracking/wrapurl.js","../../javascript/node_modules/string-natural-compare/natural-compare.js","../../javascript/node_modules/list.js/src/utils/get-by-class.js","../../javascript/node_modules/list.js/src/utils/extend.js","../../javascript/node_modules/list.js/src/utils/index-of.js","../../javascript/node_modules/list.js/src/utils/to-array.js","../../javascript/node_modules/list.js/src/utils/events.js","../../javascript/node_modules/list.js/src/utils/to-string.js","../../javascript/node_modules/list.js/src/utils/classes.js","../../javascript/node_modules/list.js/src/utils/get-attribute.js","../../javascript/node_modules/list.js/src/item.js","../../javascript/node_modules/list.js/src/add-async.js","../../javascript/node_modules/list.js/src/pagination.js","../../javascript/node_modules/list.js/src/parse.js","../../javascript/node_modules/list.js/src/templater.js","../../javascript/node_modules/list.js/src/search.js","../../javascript/node_modules/list.js/src/filter.js","../../javascript/node_modules/list.js/src/sort.js","../../javascript/node_modules/list.js/src/utils/fuzzy.js","../../javascript/node_modules/list.js/src/fuzzy-search.js","../../javascript/node_modules/list.js/src/index.js","../../javascript/utility.mjs","../../javascript/tracking/dom-util.js","../../javascript/tracking/window-layout.js","../../javascript/node_modules/leaflet.geodesic/dist/leaflet.geodesic.esm.js","../../javascript/tracking/ruler.js","../../javascript/tracking/timers.js","../../javascript/tracking/filtered-list.js","../../javascript/tracking/color.js","../../javascript/tracking/mouse-tooltip.js","../../javascript/tracking/polyfills.js","../../javascript/tracking/fence-edit.js","../../javascript/tracking/common-group.js","../../javascript/tracking/attributes.js","../../javascript/tracking/gleo/src/acetates/AcetateHeadingTriangle.mjs","../../javascript/tracking/gleo/src/symbols/HeadingTriangle.mjs","../../javascript/tracking/gleo/src/symboldecorators/bouncify.mjs","../../javascript/tracking/gleo-asset-sprite.js","../../javascript/tracking/place-marker.js","../../javascript/node_modules/handlebars/lib/handlebars/utils.js","../../javascript/node_modules/handlebars/lib/handlebars/exception.js","../../javascript/node_modules/handlebars/lib/handlebars/helpers/block-helper-missing.js","../../javascript/node_modules/handlebars/lib/handlebars/helpers/each.js","../../javascript/node_modules/handlebars/lib/handlebars/helpers/helper-missing.js","../../javascript/node_modules/handlebars/lib/handlebars/helpers/if.js","../../javascript/node_modules/handlebars/lib/handlebars/helpers/log.js","../../javascript/node_modules/handlebars/lib/handlebars/helpers/lookup.js","../../javascript/node_modules/handlebars/lib/handlebars/helpers/with.js","../../javascript/node_modules/handlebars/lib/handlebars/helpers.js","../../javascript/node_modules/handlebars/lib/handlebars/decorators/inline.js","../../javascript/node_modules/handlebars/lib/handlebars/decorators.js","../../javascript/node_modules/handlebars/lib/handlebars/logger.js","../../javascript/node_modules/handlebars/lib/handlebars/internal/create-new-lookup-object.js","../../javascript/node_modules/handlebars/lib/handlebars/internal/proto-access.js","../../javascript/node_modules/handlebars/lib/handlebars/base.js","../../javascript/node_modules/handlebars/lib/handlebars/safe-string.js","../../javascript/node_modules/handlebars/lib/handlebars/internal/wrapHelper.js","../../javascript/node_modules/handlebars/lib/handlebars/runtime.js","../../javascript/node_modules/handlebars/lib/handlebars/no-conflict.js","../../javascript/node_modules/handlebars/lib/handlebars.runtime.js","../../javascript/templates/asset.hbs","../../javascript/templates/assetGroup.hbs","../../javascript/templates/geofence.hbs","../../javascript/templates/place.hbs","../../javascript/templates/sharedView.hbs","../../javascript/templates/trip.hbs","../../javascript/tracking/templates.js","../../javascript/tracking/place.js","../../javascript/legacy/leaflet-routing-machine-all.js","../../javascript/tracking/routing-waypoint.js","../../javascript/tracking/waypoint.js","../../javascript/tracking/notifications.js","../../javascript/tracking/badges.js","../../javascript/tracking/gleo/src/3rd-party/quickselect.mjs","../../javascript/tracking/gleo/src/3rd-party/rbush.mjs","../../javascript/tracking/gleo/src/3rd-party/tinyqueue.mjs","../../javascript/tracking/gleo/src/3rd-party/rbush-knn.mjs","../../javascript/tracking/gleo/src/acetates/AcetateSolidExtrusion.mjs","../../javascript/tracking/gleo/src/symbols/Callout.mjs","../../javascript/tracking/gleo/src/symbols/Spider.mjs","../../javascript/tracking/gleo/src/symbols/MultiSymbol.mjs","../../javascript/tracking/gleo/src/symbols/CircleStroke.mjs","../../javascript/tracking/gleo/src/symbols/CircleFill.mjs","../../javascript/tracking/gleo/src/symbols/TextLabel.mjs","../../javascript/tracking/gleo/src/loaders/cluster/intersectSegments.mjs","../../javascript/tracking/gleo/src/loaders/cluster/intersectBboxes.mjs","../../javascript/tracking/gleo/src/loaders/Clusterer.mjs","../../javascript/tracking/marker-cluster.js","../../javascript/tracking/geometry-snap.js","../../javascript/tracking/geometry-create.js","../../javascript/tracking/position-history.js","../../javascript/tracking/asset-live-follow.js","../../javascript/tracking/asset-notification.js","../../javascript/tracking/asset-positions.js","../../javascript/tracking/positions.js","../../javascript/tracking/asset-live.js","../../javascript/tracking/limited-data.js","../../javascript/tracking/map-ui.js","../../javascript/tracking/assets-active.js","../../javascript/tracking/map-maptype.js","../../javascript/tracking/map-viewmode.js","../../javascript/tracking/marker.js","../../javascript/tracking/location.js","../../javascript/tracking/position-information.js","../../javascript/tracking/quick-actions.js","../../javascript/tracking/datatables.js","../../javascript/tracking/garmin.js","../../javascript/tracking/marker-click.js","../../javascript/tracking/fence.js","../../javascript/tracking/panel-settings.js","../../javascript/tracking/panel-nav.js","../../javascript/tracking/asset-state.js","../../javascript/tracking/item-list-render.js","../../javascript/tracking/item-listing.js","../../javascript/tracking/trips.js","../../javascript/tracking/map-bounds.js","../../javascript/tracking/asset-select.js","../../javascript/tracking/playback.js","../../javascript/tracking/modal-dialog-buttons.js","../../javascript/tracking/modal-dialog.js","../../javascript/tracking/driver.js","../../javascript/tracking/shared-view-dialog.js","../../javascript/tracking/shared-view-populate.js","../../javascript/tracking/shared-view.js","../../javascript/tracking/item-sorting.js","../../javascript/tracking/group-list.js","../../javascript/tracking/modal.js","../../javascript/tracking/ajax.js","../../javascript/tracking/idp-command.js","../../javascript/tracking/avl.js","../../javascript/tracking/asset-alerts.js","../../javascript/tracking/messages-history.js","../../javascript/legacy/jquery.maxchar.js","../../javascript/tracking/messages.js","../../javascript/tracking/activities.js","../../javascript/tracking/asset-events.js","../../javascript/tracking/display-filter.js","../../javascript/tracking/panel-side.js","../../javascript/tracking/asset-group.js","../../javascript/tracking/journey.js","../../javascript/tracking/marker-path.js","../../javascript/tracking/geocode.js","../../javascript/tracking/map-chooselocation.js","../../javascript/tracking/map-satellite.js","../../javascript/tracking/map-radar-weather.js","../../javascript/tracking/panel-browser-history.js","../../javascript/tracking/version.js","../../javascript/tracking/alert.js","../../javascript/node_modules/howler/dist/howler.js","../../javascript/tracking/notifications-system.js","../../javascript/tracking/signalr.js","../../javascript/tracking/data-group.js","../../javascript/tracking/time-slider-ui.js","../../javascript/tracking/gleo/src/symboldecorators/trajectorify.mjs","../../javascript/tracking/gleo/src/fields/Field.mjs","../../javascript/tracking/gleo/src/symboldecorators/intensify.mjs","../../javascript/tracking/gleo/src/util/getBlendEquationConstant.mjs","../../javascript/tracking/gleo/src/symbols/HeatPoint.mjs","../../javascript/tracking/gleo/src/symbols/VertexDot.mjs","../../javascript/tracking/gleo/src/util/glslFloatify.mjs","../../javascript/tracking/gleo/src/util/glslVecNify.mjs","../../javascript/tracking/gleo/src/fields/HeatMap.mjs","../../javascript/tracking/gleo/src/fields/QuadBin.mjs","../../javascript/tracking/gleo/src/fields/QuadMarginBin.mjs","../../javascript/tracking/gleo/src/fields/HexBin.mjs","../../javascript/tracking/time-slider.js","../../javascript/tracking/map-base.js","../../javascript/tracking/walkthrough.js","../../javascript/tracking/place-routing.js","../../javascript/tracking/device-imei.js","../../javascript/node_modules/inputmask/dist/jquery.inputmask.js","../../javascript/tracking/device-dialog.js","../../javascript/tracking/idp-config.js","../../javascript/tracking/asset-edit-dialog.js","../../javascript/tracking/panel.js","../../javascript/tracking/routing.js","../../javascript/tracking/idp-init.js","../../javascript/tracking/asset-edit.js","../../javascript/tracking/asset-init.js","../../javascript/tracking/legacy.js","../../javascript/tracking/window-load.js","../../javascript/tracking/item-filter.js","../../javascript/tracking/asset-history.js","../../javascript/tracking/edgesolar.js","../../javascript/tracking/queclink.js","../../javascript/tracking/panel-dialog.js","../../javascript/tracking/panel-secondary.js","../../javascript/tracking/user-location.js","../../javascript/node_modules/lodash/isObject.js","../../javascript/node_modules/lodash/_freeGlobal.js","../../javascript/node_modules/lodash/_root.js","../../javascript/node_modules/lodash/now.js","../../javascript/node_modules/lodash/_trimmedEndIndex.js","../../javascript/node_modules/lodash/_baseTrim.js","../../javascript/node_modules/lodash/_Symbol.js","../../javascript/node_modules/lodash/_getRawTag.js","../../javascript/node_modules/lodash/_objectToString.js","../../javascript/node_modules/lodash/_baseGetTag.js","../../javascript/node_modules/lodash/isObjectLike.js","../../javascript/node_modules/lodash/isSymbol.js","../../javascript/node_modules/lodash/toNumber.js","../../javascript/node_modules/lodash/debounce.js","../../javascript/node_modules/lodash/throttle.js","../../javascript/node_modules/simplebar-core/dist/index.mjs","../../javascript/node_modules/simplebar/dist/index.mjs","../../javascript/tracking/legacy-init.js","../../javascript/node_modules/jquery-ui-timepicker-addon/dist/jquery-ui-timepicker-addon.js","../../javascript/tracking/entry_point.js"],"sourcesContent":["Proxy ||= function(target) { return target; }; // For the browsers that don't support Proxy\r\n\r\nconst strings = new Proxy({\r\n\tMSG_MESSAGE_SENDING_ERROR: \"\",\r\n\tMSG_MESSAGE_SENT_SUCCESS: \"\",\r\n\tMSG_MESSAGE_SENT_ERROR: \"\",\r\n\tMSG_ADD_GROUP_ERROR: \"\",\r\n\tMSG_QUERY_GROUPS_ERROR: \"\",\r\n\tMSG_QUERY_USERS_ERROR: \"\",\r\n\tMSG_SINGLE_ASSET_ERROR: \"\",\r\n\tMSG_ASSET_QUERY_ERROR: \"\",\r\n\tMSG_ADD_ASSET_ERROR: \"\",\r\n\tMSG_ADD_ASSET_SUCCESS: \"\",\r\n\tMSG_EDIT_ASSET_SUCCESS: \"\",\r\n\tMSG_EDIT_ASSET_ERROR: \"\",\r\n\tMSG_EDIT_ASSET_FAILURE: \"\",\r\n\tMSG_EDIT_GROUP_SUCCESS: \"\",\r\n\tMSG_EDIT_GROUP_ERROR: \"\",\r\n\tMSG_EDIT_GROUP_FAILURE: \"\",\r\n\tMSG_DELETE_GROUP_ERROR: \"\",\r\n\tMSG_DELETE_ASSET_ERROR: \"\",\r\n\tMSG_ADD_GEOFENCE_NO_SEGMENTS: \"\",\r\n\tMSG_ADD_GEOFENCE_ERROR: \"\",\r\n\tMSG_EDIT_GEOFENCE_ERROR: \"\",\r\n\tMSG_DELETE_GEOFENCE_ERROR: \"\",\r\n\tMSG_INTERVAL_ERROR: \"\",\r\n\tMSG_SET_OUTPUT_PIN_ERROR: \"\",\r\n\tMSG_DPLUS_INTERVAL_ERROR: \"\",\r\n\tMSG_DPLUS_INTERVAL_SUCCESS: \"\",\r\n\tMSG_DPLUS_REPORT_ERROR: \"\",\r\n\tMSG_DPLUS_REPORT_SUCCESS: \"\",\r\n\tMSG_ADD_POSITION_SUCCESS: \"\",\r\n\tMSG_ADD_POSITION_ERROR: \"\",\r\n\tMSG_ADD_FILLUP_SUCCESS: \"\",\r\n\tMSG_ADD_FILLUP_ERROR: \"\",\r\n\tMSG_DELETE_FILLUP_ERROR: \"\",\r\n\tMSG_REFUEL_HISTORY_ERROR: \"\",\r\n\tMSG_LOAD_POSITION_ERROR: \"\",\r\n\tMSG_CONFIRM_NOTIFICATION_ERROR: \"\",\r\n\tMSG_DELETE_PLACE_ERROR: \"\",\r\n\tDELETE: \"\",\r\n\tCANCEL: \"\",\r\n\tFUEL_IMPERIAL: \"\",\r\n\tFUEL_LITRES: \"\",\r\n\tFUEL_US: \"\",\r\n\tASSET_COLOR: \"\",\r\n\tASSET_UNIQUEID: \"\",\r\n\tMSG_VALID_IMEI: \"\",\r\n\tMSG_ASSETS_AND_POSITIONS: \"\",\r\n\tMSG_ASSETS_SHOWN_LIVE: \"\",\r\n\tADD_GEOFENCE: \"\",\r\n\tEDIT_ASSET: \"\",\r\n\tMSG_DELETE_GROUP_CONFIRM: \"\",\r\n\tVEHICLE_FIELDS: \"\",\r\n\tSOFTWARE_VERSION: \"\",\r\n\tVEHICLE_MAKE_AND_MODEL: \"\",\r\n\tVEHICLE_PURCHASE_DATE: \"\",\r\n\tVEHICLE_VIN: \"\",\r\n\tVESSEL_FIELDS: \"\",\r\n\tVESSEL_CLASS: \"\",\r\n\tVESSEL_TONNAGE: \"\",\r\n\tVESSEL_FLAG_REGISTRY: \"\",\r\n\tVESSEL_IMO_NUMBER: \"\",\r\n\tVESSEL_CALL_SIGN: \"\",\r\n\tVESSEL_NAME: \"\",\r\n\tNOTES: \"\",\r\n\tPOSITION_INDEX: \"\",\r\n\tPOSITION_NEXT: \"\",\r\n\tPOSITION_PREV: \"\",\r\n\tPLAYBACK_STOP: \"\",\r\n\tPLAYBACK_PLAY: \"\",\r\n\tEXTRA: \"\",\r\n\tFIRST: \"\",\r\n\tLAST: \"\",\r\n\tHOURS: \"\",\r\n\tDAYS: \"\",\r\n\tWEEKS: \"\",\r\n\tMONTHS: \"\",\r\n\tUNKNOWN: \"\",\r\n\tNEVER: \"\",\r\n\tSAVE_CHANGES: \"\"\r\n}, {\r\n get(target, prop) {\r\n\t\tif (prop in target) {\r\n\t\t\treturn target[prop];\r\n\t\t}\r\n\t\tconsole.warn(`String key not found: ${prop}`);\r\n return prop;\r\n }\r\n});\r\n\r\n\r\nexport default strings;\r\n\r\n// Adds the overrides to the `strings` structure. Called from outside tracking.js\r\n// when the language of the user is known. This usually adds lots of new keys to\r\n// the structure, and therefore it's not possible to `export` individual strings.\r\nexport function overrideStrings(overrides) {\r\n\tObject.assign(strings, overrides);\r\n}\r\n","// Monolithic data structure to hold application state.\r\n// See also state.js and user.js\r\n\r\nconst trkData = {\r\n\tsoftwarePackages: null,\r\n\twizards: {\r\n\t\tsharedView: null,\r\n\t},\r\n\tsearch: {\r\n\t\tassets: null,\r\n\t\tplaces: null,\r\n\t\tfences: null,\r\n\t\tsharedViews: null,\r\n\t\tliveActivity: null,\r\n\t\thistoryActivity: null,\r\n\t},\r\n\tpending: {\r\n\t\tlive: false,\r\n\t\tevents: false,\r\n\t},\r\n\tcurrentHeight: null,\r\n\tpreviousZoom: null,\r\n\tvisible: {\r\n\t\tassets: [],\r\n\t\tgroups: [],\r\n\t\tplaces: [],\r\n\t\tfences: [],\r\n\t\ttrips: [],\r\n\t},\r\n\r\n\tassets: [],\r\n\tassetsById: {},\r\n\tmarkers: [],\r\n\tmarkersByAssetId: {},\r\n\tfenceMarkers: [],\r\n\tgroups: [],\r\n\tgroupsById: {},\r\n\tdevices: [],\r\n\tdevicesById: {},\r\n\tgatewayAccounts: [],\r\n\tfences: [],\r\n\tfenceGroups: [],\r\n\tfenceGroupUsers: [],\r\n\tfencesById: {},\r\n\tplaces: [],\r\n\tplaceGroups: [],\r\n\tplaceGroupUsers: [],\r\n\tplacesById: {},\r\n\tjourneys: [],\r\n\tjourneysById: {},\r\n\tsharedViews: [],\r\n\tsharedViewsById: {},\r\n\tusers: null,\r\n\tattributes: [],\r\n\tattributeGroups: [],\r\n\tassetUsers: [],\r\n\tassetForms: [],\r\n\tfenceUsers: [],\r\n\tassetGroupUsers: [],\r\n\tplaceUsers: [],\r\n\tplaceMarkers: [],\r\n\tlive: {\r\n\t\tisInitialized: false,\r\n\t\tlatestPosition: null,\r\n\t\tlatestPositions: [],\r\n\t\tlatestPositionsByAssetId: {},\r\n\t\tpositions: [],\r\n\t\tnormalizedPositions: [],\r\n\t\tpositionsByAssetId: {},\r\n\t\tnormalizedPositionsByAssetId: {},\r\n\t\tevents: [],\r\n\t\tnormalizedEvents: [],\r\n\t\teventIds: [],\r\n\t\tnormalizedEventIds: {},\r\n\t\tnormalizedEventsByAssetId: {},\r\n\t\tmarkers: [],\r\n\t\tmarkersByAssetId: {},\r\n\t\tmarkersByPositionId: {},\r\n\t\tmessageCounts: [],\r\n\t\tmessageCountsByAssetId: {},\r\n\t\tmessages: [],\r\n\t\tnormalizedMessages: [],\r\n\t\tmessagesByAssetId: {},\r\n\t\tnormalizedMessagesByAssetId: {},\r\n\t\teventFilters: [\"all\"],\r\n\t\tunreadNotifications: {\r\n\t\t\tpositions: [],\r\n\t\t\talerts: [],\r\n\t\t\tevents: [],\r\n\t\t\tstatus: [],\r\n\t\t\tmessages: [],\r\n\t\t},\r\n\t\tnotificationTimesByAssetId: {},\r\n\t\tmapLinesByAssetId: {},\r\n\t},\r\n\thistory: {\r\n\t\tisLimited: false,\r\n\t\tisLoadedLimitedData: false,\r\n\t\tlimitedData: null,\r\n\t\tlatestPosition: null,\r\n\t\tpositions: [],\r\n\t\tnormalizedPositions: [],\r\n\t\tpositionsByAssetId: {},\r\n\t\tnormalizedPositionsByAssetId: {},\r\n\t\tevents: [],\r\n\t\tnormalizedEvents: [],\r\n\t\tnormalizedEventIds: {},\r\n\t\tnormalizedEventsByAssetId: {},\r\n\t\tmarkers: [],\r\n\t\tmarkersByAssetId: {},\r\n\t\tmarkersByPositionId: {},\r\n\t\tmessageCounts: [],\r\n\t\tmessageCountsByAssetId: {},\r\n\t\tassetIdsWithResults: {},\r\n\t\tmessages: [],\r\n\t\tnormalizedMessages: [],\r\n\t\tmessagesByAssetId: {},\r\n\t\tnormalizedMessagesByAssetId: {},\r\n\t\tfromDate: null,\r\n\t\ttoDate: null,\r\n\t\teventFilters: [\"all\"],\r\n\t\tmapLinesByAssetId: {},\r\n\t\tmarkerClustersByAssetId: {},\r\n\t},\r\n\tactivityById: {},\r\n\tactivityByAssetId: {},\r\n\tpositionsById: {},\r\n\tpositionsByAssetId: {},\r\n\teventsById: {},\r\n\tmessagesById: {},\r\n\ttrips: {\r\n\t\tpositions: [],\r\n\t\tpositionsByTripId: {},\r\n\t\tnormalizedPositionsByTripId: {},\r\n\t\tevents: [],\r\n\t\teventsByTripId: {},\r\n\t\tnormalizedEvents: [],\r\n\t\tnormalizedEventsByTripId: {},\r\n\t\tnormalizedEventIds: {},\r\n\t\tmarkers: [],\r\n\t\tmarkersByTripId: {},\r\n\t\tmarkersByPositionId: {}, // multiple trips can include the same position\r\n\t\tmessageCounts: [],\r\n\t\tmessageCountsByTripId: {},\r\n\t\tmessages: [],\r\n\t\tnormalizedMessages: [],\r\n\t\tmessagesByTripId: {},\r\n\t\tnormalizedMessagesByTripId: {},\r\n\t\ttripIdsWithResults: {},\r\n\t\tmapLinesByTripId: {},\r\n\t\tmarkerClustersByTripId: {},\r\n\t},\r\n\tsharedView: {\r\n\t\tisLimited: false,\r\n\t\tisLoadedLimitedData: false,\r\n\t\tlimitedData: null,\r\n\t\tcurrent: null,\r\n\t\ttemp: null,\r\n\t\tevents: [],\r\n\t\tpositions: [],\r\n\t\tpositionsByAssetId: {},\r\n\t\tnormalizedPositions: [],\r\n\t\tnormalizedPositionsByAssetId: {},\r\n\t\tnormalizedEvents: [],\r\n\t\t//normalizedEventsBySharedViewId: {},\r\n\t\tmarkers: [],\r\n\t\t//markersBySharedViewId: {},\r\n\t\tmarkersByPositionId: {},\r\n\t\tmessageCounts: {\r\n\t\t\tFromMobile: 0,\r\n\t\t\tToMobile: 0,\r\n\t\t\tFromMobileChats: 0,\r\n\t\t\tToMobileChats: 0,\r\n\t\t\tFromMobileDevice: 0,\r\n\t\t\tToMobileDevice: 0,\r\n\t\t},\r\n\t\t//messageCountsBySharedViewId: {},\r\n\t\tisMessagesLoaded: false,\r\n\t\tnormalizedMessages: [],\r\n\t\t//normalizedMessagesBySharedViewId: {},\r\n\t\tmapLinesByAssetId: {},\r\n\t\tmarkerClustersByAssetId: {},\r\n\t\ttrips: {\r\n\t\t\tmarkers: [],\r\n\t\t},\r\n\t\tplaceMarkers: [],\r\n\t\tfenceMarkers: [],\r\n\t\tpriorMapType: null,\r\n\t\tpriorIsSatelliteLabelOverlayEnabled: true,\r\n\t\t// separate cached containers from trkData on purpose\r\n\t\tpositionsById: {},\r\n\t\teventsById: {},\r\n\t\tmessagesById: {},\r\n\t\tactivityById: {},\r\n\t\tfromDate: null,\r\n\t\ttoDate: null,\r\n\t},\r\n\tsharedViewUndo: null,\r\n\tbehaviorAnalysis: {\r\n\t\tpositions: [],\r\n\t\tevents: [],\r\n\t\tmarkers: [],\r\n\t},\r\n\tfollowAssets: {\r\n\t\tpositions: [],\r\n\t\tevents: [],\r\n\t\tmarkers: [],\r\n\t},\r\n\tsnapToRoadsQueue: [],\r\n\ttileLoadedQueue: [],\r\n\twaypoints: [],\r\n\tevents: [],\r\n\tpendingEvents: [],\r\n\talerts: [],\r\n\teventFilters: [\"all\"],\r\n\teventTypes: [],\r\n\tlastEventId: null,\r\n\tlastAlertId: null,\r\n\tqueryingNotifications: false,\r\n\tlastNotificationId: null,\r\n\tquickMessages: [],\r\n\tmessaging: {\r\n\t\tinbox: null,\r\n\t\toutbox: null,\r\n\t},\r\n\tvalidation: {\r\n\t\taddGroup: null,\r\n\t\teditAsset: null,\r\n\t\teditGroup: null,\r\n\t\tgeofence: null,\r\n\t\t//place: null,\r\n\t\tdPlusQuery: null,\r\n\t\tdPlusInterval: null,\r\n\t\tidpAvlGeofence: null,\r\n\t\tidpAvlParameters: null,\r\n\t\tidpCoreParameters: null,\r\n\t\tidpIoParameters: null,\r\n\t\tidpMetersSet: null,\r\n\t},\r\n\tradar: {\r\n\t\tisActive: false,\r\n\t\ttimer: null,\r\n\t},\r\n\tradarAustralia: {\r\n\t\tisActive: false,\r\n\t\ttimer: null,\r\n\t\tbasetime: null,\r\n\t\tissuetime: null,\r\n\t},\r\n\tworldIR: {\r\n\t\tisActive: false,\r\n\t\ttimer: null,\r\n\t},\r\n\tmaritime: { isActive: false },\r\n\toil: { isActive: false },\r\n\tclouds: { isActive: false },\r\n\tweather: {\r\n\t\tgeoJSON: null,\r\n\t\tgettingData: false,\r\n\t\tlayer: null,\r\n\t\tisActive: false,\r\n\t\trequest: null,\r\n\t},\r\n\tiridium: { isActive: false },\r\n\tiridiumnext: { isActive: false },\r\n\tglobalstar: { isActive: false },\r\n\tinmarsat: { isActive: false },\r\n\torbcomm: { isActive: false },\r\n\tgeostationary: { isActive: false },\r\n\ttraffic: { isActive: false },\r\n\tactiveLayers: [],\r\n\truler: {\r\n\t\tisOpen: false,\r\n\t\tpoints: [],\r\n\t\tpolygon: null,\r\n\t},\r\n\trouting: {\r\n\t\tisOpen: false,\r\n\t\tstart: null,\r\n\t\tend: null,\r\n\t\twaypoints: [],\r\n\t\tisUsingMap: false,\r\n\t\tmapClickDestination: \"waypoint\",\r\n\t},\r\n\tzoom: {\r\n\t\tisActive: false,\r\n\t\tmanager: null,\r\n\t},\r\n\tdrawing: {\r\n\t\tisActive: false,\r\n\t\tmanager: null,\r\n\t},\r\n\tsearchPositionResults: null,\r\n\tsearchPlaceResults: null,\r\n\tsearchRouteResults: null,\r\n\tplayback: {\r\n\t\tassetId: null,\r\n\t\tindex: 0,\r\n\t\tpositions: [],\r\n\t},\r\n\tsatelliteMarkers: [],\r\n\tsatelliteOrbits: [],\r\n\tsatelliteIndex: [],\r\n\tmapLayers: {\r\n\t\tother: null,\r\n\t\tnormal: null,\r\n\t\tsharedView: null,\r\n\t},\r\n\tisSatelliteLabelOverlayEnabled: true,\r\n\trouteLine: null,\r\n\trouteLineEncoded: null,\r\n\trouteLinesEncoded: null,\r\n\troutePolygon: null,\r\n\tsystemNotifications: [],\r\n\t// TODO optimize these as lookup tables instead of arrays\r\n\tMESSAGES_TEXT: [0, 10, 11],\r\n\tMESSAGE_IDP_COMMAND_RESPONSE: 76,\r\n\tMSG_IDP_UPDATER_TERMINAL_INFO_RESPONSE: 165,\r\n\tMSG_IDP_UPDATER_STATE_RESPONSE: 166,\r\n\ttooltips: [],\r\n\tMAP_TYPES: {\r\n\t\troadlight: 4,\r\n\t\troaddark: 5,\r\n\t\troad: 0,\r\n\t\tsatellite: 1,\r\n\t\tsatellitealt: 6,\r\n\t\topenstreetmap: 3,\r\n\t\tterrain: 2,\r\n\t},\r\n};\r\n\r\nexport default trkData;\r\n","export const viewModes = {\r\n\tNORMAL: Symbol(\"view-normal\"),\r\n\tSHARED_VIEW: Symbol(\"view-shared\"),\r\n};\r\n\r\nexport const panels = {\r\n\t//LIVE: 0,\r\n\t//HISTORY: 1,\r\n\tASSETS: 0,\r\n\tBEHAVIOR_ANALYSIS: 2,\r\n\tGEOFENCES: 3,\r\n\tPLACES: 4,\r\n\tADD_ITEM: 5,\r\n\tFOLLOW_ASSETS: 6,\r\n\tJOURNEYS: 10,\r\n\tSHARED_VIEWS: 12,\r\n};\r\n\r\nexport const mapModes = {\r\n\tLIVE: Symbol(\"map-live\"),\r\n\tHISTORY: Symbol(\"map-history\"),\r\n\tTIME_SLIDER: Symbol(\"map-time-slider\"),\r\n};\r\n\r\nexport const trkDataGroups = {\r\n\tNORMAL_LIVE: Symbol(\"live\"),\r\n\tNORMAL_HISTORY: Symbol(\"history\"),\r\n\tJOURNEY_HISTORY: Symbol(\"journey-history\"),\r\n\tSHARED_VIEW_HISTORY: Symbol(\"shared-history\"),\r\n\tSHARED_VIEW_JOURNEY: Symbol(\"shared-journet\"),\r\n\t// NORMAL_LIVE: 0,\r\n\t// NORMAL_HISTORY: 1,\r\n\t// JOURNEY_HISTORY: 2,\r\n\t// SHARED_VIEW_HISTORY: 3,\r\n\t// SHARED_VIEW_JOURNEY: 4,\r\n};\r\n\r\nexport const sortModes = {\r\n\tALPHABETICAL: 0,\r\n\tCUSTOM: 1,\r\n\tCOLOR: 2,\r\n\tMANUFACTURER: 3,\r\n\tDEVICE_TYPE: 4,\r\n};\r\n\r\nexport const sortDirections = {\r\n\tASC: 0,\r\n\tDESC: 1,\r\n};\r\n\r\nexport let language = \"en\";\r\n\r\nexport function setLanguage(l) {\r\n\tlanguage = l;\r\n}\r\n","import { viewModes, panels } from \"./const.js\";\r\n\r\n// Monolithic data structure to hold application state.\r\n// See also data.js and user.js\r\n\r\nconst state = {\r\n\tisSafari: !!navigator.userAgent.match(/Version\\/[\\d\\.]+.*Safari/),\r\n\tisMobile: /iPhone|iPad|iPod|Android|Blackberry|Fennec|Mobi/i.test(navigator.userAgent),\r\n\tliveFollow: {\r\n\t\tisActive: false,\r\n\t\tasset: null,\r\n\t\tassets: [],\r\n\t\tgroups: [],\r\n\t},\r\n\tisAddingAsset: false,\r\n\tisEditingGeofence: false,\r\n\tisEditingAssetGroup: false,\r\n\tgroupDialog: {\r\n\t\tisEditing: false,\r\n\t\ttype: null, // 'asset' | 'fence' | 'place'\r\n\t},\r\n\tisInLiveMode: true,\r\n\tactiveMode: null,\r\n\tactiveMapMode: null,\r\n\tactiveViewMode: viewModes.NORMAL,\r\n\thasQueriedHistory: false,\r\n\thasQueriedLive: false,\r\n\tportalLoadedEpoch: null,\r\n\tactivePanel: panels.ASSETS,\r\n\tmapClickQueue: [],\r\n\tmapClickHandlers: {\r\n\t\tPLACE: 0,\r\n\t\tPOSITION: 1,\r\n\t\tPOSITION_ADD: 2,\r\n\t\tRULER: 3,\r\n\t\tROUTING: 4,\r\n\t\tGEOFENCE: 5,\r\n\t},\r\n\tisChoosingPosition: false,\r\n\tisAddingPosition: false,\r\n\tisChoosingPlace: false,\r\n\tisFirstLoad: true,\r\n\tisInPlaybackMode: false,\r\n\topenWindow: null,\r\n\topenPanels: {\r\n\t\tprimary: true,\r\n\t\tsecondary: false,\r\n\t},\r\n\tselectedMarker: null,\r\n\tchosenLocation: null,\r\n\tchosenLocations: [],\r\n\tisSnapToRoadsQueueRunning: false,\r\n\tisSnapToRoadsQueueProcessing: false,\r\n\tisPreviewingBuffer: false,\r\n\tbounds: null,\r\n\tboundsItems: [],\r\n\tzoomTimeout: null,\r\n\tuserLocation: {\r\n\t\tposition: null,\r\n\t\tmarker: null,\r\n\t\tcircle: null,\r\n\t\twatch: null,\r\n\t\tzoomToPosition: false,\r\n\t\toptions: {\r\n\t\t\tenableHighAccuracy: true,\r\n\t\t\tmaximumAge: 60 * 1000 * 5,\r\n\t\t\ttimeout: 30 * 1000,\r\n\t\t},\r\n\t},\r\n\tpromises: {\r\n\t\tgetGroupsAndAssets: null,\r\n\t\tgetAssetUsers: null,\r\n\t\tgetLatestPositions: null,\r\n\t\tgetNotificationsAndAlerts: null,\r\n\t},\r\n\tcurrentMapType: null,\r\n\tnextFocus: null,\r\n};\r\n\r\nexport default state;\r\n","import L from \"leaflet\";\r\n\r\nconst options = {\r\n\tisDemo: false,\r\n\tuseStaticSubdomains: true,\r\n\tdisablePositionPopup: false,\r\n\tdisableMapTypeSelection: false,\r\n\tdisablePanels: false,\r\n\tdisableLayerSelection: false,\r\n\tdisableHeader: false,\r\n\tisCompact: false,\r\n\tshowAssetId: \"\",\r\n\tshowAssetUniqueId: \"\",\r\n\tdefaultMode: \"\",\r\n\tdefaultMapType: \"\",\r\n\tdefaultZoom: 14,\r\n\tmaximumZoom: 18,\r\n\tminimumZoom: 3,\r\n\tmaxBounds: L.latLngBounds([\r\n\t\t[90, -360],\r\n\t\t[-90, 360],\r\n\t]),\r\n\tshowPositionId: \"\",\r\n\tshowSharedViewId: null,\r\n\tmaxLiveTrailPositions: 10,\r\n\tradarRefreshMinutes: 3,\r\n\tworldIRRefreshMinutes: 30,\r\n\tshowSidePanel: false,\r\n\tuseFenceDocuments: false,\r\n\tplaceAssetPhotosInPositionDialog: false,\r\n\tassetMenuExclude: \"\",\r\n\tdriverFieldsExclude: \"\",\r\n\thideAlertTriggeredEvents: false,\r\n\thideEmergencyEvents: false,\r\n\tkeys: {\r\n\t\tweatherUnderground: \"\",\r\n\t\tthunderForest: \"\",\r\n\t\tmapQuest: \"\",\r\n\t},\r\n\tenableGeocoding: true,\r\n\tbaseLayers: {\r\n\t\troad: {\r\n\t\t\tenabled: true,\r\n\t\t\turl: \"\",\r\n\t\t\tminZoom: 1,\r\n\t\t\tmaxZoom: 22,\r\n\t\t},\r\n\t\troadlight: {\r\n\t\t\tenabled: false,\r\n\t\t\turl: \"\",\r\n\t\t\tminZoom: 1,\r\n\t\t\tmaxZoom: 22,\r\n\t\t},\r\n\t\troaddark: {\r\n\t\t\tenabled: false,\r\n\t\t\turl: \"\",\r\n\t\t\tminZoom: 1,\r\n\t\t\tmaxZoom: 22,\r\n\t\t},\r\n\t\tsatellitelabels: {\r\n\t\t\tenabled: false,\r\n\t\t\turl: \"\",\r\n\t\t\tminZoom: 3,\r\n\t\t\tmaxZoom: 22,\r\n\t\t},\r\n\t\tsatellitenolabels: {\r\n\t\t\tenabled: false,\r\n\t\t\turl: \"\",\r\n\t\t\tminZoom: 3,\r\n\t\t\tmaxZoom: 22,\r\n\t\t},\r\n\t\tsatellitealt: {\r\n\t\t\tenabled: false,\r\n\t\t},\r\n\t\tterrain: {\r\n\t\t\tenabled: false,\r\n\t\t\turl: \"\",\r\n\t\t\tminZoom: 1,\r\n\t\t\tmaxZoom: 22,\r\n\t\t\tsubdomains: [\"a\", \"b\", \"c\"],\r\n\t\t},\r\n\t\topenstreetmap: {\r\n\t\t\tenabled: false,\r\n\t\t\turl: \"https://tile.openstreetmap.org/{z}/{x}/{y}.png\",\r\n\t\t},\r\n\t},\r\n\tbounceOnEvents: [292, 293],\r\n\tbounceForDuration: 10000,\r\n\tenableShoutConfigOta: false,\r\n\tdefaultColors: {\r\n\t\t//assets: '#8dc63f',\r\n\t\t//fences: '#8dc63f',\r\n\t\t//places: '#8dc63f'\r\n\t},\r\n\tallowAnonymousMessaging: false,\r\n\tdateRangeMin: null,\r\n\tdateRangeMax: null,\r\n\tdefaultIconSet: \"default\",\r\n};\r\n\r\nexport default options;\r\n","import strings from \"./strings.js\";\r\n\r\nimport $j from \"jquery\";\r\n\r\nconst preferences = {\r\n\t// include defaults for intellisense\r\n\tPREFERENCE_SPEED: 0,\r\n\tPREFERENCE_LAT_LNG: 1,\r\n\tPREFERENCE_FUEL_UNIT: 1,\r\n\tPREFERENCE_MOROCCO_OVERLAY: false,\r\n\tPREFERENCE_REMOVE_ROADS: false,\r\n\tPREFERENCE_GROUP_POSITIONS: true,\r\n\tPREFERENCE_ALPHA_POSITIONS: true,\r\n\tPREFERENCE_EMERGENCY_AUDIO: false,\r\n};\r\n\r\nexport default preferences;\r\n\r\nexport function globalizeNumberFormat(number, format, suffix) {\r\n\tif (number == null || typeof number !== \"number\" || !Number.isFinite(number)) {\r\n\t\treturn \"\";\r\n\t}\r\n\tif (suffix !== undefined) {\r\n\t\treturn $j.formatGlobalization(number, format) + suffix;\r\n\t}\r\n\treturn $j.formatGlobalization(number, format);\r\n}\r\n\r\n// convert from metric to user preference for string display\r\nexport function convertAltitudeToPreference(altitude) {\r\n\tif (altitude == null || altitude == \"\") {\r\n\t\treturn \"\";\r\n\t}\r\n\r\n\tvar converted = altitude;\r\n\tswitch (preferences.PREFERENCE_SPEED) {\r\n\t\tcase 0:\r\n\t\tcase 3: // knots/feet\r\n\t\t\tconverted = altitude * 3.2808399;\r\n\t\t\tbreak;\r\n\t\tcase 1:\r\n\t\tcase 2:\r\n\t\tdefault:\r\n\t\t\tbreak;\r\n\t}\r\n\treturn globalizeNumberFormat(converted, \"n1\", \" \" + altitudeText());\r\n}\r\n\r\nexport function convertSpeedToPreference(speed) {\r\n\tif (speed == null || speed == \"\") {\r\n\t\treturn \"\";\r\n\t}\r\n\r\n\tvar converted = speed;\r\n\tswitch (preferences.PREFERENCE_SPEED) {\r\n\t\tcase 0:\r\n\t\t\tconverted = speed * 2.23693629;\r\n\t\t\tbreak;\r\n\t\tcase 1:\r\n\t\t\tconverted = speed * 3.6;\r\n\t\t\tbreak;\r\n\t\tcase 2:\r\n\t\tcase 3:\r\n\t\t\tconverted = speed * 1.94384449;\r\n\t\t\tbreak;\r\n\t\tdefault:\r\n\t\t\tconverted = speed * 2.23693629;\r\n\t\t\tbreak;\r\n\t}\r\n\r\n\treturn globalizeNumberFormat(converted, \"n1\", \" \" + speedText());\r\n}\r\n\r\nexport function convertToLatLngPreference(lat, lng, grid) {\r\n\tvar pref = preferences.PREFERENCE_LAT_LNG;\r\n\tif ((pref == 3 && grid == null) || (pref == 4 && grid == null) || (pref == 5 && grid == null)) {\r\n\t\tpref = 0;\r\n\t}\r\n\tswitch (pref) {\r\n\t\tcase 1:\r\n\t\t\treturn convertDecimalDegrees(lat, lng);\r\n\t\tcase 2:\r\n\t\t\treturn convertDegreesDecimalMinutes(lat, lng);\r\n\t\tcase 3:\r\n\t\tcase 4:\r\n\t\t\treturn grid;\r\n\t\tcase 5:\r\n\t\t\tif (grid !== null && grid.length === 8) {\r\n\t\t\t\treturn grid;\r\n\t\t\t}\r\n\t\t\treturn strings.NOT_IN_UK;\r\n\t\tcase 0:\r\n\t\tdefault:\r\n\t\t\treturn lat.toFixed(6) + \", \" + lng.toFixed(6);\r\n\t\t//case 3:\r\n\t\t// pull Grid from position\r\n\t\t// return '';\r\n\t\t//return convertMGRS(lat, lng);\r\n\t}\r\n}\r\n\r\nfunction convertDegreesDecimalMinutes(lat, lng) {\r\n\tvar directionLat = lat > 0 ? \"N\" : \"S\";\r\n\tvar directionLng = lng > 0 ? \"E\" : \"W\";\r\n\tvar degreesLat = Math.floor(Math.abs(lat));\r\n\tvar degreesLng = Math.floor(Math.abs(lng));\r\n\tvar minutesLat = ((Math.abs(lat) - Math.abs(degreesLat)) * 60).toFixed(4);\r\n\tvar minutesLng = ((Math.abs(lng) - Math.abs(degreesLng)) * 60).toFixed(4);\r\n\treturn degreesLat + \"°\" + minutesLat + \"'\" + directionLat + \" \" + degreesLng + \"°\" + minutesLng + \"'\" + directionLng;\r\n}\r\n\r\nfunction convertDecimalDegrees(lat, lng) {\r\n\tvar originalLat = lat;\r\n\tlat = Math.abs(lat);\r\n\tvar degreesLat = Math.floor(lat);\r\n\tvar minutesLat = Math.floor((lat - degreesLat) * 60);\r\n\tvar secondsLat = (((lat - degreesLat) * 60 - minutesLat) * 60).toFixed(4);\r\n\tvar directionLat = originalLat > 0 ? \"N\" : \"S\";\r\n\r\n\tvar originalLng = lng;\r\n\tlng = Math.abs(lng);\r\n\tvar degreesLng = Math.floor(lng);\r\n\tvar minutesLng = Math.floor((lng - degreesLng) * 60);\r\n\tvar secondsLng = (((lng - degreesLng) * 60 - minutesLng) * 60).toFixed(4);\r\n\tvar directionLng = originalLng > 0 ? \"E\" : \"W\";\r\n\r\n\treturn (\r\n\t\tdegreesLat +\r\n\t\t\"°\" +\r\n\t\tminutesLat +\r\n\t\t\"'\" +\r\n\t\tsecondsLat +\r\n\t\t'\"' +\r\n\t\tdirectionLat +\r\n\t\t\" \" +\r\n\t\tdegreesLng +\r\n\t\t\"°\" +\r\n\t\tminutesLng +\r\n\t\t\"'\" +\r\n\t\tsecondsLng +\r\n\t\t'\"' +\r\n\t\tdirectionLng\r\n\t);\r\n}\r\n\r\nexport function convertFromMetresToUserDistancePreference(distance) {\r\n\tif (distance == null || distance == \"\") {\r\n\t\treturn \"\";\r\n\t}\r\n\r\n\tvar converted = distance;\r\n\tvar isSmall = false;\r\n\r\n\tswitch (preferences.PREFERENCE_SPEED) {\r\n\t\tcase 3: // knots/nautical miles - avionics\r\n\t\tcase 2: // knots/nautical miles\r\n\t\t\tconverted = (distance / 1000) * 0.539956803;\r\n\t\t\tbreak;\r\n\t\tcase 0: // miles\r\n\t\t\tvar feet = distance * 3.2808399;\r\n\t\t\tif (feet > 5280) {\r\n\t\t\t\tconverted = feet / 5280;\r\n\t\t\t} else {\r\n\t\t\t\tisSmall = true;\r\n\t\t\t\tconverted = Math.floor(feet);\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\tcase 1: // metres/kilometres\r\n\t\tdefault:\r\n\t\t\tif (distance < 1000) {\r\n\t\t\t\tisSmall = true;\r\n\t\t\t\tconverted = Math.floor(distance);\r\n\t\t\t} else {\r\n\t\t\t\tconverted = distance / 1000;\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t}\r\n\r\n\treturn globalizeNumberFormat(converted, \"n2\", \" \" + distanceText(isSmall));\r\n}\r\n\r\nexport function distanceValueFromMetersToUserPreference(distance) {\r\n\tswitch (preferences.PREFERENCE_SPEED) {\r\n\t\tcase 3: // knots/nautical miles - avionics\r\n\t\tcase 2: // knots/nautical miles\r\n\t\t\treturn distance / 1000 * 0.539956803;\r\n\t\tcase 0: // miles\r\n\t\t\treturn distance / 1000 * 0.621371192;\r\n\t\tcase 1: // metres/kilometres\r\n\t\tdefault:\r\n\t\t\treturn distance / 1000;\r\n\t}\r\n}\r\n\r\nexport function distanceValueFromUserPreferenceToMeters(distance) {\r\n\tswitch (preferences.PREFERENCE_SPEED) {\r\n\t\tcase 3: // knots/nautical miles - avionics\r\n\t\tcase 2: // knots/nautical miles\r\n\t\t\treturn distance * 1000 / 0.539956803;\r\n\t\tcase 0: // miles\r\n\t\t\treturn distance * 1000 / 0.621371192;\r\n\t\tcase 1: // metres/kilometres\r\n\t\tdefault:\r\n\t\t\treturn distance * 1000;\r\n\t}\r\n}\r\n\r\nexport function distanceText(isSmall) {\r\n\tswitch (preferences.PREFERENCE_SPEED) {\r\n\t\tcase 3: // knots/nautical miles - avionics\r\n\t\tcase 2: // knots/nautical miles\r\n\t\t\treturn \"nmi\";\r\n\t\tcase 0: // feet/miles\r\n\t\t\tif (isSmall === true) {\r\n\t\t\t\treturn \"ft\";\r\n\t\t\t}\r\n\t\t\treturn \"mi\";\r\n\t\tcase 1: // metres/kilometres\r\n\t\tdefault:\r\n\t\t\tif (isSmall === true) {\r\n\t\t\t\treturn \"m\";\r\n\t\t\t}\r\n\t\t\treturn \"km\";\r\n\t}\r\n}\r\n\r\nexport function fuelText() {\r\n\tswitch (preferences.PREFERENCE_FUEL_UNIT) {\r\n\t\tcase 1:\r\n\t\t\t// imp mpg\r\n\t\t\treturn strings.FUEL_IMPERIAL;\r\n\t\tcase 2:\r\n\t\tcase 3:\r\n\t\t\t// litres\r\n\t\t\treturn strings.FUEL_LITRES;\r\n\t\tcase 0:\r\n\t\tdefault:\r\n\t\t\t// us mpg\r\n\t\t\treturn strings.FUEL_US;\r\n\t}\r\n}\r\n\r\nexport function altitudeText() {\r\n\tswitch (preferences.PREFERENCE_SPEED) {\r\n\t\tcase 0:\r\n\t\tcase 3:\r\n\t\t\treturn \"ft\";\r\n\t\tcase 1:\r\n\t\tcase 2:\r\n\t\tdefault:\r\n\t\t\treturn \"m\";\r\n\t}\r\n}\r\n\r\nexport function speedText() {\r\n\tswitch (preferences.PREFERENCE_SPEED) {\r\n\t\tcase 1:\r\n\t\t\treturn \"kph\";\r\n\t\tcase 2:\r\n\t\tcase 3:\r\n\t\t\treturn \"knots\";\r\n\t\tcase 0:\r\n\t\tdefault:\r\n\t\t\treturn \"mph\";\r\n\t}\r\n}\r\n\r\nexport function convertFuelEfficiencyToStandard(fuelEfficiency, fuelEfficiencyType) {\r\n\tvar efficiency = NaN;\r\n\tfuelEfficiency = $j.parseFloat(fuelEfficiency);\r\n\tfuelEfficiencyType = parseInt(fuelEfficiencyType);\r\n\tswitch (fuelEfficiencyType) {\r\n\t\tcase 0: // us mpg\r\n\t\t\tefficiency = (235.2146 / fuelEfficiency).toFixed(4);\r\n\t\t\tbreak;\r\n\t\tcase 1: // imperial mpg\r\n\t\t\tefficiency = (282.4809363 / fuelEfficiency).toFixed(4);\r\n\t\t\tbreak;\r\n\t\tcase 2: // kpl\r\n\t\t\tefficiency = (100 / fuelEfficiency).toFixed(4);\r\n\t\t\tbreak;\r\n\t\tcase 3: // l/100 km\r\n\t\t\tefficiency = fuelEfficiency;\r\n\t\t\tbreak;\r\n\t\tdefault:\r\n\t\t\tbreak;\r\n\t}\r\n\tif (isNaN(efficiency)) return \"\";\r\n\tefficiency = parseFloat(efficiency);\r\n\tefficiency = $j.formatGlobalization(efficiency, \"n4\");\r\n\treturn efficiency;\r\n}\r\n\r\nexport function fuelEfficiencyType(type) {\r\n\tswitch (type) {\r\n\t\tcase 0: // us mpg\r\n\t\t\treturn strings.FUEL_USMPG;\r\n\t\tcase 1: // imperial mpg\r\n\t\t\treturn strings.FUEL_IMPERIALMPG;\r\n\t\tcase 2: // kpl\r\n\t\t\treturn strings.FUEL_KPL;\r\n\t\tcase 3: // l/100 km\r\n\t\tdefault:\r\n\t\t\treturn strings.FUEL_L100KM;\r\n\t}\r\n}\r\n","import { sortModes, sortDirections } from \"./const.js\";\r\nimport trkData from \"./data.js\";\r\n\r\nimport _ from \"lodash\";\r\nimport $ from \"jquery\";\r\n\r\n// Monolithic data structure to hold application state.\r\n// See also state.js and data.js\r\n\r\nconst user = {\r\n\tisAdmin: false,\r\n\tisAnonymous: false,\r\n\tshowWalkthrough: false,\r\n\tcanEditAssets: false,\r\n\tcanAddAssets: false,\r\n\tcanDeleteAssets: false,\r\n\tcanEditAssetGroups: false,\r\n\tcanEditAlerts: false,\r\n\tcanEditGeofences: false,\r\n\tcanEditPlaces: false,\r\n\tcanSendCommands: false,\r\n\tcanEditJourneys: false,\r\n\tcanEditSharedViews: false,\r\n\tdateFormat: \"M/D/YYYY h:mm:ss A\",\r\n\thiddenPreferences: {\r\n\t\tfences: [],\r\n\t\tgroups: [],\r\n\t\tassets: [],\r\n\t\tplaces: [],\r\n\t},\r\n\tdisplayPreferences: {\r\n\t\tversion: 2,\r\n\t\texpandedGroups: [],\r\n\t\thiddenFences: [],\r\n\t\thiddenAssets: [],\r\n\t\thiddenPlaces: [],\r\n\t\tvisibleTrips: [],\r\n\t\tcustomSort: {},\r\n\t\tsortMode: {\r\n\t\t\tassets: sortModes.ALPHABETICAL,\r\n\t\t\tplaces: sortModes.ALPHABETICAL,\r\n\t\t\tfences: sortModes.ALPHABETICAL,\r\n\t\t\tjourneys: sortModes.ALPHABETICAL,\r\n\t\t\tsharedViews: sortModes.ALPHABETICAL,\r\n\t\t},\r\n\t\tsortDirection: {\r\n\t\t\tassets: sortDirections.ASC,\r\n\t\t\tplaces: sortDirections.ASC,\r\n\t\t\tfences: sortDirections.ASC,\r\n\t\t\tjourneys: sortDirections.ASC,\r\n\t\t\tsharedViews: sortDirections.ASC,\r\n\t\t},\r\n\t},\r\n\ttickOffset: 0,\r\n};\r\n\r\n// Adds/replaces the overrides to the `user` structure. Called from outside tracking.js\r\n// when the permissions of the user are known.\r\nexport function overrideUser(overrides) {\r\n\t_.merge(user, overrides);\r\n}\r\n\r\nexport default user;\r\n\r\nfunction applyDefaultExpandedGroupPreferences() {\r\n\tif (user.displayPreferences.expandedGroups === undefined || user.displayPreferences.expandedGroups === null) {\r\n\t\tuser.displayPreferences.expandedGroups = [];\r\n\t}\r\n\tif (user.displayPreferences.hideAllAssets !== undefined) {\r\n\t\tif (user.displayPreferences.hideAllAssets !== true) {\r\n\t\t\tuser.displayPreferences.expandedGroups.push('all-assets');\r\n\t\t}\r\n\t\tdelete user.displayPreferences.hideAllAssets;\r\n\t} else {\r\n\t\tif (trkData.assets.length < 200) {\r\n\t\t\tuser.displayPreferences.expandedGroups.push('all-assets');\r\n\t\t}\r\n\t}\r\n\tif (trkData.places.length < 200) {\r\n\t\tuser.displayPreferences.expandedGroups.push('all-places');\r\n\t}\r\n\tif (trkData.fences.length < 200) {\r\n\t\tuser.displayPreferences.expandedGroups.push('all-fences');\r\n\t}\r\n}\r\n\r\nexport function loadDisplayPreferences() {\r\n\tif (isStorageAvailable(\"localStorage\")) {\r\n\t\tif (\r\n\t\t\tlocalStorage.getItem(\"displayPreferences\") === null &&\r\n\t\t\tlocalStorage.getItem(\"displayPreferences-\" + user.id) === null\r\n\t\t) {\r\n\t\t\t// we may need to convert from cookies\r\n\t\t\tvar hiddenFences = $.cookie(\"hiddenFences\");\r\n\t\t\tvar hiddenAssets = $.cookie(\"hiddenAssets\");\r\n\t\t\tvar hiddenPlaces = $.cookie(\"hiddenPlaces\");\r\n\t\t\tvar hiddenGroups = $.cookie(\"hiddenGroups\");\r\n\r\n\t\t\t// remove old cookies\r\n\t\t\tif (hiddenFences != null) {\r\n\t\t\t\t$.cookie(\"hiddenFences\", null);\r\n\t\t\t}\r\n\t\t\tif (hiddenAssets != null) {\r\n\t\t\t\t$.cookie(\"hiddenAssets\", null);\r\n\t\t\t}\r\n\t\t\tif (hiddenPlaces != null) {\r\n\t\t\t\t$.cookie(\"hiddenPlaces\", null);\r\n\t\t\t}\r\n\t\t\tif (hiddenGroups != null) {\r\n\t\t\t\t$.cookie(\"hiddenGroups\", null);\r\n\t\t\t}\r\n\r\n\t\t\tif (hiddenFences == null || hiddenFences == \"\") hiddenFences = [];\r\n\t\t\telse hiddenFences = hiddenFences.split(\",\");\r\n\r\n\t\t\tif (hiddenAssets == null || hiddenAssets == \"\") hiddenAssets = [];\r\n\t\t\telse hiddenAssets = hiddenAssets.split(\",\");\r\n\r\n\t\t\tif (hiddenGroups == null || hiddenGroups == \"\") {\r\n\t\t\t\thiddenGroups = [];\r\n\t\t\t} else hiddenGroups = hiddenGroups.split(\",\");\r\n\r\n\t\t\tif (hiddenPlaces == null || hiddenPlaces == \"\") hiddenPlaces = [];\r\n\t\t\telse hiddenPlaces = hiddenPlaces.split(\",\");\r\n\r\n\t\t\t// load new preferences\r\n\t\t\tuser.displayPreferences.hiddenPlaces = hiddenPlaces;\r\n\t\t\tuser.displayPreferences.hiddenFences = hiddenFences;\r\n\t\t\tuser.displayPreferences.hiddenAssets = hiddenAssets;\r\n\t\t\tuser.displayPreferences.visibleTrips = [];\r\n\t\t\tuser.displayPreferences.customSort = {};\r\n\t\t\tuser.displayPreferences.sortMode = {\r\n\t\t\t\tassets: sortModes.ALPHABETICAL,\r\n\t\t\t\tfences: sortModes.ALPHABETICAL,\r\n\t\t\t\tplaces: sortModes.ALPHABETICAL,\r\n\t\t\t\tjourneys: sortModes.ALPHABETICAL,\r\n\t\t\t\tsharedViews: sortModes.ALPHABETICAL,\r\n\t\t\t};\r\n\t\t\tuser.displayPreferences.sortDirection = {\r\n\t\t\t\tassets: sortDirections.ASC,\r\n\t\t\t\tfences: sortDirections.ASC,\r\n\t\t\t\tplaces: sortDirections.ASC,\r\n\t\t\t\tjourneys: sortDirections.ASC,\r\n\t\t\t\tsharedViews: sortDirections.ASC,\r\n\t\t\t};\r\n\t\t\tuser.displayPreferences.warnings = {\r\n\t\t\t\taddresses: true,\r\n\t\t\t\tspeed: true,\r\n\t\t\t};\r\n\t\t\tapplyDefaultExpandedGroupPreferences();\r\n\t\t\t\r\n\t\t\tsaveDisplayPreferences();\r\n\t\t} else {\r\n\t\t\tif (localStorage.getItem(\"displayPreferences-\" + user.id) !== null) {\r\n\t\t\t\tuser.displayPreferences = JSON.parse(localStorage.getItem(\"displayPreferences-\" + user.id));\r\n\t\t\t} else {\r\n\t\t\t\tuser.displayPreferences = JSON.parse(localStorage.getItem(\"displayPreferences\"));\r\n\t\t\t}\r\n\r\n\t\t\tif (user.displayPreferences.hiddenPlaces === undefined) {\r\n\t\t\t\tuser.displayPreferences.hiddenPlaces = [];\r\n\t\t\t}\r\n\t\t\tif (user.displayPreferences.hiddenFences === undefined) {\r\n\t\t\t\tuser.displayPreferences.hiddenFences = [];\r\n\t\t\t}\r\n\t\t\tif (user.displayPreferences.hiddenAssets === undefined) {\r\n\t\t\t\tuser.displayPreferences.hiddenAssets = [];\r\n\t\t\t}\r\n\t\t\tif (user.displayPreferences.expandedGroups === undefined) {\r\n\t\t\t\tapplyDefaultExpandedGroupPreferences();\r\n\t\t\t}\r\n\t\t\tif (user.displayPreferences.visibleTrips === undefined) {\r\n\t\t\t\tuser.displayPreferences.visibleTrips = [];\r\n\t\t\t}\r\n\t\t\tif (user.displayPreferences.customSort === undefined) {\r\n\t\t\t\tuser.displayPreferences.customSort = {};\r\n\t\t\t}\r\n\t\t\tif (user.displayPreferences.sortMode === undefined) {\r\n\t\t\t\tuser.displayPreferences.sortMode = {\r\n\t\t\t\t\tassets: sortModes.ALPHABETICAL,\r\n\t\t\t\t\tfences: sortModes.ALPHABETICAL,\r\n\t\t\t\t\tplaces: sortModes.ALPHABETICAL,\r\n\t\t\t\t\tjourneys: sortModes.ALPHABETICAL,\r\n\t\t\t\t\tsharedViews: sortModes.ALPHABETICAL,\r\n\t\t\t\t};\r\n\t\t\t}\r\n\t\t\tif (user.displayPreferences.sortDirection === undefined) {\r\n\t\t\t\tuser.displayPreferences.sortDirection = {\r\n\t\t\t\t\tassets: sortDirections.ASC,\r\n\t\t\t\t\tfences: sortDirections.ASC,\r\n\t\t\t\t\tplaces: sortDirections.ASC,\r\n\t\t\t\t\tjourneys: sortDirections.ASC,\r\n\t\t\t\t\tsharedViews: sortDirections.ASC,\r\n\t\t\t\t};\r\n\t\t\t}\r\n\t\t\tif (user.displayPreferences.warnings === undefined) {\r\n\t\t\t\tuser.displayPreferences.warnings = {\r\n\t\t\t\t\taddresses: true,\r\n\t\t\t\t\tspeed: true,\r\n\t\t\t\t};\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction getDisplayPreferences(grouping) {\r\n\tswitch (grouping) {\r\n\t\tcase \"hiddenFences\":\r\n\t\t\treturn user.displayPreferences.hiddenFences;\r\n\t\tcase \"expandedGroups\":\r\n\t\t\treturn user.displayPreferences.expandedGroups;\r\n\t\tcase \"hiddenAssets\":\r\n\t\t\treturn user.displayPreferences.hiddenAssets;\r\n\t\tcase \"hiddenPlaces\":\r\n\t\t\treturn user.displayPreferences.hiddenPlaces;\r\n\t\tcase \"visibleTrips\":\r\n\t\t\treturn user.displayPreferences.visibleTrips;\r\n\t}\r\n\treturn null;\r\n}\r\n\r\nfunction setDisplayPreferences(grouping, value) {\r\n\tswitch (grouping) {\r\n\t\tcase \"hiddenFences\":\r\n\t\t\tuser.displayPreferences.hiddenFences = value;\r\n\t\t\tbreak;\r\n\t\tcase \"expandedGroups\":\r\n\t\t\tuser.displayPreferences.expandedGroups = value;\r\n\t\t\tbreak;\r\n\t\tcase \"hiddenAssets\":\r\n\t\t\tuser.displayPreferences.hiddenAssets = value;\r\n\t\t\tbreak;\r\n\t\tcase \"hiddenPlaces\":\r\n\t\t\tuser.displayPreferences.hiddenPlaces = value;\r\n\t\t\tbreak;\r\n\t\tcase \"visibleTrips\":\r\n\t\t\tuser.displayPreferences.visibleTrips = value;\r\n\t\t\tbreak;\r\n\t}\r\n\treturn;\r\n}\r\n\r\nexport function displayPreferencesRemove(grouping, id) {\r\n\t//log('Remove hidden preference: ' + grouping + ' -> ' + id);\r\n\tvar grp = getDisplayPreferences(grouping);\r\n\r\n\tif (grp == null) return;\r\n\tgrp = _.without(grp, id.toString());\r\n\tsetDisplayPreferences(grouping, grp);\r\n\tsaveDisplayPreferences();\r\n}\r\n\r\nexport function displayPreferencesAdd(grouping, id) {\r\n\tid = id.toString();\r\n\t//log('Add hidden preference: ' + grouping + ' -> ' + id);\r\n\r\n\tvar grp = getDisplayPreferences(grouping);\r\n\r\n\tif (grp == null) return;\r\n\r\n\tvar isFound = grp.indexOf(id) !== -1;\r\n\tif (!isFound) {\r\n\t\tgrp.push(id);\r\n\t}\r\n\tsetDisplayPreferences(grouping, grp);\r\n\tsaveDisplayPreferences();\r\n}\r\n\r\nexport function saveDisplayPreferences() {\r\n\tif (isStorageAvailable(\"localStorage\")) {\r\n\t\tlocalStorage.removeItem(\"displayPreferences\");\r\n\t\tlocalStorage.setItem(\"displayPreferences-\" + user.id, JSON.stringify(user.displayPreferences));\r\n\t}\r\n}\r\n\r\n//function saveDisplayPreferences() {\r\n// $j.cookie('hiddenFences', user.displayPreferences.hiddenFences.join(','), { expires: 365, path: '/', secure: true });\r\n// $j.cookie('hiddenAssets', user.displayPreferences.hiddenAssets.join(','), { expires: 365, path: '/', secure: true });\r\n// $j.cookie('hiddenGroups', user.displayPreferences.expandedGroups.join(','), { expires: 365, path: '/', secure: true });\r\n// $j.cookie('hiddenPlaces', user.displayPreferences.hiddenPlaces.join(','), { expires: 365, path: '/', secure: true });\r\n//}\r\n\r\nfunction isStorageAvailable(type) {\r\n\ttry {\r\n\t\tvar storage = window[type],\r\n\t\t\tx = \"__storage_test__\";\r\n\t\tstorage.setItem(x, x);\r\n\t\tstorage.removeItem(x);\r\n\t\treturn true;\r\n\t} catch (e) {\r\n\t\treturn (\r\n\t\t\te instanceof DOMException &&\r\n\t\t\t// everything except Firefox\r\n\t\t\t(e.code === 22 ||\r\n\t\t\t\t// Firefox\r\n\t\t\t\te.code === 1014 ||\r\n\t\t\t\t// test name field too, because code might not be present\r\n\t\t\t\t// everything except Firefox\r\n\t\t\t\te.name === \"QuotaExceededError\" ||\r\n\t\t\t\t// Firefox\r\n\t\t\t\te.name === \"NS_ERROR_DOM_QUOTA_REACHED\") &&\r\n\t\t\t// acknowledge QuotaExceededError only if there's something already stored\r\n\t\t\tstorage.length !== 0\r\n\t\t);\r\n\t}\r\n}\r\n","import user from \"./user.js\";\r\nimport options from \"./options.js\";\r\nimport trkData from \"./data.js\";\r\nimport _ from \"lodash\";\r\n\r\nexport function isSendCommandDisabledForDevice(device) {\r\n\tif (user.isAnonymous) {\r\n\t\treturn true;\r\n\t}\r\n\tif (options.enabledFeatures.indexOf(\"REMOTE_MANAGEMENT\") === -1) {\r\n\t\treturn true;\r\n\t}\r\n\tif (user.canSendCommands === undefined || !user.canSendCommands) {\r\n\t\treturn true;\r\n\t}\r\n\t// send command - this is dumb hardcoded\r\n\t// TODO property on the device\r\n\tvar groupedDeviceIds = [\r\n\t\tdevices.NAL,\r\n\t\tdevices.QUAKE_AIC,\r\n\t\tdevices.QUECLINK,\r\n\t\tdevices.FLIGHTCELL,\r\n\t\tdevices.GTTS,\r\n\t\tdevices.HUGHES,\r\n\t\tdevices.INMARSAT_C,\r\n\t\tdevices.SKYWAVE_IDP,\r\n\t\tdevices.SKYWAVE_IDP_DUAL_MODE,\r\n\t\tdevices.CALAMP,\r\n\t\tdevices.GSAT_MICROS,\r\n\t\tdevices.INREACH,\r\n\t\tdevices.GSE_FBB,\r\n\t];\r\n\tfor (var i = 0; i < groupedDeviceIds.length; i++) {\r\n\t\tif (_.indexOf(groupedDeviceIds[i], device.Id) !== -1) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\tvar individualDeviceIds = [\r\n\t\tdevices.IRIDIUM_EDGE,\r\n\t\tdevices.LT100,\r\n\t\tdevices.LT501,\r\n\t\tdevices.EDGE_SOLAR,\r\n\t\tdevices.DAN_TRACKER,\r\n\t\tdevices.GEOPRO,\r\n\t\tdevices.DPLUS,\r\n\t\tdevices.IRIDIUM_EXTREME,\r\n\t\tdevices.EDGE_SOLAR,\r\n\t\tdevices.TM3000,\r\n\t\tdevices.METOCEAN_ITRAC,\r\n\t];\r\n\r\n\tif (_.indexOf(individualDeviceIds, device.Id) !== -1) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\treturn true;\r\n}\r\n\r\nexport const devices = {\r\n\tDPLUS: 3, // ems satcom\r\n\tSKYWAVE_IDP: [26, 58, 60, 154, 157],\r\n\tSKYWAVE_IDP_DUAL_MODE: [49, 59, 70, 106, 367, 383],\r\n\tSKYWAVE_IDP_CELL_ONLY: [157],\r\n\tSKYWAVE_7XX: 59,\r\n\tSKYWAVE_782: 106,\r\n\tSKYWAVE_ST9100: 367,\r\n\tORBCOMM_ST9101: 383,\r\n\tSKYWAVE_782_CELL: 157,\r\n\tSKYWAVE_SG7100: 70,\r\n\tMOBILE: [18, 45, 56], // iPhone, Android, WP8\r\n\tGSATMICRO: [34, 275],\r\n\tGSATMICRO_GSM: [118, 276],\r\n\tGSATSOLARS: [361, 362, 380],\r\n\tISATMARKER: 163,\r\n\tGSAT_MICROS: [34, 118, 123, 163, 275, 276],\r\n\tNAL: [2, 76, 83, 84, 85, 124, 156, 164, 304, 305, 306],\r\n\tNAL_SHOUT_TS: 85,\r\n\tNAL_SHOUT_NANO: 84,\r\n\tNAL_GSM: [76, 164, 306],\r\n\tHUGHES: [166, 167, 168, 169, 170],\r\n\tQUAKE_AIC: [111],\r\n\tQUECLINK: [19, 73, 79, 80, 81, 82, 94, 95, 107, 130, 140, 142, 143, 153, 384, 391, 392, 396],\r\n\tFLIGHTCELL: [28, 261, 262],\r\n\tQUECLINK_GB100: 143,\r\n\tQUECLINK_GL200: 79,\r\n\tQUECLINK_GL300: 80,\r\n\tQUECLINK_GL300W: 130,\r\n\tQUECLINK_GL320MG: 391,\r\n\tQUECLINK_GL500: 81,\r\n\tQUECLINK_GL520: 142,\r\n\tQUECLINK_GL521MG: 392,\r\n\tQUECLINK_GV500: 82,\r\n\tQUECLINK_GV500MAP: 396,\r\n\tQUECLINK_GV200: 73,\r\n\tQUECLINK_GT300: 95,\r\n\tQUECLINK_GT301: 140,\r\n\tQUECLINK_GV300: 107,\r\n\tQUECLINK_GV600W: 153,\r\n\tQUECLINK_GV620MG: 384,\r\n\tGOTEK_PRIMELITE: 94,\r\n\tSPOT: [35, 151, 152, 159, 171, 172, 173, 174, 175, 176, 347, 351, 353, 354],\r\n\tINMARSAT_C: [41, 234, 235, 236],\r\n\tGTTS: [51, 100, 101],\r\n\tGTTS_2000BI: 51,\r\n\tGTTS_2000B: 101,\r\n\tGTTS_3000: 100,\r\n\tDAN_TRACKER: 125, // LTT10\r\n\tIRIDIUM_EDGE: 129,\r\n\tIRIDIUM_EXTREME: 16,\r\n\tTM3000: 13,\r\n\tCALAMP: [\r\n\t\t37, 232, 231, 230, 229, 228, 227, 226, 225, 224, 223, 222, 221, 220, 219, 218, 217, 216, 215, 214, 213, 212, 211,\r\n\t\t210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 199, 198, 197, 196,\r\n\t],\r\n\tINREACH: [46, 271, 272, 273, 274],\r\n\tGEOPRO: 57,\r\n\t//GARMIN_FLEET_590: 65,\r\n\tLT100: 135,\r\n\tLT501: 155,\r\n\tGSE_FBB: [9, 66],\r\n\tBEAM: 42,\r\n\tEDGE_SOLAR: 356,\r\n\tBIVY_STICK: 368,\r\n\tMETOCEAN_ITRAC: 397,\r\n};\r\n\r\nexport function findDeviceById(id) {\r\n\tvar item = _.find(trkData.devices, { Id: parseInt(id) });\r\n\treturn item === undefined ? null : item;\r\n}\r\n","const domNodes = {\r\n\tassets: {},\r\n\tassetNotificationMenu: null,\r\n\ttripNotificationMenu: null,\r\n\tgroups: {},\r\n\tgroupContents: {},\r\n\tgroupColors: {},\r\n\tassetsLive: {},\r\n\tassetsHistory: {},\r\n\tplaces: {},\r\n\tjourneys: {},\r\n\tsharedViews: {},\r\n\ttrips: {},\r\n\tfences: {},\r\n\tdialogs: {},\r\n\tinfoDialogs: {},\r\n\tmodals: {},\r\n\tmap: null,\r\n\tcontent: {\r\n\t\tbase: null,\r\n\t},\r\n\tmapTools: {\r\n\t\tcontainer: null,\r\n\t\tcurrentTime: null,\r\n\t\tvisibleSummary: null,\r\n\t},\r\n\tmapMode: {\r\n\t\tcontainer: null,\r\n\t\tcurrent: null,\r\n\t\tfrom: null,\r\n\t\tto: null,\r\n\t},\r\n\tnav: {\r\n\t\tprimary: null,\r\n\t\ttoggle: null,\r\n\t\tutility: null,\r\n\t},\r\n\tpanels: {\r\n\t\tprimary: null,\r\n\t\tsecondary: null,\r\n\t},\r\n\tsimpleBars: {\r\n\t\tprimary: null,\r\n\t\tsecondary: null,\r\n\t},\r\n\tfilter: {\r\n\t\tassetResults: null,\r\n\t\tfenceResults: null,\r\n\t\tplaceResults: null,\r\n\t\tjourneyResults: null,\r\n\t\tsharedViewResults: null,\r\n\t},\r\n\tmouseTooltip: {\r\n\t\tcontent: null,\r\n\t\ttitle: null,\r\n\t\tshow: false,\r\n\t\tposition: { x: 0, y: 0 },\r\n\t\treference: null,\r\n\t},\r\n\teventListingById: {},\r\n\tpositionListingById: {},\r\n\tmessageListingById: {},\r\n\tactivityListingById: {},\r\n\tsharedView: {\r\n\t\teventListingById: {},\r\n\t\tpositionListingById: {},\r\n\t\tmessageListingById: {},\r\n\t\tactivityListingById: {},\r\n\t},\r\n};\r\n\r\nexport default domNodes;\r\n","import options from \"./options.js\";\r\nimport user from \"./user.js\";\r\nimport state from \"./state.js\";\r\n\r\nexport default function log(msg) {\r\n\tvar m;\r\n\tif (typeof console === \"undefined\") {\r\n\t\treturn;\r\n\t}\r\n\tm = \"[\" + new Date().toISOString() + \"] Track: \" + msg;\r\n\tif (console.debug) {\r\n\t\tconsole.debug(m);\r\n\t} else if (console.log) {\r\n\t\tconsole.log(m);\r\n\t}\r\n}\r\n\r\nexport function getDbg() {\r\n\treturn {\r\n\t\tu: user.id,\r\n\t\td: options.isDemo,\r\n\t\ta: user.isAnonymous,\r\n\t\ti: user.isImpersonated,\r\n\t\tm: state.isMobile,\r\n\t\ts: state.isSafari,\r\n\t};\r\n}\r\n","/**\r\n * This is a copy of Iván's BlanketOverlay implementation, as per\r\n * https://github.com/Leaflet/Leaflet/pull/8611\r\n *\r\n * This whole file should be replaced by a \" import {BlanketOverlay} from leaflet\"\r\n * whenever BlanketOverlay makes it to a Leaflet release (which should be 2.0)\r\n */\r\n\r\nimport { Layer, DomUtil, Util, DomEvent, Bounds } from \"leaflet\";\r\n\r\n/*\r\n * @class BlanketOverlay\r\n * @inherits Layer\r\n * @aka L.BlanketOverlay\r\n *\r\n * Represents an HTML element that covers (\"blankets\") the entire surface\r\n * of the map.\r\n *\r\n * Do not use this class directly. It's meant for `Renderer`, and for plugins\r\n * that rely on one single HTML element\r\n */\r\n\r\nexport const BlanketOverlay = Layer.extend({\r\n\t// @section\r\n\t// @aka BlanketOverlay options\r\n\toptions: {\r\n\t\t// @option padding: Number = 0.1\r\n\t\t// How much to extend the clip area around the map view (relative to its size)\r\n\t\t// e.g. 0.1 would be 10% of map view in each direction\r\n\t\tpadding: 0.1,\r\n\r\n\t\t// @option continuous: Boolean = false\r\n\t\t// When `false`, the blanket will update its position only when the\r\n\t\t// map state settles (*after* a pan/zoom animation). When `true`,\r\n\t\t// it will update when the map state changes (*during* pan/zoom\r\n\t\t// animations)\r\n\t\tcontinuous: false,\r\n\t},\r\n\r\n\tinitialize(options) {\r\n\t\tUtil.setOptions(this, options);\r\n\t},\r\n\r\n\tonAdd() {\r\n\t\tif (!this._container) {\r\n\t\t\tthis._initContainer(); // defined by renderer implementations\r\n\r\n\t\t\t// always keep transform-origin as 0 0, #8794\r\n\t\t\tthis._container.classList.add(\"leaflet-zoom-animated\");\r\n\t\t}\r\n\r\n\t\tthis.getPane().appendChild(this._container);\r\n\t\tthis._resizeContainer();\r\n\t\tthis._onMoveEnd();\r\n\t},\r\n\r\n\tonRemove() {\r\n\t\tthis._destroyContainer();\r\n\t},\r\n\r\n\tgetEvents() {\r\n\t\tconst events = {\r\n\t\t\tviewreset: this._reset,\r\n\t\t\tzoom: this._onZoom,\r\n\t\t\tmoveend: this._onMoveEnd,\r\n\t\t\tzoomend: this._onZoomEnd,\r\n\t\t\tresize: this._resizeContainer,\r\n\t\t};\r\n\t\tif (this._zoomAnimated) {\r\n\t\t\tevents.zoomanim = this._onAnimZoom;\r\n\t\t}\r\n\t\tif (this.options.continuous) {\r\n\t\t\tevents.move = this._onMoveEnd;\r\n\t\t}\r\n\t\treturn events;\r\n\t},\r\n\r\n\t_onAnimZoom(ev) {\r\n\t\tthis._updateTransform(ev.center, ev.zoom);\r\n\t},\r\n\r\n\t_onZoom() {\r\n\t\tthis._updateTransform(this._map.getCenter(), this._map.getZoom());\r\n\t},\r\n\r\n\t_updateTransform(center, zoom) {\r\n\t\tconst scale = this._map.getZoomScale(zoom, this._zoom),\r\n\t\t\tviewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),\r\n\t\t\tcurrentCenterPoint = this._map.project(this._center, zoom),\r\n\t\t\ttopLeftOffset = viewHalf\r\n\t\t\t\t.multiplyBy(-scale)\r\n\t\t\t\t.add(currentCenterPoint)\r\n\t\t\t\t.subtract(this._map._getNewPixelOrigin(center, zoom));\r\n\r\n\t\tDomUtil.setTransform(this._container, topLeftOffset, scale);\r\n\t},\r\n\r\n\t_onMoveEnd(ev) {\r\n\t\t// Update pixel bounds of renderer container (for positioning/sizing/clipping later)\r\n\t\tconst p = this.options.padding,\r\n\t\t\tsize = this._map.getSize(),\r\n\t\t\tmin = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();\r\n\r\n\t\tthis._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());\r\n\r\n\t\tthis._center = this._map.getCenter();\r\n\t\tthis._zoom = this._map.getZoom();\r\n\t\tthis._updateTransform(this._center, this._zoom);\r\n\r\n\t\tthis._onSettled(ev);\r\n\t},\r\n\r\n\t_reset() {\r\n\t\tthis._onSettled();\r\n\t\tthis._updateTransform(this._center, this._zoom);\r\n\t\tthis._onViewReset();\r\n\t},\r\n\r\n\t/*\r\n\t * @section Subclass interface\r\n\t * @uninheritable\r\n\t * Subclasses must define the following methods:\r\n\t *\r\n\t * @method _initContainer(): undefined\r\n\t * Must initialize the HTML element to use as blanket, and store it as\r\n\t * `this._container`. The base implementation creates a blank `
`\r\n\t *\r\n\t * @method _destroyContainer(): undefined\r\n\t * Must destroy the HTML element in `this._container` and free any other\r\n\t * resources. The base implementation destroys the element and removes\r\n\t * any event handlers attached to it.\r\n\t *\r\n\t * @method _resizeContainer(): Point\r\n\t * The base implementation resizes the container (based on the map's size\r\n\t * and taking into account the padding), returning the new size in CSS pixels.\r\n\t *\r\n\t * Subclass implementations shall reset container parameters and data\r\n\t * structures as needed.\r\n\t *\r\n\t * @method _onZoomEnd(ev?: MouseEvent): undefined\r\n\t * (Optional) Runs on the map's `zoomend` event.\r\n\t *\r\n\t * @method _onViewReset(ev?: MouseEvent): undefined\r\n\t * (Optional) Runs on the map's `viewreset` event.\r\n\t *\r\n\t * @method _onSettled(): undefined\r\n\t * Runs whenever the map state settles after changing (at the end of pan/zoom\r\n\t * animations, etc). This should trigger the bulk of any rendering logic.\r\n\t *\r\n\t * If the `continuous` option is set to `true`, then this also runs on\r\n\t * any map state change (including *during* pan/zoom animations).\r\n\t */\r\n\t_initContainer() {\r\n\t\tthis._container = DomUtil.create(\"div\");\r\n\t},\r\n\t_destroyContainer() {\r\n\t\tDomEvent.off(this._container);\r\n\t\tthis._container.remove();\r\n\t\tdelete this._container;\r\n\t},\r\n\t_resizeContainer() {\r\n\t\tconst p = this.options.padding,\r\n\t\t\tsize = this._map\r\n\t\t\t\t.getSize()\r\n\t\t\t\t.multiplyBy(1 + p * 2)\r\n\t\t\t\t.round();\r\n\t\tthis._container.style.width = `${size.x}px`;\r\n\t\tthis._container.style.height = `${size.y}px`;\r\n\t\treturn size;\r\n\t},\r\n\t_onZoomEnd: Util.falseFn,\r\n\t_onViewReset: Util.falseFn,\r\n\t_onSettled: Util.falseFn,\r\n});\r\n","/**\r\n * @class AbstractAttributeSet\r\n *\r\n * Represents a set of attribute data for vertices (each set being a small slice\r\n * of contiguous memory), plus some niceties to add/modify records.\r\n *\r\n * Internally this represents a `gl.ARRAY_BUFFER` at the WebGL level, or a Vertex Buffer\r\n * Object (VBO) at the OpenGL level. It also includes the VertexAttrib call(s) needed\r\n * to use the attribute(s) contained here (since this is WebGL1 and there are no Vertex\r\n * Array Objects/VAOs, which would cache this).\r\n *\r\n * Note that an `AbstractAttributeSet` might correspond to just one attribute (and\r\n * offer the `BindableAttribute` interface), or several attributes (and do not offer\r\n * the `BindableAttribute` interface, but rather have properties of setters for such).\r\n *\r\n * This is the base abstract class - record size (amount of data per vertex)\r\n * for this class is zero.\r\n *\r\n */\r\nexport default class AbstractAttributeSet {\r\n\tconstructor(gl, options = {}, recordSize = 0) {\r\n\t\tthis._gl = gl;\r\n\r\n\t\t// Size of each record, in bytes. Must be set internally in each subclass.\r\n\t\tthis._recordSize = recordSize;\r\n\r\n\t\t// @option size: Number = 255\r\n\t\t// Maximum number of records (vertices) to hold\r\n\t\tthis._size = options.size || 255;\r\n\r\n\t\t// @option growFactor: Boolean = false\r\n\t\t// Specifies that the size of this attribute buffer is static.\r\n\t\t// @alternative growFactor: Number\r\n\t\t// When `growFactor` is a `Number`, the size of this attribute buffer will\r\n\t\t// grow by that factor (e.g. a factor of 2 means the buffer doubles its size each\r\n\t\t// time the size is insufficient)\r\n\t\tthis._growFactor = options.growFactor;\r\n\r\n\t\t// @option usage: GLenum = gl.STATIC_DRAW\r\n\t\t// One of `gl.STATIC_DRAW`, `gl.DYNAMIC_DRAW` or `gl.STREAM_DRAW`.\r\n\t\t// See the documentation of the `usage` parameter at\r\n\t\t// [WebGL's `bufferData`](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferData)\r\n\t\t// for more details.\r\n\t\tthis._usage = options.usage || gl.STATIC_DRAW;\r\n\r\n\t\t// Create a WebGL ARRAY_BUFFER with (record count) * (bytes per record) bytes\r\n\t\tthis._buf = gl.createBuffer();\r\n\t\tgl.bindBuffer(gl.ARRAY_BUFFER, this._buf);\r\n\t\tgl.bufferData(gl.ARRAY_BUFFER, this._recordSize * this._size, this._usage);\r\n\r\n\t\tif (this._growFactor) {\r\n\t\t\t// Growable attribute buffers need to store all the data in a\r\n\t\t\t// readable data structure, in order to call `bufferData` with\r\n\t\t\t// the new size without destroying data.\r\n\t\t\t// \t\t\tthis._arrayBuf = new ArrayBuffer(this._recordSize * this._size);\r\n\t\t\tthis._byteData = new Uint8Array(this._recordSize * this._size);\r\n\t\t}\r\n\t}\r\n\r\n\t// Maps a string containing a GLSL type to its number of components.\r\n\tstatic GLSL_TYPE_COMPONENTS = {\r\n\t\tfloat: 1,\r\n\t\tvec2: 2,\r\n\t\tvec3: 3,\r\n\t\tvec4: 4,\r\n\t\t// mat2: 4,\r\n\t\t// mat3: 9,\r\n\t\t// mat4: 16,\r\n\t};\r\n\r\n\t/**\r\n\t * @section Batch update methods\r\n\t * @method commit(index, length): this\r\n\t * Dumps the contents of the data in RAM into GPU memory. Will dump a\r\n\t * contiguous section of memory, for a block of vertices starting at `index`\r\n\t * and with the given `length`.\r\n\t *\r\n\t * Will fail if the attribute set has been created with a `growFactor` of zero.\r\n\t */\r\n\tcommit(index, length) {\r\n\t\tconst gl = this._gl;\r\n\t\tconst addr = this._recordSize * index;\r\n\t\tconst size = this._recordSize * length;\r\n\t\tconst data = new Uint8Array(this._byteData.buffer, addr, size);\r\n\r\n\t\tif (!isFinite(addr) || !isFinite(size)) {\r\n\t\t\tthrow new Error(\"Cannot commit attribute data witn non-finite start/length\");\r\n\t\t}\r\n\r\n\t\tgl.bindBuffer(gl.ARRAY_BUFFER, this._buf);\r\n\t\tgl.bufferSubData(gl.ARRAY_BUFFER, addr, data);\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Internal methods\r\n\t * @method set(index: Number, offset: Number, data: ArrayBufferView): this\r\n\t *\r\n\t * Uploads the given `data` to GPU memory, given the vertex `index` and the byte\r\n\t * `offset` into that record.\r\n\t *\r\n\t * It can be used to update one record at a time (passing a record-full of `data`),\r\n\t * one attribute field (less than a record-full of data, specifying `offset`), or several\r\n\t * contiguous records.\r\n\t *\r\n\t * The input `data` is *expected* to be in the same byte format than the expected\r\n\t * storage; subclasses do this by coercing `TypeArray`s of specific sizes. Users\r\n\t * wanting to dump binary data using this method are advised to pay attention to\r\n\t * the way the data is packed.\r\n\t *\r\n\t * Will grow self if allowed by `grow` when `index` is larger than the current size.\r\n\t * @alternative\r\n\t * @method set(index: Number, offset: Number, data: ArrayBuffer): this\r\n\t * In addition to `TypedArray`s of any kind and `DataView`s, this method can\r\n\t * also take `ArrayBuffer`s.\r\n\t */\r\n\tsetBytes(index, offset, data) {\r\n\t\t// if (index >= this._size) {\r\n\t\tconst upperIndex =\r\n\t\t\tindex + Math.floor((data.byteLength + offset) / this._recordSize);\r\n\t\tif (upperIndex > this._size) {\r\n\t\t\tthis._grow(upperIndex);\r\n\t\t}\r\n\r\n\t\tconst gl = this._gl;\r\n\t\t//if (gl.getParameter(gl.ARRAY_BUFFER_BINDING) !== this._buf) {\r\n\t\tgl.bindBuffer(gl.ARRAY_BUFFER, this._buf);\r\n\t\t//}\r\n\r\n\t\tconst addr = this._recordSize * index + offset;\r\n\r\n\t\tgl.bufferSubData(gl.ARRAY_BUFFER, addr, data);\r\n\r\n\t\tif (this._byteData) {\r\n\t\t\tif (data instanceof ArrayBuffer) {\r\n\t\t\t\tthis._byteData.set(new Uint8Array(data), addr);\r\n\t\t\t} else {\r\n\t\t\t\tthis._byteData.set(\r\n\t\t\t\t\tnew Uint8Array(data.buffer, data.byteOffset, data.byteLength),\r\n\t\t\t\t\taddr\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// Internal use only - grows the size of both the JS RAM `ArrayBuffer` and the\r\n\t// in-GPU GL arraybuffer/VBO.\r\n\t// Copies everything from the RAM-held copy `this._byteData`, then creates a\r\n\t// new, bigger RAM-held copy.\r\n\t_grow(minimum) {\r\n\t\tif (!this._growFactor) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t`Non-growable attribute buffer can only hold ${\r\n\t\t\t\t\tthis._size\r\n\t\t\t\t} records, but tried to set ${minimum + 1}-th record.`\r\n\t\t\t);\r\n\t\t}\r\n\t\tthis._size = Math.max(minimum + 1, Math.ceil(this._size * this._growFactor));\r\n\r\n\t\tconst newByteData = new Uint8Array(this._recordSize * this._size);\r\n\t\tnewByteData.set(this._byteData, 0);\r\n\t\tthis._byteData = newByteData;\r\n\r\n\t\tconst gl = this._gl;\r\n\t\tgl.bindBuffer(gl.ARRAY_BUFFER, this._buf);\r\n\t\tgl.bufferData(gl.ARRAY_BUFFER, this._byteData, this._usage);\r\n\t}\r\n}\r\n","export default [\r\n\t/**\r\n\t * @class Glii\r\n\t * @section Buffer usage constants\r\n\t * @aka Buffer usage constant\r\n\t *\r\n\t * Used in the `usage` option of `IndexBuffer`s and `AbstractAttributeSet`s,\r\n\t * these allegedly tell the hardware which region of GPU memory the data should\r\n\t * be into.\r\n\t *\r\n\t * @property STATIC_DRAW: Number\r\n\t * Hints the hardware that the contents of the buffer are likely to be used often\r\n\t * and not change often.\r\n\t * @property DYNAMIC_DRAW: Number\r\n\t * Hints the hardware that the contents of the buffer are likely to be used often\r\n\t * and change often.\r\n\t * @property STREAM_DRAW: Number\r\n\t * Hints the hardware that the contents of the buffer are likely to not be used often.\r\n\t */\r\n\t\"STATIC_DRAW\",\r\n\t\"DYNAMIC_DRAW\",\r\n\t\"STREAM_DRAW\",\r\n\r\n\t/**\r\n\t * @section Data type constants\r\n\t * @aka Data type constant\r\n\t *\r\n\t * Used in the `type` option of `IndexBuffer`s.\r\n\t *\r\n\t * Note that `BindableAttribute`s infer the data type from the subclass of `TypedArray`.\r\n\t *\r\n\t * @property BYTE: Number; 8-bit integer, complement-2 signed\r\n\t * @property UNSIGNED_BYTE: Number; 8-bit integer, unsigned\r\n\t * @property SHORT: Number; 16-bit integer, complement-2 signed\r\n\t * @property UNSIGNED_SHORT: Number; 16-bit integer, unsigned\r\n\t * @property INT: Number; 32-bit integer, complement-2 signed\r\n\t * @property UNSIGNED_INT: Number; 32-bit integer, unsigned\r\n\t * @property FLOAT: Number; 32-bit IEEE754 floating point\r\n\t */\r\n\t\"BYTE\",\r\n\t\"UNSIGNED_BYTE\",\r\n\t\"SHORT\",\r\n\t\"UNSIGNED_SHORT\",\r\n\t\"INT\",\r\n\t\"UNSIGNED_INT\",\r\n\t\"FLOAT\",\r\n\r\n\t/**\r\n\t * @section Texture pixel type constants\r\n\t * @aka Texture pixel type constant\r\n\t *\r\n\t * Used in the `type` option of `Texture`s, for the `type` parameter of `texImage2D` calls.\r\n\t *\r\n\t * Note that, in WebGL1, some values are only valid when an extension is loaded. See\r\n\t * [`WEBGL_depth_texture`](https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_depth_texture.html),\r\n\t * [`OES_texture_float`](https://developer.mozilla.org/en-US/docs/Web/API/OES_texture_float.html), and\r\n\t * [`OES_texture_half_float`](https://developer.mozilla.org/en-US/docs/Web/API/OES_texture_half_float.html).\r\n\t *\r\n\t * @property UNSIGNED_BYTE: Number; 8-bit integer, unsigned\r\n\t * @property UNSIGNED_SHORT_5_6_5: Number; 5 red bits, 6 green bits, 5 blue bits.\r\n\t * @property UNSIGNED_SHORT_4_4_4_4: Number; 4 red bits, 4 green bits, 4 blue bits, 4 alpha bits.\r\n\t * @property UNSIGNED_SHORT_5_5_5_1: Number; 5 red bits, 5 green bits, 5 blue bits, 1 alpha bit.\r\n\t * @property UNSIGNED_SHORT: Number; 16-bit integer, unsigned\r\n\t * @property UNSIGNED_INT: Number; 32-bit integer, unsigned\r\n\t * @property FLOAT: Number; 32-bit IEEE754 floating point\r\n\t */\r\n\r\n\t//\"UNSIGNED_BYTE\",\r\n\t\"UNSIGNED_SHORT_5_6_5\",\r\n\t\"UNSIGNED_SHORT_4_4_4_4\",\r\n\t\"UNSIGNED_SHORT_5_5_5_1\",\r\n\t//\"UNSIGNED_SHORT\",\r\n\t//\"UNSIGNED_INT\",\r\n\t//\"FLOAT\",\r\n\r\n\t/**\r\n\t * @section Draw mode constants\r\n\t * @aka Draw mode constant\r\n\t *\r\n\t * Used in the `drawMode` option of `SequentialIndices`, `IndexBuffer` and\r\n\t * `SparseIndices`. Determines how vertices (pointed by their indices) form draw\r\n\t * primitives.\r\n\t *\r\n\t * See [primitives in the OpenGL wiki](https://www.khronos.org/opengl/wiki/Primitive)\r\n\t * and [`drawElements` in Mozilla dev network](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements).\r\n\t *\r\n\t * @property POINTS: Number; Each vertex is drawn as a single point.\r\n\t * @property LINES: Number; Each set of two vertices is drawn as a line segment.\r\n\t * @property LINE_LOOP: Number\r\n\t * Each vertex connects to the next with a line segment. The last vertex connects to\r\n\t * the first.\r\n\t * @property LINE_STRIP: Number\r\n\t * Draw a line segment from the first vertex to each of the other vertices\r\n\t * @property TRIANGLES: Number\r\n\t * Each set of three vertices is drawn as a triangle (0-1-2, then 3-4-5, 6-7-8, etc)\r\n\t * @property TRIANGLE_STRIP: Number\r\n\t * Each group of three adjacent vertices is drawn as a triangle (0-1-2, then 2-3-4,\r\n\t * 3-4-5, etc). See [triangle strip on wikipedia](https://en.wikipedia.org/wiki/Triangle_strip)\r\n\t * @property TRIANGLE_FAN: Number\r\n\t * The first vertex plus each group of two adjacent vertices is drawn as a triangle.\r\n\t * See [triangle fan on wikipedia](https://en.wikipedia.org/wiki/Triangle_fan)\r\n\t */\r\n\t\"POINTS\",\r\n\t\"LINES\",\r\n\t\"LINE_LOOP\",\r\n\t\"LINE_STRIP\",\r\n\t\"TRIANGLES\",\r\n\t\"TRIANGLE_STRIP\",\r\n\t\"TRIANGLE_FAN\",\r\n\r\n\t/**\r\n\t * @section Texture format constants\r\n\t * @aka Texture format constant\r\n\t *\r\n\t * Determines the [image format](https://www.khronos.org/opengl/wiki/Image_Format)\r\n\t * of a `Texture`. Used in a `Texture`'s `format`&`internalFormat` options&properties.\r\n\t *\r\n\t * Some of these are only available when using a WebGL2 context. In some\r\n\t * cases, a few of the WebGL2-only formats are available when using a WebGL1\r\n\t * extension such as `OES_texture_float`.\r\n\t *\r\n\t * See https://registry.khronos.org/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE\r\n\t *\r\n\t * @property RGB: Number; Texture holds red, green and blue components.\r\n\t * @property RGBA: Number; Texture holds red, green, blue and alpha components.\r\n\t * @property ALPHA: Number; Texture holds only an alpha component\r\n\t * @property LUMINANCE: Number\r\n\t * Texture holds only a luminance component. This effectively makes the texture greyscale.\r\n\t * @property LUMINANCE_ALPHA: Number\r\n\t * Texture holds luminance and alpha. This effectively makes the texture grayscale with\r\n\t * transparency.\r\n\t * @property RED: Number; WebGL2 only. Texture holds red component only.\r\n\t * @property RG: Number\r\n\t * WebGL2 only. Texture holds red and green components only.\r\n\t * @property RED_INTEGER\r\n\t * WebGL2 only. Texture holds integers in its red component.\r\n\t * @property RG_INTEGER\r\n\t * WebGL2 only. Texture holds integer in its red and green components.\r\n\t * @property RGB_INTEGER\r\n\t * WebGL2 only. Texture holds integer in its red, green and blue components.\r\n\t * @property RGBA_INTEGER\r\n\t * WebGL2 only. Texture holds integer in its red, green, blue and alpha components.\r\n\t *\r\n\t */\r\n\t\"ALPHA\",\r\n\t\"RGB\",\r\n\t\"RGBA\",\r\n\t\"LUMINANCE\",\r\n\t\"LUMINANCE_ALPHA\",\r\n\r\n\t\"RED\",\r\n\t\"RG\",\r\n\t\"RED_INTEGER\",\r\n\t\"RG_INTEGER\",\r\n\t\"RGB_INTEGER\",\r\n\t\"RGBA_INTEGER\",\r\n\r\n\t/**\r\n\t * @section Texture interpolation constants\r\n\t * @aka Texture interpolation constant\r\n\t *\r\n\t * Determines the behaviour of texel interpolation (when a fragment shader requests\r\n\t * a texel coordinate which falls between several texels). This is used in the\r\n\t * `minFilter` and `maxFilter` options&properties of `Texture`s.\r\n\t *\r\n\t * See [sampler filtering on the OpenGL wiki](https://www.khronos.org/opengl/wiki/Sampler_Object#Filtering)\r\n\t *\r\n\t * @property NEAREST: Number; Nearest-texel interpolation\r\n\t * @property LINEAR: Number; Linear interpolation between texels\r\n\t * @property NEAREST_MIPMAP_NEAREST: Number\r\n\t * Nearest-texel interpolation, in the nearest mipmap\r\n\t * @property LINEAR_MIPMAP_NEAREST: Number\r\n\t * Linear interpolation between texels, in the nearest mipmap\r\n\t * @property NEAREST_MIPMAP_LINEAR: Number\r\n\t * Nearest-texel interpolation, in a linearly-interpolatex mipmap\r\n\t * @property LINEAR_MIPMAP_LINEAR: Number\r\n\t * Linear interpolation between texels, in a linearly-interpolated mipmap\r\n\t */\r\n\t\"NEAREST\",\r\n\t\"LINEAR\",\r\n\t\"NEAREST_MIPMAP_NEAREST\",\r\n\t\"LINEAR_MIPMAP_NEAREST\",\r\n\t\"NEAREST_MIPMAP_LINEAR\",\r\n\t\"LINEAR_MIPMAP_LINEAR\",\r\n\r\n\t/**\r\n\t * @section Texture wrapping constants\r\n\t * @aka Texture wrapping constant\r\n\t *\r\n\t * Used in the `wrapS`/`wrapT` options of a `Texture`.\r\n\t *\r\n\t * Determines the behaviour of texel sampling when the requested texel is outside\r\n\t * the bounds of the `Texture` (i.e. when the texel coordinate is outside the\r\n\t * [0..1] range).\r\n\t *\r\n\t * See [https://learnopengl.com/Getting-started/Textures](https://learnopengl.com/Getting-started/Textures)\r\n\t * for an illustrative example.\r\n\t *\r\n\t * @property REPEAT: Number; Texture repeats.\r\n\t * @property CLAMP_TO_EDGE: Number\r\n\t * Texels from the edge of the texture are used outside.\r\n\t * @property MIRRORED_REPEAT: Number\r\n\t * Texture repeats but is mirrored on every odd occurence.\r\n\t */\r\n\t\"REPEAT\",\r\n\t\"CLAMP_TO_EDGE\",\r\n\t\"MIRRORED_REPEAT\",\r\n\r\n\t/**\r\n\t * @section Renderbuffer format constants\r\n\t * @aka Renderbuffer format constant\r\n\t *\r\n\t * Determines the internal format of a `RenderBuffer` (to be attached as\r\n\t * either/both the depth component and/or the stencil component of a\r\n\t * framebuffer).\r\n\t *\r\n\t * See [`renderBufferStorage`](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/renderbufferStorage)\r\n\t *\r\n\t * Some of these are only available when using a WebGL2 context. (In some\r\n\t * cases, a few of the WebGL2-only formats are available when using a WebGL1\r\n\t * extension such as `WEBGL_depth_texture`).\r\n\t *\r\n\t * @property RGBA4: Number; 4 red bits, 4 green bits, 4 blue bits 4 alpha bits.\r\n\t * @property RGB565: Number; 5 red bits, 6 green bits, 5 blue bits.\r\n\t * @property RGB5_A1: Number; 5 red bits, 5 green bits, 5 blue bits, 1 alpha bit.\r\n\t * @property DEPTH_COMPONENT16: Number; Renderbuffer holds 16 bits of depth\r\n\t * @property STENCIL_INDEX8: Number; Renderbuffer holds 8 bits of stencil\r\n\t * @property DEPTH_STENCIL: Number; Renderbuffer holds both depth and stencil\r\n\t * (implementation-dependant; can be assumed to hold *at least* 16 bits of depth\r\n\t * and 8 bits of stencil on WebGL1; in WebGL2 it should behave as `DEPTH24_STENCIL8`)\r\n\t * @property DEPTH_COMPONENT24: Number; Renderbuffer holds 24 bits of depth\r\n\t * (WebGL2 only).\r\n\t * @property DEPTH_COMPONENT32F: Number; Renderbuffer holds depth as 32-bit\r\n\t * floating point (WebGL2 only).\r\n\t * @property DEPTH24_STENCIL8: Number; Renderbuffer holds 24 bits of depth\r\n\t * and 8 bits of stencil (WebGL2 only).\r\n\t * @property DEPTH32F_STENCIL8: Number; Renderbuffer holds depth as 32-bit\r\n\t * @property R32I: Number; Renderbuffer holds depth as 32-bit signed\r\n\t * integer.\r\n\t */\r\n\t\"RGBA4\",\r\n\t\"RGB565\",\r\n\t\"RGB5_A1\",\r\n\r\n\t\"DEPTH_COMPONENT16\",\t//0x81A5\r\n\t\"STENCIL_INDEX8\",\t//0x8D48\r\n\t\"DEPTH_STENCIL\",\t//0x84F9\r\n\r\n\t\"DEPTH_COMPONENT24\",\r\n\t\"DEPTH_COMPONENT32F\",\r\n\t\"DEPTH24_STENCIL8\",\r\n\t\"DEPTH32F_STENCIL8\",\r\n\r\n\t\"R32I\",\r\n\r\n\t/**\r\n\t * @section Comparison constants\r\n\t * @aka Comparison constant\r\n\t *\r\n\t * Used in the `depth` option of `WebGL1Program`.\r\n\t *\r\n\t * Use `glii.ALWAYS` to disable depth testing. Otherwise, the most usual\r\n\t * value is `glii.LEQUAL` or `glii.LESS`, to render fragments with a lower\r\n\t * `z` component of their `gl_Position` (\"closer to the camera\") over fragments\r\n\t * with a higher `z`.\r\n\t *\r\n\t * See [depth testing in learnopengl.com](https://learnopengl.com/Advanced-OpenGL/Depth-testing).\r\n\t *\r\n\t * @property NEVER: Number; Always fails (i.e. shall drop all fragments).\r\n\t * @property ALWAYS: Number; Disables depth testing.\r\n\t * @property LESS: Number\r\n\t * Render fragments that have a lower `z` (\"closer to the camera\") over others.\r\n\t * @property LEQUAL: Number\r\n\t * As `LESS`, but also renders fragments with the same `z`.\r\n\t * @property GREATER: Number\r\n\t * Render fragments that have a higher `z` (\"further away from the camera\") over others.\r\n\t * @property GEQUAL: Number\r\n\t * As `GREATER`, but also renders fragments with the same `z`.\r\n\t * @property EQUAL: Number\r\n\t * Only render fragments with the same `z` as the depth buffer value.\r\n\t * @property NOTEQUAL: Number; Opposite of `EQUAL`.\r\n\t */\r\n\t\"NEVER\",\r\n\t\"ALWAYS\",\r\n\t\"LESS\",\r\n\t\"LEQUAL\",\r\n\t\"GREATER\",\r\n\t\"GEQUAL\",\r\n\t\"EQUAL\",\r\n\t\"NOTEQUAL\",\r\n\r\n\t/**\r\n\t * @section Blend equation constants\r\n\t * @aka Blend equation constant\r\n\t *\r\n\t * Used in the `blend` option of `WebGL1Program`.\r\n\t *\r\n\t * Defines which kind of arithmetic operation is applied to the RGB and Alpha\r\n\t * channels of fragments when they need to be blended together (i.e. when\r\n\t * two or more fragments from several triangles have the same `x,y` position\r\n\t * in the output framebuffer)\r\n\t *\r\n\t * See `WebGLRenderingContext`'s [`blendEquationSeparate`](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/blendEquationSeparate)\r\n\t *\r\n\t * @property FUNC_ADD: Number; source + destination\r\n\t * @property FUNC_SUBTRACT: Number; source - destination\r\n\t * @property FUNC_REVERSE_SUBTRACT: Number; destination - source\r\n\t * @property MIN: Number; Minimum of source and destination\r\n\t * @property MAX: Number; Maximum of source and destination\r\n\t */\r\n\t\"FUNC_ADD\",\r\n\t\"FUNC_SUBTRACT\",\r\n\t\"FUNC_REVERSE_SUBTRACT\",\r\n\t\"MIN\",\r\n\t\"MAX\",\r\n\r\n\t/**\r\n\t * @section Blend factor constants\r\n\t * @aka Blend factor constant\r\n\t *\r\n\t * Used in the `blend` option of `WebGL1Program`.\r\n\t *\r\n\t * Defines what factors shall multiply the RGB and Alpha components of\r\n\t * overlapping fragments just prior to applying the \"blend equation\" operation.\r\n\t *\r\n\t * See `WebGLRenderingContext`'s [`blendFuncSeparate`](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/blendFuncSeparate)\r\n\t * @property ZERO: Number; Multiplies all colors by 0.\r\n\t * @property ONE: Number; Multiplies all colors by 1.\r\n\t * @property SRC_COLOR: Number; Multiplies all colors by the source colors.\r\n\t * @property ONE_MINUS_SRC_COLOR: Number; Multiplies all colors by 1 minus each source color.\r\n\t * @property DST_COLOR: Number; Multiplies all colors by the destination color.\r\n\t * @property ONE_MINUS_DST_COLOR: Number; Multiplies all colors by 1 minus each destination color.\r\n\t * @property SRC_ALPHA: Number; Multiplies all colors by the source alpha color.\r\n\t * @property ONE_MINUS_SRC_ALPHA: Number; Multiplies all colors by 1 minus the source alpha color.\r\n\t * @property DST_ALPHA: Number; Multiplies all colors by the destination alpha color.\r\n\t * @property ONE_MINUS_DST_ALPHA: Number; Multiplies all colors by 1 minus the destination alpha color.\r\n\t * @property CONSTANT_COLOR: Number; Multiplies all colors by a constant color.\r\n\t * @property ONE_MINUS_CONSTANT_COLOR: Number; Multiplies all colors by 1 minus a constant color.\r\n\t * @property CONSTANT_ALPHA: Number; Multiplies all colors by a constant alpha value.\r\n\t * @property ONE_MINUS_CONSTANT_ALPHA: Number; Multiplies all colors by 1 minus a constant alpha value.\r\n\t * @property SRC_ALPHA_SATURATE: Number; Multiplies the RGB colors by the smaller of either the source alpha color or the value of 1 minus the destination alpha color. The alpha value is multiplied by 1.\r\n\t *\r\n\t */\r\n\t\"ZERO\",\r\n\t\"ONE\",\r\n\t\"SRC_COLOR\",\r\n\t\"ONE_MINUS_SRC_COLOR\",\r\n\t\"DST_COLOR\",\r\n\t\"ONE_MINUS_DST_COLOR\",\r\n\t\"SRC_ALPHA\",\r\n\t\"ONE_MINUS_SRC_ALPHA\",\r\n\t\"DST_ALPHA\",\r\n\t\"ONE_MINUS_DST_ALPHA\",\r\n\t\"CONSTANT_COLOR\",\r\n\t\"ONE_MINUS_CONSTANT_COLOR\",\r\n\t\"CONSTANT_ALPHA\",\r\n\t\"ONE_MINUS_CONSTANT_ALPHA\",\r\n\t\"SRC_ALPHA_SATURATE\",\r\n];\r\n","import constantNames from \"./constantNames.mjs\";\r\n\r\nconst factories = {};\r\n// Inspired by Leaflet's addInitHook()\r\n// `fact` must be a factory function that expects a `WebGLContext`,\r\n// optionally expects an instances of `GliiFactory`, and\r\n// returns a (wrapped) class constructor.\r\nexport function registerFactory(name, fact) {\r\n\tfactories[name] = fact;\r\n}\r\n\r\n/**\r\n * @class Glii\r\n * @aka GliiFactory\r\n * @inherits EventTarget\r\n * Glii core. Wraps the functionality of a `WebGLRenderingContext`.\r\n *\r\n * Contains wrappers for buffer, program, texture classes; also contains\r\n * a partial set of WebGL constants (only the ones that need to be\r\n * specified as options/parameters to Glii classes).\r\n *\r\n * @example\r\n * ```\r\n * // The Glii factory class is the default export of the Glii module;\r\n * // importing it looks like...\r\n * import Glii from \"path_to_glii/index.mjs\";\r\n *\r\n * // Create a Glii factory instance from a canvas...\r\n * const glii = new Glii(document.getElementById(\"some-canvas\"));\r\n *\r\n * // ...and use such instance to spawn stuff...\r\n * let pointIndices = new glii.IndexBuffer({\r\n * \t// ...using constants available in the Glii factory instance.\r\n * \tdrawMode: glii.POINTS\r\n * });\r\n * ```\r\n *\r\n * Note that all Glii classes except for `GliiFactory` are meant to be instantiated from\r\n * the following wrapped classes. In other words: do not try to instantiate e.g.\r\n * `new IndexBuffer(...)`, but rather create a `GliiFactory` instance\r\n * (usually named lowercase `glii` in the documentation and examples) and instantiate\r\n * `new glii.IndexBuffer(...)`.\r\n *\r\n * Idem for WebGL constants: most (if not all) the constants needed in class constructors\r\n * are copied into the namespace of `GliiFactory`, as shown above with `glii.POINTS`.\r\n *\r\n */\r\n\r\nexport default class GliiFactory extends EventTarget {\r\n\t/**\r\n\t * @constructor GliiFactory(target: HTMLCanvasElement, contextAttributes?: Object)\r\n\t * Create a GL factory from a `HTMLCanvasElement`, and context attributes as per\r\n\t * [`getContext`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext)\r\n\t * @alternative\r\n\t * @constructor GliiFactory(target: WebGLRenderingContext)\r\n\t * Create a GL factory from an already instantiated `WebGLRenderingContext`\r\n\t * @alternative\r\n\t * @constructor GliiFactory(target: WebGL2RenderingContext)\r\n\t * Create a GL factory from an already instantiated `WebGL2RenderingContext`\r\n\t */\r\n\t/// TODO: Add another alternative, using only context attributes, which shall\r\n\t/// implicitly create the canvas.\r\n\tconstructor(target, contextAttributes) {\r\n\t\tsuper();\r\n\r\n\t\tif (!target || !target.constructor || !target.constructor.name) {\r\n\t\t\t// Happens on CI environments (gitlab CI)\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"Invalid target passed to GliiFactory constructor. Expected either a HTMLCanvasElement or a WebGLRenderingContext but got \" +\r\n\t\t\t\t\ttypeof target +\r\n\t\t\t\t\t\",\" +\r\n\t\t\t\t\tJSON.stringify(target) +\r\n\t\t\t\t\t\".\"\r\n\t\t\t);\r\n\t\t}\r\n\t\tswitch (target.constructor.name) {\r\n\t\t\tcase \"HTMLCanvasElement\":\r\n\t\t\t\tfunction get(name) {\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\treturn target.getContext(name, contextAttributes);\r\n\t\t\t\t\t} catch (e) {\r\n\t\t\t\t\t\treturn undefined;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis.gl =\r\n\t\t\t\t\tget(\"webgl2\") ||\r\n\t\t\t\t\tget(\"webgl\") ||\r\n\t\t\t\t\tget(\"experimental-webgl\") ||\r\n\t\t\t\t\tget(\"webgl-experimental\");\r\n\r\n\t\t\t\tif (!this.gl) {\r\n\t\t\t\t\tthrow new Error(\"Glii could not create a WebGL context from canvas.\");\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase \"WebGLRenderingContext\":\r\n\t\t\tcase \"WebGL2RenderingContext\":\r\n\t\t\tcase \"bound WebGLRenderingContext\": // Happens on headless using \"gl\" module\r\n\t\t\tcase \"bound WebGL2RenderingContext\":\r\n\t\t\t\tthis.gl = target;\r\n\t\t\t\tbreak;\r\n\t\t\tdefault:\r\n\t\t\t\tthrow new Error(\r\n\t\t\t\t\t\"Invalid target passed to GliiFactory constructor. Expected either a HTMLCanvasElement or a WebGLRenderingContext but got an instance of \" +\r\n\t\t\t\t\t\ttarget.constructor.name +\r\n\t\t\t\t\t\t\".\"\r\n\t\t\t\t);\r\n\t\t}\r\n\r\n\t\tconst gl = this.gl;\r\n\r\n\t\tthis._isWebGL2 =\r\n\t\t\tgl.constructor.name === \"WebGL2RenderingContext\" ||\r\n\t\t\tgl.constructor.name === \"bound WebGL2RenderingContext\";\r\n\r\n\t\t// Call all individual factory functions, assign the class constructors to\r\n\t\t// properties of this instance.\r\n\t\tfor (let factName in factories) {\r\n\t\t\tthis[factName] = factories[factName](gl, this);\r\n\t\t}\r\n\r\n\t\t// Copy constants from the `WebGLRenderingContext`.\r\n\t\tfor (let i in constantNames) {\r\n\t\t\tconst name = constantNames[i];\r\n\t\t\tthis[name] = gl[name];\r\n\t\t}\r\n\r\n\t\tif (\"canvas\" in gl) {\r\n\t\t\tgl.canvas.addEventListener(\r\n\t\t\t\t\"webglcontextlost\",\r\n\t\t\t\t(ev) => {\r\n\t\t\t\t\tconsole.warn(\"glii has lost context\", ev);\r\n\t\t\t\t\tev.preventDefault();\r\n\t\t\t\t},\r\n\t\t\t\tfalse\r\n\t\t\t);\r\n\t\t\tgl.canvas.addEventListener(\r\n\t\t\t\t\"webglcontextrestored\",\r\n\t\t\t\t(ev) => {\r\n\t\t\t\t\tconsole.warn(\"glii lost context has been restored\", ev);\r\n\t\t\t\t},\r\n\t\t\t\tfalse\r\n\t\t\t);\r\n\r\n\t\t\tconst resizeObserver = new ResizeObserver(this.#onResize.bind(this));\r\n\r\n\t\t\tresizeObserver.observe(gl.canvas, { box: \"content-box\" });\r\n\t\t}\r\n\r\n\t\tthis.refreshDrawingBufferSize();\r\n\r\n\t\tthis._loadedExtensions = new Map();\r\n\r\n\t\t/// TODO: simulate context loss with gl.getExtension('WEBGL_lose_context').loseContext();\r\n\r\n\t\t// \t\t// Fetch some info from the context\r\n\t\t//\r\n\t\t// \t\t// This kinda assumes that, when given a WebGLRenderingContext/\r\n\t\t// \t\t// WebGL2RenderingContext, there have been no framebuffer shenanigans.\r\n\t\t// \t\tthis._defaultFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);\r\n\t\t// \t\tthis._defaultRenderbuffer = gl.getParameter(gl.RENDERBUFFER_BINDING);\r\n\t\t// \t\tthis._glslVersion = gl.getParameter(gl.SHADING_LANGUAGE_VERSION);\r\n\t\t//\r\n\t\t// \t\tconst attachments = [gl.COLOR_ATTACHMENT0, gl.DEPTH_ATTACHMENT, gl.STENCIL_ATTACHMENT];\r\n\t\t// \t\tconst pnames = [\r\n\t\t// \t\t\tgl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,\r\n\t\t// \t\t\tgl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,\r\n\t\t// \t\t\tgl.FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL,\r\n\t\t// // \t\t\tgl.FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE\r\n\t\t// \t\t];\r\n\t\t//\r\n\t\t// // \t\t\tgl.bindFramebuffer(gl.FRAMEBUFFER, null);\r\n\t\t// \t\tthis._defaultAttachments = {};\r\n\t\t// \t\tfor (let att of attachments){\r\n\t\t// \t\t\tthis._defaultAttachments[att] = {};\r\n\t\t// \t\t\tfor (let i=0; i<0xFFFF; i++) {\r\n\t\t// // \t\t\t\tfor (let pname of pnames){\r\n\t\t// // \t\t\t\t\tconsole.log(att, pname);\r\n\t\t// // \t\t\t\t\tthis._defaultAttachments[att][pname] =\r\n\t\t// \t\t\t\tconst value =\r\n\t\t// // \t\t\t\t\t\tgl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, att, pname);\r\n\t\t// \t\t\t\t\tgl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, att, i);\r\n\t\t// // \t\t\t\t\t\tgl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, att, null);\r\n\t\t// \t\t\t\tif (value) {\r\n\t\t// \t\t\t\t\tconsole.log(att, i, value);\r\n\t\t// \t\t\t\t}\r\n\t\t// \t\t\t}\r\n\t\t// \t\t}\r\n\t\t//\r\n\t\t// \t\tconsole.log('default framebuffer: ', this._defaultFramebuffer);\r\n\t\t// \t\tconsole.log('default renderbuffer: ', this._defaultRenderbuffer);\r\n\t\t// \t\tconsole.log('default attachments: ', this._defaultAttachments);\r\n\t\t// \t\tconsole.log('GLSL version: ', this._glslVersion);\r\n\t}\r\n\r\n\t/**\r\n\t * @method getSupportedExtensions(): Array of String\r\n\t * Returns the list of GL extensions supported in the running platform, as per\r\n\t * https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getSupportedExtensions.html\r\n\t */\r\n\tgetSupportedExtensions() {\r\n\t\tif (this._knownExtensions) {\r\n\t\t\treturn this._knownExtensions;\r\n\t\t}\r\n\t\treturn (this._knownExtensions = this.gl.getSupportedExtensions());\r\n\t}\r\n\r\n\t/**\r\n\t * @method isExtensionSupported(extName: String): Boolean\r\n\t * Returns whether the given extension is supported in the running platform\r\n\t */\r\n\tisExtensionSupported(extName) {\r\n\t\treturn this.getSupportedExtensions().includes(extName);\r\n\t}\r\n\r\n\t/**\r\n\t * @method loadExtension(ext: String): Object\r\n\t * Tries to load the given GL extension. Throws an error if the extension is\r\n\t * not supported.\r\n\t *\r\n\t * Returns the extension object, which may vary by extension.\r\n\t */\r\n\tloadExtension(extName) {\r\n\t\tlet ext = this._loadedExtensions.get(extName);\r\n\t\tif (ext) {\r\n\t\t\treturn ext;\r\n\t\t} else {\r\n\t\t\tif (!this.isExtensionSupported(extName)) {\r\n\t\t\t\tthrow new Error(`WebGL extension ${extName} is not supported`);\r\n\t\t\t}\r\n\t\t\text = this.gl.getExtension(extName);\r\n\t\t\tthis._loadedExtensions.set(extName, ext);\r\n\t\t\treturn ext;\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method isWebGL2(): Boolean\r\n\t * Returns whether the Glii instance is using a `WebGL2RenderingContext` or\r\n\t * not.\r\n\t */\r\n\tisWebGL2() {\r\n\t\treturn this._isWebGL2;\r\n\t}\r\n\r\n\t// React to resize observer updates, and cache the dimensions (in device\r\n\t// pixels) of the canvas. The canvas is not updated immediately; instead\r\n\t// the `refreshDrawingBufferSize()` method should be called prior to a redraw\r\n\t#onResize(entries) {\r\n\t\tlet entry = entries[0];\r\n\r\n\t\t// From https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html\r\n\t\tlet width;\r\n\t\tlet height;\r\n\t\tlet dpr = devicePixelRatio ?? 1;\r\n\r\n\t\tif (entry.devicePixelContentBoxSize) {\r\n\t\t\t// NOTE: Only this path gives the correct answer\r\n\t\t\t// The other paths are imperfect fallbacks\r\n\t\t\t// for browsers that don't provide anyway to do this\r\n\t\t\twidth = entry.devicePixelContentBoxSize[0].inlineSize;\r\n\t\t\theight = entry.devicePixelContentBoxSize[0].blockSize;\r\n\t\t\tdpr = 1; // it's already in width and height\r\n\t\t} else if (entry.contentBoxSize) {\r\n\t\t\tif (entry.contentBoxSize[0]) {\r\n\t\t\t\twidth = entry.contentBoxSize[0].inlineSize;\r\n\t\t\t\theight = entry.contentBoxSize[0].blockSize;\r\n\t\t\t} else {\r\n\t\t\t\twidth = entry.contentBoxSize.inlineSize;\r\n\t\t\t\theight = entry.contentBoxSize.blockSize;\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\twidth = entry.contentRect.width;\r\n\t\t\theight = entry.contentRect.height;\r\n\t\t}\r\n\t\tthis.#resizedWidth = Math.round(width * dpr);\r\n\t\tthis.#resizedHeight = Math.round(height * dpr);\r\n\r\n\t\tthis._drawingBufferSizeChanged = true;\r\n\r\n\t\t/**\r\n\t\t * @event resize: CustomEvent\r\n\t\t * Fired whenever the underlying `` changes size. The next\r\n\t\t * call to `refreshDrawingBufferSize()` will update the output\r\n\t\t * framebuffer to the updated size (in device pixels).\r\n\t\t */\r\n\t\tthis.dispatchEvent(\r\n\t\t\tnew CustomEvent(\"resized\", {\r\n\t\t\t\tdetail: { x: this.#resizedWidth, y: this.#resizedHeight },\r\n\t\t\t})\r\n\t\t);\r\n\t}\r\n\r\n\t#resizedWidth;\r\n\t#resizedHeight;\r\n\r\n\t/**\r\n\t * @section Internal methods\r\n\t * @method refreshDrawingBufferSize(): Array of Number\r\n\t * Ensure that the size of the linked to the `WebGLRenderingContext`\r\n\t * matches the size provided by `getClientRect()`.\r\n\t *\r\n\t * Meant to be called from a `WebGL1Program` right before fetching the drawing buffer\r\n\t * size. This technique should lower blinking when the `` is resized.\r\n\t *\r\n\t * Returns the current canvas dimensions in `[width, height]` form.\r\n\t */\r\n\trefreshDrawingBufferSize() {\r\n\t\tif (this._drawingBufferSizeChanged) {\r\n\t\t\tconst canvas = this.gl.canvas;\r\n\t\t\tif (this.#resizedWidth) {\r\n\t\t\t\tthis._width = canvas.width = this.#resizedWidth;\r\n\t\t\t\tthis._height = canvas.height = this.#resizedHeight;\r\n\t\t\t} else {\r\n\t\t\t\tlet dpr = devicePixelRatio ?? 1;\r\n\t\t\t\tlet rect = canvas.getClientRects && canvas.getClientRects()[0];\r\n\t\t\t\tlet width, height;\r\n\r\n\t\t\t\tif (rect) {\r\n\t\t\t\t\t// Canvas is in the DOM, possibly with applied CSS\r\n\t\t\t\t\twidth = rect.width;\r\n\t\t\t\t\theight = rect.height;\r\n\t\t\t\t} else if (canvas.width) {\r\n\t\t\t\t\t// Canvas is *not* in the DOM, so trust its width/height\r\n\t\t\t\t\t/// FIXME: What if canvas is a WebGLRenderingContext?\r\n\t\t\t\t\twidth = canvas.width;\r\n\t\t\t\t\theight = canvas.height;\r\n\t\t\t\t} else if (canvas.drawingBufferWidth) {\r\n\t\t\t\t\twidth = canvas.drawingBufferWidth;\r\n\t\t\t\t\theight = canvas.drawingBufferHeight;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis._width = canvas.width = width * dpr;\r\n\t\t\t\tthis._height = canvas.height = height * dpr;\r\n\t\t\t}\r\n\t\t\tthis._drawingBufferSizeChanged = false;\r\n\t\t}\r\n\t\treturn [this._width, this._height];\r\n\t}\r\n\r\n\t/// TODO: lightweight event handler for resizing; uniforms might need to be re-set.\r\n}\r\n","// Maps each kind of TypedArray to the GL constant for the corresponding type.\r\n// Only includes kinds of JS TypedArray that have a counterpart in WebGL.\r\n\r\n// prettier-ignore\r\nexport default new Map([\r\n\t[Int8Array, 0x1400], // gl.BYTE\r\n\t[Uint8Array, 0x1401], // gl.UNSIGNED_BYTE\r\n\t[Uint8ClampedArray, 0x1401], // gl.UNSIGNED_BYTE\r\n\t[Int16Array, 0x1402], // gl.SHORT\r\n\t[Uint16Array, 0x1403], // gl.UNSIGNED_SHORT\r\n\t[Int32Array, 0x1404], // gl.INT\r\n\t[Uint32Array, 0x1405], // gl.UNSIGNED_INT\r\n\t[Float32Array, 0x1406], // gl.FLOAT\r\n]);\r\n\r\n/*\r\n * Notes:\r\n *\r\n * - For attributes, gl.INT and gl.UNSIGNED_INT can only\r\n * be used in WebGL2 (vertexAttrib*I*Pointer)\r\n *\r\n * - For index buffers, only unsigned types are valid.\r\n *\r\n * - While the rest of the code copies the GL constants from the WebGLRenderingContext,\r\n * this *assumes* the type constants are indeed constants. I always have this\r\n * fear that the actual integer values for these constants might change without notice.\r\n *\r\n */\r\n","// Partial regexp for precision qualifiers:\r\n// A capturing group that matches lowp|mediump|highp, then one or more spaces,\r\n// all of it optional.\r\nconst precisionQualifiers = \"((?:(?:lowp)|(?:mediump)|(?:highp))\\\\s+)?\";\r\n\r\n// Accepted GLSL types for attribute buffers\r\n// mat2/mat3/mat4 not yet here, see https://gitlab.com/IvanSanchez/glii/-/issues/18\r\n// A capturing group that matches float|vec2|vec3|vec4\r\nconst glsl1AttribTypes = \"((?:float)|(?:vec[2-4]))\";\r\n\r\n// Accepted GLSL types for declaration of varyings. Reused for uniforms.\r\n// A capturing group that matches float|int|bool|vec2|vec3|vec4|ivec2|ivec3|ivec4|\r\n// bvec2|bvec3|bvec4|mat2|mat3|mat4.\r\nconst glsl1VaryingTypes = \"((?:float)|(?:int)|(?:bool)|(?:[ib]?vec[2-4])|(?:mat[2-4]))\";\r\n\r\nconst regexpAttrib = new RegExp(\r\n\t// Nothing before.\r\n\t\"^\" +\r\n\t\t// First capturing group: optional precision qualifier\r\n\t\tprecisionQualifiers +\r\n\t\t// Second capturing group: float|vecN.\r\n\t\tglsl1AttribTypes +\r\n\t\t// Nothing afterwards.\r\n\t\t\"$\"\r\n);\r\n\r\nconst regexpVarying = new RegExp(\"^\" + precisionQualifiers + glsl1VaryingTypes + \"$\");\r\n\r\n/**\r\n * Parses a string containing:\r\n * - An (optional) precision qualifier\r\n * - A GLSL type for an attribute\r\n *\r\n * Returns a string of the form [precision, type]\r\n */\r\nexport function parseGlslAttribType(str) {\r\n\tconst match = regexpAttrib.exec(str);\r\n\tif (!match) {\r\n\t\tthrow new Error(\r\n\t\t\t`Invalid GLSL type. Expected float|vec2|vec3|vec4 (optionally prepended by lowp|mediump|highp), but found \"${str}\"`\r\n\t\t);\r\n\t}\r\n\tconst [_, precision, type] = match;\r\n\treturn [precision, type];\r\n}\r\n\r\n/**\r\n * Parses a string containing:\r\n * - An (optional) precision qualifier\r\n * - A GLSL type for a varying (reused for uniforms)\r\n *\r\n * Returns a string of the form [precision, type]\r\n */\r\nexport function parseGlslVaryingType(str) {\r\n\tconst match = regexpVarying.exec(str);\r\n\tif (!match) {\r\n\t\tthrow new Error(\r\n\t\t\t`Invalid GLSL type. Expected float|vec(234)|(ib)vec(234)|mat(234) (optionally prepended by lowp|mediump|highp), but found \"${str}\"`\r\n\t\t);\r\n\t}\r\n\tconst [_, precision, type] = match;\r\n\treturn [precision, type];\r\n}\r\n\r\nexport const parseGlslUniformType = parseGlslVaryingType;\r\n","import { default as typeMap } from \"../util/typeMap.mjs\";\r\n\r\nfunction stridify(arrayType) {\r\n\t/**\r\n\t * @class StridedTypedArray\r\n\t *\r\n\t * This is not a standalone class, but a decorated `TypedArray`, with\r\n\t * the ability to store items based on a *record index* instead of an\r\n\t * index relative to the number of elements in the array.\r\n\t *\r\n\t * @example\r\n\t * ```\r\n\t * // Get a strided array representation of the 3rd field in an interleaved\r\n\t * // attribute set (position `2` since it's zero-indexed), capable of\r\n\t * // holding at least 1000 vertices\r\n\t * let strided = myInterlavedAttributes.asStridedArray(2, 1000);\r\n\t *\r\n\t * // Assuming the field is a `vec3`, this will store data for the 42th vertex\r\n\t * // (`41` because, again, it's zero-indexed)\r\n\t * strided.set([x, y, z], 41);\r\n\t * ```\r\n\t */\r\n\treturn class StridedTypeArray extends arrayType {\r\n\t\t#stride;\r\n\t\t#offset;\r\n\r\n\t\tconstructor(buffer, stride, offset) {\r\n\t\t\tsuper(buffer);\r\n\r\n\t\t\tthis.#stride = stride;\r\n\t\t\tthis.#offset = offset;\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * @method set(values: Array of Number, index: Number = 0): undefined\r\n\t\t * Sets the given values (as per\r\n\t\t * [`TypedArray.set()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set)),\r\n\t\t * but taking an `index` (relative to the number of records) instead of\r\n\t\t * an `offset` (relative to the number of elements in the array).\r\n\t\t *\r\n\t\t * No sanity checks are performed on the length on the input data. (In\r\n\t\t * other words: if a `StridedTypeArray` represents a `vec2` attribute,\r\n\t\t * then `values` should have a length of `2`, idem for `vec2` and `vec4`).\r\n\t\t */\r\n\t\tset(values, index) {\r\n\t\t\tsuper.set(values, index * this.#stride + this.#offset);\r\n\t\t}\r\n\t};\r\n}\r\n\r\nconst stridedArrays = new Map(Array.from(typeMap.keys()).map((t) => [t, stridify(t)]));\r\n\r\nexport default stridedArrays;\r\n","import { default as AbstractAttributeSet } from \"./AbstractAttributeSet.mjs\";\r\nimport { registerFactory } from \"../GliiFactory.mjs\";\r\nimport { default as typeMap } from \"../util/typeMap.mjs\";\r\nimport { parseGlslAttribType } from \"../util/parseGlslType.mjs\";\r\nimport stridedArrays from \"./StridedTypedArrays.mjs\";\r\n\r\n/**\r\n * @class SingleAttribute\r\n * @inherits AbstractAttributeSet\r\n * @inherits BindableAttribute\r\n *\r\n * Represents a `gl.ARRAY_BUFFER` holding data for a single attribute.\r\n *\r\n * @example\r\n *\r\n * ```\r\n * const posInPlane = new glii.SingleAttribute({\r\n * \ttype: Float32Array\r\n * \tglslType: 'vec2'\r\n * });\r\n *\r\n * const rgbaColour = new glii.SingleAttribute({\r\n * \ttype: Uint8Array,\r\n * \tnormalized: true,\r\n * \tglslType: 'vec4'\r\n * });\r\n * ```\r\n */\r\n\r\n/// TODO: Somehow implement integer GLSL types for WebGL2.\r\n\r\nexport default class SingleAttribute extends AbstractAttributeSet {\r\n\tconstructor(gl, options) {\r\n\t\t/**\r\n\t\t * @section\r\n\t\t * @aka SingleAttribute options\r\n\t\t * @option type: prototype = Float32Array\r\n\t\t * A specific subclass of `TypedArray` defining the data format\r\n\t\t */\r\n\t\tconst type = options.type || Float32Array;\r\n\t\tconst bytesPerElement = type.BYTES_PER_ELEMENT;\r\n\r\n\t\t/**\r\n\t\t * @option glslType: String = 'float'\r\n\t\t * The GLSL type associated with this attribute. One of `float`, `vec2`, `vec3`, `vec4`, with an optional precision qualifier after it (`lowp`, `mediump` or\r\n\t\t * `highp`, e.g. `\"mediump vec3\"`).\r\n\t\t *\r\n\t\t * This also defines the number of components for this attribute (1, 2, 3 or 4, respectively).\r\n\t\t *\r\n\t\t * `matN` attributes are not supported (yet), see https://gitlab.com/IvanSanchez/glii/-/issues/18\r\n\t\t */\r\n\t\tconst fullGlslType = options.glslType || \"float\";\r\n\t\tconst [glslPrecision, glslType] = parseGlslAttribType(fullGlslType);\r\n\t\tif (!(glslType in AbstractAttributeSet.GLSL_TYPE_COMPONENTS)) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"Invalid value for the `glslType` option; must be `float`, `vec2`, `vec3`, or `vec4`.\"\r\n\t\t\t);\r\n\t\t}\r\n\t\tconst componentCount = AbstractAttributeSet.GLSL_TYPE_COMPONENTS[glslType];\r\n\r\n\t\tsuper(gl, options, bytesPerElement * componentCount);\r\n\r\n\t\tthis._glslType = fullGlslType;\r\n\t\tthis._componentCount = componentCount;\r\n\t\tthis._glType = typeMap.get(type);\r\n\r\n\t\tthis._normalized = options.normalized;\r\n\r\n\t\t/**\r\n\t\t * @method set(index: Number, value: Number): this\r\n\t\t * Alias of `setNumber`, available when `glslType` is `float`.\r\n\t\t * @alternative\r\n\t\t * @method set(index: Number, values: [Number]): this\r\n\t\t * Alias of `setArray`, available when `glslType` is `vec2`, `vec3` or `vec4`. `values`\r\n\t\t * must be an array of length 2, 3 or 4 (respectively).\r\n\t\t */\r\n\t\tif (options.glslType === \"float\") {\r\n\t\t\tthis.set = this.setNumber;\r\n\t\t} else {\r\n\t\t\tthis.set = this.setArray;\r\n\t\t}\r\n\r\n\t\tthis._recordBuf = new type(componentCount);\r\n\t\tthis._arrayType = type;\r\n\t}\r\n\r\n\t/**\r\n\t * @method setNumber(index: Number, value: Number): this\r\n\t * Sets the value for the `index`th vertex. Valid when `glslType` is `float`.\r\n\t */\r\n\tsetNumber(index, value) {\r\n\t\tthis._recordBuf[0] = value;\r\n\t\tsuper.setBytes(index, 0, this._recordBuf);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method setArray(index: Number, values: Array of Number): this\r\n\t * Sets the values for the `index`th vertex. Valid when `glslType` is `vec2`, `vec3` or `vec4`. `val` must be an array of length 2, 3 or 4 (respectively).\r\n\t */\r\n\tsetArray(index, values) {\r\n\t\tif (values.length !== this._componentCount) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t`Expected ${this._componentCount} values but got ${values.length}.`\r\n\t\t\t);\r\n\t\t}\r\n\t\tthis._recordBuf.set(values);\r\n\t\tsuper.setBytes(index, 0, this._recordBuf);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Batch update methods\r\n\t *\r\n\t * These methods are a less convenient, but more performant, way of updating\r\n\t * attribute data.\r\n\t *\r\n\t * For a `SingleAttribute`, the workflow is:\r\n\t * - Call `asTypedArray()`\r\n\t * - Update the values in the returned typed array (using typed array offsets,\r\n\t * avoiding array concatenations)\r\n\t * - Call `commit()`\r\n\t *\r\n\t * These methods need the attribute set to have been created with a `growFactor`\r\n\t * larger than zero.\r\n\t *\r\n\t * @method asStridedArray(minSize: Number): StridedTypedArray\r\n\t * Returns a view of the internal in-RAM data buffer, as a `TypedArray` of\r\n\t * the appropriate type.\r\n\t */\r\n\tasStridedArray(minSize) {\r\n\t\tif (minSize > this._size) {\r\n\t\t\tthis._grow(minSize);\r\n\t\t}\r\n\t\treturn new (stridedArrays.get(this._arrayType))(\r\n\t\t\tthis._byteData.buffer,\r\n\t\t\tthis._componentCount,\r\n\t\t\t0\r\n\t\t);\r\n\t}\r\n\r\n\t/**\r\n\t * @method multiSet(index: Number, values: Array of Number): this\r\n\t *\r\n\t * Batch version of `setArray()`.\r\n\t *\r\n\t * Sets values for several contiguous values at once, starting with the `index`th.\r\n\t *\r\n\t * The length of `values` must be a multiple of 2, 3 or 4 when `glslType` is `vec2`,\r\n\t * `vec3` or `vec4` (respectively). `values` must be a flat array (i.e. run\r\n\t * `.flat()` if needed).\r\n\t */\r\n\tmultiSet(index, values) {\r\n\t\tif (values.length % this._componentCount) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t`Expected values to be a multiple of ${this._componentCount} but got ${values.length}.`\r\n\t\t\t);\r\n\t\t}\r\n\t\tsuper.setBytes(index, 0, this._arrayType.from(values));\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// Method implementing `BindableAttribute` interface.\r\n\tbindWebGL1(location) {\r\n\t\tconst gl = this._gl;\r\n\t\tgl.bindBuffer(gl.ARRAY_BUFFER, this._buf);\r\n\t\tgl.enableVertexAttribArray(location);\r\n\t\tgl.vertexAttribPointer(\r\n\t\t\tlocation,\r\n\t\t\tthis._componentCount,\r\n\t\t\tthis._glType,\r\n\t\t\tthis._normalized,\r\n\t\t\tthis._recordSize, // stride\r\n\t\t\t0 // offset\r\n\t\t);\r\n\t}\r\n\r\n\t// Method implementing `BindableAttribute` interface.\r\n\tgetGlslType() {\r\n\t\treturn this._glslType;\r\n\t}\r\n\r\n\t/**\r\n\t * @method destroy(): this\r\n\t * Tells WebGL to free resources associated with this `SingleAttribute`. Use\r\n\t * when the `SingleAttribute` won't be used anymore.\r\n\t *\r\n\t * After being destroyed, WebGL programs should not use the destroyed `SingleAttribute`.\r\n\t */\r\n\tdestroy() {\r\n\t\tthis._gl.deleteBuffer(this._buf);\r\n\t}\r\n\r\n\tdebugDump(start, length) {\r\n\t\tstart ??= 0;\r\n\t\tlength ??= this._size;\r\n\t\tconst end = start + length;\r\n\r\n\r\n\t\tconst view = new this._arrayType(this._byteData.buffer);\r\n\r\n\t\t// return Array.from(new Array(this._size), (_, i) => {\r\n\t\tconst result = new Array(this._size);\r\n\r\n\t\tfor (let i=start; i new f.type(this._recordBuf, f.offset, f.components)\r\n\t\t);\r\n\t}\r\n\r\n\t/**\r\n\t * @method setField(vertexIndex: Number, fieldIndex, values: Array of Number): this\r\n\t * Sets the value(s) for the given vertex index and 0-indexed field.\r\n\t *\r\n\t * Values must be given as an `Array` (or `Array-like`) of numbers, even for\r\n\t * 1-component fields with the `float` GLSL type.\r\n\t */\r\n\tsetField(vertexIndex, fieldIndex, values) {\r\n\t\tthis._typedArrays[fieldIndex].set(values);\r\n\t\tsuper.setBytes(\r\n\t\t\tvertexIndex,\r\n\t\t\tthis._fields[fieldIndex].offset,\r\n\t\t\tthis._typedArrays[fieldIndex]\r\n\t\t);\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method setFields(vertexIndex: Number, values: Array of Array of Number): this\r\n\t * Sets the value(s) for all fields for the given vertex index.\r\n\t *\r\n\t * Values must be given as an `Array`; the `n`th element in this `Array` must be an\r\n\t * `Array` (or `Array-like`) of arrays of numbers with the values for the `n`th\r\n\t * field.\r\n\t */\r\n\tsetFields(vertexIndex, values) {\r\n\t\tthis._typedArrays.forEach((arr, f) => {\r\n\t\t\tarr.set(values[f]);\r\n\t\t});\r\n\r\n\t\tsuper.setBytes(vertexIndex, 0, this._recordBuf);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method multiSet(vertexIndex: Number, values: Array of Array of Array of Number): this\r\n\t *\r\n\t * Batch version of `setFields`. Instead of\r\n\t * `attrs.setFields(i, foo); attrs.setFields(i+1,bar);` one can do\r\n\t * `attrs.multiSet(i, [foo, bar])`.\r\n\t */\r\n\tmultiSet(vertexIndex, values) {\r\n\t\tconst multiBuf = new ArrayBuffer(this._recordSize * values.length);\r\n\t\tconst tmpDst = new Uint8Array(multiBuf);\r\n\t\tconst tmpSrc = new Uint8Array(this._recordBuf);\r\n\r\n\t\t/// TODO: Possible optimization here - dump values directly to `tmp`\r\n\t\t/// by looping through offsets, instead of dumping to `_this._recordBuf`\r\n\t\t/// and then copying it.\r\n\t\t/// That would require some refactoring of `this._fields`, however.\r\n\t\tvalues.forEach((fields, i) => {\r\n\t\t\tthis._typedArrays.forEach((arr, f) => {\r\n\t\t\t\tarr.set(fields[f]);\r\n\t\t\t});\r\n\t\t\ttmpDst.set(tmpSrc, this._recordSize * i);\r\n\t\t});\r\n\r\n\t\tsuper.setBytes(vertexIndex, 0, multiBuf);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method getBindableAttribute(fieldIndex: Number): BindableAttribute\r\n\t */\r\n\tgetBindableAttribute(fieldIndex) {\r\n\t\tconst field = this._fields[fieldIndex];\r\n\t\tconst glType = typeMap.get(field.type);\r\n\t\treturn {\r\n\t\t\tbindWebGL1: function bindWebGL1(location) {\r\n\t\t\t\tif (location === -1) {\r\n\t\t\t\t\tconsole.warn(\"Tried to bind an attribute not in use\");\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\tconst gl = this._gl;\r\n\t\t\t\tgl.bindBuffer(gl.ARRAY_BUFFER, this._buf);\r\n\t\t\t\tgl.enableVertexAttribArray(location);\r\n\t\t\t\tgl.vertexAttribPointer(\r\n\t\t\t\t\tlocation,\r\n\t\t\t\t\tfield.components,\r\n\t\t\t\t\tglType,\r\n\t\t\t\t\tfield.normalized,\r\n\t\t\t\t\tthis._recordSize, // stride\r\n\t\t\t\t\tfield.offset // offset\r\n\t\t\t\t);\r\n\t\t\t}.bind(this),\r\n\r\n\t\t\tgetGlslType: function getGlslType() {\r\n\t\t\t\treturn field.glslType;\r\n\t\t\t},\r\n\r\n\t\t\tdebugDump: function debugDump(start, length) {\r\n\t\t\t\tstart ??= 0;\r\n\t\t\t\tlength ??= this._size;\r\n\t\t\t\tconst end = start + length;\r\n\r\n\t\t\t\tconst result = new Array(this._size);\r\n\r\n\t\t\t\tfor (let i=start; i this._size) {\r\n\t\t\tthis._grow(minSize);\r\n\t\t}\r\n\t\tconst { type, offset } = this._fields[fieldIndex];\r\n\t\tconst bpe = type.BYTES_PER_ELEMENT;\r\n\r\n\t\treturn new (stridedArrays.get(type))(\r\n\t\t\tthis._byteData.buffer,\r\n\t\t\tthis._recordSize / bpe,\r\n\t\t\toffset / bpe\r\n\t\t);\r\n\t}\r\n\r\n\t/**\r\n\t * @method destroy(): this\r\n\t * Tells WebGL to free resources associated with this `InterleavedAttributes`. Use\r\n\t * when the `InterleavedAttributes` won't be used anymore.\r\n\t *\r\n\t * After being destroyed, WebGL programs should not use any `BindableAttribute`\r\n\t * linked to a destroyed `InterleavedAttributes`.\r\n\t */\r\n\tdestroy() {\r\n\t\tthis._gl.deleteBuffer(this._buf);\r\n\t}\r\n}\r\n\r\n/**\r\n * @factory GliiFactory.InterleavedAttributes(options: InterleavedAttributes options, fields: Array of BindableAttributeOptions)\r\n * @class Glii\r\n * @section Class wrappers\r\n * @property InterleavedAttributes(options: InterleavedAttributes options, fields: Array of BindableAttributeOptions): Prototype of InterleavedAttributes\r\n * Wrapped `InterleavedAttributes` class\r\n */\r\nregisterFactory(\"InterleavedAttributes\", function (gl) {\r\n\treturn class WrappedInterleavedAttributes extends InterleavedAttributes {\r\n\t\tconstructor(options, fields) {\r\n\t\t\tsuper(gl, options, fields);\r\n\t\t}\r\n\t};\r\n});\r\n","import { registerFactory } from \"../GliiFactory.mjs\";\r\n\r\n/**\r\n * @class SequentialIndices\r\n *\r\n * Represents a set of sequential vertex indices. The order cannot\r\n * be changed.\r\n *\r\n * A `SequentialIndices` of size `n` behaves (in practice) the same as a\r\n * `IndexBuffer` of size `n` with sequential indices starting at zero.\r\n *\r\n * In other words, the following two are equivalent in practice:\r\n *\r\n * ```\r\n * let seqIdx = new glii.SequentialIndices({ size: 9 });\r\n *\r\n * let idxBuf = new glii.IndexBuffer({ size: 9 });\r\n * idxBuf.set(0, [0,1,2,3,4,5,6,7,8]);\r\n * ```\r\n *\r\n * Internally, this performs a `gl.drawArrays` calls instead of `gl.drawElements`.\r\n *\r\n */\r\n\r\nexport default class SequentialIndices {\r\n\tconstructor(gl, options = {}) {\r\n\t\tthis._gl = gl;\r\n\r\n\t\t// @section\r\n\t\t// @aka SequentialIndices options\r\n\t\t// @option size: Number = 3\r\n\t\t// Exact number of indices to refer to.\r\n\t\tthis._size = options.size || 3;\r\n\r\n\t\t// @option drawMode: Draw mode constant = glii.TRIANGLES\r\n\t\t// Determines what kind of primitive is represented by the vertices\r\n\t\t// pointed by the indices.\r\n\t\tthis._drawMode = options.drawMode === undefined ? gl.TRIANGLES : options.drawMode;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Internal methods\r\n\t * @uninheritable\r\n\t * @method drawMe(): undefined\r\n\t * Internal use only. Does the `WebGLRenderingContext.drawArrays()` calls, but\r\n\t * assumes that everyhing else (bound program, textures, attribute name-locations,\r\n\t * uniform name-locations-values) has been set up already.\r\n\t *\r\n\t * This is expected to be called from `WebGL1Program` only.\r\n\t */\r\n\tdrawMe() {\r\n\t\tthis._gl.drawArrays(this._drawMode, 0, this._size);\r\n\t}\r\n\r\n\t/**\r\n\t * @method drawMePartial(start: Number, count: Number): undefined\r\n\t * Internal use only. As `drawMe()`, but lets the programmer explicitly\r\n\t * set the range of vertex slots to be drawn.\r\n\t *\r\n\t * This is expected to be called from `WebGL1Program` only.\r\n\t */\r\n\tdrawMePartial(start, count) {\r\n\t\tthis._gl.drawArrays(this._drawMode, start, count);\r\n\t}\r\n}\r\n\r\n/**\r\n * @section\r\n * @factory GliiFactory.SequentialIndices(options: SequentialIndices options)\r\n * @class Glii\r\n * @section Class wrappers\r\n * @property SequentialIndices(options: SequentialIndices options): Prototype of SequentialIndices\r\n * Wrapped `SequentialIndices` class\r\n */\r\nregisterFactory(\"SequentialIndices\", function (gl) {\r\n\treturn class WrappedSequentialIndices extends SequentialIndices {\r\n\t\tconstructor(options) {\r\n\t\t\tsuper(gl, options);\r\n\t\t}\r\n\t};\r\n});\r\n","import { registerFactory } from \"../GliiFactory.mjs\";\r\nimport { default as SequentialIndices } from \"./SequentialIndices.mjs\";\r\n\r\n/**\r\n * @class IndexBuffer\r\n * @inherits SequentialIndices\r\n *\r\n * Represents a set of configurable vertex indices. This will tell the\r\n * program which vertices to draw, in which mode (points/lines/triangles),\r\n * and in which order. Includes a couple of niceties like setters and\r\n * handling of the data types.\r\n *\r\n * The implementation of `IndexBuffer` is low-ish level. Using the\r\n * `TriangleIndices` subclass might feel easier.\r\n *\r\n * Internally this represents a `gl.ELEMENT_ARRAY_BUFFER` at the WebGL level, or an\r\n * Element Array Buffer (EAB) at the OpenGL level.\r\n */\r\n\r\nexport default class IndexBuffer extends SequentialIndices {\r\n\tconstructor(gl, gliiFactory, options = {}) {\r\n\t\tsuper(gl, options);\r\n\r\n\t\t// @section\r\n\t\t// @aka IndexBuffer options\r\n\t\t// @option size: Number = 255\r\n\t\t// Maximum number of indices to hold\r\n\t\tthis._size = options.size || 255;\r\n\r\n\t\t// @option growFactor: Boolean = false\r\n\t\t// Specifies that the size of this indices buffer is static.\r\n\t\t// @alternative\r\n\t\t// @option growFactor: Number\r\n\t\t// Allows this buffer to automatically grow when `set()` is out of bounds.\r\n\t\t//\r\n\t\t// Each time that happens, the `size` of this indices buffer will\r\n\t\t// grow by that factor (e.g. a `growFactor` of 2 means the buffer doubles its size each\r\n\t\t// time the size is insufficient). `growFactor` should be greater than `1`.\r\n\t\tthis._growFactor = options.growFactor || 1.2;\r\n\r\n\t\t// @option usage: Buffer usage constant = glii.STATIC_DRAW\r\n\t\t// One of `gl.STATIC_DRAW`, `gl.DYNAMIC_DRAW` or `gl.STREAM_DRAW`.\r\n\t\t// See the documentation of the `usage` parameter at\r\n\t\t// [`bufferData`](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferData)\r\n\t\t// for more details.\r\n\t\tthis._usage = options.usage || gl.STATIC_DRAW;\r\n\r\n\t\t// @option type: Data type constant = glii.UNSIGNED_SHORT\r\n\t\t// One of `glii.UNSIGNED_BYTE`, `glii.UNSIGNED_SHORT` or `glii.UNSIGNED_INT`.\r\n\t\t// This sets the maximum index that can be referenced by this `IndexBuffer`\r\n\t\t// (but not how many indices this `IndexBuffer` can hold):\r\n\t\t// 2^8, 2^16 or 2^32 respectively.\r\n\t\t// See the documentation of the `usage` parameter at\r\n\t\t// [`gl.drawElements`](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements)\r\n\t\t// for more details.\r\n\t\tthis._type = options.type || gl.UNSIGNED_SHORT;\r\n\r\n\t\tif (this._type === gl.UNSIGNED_BYTE) {\r\n\t\t\tthis._bytesPerSlot = 1;\r\n\t\t\tthis._typedArray = Uint8Array;\r\n\t\t\tthis._maxValue = 1 << 8;\r\n\t\t} else if (this._type === gl.UNSIGNED_SHORT) {\r\n\t\t\tthis._bytesPerSlot = 2;\r\n\t\t\tthis._typedArray = Uint16Array;\r\n\t\t\tthis._maxValue = 1 << 16;\r\n\t\t} else if (this._type === gl.UNSIGNED_INT) {\r\n\t\t\t/// Manually load the relevant GL extension, only needed in WebGL1\r\n\t\t\t/// contexts.\r\n\t\t\tgliiFactory.isWebGL2() || gliiFactory.loadExtension(\"OES_element_index_uint\");\r\n\r\n\t\t\tthis._bytesPerSlot = 4;\r\n\t\t\tthis._typedArray = Uint32Array;\r\n\t\t\tthis._maxValue = (1 << 16) * (1 << 16); // The JS << operator is clamped to 2^32 :-/\r\n\t\t} else {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"Invalid type for IndexBuffer. Must be one of `gl.UNSIGNED_BYTE`, `gl.UNSIGNED_SHORT` or `gl.UNSIGNED_INT`.\"\r\n\t\t\t);\r\n\t\t}\r\n\t\tthis._buf = gl.createBuffer();\r\n\t\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._buf);\r\n\t\tgl.bufferData(\r\n\t\t\tgl.ELEMENT_ARRAY_BUFFER,\r\n\t\t\tthis._size * this._bytesPerSlot,\r\n\t\t\tthis._usage\r\n\t\t);\r\n\r\n\t\t// Upper bound of many indices in this IndexBuffer are pointing to valid vertices.\r\n\t\t// AKA \"highest set slot\"\r\n\t\tthis._activeIndices = 0;\r\n\r\n\t\tif (this._growFactor) {\r\n\t\t\t// Growable index buffers need to store all the data in a\r\n\t\t\t// readable data structure, in order to call `bufferData` with\r\n\t\t\t// the new size without destroying data.\r\n\t\t\tthis._ramData = new this._typedArray(this._size);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method set(n: Number, indices: Array of Number): this\r\n\t * Stores the given indices as 1-, 2- or 4-byte integers, starting at the `n`-th\r\n\t * position in this `IndexBuffer` (zero-indexed; the first index slot is at `n`=0).\r\n\t *\r\n\t * The indices passed must exist (and likely, have values) in any\r\n\t * `AttributeBuffer`s being used together with this `IndexBuffer` in a\r\n\t * GL program.\r\n\t */\r\n\tset(n, indices) {\r\n\t\tif (indices.length === 0) {\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\tconst gl = this._gl;\r\n\t\tthis.grow(n + indices.length);\r\n\r\n\t\tgl.bufferSubData(\r\n\t\t\tgl.ELEMENT_ARRAY_BUFFER,\r\n\t\t\tn * this._bytesPerSlot,\r\n\t\t\tthis._typedArray.from(indices)\r\n\t\t);\r\n\r\n\t\tthis._setActiveIndices(n + indices.length);\r\n\r\n\t\tif (this._ramData) {\r\n\t\t\tthis._ramData.set(indices, n);\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method truncate(n: Number): this\r\n\t * Shrinks the amount of indices to be drawn, so that only the first `n`\r\n\t * indices are considered.\r\n\t */\r\n\ttruncate(n) {\r\n\t\tthis._activeIndices = Math.min(this._activeIndices, n);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Internal methods\r\n\t * @uninheritable\r\n\t * @method grow(minimum): undefined\r\n\t * Internal usage only. Grows the size of both the internal buffer, so\r\n\t * it can contain at least `minimum` index slots.\r\n\t */\r\n\tgrow(minimum) {\r\n\t\tthis.bindMe();\r\n\t\tif (this._size >= minimum) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tif (!this._growFactor) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t`Tried to set index out of bounds of non-growable IndexBuffer (requested ${minimum} vs size ${this._size})`\r\n\t\t\t);\r\n\t\t} else {\r\n\t\t\tthis._size = Math.max(minimum + 1, Math.ceil(this._size * this._growFactor));\r\n\r\n\t\t\tconst newRamData = new this._typedArray(this._size);\r\n\t\t\tnewRamData.set(this._ramData, 0);\r\n\t\t\tthis._ramData = newRamData;\r\n\r\n\t\t\tconst gl = this._gl;\r\n\t\t\tgl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this._ramData, this._usage);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method bindMe(): undefined\r\n\t * Internal use only. (Re-)binds itself as the `ELEMENT_ARRAY_BUFFER` of the\r\n\t * `WebGLRenderingContext`.\r\n\t *\r\n\t * In practice, this should happen every time the program changes in the\r\n\t * current context.\r\n\t *\r\n\t * This is expected to be called from `WebGL1Program` only.\r\n\t */\r\n\tbindMe() {\r\n\t\tconst gl = this._gl;\r\n\t\t//if (gl.getParameter(gl.ELEMENT_ARRAY_BUFFER_BINDING) !== this._buf) {\r\n\t\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._buf);\r\n\t\t//}\r\n\t}\r\n\r\n\t/**\r\n\t * @method drawMe(): undefined\r\n\t * Internal use only. Does the `WebGLRenderingContext.drawElement()` calls, but\r\n\t * assumes that everyhing else (bound program, textures, attribute name-locations,\r\n\t * uniform name-locations-values) has been set up already.\r\n\t *\r\n\t * This is expected to be called from `WebGL1Program` only.\r\n\t */\r\n\tdrawMe() {\r\n\t\tthis.bindMe();\r\n\t\tthis._gl.drawElements(this._drawMode, this._activeIndices, this._type, 0);\r\n\t}\r\n\r\n\t/**\r\n\t * @method drawMePartial(start: Number, count: Number): undefined\r\n\t * Internal use only. As `drawMe()`, but lets the programmer explicitly\r\n\t * set the range of vertex slots to be drawn.\r\n\t *\r\n\t * This is expected to be called from `WebGL1Program` only.\r\n\t */\r\n\tdrawMePartal(start, count) {\r\n\t\tthis.bindMe();\r\n\t\tthis._gl.drawElements(\r\n\t\t\tthis._drawMode,\r\n\t\t\tcount,\r\n\t\t\tthis._type,\r\n\t\t\tstart * this._bytesPerSlot\r\n\t\t);\r\n\t}\r\n\r\n\t// Set the number of active indices to either the given number or the number of active indices,\r\n\t// whatever is greater.\r\n\t_setActiveIndices(n) {\r\n\t\tthis._activeIndices = Math.max(this._activeIndices, n);\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @method destroy(): this\r\n\t * Tells WebGL to free resources associated with this `IndexBuffer`. Use\r\n\t * when the `IndexBuffer` won't be used anymore.\r\n\t *\r\n\t * After being destroyed, WebGL programs should not use the destroyed `IndexBuffer`.\r\n\t */\r\n\tdestroy() {\r\n\t\tthis._gl.deleteBuffer(this._buf);\r\n\t}\r\n\r\n\t/**\r\n\t * @section Batch update methods\r\n\t *\r\n\t * These methods are a less convenient, but more performant, way of updating\r\n\t * indices data.\r\n\t *\r\n\t * For a `IndexBuffer`, the workflow is:\r\n\t * - Call `asTypedArray()` once\r\n\t * - Update the values in the returned typed array (using typed array offsets,\r\n\t * avoiding array concatenations)\r\n\t * - Call `commit()`\r\n\t *\r\n\t * These methods need the index buffer to have been created with a `growFactor`\r\n\t * larger than zero.\r\n\t *\r\n\t * @method asTypedarray(minSize?: Number): TypedArray\r\n\t * Returns a view of the internal in-RAM buffer for the index data, able to\r\n\t * contain at least `minSize` indices.\r\n\t */\r\n\tasTypedArray(minSize) {\r\n\t\tif (isFinite(minSize)) {\r\n\t\t\tthis.grow(minSize);\r\n\t\t}\r\n\t\treturn this._ramData;\r\n\t}\r\n\r\n\t/**\r\n\t * @method commit(start, length): this\r\n\t * Dumps the contents of the data in RAM into GPU memory. Will dump\r\n\t * a contiguous section, for a block of indices starting at `start` and\r\n\t * with the given `length`.\r\n\t */\r\n\tcommit(start, length) {\r\n\t\tthis.bindMe();\r\n\r\n\t\tconst addr = this._bytesPerSlot * start;\r\n\t\tconst data = new this._typedArray(this._ramData.buffer, addr, length);\r\n\t\tconst gl = this._gl;\r\n\r\n\t\tgl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, addr, data);\r\n\r\n\t\tthis._setActiveIndices(start + length);\r\n\t}\r\n}\r\n\r\n/**\r\n * @factory GliiFactory.IndexBuffer(options: IndexBuffer options)\r\n * @class Glii\r\n * @section Class wrappers\r\n * @property IndexBuffer(options: IndexBuffer options): Prototype of IndexBuffer\r\n * Wrapped `IndexBuffer` class\r\n */\r\nregisterFactory(\"IndexBuffer\", function (gl, gliiFactory) {\r\n\treturn class WrappedIndexBuffer extends IndexBuffer {\r\n\t\tconstructor(options) {\r\n\t\t\tsuper(gl, gliiFactory, options);\r\n\t\t}\r\n\t};\r\n});\r\n","/**\r\n * @class Allocator\r\n *\r\n * The purpose of an `Allocator` is to provide a way to request (and\r\n * return) blocks of 0-indexed IDs.\r\n *\r\n * The general idea is that a user should request N points/linesegments/triangles,\r\n * and this class would be responsible for creating numeric indices for them.\r\n * Then, the allocated indices would use up space in a `PointIndices`/\r\n * `LineSegmentIndices`/`TriangleIndices`.\r\n *\r\n * @example\r\n *\r\n * The `Allocator` class is `import`ed directly from the `glii` module:\r\n *\r\n * ```\r\n * import { default as Glii, Allocator } from \"path_to_glii/index.mjs\";\r\n *\r\n * const glii = GliiFactory(// etc //);\r\n *\r\n * const myAllocator = new Allocator();\r\n * ```\r\n *\r\n * Trying to spawn an `Allocator` from a `GliiFactory` will fail:\r\n *\r\n * ```\r\n * import { default as Glii, Allocator } from \"path_to_glii/index.mjs\";\r\n * const glii = GliiFactory(// etc //);\r\n *\r\n * const myAllocator = new glii.Allocator();\t// BAD!\r\n * ```\r\n *\r\n */\r\n\r\n// This is kinda similar to https://github.com/redboltz/number-allocator ,\r\n// but allows allocating a range, and has worse complexity (O(n) instead of\r\n// O(n·log(n)) ).\r\n\r\nexport default class Allocator {\r\n\tconstructor(max = Number.MAX_SAFE_INTEGER) {\r\n\t\t/**\r\n\t\t * @constructor Allocator(max: Number)\r\n\t\t * Creates a new `Allocator` instance, given the upper limit\r\n\t\t * of the allocatable area.\r\n\t\t */\r\n\r\n\t\tthis._max = max;\r\n\t\t// The 'points' structure is effectively a linked list of\r\n\t\t// start points of free/allocated regions.\r\n\t\tthis._points = new Map();\r\n\t\tthis._points.set(0, {\r\n\t\t\tfree: true,\r\n\t\t\tnext: max,\r\n\t\t});\r\n\t}\r\n\r\n\t/**\r\n\t * @method allocateBlock(size:Number): Number\r\n\t * Given the count of IDs to allocate, returns a `Number` with the\r\n\t * first ID of the allocated block (last would be return + count - 1)\r\n\t */\r\n\tallocateBlock(size) {\r\n\t\tif (size === 0) {\r\n\t\t\treturn NaN;\r\n\t\t}\r\n\t\tlet prev = 0;\r\n\t\tlet ptr = 0;\r\n\r\n\t\twhile (true) {\r\n\t\t\tconst block = this._points.get(ptr);\r\n\t\t\tconst end = ptr + size;\r\n\r\n\t\t\tif (block.free) {\r\n\t\t\t\tif (ptr === 0 && end < block.next) {\r\n\t\t\t\t\t// Allocate at the very beginning\r\n\t\t\t\t\tthis._points.set(0, { free: false, next: end });\r\n\t\t\t\t\tthis._points.set(end, { free: true, next: block.next });\r\n\t\t\t\t\treturn 0;\r\n\t\t\t\t}\r\n\t\t\t\tif (ptr === 0 && end === block.next) {\r\n\t\t\t\t\t// Allocate at the very beginning, merge with next block\r\n\t\t\t\t\tconst nextBlock = this._points.get(end);\r\n\t\t\t\t\tthis._points.set(0, { free: false, next: nextBlock.next });\r\n\t\t\t\t\tthis._points.delete(block.next);\r\n\t\t\t\t\treturn 0;\r\n\t\t\t\t}\r\n\t\t\t\tif (end < block.next) {\r\n\t\t\t\t\t// Increase the size of the previous, used, block\r\n\t\t\t\t\tthis._points.set(prev, { free: false, next: end });\r\n\t\t\t\t\tthis._points.delete(ptr);\r\n\t\t\t\t\tthis._points.set(end, { free: true, next: block.next });\r\n\t\t\t\t\treturn ptr;\r\n\t\t\t\t}\r\n\t\t\t\tif (end === block.next) {\r\n\t\t\t\t\t// Allocate an entire free block,\r\n\t\t\t\t\t// merge neighbouring used blocks\r\n\t\t\t\t\tconst nextBlock = this._points.get(end);\r\n\t\t\t\t\tthis._points.set(prev, { free: false, next: nextBlock.next });\r\n\t\t\t\t\tthis._points.delete(ptr);\r\n\t\t\t\t\tthis._points.delete(block.next);\r\n\t\t\t\t\treturn ptr;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tprev = ptr;\r\n\t\t\tptr = block.next;\r\n\t\t\tif (ptr <= prev) {\r\n\t\t\t\tthrow new Error(`Bad allocation map: tried to go backwards`);\r\n\t\t\t}\r\n\t\t\t// \t\t\tif (ptr === Number.MAX_SAFE_INTEGER) {\r\n\t\t\tif (ptr >= this._max) {\r\n\t\t\t\tthrow new Error(`No allocatable space`);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method deallocateBlock([Number]): this\r\n\t * Given a starting ID and the size of a block, deallocates that block\r\n\t * (marks it as allocatable again)\r\n\t */\r\n\tdeallocateBlock(start, size) {\r\n\t\tif (size === 0) {\r\n\t\t\treturn this;\r\n\t\t}\r\n\t\tlet prev = 0;\r\n\t\tlet ptr = 0;\r\n\t\tconst end = start + size;\r\n\r\n\t\twhile (true) {\r\n\t\t\tconst block = this._points.get(ptr);\r\n\r\n\t\t\tif (!block.free) {\r\n\t\t\t\tif (ptr === 0 && start === 0 && end === block.next) {\r\n\t\t\t\t\t// Deallocate entire block at beginning\r\n\t\t\t\t\tconst nextBlock = this._points.get(end);\r\n\t\t\t\t\tthis._points.set(0, { free: true, next: nextBlock.next });\r\n\t\t\t\t\tthis._points.delete(end);\r\n\t\t\t\t\treturn this;\r\n\t\t\t\t}\r\n\t\t\t\tif (ptr === 0 && start === 0 && end < block.next) {\r\n\t\t\t\t\t// Deallocate partial block at beginning,\r\n\t\t\t\t\t// lower next block start\r\n\t\t\t\t\tthis._points.set(0, { free: true, next: end });\r\n\t\t\t\t\tthis._points.set(end, { free: false, next: block.next });\r\n\t\t\t\t\treturn this;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (ptr === start && end < block.next) {\r\n\t\t\t\t\t// Deallocate at the beginning of a used block\r\n\t\t\t\t\t// Grow the previous free block\r\n\t\t\t\t\tthis._points.set(prev, { free: true, next: end });\r\n\t\t\t\t\tthis._points.delete(ptr);\r\n\t\t\t\t\tthis._points.set(end, { free: false, next: block.next });\r\n\t\t\t\t\treturn this;\r\n\t\t\t\t}\r\n\t\t\t\tif (ptr === start && end === block.next) {\r\n\t\t\t\t\t// Deallocate the entire block\r\n\t\t\t\t\t// Merge neighbouring free blocks\r\n\t\t\t\t\tconst nextBlock = this._points.get(block.next);\r\n\t\t\t\t\tthis._points.set(prev, { free: true, next: nextBlock.next });\r\n\t\t\t\t\tthis._points.delete(ptr);\r\n\t\t\t\t\tthis._points.delete(block.next);\r\n\t\t\t\t\treturn this;\r\n\t\t\t\t}\r\n\t\t\t\tif (ptr < start && end === block.next) {\r\n\t\t\t\t\t// Deallocate the end of the block\r\n\t\t\t\t\t// Grow the next free block\r\n\t\t\t\t\tconst nextBlock = this._points.get(block.next);\r\n\t\t\t\t\tthis._points.set(ptr, { free: false, next: start });\r\n\t\t\t\t\tthis._points.delete(block.next);\r\n\t\t\t\t\tthis._points.set(start, { free: true, next: nextBlock.next });\r\n\t\t\t\t\treturn this;\r\n\t\t\t\t}\r\n\t\t\t\tif (ptr < start && end < block.next) {\r\n\t\t\t\t\t// Deallocate middle of a block\r\n\t\t\t\t\tthis._points.set(ptr, { free: false, next: start });\r\n\t\t\t\t\tthis._points.set(start, { free: true, next: end });\r\n\t\t\t\t\tthis._points.set(end, { free: false, next: block.next });\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tprev = ptr;\r\n\t\t\tptr = block.next;\r\n\t\t\tif (ptr <= prev) {\r\n\t\t\t\tthrow new Error(`Bad allocation map: tried to go backwards`);\r\n\t\t\t}\r\n\t\t\t// if (start === Number.MAX_SAFE_INTEGER) {\r\n\t\t\tif (ptr >= this._max) {\r\n\t\t\t\tthrow new Error(`Could not deallocate. Sparse?`);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method forEachBlock(fn: Function): this\r\n\t * Runs the given callback `Function` `fn`. `fn` receives\r\n\t * the start and length of each allocated block as its two\r\n\t * parameters.\r\n\t */\r\n\tforEachBlock(fn) {\r\n\t\tif (this._points.size <= 1) {\r\n\t\t\treturn this;\r\n\t\t}\r\n\t\tlet ptr = 0;\r\n\t\twhile (true) {\r\n\t\t\tconst block = this._points.get(ptr);\r\n\t\t\tif (!block) {\r\n\t\t\t\tthrow new Error(\r\n\t\t\t\t\t`Bad allocation map: was modified inside a forEach() callback`\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t\tif (!block.free) {\r\n\t\t\t\tfn(ptr, block.next - ptr);\r\n\t\t\t}\r\n\t\t\tif (block.next <= ptr) {\r\n\t\t\t\tthrow new Error(`Bad allocation map: tried to go backwards`);\r\n\t\t\t}\r\n\t\t\tptr = block.next;\r\n\t\t\tif (ptr >= this._max) {\r\n\t\t\t\treturn this;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n","import { registerFactory } from \"../GliiFactory.mjs\";\r\nimport { default as IndexBuffer } from \"./IndexBuffer.mjs\";\r\nimport { default as Allocator } from \"../Allocator.mjs\";\r\n\r\n/**\r\n * @class SparseIndices\r\n * @inherits IndexBuffer\r\n * @relationship compositionOf Allocator, 0..1, 1..1\r\n *\r\n * A `SparseIndices` is an `IndexBuffer`, plus an `Allocator` of index\r\n * slots - whenever more index slots for primitives are needed,\r\n * call `allocateSlots` (and later `deallocateSlots` if needed).\r\n *\r\n * (Drawing this `Indices` shall trigger one draw call per contiguous\r\n * block of used indices. By comparison, the basic `IndexBuffer` triggers\r\n * just one `drawElements` call, from 0 to number-of-used-indices -1)\r\n *\r\n */\r\n\r\nexport default class SparseIndices extends IndexBuffer {\r\n\tconstructor(gl, gliiFactory, options = {}) {\r\n\t\tsuper(gl, gliiFactory, options);\r\n\r\n\t\t// Allocator instance for blocks in self's `ELEMENT_ARRAY_BUFFER`.\r\n\t\tif (this._growFactor) {\r\n\t\t\tthis._slotAllocator = new Allocator();\r\n\t\t} else {\r\n\t\t\tthis._slotAllocator = new Allocator(this._size);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method allocateSlots(count: Number): Number\r\n\t * Allocates `count` slots for indices. Returns the offset of the first\r\n\t * slot.\r\n\t *\r\n\t * For `gl.TRIANGLES` (`gl.LINES`), allocate 3 (2) slots per triangle (line).\r\n\t */\r\n\tallocateSlots(count) {\r\n\t\tconst start = this._slotAllocator.allocateBlock(count);\r\n\t\tthis.grow(start + count);\r\n\t\treturn start;\r\n\t}\r\n\r\n\t/**\r\n\t * @method deallocateSlots(start: Number, count: Number): this\r\n\t * Deallocates `count` slots for indices, started with the `start`th slot.\r\n\t *\r\n\t * For `gl.TRIANGLES` (`gl.LINES`), allocate 3 (2) slots per triangle (line).\r\n\t */\r\n\tdeallocateSlots(start, count) {\r\n\t\tthis._slotAllocator.deallocateBlock(start, count);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method allocateSet(indices: Array of Number): Number\r\n\t * Combination of `allocate()` and `set()`. Allocates the neccesary\r\n\t * space for the given indices, and sets their values.\r\n\t *\r\n\t * Returns the offset of the allocation block start.\r\n\t */\r\n\tallocateSet(indices) {\r\n\t\tconst start = this.allocateSlots(indices.length);\r\n\t\tthis.set(start, indices);\r\n\t\treturn start;\r\n\t}\r\n\r\n\t/**\r\n\t * @method forEachBlock(fn: Function): this\r\n\t * Runs the given callback `Function` `fn` once per allocated block.\r\n\t *\r\n\t * The callback function shall receive the start and length of each\r\n\t * allocated block. Both figures are given in number of *vertex slots*\r\n\t * and not in primitives (i.e. divide by 3 when working with triangles).\r\n\t */\r\n\tforEachBlock(fn) {\r\n\t\tthis._slotAllocator.forEachBlock(fn);\r\n\t\treturn this;\r\n\t}\r\n\r\n\ttruncate(n) {\r\n\t\treturn this.deallocateSlots(n, Number.MAX_SAFE_INTEGER);\r\n\t}\r\n\r\n\t// Internal only. Does the GL drawElement() calls, but assumes that everyhing else\r\n\t// (bound program, textures, attribute name-locations, uniform name-locations-values)\r\n\t// has been set up already.\r\n\tdrawMe() {\r\n\t\tthis.bindMe();\r\n\t\tthis._slotAllocator.forEachBlock((start, length) => {\r\n\t\t\tconst startByte = start * this._bytesPerSlot;\r\n\t\t\tthis._gl.drawElements(this._drawMode, length, this._type, startByte);\r\n\t\t});\r\n\t}\r\n\r\n\t// Internal only. Does the GL drawElement() calls, but assumes that everyhing else\r\n\t// (bound program, textures, attribute name-locations, uniform name-locations-values)\r\n\t// has been set up already.\r\n\tdrawMePartial(start, count) {\r\n\t\tthis.bindMe();\r\n\t\tthis._gl.drawElements(\r\n\t\t\tthis._drawMode,\r\n\t\t\tcount,\r\n\t\t\tthis._type,\r\n\t\t\tstart * this._bytesPerSlot\r\n\t\t);\r\n\t}\r\n}\r\n\r\n/**\r\n * @factory GliiFactory.SparseIndices(options: SparseIndices options)\r\n * @class Glii\r\n * @section Class wrappers\r\n * @property SparseIndices(options: SparseIndices options): Prototype of SparseIndices\r\n * Wrapped `SparseIndices` class\r\n */\r\nregisterFactory(\"SparseIndices\", function (gl, gliiFactory) {\r\n\treturn class WrappedSparseIndices extends SparseIndices {\r\n\t\tconstructor(options) {\r\n\t\t\tsuper(gl, gliiFactory, options);\r\n\t\t}\r\n\t};\r\n});\r\n","import { registerFactory } from \"../GliiFactory.mjs\";\r\nimport SequentialIndices from \"./SequentialIndices.mjs\";\r\nimport { default as Allocator } from \"../Allocator.mjs\";\r\n\r\n/**\r\n * @class SequentialSparseIndices\r\n * @inherits SequentialIndices\r\n * @relationship compositionOf Allocator, 0..1, 1..1\r\n *\r\n * Works as a `SequentialIndices` in that makes `drawArrays` calls, but one\r\n * call per allocated block (as per `SparseIndices`).\r\n */\r\n\r\nexport default class SequentialSparseIndices extends SequentialIndices {\r\n\tconstructor(gl, options = {}) {\r\n\t\tsuper(gl, options);\r\n\t\tthis._slotAllocator = new Allocator();\r\n\t}\r\n\r\n\t/**\r\n\t * @method allocateSlots(count: Number): Number\r\n\t * Allocates `count` slots for indices. Returns the offset of the first\r\n\t * slot.\r\n\t *\r\n\t * For `gl.TRIANGLES` (`gl.LINES`), allocate 3 (2) slots per triangle (line).\r\n\t */\r\n\tallocateSlots(count) {\r\n\t\treturn this._slotAllocator.allocateBlock(count);\r\n\t}\r\n\r\n\t/**\r\n\t * @method deallocateSlots(start: Number, count: Number): this\r\n\t * Deallocates `count` slots for indices, started with the `start`th slot.\r\n\t *\r\n\t * For `gl.TRIANGLES` (`gl.LINES`), allocate 3 (2) slots per triangle (line).\r\n\t */\r\n\tdeallocateSlots(start, count) {\r\n\t\tthis._slotAllocator.deallocateBlock(start, count);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method forEachBlock(fn: Function): this\r\n\t * Runs the given callback `Function` `fn` once per allocated block.\r\n\t *\r\n\t * The callback function shall receive the start and length of each\r\n\t * allocated block. Both figures are given in number of *vertex slots*\r\n\t * and not in primitives (i.e. divide by 3 when working with triangles).\r\n\t */\r\n\tforEachBlock(fn) {\r\n\t\tthis._slotAllocator.forEachBlock(fn);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// Internal only. Does the GL drawElement() calls, but assumes that everyhing else\r\n\t// (bound program, textures, attribute name-locations, uniform name-locations-values)\r\n\t// has been set up already.\r\n\tdrawMe() {\r\n\t\tthis._slotAllocator.forEachBlock((start, length) => {\r\n\t\t\tthis._gl.drawArrays(this._drawMode, start, length);\r\n\t\t});\r\n\t}\r\n\r\n\t// Internal only. Does the GL drawElement() calls, but assumes that everyhing else\r\n\t// (bound program, textures, attribute name-locations, uniform name-locations-values)\r\n\t// has been set up already.\r\n\tdrawMePartial(start, count) {\r\n\t\tthis._gl.drawArrays(this._drawMode, start, count);\r\n\t}\r\n}\r\n\r\n/**\r\n * @factory GliiFactory.SparseIndices(options: SparseIndices options)\r\n * @class Glii\r\n * @section Class wrappers\r\n * @property SparseIndices(options: SparseIndices options): Prototype of SparseIndices\r\n * Wrapped `SparseIndices` class\r\n */\r\nregisterFactory(\"SequentialSparseIndices\", function (gl) {\r\n\treturn class WrappedSequentialSparseIndices extends SequentialSparseIndices {\r\n\t\tconstructor(options) {\r\n\t\t\tsuper(gl, options);\r\n\t\t}\r\n\t};\r\n});\r\n","import { registerFactory } from \"../GliiFactory.mjs\";\r\nimport { default as SparseIndices } from \"./SparseIndices.mjs\";\r\n\r\n/**\r\n * @class TriangleIndices\r\n * @inherits SparseIndices\r\n * @relationship associated Triangle\r\n * @relationship associated Quad\r\n *\r\n * A decorated flavour of `SparseIndices`. Allows spawning instances of the `Triangle`\r\n * utility class.\r\n *\r\n * The `drawMode` of a `TriangleIndices` is forced to `gl.TRIANGLES`.\r\n *\r\n * @example\r\n *\r\n * ```\r\n * const myTriangles = new GliiFactory.TriangleIndices();\r\n *\r\n * const trig1 = new myTriangles.Triangle(0,1,2);\r\n * trig1.setVertices(0,1,2);\r\n * trig1.destroy();\r\n * ```\r\n */\r\n\r\nexport default class TriangleIndices extends SparseIndices {\r\n\tconstructor(gl, gliiFactory, options = {}) {\r\n\t\t/**\r\n\t\t * @section\r\n\t\t * @aka TriangleIndices options\r\n\t\t * @option size: Number = 85; The (initial) amount of triangles (not vertices) to hold\r\n\t\t */\r\n\t\toptions.size = (options.size || 85) * 3;\r\n\t\tsuper(gl, options);\r\n\r\n\t\tconst container = this;\r\n\t\t/**\r\n\t\t * @property Triangle: Triangle prototype\r\n\t\t * Class prototype for `Triangle`s.\r\n\t\t */\r\n\t\tthis.Triangle = class WrappedTriangle extends Triangle {\r\n\t\t\tconstructor() {\r\n\t\t\t\tsuper(container);\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\t/**\r\n\t\t * @property Quad: Quad prototype\r\n\t\t * Class prototype for `Quad`s.\r\n\t\t */\r\n\t\tthis.Quad = class WrappedQuad extends Quad {\r\n\t\t\tconstructor() {\r\n\t\t\t\tsuper(container);\r\n\t\t\t}\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * @method allocateSlots(count: Number): Number\r\n\t * Allocates `count` slots for indices, `count` must be a multiple of 3.\r\n\t * Returns the offset of the first slot.\r\n\t */\r\n\tallocateSlots(count) {\r\n\t\tif (count % 3) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"Number of vertices to be allocated from a TriangleIndices must be a multiple of 3\"\r\n\t\t\t);\r\n\t\t}\r\n\t\treturn super.allocateSlots(count);\r\n\t}\r\n\r\n\t/**\r\n\t * @method deallocateSlots(start, count: Number): this\r\n\t * Deallocates `count` slots for indices, started with the `start`th slot.\r\n\t * Both `start` and `count` must be multiples of 3.\r\n\t */\r\n\tdeallocateSlots(start, count) {\r\n\t\tif (count % 3) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"Number of vertices to be deallocated from a TriangleIndices must be a multiple of 3\"\r\n\t\t\t);\r\n\t\t}\r\n\t\tif (start % 3) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"The starting slot to be deallocated from a TriangleIndices must be a multiple of 3\"\r\n\t\t\t);\r\n\t\t}\r\n\t\tthis._slotAllocator.deallocateBlock(start, count);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// Internal only. Does the GL drawElement() calls, but assumes that everyhing else\r\n\t// (bound program, textures, attribute name-locations, uniform name-locations-values)\r\n\t// has been set up already.\r\n\tdrawMe() {\r\n\t\tthis.bindMe();\r\n\t\tthis._slotAllocator.forEachBlock((start, length) => {\r\n\t\t\tconst startByte = start * this._bytesPerSlot;\r\n\t\t\tthis._gl.drawElements(this._gl.TRIANGLES, length, this._type, startByte);\r\n\t\t});\r\n\t}\r\n}\r\n\r\n/**\r\n * @class Triangle\r\n *\r\n * Sintactic sugar over a triplet of vertex indices.\r\n *\r\n * Cannot be instantiated directly; a `TriangleIndices` must be used (since every\r\n * `Triangle` belongs to one and only one `TriangleIndices`).\r\n *\r\n * Instantiating a `Triangle` automativally allocates vertex slots in\r\n * the `TriangleIndices`.\r\n *\r\n * @example\r\n * ```\r\n * const trigs = new glii.TriangleIndices( // etc // );\r\n *\r\n * let trig1 = new trigs.Triangle();\r\n * trig1.setVertices(1000,1001,1002);\r\n * ```\r\n */\r\n\r\nclass Triangle {\r\n\tconstructor(indices) {\r\n\t\tthis._container = indices;\r\n\t\tthis._idx = indices.allocateSlots(3);\r\n\t\tthis._allocated = true;\r\n\t}\r\n\r\n\t/**\r\n\t * @method setVertices(v1: Number, v2: Number, v3: Number): this\r\n\t * Sets the vertex indices for this triangle.\r\n\t *\r\n\t * The indices passed must exist (and likely, have values) in any\r\n\t * `AttributeBuffer`s being used together with the containing `TriangleIndices`\r\n\t * in a GL program.\r\n\t *\r\n\t * Internally, this is akin to calling `IndexBuffer.set(idx, [v1,v2,v3])`,\r\n\t * if `idx` to `idx+2` were allocated manually.\r\n\t */\r\n\tsetVertices(v1, v2, v3) {\r\n\t\tif (!this._allocated) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"Cannot set vertices in a `Triangle` which has been destroyed.\"\r\n\t\t\t);\r\n\t\t}\r\n\t\tthis._container.set(this._idx, [v1, v2, v3]);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method destroy():this\r\n\t * Signals the containing `TriangleIndices` that this triangle should not\r\n\t * be drawn anymore.\r\n\t *\r\n\t * [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete)ing\r\n\t * all references to this instances manually, right afterwards, is encouraged.\r\n\t */\r\n\tdestroy() {\r\n\t\tthis._container.deallocateSlots(this._idx, 3);\r\n\t\tthis._allocated = false;\r\n\t}\r\n}\r\n\r\n/**\r\n * @class Quad\r\n *\r\n * Sintactic sugar over two triangles forming a quadrangle (hence, \"quad\").\r\n *\r\n * Cannot be instantiated directly; a `TriangleIndices` must be used (since every\r\n * `Quad` belongs to one and only one `TriangleIndices`).\r\n *\r\n * Instantiating a `Quad` automativally allocates vertex slots in\r\n * the `TriangleIndices` enough for two triangles.\r\n *\r\n * @example\r\n * ```\r\n * const trigs = new glii.TriangleIndices( // etc // );\r\n *\r\n * let quad1 = new trigs.Quad();\r\n * quad1.setVertices(1000,1001,1002,1003);\r\n * ```\r\n */\r\n\r\nclass Quad {\r\n\tconstructor(indices) {\r\n\t\tthis._container = indices;\r\n\t\tthis._idx = indices.allocateSlots(6);\r\n\t\tthis._allocated = true;\r\n\t}\r\n\r\n\t/**\r\n\t * @method setVertices(v1: Number, v2: Number, v3: Number, v4: Number): this\r\n\t * Sets the vertex indices for this quad.\r\n\t *\r\n\t * The indices passed must exist (and likely, have values) in any\r\n\t * `AttributeBuffer`s being used together with the containing `TriangleIndices`\r\n\t * in a GL program.\r\n\t *\r\n\t * Internally, this is akin to calling `IndexBuffer.set(idx, [v1,v2,v3])`\r\n\t * and `IndexBuffer.set(idx, [v1,v3,v4])`, if `idx` to `idx+5`\r\n\t * were allocated manually.\r\n\t */\r\n\tsetVertices(v1, v2, v3, v4) {\r\n\t\tif (!this._allocated) {\r\n\t\t\tthrow new Error(\"Cannot set vertices in a `Quad` which has been destroyed.\");\r\n\t\t}\r\n\t\tthis._container.set(this._idx, [v1, v2, v3]);\r\n\t\tthis._container.set(this._idx + 3, [v1, v3, v4]);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method destroy():this\r\n\t * Signals the containing `TriangleIndices` that this triangle should not\r\n\t * be drawn anymore.\r\n\t *\r\n\t * [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete)ing\r\n\t * all references to this instances manually, right afterwards, is encouraged.\r\n\t */\r\n\tdestroy() {\r\n\t\tthis._container.deallocateSlots(this._idx, 6);\r\n\t\tthis._allocated = false;\r\n\t}\r\n}\r\n\r\n/**\r\n * @class TriangleIndices\r\n * @factory GliiFactory.TriangleIndices(options: TriangleIndices options)\r\n * @class Glii\r\n * @section Class wrappers\r\n * @property TriangleIndices(options: TriangleIndices options): Prototype of TriangleIndices\r\n * Wrapped `TriangleIndices` class\r\n */\r\nregisterFactory(\"TriangleIndices\", function (gl, gliiFactory) {\r\n\treturn class WrappedTriangleIndices extends TriangleIndices {\r\n\t\tconstructor(options) {\r\n\t\t\tsuper(gl, gliiFactory, options);\r\n\t\t}\r\n\t};\r\n});\r\n","/**\r\n * @class LoDAllocator\r\n *\r\n * An expansion of `Allocator`; its purpose is to allocate blocks linked to\r\n * an identifier; and instead of iterating through allocated blocks, can\r\n * iterate through a filtered subset of blocks.\r\n *\r\n * The idea is to allow for level-of-detail (LoD) triangle meshes. Several\r\n * meshes with different LoD IDs can be allocated on the same underlying\r\n * `IndexBuffer`, and then one specific LoD can be filtered out to be drawn.\r\n *\r\n * @example\r\n *\r\n * The `LoDAllocator` class is `import`ed directly from the `glii` module:\r\n *\r\n * ```\r\n * import { default as Glii } from \"path_to_glii/index.mjs\";\r\n * import { default as LoDAllocator } from \"path_to_glii/LoDAllocator.mjs\";\r\n *\r\n * const glii = GliiFactory(// etc //);\r\n *\r\n * const myAllocator = new LoDAllocator();\r\n * ```\r\n *\r\n * Trying to spawn an `LoDAllocator` from a `GliiFactory` will fail:\r\n *\r\n * ```\r\n * import { default as Glii, LoDAllocator } from \"path_to_glii/index.mjs\";\r\n * const glii = GliiFactory(// etc //);\r\n *\r\n * const myAllocator = new glii.LoDAllocator();\t// BAD!\r\n * ```\r\n *\r\n */\r\n\r\nexport default class LoDAllocator {\r\n\tconstructor(max = Number.MAX_SAFE_INTEGER) {\r\n\t\t/**\r\n\t\t * @constructor Allocator(max: Number)\r\n\t\t * Creates a new `Allocator` instance, given the upper limit\r\n\t\t * of the allocatable area.\r\n\t\t */\r\n\r\n\t\tthis._max = max;\r\n\t\t// The 'points' structure is effectively a linked list of\r\n\t\t// start points of free/allocated regions.\r\n\t\tthis._points = new Map();\r\n\t\tthis._points.set(0, {\r\n\t\t\tfree: true,\r\n\t\t\tnext: max,\r\n\t\t\tdata: undefined,\r\n\t\t});\r\n\t}\r\n\r\n\t/**\r\n\t * @method allocateBlock(size:Number, data: Number): Number\r\n\t * Given the count of IDs to allocate, returns a `Number` with the\r\n\t * first ID of the allocated block (last would be return + count - 1).\r\n\t *\r\n\t * Receives an numerical `data` parameter that will be attached\r\n\t * internally to the allocation (and shall be used for filtering\r\n\t * allocation blocks later on). This should be the LoD (if the LoD is\r\n\t * numerical)\r\n\t * @alternative\r\n\t * @method allocateBlock(size:Number, data: String): Number\r\n\t * Can take a `String` as the LoD identifier as well.\r\n\t * @method allocateBlock(size:Number, data: Object): Number\r\n\t * Can take any `Object` as the LoD identifier as well. Do note that,\r\n\t * internally, the `===` equality operator is used to check equality\r\n\t * of LoD identifiers among allocation blocks.\r\n\t */\r\n\tallocateBlock(size, data) {\r\n\t\tlet prev = 0;\r\n\t\tlet prevBlock;\r\n\t\tlet ptr = 0;\r\n\r\n\t\twhile (true) {\r\n\t\t\tconst block = this._points.get(ptr);\r\n\t\t\tconst end = ptr + size;\r\n\r\n\t\t\tif (block.free) {\r\n\t\t\t\tif (ptr === 0 && end < block.next) {\r\n\t\t\t\t\t// Allocate at the very beginning, leave gap\r\n\t\t\t\t\tthis._points.set(0, { free: false, next: end, data });\r\n\t\t\t\t\tthis._points.set(end, { free: true, next: block.next });\r\n\t\t\t\t\treturn 0;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tconst nextBlock = this._points.get(end);\r\n\r\n\t\t\t\tif (ptr === 0 && end === block.next) {\r\n\t\t\t\t\tif (data === nextBlock.data) {\r\n\t\t\t\t\t\t// Allocate at the very beginning, merge with next block\r\n\t\t\t\t\t\tthis._points.set(0, { free: false, next: nextBlock.next, data });\r\n\t\t\t\t\t\tthis._points.delete(block.next);\r\n\t\t\t\t\t\treturn 0;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// Allocate at the very beginning, do not merge with next block\r\n\t\t\t\t\t\tthis._points.set(0, { free: false, next: end, data });\r\n\t\t\t\t\t\t//this._points.set(end, { free: true, next: block.next });\r\n\t\t\t\t\t\treturn 0;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (end < block.next) {\r\n\t\t\t\t\tif (prevBlock.data === data) {\r\n\t\t\t\t\t\t// Increase the size of the previous, used, block\r\n\t\t\t\t\t\tthis._points.set(prev, { free: false, next: end, data });\r\n\t\t\t\t\t\tthis._points.delete(ptr);\r\n\t\t\t\t\t\tthis._points.set(end, { free: true, next: block.next });\r\n\t\t\t\t\t\treturn ptr;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// Allocate next to previous, used, block\r\n\t\t\t\t\t\tthis._points.set(ptr, { free: false, next: end, data });\r\n\t\t\t\t\t\tthis._points.set(end, { free: true, next: block.next });\r\n\t\t\t\t\t\treturn ptr;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (end === block.next) {\r\n\t\t\t\t\tif (prevBlock.data === data && nextBlock.data === data) {\r\n\t\t\t\t\t\t// Allocate an entire free block,\r\n\t\t\t\t\t\t// merge neighbouring used blocks\r\n\t\t\t\t\t\tthis._points.set(prev, { free: false, next: nextBlock.next });\r\n\t\t\t\t\t\tthis._points.delete(ptr);\r\n\t\t\t\t\t\tthis._points.delete(block.next);\r\n\t\t\t\t\t\treturn ptr;\r\n\t\t\t\t\t} else if (prevBlock.data === data && nextBlock.data !== data) {\r\n\t\t\t\t\t\t// Merge with the previous block\r\n\t\t\t\t\t\tthis._points.set(prev, { free: false, next: block.next });\r\n\t\t\t\t\t\tthis._points.delete(ptr);\r\n\t\t\t\t\t\treturn ptr;\r\n\t\t\t\t\t} else if (prevBlock.data !== data && nextBlock.data === data) {\r\n\t\t\t\t\t\t// Merge with the next block\r\n\t\t\t\t\t\tthis._points.set(ptr, {\r\n\t\t\t\t\t\t\tfree: false,\r\n\t\t\t\t\t\t\tnext: nextBlock.next,\r\n\t\t\t\t\t\t\tdata,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis._points.delete(block.next);\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// Set block as allocated, do not merge anything\r\n\t\t\t\t\t\tthis._points.set(ptr, { free: false, next: end, data });\r\n\t\t\t\t\t\treturn ptr;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tprev = ptr;\r\n\t\t\tprevBlock = block;\r\n\t\t\tptr = block.next;\r\n\t\t\tif (ptr <= prev) {\r\n\t\t\t\tthrow new Error(`Bad allocation map: tried to go backwards`);\r\n\t\t\t}\r\n\t\t\t// \t\t\tif (ptr === Number.MAX_SAFE_INTEGER) {\r\n\t\t\tif (ptr >= this._max) {\r\n\t\t\t\tthrow new Error(`No allocatable space`);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method deallocateBlock(start: Number, size: Number): this\r\n\t * Given a starting ID and the size of a block, deallocates that block\r\n\t * (marks it as allocatable again).\r\n\t *\r\n\t * The given start and size must fall within the same allocation block (i.e.\r\n\t * the deallocation must correspond to just one LoD).\r\n\t */\r\n\tdeallocateBlock(start, size) {\r\n\t\tlet prev = 0;\r\n\t\tlet prevBlock;\r\n\t\tlet ptr = 0;\r\n\t\tconst end = start + size;\r\n\r\n\t\twhile (true) {\r\n\t\t\tconst block = this._points.get(ptr);\r\n\r\n\t\t\tif (!block.free) {\r\n\t\t\t\tconst nextBlock = this._points.get(end);\r\n\r\n\t\t\t\tif (ptr === 0 && start === 0 && end === block.next) {\r\n\t\t\t\t\tif (nextBlock.free) {\r\n\t\t\t\t\t\t// Deallocate entire block at beginning, grow\r\n\t\t\t\t\t\t// next free block\r\n\t\t\t\t\t\tthis._points.set(0, { free: true, next: nextBlock.next });\r\n\t\t\t\t\t\tthis._points.delete(end);\r\n\t\t\t\t\t\treturn this;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// Deallocate entire block at beginning, ignore\r\n\t\t\t\t\t\t// next used block\r\n\t\t\t\t\t\tthis._points.set(0, { free: true, next: block.next });\r\n\t\t\t\t\t\treturn this;\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (ptr === 0 && start === 0 && end < block.next) {\r\n\t\t\t\t\t// Deallocate partial block at beginning,\r\n\t\t\t\t\t// lower next block start\r\n\t\t\t\t\tthis._points.set(0, { free: true, next: end });\r\n\t\t\t\t\tthis._points.set(end, {\r\n\t\t\t\t\t\tfree: false,\r\n\t\t\t\t\t\tnext: block.next,\r\n\t\t\t\t\t\tdata: block.data,\r\n\t\t\t\t\t});\r\n\t\t\t\t\treturn this;\r\n\t\t\t\t} else if (ptr === start && end < block.next) {\r\n\t\t\t\t\tif (prevBlock.free) {\r\n\t\t\t\t\t\t// Deallocate at the beginning of a used block\r\n\t\t\t\t\t\t// Grow the previous free block\r\n\t\t\t\t\t\tthis._points.set(prev, { free: true, next: end });\r\n\t\t\t\t\t\tthis._points.delete(ptr);\r\n\t\t\t\t\t\tthis._points.set(end, {\r\n\t\t\t\t\t\t\tfree: false,\r\n\t\t\t\t\t\t\tnext: block.next,\r\n\t\t\t\t\t\t\tdata: block.data,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\treturn this;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// Deallocate at the beginning of a used block,\r\n\t\t\t\t\t\t// ignore previous free block\r\n\t\t\t\t\t\tthis._points.set(ptr, { free: true, next: end });\r\n\t\t\t\t\t\tthis._points.set(end, {\r\n\t\t\t\t\t\t\tfree: false,\r\n\t\t\t\t\t\t\tnext: block.next,\r\n\t\t\t\t\t\t\tdata: block.data,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\treturn this;\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (ptr === start && end === block.next) {\r\n\t\t\t\t\tif (prevBlock.free && nextBlock.free) {\r\n\t\t\t\t\t\t// Deallocate the entire block\r\n\t\t\t\t\t\t// Merge neighbouring free blocks\r\n\t\t\t\t\t\tthis._points.set(prev, { free: true, next: nextBlock.next });\r\n\t\t\t\t\t\tthis._points.delete(ptr);\r\n\t\t\t\t\t\tthis._points.delete(block.next);\r\n\t\t\t\t\t\treturn this;\r\n\t\t\t\t\t} else if (!prevBlock.free && !nextBlock.free) {\r\n\t\t\t\t\t\t// Deallocate the entire block\r\n\t\t\t\t\t\t// Ignore neighbouring used blocks\r\n\t\t\t\t\t\tthis._points.set(ptr, { free: true, next: block.next });\r\n\t\t\t\t\t\treturn this;\r\n\t\t\t\t\t} else if (prevBlock.free && !nextBlock.free) {\r\n\t\t\t\t\t\t// Deallocate the entire block\r\n\t\t\t\t\t\t// Merge previous free block\r\n\t\t\t\t\t\tthis._points.set(prev, { free: true, next: block.next });\r\n\t\t\t\t\t\tthis._points.delete(ptr);\r\n\t\t\t\t\t\treturn this;\r\n\t\t\t\t\t} else if (!prevBlock.free && nextBlock.free) {\r\n\t\t\t\t\t\t// Deallocate the entire block\r\n\t\t\t\t\t\t// Merge next free block\r\n\t\t\t\t\t\tthis._points.set(ptr, { free: true, next: nextBlock.next });\r\n\t\t\t\t\t\tthis._points.delete(block.next);\r\n\t\t\t\t\t\treturn this;\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (ptr < start && end === block.next) {\r\n\t\t\t\t\tif (nextBlock.free) {\r\n\t\t\t\t\t\t// Deallocate the end of the block\r\n\t\t\t\t\t\t// Grow the next free block\r\n\t\t\t\t\t\tthis._points.set(ptr, {\r\n\t\t\t\t\t\t\tfree: false,\r\n\t\t\t\t\t\t\tnext: start,\r\n\t\t\t\t\t\t\tdata: block.data,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis._points.delete(block.next);\r\n\t\t\t\t\t\tthis._points.set(start, { free: true, next: nextBlock.next });\r\n\t\t\t\t\t\treturn this;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// Deallocate the end of the block\r\n\t\t\t\t\t\t// Ignore next used block\r\n\t\t\t\t\t\tthis._points.set(ptr, {\r\n\t\t\t\t\t\t\tfree: false,\r\n\t\t\t\t\t\t\tnext: start,\r\n\t\t\t\t\t\t\tdata: block.data,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tthis._points.set(start, { free: true, next: block.next });\r\n\t\t\t\t\t\treturn this;\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (ptr < start && end < block.next) {\r\n\t\t\t\t\t// Deallocate middle of a block\r\n\t\t\t\t\tthis._points.set(ptr, { free: false, next: start, data: block.data });\r\n\t\t\t\t\tthis._points.set(start, { free: true, next: end });\r\n\t\t\t\t\tthis._points.set(end, {\r\n\t\t\t\t\t\tfree: false,\r\n\t\t\t\t\t\tnext: block.next,\r\n\t\t\t\t\t\tdata: block.data,\r\n\t\t\t\t\t});\r\n\t\t\t\t\treturn this;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tprev = ptr;\r\n\t\t\tprevBlock = block;\r\n\t\t\tptr = block.next;\r\n\t\t\tif (ptr <= prev) {\r\n\t\t\t\tthrow new Error(`Bad allocation map: tried to go backwards`);\r\n\t\t\t}\r\n\t\t\t// if (start === Number.MAX_SAFE_INTEGER) {\r\n\t\t\tif (ptr >= this._max) {\r\n\t\t\t\tthrow new Error(`Could not deallocate. Sparse?`);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method forEachBlock(fn: Function, data: Number): this\r\n\t * Runs the given callback `Function` `fn`, only on blocks allocated with\r\n\t * an LoD identifier (`data`) exactly equal (`===`) to the given one. `fn`\r\n\t * receives the start and length of each allocated block as its two\r\n\t * parameters.\r\n\t * @alternative\r\n\t * @method forEachBlock(fn: Function, data: String): this\r\n\t * @alternative\r\n\t * @method forEachBlock(fn: Function, data: undefined): this\r\n\t * Runs the given fallback `Function` `fn` on all allocated blocks.\r\n\t */\r\n\tforEachBlock(fn, data) {\r\n\t\tlet ptr = 0;\r\n\t\twhile (true) {\r\n\t\t\tconst block = this._points.get(ptr);\r\n\t\t\tif (!block.free && (data === undefined || block.data === data)) {\r\n\t\t\t\tfn(ptr, block.next - ptr);\r\n\t\t\t}\r\n\t\t\tif (block.next <= ptr) {\r\n\t\t\t\tthrow new Error(`Bad allocation map: tried to go backwards`);\r\n\t\t\t}\r\n\t\t\tptr = block.next;\r\n\t\t\tif (ptr >= this._max) {\r\n\t\t\t\treturn this;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n","import { registerFactory } from \"../GliiFactory.mjs\";\r\nimport { default as IndexBuffer } from \"./IndexBuffer.mjs\";\r\nimport { default as LoDAllocator } from \"../LoDAllocator.mjs\";\r\n\r\n/**\r\n * @class LoDIndices\r\n * @inherits IndexBuffer\r\n * @relationship compositionOf LoDAllocator, 0..1, 1..1\r\n *\r\n * Similar to `SparseIndices`, a `LoDIndices` allows for allocating and\r\n * deallocating blocks of primitive slots via `allocateSlots` and `deallocateSlots`.\r\n *\r\n * The main difference is that each allocation must be done with a level-of-detail\r\n * (LoD) identifier, commonly a `Number` or a `String` (but also a `Symbol`).\r\n *\r\n * Like `SparseIndices`, calling `drawMe` will make a WebGL draw call per\r\n * allocation block. Unlike `SparseIndices`, only those blocks with a specific\r\n * LoD will be drawn.\r\n *\r\n */\r\n\r\nexport default class LoDIndices extends IndexBuffer {\r\n\tconstructor(gl, gliiFactory, options = {}) {\r\n\t\tsuper(gl, gliiFactory, options);\r\n\r\n\t\t// Allocator instance for blocks in self's `ELEMENT_ARRAY_BUFFER`.\r\n\t\tif (this._growFactor) {\r\n\t\t\tthis._slotAllocator = new LoDAllocator();\r\n\t\t} else {\r\n\t\t\tthis._slotAllocator = new LoDAllocator(this._size);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method allocateSlots(count: Number, lod: Number): Number\r\n\t * Allocates `count` slots for indices. Returns the offset of the first\r\n\t * slot.\r\n\t *\r\n\t * For `gl.TRIANGLES` (`gl.LINES`), allocate 3 (2) slots per triangle (line).\r\n\t * @alternative\r\n\t * @method allocateSlots(count: Number, lod: String): Number\r\n\t */\r\n\tallocateSlots(count, lod) {\r\n\t\treturn this._slotAllocator.allocateBlock(count, lod);\r\n\t}\r\n\r\n\t/**\r\n\t * @method allocateSet(indices: Array of Number, lod: Number, relative?:Boolean): Number\r\n\t * Combination of `allocate()` and `set()`. Allocates the neccesary\r\n\t * space for the given indices in the given LoD, and sets their values.\r\n\t *\r\n\t * If `relative` is set to `true`, then indices are considered to be\r\n\t * relative to the allocation start (otherwise, they're absolute).\r\n\t *\r\n\t * Returns the offset of the allocation block start.\r\n\t * @alternative\r\n\t * @method allocateSet(indices: Array of Number, lod: String, relative?:Boolean): Number\r\n\t */\r\n\tallocateSet(lod, indices, relative = false) {\r\n\t\tconst start = this.allocateSlots(indices.length, lod);\r\n\t\tif (relative) {\r\n\t\t\tthis.set(\r\n\t\t\t\tstart,\r\n\t\t\t\tindices.map((i) => start + i)\r\n\t\t\t);\r\n\t\t} else {\r\n\t\t\tthis.set(start, indices);\r\n\t\t}\r\n\t\treturn start;\r\n\t}\r\n\r\n\t/**\r\n\t * @method deallocateSlots(start, count: Number): this\r\n\t * Deallocates `count` slots for indices, started with the `start`th slot.\r\n\t *\r\n\t * For `gl.TRIANGLES` (`gl.LINES`), allocate 3 (2) slots per triangle (line).\r\n\t *\r\n\t * All deallocated slots must belong to the same LoD. Otherwose, behaviour\r\n\t * may be unpredictable.\r\n\t * @alternative\r\n\t * @method deallocateSlots(start, count: Number): this\r\n\t */\r\n\tdeallocateSlots(start, count) {\r\n\t\tthis._slotAllocator.deallocateBlock(start, count);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method deallocateLoD(lod: Number): this\r\n\t * Deallocates all the slots of the given LoD.\r\n\t * @alternative\r\n\t * @method deallocateLoD(lod: String): this\r\n\t */\r\n\tdeallocateLoD(lod) {\r\n\t\treturn this.forEachBlock(lod, this.deallocateSlots.bind(this));\r\n\t}\r\n\r\n\t/**\r\n\t * @method forEachBlock(lod: Number, fn: Function): this\r\n\t * Runs the given callback `Function` `fn` once per allocated block, but\r\n\t * only for blocks with the given LoD.\r\n\t *\r\n\t * The callback function shall receive the start and length of each\r\n\t * allocated block. Both figures are given in number of *vertex slots*\r\n\t * and not in primitives (i.e. divide by 3 when working with triangles).\r\n\t * @alternative\r\n\t * @method forEachBlock(lod: String, count: Number): Number\r\n\t */\r\n\tforEachBlock(fn, lod) {\r\n\t\tthis._slotAllocator.forEachBlock(fn, lod);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method copyWithin(target: Number, start: Number, end: Number): this\r\n\t * Akin to `TypedArray.copyWithin()` copies indices from `start` to `end`\r\n\t * into a section of itself, starting at `target`.\r\n\t *\r\n\t * Unlike `TypedArray.copyWithin()`, it will grow the data structures if needed.\r\n\t *\r\n\t * The typical use case is to copy a portion of a LoD into another LoD.\r\n\t */\r\n\tcopyWithin(target, start, end) {\r\n\t\tif (!this._ramData) {\r\n\t\t\tthrow new Error(\"Cannot copyWithin() in a non-growable LoDIndices.\");\r\n\t\t}\r\n\t\tthis.grow(target + end - start);\r\n\t\tthis._ramData.copyWithin(target, start, end);\r\n\r\n\t\tthis._gl.bufferSubData(\r\n\t\t\tthis._gl.ELEMENT_ARRAY_BUFFER,\r\n\t\t\ttarget * this._bytesPerSlot,\r\n\t\t\tthis._ramData.subarray(start, end)\r\n\t\t);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// Internal only. Does the GL drawElement() calls, but assumes that everyhing else\r\n\t// (bound program, textures, attribute name-locations, uniform name-locations-values)\r\n\t// has been set up already.\r\n\tdrawMe(lod) {\r\n\t\tthis.bindMe();\r\n\t\tthis._slotAllocator.forEachBlock((start, length) => {\r\n\t\t\tconst startByte = start * this._bytesPerSlot;\r\n\t\t\tthis._gl.drawElements(this._drawMode, length, this._type, startByte);\r\n\t\t}, lod);\r\n\t}\r\n\r\n\t// Internal only. Does the GL drawElement() calls, but assumes that everyhing else\r\n\t// (bound program, textures, attribute name-locations, uniform name-locations-values)\r\n\t// has been set up already.\r\n\tdrawMePartial(start, count) {\r\n\t\tthis.bindMe();\r\n\t\tthis._gl.drawElements(\r\n\t\t\tthis._drawMode,\r\n\t\t\tcount,\r\n\t\t\tthis._type,\r\n\t\t\tstart * this._bytesPerSlot\r\n\t\t);\r\n\t}\r\n}\r\n\r\n/**\r\n * @factory GliiFactory.LoDIndices(options: LoDIndices options)\r\n * @class Glii\r\n * @section Class wrappers\r\n * @property LoDIndices(options: LoDIndices options): Prototype of LoDIndices\r\n * Wrapped `LoDIndices` class\r\n */\r\nregisterFactory(\"LoDIndices\", function (gl, gliiFactory) {\r\n\treturn class WrappedLoDIndices extends LoDIndices {\r\n\t\tconstructor(options) {\r\n\t\t\tsuper(gl, gliiFactory, options);\r\n\t\t}\r\n\t};\r\n});\r\n","import { registerFactory } from \"../GliiFactory.mjs\";\r\nimport { default as TriangleIndices } from \"./TriangleIndices.mjs\";\r\n\r\n/**\r\n * @class WireframeTriangleIndices\r\n * @inherits TriangleIndices\r\n *\r\n * A decorated flavour of `TriangleIndices`; draws each triplet of vertices as a\r\n * `gl.LINE_LOOP` to produce a wireframe result.\r\n *\r\n * The width of the wireframe can be configured via the `width` constructor option.\r\n * Otherwise, this class works as a drop-in replacement for `TriangleIndices`.\r\n *\r\n * Note that the available line widths depend on your platform (i.e. graphics card +\r\n * web browser + OpenGL software stack). You should not assume that line widths\r\n * greater than 1 are available.\r\n */\r\n\r\nexport default class WireframeTriangleIndices extends TriangleIndices {\r\n\tconstructor(gl, gliiFactory, options = {}) {\r\n\t\tsuper(gl, gliiFactory, options);\r\n\t\t/**\r\n\t\t * @section\r\n\t\t * @aka WireframeTriangleIndices options\r\n\t\t * @option width: Number = 1; Width of the wireframe lines, in pixels.\r\n\t\t */\r\n\t\tthis._width = options.width || 1;\r\n\t}\r\n\r\n\t// Internal only. Does the GL drawElement() calls, but assumes that everyhing else\r\n\t// (bound program, textures, attribute name-locations, uniform name-locations-values)\r\n\t// has been set up already.\r\n\tdrawMe() {\r\n\t\tthis.bindMe();\r\n\t\tthis._gl.lineWidth(this._width);\r\n\t\tthis._slotAllocator.forEachBlock((start, length) => {\r\n\t\t\tfor (let i = 0; i < length; i += 3) {\r\n\t\t\t\tconst startByte = (start + i) * this._bytesPerSlot;\r\n\t\t\t\tthis._gl.drawElements(this._gl.LINE_LOOP, 3, this._type, startByte);\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n\tdrawMePartial(start, count) {\r\n\t\tthis.bindMe();\r\n\t\tthis._gl.lineWidth(this._width);\r\n\t\tfor (let i = 0; i < count; i += 3) {\r\n\t\t\tconst startByte = (start + i) * this._bytesPerSlot;\r\n\t\t\tthis._gl.drawElements(this._gl.LINE_LOOP, 3, this._type, startByte);\r\n\t\t}\r\n\t}\r\n}\r\n\r\n/**\r\n * @class WireframeTriangleIndices\r\n * @factory GliiFactory.WireframeTriangleIndices(options: WireframeTriangleIndices options)\r\n * @class Glii\r\n * @section Class wrappers\r\n * @property WireframeTriangleIndices(options: WireframeTriangleIndices options): Prototype of WireframeTriangleIndices\r\n * Wrapped `WireframeTriangleIndices` class\r\n */\r\nregisterFactory(\"WireframeTriangleIndices\", function (gl, gliiFactory) {\r\n\treturn class WrappedWireframeTriangleIndices extends WireframeTriangleIndices {\r\n\t\tconstructor(options) {\r\n\t\t\tsuper(gl, gliiFactory, options);\r\n\t\t}\r\n\t};\r\n});\r\n","import { registerFactory } from \"../GliiFactory.mjs\";\r\nimport { default as SparseIndices } from \"./SparseIndices.mjs\";\r\n\r\n/**\r\n * @class PointIndices\r\n * @inherits SparseIndices\r\n *\r\n * Represents a set of vertex indices for point primitives (i.e. one vertex per point).\r\n *\r\n * The draw mode of a `PointIndices` is forced into being `gl.POINTS`.\r\n */\r\n\r\nexport default class PointIndices extends SparseIndices {\r\n\tconstructor(gl, gliiFactory, options = {}) {\r\n\t\toptions.drawMode = gl.POINTS;\r\n\t\tsuper(gl, gliiFactory, options);\r\n\t}\r\n\r\n\t/**\r\n\t * @method allocatePoints(count: Number): Number\r\n\t */\r\n\tallocatePoints(count) {\r\n\t\t// For points, there shall be one slot per point = vertex.\r\n\t\treturn super.allocateSlots(count);\r\n\t}\r\n\r\n\t/**\r\n\t * @method deallocatePoints(start, count: Number): this\r\n\t */\r\n\tdeallocatePoints(start, count) {\r\n\t\treturn super.deallocateSlots(start, count);\r\n\t}\r\n}\r\n\r\n/**\r\n * @factory GliiFactory.PointIndices(options: PointIndices options)\r\n * @class Glii\r\n * @section Class wrappers\r\n * @property PointIndices(options: PointIndices options): Prototype of PointIndices\r\n * Wrapped `PointIndices` class\r\n */\r\nregisterFactory(\"PointIndices\", function (gl, gliiFactory) {\r\n\treturn class WrappedPointIndices extends PointIndices {\r\n\t\tconstructor(options) {\r\n\t\t\tsuper(gl, gliiFactory, options);\r\n\t\t}\r\n\t};\r\n});\r\n","// Akin to typeMap, but in reverse: maps GL constants to TypedArray prototypes intead.\r\n\r\n// Includes constants for texture pixel types as well (same kind of mapping, constant\r\n// to corresponding TypedArray prototype able to hold that kind of data).\r\n\r\n// prettier-ignore\r\nexport default new Map([\r\n\t[0x1400,\tInt8Array ], // gl.BYTE\r\n\t[0x1401,\tUint8Array ], // gl.UNSIGNED_BYTE\r\n\t[0x1402,\tInt16Array ], // gl.SHORT\r\n\t[0x1402,\tInt16Array ], // gl.SHORT\r\n\t[0x1403,\tUint16Array ], // gl.UNSIGNED_SHORT\r\n\t[0x1404,\tInt32Array ], // gl.INT\r\n\t[0x1405,\tUint32Array ], // gl.UNSIGNED_INT\r\n\t[0x1406,\tFloat32Array], // gl.FLOAT\r\n\r\n\t[0x8033,\tUint16Array], // gl.UNSIGNED_SHORT_4_4_4_4\r\n\t[0x8034,\tUint16Array], // gl.UNSIGNED_SHORT_5_5_5_1\r\n\t[0x8363,\tUint16Array], // gl.UNSIGNED_SHORT_5_6_5\r\n\r\n\t[0x84FA,\tUint16Array], // ext.UNSIGNED_INT_24_8_WEBGL from WEBGL_depth_texture\r\n\r\n\t[0x8D61,\tUint16Array], // ext.HALF_FLOAT_OES from OES_texture_half_float\r\n]);\r\n","/**\r\n * @class RenderBuffer\r\n * @inherits AbstractFrameBufferAttachment\r\n *\r\n * Wraps a [`WebGLRenderbuffer`](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer)\r\n * and offers convenience methods.\r\n *\r\n * A `RenderBuffer` is most akin to an image: a rectangular collection of\r\n * pixels with `width`, `height` and a `internalFormat`. The main difference\r\n * between a `RenderBuffer` and a 2D `Texture` is the different `internalFormat`s.\r\n */\r\n\r\nimport { registerFactory } from \"../GliiFactory.mjs\";\r\n\r\nexport default class RenderBuffer {\r\n\t#width;\r\n\t#height;\r\n\t#gl;\r\n\t#rb;\r\n\t#internalFormat;\r\n\t#multisample;\r\n\r\n\tconstructor(gl, opts = {}) {\r\n\t\tthis.#gl = gl;\r\n\t\tthis.#rb = gl.createRenderbuffer();\r\n\r\n\t\t// @option size: Array of Number\r\n\t\t// Width and height of this `FrameBuffer`, in pixels, as a 2-component array.\r\n\t\t// If specified, it overrides the `width` and `height` options.\r\n\t\tif (\"size\" in opts) {\r\n\t\t\tif (\"width\" in opts || \"height\" in opts) {\r\n\t\t\t\tthrow new Error(\r\n\t\t\t\t\t'Expected either \"size\" or \"width\"/\"height\", but both were provided'\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t\tthis.#width = opts.size[0];\r\n\t\t\tthis.#height = opts.size[1];\r\n\t\t} else {\r\n\t\t\t// @option width: Number = 256\r\n\t\t\t// Width of this `RenderBuffer`, in pixels.\r\n\t\t\tthis.#width = opts.width || 256;\r\n\r\n\t\t\t// @option height: Number = 256\r\n\t\t\t// Height of this `RenderBuffer`, in pixels.\r\n\t\t\tthis.#height = opts.height || 256;\r\n\t\t}\r\n\r\n\t\t// @option internalFormat: Renderbuffer format constant = gl.RGBA4\r\n\t\t// Internal format of this `RenderBuffer`, as per [`renderBufferStorage`](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/renderbufferStorage).\r\n\t\tthis.#internalFormat = opts.internalFormat || gl.RGBA4;\r\n\r\n\t\t// @option multisample: Number = 0\r\n\t\t// Number of samples to be used in the `RenderBuffer`. Set to higher values\r\n\t\t// for smoother antialiasing. Only has effect if the Glii context is WebGL2.\r\n\t\tthis.#multisample = opts.multisample || 0;\r\n\r\n\t\tthis.resize(this.#width, this.#height);\r\n\t}\r\n\r\n\t/**\r\n\t * @property rb: WebGLRenderbuffer\r\n\t * The underlying instance of `WebGLRenderBuffer`. Read-only.\r\n\t */\r\n\tget rb() {\r\n\t\tif (!this.#rb) {\r\n\t\t\tthrow new Error(\"RenderBuffer has been destroyed and cannot be used\");\r\n\t\t}\r\n\t\treturn this.#rb;\r\n\t}\r\n\r\n\t/**\r\n\t * @method resize(x: Number, y: Number): this\r\n\t * Sets a new size for the renderbuffer (destroying its data in the process).\r\n\t */\r\n\tresize(x, y) {\r\n\t\tthis.#width = x;\r\n\t\tthis.#height = y;\r\n\t\tconst gl = this.#gl;\r\n\r\n\t\tgl.bindRenderbuffer(gl.RENDERBUFFER, this.#rb);\r\n\r\n\t\tif (\"renderbufferStorageMultisample\" in gl && this.#multisample > 1) {\r\n\t\t\tgl.renderbufferStorageMultisample(\r\n\t\t\t\tgl.RENDERBUFFER,\r\n\t\t\t\tthis.#multisample,\r\n\t\t\t\tthis.#internalFormat,\r\n\t\t\t\tthis.#width,\r\n\t\t\t\tthis.#height\r\n\t\t\t);\r\n\t\t} else {\r\n\t\t\tgl.renderbufferStorage(\r\n\t\t\t\tgl.RENDERBUFFER,\r\n\t\t\t\tthis.#internalFormat,\r\n\t\t\t\tthis.#width,\r\n\t\t\t\tthis.#height\r\n\t\t\t);\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method destroy(): this\r\n\t * Tells WebGL to free resources associated with this `RenderBuffer`. Use\r\n\t * when the `RenderBuffer` won't be used anymore.\r\n\t *\r\n\t * After being destroyed, WebGL programs should not use any `FrameBuffer` which\r\n\t * points to the destroyed `RenderBuffer`.\r\n\t */\r\n\tdestroy() {\r\n\t\tthis.#gl.deleteRenderbuffer(this.#rb);\r\n\t\tthis.#rb = undefined;\r\n\t}\r\n}\r\n\r\n/**\r\n * @factory GliiFactory.RenderBuffer(options: RenderBuffer options)\r\n * @class Glii\r\n * @section Class wrappers\r\n * @property RenderBuffer(options: RenderBuffer options): Prototype of RenderBuffer\r\n * Wrapped `RenderBuffer` class\r\n */\r\nregisterFactory(\"RenderBuffer\", function (gl) {\r\n\treturn class WrappedRenderBuffer extends RenderBuffer {\r\n\t\tconstructor(opts) {\r\n\t\t\tsuper(gl, opts);\r\n\t\t}\r\n\t};\r\n});\r\n","import { default as reverseTypeMap } from \"../util/reverseTypeMap.mjs\";\r\n\r\n/**\r\n * @class FrameBuffer\r\n * @relationship compositionOf AbstractFrameBufferAttachment, 0..n, 1..n\r\n *\r\n * Wraps a [`WebGLFramebuffer`](https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer)\r\n * and offers convenience methods.\r\n *\r\n * A `FrameBuffer` is a collection of `Texture`s/`RenderBuffer`s: (at least) one for\r\n * colour (RGBA), an optional one for depth and an optional one for stencil.\r\n *\r\n * In GL parlance, each of the `Texture`/`RenderBuffer`s that make up a `FrameBuffer`\r\n * is called an \"attachment\". Each attachment must have an `internalFormat` fitting its\r\n * colour/depth/stencil role.\r\n *\r\n * In any operations that allow a `FrameBuffer`, not giving one (or explicitly setting it\r\n * to `null`) shall work on the \"default\" framebuffer - in the usual case, this means an\r\n * internally-created framebuffer with the colour attachment linked to the ``\r\n * that the GL context was created out of.\r\n *\r\n * Multiple colour attachments are only possible in WebGL2, or in WebGL1 when the\r\n * [`WEBGL_draw_buffers`](https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_draw_buffers) extension is available.\r\n *\r\n * Note that (in most combinations of drivers&hardware) the colour attachment(s) **must**\r\n * be textures using the `RGBA`/`UNSIGNED_BYTE` format+type combination.\r\n *\r\n * @example\r\n *\r\n * ```\r\n * var fb1 = new gliiFactory.FrameBuffer({\r\n * \tsize: new XY(1024, 1024),\r\n * \tcolor: [new gliiFactory.Texture( ... )],\r\n * \tstencil: new gliiFactory.RenderBuffer( ... ),\r\n * \tdepth: new gliiFactory.RenderBuffer( ... ),\r\n * });\r\n *\r\n * var size = new XY(1024, 1024);\r\n * var fb2 = new gliiFactory.FrameBuffer({\r\n * \tsize: size,\r\n * \tcolor: [\r\n * \t\tnew gliiFactory.Texture( size: size, ... ),\r\n * \t\tnew gliiFactory.Texture( size: size, ... )\r\n * \t],\r\n * \tstencil: false,\r\n * \tdepth: false,\r\n * });\r\n * ```\r\n */\r\n\r\n/// TODO: depth format for textures, only available with extension:\r\n/// https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_depth_texture\r\n\r\nimport { registerFactory } from \"../GliiFactory.mjs\";\r\nimport Texture from \"../Texture.mjs\";\r\nimport RenderBuffer from \"./RenderBuffer.mjs\";\r\n\r\nexport default class FrameBuffer {\r\n\t#gl;\r\n\t#fb;\r\n\r\n\t#width;\r\n\t#height;\r\n\r\n\t#colourAttachs;\r\n\t#depth;\r\n\t#stencil;\r\n\r\n\tconstructor(gl, opts = {}) {\r\n\t\tthis.#gl = gl;\r\n\t\tthis.#fb = gl.createFramebuffer();\r\n\r\n\t\tgl.bindFramebuffer(gl.FRAMEBUFFER, this.#fb);\r\n\r\n\t\t// @section\r\n\t\t// @aka FrameBuffer options\r\n\t\t// @option size: Array of Number\r\n\t\t// Width and height of this `FrameBuffer`, in pixels, as a 2-component array.\r\n\t\t// If specified, it overrides the `width` and `height` options.\r\n\t\tif (\"size\" in opts) {\r\n\t\t\tif (\"width\" in opts || \"height\" in opts) {\r\n\t\t\t\tthrow new Error(\r\n\t\t\t\t\t'Expected either \"size\" or \"width\"/\"height\", but both were provided'\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t\tthis.#width = opts.size[0];\r\n\t\t\tthis.#height = opts.size[1];\r\n\t\t} else {\r\n\t\t\t// @option width: Number = 256\r\n\t\t\t// Width of this `RenderBuffer`, in pixels.\r\n\t\t\tthis.#width = opts.width || 256;\r\n\r\n\t\t\t// @option height: Number = 256\r\n\t\t\t// Height of this `RenderBuffer`, in pixels.\r\n\t\t\tthis.#height = opts.height || 256;\r\n\t\t}\r\n\r\n\t\t// @option colour: Array of AbstractFrameBufferAttachment = []\r\n\t\t// An array of colour attachment(s) (either `Texture`s or `RenderBuffer`s)\r\n\t\tthis.#colourAttachs = opts.colour || opts.color || [];\r\n\t\t// \t\tthis.colourAttachZero = colourAttachs[0];\r\n\t\tthis.#colourAttachs.forEach((att, i) => {\r\n\t\t\tif (att instanceof Texture) {\r\n\t\t\t\tatt.getUnit();\r\n\t\t\t\tif (!att.isLoaded()) {\r\n\t\t\t\t\t// If the texture is empty (which means, no width/height set),\r\n\t\t\t\t\t// init it to a blank texture the same size as this FB.\r\n\t\t\t\t\t/// TODO: This will fail for pixel types UNSIGNED_SHORT_5_5_5_1 *et al*,\r\n\t\t\t\t\t/// since the size of the arrays and the components per pixel do not match.\r\n\t\t\t\t\t/// There is a need for an additional utility function to instantiate\r\n\t\t\t\t\t/// this kind of typed array.\r\n\t\t\t\t\tatt.texArray(\r\n\t\t\t\t\t\tthis.#width,\r\n\t\t\t\t\t\tthis.#height,\r\n\t\t\t\t\t\tnew (reverseTypeMap.get(att.type))(\r\n\t\t\t\t\t\t\tthis.#width * this.#height * att.getComponentsPerTexel()\r\n\t\t\t\t\t\t)\r\n\t\t\t\t\t);\r\n\r\n\t\t\t\t\t// gl.texImage2D(gl.TEXTURE_2D, 0, this.internalFormat,this.#width, this.#height, 0, this.format, this.type, null);\r\n\t\t\t\t}\r\n\t\t\t\tgl.framebufferTexture2D(\r\n\t\t\t\t\tgl.FRAMEBUFFER,\r\n\t\t\t\t\tgl.COLOR_ATTACHMENT0 + i,\r\n\t\t\t\t\tgl.TEXTURE_2D,\r\n\t\t\t\t\tatt.tex,\r\n\t\t\t\t\t0\r\n\t\t\t\t);\r\n\t\t\t} else if (att instanceof RenderBuffer) {\r\n\t\t\t\tgl.framebufferRenderbuffer(\r\n\t\t\t\t\tgl.FRAMEBUFFER,\r\n\t\t\t\t\tgl.COLOR_ATTACHMENT0 + i,\r\n\t\t\t\t\tgl.RENDERBUFFER,\r\n\t\t\t\t\tatt.rb\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// @option depth: RenderBuffer = false\r\n\t\t// A `RenderBuffer` for the depth attachment. Must have a depth-compatible `internalformat`.\r\n\t\tif (opts.depth && opts.depth instanceof RenderBuffer) {\r\n\t\t\tthis.#depth = opts.depth;\r\n\t\t\tgl.framebufferRenderbuffer(\r\n\t\t\t\tgl.FRAMEBUFFER,\r\n\t\t\t\tgl.DEPTH_ATTACHMENT,\r\n\t\t\t\tgl.RENDERBUFFER,\r\n\t\t\t\topts.depth.rb\r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\t// @option stencil: RenderBuffer = false\r\n\t\t// A `RenderBuffer` for the stencil attachment. Must have a stencil-compatible `internalformat`.\r\n\t\tif (opts.stencil && opts.stencil instanceof RenderBuffer) {\r\n\t\t\tthis.#stencil = opts.stencil;\r\n\t\t\tgl.framebufferRenderbuffer(\r\n\t\t\t\tgl.FRAMEBUFFER,\r\n\t\t\t\tgl.STENCIL_ATTACHMENT,\r\n\t\t\t\tgl.RENDERBUFFER,\r\n\t\t\t\topts.stencil.rb\r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\tthis.#checkStatus();\r\n\t}\r\n\r\n\t/**\r\n\t * @property fb: WebGLFramebuffer\r\n\t * The underlying instance of `WebGLFramebuffer`. Read-only.\r\n\t */\r\n\tget fb() {\r\n\t\treturn this.#fb;\r\n\t}\r\n\r\n\t/**\r\n\t * @property width: Number\r\n\t * The width of the framebuffer (and all its attachments), in pixels. Read-only.\r\n\t */\r\n\tget width() {\r\n\t\treturn this.#width;\r\n\t}\r\n\r\n\t/**\r\n\t * @property height: Number\r\n\t * The height of the framebuffer (and all its attachments), in pixels. Read-only.\r\n\t */\r\n\tget height() {\r\n\t\treturn this.#height;\r\n\t}\r\n\r\n\t/**\r\n\t * @method resize(x: Number, y: Number): this\r\n\t * Sets a new size for the framebuffer's attachments (textures/renderbuffers),\r\n\t * destroying their data in the process.\r\n\t */\r\n\tresize(x, y) {\r\n\t\tthis.#height = y;\r\n\t\tthis.#width = x;\r\n\r\n\t\tthis.#colourAttachs.forEach((att, _) => {\r\n\t\t\tatt.getUnit();\r\n\t\t\tatt.texArray(\r\n\t\t\t\tx,\r\n\t\t\t\ty,\r\n\t\t\t\tnew (reverseTypeMap.get(att.type))(\r\n\t\t\t\t\tthis.#width * this.#height * att.getComponentsPerTexel()\r\n\t\t\t\t)\r\n\t\t\t);\r\n\t\t});\r\n\r\n\t\tif (this.#depth && this.#depth instanceof RenderBuffer) {\r\n\t\t\tthis.#depth.resize(x, y);\r\n\t\t}\r\n\r\n\t\tif (this.#stencil && this.#stencil instanceof RenderBuffer) {\r\n\t\t\tthis.#depth.resize(x, y);\r\n\t\t}\r\n\t\tthis.#checkStatus();\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method destroy(): this\r\n\t * Tells WebGL to free resources associated with this framebuffer. Use\r\n\t * when the framebuffer won't be used anymore.\r\n\t *\r\n\t * After being destroyed, the framebuffer should not be used in a program.\r\n\t * Does not destroy any associated textures or renderbuffers.\r\n\t */\r\n\tdestroy() {\r\n\t\tthis.#gl.deleteFramebuffer(this.#fb);\r\n\t}\r\n\r\n\t#checkStatus() {\r\n\t\tconst gl = this.#gl;\r\n\t\tconst status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);\r\n\t\tif (status === gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT) {\r\n\t\t\t// One reason for this is colour attachment being textures of a format/type\r\n\t\t\t// different than RGBA/UNSIGNED_BYTE. See\r\n\t\t\t// https://www.khronos.org/registry/webgl/extensions/WEBGL_draw_buffers/\r\n\t\t\tthrow new Error(\r\n\t\t\t\t`The attachment types are mismatched or not all framebuffer attachment points are framebuffer attachment complete.\r\nFor valid format/type combinations of framebuffer attachments, see https://www.khronos.org/registry/webgl/specs/1.0/#6.6 and https://www.khronos.org/registry/webgl/extensions/WEBGL_draw_buffers/`\r\n\t\t\t);\r\n\t\t} else if (status === gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) {\r\n\t\t\tthrow new Error(\"There is no attachment.\");\r\n\t\t} else if (status === gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS) {\r\n\t\t\tthrow new Error(\"Height and width of the attachment are not the same.\");\r\n\t\t} else if (status === gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) {\r\n\t\t\tthrow new Error(\"There is no attachment.\");\r\n\t\t} else if (status === gl.FRAMEBUFFER_UNSUPPORTED) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"The format of the attachment is not supported, or depth and stencil attachments are not the same renderbuffer.\"\r\n\t\t\t);\r\n\t\t} else if (status !== gl.FRAMEBUFFER_COMPLETE) {\r\n\t\t\tthrow new Error(\"FrameBuffer invalid \" + status);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method readPixels: TypedArray\r\n\t * @param x?: Number\r\n\t * @param y?: Number\r\n\t * @param width?: Number\r\n\t * @return TypedArray\r\n\t *\r\n\t * Reads pixels from the colour attachment of the framebuffer, and returns a `TypedArray`\r\n\t * (e.g. a `Uint8Array` for 8-bit RGBA textures) with the data.\r\n\t *\r\n\t * Defaults to reading the entire colour attachment (from `0,0` to its witdh-height),\r\n\t * handles the datatypes, and creates a new `TypedArray` of the appropriate kind.\r\n\t *\r\n\t */\r\n\t/// TODO: How are float32 readbacks handled?? It seems that they neccesarily need an extension,\r\n\t/// but the documentation is scarce about the issue.\r\n\r\n\treadPixels(x, y, w, h) {\r\n\t\tconst gl = this.#gl;\r\n\r\n\t\tx = x || 0;\r\n\t\ty = y || 0;\r\n\t\tw = w || this.#width;\r\n\t\th = h || this.#height;\r\n\r\n\t\tconst attach = this.#colourAttachs[0];\r\n\t\tlet format, type, arrClass;\r\n\t\tlet itemsPerPx = 1;\r\n\r\n\t\tif (attach instanceof Texture) {\r\n\t\t\tif (\r\n\t\t\t\tattach.internalFormat === gl.RGBA ||\r\n\t\t\t\tattach.internalFormat === gl.RGB ||\r\n\t\t\t\tattach.internalFormat === gl.ALPHA\r\n\t\t\t) {\r\n\t\t\t\tformat = attach.internalFormat;\r\n\t\t\t\titemsPerPx = attach.getComponentsPerTexel();\r\n\t\t\t} else if (attach.internalFormat === gl.LUMINANCE) {\r\n\t\t\t\t/// Untested!!!\r\n\t\t\t\tformat = gl.RGB;\r\n\t\t\t\titemsPerPx = 3;\r\n\t\t\t} else if (attach.internalFormat === gl.R32F) {\r\n\t\t\t\t// Needs WebGL2, or float texture extensions\r\n\t\t\t\t// This reads 4 floats instead of 1 float per pixel. But works.\r\n\t\t\t\t// Using gl.RED fails for whatever reason.\r\n\t\t\t\tformat = gl.RGBA;\r\n\t\t\t\titemsPerPx = 4;\r\n\t\t\t} else if (attach.internalFormat === gl.RG32F) {\r\n\t\t\t\t// As the R32F case.\r\n\t\t\t\tformat = gl.RGBA;\r\n\t\t\t\titemsPerPx = 4;\r\n\t\t\t} else {\r\n\t\t\t\tthrow new Error(\r\n\t\t\t\t\t\"Pixels cannot be read back from texture: texture internal format must be R32F, RGB, RGBA, ALPHA, LUMINANCE or LUMINANCE_ALPHA (all other formats yet unsupported by glii)\"\r\n\t\t\t\t);\r\n\t\t\t}\r\n\r\n\t\t\ttype = attach.type || gl.UNSIGNED_BYTE;\r\n\t\t\tarrClass = reverseTypeMap.get(type);\r\n\r\n\t\t\tif (!arrClass) {\r\n\t\t\t\tthrow new Error(\"Unknown texture pixel type\");\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\t// attach instanceof RenderBuffer\r\n\t\t\tif (attach.internalFormat === gl.RGBA4) {\r\n\t\t\t\tformat = gl.RGBA;\r\n\t\t\t\ttype = gl.UNSIGNED_SHORT_4_4_4_4;\r\n\t\t\t\tarrClass = Uint16Array;\r\n\t\t\t} else if (attach.internalFormat === gl.RGB565) {\r\n\t\t\t\tformat = gl.RGB565;\r\n\t\t\t\ttype = gl.UNSIGNED_SHORT_5_6_5;\r\n\t\t\t\tarrClass = Uint16Array;\r\n\t\t\t} else if (attach.internalFormat === gl.RGB5_A1) {\r\n\t\t\t\tformat = gl.RGB5_A1;\r\n\t\t\t\ttype = gl.UNSIGNED_SHORT_5_5_5_1;\r\n\t\t\t\tarrClass = Uint16Array;\r\n\t\t\t} else {\r\n\t\t\t\tthrow new Error(\r\n\t\t\t\t\t\"Pixels cannot be read back from renderbuffer: renderbuffer internal format must be RGBA4, RGB565 or RGB5_A1\"\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst pixelCount = w * h;\r\n\r\n\t\tconst out = new arrClass(pixelCount * itemsPerPx);\r\n\r\n\t\tgl.bindFramebuffer(gl.FRAMEBUFFER, this.#fb);\r\n\r\n\t\tgl.readPixels(x, y, w, h, format, type, out);\r\n\t\treturn out;\r\n\t}\r\n}\r\n\r\n/**\r\n * @factory GliiFactory.FrameBuffer(options: FrameBuffer options)\r\n * @class Glii\r\n * @section Class wrappers\r\n * @property FrameBuffer(options: FrameBuffer options): Prototype of FrameBuffer\r\n * Wrapped `FrameBuffer` class\r\n */\r\nregisterFactory(\"FrameBuffer\", function (gl) {\r\n\treturn class WrappedFrameBuffer extends FrameBuffer {\r\n\t\tconstructor(opts) {\r\n\t\t\tsuper(gl, opts);\r\n\t\t}\r\n\t};\r\n});\r\n","import { default as typeMap } from \"./util/typeMap.mjs\";\r\nimport { default as reverseTypeMap } from \"./util/reverseTypeMap.mjs\";\r\nimport FrameBuffer from \"./FrameBuffer/FrameBuffer.mjs\";\r\n\r\n/// TODO: Subclass? from HTMLImageElement\r\n/// TODO: Subclass? from HTMLVideoElement\r\n\r\n/// TODO: pixelStorei, from\r\n/// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/pixelStorei\r\n/// (e.g. flip Y coordinate)\r\n\r\n/**\r\n * @class Texture\r\n * @inherits AbstractFrameBufferAttachment\r\n * Wraps a [`WebGLTexture`](https://developer.mozilla.org/docs/Web/API/WebGLTexture)\r\n * and offers convenience methods.\r\n *\r\n * Contrary to what one might think, a `Texture` has no implicit size. Its size\r\n * is (re-)defined on any full update (`texImage2D` or `linkRenderBuffer`).\r\n */\r\n\r\nimport { registerFactory } from \"./GliiFactory.mjs\";\r\n\r\nexport default class Texture {\r\n\t#gl;\r\n\t#tex;\r\n\r\n\tconstructor(gl, opts = {}) {\r\n\t\tthis.#gl = gl;\r\n\t\tthis.#tex = gl.createTexture();\r\n\r\n\t\t/**\r\n\t\t * @section\r\n\t\t * @aka Texture options\r\n\t\t * @option minFilter: Texture interpolation constant = gl.NEAREST\r\n\t\t * Initial value of the `minFilter` property\r\n\t\t * @option magFilter: Texture interpolation constant = gl.NEAREST\r\n\t\t * Initial value of the `magFilter` property\r\n\t\t * @option wrapS: Texture wrapping constant = gl.CLAMP_TO_EDGE\r\n\t\t * Initial value for the `wrapS` property\r\n\t\t * @option wrapT: Texture wrapping constant = gl.CLAMP_TO_EDGE\r\n\t\t * Initial value for the `wrapS` property\r\n\t\t * @option internalFormat: Texture format constant = gl.RGBA\r\n\t\t * Initial value for the `internalFormat` property\r\n\t\t * @option format: Texture format constant = gl.RGBA\r\n\t\t * Initial value for the `format` property\r\n\t\t * @option type: Texture type constant = gl.UNSIGNED_BYTE\r\n\t\t * Initial value for the `type` property\r\n\t\t */\r\n\r\n\t\t// Helper for caching bound textures and their active texture unit\r\n\t\tthis._unit = undefined;\r\n\r\n\t\t// Helper for LRU-ing texture units. Shall be (re-)set every time\r\n\t\t// this texture is promoted to an available unit\r\n\t\tthis._lastActive = performance.now();\r\n\r\n\t\t// @property minFilter: Texture interpolation constant = glii.NEAREST\r\n\t\t// Texture minification filter (or \"what to do when pixels in the texture\r\n\t\t// are smaller than pixels in the output image\")\r\n\t\tthis.minFilter = opts.minFilter || gl.NEAREST;\r\n\r\n\t\t// @property magFilter: Texture interpolation constant = glii.NEAREST\r\n\t\t// Texture magification filter (or \"what to do when pixels in the texture\r\n\t\t// are bigger than pixels in the output image\"). Cannot use mipmaps (as\r\n\t\t// mipmaps are always smaller than the texture).\r\n\t\tthis.magFilter = opts.magFilter || gl.NEAREST;\r\n\r\n\t\t// @property wrapS: Texture wrapping constant\r\n\t\t// Value for the `TEXTURE_WRAP_S` [texture parameter](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texParameter) for any subsequent texture full updates\r\n\t\tthis.wrapS = opts.wrapS || gl.CLAMP_TO_EDGE;\r\n\r\n\t\t// @property wrapT: Texture wrapping constant\r\n\t\t// Value for the `TEXTURE_WRAP_T` [texture parameter](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texParameter) for any subsequent texture full updates\r\n\t\tthis.wrapT = opts.wrapT || gl.CLAMP_TO_EDGE;\r\n\r\n\t\t// @property internalFormat: Texture format constant\r\n\t\t// Value for the `internalFormat` parameter for `texImage2D` calls.\r\n\t\tthis.internalFormat = opts.internalFormat || gl.RGBA;\r\n\r\n\t\t// @property format: Texture format constant\r\n\t\t// Value for the `format` parameter for `texImage2D` calls. in WebGL1, this must\r\n\t\t// be equal to `internalFormat`. For WebGL2, see\r\n\t\t// https://registry.khronos.org/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE\r\n\t\tthis.format = opts.format || gl.RGBA;\r\n\r\n\t\t// @property type: Texture pixel type constant\r\n\t\t// Value for the `type` parameter for `texImage2D` calls.\r\n\t\tthis.type = opts.type || gl.UNSIGNED_BYTE;\r\n\r\n\t\t// Loaded flag, to let `FrameBuffer` know when the texture has to be init'd\r\n\t\t// with a specific width/height\r\n\t\tthis._isLoaded = false;\r\n\r\n\t\tthis.width = undefined;\r\n\t\tthis.height = undefined;\r\n\t}\r\n\r\n\t// Each element of this `WeakMap` is keyed by a `WebGLRenderingContext`, and its value\r\n\t// is a plain `Array` of `Texture`s.\r\n\tstatic _boundUnits = new WeakMap();\r\n\r\n\t/**\r\n\t * @property tex: WebGLTexture\r\n\t * The underlying instance of `WebGLTexture`. Read-only.\r\n\t */\r\n\tget tex() {\r\n\t\tif (!this.#tex) {\r\n\t\t\tthrow new Error(\"Texture has been destroyed and cannot be used\");\r\n\t\t}\r\n\t\treturn this.#tex;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Internal methods\r\n\t * @method getUnit(): Number\r\n\t * Returns a the texture unit index (or \"name\" in GL parlance) that this texture\r\n\t * is bound to.\r\n\t *\r\n\t * Calling this method guarantees that the texture is bound into a valid unit,\r\n\t * and that that unit is the active one (until a number of other `Texture`s\r\n\t * call `getUnit()`, at least `MAX_COMBINED_TEXTURE_IMAGE_UNITS`)\r\n\t *\r\n\t * This might expel (unbind) the texture which was used the longest ago.\r\n\t */\r\n\tgetUnit() {\r\n\t\tthis._lastActive = performance.now();\r\n\t\tconst gl = this.#gl;\r\n\t\tif (this._unit !== undefined) {\r\n\t\t\tgl.activeTexture(gl.TEXTURE0 + this._unit);\r\n\t\t\t// \t\t\tconsole.log(\"Texture already bound to unit\", this._unit);\r\n\t\t\treturn this._unit;\r\n\t\t}\r\n\r\n\t\tif (!Texture._boundUnits.has(this.#gl)) {\r\n\t\t\tconst maxUnits = this.#gl.getParameter(\r\n\t\t\t\tthis.#gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS\r\n\t\t\t);\r\n\t\t\tTexture._boundUnits.set(this.#gl, new Array(maxUnits));\r\n\t\t}\r\n\t\tconst units = Texture._boundUnits.get(this.#gl);\r\n\r\n\t\tlet oldestUnit = -1;\r\n\t\tlet oldestTime = Infinity;\r\n\t\tfor (let i = 0, l = units.length; i < l; i++) {\r\n\t\t\tif (units[i] === undefined) {\r\n\t\t\t\t// \t\t\t\tconsole.log(\"Texture newly bound to unit\", i);\r\n\t\t\t\tgl.activeTexture(gl.TEXTURE0 + i);\r\n\t\t\t\tgl.bindTexture(gl.TEXTURE_2D, this.#tex);\r\n\t\t\t\tunits[i] = this;\r\n\t\t\t\treturn (this._unit = i);\r\n\t\t\t}\r\n\t\t\tif (units[i]._lastActive < oldestTime) {\r\n\t\t\t\toldestUnit = i;\r\n\t\t\t\toldestTime = units[i]._lastActive;\r\n\t\t\t}\r\n\t\t}\r\n\t\t// \t\tconsole.log(\"Expelled texture to bound to unit\", oldestUnit);\r\n\t\tgl.activeTexture(gl.TEXTURE0 + oldestUnit);\r\n\t\tgl.bindTexture(gl.TEXTURE_2D, this.#tex);\r\n\t\tunits[oldestUnit]._unit = undefined;\r\n\t\tunits[oldestUnit] = this;\r\n\t\treturn (this._unit = oldestUnit);\r\n\t}\r\n\r\n\t_resetParameters() {\r\n\t\tconst gl = this.#gl;\r\n\r\n\t\tgl.bindTexture(gl.TEXTURE_2D, this.#tex);\r\n\r\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.minFilter);\r\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.magFilter);\r\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, this.wrapS);\r\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this.wrapT);\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @method setParameters: this\r\n\t * @param minFilter?: Texture interpolation constant\r\n\t * @param maxFilter?: Texture interpolation constant\r\n\t * @param wrapS?: Texture wrapping constant\r\n\t * @param wrapT?: Texture wrapping constant\r\n\t * Resets the interpolation and wrapping parameters to the given ones.\r\n\t * Any value can be `undefined` (and if so, won't be updated)\r\n\t */\r\n\tsetParameters(minFilter, maxFilter, wrapS, wrapT) {\r\n\t\tthis.minFilter = minFilter ?? this.minFilter;\r\n\t\tthis.maxFilter = maxFilter ?? this.maxFilter;\r\n\t\tthis.wrapS = wrapS ?? this.wrapS;\r\n\t\tthis.wrapT = wrapT ?? this.wrapT;\r\n\r\n\t\tthis._resetParameters();\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @method texImage2D(img: HTMLImageElement): this\r\n\t * (Re-)sets the texture contents to a copy of the given image. This is considered a \"full update\" of the texture.\r\n\t *\r\n\t * If the texture's format is other than `RGBA`, some data might be dropped (e.g.\r\n\t * putting an RGBA `HTMLImageElement` with an alpha channel into a texture with `RGB`\r\n\t * format shall drop the alpha channel).\r\n\t * @alternative\r\n\t * @method texImage2D(img: HTMLCanvasElement): this\r\n\t * @alternative\r\n\t * @method texImage2D(img: ImageData): this\r\n\t * @alternative\r\n\t * @method texImage2D(img: HTMLVideoElement): this\r\n\t * @alternative\r\n\t * @method texImage2D(img: ImageBitmap): this\r\n\t */\r\n\ttexImage2D(img) {\r\n\t\t/// TODO: set as dirty (?)\r\n\t\t/// TODO: notify programs using this texture that they're dirty now\r\n\t\t/// TODO: Read width/height from the image?? Then set as .width / .height\r\n\t\tconst gl = this.#gl;\r\n\t\tthis._isLoaded = true;\r\n\t\tthis.width = img.width;\r\n\t\tthis.height = img.height;\r\n\t\tthis.getUnit();\r\n\t\tgl.texImage2D(gl.TEXTURE_2D, 0, this.internalFormat, this.format, this.type, img);\r\n\t\tthis._resetParameters();\r\n\r\n\t\tthis.#generateMipmap();\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @method texSubImage2D(img: HTMLImageElement, x: Number, y: Number): this\r\n\t * (Re-)sets a portion of the texture contents to a copy of the given image. The portion\r\n\t * starts at the given `x` and `y` coordinates, and is as big as the image.\r\n\t *\r\n\t * Otherwise, same as `texImage2D`.\r\n\t * @alternative\r\n\t * @method texSubImage2D(img: HTMLCanvasElement, x: Number, y: Number): this\r\n\t * @alternative\r\n\t * @method texSubImage2D(img: ImageData, x: Number, y: Number): this\r\n\t * @alternative\r\n\t * @method texSubImage2D(img: HTMLVideoElement, x: Number, y: Number): this\r\n\t * @alternative\r\n\t * @method texSubImage2D(img: ImageBitmap, x: Number, y: Number): this\r\n\t */\r\n\ttexSubImage2D(img, x, y) {\r\n\t\tconst gl = this.#gl;\r\n\t\tthis._isLoaded = true;\r\n\r\n\t\tthis.getUnit();\r\n\t\tgl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, this.internalFormat, this.type, img);\r\n\t\tthis._resetParameters();\r\n\r\n\t\tthis.#generateMipmap();\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method texArray(w: Number, h: Number, arr: ArrayBufferView): this\r\n\t * (Re-)sets the texture contents to a copy of the given `ArrayBufferView`\r\n\t * (typically a `TypedArray` fitting this texture's `type`/`format`).\r\n\t * Must be given width and height as well.\r\n\t * @alternative\r\n\t * @method texArray(w: Number, h: Number, arr: null): this\r\n\t * Zeroes out the texture.\r\n\t */\r\n\ttexArray(w, h, arr) {\r\n\t\tthis.getUnit();\r\n\t\tconst gl = this.#gl;\r\n\r\n\t\tthis._isLoaded = true;\r\n\t\tthis.width = w;\r\n\t\tthis.height = h;\r\n\r\n\t\t// \t\tgl.texImage2D(target, level, internalformat, width, height, border, format, type, ArrayBufferView? pixels);\r\n\r\n\t\tif (arr === null || reverseTypeMap.get(this.type) !== arr.constructor) {\r\n\t\t\tthrow new Error(\"Passed TypedArray doesn't match the texture's pixel type \");\r\n\t\t}\r\n\r\n\t\tgl.texImage2D(\r\n\t\t\tgl.TEXTURE_2D,\r\n\t\t\t0,\r\n\t\t\tthis.internalFormat,\r\n\t\t\tw,\r\n\t\t\th,\r\n\t\t\t0,\r\n\t\t\tthis.format,\r\n\t\t\tthis.type,\r\n\t\t\tarr\r\n\t\t);\r\n\t\tthis._resetParameters();\r\n\r\n\t\tthis.#generateMipmap();\r\n\t\treturn this;\r\n\t}\r\n\r\n\t#generateMipmap() {\r\n\t\tconst gl = this.#gl;\r\n\t\tif (\r\n\t\t\t(this.minFilter === gl.NEAREST || this.minFilter === gl.LINEAR) &&\r\n\t\t\t(this.magFilter === gl.NEAREST || this.magFilter === gl.LINEAR)\r\n\t\t) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tgl.generateMipmap(gl.TEXTURE_2D);\r\n\t}\r\n\r\n\t/**\r\n\t * @method isLoaded(): Boolean\r\n\t * Returns whether the texture has been initialized with any data at all. `true` after `texImage2D()` and the like.\r\n\t */\r\n\tisLoaded() {\r\n\t\treturn this._isLoaded;\r\n\t}\r\n\r\n\t/**\r\n\t * @method getComponentsPerTexel(): Number\r\n\t * Returns the number of components per texel, based on the `format` property.\r\n\t * (e.g. 3 for `RGB`, 4 for `RGBA`, etc).\r\n\t */\r\n\tgetComponentsPerTexel() {\r\n\t\tconst gl = this.#gl;\r\n\t\tswitch (this.format) {\r\n\t\t\tcase gl.RGBA:\r\n\t\t\tcase gl.RGBA_INTEGER:\r\n\t\t\t\treturn 4;\r\n\t\t\tcase gl.RGB:\r\n\t\t\tcase gl.RGB_INTEGER:\r\n\t\t\t\treturn 3;\r\n\t\t\tcase gl.LUMINANCE_ALPHA:\r\n\t\t\tcase gl.RG:\r\n\t\t\tcase gl.RG_INTEGER:\r\n\t\t\t\treturn 2;\r\n\t\t\tcase gl.LUMINANCE:\r\n\t\t\tcase gl.ALPHA:\r\n\t\t\tcase gl.RED:\r\n\t\t\tcase gl.RED_INTEGER:\r\n\t\t\t\treturn 1;\r\n\t\t\tdefault:\r\n\t\t\t\tthrow new Error(\"Unknown texel data format\");\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method asImageData(x:Number, y:Number, w:Number, h:Number): ImageData\r\n\t * Returns an instance of `ImageData` with a copy of the current contents\r\n\t * of the texture.\r\n\t *\r\n\t * Note this is **not** a performant method call (it creates and destroys\r\n\t * an interim `FrameBuffer`) and is meant for debugging purposes only (i.e.\r\n\t * dumping a texture to a `HTMLCanvasElement` via [`putImageData()`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/putImageData.html)).\r\n\t *\r\n\t * `x` and `y` specify the start of the dump (in texels); `w` and `h` specify\r\n\t * the width and height of the dump. When not specified, the whole texture is\r\n\t * dumped.\r\n\t */\r\n\tasImageData(x, y, w, h) {\r\n\t\tif (!this._isLoaded) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"Must load something into the Texture before calling asImageData()\"\r\n\t\t\t);\r\n\t\t}\r\n\t\tconst gl = this.#gl;\r\n\t\tif (this.format !== gl.RGBA && this.internalFormat !== gl.R32F) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"asImageData() only available for textures with RGBA8 or R32F format\"\r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\tconst fb = new FrameBuffer(gl, {\r\n\t\t\twidth: this.width,\r\n\t\t\theight: this.height,\r\n\t\t\tcolour: [this],\r\n\t\t});\r\n\r\n\t\tlet pixels = fb.readPixels(x, y, w, h);\r\n\r\n\t\tif (this.internalFormat === gl.R32F) {\r\n\t\t\t// Due to WebGL shenanigans, reading a R32F texture has to create\r\n\t\t\t// a RGBA32F texture. Filter the GBA components to leave just the R\r\n\t\t\t// component.\r\n\t\t\tconst size = this.width * this.height;\r\n\t\t\tconst redComponent = new Float32Array(size);\r\n\t\t\tfor (let i = 0; i < size; i++) {\r\n\t\t\t\tredComponent[i] = pixels[i * 4];\r\n\t\t\t}\r\n\t\t\tpixels = redComponent;\r\n\t\t}\r\n\r\n\t\tconst imagedata = new ImageData(\r\n\t\t\tnew Uint8ClampedArray(pixels.buffer),\r\n\t\t\tw ?? this.width,\r\n\t\t\th ?? this.height\r\n\t\t);\r\n\t\tfb.destroy();\r\n\r\n\t\treturn imagedata;\r\n\t}\r\n\r\n\t/**\r\n\t * @method debugIntoCanvas(canvas: HTMLCanvasElement): this\r\n\t *\r\n\t * Convenience wrapper around `asImageData()`. Automates fetching a 2D context\r\n\t * from the given `HTMLCanvasElement`, resizing it, and running `putImageData()`.\r\n\t *\r\n\t * The texture might be inverted in the Y-axis (see `UNPACK_FLIP_Y_WEBGL`), it is\r\n\t * suggested to flip the destination canvas as well with a `transform:scaleY(-1)`\r\n\t * CSS rule.\r\n\t */\r\n\tdebugIntoCanvas(canvas) {\r\n\t\tconst data = this.asImageData();\r\n\t\tcanvas.width = this.width;\r\n\t\tcanvas.height = this.height;\r\n\t\tcanvas.getContext(\"2d\").putImageData(data, 0, 0);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method destroy(): this\r\n\t * Tells WebGL to free resources associated with this `Texture`. Use\r\n\t * when the `Texture` won't be used anymore.\r\n\t *\r\n\t * After being destroyed, WebGL programs should not use the destroyed `Texture`,\r\n\t * not any `FrameBuffer` pointing to the destroyed texture.\r\n\t */\r\n\tdestroy() {\r\n\t\tthis.#gl.deleteTexture(this.#tex);\r\n\t\tthis.#tex = undefined;\r\n\t}\r\n}\r\n\r\n/**\r\n * @factory GliiFactory.Texture(options: Texture options)\r\n *\r\n * @class Glii\r\n * @section Class wrappers\r\n * @property Texture(options: Texture options): Prototype of Texture\r\n * Wrapped `Texture` class\r\n */\r\nregisterFactory(\"Texture\", function (gl) {\r\n\treturn class WrappedTexture extends Texture {\r\n\t\tconstructor(opts) {\r\n\t\t\tsuper(gl, opts);\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * @class Texture\r\n\t\t * @function getMaxSize(): Number\r\n\t\t * Returns the maximum width/height that a `Texture` instance can have.\r\n\t\t */\r\n\t\tstatic getMaxSize() {\r\n\t\t\treturn gl.getParameter(gl.MAX_TEXTURE_SIZE);\r\n\t\t}\r\n\t};\r\n});\r\n","const errRegexp = /ERROR: 0:([0-9]+):(.*)\\n/;\r\n\r\n/**\r\n * Internal helper function\r\n *\r\n * Tries to parse the (first) error from a shader compile log (from a\r\n * getShaderInfoLog() call), extracts the corresponding (offset) line\r\n * from the given source, and throws an error containing that information.\r\n */\r\nexport default function prettifyGlslError(log, header, src, type, lineOffset) {\r\n\tconst match = errRegexp.exec(log);\r\n\tif (match) {\r\n\t\tlet lineNumber = match[1];\r\n\t\tconst msg = match[2];\r\n\r\n\t\tif (lineNumber < lineOffset) {\r\n\t\t\tconst line = header.split(\"\\n\")[lineNumber - 1];\r\n\t\t\tthrow new Error(\r\n\t\t\t\t`Could not compile ${type} shader, reason:\\n${msg}\\nError lies in injected GLSL headers (check inputs like attributes and uniforms)\\nAround line ${lineNumber}: ${line}`\r\n\t\t\t);\r\n\t\t} else {\r\n\t\t\tlineNumber -= lineOffset;\r\n\t\t\tconst line = src.split(\"\\n\")[lineNumber - 1];\r\n\t\t\tthrow new Error(\r\n\t\t\t\t`Could not compile ${type} shader, reason:\\n${msg}\\nAround line ${lineNumber}: ${line}`\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n\r\n\tthrow new Error(`Could not compile ${type} shader, reason in unknown line:\\n${log}`);\r\n}\r\n","import { registerFactory } from \"../GliiFactory.mjs\";\r\n// import { default as IndexBuffer } from \"./Indices/IndexBuffer.mjs\";\r\nimport { default as SequentialIndices } from \"../Indices/SequentialIndices.mjs\";\r\n// import { default as AttributeBuffer } from \"./AbstractAttributeBuffer.mjs\";\r\n// import { default as addLineNumbers } from \"./util/addLineNumbers.mjs\";\r\nimport { default as prettifyGlslError } from \"../util/prettifyGlslError.mjs\";\r\nimport { parseGlslVaryingType, parseGlslUniformType } from \"../util/parseGlslType.mjs\";\r\n\r\n/**\r\n * @class WebGL1Program\r\n *\r\n * Represents a draw call using only WebGL1 APIs.\r\n *\r\n * A `WebGL1Program` compiles the shader strings and binding textures,\r\n * indices, attributes and the framebuffer together (even though most\r\n * functionality is delegated).\r\n *\r\n * @relationship compositionOf SequentialIndices, 1..1, 0..n\r\n * @relationship compositionOf BindableAttribute, 0..n, 0..n\r\n * @relationship compositionOf Texture, 0..n, 0..n\r\n * @relationship compositionOf FrameBuffer, 0..1, 0..n\r\n */\r\n\r\nexport default class WebGL1Program {\r\n\t// TODO: alias \"vertexShaderSource\" to \"vert\"?\r\n\t// TODO: alias \"fragmentShaderSource\" to \"frag\"?\r\n\t// TODO: alias \"indexBuffer\" to \"indices\"?\r\n\t// TODO: alias \"attributeBuffers\" to \"attributes\" to \"attrs\"?\r\n\r\n\t// TODO: Stuff from REGL https://regl.party/api :\r\n\t// * Stencil\r\n\t// * Polygon offset\r\n\t// * Culling - IndexBuffer ?\r\n\t// * Front face - IndexBuffer ?\r\n\t// * Dithering\r\n\t// * Line width - IndexBuffer ?\r\n\t// * Color mask\r\n\t// * Sample coverage\r\n\t// * Scissor\r\n\t// * Viewport\r\n\r\n\tconstructor(\r\n\t\tgl,\r\n\t\tgliiFactory,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @section\r\n\t\t\t * @aka WebGL1Program options\r\n\t\t\t * @option vertexShaderSource: String; GLSL v1.00 source code for the vertex shader\r\n\t\t\t */\r\n\t\t\tvertexShaderSource,\r\n\t\t\t/**\r\n\t\t\t * @option varyings: [Object]; A key-value map of varying names and their GLSL v1.00 types\r\n\t\t\t */\r\n\t\t\tvaryings = {},\r\n\t\t\t/**\r\n\t\t\t * @option fragmentShaderSource: String; GLSL v1.00 source code for the fragment shader\r\n\t\t\t */\r\n\t\t\tfragmentShaderSource,\r\n\t\t\t/**\r\n\t\t\t * @option indexBuffer: IndexBuffer\r\n\t\t\t * The `IndexBuffer` containing which vertices to draw.\r\n\t\t\t */\r\n\t\t\tindexBuffer,\r\n\t\t\t/**\r\n\t\t\t * @option attributes: Object = {}; A key-value map of attribute names and their `BindableAttribute`\r\n\t\t\t */\r\n\t\t\tattributes = {},\r\n\t\t\t/**\r\n\t\t\t * @option uniforms: Object = {}; A key-value map of uniform names and their GLSL v1.00 types\r\n\t\t\t */\r\n\t\t\tuniforms = {},\r\n\t\t\t/**\r\n\t\t\t * @option textures: Object = {}; A key-value map of texture names and their `Texture` counterpart\r\n\t\t\t */\r\n\t\t\ttextures = {},\r\n\t\t\t/**\r\n\t\t\t * @option target: FrameBuffer = null\r\n\t\t\t * When `target` is null or not specified, the program\r\n\t\t\t * will draw to the default framebuffer (the one attached to the `` being used).\t\t * @alternative\r\n\t\t\t * @option target: FrameBuffer = null; The `FrameBuffer` to draw to.\r\n\t\t\t */\r\n\t\t\ttarget = null,\r\n\t\t\t/**\r\n\t\t\t * @option depth: Comparison constant = glii.ALWAYS\r\n\t\t\t * Initial value for the `depth` property.\r\n\t\t\t * @property depth: Comparison constant = glii.ALWAYS\r\n\t\t\t * Whether this program performs depth testing, and how. Can be changed during runtime.\r\n\t\t\t *\r\n\t\t\t * `gl.ALWAYS` is the same as disabling depth testing.\r\n\t\t\t *\r\n\t\t\t * Has no effect if the `FrameBuffer` for this program has no depth attachment.\r\n\t\t\t */\r\n\t\t\tdepth = 0x0207, /// 0x207 = gl.ALWAYS\r\n\t\t\t/**\r\n\t\t\t * @option blend: Boolean = false\r\n\t\t\t * Disables fragment blending\r\n\t\t\t * @alternative\r\n\t\t\t * @option blend: BlendDefinition\r\n\t\t\t * Enables fragment blending, with the provided configuration.\r\n\t\t\t */\r\n\t\t\tblend = false,\r\n\r\n\t\t\t/**\r\n\t\t\t * @option unusedWarning: Boolean = true\r\n\t\t\t * Whether to display warnings in the browser's console about\r\n\t\t\t * unused attributes, unused textures and unused uniforms.\r\n\t\t\t */\r\n\t\t\tunusedWarning = true,\r\n\t\t}\r\n\t) {\r\n\t\tthis._gl = gl;\r\n\r\n\t\t// The factory that spawned this program is important for fetching the\r\n\t\t// size of the default framebuffer, in order to prevent blinking when a\r\n\t\t// is resized.\r\n\t\tthis._gliiFactory = gliiFactory;\r\n\r\n\t\t// Loop through attribute buffers to fetch defined attribute names and their types\r\n\t\t// to build up a header for the fragment shader.\r\n\t\tlet attribDefs = \"\";\r\n\r\n\t\tfor (let attribName in attributes) {\r\n\t\t\tconst type = attributes[attribName].getGlslType();\r\n\t\t\tattribDefs += `attribute ${type} ${attribName};\\n`;\r\n\t\t}\r\n\r\n\t\t// Loop through uniform and texture definitions to get their names and types\r\n\t\t// to build up a header common for both the vertex and the fragment shader\r\n\t\tlet uniformDefs = \"\";\r\n\t\tthis._unifSetters = {};\r\n\t\tfor (let uName in uniforms) {\r\n\t\t\tconst uniformType = uniforms[uName];\r\n\t\t\tconst [_, glslType] = parseGlslUniformType(uniformType);\r\n\t\t\tswitch (glslType) {\r\n\t\t\t\tcase \"float\":\r\n\t\t\t\t\tthis._unifSetters[uName] = gl.uniform1f.bind(gl);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"vec2\":\r\n\t\t\t\t\tthis._unifSetters[uName] = gl.uniform2fv.bind(gl);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"vec3\":\r\n\t\t\t\t\tthis._unifSetters[uName] = gl.uniform3fv.bind(gl);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"vec4\":\r\n\t\t\t\t\tthis._unifSetters[uName] = gl.uniform4fv.bind(gl);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"int\":\r\n\t\t\t\tcase \"bool\":\r\n\t\t\t\t\tthis._unifSetters[uName] = gl.uniform1i.bind(gl);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"ivec2\":\r\n\t\t\t\tcase \"bvec2\":\r\n\t\t\t\t\tthis._unifSetters[uName] = gl.uniform2iv.bind(gl);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"ivec3\":\r\n\t\t\t\tcase \"bvec3\":\r\n\t\t\t\t\tthis._unifSetters[uName] = gl.uniform3iv.bind(gl);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"ivec4\":\r\n\t\t\t\tcase \"bvec4\":\r\n\t\t\t\t\tthis._unifSetters[uName] = gl.uniform4iv.bind(gl);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"mat2\":\r\n\t\t\t\t\tthis._unifSetters[uName] = (p, v) => gl.uniformMatrix2fv(p, false, v);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"mat3\":\r\n\t\t\t\t\tthis._unifSetters[uName] = (p, v) => gl.uniformMatrix3fv(p, false, v);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"mat4\":\r\n\t\t\t\t\tthis._unifSetters[uName] = (p, v) => gl.uniformMatrix4fv(p, false, v);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tdefault:\r\n\t\t\t\t\tthrow new Error(`Unknown uniform GLSL type \"${uniformType}\"`);\r\n\t\t\t}\r\n\r\n\t\t\tuniformDefs += `uniform ${uniformType} ${uName};\\n`;\r\n\t\t}\r\n\t\tfor (let texName in textures) {\r\n\t\t\tuniformDefs += \"uniform sampler2D \" + texName + \";\\n\";\r\n\t\t}\r\n\r\n\t\tlet varyingDefs = Object.entries(varyings)\r\n\t\t\t.map(([n, t]) => {\r\n\t\t\t\tparseGlslVaryingType(t);\r\n\t\t\t\treturn `varying ${t} ${n};\\n`;\r\n\t\t\t})\r\n\t\t\t.join(\"\");\r\n\r\n\t\t/// TODO: allow the dev to change this\r\n\t\tconst precisionHeader = \"precision highp float;\\n\";\r\n\r\n\t\tconst program = (this._program = gl.createProgram());\r\n\t\tconst vertexShader = this._compileShader(\r\n\t\t\tgl.VERTEX_SHADER,\r\n\t\t\t// \"#version 100\\n\" +\r\n\t\t\tprecisionHeader + attribDefs + varyingDefs + uniformDefs,\r\n\t\t\tvertexShaderSource\r\n\t\t);\r\n\t\tconst fragmtShader = this._compileShader(\r\n\t\t\tgl.FRAGMENT_SHADER,\r\n\t\t\t// \"#version 100\\n\" +\r\n\t\t\tprecisionHeader + varyingDefs + uniformDefs,\r\n\t\t\tfragmentShaderSource\r\n\t\t);\r\n\t\tgl.linkProgram(program);\r\n\t\tvar success = gl.getProgramParameter(program, gl.LINK_STATUS);\r\n\t\tif (!success) {\r\n\t\t\tconsole.warn(gl.getProgramInfoLog(program));\r\n\t\t\tgl.deleteProgram(program);\r\n\t\t\tthrow new Error(\"Could not compile shaders into a WebGL1 program\");\r\n\t\t}\r\n\r\n\t\t// According to a note in\r\n\t\t// https://webglfundamentals.org/webgl/lessons/resources/webgl-state-diagram.html ,\r\n\t\t// it is safe to detach and delete shaders once the program is linked.\r\n\t\tgl.detachShader(program, vertexShader);\r\n\t\tgl.deleteShader(vertexShader);\r\n\t\tgl.detachShader(program, fragmtShader);\r\n\t\tgl.deleteShader(fragmtShader);\r\n\r\n\t\tif (!(indexBuffer instanceof SequentialIndices)) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"The WebGL1Program constructor needs a valid `IndexBuffer` to be passed as an option.\"\r\n\t\t\t);\r\n\t\t}\r\n\t\tthis._indexBuff = indexBuffer;\r\n\r\n\t\tthis._attrs = attributes;\r\n\t\tthis._attribsMap = {};\r\n\r\n\t\tfor (let attribName in attributes) {\r\n\t\t\tconst loc = gl.getAttribLocation(this._program, attribName);\r\n\t\t\tif (loc === -1) {\r\n\t\t\t\tif (unusedWarning)\r\n\t\t\t\t\tconsole.warn(\r\n\t\t\t\t\t\t`Attribute \"${attribName}\" is not used in the shaders and will be ignored`\r\n\t\t\t\t\t);\r\n\t\t\t\tdelete this._attrs[attribName];\r\n\t\t\t} else {\r\n\t\t\t\tthis._attribsMap[attribName] = loc;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis._unifsMap = {};\r\n\t\tthis._texs = textures;\r\n\t\tthis._unifs = uniforms;\r\n\t\tfor (let unifName in uniforms) {\r\n\t\t\tconst loc = gl.getUniformLocation(this._program, unifName);\r\n\t\t\tif (unusedWarning && loc === -1) {\r\n\t\t\t\tconsole.warn(`Uniform \"${unifName}\" is not being used in the shaders.`);\r\n\t\t\t}\r\n\t\t\tthis._unifsMap[unifName] = loc;\r\n\t\t}\r\n\t\tfor (let texName in textures) {\r\n\t\t\tif (texName in this._unifsMap) {\r\n\t\t\t\tthrow new Error(\r\n\t\t\t\t\t`Texture name \"${texName}\" conflicts with already defined (non-texture) uniform.`\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t\tconst loc = gl.getUniformLocation(this._program, texName);\r\n\t\t\tif (unusedWarning && loc === -1) {\r\n\t\t\t\tconsole.warn(`Texture \"${texName}\" is not being used in the shaders.`);\r\n\t\t\t}\r\n\t\t\tthis._unifsMap[texName] = loc;\r\n\t\t}\r\n\r\n\t\tthis._target = target;\r\n\t\tthis.depth = depth;\r\n\t\tthis.blend = blend;\r\n\t\t// console.log(\"attrib map: \", this._attribsMap);\r\n\t\t// console.log(\"unifs map: \", this._unifsMap);\r\n\t}\r\n\r\n\t_compileShader(type, header, src) {\r\n\t\tconst gl = this._gl;\r\n\t\t/// FIXME: Running on puppeteer, `shader` is not a `WebGLShader` instance,\r\n\t\t/// which throws an error when trying to set the source.\r\n\t\t// See also: https://github.com/mapbox/mapbox-gl-js/pull/9017\r\n\t\tconst shader = gl.createShader(type);\r\n\t\t// \t\ttry {\r\n\t\tgl.shaderSource(shader, \"#line 1\\n\" + header + \"#line 10001\\n\" + src);\r\n\t\t// \t\t} catch(ex) {\r\n\t\t// \t\t\tconsole.warn(\"Context lost?\");\r\n\t\t// \t\t\tconsole.log(shader);\r\n\t\t// \t\t\tdebugger;\r\n\t\t// \t\t}\r\n\t\tgl.compileShader(shader);\r\n\t\tconst success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);\r\n\t\tif (success) {\r\n\t\t\t// console.warn(addLineNumbers(gl.getShaderSource(shader)));\r\n\t\t\tgl.attachShader(this._program, shader);\r\n\t\t\treturn shader;\r\n\t\t}\r\n\r\n\t\tconst log = gl.getShaderInfoLog(shader);\r\n\t\tgl.deleteShader(shader);\r\n\r\n\t\t// Try to throw a pretty error message\r\n\t\tconst readableType = type === gl.VERTEX_SHADER ? \"vertex\" : \"fragment\";\r\n\t\tprettifyGlslError(log, header, src, readableType, 10000);\r\n\r\n\t\t// console.warn(gl.getSupportedExtensions());\r\n\t\t// console.warn(addLineNumbers(gl.getShaderSource(shader)));\r\n\t\t// console.warn(gl.getShaderInfoLog(shader));\r\n\t\tthrow new Error(\"Could not compile shader.\");\r\n\t}\r\n\r\n\t_preRun() {\r\n\t\tconst gl = this._gl;\r\n\r\n\t\t// TODO: Double- and triple-check that viewport and clear are needed at this stage\r\n\t\t// TODO: handle explicit viewports.\r\n\t\t// TODO: Allow the dev to override the following defaults:\r\n\t\tif (!this._target) {\r\n\t\t\tgl.bindFramebuffer(gl.FRAMEBUFFER, null);\r\n\t\t\t// const [width, height] = this._gliiFactory.getDrawingBufferSize();\r\n\t\t\t// gl.viewport(0, 0, width, height);\r\n\t\t\tthis._gliiFactory.refreshDrawingBufferSize();\r\n\t\t\tgl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);\r\n\t\t} else {\r\n\t\t\tgl.bindFramebuffer(gl.FRAMEBUFFER, this._target.fb);\r\n\t\t\tgl.viewport(0, 0, this._target.width, this._target.height);\r\n\t\t}\r\n\r\n\t\tif (this.blend) {\r\n\t\t\tgl.enable(gl.BLEND);\r\n\t\t\tgl.blendEquationSeparate(this.blend.equationRGB, this.blend.equationAlpha);\r\n\t\t\tgl.blendFuncSeparate(\r\n\t\t\t\tthis.blend.srcRGB,\r\n\t\t\t\tthis.blend.dstRGB,\r\n\t\t\t\tthis.blend.srcAlpha,\r\n\t\t\t\tthis.blend.dstAlpha\r\n\t\t\t);\r\n\t\t\tif (this.blend.colour) {\r\n\t\t\t\tgl.blendColor(\r\n\t\t\t\t\tthis.blend.colour[0],\r\n\t\t\t\t\tthis.blend.colour[1],\r\n\t\t\t\t\tthis.blend.colour[2],\r\n\t\t\t\t\tthis.blend.colour[3]\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tgl.disable(gl.BLEND);\r\n\t\t}\r\n\r\n\t\t/// TODO: Consider caching the loaded program somehow. Maybe copy\r\n\t\t/// the technique from the Texture's WeakMap?\r\n\t\tgl.useProgram(this._program);\r\n\r\n\t\tif (this.depth === gl.ALWAYS) {\r\n\t\t\tgl.disable(gl.DEPTH_TEST);\r\n\t\t} else {\r\n\t\t\tgl.enable(gl.DEPTH_TEST);\r\n\t\t\tgl.depthFunc(this.depth);\r\n\t\t}\r\n\r\n\t\tfor (let attribName in this._attrs) {\r\n\t\t\tconst location = this._attribsMap[attribName];\r\n\t\t\tthis._attrs[attribName].bindWebGL1(location);\r\n\t\t}\r\n\r\n\t\tfor (let texName in this._texs) {\r\n\t\t\tif (this._texs[texName]) {\r\n\t\t\t\tgl.uniform1i(this._unifsMap[texName], this._texs[texName].getUnit());\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @section Draw methods\r\n\t * @method run():this\r\n\t * Runs the draw call for this program\r\n\t * @alternative\r\n\t * @method run(lod: Number): this\r\n\t * If the program's index buffer is a `LodIndices`, then this runs the\r\n\t * draw call for this program, but only for the primitives in the given LoD.\r\n\t * @alternative\r\n\t * @method run(lod: String): this\r\n\t * Idem, but for `String` LoD identifiers.\r\n\t */\r\n\trun(lod) {\r\n\t\tthis._preRun();\r\n\t\tthis._indexBuff.drawMe(lod);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method runPartial(start: Number, count: Number):this\r\n\t * Runs the draw call for this program, but explicitly only for\r\n\t * the slots given as parameter (instead of using the information\r\n\t * of slots in use from the `IndexBuffer` of this program).\r\n\t *\r\n\t * Beware that `runPartial` does not perform any validity checks\r\n\t * on the given range. This should only be used when the programmer\r\n\t * is really really sure of what vertex slots to draw.\r\n\t */\r\n\trunPartial(start, count) {\r\n\t\tthis._preRun();\r\n\t\tthis._indexBuff.drawMePartial(start, count);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Mutation methods\r\n\t *\r\n\t * The following methods allow changing some of the components of a program\r\n\t * during runtime.\r\n\t *\r\n\t * @method setUniform(name: String, value: Number): this\r\n\t * (Re-)sets the value of a uniform in this program, for `float`/`int` uniforms.\r\n\t * @alternative\r\n\t * @method setUniform(name: String, value: [Number]): this\r\n\t * (Re-)sets the value of a uniform in this program, for `vecN`/`ivecN`/`matN` uniforms.\r\n\t */\r\n\tsetUniform(name, value) {\r\n\t\t// TODO: mark self as dirty\r\n\t\tthis._gl.useProgram(this._program);\r\n\r\n\t\tif (name in this._unifSetters) {\r\n\t\t\tthis._unifSetters[name](this._unifsMap[name], value);\r\n\t\t\treturn this;\r\n\t\t} else {\r\n\t\t\tthrow new Error(`Uniform name ${name} is unknown in this WebGL1Program.`);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method getUniform(name: String): *\r\n\t * Returns the value of the uniform with the given name. Return value will\r\n\t * be a `Number` or a `TypedArray` depending on the uniform's type.\r\n\t */\r\n\tgetUniform(name) {\r\n\t\tconst location = this._unifsMap[name];\r\n\t\tif (location) {\r\n\t\t\treturn this._gl.getUniform(this._program, location);\r\n\t\t} else {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t`Uniform name ${name} is unknown or unused in this WebGL1Program.`\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method setTexture(name: String, texture: Texture): this\r\n\t * (Re-)sets the value of a texture in this program.\r\n\t */\r\n\tsetTexture(name, texture) {\r\n\t\tthis._texs[name] = texture;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method setIndexBuffer(buf: IndexBuffer): this\r\n\t * Changes the index buffer that this program uses.\r\n\t */\r\n\tsetIndexBuffer(buf) {\r\n\t\tthis._indexBuff = buf;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method setAttribute(name: Stringattr: BindableAttribute): this\r\n\t * (Re-)sets one of the named attributes to a new `BindableAttribute`.\r\n\t *\r\n\t * The GLSL type of the new attribute must match the old one.\r\n\t */\r\n\tsetAttribute(name, attr) {\r\n\t\tif (this._attrs[name].getGlslType() !== attr.getGlslType()) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t`Bindable attribute named ${name} expected to be of type ${this._attrs[\r\n\t\t\t\t\tname\r\n\t\t\t\t].getGlslType()}, but instead got ${attr.getGlslType()}`\r\n\t\t\t);\r\n\t\t}\r\n\t\tthis._attrs[name] = attr;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method setTarget(target: FrameBuffer): this\r\n\t * Sets the `FrameBuffer` that this program should draw into.\r\n\t * @alternative\r\n\t * @method setTarget(target: null): this\r\n\t * Setting the draw target to `null` (or a falsy value) will make the program\r\n\t * draw to the default framebuffer (the one attached to the `` used\r\n\t * to spawn the Glii instance).\r\n\t */\r\n\tsetTarget(target) {\r\n\t\tthis._target = target;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Lifetime methods\r\n\t *\r\n\t * @method destroy(): this\r\n\t * Tells WebGL to free resources associated with this `WebGL1Program`. Use\r\n\t * when the `WebGL1Program` won't be used anymore.\r\n\t */\r\n\tdestroy() {\r\n\t\tthis._gl.deleteProgram(this._program);\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @method debugDumpAttributes(): Array of Object of TypedArray\r\n\t * Returns a readable representation of the current attribute values. This is\r\n\t * only possible when all attribute storages are growable (i.e. those defined with\r\n\t * a `growFactor` greater than zero).\r\n\t *\r\n\t * This is a costly operation, and should be only used for manual debugging purposes.\t */\r\n\tdebugDumpAttributes(start, length) {\r\n\r\n\t\tconst attrValues = {};\r\n\r\n\t\tfor (const [name, bound] of Object.entries(this._attrs)) {\r\n\t\t\tattrValues[name] = bound.debugDump();\r\n\t\t}\r\n\t\t// Reorganize data, so it's ordered by vertex index first,\r\n\t\t// attribute name second.\r\n\r\n\t\tlet maxLength = 0;\r\n\t\tfor (let values of Object.values(attrValues)) {\r\n\t\t\tmaxLength = Math.max(maxLength, values.length);\r\n\t\t}\r\n\r\n\t\tstart ??= 0;\r\n\t\tlength ??= maxLength;\r\n\t\tconst end = start +length;\r\n\r\n\t\tconst result = new Array(maxLength);\r\n\r\n\t\tfor (let i=start; i p.run(lod));\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method runPartial(start: Number, count: Number):this\r\n\t * Runs a partial draw call for all the bundled programs.\r\n\t *\r\n\t * This should be used only when all the bundled programs share the same `IndexBuffer`.\r\n\t */\r\n\trunPartial(start, count) {\r\n\t\tthis._programs.forEach((p) => p.runPartial(start, count));\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Mutation methods\r\n\t * The following methods allow changing some of the components (uniforms/textures/bindable\r\n\t * attributes) of the bundled programs during runtime. These change the components of all\r\n\t * bundled programs, but will fail silently *if* a program doesn't have a given component.\r\n\t *\r\n\t * Note that `setTarget` is missing from the set of methods - there is no (known) use\r\n\t * case where that would be useful, since `run()`ning all bundled programs at the same time\r\n\t * would mean overwriting their outputs, defeating the purpose.\r\n\t *\r\n\t * @method setUniform(name: String, value: Number): this\r\n\t * (Re-)sets the value of a uniform in the bundled programs, for `float`/`int` uniforms.\r\n\t * @alternative\r\n\t * @method setUniform(name: String, value: [Number]): this\r\n\t * (Re-)sets the value of a uniform in the bundled programs, for `vecN`/`ivecN`/`matN` uniforms.\r\n\t */\r\n\tsetUniform(name, value) {\r\n\t\tthis._programs.forEach((p) => {\r\n\t\t\tif (name in p._unifSetters) {\r\n\t\t\t\tp.setUniform(name, value);\r\n\t\t\t}\r\n\t\t});\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method setTexture(name: String, texture: Texture): this\r\n\t * (Re-)sets the value of a texture in the bundled programs.\r\n\t */\r\n\tsetTexture(name, texture) {\r\n\t\tthis._programs.forEach((p) => {\r\n\t\t\tif (name in p._texs) {\r\n\t\t\t\tp.setTexture(name, texture);\r\n\t\t\t}\r\n\t\t});\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method setIndexBuffer(buf: IndexBuffer): this\r\n\t * Changes the index buffer that the bundled programs use.\r\n\t */\r\n\tsetIndexBuffer(buf) {\r\n\t\tthis._programs.forEach((p) => p.setIndexBuffer(buf));\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method setAttribute(name: Stringattr: BindableAttribute): this\r\n\t * (Re-)sets one of the named attributes to a new `BindableAttribute`.\r\n\t *\r\n\t * The GLSL type of the new attribute must match the old one.\r\n\t */\r\n\tsetAttribute(name, attr) {\r\n\t\tthis._programs.forEach((p) => {\r\n\t\t\tif (name in p._attrs) {\r\n\t\t\t\tp.setAttribute(name, attr);\r\n\t\t\t}\r\n\t\t});\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Lifetime methods\r\n\t *\r\n\t * @method destroy(): this\r\n\t * Tells WebGL to free resources associated with **all** the programs\r\n\t * in this `MultiProgram`. Use when **none** of the programs\r\n\t * will be used anymore.\r\n\t */\r\n\tdestroy() {\r\n\t\tthis._programs.forEach((p) => p.destroy());\r\n\t}\r\n}\r\n\r\n/**\r\n * @factory GliiFactory.MultiProgram(programs: [WebGL1Program])\r\n * @class Glii\r\n * @section Class wrappers\r\n * @property MultiProgram(programs: [WebGL1Program]): Prototype of MultiProgram\r\n * Wrapped `MultiProgram` class\r\n */\r\nregisterFactory(\"MultiProgram\", function (gl, gliiFactory) {\r\n\treturn class WrappedMultiProgram extends MultiProgram {\r\n\t\tconstructor(opts) {\r\n\t\t\tsuper(gl, gliiFactory, opts);\r\n\t\t}\r\n\t};\r\n});\r\n","import { registerFactory } from \"./GliiFactory.mjs\";\r\n\r\n/**\r\n * @class WebGL1Clear\r\n * @relationship compositionOf FrameBuffer, 0..1, 0..n\r\n *\r\n * Represents a clear call using only WebGL1 APIs.\r\n *\r\n * Optionally specify a target `FrameBuffer` and values to clear the depth and\r\n * stencil parts of said framebuffer. Specify `false` values to *not* clear\r\n * colour, depth or stencil parts of a framebuffer.\r\n *\r\n * A `WebGL1Clear` operation is not needed when the `target` is `null` and the\r\n * `WebGLRenderingContext` has been instantiated with `preserveDrawingBuffer`\r\n * set to `false` (the default); in this case, an implicit clear operation is\r\n * performed prior to every draw call (i.e. every time a `WebGL1Program` `run()`s).\r\n */\r\n\r\nexport default class WebGL1Clear {\r\n\tconstructor(\r\n\t\tgl,\r\n\t\t{\r\n\t\t\t// @section\r\n\t\t\t// @aka WebGL1Clear options\r\n\t\t\t// @option color: Array of Number = [0.5, 0.5, 0.5, 0.0]; RGBA colour to clear with.\r\n\t\t\t// @alternative\r\n\t\t\t// @option color: false; When `false`, the colour part of the framebuffer is not cleared.\r\n\t\t\tcolor = [0.5, 0.5, 0.5, 0.0],\r\n\t\t\t// @option depth: Number = 1; The value to clear the depth part of the framebuffer with.\r\n\t\t\t// @alternative\r\n\t\t\t// @option depth: false; When `false`, the depth part of the framebuffer is not cleared.\r\n\t\t\tdepth = 1,\r\n\t\t\t// @option stencil: Number = 0.5; The value to clear the stencil part of the framebuffer with.\r\n\t\t\t// @alternative\r\n\t\t\t// @option stencil: false; When `false`, the stencil part of the framebuffer is not cleared.\r\n\t\t\tstencil = 0,\r\n\t\t\t// @option target: null; When `target` is null or not specified) the default\r\n\t\t\t// framebuffer is cleared (the one attached to the `` being used).\r\n\t\t\t// @alternative\r\n\t\t\t// @option target: FrameBuffer; The `FrameBuffer` to clear.\r\n\t\t\ttarget,\r\n\t\t}\r\n\t) {\r\n\t\t/// TODO: This needs a draw target - which canvas/RenderBuffer to use??!!\r\n\t\t/// i.e. make a call like gl.bindFrameBuffer(gl.FRAMEBUFFER, this._renderBuffer)\r\n\t\tthis._gl = gl;\r\n\t\tthis.color = color;\r\n\t\tthis.depth = depth;\r\n\t\tthis.stencil = stencil;\r\n\t\tthis.target = target || null;\r\n\t}\r\n\r\n\t/**\r\n\t * @method run(): this\r\n\t * Runs the clear call\r\n\t */\r\n\trun() {\r\n\t\tconst gl = this._gl;\r\n\r\n\t\tif (!this.target) {\r\n\t\t\tgl.bindFramebuffer(gl.FRAMEBUFFER, null);\r\n\t\t\tgl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);\r\n\t\t} else {\r\n\t\t\tgl.bindFramebuffer(gl.FRAMEBUFFER, this.target.fb);\r\n\t\t\tgl.viewport(0, 0, this.target.width, this.target.height);\r\n\t\t}\r\n\r\n\t\tlet bitmask = 0;\r\n\t\tif (this.color !== false) {\r\n\t\t\tgl.clearColor(...this.color);\r\n\t\t\tbitmask |= gl.COLOR_BUFFER_BIT;\r\n\t\t}\r\n\t\tif (this.depth !== false) {\r\n\t\t\tgl.clearDepth(this.depth);\r\n\t\t\tbitmask |= gl.DEPTH_BUFFER_BIT;\r\n\t\t}\r\n\t\tif (this.stencil !== false) {\r\n\t\t\tgl.clearStencil(this.stencil);\r\n\t\t\tbitmask |= gl.STENCIL_BUFFER_BIT;\r\n\t\t}\r\n\t\tgl.clear(bitmask);\r\n\t\treturn this;\r\n\t}\r\n}\r\n\r\n/**\r\n * @factory GliiFactory.WebGL1Clear(options: WebGL1Clear options)\r\n * @class Glii\r\n * @section Class wrappers\r\n * @property WebGL1Clear(options: WebGL1Clear options): Prototype of WebGL1Clear\r\n * Wrapped `WebGL1Clear` class\r\n */\r\nregisterFactory(\"WebGL1Clear\", function (gl) {\r\n\treturn class WrappedWebGL1Clear extends WebGL1Clear {\r\n\t\tconstructor(opts) {\r\n\t\t\tsuper(gl, opts);\r\n\t\t}\r\n\t};\r\n});\r\n","/**\r\n * @namespace knownCRSs\r\n * @relationship aggregationOf BaseCRS, 1..1, 0..n\r\n *\r\n * Holds a list of `BaseCRS`s globally known to Gleo, keyed by their OGC URI.\r\n */\r\n\r\nconst knownCRSs = new Map();\r\n\r\n// This is a super-simplistic CURIE parser that doesn't take into account\r\n// the full spec (the full list of valid characters), instead parses only\r\n// the brackets, double colon and non-whitespace. See https://www.w3.org/TR/curie/ .\r\nconst curieRegexp = /^\\[(\\S+):(\\S+)\\]$/;\r\n\r\n/**\r\n * @function getCRS(ogcUri: String): BaseCRS\r\n * Returns the known `BaseCRS` with the given OGC URI. Accepts the URI in CURIE\r\n * form (e.g. `[EPSG:4326]`) as well. Throws an error if not found.\r\n * @alternative\r\n * @function getCRS(name: String): BaseCRS\r\n * Returns the known `BaseCRS` with the given internal name (e.g. \"EPSG:4326\",\r\n * \"cartesian\"). Throws an error if not found.\r\n */\r\nexport function getCRS(ogcUri) {\r\n\tconst match = curieRegexp.exec(ogcUri);\r\n\tlet uri = ogcUri;\r\n\tif (match) {\r\n\t\tconst [_, authority, code] = match;\r\n\t\turi = `http://www.opengis.net/def/crs/${authority}/0/${code}`;\r\n\t}\r\n\tconst crs = knownCRSs.get(uri);\r\n\tif (!crs) {\r\n\t\tthrow new Error(\r\n\t\t\t\"There is no known Gleo CRS for the given name/OGC URI: \" + ogcUri\r\n\t\t);\r\n\t}\r\n\treturn crs;\r\n}\r\n\r\n/**\r\n * @function registerCRS(crs: BaseCRS, overrideUri: undefined): undefined\r\n * Registers the given CRS. Meant for internal use only. Trying to register\r\n * a CRS twice (or two CRSs with the same OGC URI) will throw an error.\r\n * @alternative\r\n * @function registerCRS(crs: BaseCRS, overrideUri: String): undefined\r\n * Registers the given CRS, but with an alternate OGC URI. Meant for internal\r\n * use only, for pointing both `EPSG:4326` and `OGC:WGS84` to the same object.\r\n */\r\nexport function registerCRS(crs, overrideUri) {\r\n\tconst uri = overrideUri ?? crs.ogcUri;\r\n\tif (uri) {\r\n\t\tif (knownCRSs.has(uri)) {\r\n\t\t\tthrow new Error(\"CRS has been defined twice for the given OGC URL: \" + uri);\r\n\t\t}\r\n\t\tknownCRSs.set(uri, crs);\r\n\t}\r\n\tknownCRSs.set(crs.name, crs);\r\n}\r\n","/**\r\n * @namespace projector\r\n *\r\n * The `projector` is the piece of code in charge of transforming (\"reprojecting\")\r\n * coordinates (from either `Coord` or `CoordNest`) into a different `BaseCRS`.\r\n *\r\n * By default, Gleo only supports projecting from/to `EPSG:4326` and `EPSG:3857`.\r\n * The intended way to support any other projections is to inject the\r\n * `proj4`/`proj4js` dependency via `enableProj()`.\r\n *\r\n * `projector` works as a Singleton pattern, and cannot be instanced.\r\n *\r\n * @example\r\n * ```\r\n * import proj4 from 'proj';\r\n * import {enableProj, project} from 'gleo/src/crs/projector.mjs';\r\n *\r\n * enableProj(Proj4js);\r\n *\r\n * proj4.defs(\"EPSG:3995\",\"+proj=stere +lat_0=90 +lat_ts=71 +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs\");\r\n *\r\n * const epsg3995 = new BaseCRS(\"EPSG:3995\", Infinity, Infinity);\r\n *\r\n * map.crs = epsg3995;\r\n * ```\r\n */\r\n\r\nfunction gleoProject(sCRS, dCRS, xy) {\r\n\tif (sCRS === \"EPSG:4326\" && dCRS === \"EPSG:3857\") {\r\n\t\treturn lnglat2webmercator(xy);\r\n\t} else if (sCRS === \"EPSG:3857\" && dCRS === \"EPSG:4326\") {\r\n\t\treturn webmercator2lnglat(xy);\r\n\t} else {\r\n\t\tthrow new Error(`Unsupported coordinate reprojection ${sCRS}→${dCRS}`);\r\n\t}\r\n}\r\n\r\ngleoProject.defs = function noop() {};\r\n\r\n/**\r\n * @function project(sCRS: String, dCRS: String, xy: Array of Number): Array of Number\r\n * Projects the given `Array` of two `Number`s from the source CRS `sCRS` into\r\n * the destination `dCRS`, returning a new `Array` of two `Number`s.\r\n */\r\nexport let project = gleoProject;\r\n\r\n/**\r\n * @function registerProjectionFunction(sCRS: String, dCRS: String, fn: Function): undefined\r\n * Registers the given projection function, so it will be used whenever Gleo\r\n * needs to project coordinates from `sCRS` into `dCRS`.\r\n *\r\n */\r\nexport function registerProjectionFunction(sCRS, dCRS, fn) {\r\n\tconst prev = gleoProject;\r\n\tgleoProject = function gleoProject(s, d, xy) {\r\n\t\tif (s === sCRS && d === dCRS) {\r\n\t\t\treturn fn(xy);\r\n\t\t} else {\r\n\t\t\treturn prev(s, d, xy);\r\n\t\t}\r\n\t};\r\n\tif (project === prev) {\r\n\t\tproject = gleoProject;\r\n\t}\r\n}\r\n\r\n/**\r\n * @function enableProj(proj: Module): undefined\r\n * Expects a reference to the `proj4` (AKA `proj4js`) module. All further reprojections\r\n * (including those from `Coord.toCRS()`) shall be done via the specified module.\r\n * @alternative\r\n * @function enableProj(undefined: undefined): undefined\r\n * Disables usage of `proj4`/`proj4js`, and re-enables Gleo's built-in reprojection code.\r\n */\r\nexport function enableProj(proj) {\r\n\tif (proj) {\r\n\t\tproject = proj;\r\n\t} else {\r\n\t\tproject = gleoProject;\r\n\t}\r\n}\r\n\r\nconst R = 6378137; // Earth's radius as per spherical mercator\r\nconst D = Math.PI / 180; // One degree, in radians\r\nconst rad = 180 / Math.PI; // One radian, in degrees\r\nconst halfPi = Math.PI / 2;\r\n\r\nfunction lnglat2webmercator([lng, lat]) {\r\n\tconst sin = Math.sin(lat * D);\r\n\treturn [R * D * lng, (R * Math.log((1 + sin) / (1 - sin))) / 2];\r\n}\r\n\r\nfunction webmercator2lnglat([x, y]) {\r\n\treturn [(x * rad) / R, (2 * Math.atan(Math.exp(y / R)) - halfPi) * rad];\r\n}\r\n","import { getCRS, registerCRS } from \"./knownCRSs.mjs\";\r\nimport { project } from \"./projector.mjs\";\r\n\r\n/**\r\n * @class BaseCRS\r\n *\r\n * Represents a Coordinate Reference System.\r\n *\r\n * A `CRS` is identified by:\r\n * - Its well-known name (e.g. \"EPSG:4326\" or \"cartesian\")\r\n * - A wrapping delta vector (the `wrapPeriodX` and `wrapPeriodY` options)\r\n * - A way to calculate distance, either\r\n * - A distance function that takes two points in the CRS, *or*\r\n * - An instance of a different CRS that will perform the distance\r\n * calculations after reprojecting the points\r\n * - An bounding box (in the `[minX, minY, maxX, maxY]` form)\r\n * informative of the area in which the CRS makes sense; this is mostly to\r\n * prevent users from setting the platina's center too far away.\r\n * - An informative set of minimum and maximum span; this is mostly to\r\n * prevent users from zooming in/out foo far.\r\n *\r\n * Members of bounding boxes, as well as the minimum/maximum span, can have\r\n * values of `Infinity` or `-Infinity` (or `Number.POSITIVE_INFINITY`/`Number.NEGATIVE_INFINITY`).\r\n *\r\n * The wrapping delta vector is meant to draw `Acetate`s multiple times, offsetting\r\n * by that value. Also used to wrap coordinates when their X (or Y) component is larger\r\n * than *half* the X (or Y) component of the wrapping delta vector.\r\n *\r\n * It's important to note that the the wrapping vector does **not** mean that\r\n * coordinates wrap, but rather that the *display* of the coordinates wrap.\r\n * Thus, a point at a position `P` and another point at `P+wrap` are different,\r\n * but will be displayed at the same pixel. (Idem for `P+2*wrap`, `P-wrap` and\r\n * in general, for `P+n*wrap` for all natural numbers `n`).\r\n *\r\n * For geographical CRSs, it's highly recommended to use names that match\r\n * a Proj definition. Other functionality, such as the `ConformalWMS` loader,\r\n * depends on the CRS names.\r\n */\r\n\r\nexport default class BaseCRS {\r\n\t/**\r\n\t * @constructor BaseCRS(name: String, opts: BaseCRS Options)\r\n\t */\r\n\tconstructor(\r\n\t\tname,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @section\r\n\t\t\t * @aka BaseCRS Options\r\n\t\t\t * @uninheritable\r\n\t\t\t * @option wrapPeriodX: Number = Infinity\r\n\t\t\t * The horizontal length, in CRS units, of the display wrapping.\r\n\t\t\t * @option wrapPeriodY: Number = Infinity\r\n\t\t\t * The vertical length, in CRS units, of the display wrapping.\r\n\t\t\t *\r\n\t\t\t * @option distance: Function\r\n\t\t\t * A distance function, that should take two point `Geometry`s as arguments\r\n\t\t\t * and return the distance between them (when given non-point `Geometry`s,\r\n\t\t\t * it shall return the distance between the first coordinate pair of each\r\n\t\t\t * `Geometry`). The units of distance depend on the CRS. They **should**\r\n\t\t\t * be meters for geographical CRSs, and unitless for `cartesian`.\r\n\t\t\t * @alternative\r\n\t\t\t * @option distance: BaseCRS\r\n\t\t\t * Whenever it's not trivial or convenient to calculate distances,\r\n\t\t\t * calculations can be proxied to another CRS (which must have a `distance`\r\n\t\t\t * function defined). This is useful for geographical CRSs where a CRS\r\n\t\t\t * represents a geoid (e.g. the `EPSG:4326` CRS represents the `WGS84` geoid)\r\n\t\t\t * and all CRSs for that geoid use one common distance calculation.\r\n\t\t\t *\r\n\t\t\t * @option flipAxes: Boolean = false\r\n\t\t\t * Used **only** for OGC services (WMS, WFS, etc). The default `false` works\r\n\t\t\t * for CRSs with an X-Y axis order (or easting-northing, or longitude-latitude).\r\n\t\t\t *\r\n\t\t\t * This should be set to `true` whenever the CRS definition specifies that\r\n\t\t\t * the axes should be in Y-X order (or northing-easting, or latitude-longitude)\r\n\t\t\t * (e.g. EPSG:4326 and EPSG:3035).\r\n\t\t\t *\r\n\t\t\t * Gleo `Geometry`s always store data in X-Y (or easting-northing, or\r\n\t\t\t * lng-lat) order, regardless of this setting.\r\n\t\t\t *\r\n\t\t\t * @option ogcUri: String = \"\"\r\n\t\t\t * Used **only** for OGC API services (OGC API Tiles, etc). This\r\n\t\t\t * should be a URI string like `\"https://www.opengis.net/def/crs/EPSG/0/4326\"`\r\n\t\t\t * that will be used to match metadata.\r\n\t\t\t *\r\n\t\t\t * @option minSpan: Number = 0\r\n\t\t\t * The minimum span of a map/platina using the CRS, expressed in CRS units\r\n\t\t\t * (meters/degrees/etc) across the diagonal of a platina.\r\n\t\t\t *\r\n\t\t\t * This is an informative value that actuators use to prevent the user from\r\n\t\t\t * zooming in too far.\r\n\t\t\t *\r\n\t\t\t * @option maxSpan: Number = Infinity\r\n\t\t\t * The maximum span of a map/platina using the CRS, expressed in CRS units\r\n\t\t\t * (meters/degrees/etc) across the diagonal of a platina.\r\n\t\t\t *\r\n\t\t\t * This is an informative value that actuators use to prevent the user from\r\n\t\t\t * zooming out too far.\r\n\t\t\t *\r\n\t\t\t * @option viewableBounds: Array of Number = [-Infinity, -Infinity, Infinity, Infinity]\r\n\t\t\t * The *practical* viewable bounds of the CRS, as an array of the form\r\n\t\t\t * `[x1, y1, x2, y2]`.\r\n\t\t\t *\r\n\t\t\t * This is an informative value that actuators use to prevent the user\r\n\t\t\t * from moving away from areas where the CRS makes sense.\r\n\t\t\t *\r\n\t\t\t * Values are absolute, not relative to the CRS's offset (important\r\n\t\t\t * for instances of `offsetCRS`).\r\n\t\t\t */\r\n\t\t\twrapPeriodX = Infinity,\r\n\t\t\twrapPeriodY = Infinity,\r\n\t\t\tdistance,\r\n\t\t\tflipAxes = false,\r\n\t\t\togcUri = \"\",\r\n\t\t\tminSpan = 0,\r\n\t\t\tmaxSpan = Infinity,\r\n\t\t\tviewableBounds = [-Infinity, -Infinity, Infinity, Infinity],\r\n\t\t} = {}\r\n\t) {\r\n\t\tObject.defineProperty(this, \"name\", { value: name, writable: false });\r\n\t\tthis.wrapPeriodX = wrapPeriodX;\r\n\t\tthis.wrapPeriodY = wrapPeriodY;\r\n\t\tthis.halfPeriodX = wrapPeriodX / 2;\r\n\t\tthis.halfPeriodY = wrapPeriodY / 2;\r\n\r\n\t\tif (wrapPeriodX === Infinity && wrapPeriodY === Infinity) {\r\n\t\t\tthis.wrap = this._wrapNone;\r\n\t\t\tthis.wrapString = this._wrapNone;\r\n\t\t} else if (wrapPeriodX !== Infinity && wrapPeriodY === Infinity) {\r\n\t\t\tthis.wrap = this._wrapX;\r\n\t\t} else if (wrapPeriodX === Infinity && wrapPeriodY !== Infinity) {\r\n\t\t\tthis.wrap = this._wrapY;\r\n\t\t} else {\r\n\t\t\tthis.wrap = this._wrapXY;\r\n\t\t}\r\n\r\n\t\tif (distance instanceof Function) {\r\n\t\t\t// Ensure both parameters are in the desired CRS before going on\r\n\t\t\tthis.distance = function assertedDistance(a, b) {\r\n\t\t\t\treturn distance(a.toCRS(this), b.toCRS(this));\r\n\t\t\t}.bind(this);\r\n\t\t} else if (distance instanceof BaseCRS) {\r\n\t\t\tconst proxiedCRS = distance;\r\n\t\t\t// Proxy to the distance calculation of the given CRS\r\n\t\t\t// The CRS that this calculation is ultimately proxied to\r\n\t\t\t// will assert geometries are reprojected.\r\n\t\t\tthis.distance = function proxiedDistance(a, b) {\r\n\t\t\t\treturn proxiedCRS.distance(a, b);\r\n\t\t\t};\r\n\t\t} else {\r\n\t\t\tthis.distance = function noDistance() {\r\n\t\t\t\tthrow new Error(\"CRS cannot calculate distances\");\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\tthis.flipAxes = flipAxes;\r\n\t\tthis.minSpan = minSpan;\r\n\t\tthis.maxSpan = maxSpan;\r\n\t\tthis.viewableBounds = viewableBounds;\r\n\t\t// console.log(\"Instantiated CRS\", this.name, this.wrapPeriodX, this.wrapPeriodY);\r\n\t\tthis.ogcUri = ogcUri;\r\n\r\n\t\tif (ogcUri && this.constructor === BaseCRS) {\r\n\t\t\tregisterCRS(this);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method offsetToBase(xy: Array of Number): Array of Number\r\n\t * Identity function (all coordinates represented in a Base CRS are already\r\n\t * relative to the 0,0 origin of coordinates).\r\n\t */\r\n\toffsetToBase(xy) {\r\n\t\treturn xy;\r\n\t}\r\n\r\n\t/**\r\n\t * @method offsetFromBase(xy: Array of Number): Array of Number\r\n\t * Identity function (all coordinates represented in a Base CRS are already\r\n\t * relative to the 0,0 origin of coordinates).\r\n\t */\r\n\toffsetFromBase(xy) {\r\n\t\treturn xy;\r\n\t}\r\n\r\n\t/**\r\n\t * @method wrap(xy: Array of Number, ref: Array of Number): Array of Number\r\n\t * Wraps the given coordinate if it's further away from the reference `ref`\r\n\t * than half the wrap period. This guarantees that the return value is less than\r\n\t * half a period away from the reference.\r\n\t */\r\n\r\n\t_wrapNone(xy) {\r\n\t\treturn xy;\r\n\t}\r\n\r\n\t_wrapX([x, y], [refX, refY]) {\r\n\t\treturn [\r\n\t\t\tmodulo(x - refX + this.halfPeriodX, this.wrapPeriodX) +\r\n\t\t\t\trefX -\r\n\t\t\t\tthis.halfPeriodX,\r\n\t\t\ty,\r\n\t\t];\r\n\t}\r\n\r\n\t_wrapY([x, y], [refX, refY]) {\r\n\t\treturn [\r\n\t\t\tx,\r\n\t\t\tmodulo(y - refY + this.halfPeriodY, this.wrapPeriodY) +\r\n\t\t\t\trefY -\r\n\t\t\t\tthis.halfPeriodY,\r\n\t\t];\r\n\t}\r\n\r\n\t_wrapXY([x, y], [refX, refY]) {\r\n\t\treturn [\r\n\t\t\tmodulo(x - refX + this.halfPeriodX, this.wrapPeriodX) +\r\n\t\t\t\trefX -\r\n\t\t\t\tthis.halfPeriodX,\r\n\t\t\tmodulo(y - refY + this.halfPeriodY, this.wrapPeriodY) +\r\n\t\t\t\trefY -\r\n\t\t\t\tthis.halfPeriodY,\r\n\t\t];\r\n\t}\r\n\r\n\t/**\r\n\t * @method wrapString(xys: Array of Number): Array of Number\r\n\t * Given a linestring array of the form `[x1,y2, x2,y2, ... xn,yn],\r\n\t * runs `wrap()` on every `x,y` pair. This ensures that the first point of\r\n\t * the linestring is less than half a period away from the CRS' origin, and\r\n\t * idem with each pair of consecutive points.\r\n\t */\r\n\twrapString(xys) {\r\n\t\tconst l = xys.length / 2;\r\n\t\tconst dest = new Array(l);\r\n\t\tlet ref = this.offset || [0, 0];\r\n\t\tfor (let i = 0; i < l; i++) {\r\n\t\t\tconst j = i * 2;\r\n\t\t\tconst xy = xys.slice(j, j + 2);\r\n\t\t\tdest[i] = this.wrap(xy, ref);\r\n\t\t\tif (Number.isFinite(xy[0]) && Number.isFinite(xy[1])) {\r\n\t\t\t\tref = dest[i];\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn dest.flat();\r\n\t}\r\n\r\n\t/**\r\n\t * @function guessFromCode(crs: String): Promise to BaseCRS\r\n\t *\r\n\t * Factory method. Expects a string like `\"EPSG:12345\"`.\r\n\t *\r\n\t * Fetches information from https://crs-explorer.proj.org/ and\r\n\t * tries to build a Gleo CRS on a best-effort basis. Registers it via `proj4js`\r\n\t * as well, assuming `enableProj()` has been called.\r\n\t *\r\n\t * The resulting CRS might lack information such as wrap periods or min/max spans.\r\n\t */\r\n\tstatic async guessFromCode(code) {\r\n\t\ttry {\r\n\t\t\treturn getCRS(code);\r\n\t\t} catch (ex) {\r\n\t\t\tconst [_, org, number] = /(\\w+):(\\d+)/.exec(code);\r\n\r\n\t\t\tlet wkt;\r\n\t\t\ttry {\r\n\t\t\t\twkt = await (\r\n\t\t\t\t\tawait fetch(`https://crs-explorer.proj.org/wkt1/${org}/${number}.txt`)\r\n\t\t\t\t).text();\r\n\t\t\t} catch (ex) {\r\n\t\t\t\twkt = await (\r\n\t\t\t\t\tawait fetch(\r\n\t\t\t\t\t\t`https://spatialreference.org/ref/${org}/${number}/ogcwkt/`\r\n\t\t\t\t\t)\r\n\t\t\t\t).text();\r\n\t\t\t}\r\n\r\n\t\t\tproject.defs(code, wkt);\r\n\r\n\t\t\t// Assume that all EPSG CRSs are earth-based, and therefore can rely\r\n\t\t\t// on distance calculation via reprojection to EPSG:4326 and haversine\r\n\t\t\t// formula.\r\n\t\t\tconst distance =\r\n\t\t\t\torg === \"EPSG\"\r\n\t\t\t\t\t? (await import(\"./epsg4326.mjs\")).default.distance\r\n\t\t\t\t\t: undefined;\r\n\r\n\t\t\treturn new BaseCRS(code, { distance });\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction modulo(a, n) {\r\n\t// As per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder\r\n\t// return ((a % n ) + n ) % n;\r\n\r\n\treturn a === n ? a : ((a % n) + n) % n;\r\n}\r\n","import BaseCRS from \"./BaseCRS.mjs\";\r\n\r\n/**\r\n * @class OffsetCRS\r\n * @inherits BaseCRS\r\n *\r\n * Represents a Coordinate Reference System, with the same properties than a\r\n * `BaseCRS`, but with the `[0,0]` center of coordinates being offset.\r\n *\r\n * The rationale is the difference of precision between numbers.\r\n * A Gleo coordinate is a floating point number, which typically\r\n * will be represented by:\r\n * - 8 bytes / 64 bits (`Number`) when defined by the programmer or red by an API\r\n * - 4 bytes / 32 bits (`Float32Array`) when stored in a WebGL-friendly typed array\r\n * - 3 bytes / 24 bits (`highp float`) when running inside a GLSL 1.00 shader\r\n *\r\n * In order to keep the numerical precision, it's important to keep the numbers low\r\n * (so the mantissa has a precision of less than one screen pixel).\r\n *\r\n * The way to do so is to offset the coordinates. A system based on vector tiles\r\n * does this implicitly (coordinates inside a typical vector tile range from 0 to 4096,\r\n * and the CRS coordinate of each tile's corner is the implicit CRS offset).\r\n *\r\n * Gleo does this explicitly, translating coordinates to a `offsetCRS` which has a center\r\n * relatively near the screen center. In other words, when the user pans or zooms the map\r\n * far enough from the CRS' origin, then Gleo shall establish a new origin near\r\n * the updated user's viewport, and translate (AKA \"offset\") all `Geometry`s.\r\n * This doesn't lose (significant) precision since numbers are originally stored\r\n * in 64-bit floats (`Number`s).\r\n *\r\n */\r\n\r\nexport default class OffsetCRS extends BaseCRS {\r\n\t/**\r\n\t * @section\r\n\t * Build a new offset CRS, given a point `Geometry`. The CRS name, wrap\r\n\t * periods and other `BaseCRS Options` are taken from the `Geometry`'s CRS,\r\n\t * and the offset is the *absolute* value of the `Geometry`'s coordinates.\r\n\t * @constructor OffsetCRS(offset: Geometry)\r\n\t */\r\n\tconstructor(offset) {\r\n\t\tsuper(offset.crs.name, {\r\n\t\t\twrapPeriodX: offset.crs.wrapPeriodX,\r\n\t\t\twrapPeriodY: offset.crs.wrapPeriodY,\r\n\t\t\tdistance: offset.crs.distance,\r\n\t\t\tflipAxes: offset.crs.flipAxes,\r\n\t\t\tminSpan: offset.crs.minSpan,\r\n\t\t\tmaxSpan: offset.crs.maxSpan,\r\n\t\t\tviewableBounds: offset.crs.viewableBounds,\r\n\t\t});\r\n\r\n\t\tif (offset.coords.length !== offset.dimension) {\r\n\t\t\tthrow new Error(\"Offset geometry must be a point\");\r\n\t\t}\r\n\r\n\t\tthis.offset = offset.coords;\r\n\t\tthis.offset[0] %= offset.crs.wrapPeriodX;\r\n\t\tthis.offset[1] %= offset.crs.wrapPeriodY;\r\n\r\n\t\t/// TODO: Decide whether to:\r\n\t\t/// Change this so that the `offset.crs`'s own offset is added to the\r\n\t\t/// offset absolute value - that is, offsets are accumulated.\r\n\t\t/// or\r\n\t\t/// Keep it this way and ensure that all offsets are relative to the base,\r\n\t\t/// i.e. `offsetToBase()` is applied.\r\n\t}\r\n\r\n\t/**\r\n\t * @method offsetToBase(xys: Array of Number): Array of Number\r\n\t * Given a set of coordinates `[x1, y1, x2, y2, ... xn, yn]`, returns\r\n\t * those coordinates as if they were using the `BaseCRS`'s (0,0) origin\r\n\t * of coordinates.\r\n\t */\r\n\toffsetToBase(xys) {\r\n\t\tconst l = xys.length;\r\n\t\tconst out = new Array(l);\r\n\t\tconst ox = this.offset[0];\r\n\t\tconst oy = this.offset[1];\r\n\t\tfor (let i = 0; i < l; i += 2) {\r\n\t\t\tout[i] = xys[i] + ox;\r\n\t\t\tout[i + 1] = xys[i + 1] + oy;\r\n\t\t}\r\n\t\treturn out;\r\n\t}\r\n\r\n\t/**\r\n\t * @method offsetFromBase(xy: Array of Number): Array of Number\r\n\t * Given a set of coordinates `[x1, y1, x2, y2, ... xn, yn]` in the\r\n\t * `BaseCRS` of this CRS, returns those coordinates as if they were using\r\n\t * the origin of coordinates of this offset CRS.\r\n\t */\r\n\toffsetFromBase(xys) {\r\n\t\tconst l = xys.length;\r\n\t\tconst out = new Array(l);\r\n\t\tconst ox = this.offset[0];\r\n\t\tconst oy = this.offset[1];\r\n\t\tfor (let i = 0; i < l; i += 2) {\r\n\t\t\tout[i] = xys[i] - ox;\r\n\t\t\tout[i + 1] = xys[i + 1] - oy;\r\n\t\t}\r\n\t\treturn out;\r\n\t}\r\n}\r\n\r\n/*\r\n * TODO: store the exponent of the (x,y) offset(s) somehow. There should be some way\r\n * to map the full exponent resolution of float64s into float24s.\r\n *\r\n * In other words, float24 only has 7 bits of exponent, so how to represent things\r\n * in the order of e.g. 2^-129 (smaller than 2^-128), which can be represented in float64 ?\r\n *\r\n * The exponent of the scale delta (i.e. how many CRS units per *one* pixel) should be\r\n * used here as well.\r\n *\r\n * For now, I'll just assume that using numbers with magnitudes smaller than 2^-127\r\n * or larger than 2^127 is not a foreseeable use case.\r\n */\r\n","import { project } from \"../crs/projector.mjs\";\r\nimport { getCRS } from \"../crs/knownCRSs.mjs\";\r\n\r\n/**\r\n * @class RawGeometry\r\n * @relationship compositionOf BaseCRS, 1..1, 0..n\r\n * @relationship associated projector\r\n * @relationship dependsOn knownCRSs\r\n *\r\n * Like `Geometry`, but expects the \"raw\" flattened coordinate array,\r\n * rings array, hulls array, and skips the assertions.\r\n *\r\n */\r\n\r\nexport default class RawGeometry {\r\n\t/**\r\n\t * @constructor RawGeometry(crs: BaseCRS, coords: Array of Number, rings: Array of Number, hulls: Array of Number, options: RawGeometry Options)\r\n\t */\r\n\tconstructor(\r\n\t\tcrs,\r\n\t\tcoords,\r\n\t\trings = [],\r\n\t\thulls = [],\r\n\t\t{ wrap = true, dimension = 2 } = {}\r\n\t) {\r\n\t\t/**\r\n\t\t * @section RawGeometry Options\r\n\t\t * @option wrap: Boolean = true\r\n\t\t * Whether antimeridian-wrap functionality should be enabled for this geometry.\r\n\t\t *\r\n\t\t * Only works for 2-dimensional `Geometry`s.\r\n\t\t *\r\n\t\t * @option dimension: Number = 2\r\n\t\t * The dimension of each coordinate. 2 for X-Y, 3 for X-Y-Z, 4 for X-Y-Z-M.\r\n\t\t */\r\n\t\tthis.wrap = wrap;\r\n\t\tthis.dimension = dimension;\r\n\t\tthis.crs = crs;\r\n\r\n\t\t/**\r\n\t\t * @property coords: Array of Number\r\n\t\t * A flat `Array` containing the CRS-relative coordinates or the\r\n\t\t * geometry, in `[x1, y1, x2, y2, ..., xn, yn]` form.\r\n\t\t */\r\n\t\tthis.coords = this.wrap ? crs.wrapString(coords) : coords;\r\n\t\t/**\r\n\t\t * @property rings: Array of Number\r\n\t\t * A flat `Array` containing indices (0-indexed) of the coordinate\r\n\t\t * pairs that start a new ring.\r\n\t\t */\r\n\t\tthis.rings = rings;\r\n\t\t/**\r\n\t\t * @property hulls: Array of Number\r\n\t\t * A flat `Array` containing indices (0-indexed) of the coordinate\r\n\t\t * pairs that start a new hull.\r\n\t\t */\r\n\t\tthis.hulls = hulls;\r\n\t}\r\n\r\n\t/**\r\n\t * @method toCRS(newCRS: CRS): Geometry\r\n\t * Returns the `Geometry`, translated/projected to the given CRS, as a new instance.\r\n\t *\r\n\t * If the CRS is exactly the same, `this` is returned instead.\r\n\t * @alternative\r\n\t * @method toCRS(newCRS: String): Geometry\r\n\t * Idem, but takes a `String` containing the name (e.g. \"EPSG:4326\", \"cartesian\")\r\n\t * or the OGC URI of a CRS. The corresponding CRS instance will be looked up.\r\n\t */\r\n\ttoCRS(newCRS) {\r\n\t\tif (newCRS === this.crs) {\r\n\t\t\treturn this;\r\n\t\t} else if (typeof newCRS === \"string\") {\r\n\t\t\treturn this.toCRS(getCRS(newCRS));\r\n\t\t} else if (newCRS.name === this.crs.name) {\r\n\t\t\t// Return a new Geometry mapping an offset to all xy pairs.\r\n\t\t\treturn new RawGeometry(\r\n\t\t\t\tnewCRS,\r\n\t\t\t\tnewCRS.offsetFromBase(this.crs.offsetToBase(this.coords)),\r\n\t\t\t\tthis.rings,\r\n\t\t\t\tthis.hulls,\r\n\t\t\t\t{ wrap: this.wrap, dimension: 2 }\r\n\t\t\t);\r\n\r\n\t\t\t/// TODO: For wrapping CRSs, wrap the coordinate... with the original CRS\r\n\t\t\t/// wrapping limit/dimension/span, but using the offset center.\r\n\t\t\t/// So e.g. epsg:4326 offset to +170 longitude would wrap -175 to relative\r\n\t\t\t/// +15; this pushes the antimeridian opposite to the offset center.\r\n\t\t} else {\r\n\t\t\t// Reproject\r\n\t\t\treturn new RawGeometry(\r\n\t\t\t\tnewCRS,\r\n\t\t\t\tthis.mapCoords((xy) =>\r\n\t\t\t\t\tnewCRS.offsetFromBase(\r\n\t\t\t\t\t\tproject(this.crs.name, newCRS.name, this.crs.offsetToBase(xy))\r\n\t\t\t\t\t)\r\n\t\t\t\t),\r\n\t\t\t\tthis.rings,\r\n\t\t\t\tthis.hulls,\r\n\t\t\t\t{ wrap: this.wrap, dimension: 2 }\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method asLatLng(): Array of Number\r\n\t *\r\n\t * Returns a **flat** array of latitude-longitude (Y-X) representing this geometry,\r\n\t * in the form `[lat1, lng1, lat2, lng2, ... latN, lngN]`.\r\n\t *\r\n\t * Will throw an error if the geometry cannot be converted to latitude-longitude\r\n\t * (i.e. is in a CRS that cannot be reprojected to EPSG:4326).\r\n\t *\r\n\t */\r\n\tasLatLng() {\r\n\t\tconst xys = this.asLngLat();\r\n\r\n\t\tconst yxs = new Array(xys.length);\r\n\t\tfor (let i = 0, l = xys.length; i < l; i += 2) {\r\n\t\t\tyxs[i] = xys[i + 1];\r\n\t\t\tyxs[i + 1] = xys[i];\r\n\t\t}\r\n\t\treturn yxs;\r\n\t\t/*\r\n\t\treturn new Array(xys,(_,i)=> xys[\r\n\t\t\t(i >> 1 << 1) + // Get rid of the least significant bit\r\n\t\t\t!(i % 2) // Reverse of modulo 2\r\n\t\t]);*/\r\n\t}\r\n\r\n\t/**\r\n\t * @method asLngLat(): Array of Number\r\n\t *\r\n\t * Returns a **flat** array of longitude-latitude (X-Y) representing this geometry,\r\n\t * in the form `[lng1, lat1, lng2, lat2, ... lngN, latN]`.\r\n\t *\r\n\t * Will throw an error if the geometry cannot be converted to latitude-longitude\r\n\t * (i.e. is in a CRS that cannot be reprojected to EPSG:4326).\r\n\t *\r\n\t */\r\n\tasLngLat() {\r\n\t\treturn this.toCRS(\"EPSG:4326\").coords;\r\n\t}\r\n\r\n\t#loops;\r\n\t/**\r\n\t * @property loops: Array of Boolean\r\n\t * A read-only `Array` with one `Boolean` values per ring. The value is\r\n\t * `true` for those rings which forms loops (the first coordinate pair\r\n\t * equals the last one).\r\n\t */\r\n\tget loops() {\r\n\t\tif (this.#loops) {\r\n\t\t\treturn this.#loops;\r\n\t\t}\r\n\r\n\t\t/// NOTE: This implementation only works for dimension 2 (XY) geometries.\r\n\t\t/// Ideally t should cover XYZ/XYM/ZYZM geometries.\r\n\r\n\t\t/// FIXME: Logic when the linestring loops across the antimridian.\r\n\t\t/// Compare start and end points, **taking into account CRS wrapping**.\r\n\t\treturn (this.#loops = this.mapRings((start, end) => {\r\n\t\t\tconst start2 = start * 2;\r\n\t\t\tconst end2 = 2 * (end - 1);\r\n\t\t\treturn (\r\n\t\t\t\tthis.coords[start2 + 0] === this.coords[end2 + 0] &&\r\n\t\t\t\tthis.coords[start2 + 1] === this.coords[end2 + 1]\r\n\t\t\t);\r\n\t\t}));\r\n\t}\r\n\r\n\t/**\r\n\t * @property loops: Array of Boolean\r\n\t * A read-only containing the starts (inclusive) and ends (exclusive)\r\n\t * of all rings.\r\n\t *\r\n\t * Contains, at least, `0` and the amount of coordinate pairs (for\r\n\t * geometries with just one ring)\r\n\t */\r\n\t#stops;\r\n\tget stops() {\r\n\t\tif (this.#stops) {\r\n\t\t\treturn this.#stops;\r\n\t\t}\r\n\t\treturn (this.#stops = [\r\n\t\t\t0,\r\n\t\t\t...this.rings,\r\n\t\t\t...this.hulls,\r\n\t\t\tthis.coords.length / this.dimension,\r\n\t\t].sort((a, b) => a - b));\r\n\t}\r\n\r\n\t/**\r\n\t * @method mapCoords(fn: Function): Array of Number\r\n\t * Returns a new array of the form `[x1,y1, ... xn,yn]`, having run the given\r\n\t * `Function` on every `x,y` pair of coordinates from self. The `Function`\r\n\t * must take an `Array` of 2 `Number`s as its first parameter (the coordinate\r\n\t * pair), a `Number` as its second parameter (the index of the current\r\n\t * coordinate pair, 0-indexed), and must return an `Array` of 2 `Number`s as well.\r\n\t *\r\n\t * Takes into account the dimension (dimension 3 works for `x,y,z` and dimension 4\r\n\t * works for `x,y,z,m`)\r\n\t *\r\n\t */\r\n\tmapCoords(fn) {\r\n\t\tconst d = this.dimension;\r\n\t\tconst l = this.coords.length / d;\r\n\t\tconst result = new Array(l);\r\n\r\n\t\tfor (let i = 0; i < l; i++) {\r\n\t\t\tconst j = i * d;\r\n\t\t\tresult[i] = fn(this.coords.slice(j, j + d), i);\r\n\t\t}\r\n\r\n\t\treturn result.flat();\r\n\t}\r\n\r\n\t/**\r\n\t * @method mapRings(fn: Function): Array\r\n\t * Runs the given `Function` once per ring (including each ring in each hull, if\r\n\t * applicable), and returns an `Array` containing the return values.\r\n\t *\r\n\t * The given `Function` can expect four parameters:\r\n\t * * `start` coordinate (0-indexed, inclusive)\r\n\t * * `end` coordinate (0-indexed, exclusive)\r\n\t * * `length` of the ring (how many coordinates in that ring, also `end-start`)\r\n\t * * `i`, index of the current ring (0-indexed)\r\n\t */\r\n\tmapRings(fn) {\r\n\t\t// I'm sure this can be done more efficiently in a C-like fashion\r\n\t\t// (i.e. pulling a value from either this.rings or this.hulls at\r\n\t\t// each pass of the loop, no array sorting/concat'ing), but this\r\n\t\t// should do for now.\r\n\r\n\t\tlet stops = this.stops;\r\n\r\n\t\tconst result = new Array(stops.length - 1);\r\n\r\n\t\tfor (let i = 0, l = stops.length - 1; i < l; i++) {\r\n\t\t\tconst start = stops[i];\r\n\t\t\tconst end = stops[i + 1];\r\n\t\t\tresult[i] = fn(start, end, end - start, i);\r\n\t\t}\r\n\r\n\t\treturn result;\r\n\t}\r\n}\r\n","import RawGeometry from \"./RawGeometry.mjs\";\r\nimport { getCRS } from \"../crs/knownCRSs.mjs\";\r\nimport BaseCRS from \"../crs/BaseCRS.mjs\";\r\n\r\n/**\r\n * @class Geometry\r\n * @inherits RawGeometry\r\n *\r\n * A Gleo `Geometry` is akin to geometries in the OGC Simple Features Specification:\r\n * points, linestrings, polygons and multipolygons.\r\n *\r\n * Internally, `Geometry`s are represented as a flat array of coordinates, in the form\r\n * `[x1,y2, x2,y2, x3,y3 ... xn,yn]` for 2-dimensional geometries; plus a list of\r\n * offsets specifying the n-th coordinate where a hull starts and a ring starts (0th\r\n * hulls and rings are ommitted).\r\n *\r\n * (About nomenclature: a multipolygon has one or more hulls, and each hull\r\n * has an outer ring and zero or more inner rings. Hulls tell apart polygons\r\n * within a multipolygon, and rings tell apart inner/outer boundaries of a\r\n * polygon).\r\n *\r\n * (TODO: [x1,y1,z1, ... xn,yn,zn] for 3-dimensional, and [x1,y1,z1,m1, ... xn,yn,zn,mn]\r\n * for 4-dimensional)\r\n *\r\n * @example\r\n *\r\n * ```\r\n * let point = new Geometry(crs, [x,y]);\r\n *\r\n * let linestring = new Geometry(crs, [[x1,y1],[x2,y2]]);\r\n * ```\r\n *\r\n **/\r\n\r\n/*\r\n\r\nDepths:\r\n\r\n0 Point\r\n1 Multipoint Linestring\r\n2 Multilinestring Polygon\r\n3 Multipolygon\r\n\r\n */\r\n\r\nexport default class Geometry extends RawGeometry {\r\n\t/**\r\n\t * @section\r\n\t * The constructor for a `Geometry` can take either a `BaseCRS` instance, or\r\n\t * its name.\r\n\t *\r\n\t * The geometry can be:\r\n\t * - An `Array` of two `Number`s (for points)\r\n\t * - An `Array` of `Array`s of two `Number`s (for multipoints or linestrings)\r\n\t * - An `Array` of `Array`s of `Array`s of two `Number`s (for multilinestrings or polygons)\r\n\t * - An `Array` of `Array`s of `Array`s of `Array`s of two `Number`s (for multipolygons)\r\n\t *\r\n\t * @constructor Geometry(crs: BaseCRS, coords: Array of Number, opts: Geometry Options)\r\n\t * @alternative\r\n\t * @constructor Geometry(crs: String, coords: Array of Number, opts: Geometry Options)\r\n\t * @alternative\r\n\t * @constructor Geometry(crs: BaseCRS, coords: Array of Array of Number, opts: Geometry Options)\r\n\t * @alternative\r\n\t * @constructor Geometry(crs: BaseCRS, coords: Array of Array of Array of Number, opts: Geometry Options)\r\n\t * @alternative\r\n\t * @constructor Geometry(crs: BaseCRS, coords: Array of Array of Array of Array of Number, opts: Geometry Options)\r\n\t */\r\n\tconstructor(\r\n\t\tcrs,\r\n\t\tcoords,\r\n\t\t{\r\n\t\t\twrap,\r\n\t\t\tdimension = 2,\r\n\t\t\t/**\r\n\t\t\t * @section Geometry Options\r\n\t\t\t * @option deduplicate: Boolean = true\r\n\t\t\t * Whether to detect and remove duplicated consecutive coordinates. Prevents\r\n\t\t\t * graphical artefacts on some edge cases of topologically malformed data.\r\n\t\t\t */\r\n\t\t\tdeduplicate = true,\r\n\t\t} = {}\r\n\t) {\r\n\t\tlet rings = [];\r\n\t\tlet hulls = [];\r\n\t\tlet depth;\r\n\t\tif (typeof coords[0] === \"number\") {\r\n\t\t\tdepth = 0;\r\n\t\t} else {\r\n\t\t\tif (deduplicate) {\r\n\t\t\t\tcoords = deduplicateConsecutives(coords);\r\n\t\t\t}\r\n\r\n\t\t\tif (typeof coords[0][0] === \"number\") {\r\n\t\t\t\tdepth = 1;\r\n\t\t\t} else if (typeof coords[0][0][0] === \"number\") {\r\n\t\t\t\tdepth = 2;\r\n\t\t\t\t// Calculate rings\r\n\t\t\t\tlet ringOffset = 0;\r\n\t\t\t\tfor (let r = 0, l = coords.length - 1; r < l; r++) {\r\n\t\t\t\t\trings.push((ringOffset += coords[r].length));\r\n\t\t\t\t}\r\n\t\t\t} else if (typeof coords[0][0][0][0] === \"number\") {\r\n\t\t\t\tdepth = 3;\r\n\t\t\t\t// hulls and rings\r\n\t\t\t\tlet hullOffset = 0;\r\n\t\t\t\tlet ringOffset = 0;\r\n\t\t\t\tfor (let h = 0, l = coords.length; h < l; h++) {\r\n\t\t\t\t\tlet hullSize = 0;\r\n\t\t\t\t\tfor (let r = 0, ll = coords[h].length; r < ll; r++) {\r\n\t\t\t\t\t\tconst ringLength = coords[h][r].length;\r\n\t\t\t\t\t\tringOffset += ringLength;\r\n\t\t\t\t\t\tif (r !== ll - 1) {\r\n\t\t\t\t\t\t\trings.push(ringOffset);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\thullSize += ringLength;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (h !== l - 1) {\r\n\t\t\t\t\t\thulls.push((hullOffset += hullSize));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tthrow new Error(\r\n\t\t\t\t\t\"Coordinate array passed to Geometry constructor has too many levels of array nesting.\"\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Assert dimension\r\n\t\tif (depth) {\r\n\t\t\tif (!coords.flat(depth - 1).every((v) => v.length === dimension)) {\r\n\t\t\t\tthrow new Error(\r\n\t\t\t\t\t`While instancing Geometry, expected all coordinates to be of dimension ${dimension}`\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tif (coords.length !== dimension) {\r\n\t\t\t\tthrow new Error(\r\n\t\t\t\t\t`While instancing point Geometry, expected all coordinates to be of dimension ${dimension}`\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (!(crs instanceof BaseCRS)) {\r\n\t\t\tcrs = getCRS(crs);\r\n\t\t}\r\n\r\n\t\tsuper(crs, coords.flat(depth), rings, hulls, { wrap, dimension });\r\n\t}\r\n}\r\n\r\nfunction deduplicateConsecutives(coords) {\r\n\tif (typeof coords[0][0] !== \"number\") {\r\n\t\treturn coords.map(deduplicateConsecutives);\r\n\t}\r\n\r\n\tconst l = coords.length - 1;\r\n\treturn coords.filter(\r\n\t\t(c, i) => i === l || c[0] !== coords[i + 1][0] || c[1] !== coords[i + 1][1]\r\n\t);\r\n}\r\n","/**\r\n * @class Evented\r\n * @inherits EventTarget\r\n *\r\n * Lightweight utility wrapper around `EventTarget`.\r\n */\r\n\r\nexport default class Evented extends EventTarget {\r\n\t/**\r\n\t * @section Event Methods\r\n\t * @method on(eventName: String, handler: Function): this\r\n\t * Alias to `EventTarget`'s `addEventListener`.\r\n\t * @method off(eventName: String, handler: Function): this\r\n\t * Alias to `EventTarget`'s `removeEventListener`.\r\n\t */\r\n\ton() {\r\n\t\tthis.addEventListener.apply(this, arguments);\r\n\t\treturn this;\r\n\t}\r\n\toff() {\r\n\t\tthis.removeEventListener.apply(this, arguments);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method once(eventName: String, handler?: Function): Promise\r\n\t * As `on()`, but the handler function will only be called once (it'll be\r\n\t * detached after the first fired event). Returns a `Promise` that resolves\r\n\t * to the event when that event is fired.\r\n\t */\r\n\tonce(eventName, handler) {\r\n\t\treturn new Promise((resolve) => {\r\n\t\t\tif (handler) {\r\n\t\t\t\tthis.on(eventName, handler, { once: true });\r\n\t\t\t}\r\n\t\t\tthis.on(eventName, resolve, { once: true });\r\n\t\t});\r\n\t}\r\n\r\n\t/**\r\n\t * @method fire(eventName: String, detail: Object): Boolean\r\n\t *\r\n\t * Wrapper over `EventTarget`'s `dispatchEvent`. Creates a new instance of\r\n\t * `CustomEvent`, dispatches it, and returns `true` if some event handler\r\n\t * did `preventDefault` the event.\r\n\t */\r\n\tfire(eventName, detail) {\r\n\t\treturn this.dispatchEvent(new CustomEvent(eventName, { detail }));\r\n\t}\r\n}\r\n","import Platina from \"../Platina.mjs\";\r\nimport Evented from \"../dom/Evented.mjs\";\r\n\r\n/**\r\n * @class Loader\r\n * @inherits Evented\r\n *\r\n * A `Loader` loads/unloads symbols as needed.\r\n *\r\n * Some `Loader`s might watch for changes in the map's (or the `Platina`s)\r\n * state (center, scale, etc) and load/unload symbols based on that.\r\n *\r\n * Some other `Loader`s work with data formats, making network requests and\r\n * parsing data.\r\n *\r\n *\r\n * @event symbolsadded\r\n * Fired whenever the loader reports new symbols. Even details include such symbols.\r\n * @event symbolsremoved\r\n * Fired whenever the loader forgets or unloads old symbols. Event details include such symbols.\r\n */\r\n\r\nexport default class Loader extends Evented {\r\n\t#target;\r\n\t#platina;\r\n\r\n\tconstructor({ attribution } = {}) {\r\n\t\tsuper();\r\n\t\t/**\r\n\t\t * @option attribution: String = undefined\r\n\t\t * The HTML attribution to be shown in the `AttributionControl`, if any.\r\n\t\t */\r\n\t\tthis.attribution = attribution;\r\n\t}\r\n\r\n\t/**\r\n\t * @method addTo(target: GleoMap): this\r\n\t * Adds the `Loader` to the given `GleoMap`\r\n\t * @alternative\r\n\t * @method addTo(target: Platina): this\r\n\t * Adds the `Loader` to the given `Platina`\r\n\t * @alternative addTo(target: SymbolGroup\r\n\t * Adds the `Loader` to the given `SymbolGroup`\r\n\t */\r\n\taddTo(target) {\r\n\t\tif (!this.#target && target.has(this)) {\r\n\t\t\t// This loader is in the process of being added to a SymbolGroup\r\n\t\t\tthis.#target = target;\r\n\t\t} else {\r\n\t\t\tthis.#target = target;\r\n\t\t\ttarget.add(this);\r\n\t\t}\r\n\r\n\t\tif (target.platina) {\r\n\t\t\tthis._addToPlatina(target.platina);\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @property target: GleoMap\r\n\t * The target where the loader was added to. Might be a `GleoMap`, a `Platina`\r\n\t * or a `SymbolGroup`. Read-only.\r\n\t * @alternative\r\n\t * @property target: Platina\r\n\t * @alternative\r\n\t * @property target: SymbolGroup\r\n\t */\r\n\tget target() {\r\n\t\treturn this.#target;\r\n\t}\r\n\r\n\t/**\r\n\t * @property platina\r\n\t * The `Platina` where symbols from this loader will be drawn into. Read-only.\r\n\t */\r\n\tget platina() {\r\n\t\treturn this.#platina;\r\n\t}\r\n\r\n\t/**\r\n\t * @class Loader\r\n\t * @method remove(): this\r\n\t * Removes the `Loader` from the map/platina it was in.\r\n\t */\r\n\tremove() {\r\n\t\tif (!this.#target) {\r\n\t\t\tthrow new Error(\"Cannot remove Loader: is already removed\");\r\n\t\t}\r\n\r\n\t\tthis.#platina = this.#target = undefined;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// Internal use only.\r\n\t// Called when the loader gets attached to a platina. Might not happen\r\n\t// immediately, in cases like a loader gets added to a SymbolGroup, and later\r\n\t// that SymbolGroup gets added to a Platina.\r\n\t// Subclasses may extend this method.\r\n\t_addToPlatina(platina) {\r\n\t\tthis.#platina = platina;\r\n\t\tthis.#target ||= platina;\r\n\t}\r\n}\r\n","/**\r\n * Common utilities\r\n * @module glMatrix\r\n */\r\n// Configuration Constants\r\nexport var EPSILON = 0.000001;\r\nexport var ARRAY_TYPE = typeof Float32Array !== \"undefined\" ? Float32Array : Array;\r\nexport var RANDOM = Math.random;\r\nexport var ANGLE_ORDER = \"zyx\";\r\n/**\r\n * Sets the type of array used when creating new vectors and matrices\r\n *\r\n * @param {Float32ArrayConstructor | ArrayConstructor} type Array type, such as Float32Array or Array\r\n */\r\n\r\nexport function setMatrixArrayType(type) {\r\n ARRAY_TYPE = type;\r\n}\r\nvar degree = Math.PI / 180;\r\n/**\r\n * Convert Degree To Radian\r\n *\r\n * @param {Number} a Angle in Degrees\r\n */\r\n\r\nexport function toRadian(a) {\r\n return a * degree;\r\n}\r\n/**\r\n * Tests whether or not the arguments have approximately the same value, within an absolute\r\n * or relative tolerance of glMatrix.EPSILON (an absolute tolerance is used for values less\r\n * than or equal to 1.0, and a relative tolerance is used for larger values)\r\n *\r\n * @param {Number} a The first number to test.\r\n * @param {Number} b The second number to test.\r\n * @returns {Boolean} True if the numbers are approximately equal, false otherwise.\r\n */\r\n\r\nexport function equals(a, b) {\r\n return Math.abs(a - b) <= EPSILON * Math.max(1.0, Math.abs(a), Math.abs(b));\r\n}\r\nif (!Math.hypot) Math.hypot = function () {\r\n var y = 0,\r\n i = arguments.length;\r\n\r\n while (i--) {\r\n y += arguments[i] * arguments[i];\r\n }\r\n\r\n return Math.sqrt(y);\r\n};","import * as glMatrix from \"./common.mjs\";\r\n/**\r\n * 3x3 Matrix\r\n * @module mat3\r\n */\r\n\r\n/**\r\n * Creates a new identity mat3\r\n *\r\n * @returns {mat3} a new 3x3 matrix\r\n */\r\n\r\nexport function create() {\r\n var out = new glMatrix.ARRAY_TYPE(9);\r\n\r\n if (glMatrix.ARRAY_TYPE != Float32Array) {\r\n out[1] = 0;\r\n out[2] = 0;\r\n out[3] = 0;\r\n out[5] = 0;\r\n out[6] = 0;\r\n out[7] = 0;\r\n }\r\n\r\n out[0] = 1;\r\n out[4] = 1;\r\n out[8] = 1;\r\n return out;\r\n}\r\n/**\r\n * Copies the upper-left 3x3 values into the given mat3.\r\n *\r\n * @param {mat3} out the receiving 3x3 matrix\r\n * @param {ReadonlyMat4} a the source 4x4 matrix\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function fromMat4(out, a) {\r\n out[0] = a[0];\r\n out[1] = a[1];\r\n out[2] = a[2];\r\n out[3] = a[4];\r\n out[4] = a[5];\r\n out[5] = a[6];\r\n out[6] = a[8];\r\n out[7] = a[9];\r\n out[8] = a[10];\r\n return out;\r\n}\r\n/**\r\n * Creates a new mat3 initialized with values from an existing matrix\r\n *\r\n * @param {ReadonlyMat3} a matrix to clone\r\n * @returns {mat3} a new 3x3 matrix\r\n */\r\n\r\nexport function clone(a) {\r\n var out = new glMatrix.ARRAY_TYPE(9);\r\n out[0] = a[0];\r\n out[1] = a[1];\r\n out[2] = a[2];\r\n out[3] = a[3];\r\n out[4] = a[4];\r\n out[5] = a[5];\r\n out[6] = a[6];\r\n out[7] = a[7];\r\n out[8] = a[8];\r\n return out;\r\n}\r\n/**\r\n * Copy the values from one mat3 to another\r\n *\r\n * @param {mat3} out the receiving matrix\r\n * @param {ReadonlyMat3} a the source matrix\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function copy(out, a) {\r\n out[0] = a[0];\r\n out[1] = a[1];\r\n out[2] = a[2];\r\n out[3] = a[3];\r\n out[4] = a[4];\r\n out[5] = a[5];\r\n out[6] = a[6];\r\n out[7] = a[7];\r\n out[8] = a[8];\r\n return out;\r\n}\r\n/**\r\n * Create a new mat3 with the given values\r\n *\r\n * @param {Number} m00 Component in column 0, row 0 position (index 0)\r\n * @param {Number} m01 Component in column 0, row 1 position (index 1)\r\n * @param {Number} m02 Component in column 0, row 2 position (index 2)\r\n * @param {Number} m10 Component in column 1, row 0 position (index 3)\r\n * @param {Number} m11 Component in column 1, row 1 position (index 4)\r\n * @param {Number} m12 Component in column 1, row 2 position (index 5)\r\n * @param {Number} m20 Component in column 2, row 0 position (index 6)\r\n * @param {Number} m21 Component in column 2, row 1 position (index 7)\r\n * @param {Number} m22 Component in column 2, row 2 position (index 8)\r\n * @returns {mat3} A new mat3\r\n */\r\n\r\nexport function fromValues(m00, m01, m02, m10, m11, m12, m20, m21, m22) {\r\n var out = new glMatrix.ARRAY_TYPE(9);\r\n out[0] = m00;\r\n out[1] = m01;\r\n out[2] = m02;\r\n out[3] = m10;\r\n out[4] = m11;\r\n out[5] = m12;\r\n out[6] = m20;\r\n out[7] = m21;\r\n out[8] = m22;\r\n return out;\r\n}\r\n/**\r\n * Set the components of a mat3 to the given values\r\n *\r\n * @param {mat3} out the receiving matrix\r\n * @param {Number} m00 Component in column 0, row 0 position (index 0)\r\n * @param {Number} m01 Component in column 0, row 1 position (index 1)\r\n * @param {Number} m02 Component in column 0, row 2 position (index 2)\r\n * @param {Number} m10 Component in column 1, row 0 position (index 3)\r\n * @param {Number} m11 Component in column 1, row 1 position (index 4)\r\n * @param {Number} m12 Component in column 1, row 2 position (index 5)\r\n * @param {Number} m20 Component in column 2, row 0 position (index 6)\r\n * @param {Number} m21 Component in column 2, row 1 position (index 7)\r\n * @param {Number} m22 Component in column 2, row 2 position (index 8)\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function set(out, m00, m01, m02, m10, m11, m12, m20, m21, m22) {\r\n out[0] = m00;\r\n out[1] = m01;\r\n out[2] = m02;\r\n out[3] = m10;\r\n out[4] = m11;\r\n out[5] = m12;\r\n out[6] = m20;\r\n out[7] = m21;\r\n out[8] = m22;\r\n return out;\r\n}\r\n/**\r\n * Set a mat3 to the identity matrix\r\n *\r\n * @param {mat3} out the receiving matrix\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function identity(out) {\r\n out[0] = 1;\r\n out[1] = 0;\r\n out[2] = 0;\r\n out[3] = 0;\r\n out[4] = 1;\r\n out[5] = 0;\r\n out[6] = 0;\r\n out[7] = 0;\r\n out[8] = 1;\r\n return out;\r\n}\r\n/**\r\n * Transpose the values of a mat3\r\n *\r\n * @param {mat3} out the receiving matrix\r\n * @param {ReadonlyMat3} a the source matrix\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function transpose(out, a) {\r\n // If we are transposing ourselves we can skip a few steps but have to cache some values\r\n if (out === a) {\r\n var a01 = a[1],\r\n a02 = a[2],\r\n a12 = a[5];\r\n out[1] = a[3];\r\n out[2] = a[6];\r\n out[3] = a01;\r\n out[5] = a[7];\r\n out[6] = a02;\r\n out[7] = a12;\r\n } else {\r\n out[0] = a[0];\r\n out[1] = a[3];\r\n out[2] = a[6];\r\n out[3] = a[1];\r\n out[4] = a[4];\r\n out[5] = a[7];\r\n out[6] = a[2];\r\n out[7] = a[5];\r\n out[8] = a[8];\r\n }\r\n\r\n return out;\r\n}\r\n/**\r\n * Inverts a mat3\r\n *\r\n * @param {mat3} out the receiving matrix\r\n * @param {ReadonlyMat3} a the source matrix\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function invert(out, a) {\r\n var a00 = a[0],\r\n a01 = a[1],\r\n a02 = a[2];\r\n var a10 = a[3],\r\n a11 = a[4],\r\n a12 = a[5];\r\n var a20 = a[6],\r\n a21 = a[7],\r\n a22 = a[8];\r\n var b01 = a22 * a11 - a12 * a21;\r\n var b11 = -a22 * a10 + a12 * a20;\r\n var b21 = a21 * a10 - a11 * a20; // Calculate the determinant\r\n\r\n var det = a00 * b01 + a01 * b11 + a02 * b21;\r\n\r\n if (!det) {\r\n return null;\r\n }\r\n\r\n det = 1.0 / det;\r\n out[0] = b01 * det;\r\n out[1] = (-a22 * a01 + a02 * a21) * det;\r\n out[2] = (a12 * a01 - a02 * a11) * det;\r\n out[3] = b11 * det;\r\n out[4] = (a22 * a00 - a02 * a20) * det;\r\n out[5] = (-a12 * a00 + a02 * a10) * det;\r\n out[6] = b21 * det;\r\n out[7] = (-a21 * a00 + a01 * a20) * det;\r\n out[8] = (a11 * a00 - a01 * a10) * det;\r\n return out;\r\n}\r\n/**\r\n * Calculates the adjugate of a mat3\r\n *\r\n * @param {mat3} out the receiving matrix\r\n * @param {ReadonlyMat3} a the source matrix\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function adjoint(out, a) {\r\n var a00 = a[0],\r\n a01 = a[1],\r\n a02 = a[2];\r\n var a10 = a[3],\r\n a11 = a[4],\r\n a12 = a[5];\r\n var a20 = a[6],\r\n a21 = a[7],\r\n a22 = a[8];\r\n out[0] = a11 * a22 - a12 * a21;\r\n out[1] = a02 * a21 - a01 * a22;\r\n out[2] = a01 * a12 - a02 * a11;\r\n out[3] = a12 * a20 - a10 * a22;\r\n out[4] = a00 * a22 - a02 * a20;\r\n out[5] = a02 * a10 - a00 * a12;\r\n out[6] = a10 * a21 - a11 * a20;\r\n out[7] = a01 * a20 - a00 * a21;\r\n out[8] = a00 * a11 - a01 * a10;\r\n return out;\r\n}\r\n/**\r\n * Calculates the determinant of a mat3\r\n *\r\n * @param {ReadonlyMat3} a the source matrix\r\n * @returns {Number} determinant of a\r\n */\r\n\r\nexport function determinant(a) {\r\n var a00 = a[0],\r\n a01 = a[1],\r\n a02 = a[2];\r\n var a10 = a[3],\r\n a11 = a[4],\r\n a12 = a[5];\r\n var a20 = a[6],\r\n a21 = a[7],\r\n a22 = a[8];\r\n return a00 * (a22 * a11 - a12 * a21) + a01 * (-a22 * a10 + a12 * a20) + a02 * (a21 * a10 - a11 * a20);\r\n}\r\n/**\r\n * Multiplies two mat3's\r\n *\r\n * @param {mat3} out the receiving matrix\r\n * @param {ReadonlyMat3} a the first operand\r\n * @param {ReadonlyMat3} b the second operand\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function multiply(out, a, b) {\r\n var a00 = a[0],\r\n a01 = a[1],\r\n a02 = a[2];\r\n var a10 = a[3],\r\n a11 = a[4],\r\n a12 = a[5];\r\n var a20 = a[6],\r\n a21 = a[7],\r\n a22 = a[8];\r\n var b00 = b[0],\r\n b01 = b[1],\r\n b02 = b[2];\r\n var b10 = b[3],\r\n b11 = b[4],\r\n b12 = b[5];\r\n var b20 = b[6],\r\n b21 = b[7],\r\n b22 = b[8];\r\n out[0] = b00 * a00 + b01 * a10 + b02 * a20;\r\n out[1] = b00 * a01 + b01 * a11 + b02 * a21;\r\n out[2] = b00 * a02 + b01 * a12 + b02 * a22;\r\n out[3] = b10 * a00 + b11 * a10 + b12 * a20;\r\n out[4] = b10 * a01 + b11 * a11 + b12 * a21;\r\n out[5] = b10 * a02 + b11 * a12 + b12 * a22;\r\n out[6] = b20 * a00 + b21 * a10 + b22 * a20;\r\n out[7] = b20 * a01 + b21 * a11 + b22 * a21;\r\n out[8] = b20 * a02 + b21 * a12 + b22 * a22;\r\n return out;\r\n}\r\n/**\r\n * Translate a mat3 by the given vector\r\n *\r\n * @param {mat3} out the receiving matrix\r\n * @param {ReadonlyMat3} a the matrix to translate\r\n * @param {ReadonlyVec2} v vector to translate by\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function translate(out, a, v) {\r\n var a00 = a[0],\r\n a01 = a[1],\r\n a02 = a[2],\r\n a10 = a[3],\r\n a11 = a[4],\r\n a12 = a[5],\r\n a20 = a[6],\r\n a21 = a[7],\r\n a22 = a[8],\r\n x = v[0],\r\n y = v[1];\r\n out[0] = a00;\r\n out[1] = a01;\r\n out[2] = a02;\r\n out[3] = a10;\r\n out[4] = a11;\r\n out[5] = a12;\r\n out[6] = x * a00 + y * a10 + a20;\r\n out[7] = x * a01 + y * a11 + a21;\r\n out[8] = x * a02 + y * a12 + a22;\r\n return out;\r\n}\r\n/**\r\n * Rotates a mat3 by the given angle\r\n *\r\n * @param {mat3} out the receiving matrix\r\n * @param {ReadonlyMat3} a the matrix to rotate\r\n * @param {Number} rad the angle to rotate the matrix by\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function rotate(out, a, rad) {\r\n var a00 = a[0],\r\n a01 = a[1],\r\n a02 = a[2],\r\n a10 = a[3],\r\n a11 = a[4],\r\n a12 = a[5],\r\n a20 = a[6],\r\n a21 = a[7],\r\n a22 = a[8],\r\n s = Math.sin(rad),\r\n c = Math.cos(rad);\r\n out[0] = c * a00 + s * a10;\r\n out[1] = c * a01 + s * a11;\r\n out[2] = c * a02 + s * a12;\r\n out[3] = c * a10 - s * a00;\r\n out[4] = c * a11 - s * a01;\r\n out[5] = c * a12 - s * a02;\r\n out[6] = a20;\r\n out[7] = a21;\r\n out[8] = a22;\r\n return out;\r\n}\r\n/**\r\n * Scales the mat3 by the dimensions in the given vec2\r\n *\r\n * @param {mat3} out the receiving matrix\r\n * @param {ReadonlyMat3} a the matrix to rotate\r\n * @param {ReadonlyVec2} v the vec2 to scale the matrix by\r\n * @returns {mat3} out\r\n **/\r\n\r\nexport function scale(out, a, v) {\r\n var x = v[0],\r\n y = v[1];\r\n out[0] = x * a[0];\r\n out[1] = x * a[1];\r\n out[2] = x * a[2];\r\n out[3] = y * a[3];\r\n out[4] = y * a[4];\r\n out[5] = y * a[5];\r\n out[6] = a[6];\r\n out[7] = a[7];\r\n out[8] = a[8];\r\n return out;\r\n}\r\n/**\r\n * Creates a matrix from a vector translation\r\n * This is equivalent to (but much faster than):\r\n *\r\n * mat3.identity(dest);\r\n * mat3.translate(dest, dest, vec);\r\n *\r\n * @param {mat3} out mat3 receiving operation result\r\n * @param {ReadonlyVec2} v Translation vector\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function fromTranslation(out, v) {\r\n out[0] = 1;\r\n out[1] = 0;\r\n out[2] = 0;\r\n out[3] = 0;\r\n out[4] = 1;\r\n out[5] = 0;\r\n out[6] = v[0];\r\n out[7] = v[1];\r\n out[8] = 1;\r\n return out;\r\n}\r\n/**\r\n * Creates a matrix from a given angle\r\n * This is equivalent to (but much faster than):\r\n *\r\n * mat3.identity(dest);\r\n * mat3.rotate(dest, dest, rad);\r\n *\r\n * @param {mat3} out mat3 receiving operation result\r\n * @param {Number} rad the angle to rotate the matrix by\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function fromRotation(out, rad) {\r\n var s = Math.sin(rad),\r\n c = Math.cos(rad);\r\n out[0] = c;\r\n out[1] = s;\r\n out[2] = 0;\r\n out[3] = -s;\r\n out[4] = c;\r\n out[5] = 0;\r\n out[6] = 0;\r\n out[7] = 0;\r\n out[8] = 1;\r\n return out;\r\n}\r\n/**\r\n * Creates a matrix from a vector scaling\r\n * This is equivalent to (but much faster than):\r\n *\r\n * mat3.identity(dest);\r\n * mat3.scale(dest, dest, vec);\r\n *\r\n * @param {mat3} out mat3 receiving operation result\r\n * @param {ReadonlyVec2} v Scaling vector\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function fromScaling(out, v) {\r\n out[0] = v[0];\r\n out[1] = 0;\r\n out[2] = 0;\r\n out[3] = 0;\r\n out[4] = v[1];\r\n out[5] = 0;\r\n out[6] = 0;\r\n out[7] = 0;\r\n out[8] = 1;\r\n return out;\r\n}\r\n/**\r\n * Copies the values from a mat2d into a mat3\r\n *\r\n * @param {mat3} out the receiving matrix\r\n * @param {ReadonlyMat2d} a the matrix to copy\r\n * @returns {mat3} out\r\n **/\r\n\r\nexport function fromMat2d(out, a) {\r\n out[0] = a[0];\r\n out[1] = a[1];\r\n out[2] = 0;\r\n out[3] = a[2];\r\n out[4] = a[3];\r\n out[5] = 0;\r\n out[6] = a[4];\r\n out[7] = a[5];\r\n out[8] = 1;\r\n return out;\r\n}\r\n/**\r\n * Calculates a 3x3 matrix from the given quaternion\r\n *\r\n * @param {mat3} out mat3 receiving operation result\r\n * @param {ReadonlyQuat} q Quaternion to create matrix from\r\n *\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function fromQuat(out, q) {\r\n var x = q[0],\r\n y = q[1],\r\n z = q[2],\r\n w = q[3];\r\n var x2 = x + x;\r\n var y2 = y + y;\r\n var z2 = z + z;\r\n var xx = x * x2;\r\n var yx = y * x2;\r\n var yy = y * y2;\r\n var zx = z * x2;\r\n var zy = z * y2;\r\n var zz = z * z2;\r\n var wx = w * x2;\r\n var wy = w * y2;\r\n var wz = w * z2;\r\n out[0] = 1 - yy - zz;\r\n out[3] = yx - wz;\r\n out[6] = zx + wy;\r\n out[1] = yx + wz;\r\n out[4] = 1 - xx - zz;\r\n out[7] = zy - wx;\r\n out[2] = zx - wy;\r\n out[5] = zy + wx;\r\n out[8] = 1 - xx - yy;\r\n return out;\r\n}\r\n/**\r\n * Calculates a 3x3 normal matrix (transpose inverse) from the 4x4 matrix\r\n *\r\n * @param {mat3} out mat3 receiving operation result\r\n * @param {ReadonlyMat4} a Mat4 to derive the normal matrix from\r\n *\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function normalFromMat4(out, a) {\r\n var a00 = a[0],\r\n a01 = a[1],\r\n a02 = a[2],\r\n a03 = a[3];\r\n var a10 = a[4],\r\n a11 = a[5],\r\n a12 = a[6],\r\n a13 = a[7];\r\n var a20 = a[8],\r\n a21 = a[9],\r\n a22 = a[10],\r\n a23 = a[11];\r\n var a30 = a[12],\r\n a31 = a[13],\r\n a32 = a[14],\r\n a33 = a[15];\r\n var b00 = a00 * a11 - a01 * a10;\r\n var b01 = a00 * a12 - a02 * a10;\r\n var b02 = a00 * a13 - a03 * a10;\r\n var b03 = a01 * a12 - a02 * a11;\r\n var b04 = a01 * a13 - a03 * a11;\r\n var b05 = a02 * a13 - a03 * a12;\r\n var b06 = a20 * a31 - a21 * a30;\r\n var b07 = a20 * a32 - a22 * a30;\r\n var b08 = a20 * a33 - a23 * a30;\r\n var b09 = a21 * a32 - a22 * a31;\r\n var b10 = a21 * a33 - a23 * a31;\r\n var b11 = a22 * a33 - a23 * a32; // Calculate the determinant\r\n\r\n var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;\r\n\r\n if (!det) {\r\n return null;\r\n }\r\n\r\n det = 1.0 / det;\r\n out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;\r\n out[1] = (a12 * b08 - a10 * b11 - a13 * b07) * det;\r\n out[2] = (a10 * b10 - a11 * b08 + a13 * b06) * det;\r\n out[3] = (a02 * b10 - a01 * b11 - a03 * b09) * det;\r\n out[4] = (a00 * b11 - a02 * b08 + a03 * b07) * det;\r\n out[5] = (a01 * b08 - a00 * b10 - a03 * b06) * det;\r\n out[6] = (a31 * b05 - a32 * b04 + a33 * b03) * det;\r\n out[7] = (a32 * b02 - a30 * b05 - a33 * b01) * det;\r\n out[8] = (a30 * b04 - a31 * b02 + a33 * b00) * det;\r\n return out;\r\n}\r\n/**\r\n * Generates a 2D projection matrix with the given bounds\r\n *\r\n * @param {mat3} out mat3 frustum matrix will be written into\r\n * @param {number} width Width of your gl context\r\n * @param {number} height Height of gl context\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function projection(out, width, height) {\r\n out[0] = 2 / width;\r\n out[1] = 0;\r\n out[2] = 0;\r\n out[3] = 0;\r\n out[4] = -2 / height;\r\n out[5] = 0;\r\n out[6] = -1;\r\n out[7] = 1;\r\n out[8] = 1;\r\n return out;\r\n}\r\n/**\r\n * Returns a string representation of a mat3\r\n *\r\n * @param {ReadonlyMat3} a matrix to represent as a string\r\n * @returns {String} string representation of the matrix\r\n */\r\n\r\nexport function str(a) {\r\n return \"mat3(\" + a[0] + \", \" + a[1] + \", \" + a[2] + \", \" + a[3] + \", \" + a[4] + \", \" + a[5] + \", \" + a[6] + \", \" + a[7] + \", \" + a[8] + \")\";\r\n}\r\n/**\r\n * Returns Frobenius norm of a mat3\r\n *\r\n * @param {ReadonlyMat3} a the matrix to calculate Frobenius norm of\r\n * @returns {Number} Frobenius norm\r\n */\r\n\r\nexport function frob(a) {\r\n return Math.hypot(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]);\r\n}\r\n/**\r\n * Adds two mat3's\r\n *\r\n * @param {mat3} out the receiving matrix\r\n * @param {ReadonlyMat3} a the first operand\r\n * @param {ReadonlyMat3} b the second operand\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function add(out, a, b) {\r\n out[0] = a[0] + b[0];\r\n out[1] = a[1] + b[1];\r\n out[2] = a[2] + b[2];\r\n out[3] = a[3] + b[3];\r\n out[4] = a[4] + b[4];\r\n out[5] = a[5] + b[5];\r\n out[6] = a[6] + b[6];\r\n out[7] = a[7] + b[7];\r\n out[8] = a[8] + b[8];\r\n return out;\r\n}\r\n/**\r\n * Subtracts matrix b from matrix a\r\n *\r\n * @param {mat3} out the receiving matrix\r\n * @param {ReadonlyMat3} a the first operand\r\n * @param {ReadonlyMat3} b the second operand\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function subtract(out, a, b) {\r\n out[0] = a[0] - b[0];\r\n out[1] = a[1] - b[1];\r\n out[2] = a[2] - b[2];\r\n out[3] = a[3] - b[3];\r\n out[4] = a[4] - b[4];\r\n out[5] = a[5] - b[5];\r\n out[6] = a[6] - b[6];\r\n out[7] = a[7] - b[7];\r\n out[8] = a[8] - b[8];\r\n return out;\r\n}\r\n/**\r\n * Multiply each element of the matrix by a scalar.\r\n *\r\n * @param {mat3} out the receiving matrix\r\n * @param {ReadonlyMat3} a the matrix to scale\r\n * @param {Number} b amount to scale the matrix's elements by\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function multiplyScalar(out, a, b) {\r\n out[0] = a[0] * b;\r\n out[1] = a[1] * b;\r\n out[2] = a[2] * b;\r\n out[3] = a[3] * b;\r\n out[4] = a[4] * b;\r\n out[5] = a[5] * b;\r\n out[6] = a[6] * b;\r\n out[7] = a[7] * b;\r\n out[8] = a[8] * b;\r\n return out;\r\n}\r\n/**\r\n * Adds two mat3's after multiplying each element of the second operand by a scalar value.\r\n *\r\n * @param {mat3} out the receiving vector\r\n * @param {ReadonlyMat3} a the first operand\r\n * @param {ReadonlyMat3} b the second operand\r\n * @param {Number} scale the amount to scale b's elements by before adding\r\n * @returns {mat3} out\r\n */\r\n\r\nexport function multiplyScalarAndAdd(out, a, b, scale) {\r\n out[0] = a[0] + b[0] * scale;\r\n out[1] = a[1] + b[1] * scale;\r\n out[2] = a[2] + b[2] * scale;\r\n out[3] = a[3] + b[3] * scale;\r\n out[4] = a[4] + b[4] * scale;\r\n out[5] = a[5] + b[5] * scale;\r\n out[6] = a[6] + b[6] * scale;\r\n out[7] = a[7] + b[7] * scale;\r\n out[8] = a[8] + b[8] * scale;\r\n return out;\r\n}\r\n/**\r\n * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===)\r\n *\r\n * @param {ReadonlyMat3} a The first matrix.\r\n * @param {ReadonlyMat3} b The second matrix.\r\n * @returns {Boolean} True if the matrices are equal, false otherwise.\r\n */\r\n\r\nexport function exactEquals(a, b) {\r\n return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7] && a[8] === b[8];\r\n}\r\n/**\r\n * Returns whether or not the matrices have approximately the same elements in the same position.\r\n *\r\n * @param {ReadonlyMat3} a The first matrix.\r\n * @param {ReadonlyMat3} b The second matrix.\r\n * @returns {Boolean} True if the matrices are equal, false otherwise.\r\n */\r\n\r\nexport function equals(a, b) {\r\n var a0 = a[0],\r\n a1 = a[1],\r\n a2 = a[2],\r\n a3 = a[3],\r\n a4 = a[4],\r\n a5 = a[5],\r\n a6 = a[6],\r\n a7 = a[7],\r\n a8 = a[8];\r\n var b0 = b[0],\r\n b1 = b[1],\r\n b2 = b[2],\r\n b3 = b[3],\r\n b4 = b[4],\r\n b5 = b[5],\r\n b6 = b[6],\r\n b7 = b[7],\r\n b8 = b[8];\r\n return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)) && Math.abs(a6 - b6) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a6), Math.abs(b6)) && Math.abs(a7 - b7) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a7), Math.abs(b7)) && Math.abs(a8 - b8) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a8), Math.abs(b8));\r\n}\r\n/**\r\n * Alias for {@link mat3.multiply}\r\n * @function\r\n */\r\n\r\nexport var mul = multiply;\r\n/**\r\n * Alias for {@link mat3.subtract}\r\n * @function\r\n */\r\n\r\nexport var sub = subtract;\r\n","import * as glMatrix from \"./common.mjs\";\r\n/**\r\n * 3 Dimensional Vector\r\n * @module vec3\r\n */\r\n\r\n/**\r\n * Creates a new, empty vec3\r\n *\r\n * @returns {vec3} a new 3D vector\r\n */\r\n\r\nexport function create() {\r\n var out = new glMatrix.ARRAY_TYPE(3);\r\n\r\n if (glMatrix.ARRAY_TYPE != Float32Array) {\r\n out[0] = 0;\r\n out[1] = 0;\r\n out[2] = 0;\r\n }\r\n\r\n return out;\r\n}\r\n/**\r\n * Creates a new vec3 initialized with values from an existing vector\r\n *\r\n * @param {ReadonlyVec3} a vector to clone\r\n * @returns {vec3} a new 3D vector\r\n */\r\n\r\nexport function clone(a) {\r\n var out = new glMatrix.ARRAY_TYPE(3);\r\n out[0] = a[0];\r\n out[1] = a[1];\r\n out[2] = a[2];\r\n return out;\r\n}\r\n/**\r\n * Calculates the length of a vec3\r\n *\r\n * @param {ReadonlyVec3} a vector to calculate length of\r\n * @returns {Number} length of a\r\n */\r\n\r\nexport function length(a) {\r\n var x = a[0];\r\n var y = a[1];\r\n var z = a[2];\r\n return Math.hypot(x, y, z);\r\n}\r\n/**\r\n * Creates a new vec3 initialized with the given values\r\n *\r\n * @param {Number} x X component\r\n * @param {Number} y Y component\r\n * @param {Number} z Z component\r\n * @returns {vec3} a new 3D vector\r\n */\r\n\r\nexport function fromValues(x, y, z) {\r\n var out = new glMatrix.ARRAY_TYPE(3);\r\n out[0] = x;\r\n out[1] = y;\r\n out[2] = z;\r\n return out;\r\n}\r\n/**\r\n * Copy the values from one vec3 to another\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the source vector\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function copy(out, a) {\r\n out[0] = a[0];\r\n out[1] = a[1];\r\n out[2] = a[2];\r\n return out;\r\n}\r\n/**\r\n * Set the components of a vec3 to the given values\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {Number} x X component\r\n * @param {Number} y Y component\r\n * @param {Number} z Z component\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function set(out, x, y, z) {\r\n out[0] = x;\r\n out[1] = y;\r\n out[2] = z;\r\n return out;\r\n}\r\n/**\r\n * Adds two vec3's\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function add(out, a, b) {\r\n out[0] = a[0] + b[0];\r\n out[1] = a[1] + b[1];\r\n out[2] = a[2] + b[2];\r\n return out;\r\n}\r\n/**\r\n * Subtracts vector b from vector a\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function subtract(out, a, b) {\r\n out[0] = a[0] - b[0];\r\n out[1] = a[1] - b[1];\r\n out[2] = a[2] - b[2];\r\n return out;\r\n}\r\n/**\r\n * Multiplies two vec3's\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function multiply(out, a, b) {\r\n out[0] = a[0] * b[0];\r\n out[1] = a[1] * b[1];\r\n out[2] = a[2] * b[2];\r\n return out;\r\n}\r\n/**\r\n * Divides two vec3's\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function divide(out, a, b) {\r\n out[0] = a[0] / b[0];\r\n out[1] = a[1] / b[1];\r\n out[2] = a[2] / b[2];\r\n return out;\r\n}\r\n/**\r\n * Math.ceil the components of a vec3\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a vector to ceil\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function ceil(out, a) {\r\n out[0] = Math.ceil(a[0]);\r\n out[1] = Math.ceil(a[1]);\r\n out[2] = Math.ceil(a[2]);\r\n return out;\r\n}\r\n/**\r\n * Math.floor the components of a vec3\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a vector to floor\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function floor(out, a) {\r\n out[0] = Math.floor(a[0]);\r\n out[1] = Math.floor(a[1]);\r\n out[2] = Math.floor(a[2]);\r\n return out;\r\n}\r\n/**\r\n * Returns the minimum of two vec3's\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function min(out, a, b) {\r\n out[0] = Math.min(a[0], b[0]);\r\n out[1] = Math.min(a[1], b[1]);\r\n out[2] = Math.min(a[2], b[2]);\r\n return out;\r\n}\r\n/**\r\n * Returns the maximum of two vec3's\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function max(out, a, b) {\r\n out[0] = Math.max(a[0], b[0]);\r\n out[1] = Math.max(a[1], b[1]);\r\n out[2] = Math.max(a[2], b[2]);\r\n return out;\r\n}\r\n/**\r\n * Math.round the components of a vec3\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a vector to round\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function round(out, a) {\r\n out[0] = Math.round(a[0]);\r\n out[1] = Math.round(a[1]);\r\n out[2] = Math.round(a[2]);\r\n return out;\r\n}\r\n/**\r\n * Scales a vec3 by a scalar number\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the vector to scale\r\n * @param {Number} b amount to scale the vector by\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function scale(out, a, b) {\r\n out[0] = a[0] * b;\r\n out[1] = a[1] * b;\r\n out[2] = a[2] * b;\r\n return out;\r\n}\r\n/**\r\n * Adds two vec3's after scaling the second operand by a scalar value\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @param {Number} scale the amount to scale b by before adding\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function scaleAndAdd(out, a, b, scale) {\r\n out[0] = a[0] + b[0] * scale;\r\n out[1] = a[1] + b[1] * scale;\r\n out[2] = a[2] + b[2] * scale;\r\n return out;\r\n}\r\n/**\r\n * Calculates the euclidian distance between two vec3's\r\n *\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @returns {Number} distance between a and b\r\n */\r\n\r\nexport function distance(a, b) {\r\n var x = b[0] - a[0];\r\n var y = b[1] - a[1];\r\n var z = b[2] - a[2];\r\n return Math.hypot(x, y, z);\r\n}\r\n/**\r\n * Calculates the squared euclidian distance between two vec3's\r\n *\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @returns {Number} squared distance between a and b\r\n */\r\n\r\nexport function squaredDistance(a, b) {\r\n var x = b[0] - a[0];\r\n var y = b[1] - a[1];\r\n var z = b[2] - a[2];\r\n return x * x + y * y + z * z;\r\n}\r\n/**\r\n * Calculates the squared length of a vec3\r\n *\r\n * @param {ReadonlyVec3} a vector to calculate squared length of\r\n * @returns {Number} squared length of a\r\n */\r\n\r\nexport function squaredLength(a) {\r\n var x = a[0];\r\n var y = a[1];\r\n var z = a[2];\r\n return x * x + y * y + z * z;\r\n}\r\n/**\r\n * Negates the components of a vec3\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a vector to negate\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function negate(out, a) {\r\n out[0] = -a[0];\r\n out[1] = -a[1];\r\n out[2] = -a[2];\r\n return out;\r\n}\r\n/**\r\n * Returns the inverse of the components of a vec3\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a vector to invert\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function inverse(out, a) {\r\n out[0] = 1.0 / a[0];\r\n out[1] = 1.0 / a[1];\r\n out[2] = 1.0 / a[2];\r\n return out;\r\n}\r\n/**\r\n * Normalize a vec3\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a vector to normalize\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function normalize(out, a) {\r\n var x = a[0];\r\n var y = a[1];\r\n var z = a[2];\r\n var len = x * x + y * y + z * z;\r\n\r\n if (len > 0) {\r\n //TODO: evaluate use of glm_invsqrt here?\r\n len = 1 / Math.sqrt(len);\r\n }\r\n\r\n out[0] = a[0] * len;\r\n out[1] = a[1] * len;\r\n out[2] = a[2] * len;\r\n return out;\r\n}\r\n/**\r\n * Calculates the dot product of two vec3's\r\n *\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @returns {Number} dot product of a and b\r\n */\r\n\r\nexport function dot(a, b) {\r\n return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];\r\n}\r\n/**\r\n * Computes the cross product of two vec3's\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function cross(out, a, b) {\r\n var ax = a[0],\r\n ay = a[1],\r\n az = a[2];\r\n var bx = b[0],\r\n by = b[1],\r\n bz = b[2];\r\n out[0] = ay * bz - az * by;\r\n out[1] = az * bx - ax * bz;\r\n out[2] = ax * by - ay * bx;\r\n return out;\r\n}\r\n/**\r\n * Performs a linear interpolation between two vec3's\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @param {Number} t interpolation amount, in the range [0-1], between the two inputs\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function lerp(out, a, b, t) {\r\n var ax = a[0];\r\n var ay = a[1];\r\n var az = a[2];\r\n out[0] = ax + t * (b[0] - ax);\r\n out[1] = ay + t * (b[1] - ay);\r\n out[2] = az + t * (b[2] - az);\r\n return out;\r\n}\r\n/**\r\n * Performs a spherical linear interpolation between two vec3's\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @param {Number} t interpolation amount, in the range [0-1], between the two inputs\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function slerp(out, a, b, t) {\r\n var angle = Math.acos(Math.min(Math.max(dot(a, b), -1), 1));\r\n var sinTotal = Math.sin(angle);\r\n var ratioA = Math.sin((1 - t) * angle) / sinTotal;\r\n var ratioB = Math.sin(t * angle) / sinTotal;\r\n out[0] = ratioA * a[0] + ratioB * b[0];\r\n out[1] = ratioA * a[1] + ratioB * b[1];\r\n out[2] = ratioA * a[2] + ratioB * b[2];\r\n return out;\r\n}\r\n/**\r\n * Performs a hermite interpolation with two control points\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @param {ReadonlyVec3} c the third operand\r\n * @param {ReadonlyVec3} d the fourth operand\r\n * @param {Number} t interpolation amount, in the range [0-1], between the two inputs\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function hermite(out, a, b, c, d, t) {\r\n var factorTimes2 = t * t;\r\n var factor1 = factorTimes2 * (2 * t - 3) + 1;\r\n var factor2 = factorTimes2 * (t - 2) + t;\r\n var factor3 = factorTimes2 * (t - 1);\r\n var factor4 = factorTimes2 * (3 - 2 * t);\r\n out[0] = a[0] * factor1 + b[0] * factor2 + c[0] * factor3 + d[0] * factor4;\r\n out[1] = a[1] * factor1 + b[1] * factor2 + c[1] * factor3 + d[1] * factor4;\r\n out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4;\r\n return out;\r\n}\r\n/**\r\n * Performs a bezier interpolation with two control points\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the first operand\r\n * @param {ReadonlyVec3} b the second operand\r\n * @param {ReadonlyVec3} c the third operand\r\n * @param {ReadonlyVec3} d the fourth operand\r\n * @param {Number} t interpolation amount, in the range [0-1], between the two inputs\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function bezier(out, a, b, c, d, t) {\r\n var inverseFactor = 1 - t;\r\n var inverseFactorTimesTwo = inverseFactor * inverseFactor;\r\n var factorTimes2 = t * t;\r\n var factor1 = inverseFactorTimesTwo * inverseFactor;\r\n var factor2 = 3 * t * inverseFactorTimesTwo;\r\n var factor3 = 3 * factorTimes2 * inverseFactor;\r\n var factor4 = factorTimes2 * t;\r\n out[0] = a[0] * factor1 + b[0] * factor2 + c[0] * factor3 + d[0] * factor4;\r\n out[1] = a[1] * factor1 + b[1] * factor2 + c[1] * factor3 + d[1] * factor4;\r\n out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4;\r\n return out;\r\n}\r\n/**\r\n * Generates a random vector with the given scale\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {Number} [scale] Length of the resulting vector. If ommitted, a unit vector will be returned\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function random(out, scale) {\r\n scale = scale || 1.0;\r\n var r = glMatrix.RANDOM() * 2.0 * Math.PI;\r\n var z = glMatrix.RANDOM() * 2.0 - 1.0;\r\n var zScale = Math.sqrt(1.0 - z * z) * scale;\r\n out[0] = Math.cos(r) * zScale;\r\n out[1] = Math.sin(r) * zScale;\r\n out[2] = z * scale;\r\n return out;\r\n}\r\n/**\r\n * Transforms the vec3 with a mat4.\r\n * 4th vector component is implicitly '1'\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the vector to transform\r\n * @param {ReadonlyMat4} m matrix to transform with\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function transformMat4(out, a, m) {\r\n var x = a[0],\r\n y = a[1],\r\n z = a[2];\r\n var w = m[3] * x + m[7] * y + m[11] * z + m[15];\r\n w = w || 1.0;\r\n out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w;\r\n out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w;\r\n out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w;\r\n return out;\r\n}\r\n/**\r\n * Transforms the vec3 with a mat3.\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the vector to transform\r\n * @param {ReadonlyMat3} m the 3x3 matrix to transform with\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function transformMat3(out, a, m) {\r\n var x = a[0],\r\n y = a[1],\r\n z = a[2];\r\n out[0] = x * m[0] + y * m[3] + z * m[6];\r\n out[1] = x * m[1] + y * m[4] + z * m[7];\r\n out[2] = x * m[2] + y * m[5] + z * m[8];\r\n return out;\r\n}\r\n/**\r\n * Transforms the vec3 with a quat\r\n * Can also be used for dual quaternions. (Multiply it with the real part)\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @param {ReadonlyVec3} a the vector to transform\r\n * @param {ReadonlyQuat} q quaternion to transform with\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function transformQuat(out, a, q) {\r\n // benchmarks: https://jsperf.com/quaternion-transform-vec3-implementations-fixed\r\n var qx = q[0],\r\n qy = q[1],\r\n qz = q[2],\r\n qw = q[3];\r\n var x = a[0],\r\n y = a[1],\r\n z = a[2]; // var qvec = [qx, qy, qz];\r\n // var uv = vec3.cross([], qvec, a);\r\n\r\n var uvx = qy * z - qz * y,\r\n uvy = qz * x - qx * z,\r\n uvz = qx * y - qy * x; // var uuv = vec3.cross([], qvec, uv);\r\n\r\n var uuvx = qy * uvz - qz * uvy,\r\n uuvy = qz * uvx - qx * uvz,\r\n uuvz = qx * uvy - qy * uvx; // vec3.scale(uv, uv, 2 * w);\r\n\r\n var w2 = qw * 2;\r\n uvx *= w2;\r\n uvy *= w2;\r\n uvz *= w2; // vec3.scale(uuv, uuv, 2);\r\n\r\n uuvx *= 2;\r\n uuvy *= 2;\r\n uuvz *= 2; // return vec3.add(out, a, vec3.add(out, uv, uuv));\r\n\r\n out[0] = x + uvx + uuvx;\r\n out[1] = y + uvy + uuvy;\r\n out[2] = z + uvz + uuvz;\r\n return out;\r\n}\r\n/**\r\n * Rotate a 3D vector around the x-axis\r\n * @param {vec3} out The receiving vec3\r\n * @param {ReadonlyVec3} a The vec3 point to rotate\r\n * @param {ReadonlyVec3} b The origin of the rotation\r\n * @param {Number} rad The angle of rotation in radians\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function rotateX(out, a, b, rad) {\r\n var p = [],\r\n r = []; //Translate point to the origin\r\n\r\n p[0] = a[0] - b[0];\r\n p[1] = a[1] - b[1];\r\n p[2] = a[2] - b[2]; //perform rotation\r\n\r\n r[0] = p[0];\r\n r[1] = p[1] * Math.cos(rad) - p[2] * Math.sin(rad);\r\n r[2] = p[1] * Math.sin(rad) + p[2] * Math.cos(rad); //translate to correct position\r\n\r\n out[0] = r[0] + b[0];\r\n out[1] = r[1] + b[1];\r\n out[2] = r[2] + b[2];\r\n return out;\r\n}\r\n/**\r\n * Rotate a 3D vector around the y-axis\r\n * @param {vec3} out The receiving vec3\r\n * @param {ReadonlyVec3} a The vec3 point to rotate\r\n * @param {ReadonlyVec3} b The origin of the rotation\r\n * @param {Number} rad The angle of rotation in radians\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function rotateY(out, a, b, rad) {\r\n var p = [],\r\n r = []; //Translate point to the origin\r\n\r\n p[0] = a[0] - b[0];\r\n p[1] = a[1] - b[1];\r\n p[2] = a[2] - b[2]; //perform rotation\r\n\r\n r[0] = p[2] * Math.sin(rad) + p[0] * Math.cos(rad);\r\n r[1] = p[1];\r\n r[2] = p[2] * Math.cos(rad) - p[0] * Math.sin(rad); //translate to correct position\r\n\r\n out[0] = r[0] + b[0];\r\n out[1] = r[1] + b[1];\r\n out[2] = r[2] + b[2];\r\n return out;\r\n}\r\n/**\r\n * Rotate a 3D vector around the z-axis\r\n * @param {vec3} out The receiving vec3\r\n * @param {ReadonlyVec3} a The vec3 point to rotate\r\n * @param {ReadonlyVec3} b The origin of the rotation\r\n * @param {Number} rad The angle of rotation in radians\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function rotateZ(out, a, b, rad) {\r\n var p = [],\r\n r = []; //Translate point to the origin\r\n\r\n p[0] = a[0] - b[0];\r\n p[1] = a[1] - b[1];\r\n p[2] = a[2] - b[2]; //perform rotation\r\n\r\n r[0] = p[0] * Math.cos(rad) - p[1] * Math.sin(rad);\r\n r[1] = p[0] * Math.sin(rad) + p[1] * Math.cos(rad);\r\n r[2] = p[2]; //translate to correct position\r\n\r\n out[0] = r[0] + b[0];\r\n out[1] = r[1] + b[1];\r\n out[2] = r[2] + b[2];\r\n return out;\r\n}\r\n/**\r\n * Get the angle between two 3D vectors\r\n * @param {ReadonlyVec3} a The first operand\r\n * @param {ReadonlyVec3} b The second operand\r\n * @returns {Number} The angle in radians\r\n */\r\n\r\nexport function angle(a, b) {\r\n var ax = a[0],\r\n ay = a[1],\r\n az = a[2],\r\n bx = b[0],\r\n by = b[1],\r\n bz = b[2],\r\n mag1 = Math.sqrt(ax * ax + ay * ay + az * az),\r\n mag2 = Math.sqrt(bx * bx + by * by + bz * bz),\r\n mag = mag1 * mag2,\r\n cosine = mag && dot(a, b) / mag;\r\n return Math.acos(Math.min(Math.max(cosine, -1), 1));\r\n}\r\n/**\r\n * Set the components of a vec3 to zero\r\n *\r\n * @param {vec3} out the receiving vector\r\n * @returns {vec3} out\r\n */\r\n\r\nexport function zero(out) {\r\n out[0] = 0.0;\r\n out[1] = 0.0;\r\n out[2] = 0.0;\r\n return out;\r\n}\r\n/**\r\n * Returns a string representation of a vector\r\n *\r\n * @param {ReadonlyVec3} a vector to represent as a string\r\n * @returns {String} string representation of the vector\r\n */\r\n\r\nexport function str(a) {\r\n return \"vec3(\" + a[0] + \", \" + a[1] + \", \" + a[2] + \")\";\r\n}\r\n/**\r\n * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)\r\n *\r\n * @param {ReadonlyVec3} a The first vector.\r\n * @param {ReadonlyVec3} b The second vector.\r\n * @returns {Boolean} True if the vectors are equal, false otherwise.\r\n */\r\n\r\nexport function exactEquals(a, b) {\r\n return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];\r\n}\r\n/**\r\n * Returns whether or not the vectors have approximately the same elements in the same position.\r\n *\r\n * @param {ReadonlyVec3} a The first vector.\r\n * @param {ReadonlyVec3} b The second vector.\r\n * @returns {Boolean} True if the vectors are equal, false otherwise.\r\n */\r\n\r\nexport function equals(a, b) {\r\n var a0 = a[0],\r\n a1 = a[1],\r\n a2 = a[2];\r\n var b0 = b[0],\r\n b1 = b[1],\r\n b2 = b[2];\r\n return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2));\r\n}\r\n/**\r\n * Alias for {@link vec3.subtract}\r\n * @function\r\n */\r\n\r\nexport var sub = subtract;\r\n/**\r\n * Alias for {@link vec3.multiply}\r\n * @function\r\n */\r\n\r\nexport var mul = multiply;\r\n/**\r\n * Alias for {@link vec3.divide}\r\n * @function\r\n */\r\n\r\nexport var div = divide;\r\n/**\r\n * Alias for {@link vec3.distance}\r\n * @function\r\n */\r\n\r\nexport var dist = distance;\r\n/**\r\n * Alias for {@link vec3.squaredDistance}\r\n * @function\r\n */\r\n\r\nexport var sqrDist = squaredDistance;\r\n/**\r\n * Alias for {@link vec3.length}\r\n * @function\r\n */\r\n\r\nexport var len = length;\r\n/**\r\n * Alias for {@link vec3.squaredLength}\r\n * @function\r\n */\r\n\r\nexport var sqrLen = squaredLength;\r\n/**\r\n * Perform some operation over an array of vec3s.\r\n *\r\n * @param {Array} a the array of vectors to iterate over\r\n * @param {Number} stride Number of elements between the start of each vec3. If 0 assumes tightly packed\r\n * @param {Number} offset Number of elements to skip at the beginning of the array\r\n * @param {Number} count Number of vec3s to iterate over. If 0 iterates over entire array\r\n * @param {Function} fn Function to call for each vector in the array\r\n * @param {Object} [arg] additional argument to pass to fn\r\n * @returns {Array} a\r\n * @function\r\n */\r\n\r\nexport var forEach = function () {\r\n var vec = create();\r\n return function (a, stride, offset, count, fn, arg) {\r\n var i, l;\r\n\r\n if (!stride) {\r\n stride = 3;\r\n }\r\n\r\n if (!offset) {\r\n offset = 0;\r\n }\r\n\r\n if (count) {\r\n l = Math.min(count * stride + offset, a.length);\r\n } else {\r\n l = a.length;\r\n }\r\n\r\n for (i = offset; i < l; i += stride) {\r\n vec[0] = a[i];\r\n vec[1] = a[i + 1];\r\n vec[2] = a[i + 2];\r\n fn(vec, vec, arg);\r\n a[i] = vec[0];\r\n a[i + 1] = vec[1];\r\n a[i + 2] = vec[2];\r\n }\r\n\r\n return a;\r\n };\r\n}();\r\n","// @namespace dom\r\n\r\n// Pretty much ripped off Leaflet's `L.DomEvent.getMousePosition`\r\n\r\n// @function getMousePosition(ev: MouseEvent, element?: HTMLElement): Array of Number\r\n// Gets normalized mouse position from a DOM mouse/pointer event relative to the\r\n// `element` (border excluded) or to the whole page if not specified.\r\n//\r\n// The return value is an array of the form `[x, y]`, in CSS pixels\r\nexport function getMousePosition(ev, element) {\r\n\tif (!element) {\r\n\t\treturn [ev.clientX, ev.clientY];\r\n\t}\r\n\r\n\tvar scale = getScale(element),\r\n\t\toffset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)\r\n\r\n\treturn [\r\n\t\t// offset.left/top values are in page scale (like clientX/Y),\r\n\t\t// whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).\r\n\t\t(ev.clientX - offset.left) / scale.x - element.clientLeft,\r\n\t\t(ev.clientY - offset.top) / scale.y - element.clientTop,\r\n\t];\r\n}\r\n\r\n// Pretty much ripped off Leaflet's `L.DomUtil.getScale`\r\n\r\n// @function getScale(el: HTMLElement): Object\r\n// Computes the CSS scale currently applied on the element.\r\n// Returns an object with `x` and `y` members as horizontal and vertical scales respectively,\r\n// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).\r\nexport function getScale(element) {\r\n\tvar rect = element.getBoundingClientRect(); // Read-only in old browsers.\r\n\r\n\treturn {\r\n\t\tx: rect.width / element.offsetWidth || 1,\r\n\t\ty: rect.height / element.offsetHeight || 1,\r\n\t\tboundingClientRect: rect,\r\n\t};\r\n}\r\n","// ES6-style class mixin (see\r\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#mix-ins )\r\n// for both `GleoMouseEvent` and `GleoPointerEvent`\r\n\r\n/**\r\n * @class GleoEvent\r\n *\r\n * [Class mix-in](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#mix-ins)\r\n * for functionality common to `GleoPointerEvent` and `GleoMouseEvent`.\r\n */\r\n\r\nexport default function GleofyEventClass(Base) {\r\n\treturn class GleoEvent extends Base {\r\n\t\tconstructor(type, init) {\r\n\t\t\tsuper(type, init);\r\n\r\n\t\t\t/**\r\n\t\t\t * @property geometry: Geometry\r\n\t\t\t * A point `Geometry`, containing the coordinates in map's CRS where the\r\n\t\t\t * event took place.\r\n\t\t\t * This is akin to Leaflet's `latlng` event property.\r\n\t\t\t *\r\n\t\t\t * @property canvasX: Number\r\n\t\t\t * Akin to `clientX`, but relative to the map's `` element.\r\n\t\t\t *\r\n\t\t\t * @property canvasY: Number\r\n\t\t\t * Akin to `clientY`, but relative to the map's `` element.\r\n\t\t\t */\r\n\t\t\tthis.geometry = init.geometry;\r\n\t\t\tthis.canvasX = init.canvasX;\r\n\t\t\tthis.canvasY = init.canvasY;\r\n\r\n\t\t\t/**\r\n\t\t\t * @property colour: Array of Number\r\n\t\t\t * For `Acetate`s set as \"queryable\" (and `GleoSymbol`s being\r\n\t\t\t * drawn in such acetates), this contains the colour of the pixel\r\n\t\t\t * the event took place over, as a 4-element array of the form\r\n\t\t\t * `[r, g, b, a]`, with values between 0 and 255.\r\n\t\t\t * @alternative\r\n\t\t\t * @property colour: undefined\r\n\t\t\t * For `Acetate`s **not** set as \"queryable\" (and `GleoSymbol`s\r\n\t\t\t * being drawn in such acetates), and for event that happened outside\r\n\t\t\t * of the map (e.g. `pointerout` events), this value will always\r\n\t\t\t * be `undefined`)\r\n\t\t\t */\r\n\t\t\tthis.colour = init.colour;\r\n\r\n\t\t\tthis._canPropagate = true;\r\n\t\t}\r\n\r\n\t\tstopPropagation() {\r\n\t\t\tthis._canPropagate = false;\r\n\t\t\treturn super.stopPropagation();\r\n\t\t}\r\n\t};\r\n}\r\n","import GleofyEventClass from \"./GleoEventMixin.mjs\";\r\n\r\n/**\r\n *\r\n * @class GleoMouseEvent\r\n * @inherits MouseEvent\r\n * @inherits GleoEvent\r\n *\r\n * Akin to `GleoPointerEvent`, but only used for `click` events (since `click` events are\r\n * instances of `MouseEvent`, not `PointerEvent`).\r\n *\r\n * Note that Gleo **does not handle** any other `MouseEvent`s. Trying to do something like\r\n * `map.on('mousedown', fn)` will do nothing.\r\n *\r\n * @example\r\n *\r\n * ```js\r\n * map.on('click', function(ev) {\r\n * \tconsole.log(ev.geom);\r\n * });\r\n * ```\r\n *\r\n */\r\n\r\nexport default class GleoMouseEvent extends GleofyEventClass(MouseEvent) {}\r\n","import GleofyEventClass from \"./GleoEventMixin.mjs\";\r\n\r\n/**\r\n *\r\n * @class GleoPointerEvent\r\n * @inherits PointerEvent\r\n * @inherits GleoEvent\r\n *\r\n * Gleo maps handle pointer events - such as `pointerdown`. All pointer\r\n * events fired by a map instance are not instances of `PointerEvent`, but of\r\n * `GleoPointerEvent`.\r\n *\r\n * This means that a map's pointer events have extra properties - most notably\r\n * a geometry with the CRS coordinates where the pointer event happened.\r\n *\r\n * For the `click` event, see `GleoMouseEvent`.\r\n *\r\n * @example\r\n *\r\n * ```js\r\n * map.on('pointerdown', function(ev) {\r\n * \tconsole.log(ev.geom);\r\n * });\r\n * ```\r\n */\r\n\r\nexport default class GleoPointerEvent extends GleofyEventClass(PointerEvent) {}\r\n","/**\r\n * @class ExpandBox\r\n *\r\n * Minimalistic, simplistic, 2-dimensional, *expanding* bounding box implementation.\r\n *\r\n * This class is needed only when there's a need to calculate a bbox that covers\r\n * a given set of points. Bounding boxes that can be trivially calculated are best\r\n * handled manually as 4-element arrays.\r\n *\r\n * This implementation also assumes that the bounds are parallel to the CRS's axes,\r\n * and is not suitable for boundign boxes whenever there's a yaw rotation involved.\r\n */\r\n\r\nexport default class ExpandBox {\r\n\tconstructor() {\r\n\t\tthis.reset();\r\n\t}\r\n\r\n\t/**\r\n\t * @method reset(): this\r\n\t * Resets all properties to their `Infinity`/`-Infinity` default values.\r\n\t */\r\n\treset() {\r\n\t\t/**\r\n\t\t * @property minX: Number = Infinity\r\n\t\t * @property maxX: Number = -Infinity\r\n\t\t * @property minY: Number = Infinity\r\n\t\t * @property maxY: Number = -Infinity\r\n\t\t */\r\n\t\tthis.minX = this.minY = Infinity;\r\n\t\tthis.maxX = this.maxY = -Infinity;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method clone(): ExpandBox\r\n\t * Returns a cloned copy of this box.\r\n\t */\r\n\tclone() {\r\n\t\tconst newBox = new ExpandBox();\r\n\t\tnewBox.minX = this.minX;\r\n\t\tnewBox.minY = this.minY;\r\n\t\tnewBox.maxX = this.maxX;\r\n\t\tnewBox.maxY = this.maxY;\r\n\t\treturn newBox;\r\n\t}\r\n\r\n\t/**\r\n\t * @method expandPair(xy: Array of Number): this\r\n\t *\r\n\t * Expands the bounding box to cover the given coordinate pair. The coordinate\r\n\t * pair is expected to have the form `[x, y]`.\r\n\t */\r\n\texpandPair([x, y]) {\r\n\t\tthis.minX = Math.min(this.minX, x);\r\n\t\tthis.maxX = Math.max(this.maxX, x);\r\n\t\tthis.minY = Math.min(this.minY, y);\r\n\t\tthis.maxY = Math.max(this.maxY, y);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method expandXY(x: Number, y: Number): this\r\n\t *\r\n\t * Expands the bounding box to cover the given coordinate pair.\r\n\t */\r\n\texpandXY(x, y) {\r\n\t\tthis.minX = Math.min(this.minX, x);\r\n\t\tthis.maxX = Math.max(this.maxX, x);\r\n\t\tthis.minY = Math.min(this.minY, y);\r\n\t\tthis.maxY = Math.max(this.maxY, y);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method expandGeometry(geom: RawGeometry): this\r\n\t * Expands the bounding box to cover all points of the given `Geometry`.\r\n\t * Note that no reprojection is performed, and that an `ExpandBox` is\r\n\t * CRS-agnostic.\r\n\t */\r\n\texpandGeometry(geom) {\r\n\t\tconst coords = geom.coords;\r\n\t\tfor (let i = 0, l = coords.length; i < l; i += 2) {\r\n\t\t\tthis.expandXY(coords[i], coords[i + 1]);\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method expandPercentage(p: Number): this\r\n\t *\r\n\t * Expand the bounding box by the given percentage **on four sides**.\r\n\t *\r\n\t * e.g. a value of `0.1` will raise the top by 10%, lower\r\n\t * the bottom by 10% (idem for left & right), increasing the height\r\n\t * by 20% (idem for width).\r\n\t */\r\n\texpandPercentage(p) {\r\n\t\tconst h = this.maxX - this.minX;\r\n\t\tconst w = this.maxY - this.minY;\r\n\t\tif (isFinite(w)) {\r\n\t\t\tthis.minX -= w * p;\r\n\t\t\tthis.maxX += w * p;\r\n\t\t}\r\n\t\tif (isFinite(h)) {\r\n\t\t\tthis.minY -= h * p;\r\n\t\t\tthis.maxY += h * p;\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method expandPercentages(px: Number, py: Number): this\r\n\t *\r\n\t * Expand the bounding box by the given percentages, to the left and right by\r\n\t * `px`, and to the top and bottom by `py`\r\n\t */\r\n\texpandPercentages(px, py) {\r\n\t\tconst h = this.maxX - this.minX;\r\n\t\tconst w = this.maxY - this.minY;\r\n\t\tif (isFinite(w)) {\r\n\t\t\tthis.minX -= w * px;\r\n\t\t\tthis.maxX += w * px;\r\n\t\t}\r\n\t\tif (isFinite(h)) {\r\n\t\t\tthis.minY -= h * py;\r\n\t\t\tthis.maxY += h * py;\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method intersectsBox(b: ExpandBox): Boolean\r\n\t * Returns `true` if the given `ExpandBox` has at least one point in\r\n\t * common.\r\n\t */\r\n\tintersectsBox(b) {\r\n\t\treturn (\r\n\t\t\tb.maxX > this.minX &&\r\n\t\t\tb.minX < this.maxX &&\r\n\t\t\tb.maxY > this.minY &&\r\n\t\t\tb.minY < this.maxY\r\n\t\t);\r\n\t}\r\n\r\n\t/**\r\n\t * @method containsBox(b: ExpandBox): Boolean\r\n\t * Returns `true` if the given `ExpandBox` completely fits.\r\n\t */\r\n\tcontainsBox(b) {\r\n\t\treturn (\r\n\t\t\tb.maxX < this.maxX &&\r\n\t\t\tb.minX > this.minX &&\r\n\t\t\tb.maxY < this.maxY &&\r\n\t\t\tb.minY > this.minY\r\n\t\t);\r\n\t}\r\n}\r\n","import RawGeometry from \"./RawGeometry.mjs\";\r\n\r\nconst nullGeometry = new RawGeometry({ name: \"null\" }, [], [], [], { wrap: false });\r\n\r\n/**\r\n * @namespace DefaultGeometry\r\n * @relationship associated Geometry\r\n *\r\n * This module contains facilities for easing the task of defining geometries\r\n * when a well-known CRS is used for all input coordinates.\r\n *\r\n * Note that this functionality is **global to all maps** since it must work for\r\n * stuff which does not belong to any map, or belongs to more than one map at\r\n * the same time.\r\n *\r\n * @example\r\n * ```\r\n * import { setFactory, factory } from `gleo/src/coords/DefaultGeometry.mjs`;\r\n *\r\n * setFactory(function(coords, opts) {\r\n * \treturn new Geometry( myFavouriteCRS, coords, opts);\r\n * }\r\n *\r\n * map.center = [100, -500]; // These stand-alone coordinates will internally\r\n * // be converted into a `Geometry` of `myFavouriteCRS`\r\n * ```\r\n *\r\n * Calling `setFactory` more than once (either manually or by importing `MercatorMap`)\r\n * is highly discouraged.\r\n */\r\n\r\nlet currentFactory = function uninitializedDefaultGeometry() {\r\n\tthrow new Error(\r\n\t\t`A way to spawn geometries without an explicit CRS has not been defined`\r\n\t);\r\n};\r\n\r\n/**\r\n * @function setFactory(fn: Function): undefined\r\n * Sets the factory function for transforming stand-alone coordinates into\r\n * geometries with a specific CRS.\r\n *\r\n * The function must take a single `coordinates` argument, and return a `Geometry`\r\n * instance.\r\n */\r\nexport function setFactory(fn) {\r\n\tcurrentFactory = fn;\r\n}\r\n\r\n/**\r\n * @function factory(coords: Array of Number, opts?: Geometry Options): RawGeometry\r\n * The factory function that takes coordinates and returns `Geometry`s.\r\n *\r\n * All Gleo functionality that can take geometries as input **must** pass it\r\n * through this factory function.\r\n *\r\n * In some cases, geometry constructor options (such as `deduplicate` or `wrap`)\r\n * will be passed. The factory function should honour these.\r\n * @alternative\r\n * @function factory(geom: RawGeometry, opts?: Geometry Options): RawGeometry\r\n * When the factory function receives a `Geometry`, it is returned as is.\r\n *\r\n * In this way, all Gleo functionality that uses this factory will be able to\r\n * take as input either stand-alone coordinates, or fully-defined `Geometry`s,\r\n * in a transparent way.\r\n *\r\n * This check is performed by the `DefaultGeometry` module; users do **not** need\r\n * to check whether the input is a `RawGeometry` instance when using `setFactory()`.\r\n * @alternative\r\n * @function factory(geom: undefined): RawGeometry\r\n * A \"null\" geometry, containing zero vertices, will be returned.\r\n */\r\nexport function factory(coords, opts) {\r\n\tif (coords instanceof RawGeometry) {\r\n\t\treturn coords;\r\n\t}\r\n\tif (coords === undefined) {\r\n\t\treturn nullGeometry;\r\n\t}\r\n\treturn currentFactory(coords, opts);\r\n}\r\n","// © Dean McNamee , 2012.\r\n// © Iván Sánchez Ortega 255 ? 255 : i;\r\n}\r\n\r\nfunction clamp_css_float(f) {\r\n\t// Clamp to float 0.0 .. 1.0.\r\n\treturn f < 0 ? 0 : f > 1 ? 1 : f;\r\n}\r\n\r\nfunction parse_css_int(str) {\r\n\t// int or percentage.\r\n\tif (str[str.length - 1] === \"%\") return clamp_css_byte((parseFloat(str) / 100) * 255);\r\n\treturn clamp_css_byte(parseInt(str));\r\n}\r\n\r\nfunction parse_css_float(str) {\r\n\t// float or percentage.\r\n\tif (str[str.length - 1] === \"%\") return clamp_css_float(parseFloat(str) / 100);\r\n\treturn clamp_css_float(parseFloat(str));\r\n}\r\n\r\nfunction css_hue_to_rgb(m1, m2, h) {\r\n\tif (h < 0) h += 1;\r\n\telse if (h > 1) h -= 1;\r\n\r\n\tif (h * 6 < 1) return m1 + (m2 - m1) * h * 6;\r\n\tif (h * 2 < 1) return m2;\r\n\tif (h * 3 < 2) return m1 + (m2 - m1) * (2 / 3 - h) * 6;\r\n\treturn m1;\r\n}\r\n\r\nexport default function parseCSSColor(css_str) {\r\n\tif (\r\n\t\tcss_str instanceof Array ||\r\n\t\tcss_str instanceof Uint8Array ||\r\n\t\tcss_str instanceof Uint8ClampedArray\r\n\t) {\r\n\t\tif (css_str.length === 4) {\r\n\t\t\treturn css_str;\r\n\t\t} else if (css_str.length === 3) {\r\n\t\t\treturn [...css_str, 255];\r\n\t\t} else {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\r\n\t// Remove all whitespace, not compliant, but should just be more accepting.\r\n\tvar str = css_str.replace(/ /g, \"\").toLowerCase();\r\n\r\n\t// Color keywords (and transparent) lookup.\r\n\tif (str in kCSSColorTable) return kCSSColorTable[str].slice(); // dup.\r\n\r\n\t// #abc and #abc123 syntax.\r\n\tif (str[0] === \"#\") {\r\n\t\tvar iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing.\r\n\t\tif (str.length === 4) {\r\n\t\t\t// #rgb\r\n\t\t\tif (!(iv >= 0 && iv <= 0xfff)) return null; // Covers NaN.\r\n\t\t\treturn [\r\n\t\t\t\t((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8),\r\n\t\t\t\t(iv & 0x0f0) | ((iv & 0x0f0) >> 4),\r\n\t\t\t\t((iv & 0x00f) << 4) | (iv & 0x00f),\r\n\t\t\t\t255,\r\n\t\t\t];\r\n\t\t} else if (str.length === 7) {\r\n\t\t\t// #rrggbb\r\n\t\t\tif (!(iv >= 0 && iv <= 0xffffff)) return null; // Covers NaN.\r\n\t\t\treturn [(iv & 0xff0000) >> 16, (iv & 0x00ff00) >> 8, iv & 0x0000ff, 255];\r\n\t\t} else if (str.length === 5) {\r\n\t\t\t// #rgba\r\n\t\t\tif (!(iv >= 0 && iv <= 0xffff)) return null; // Covers NaN.\r\n\t\t\treturn [\r\n\t\t\t\t((iv & 0xf000) >> 8) | ((iv & 0xf000) >> 12),\r\n\t\t\t\t((iv & 0x0f00) >> 4) | ((iv & 0x0f00) >> 8),\r\n\t\t\t\t(iv & 0x00f0) | ((iv & 0x00f0) >> 4),\r\n\t\t\t\t((iv & 0x000f) << 4) | (iv & 0x000f),\r\n\t\t\t];\r\n\t\t} else if (str.length === 9) {\r\n\t\t\t// #rrggbbaa\r\n\t\t\tif (!(iv >= 0 && iv <= 0xffffffff)) return null; // Covers NaN.\r\n\t\t\treturn [\r\n\t\t\t\t((iv & 0xff000000) >> 24) & 0xff,\r\n\t\t\t\t(iv & 0x00ff0000) >> 16,\r\n\t\t\t\t(iv & 0x0000ff00) >> 8,\r\n\t\t\t\tiv & 0x000000ff,\r\n\t\t\t];\r\n\t\t}\r\n\r\n\t\treturn null;\r\n\t}\r\n\r\n\tvar op = str.indexOf(\"(\"),\r\n\t\tep = str.indexOf(\")\");\r\n\tif (op !== -1 && ep + 1 === str.length) {\r\n\t\tvar fname = str.substr(0, op);\r\n\t\tvar params = str.substr(op + 1, ep - (op + 1)).split(\",\");\r\n\t\tvar alpha = 255; // To allow case fallthrough.\r\n\t\tswitch (fname) {\r\n\t\t\tcase \"rgba\":\r\n\t\t\t\tif (params.length !== 4) return null;\r\n\t\t\t\talpha = parse_css_int(params.pop());\r\n\t\t\t// Fall through.\r\n\t\t\tcase \"rgb\":\r\n\t\t\t\tif (params.length !== 3) return null;\r\n\t\t\t\treturn [\r\n\t\t\t\t\tparse_css_int(params[0]),\r\n\t\t\t\t\tparse_css_int(params[1]),\r\n\t\t\t\t\tparse_css_int(params[2]),\r\n\t\t\t\t\talpha,\r\n\t\t\t\t];\r\n\t\t\tcase \"hsla\":\r\n\t\t\t\tif (params.length !== 4) return null;\r\n\t\t\t\talpha = Math.round(parse_css_float(params.pop()) * 255);\r\n\t\t\t// Fall through.\r\n\t\t\tcase \"hsl\":\r\n\t\t\t\tif (params.length !== 3) return null;\r\n\t\t\t\tvar h = (((parseFloat(params[0]) % 360) + 360) % 360) / 360; // 0 .. 1\r\n\t\t\t\t// NOTE(deanm): According to the CSS spec s/l should only be\r\n\t\t\t\t// percentages, but we don't bother and let float or percentage.\r\n\t\t\t\tvar s = parse_css_float(params[1]);\r\n\t\t\t\tvar l = parse_css_float(params[2]);\r\n\t\t\t\tvar m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;\r\n\t\t\t\tvar m1 = l * 2 - m2;\r\n\t\t\t\treturn [\r\n\t\t\t\t\tclamp_css_byte(css_hue_to_rgb(m1, m2, h + 1 / 3) * 255),\r\n\t\t\t\t\tclamp_css_byte(css_hue_to_rgb(m1, m2, h) * 255),\r\n\t\t\t\t\tclamp_css_byte(css_hue_to_rgb(m1, m2, h - 1 / 3) * 255),\r\n\t\t\t\t\talpha,\r\n\t\t\t\t];\r\n\t\t\tdefault:\r\n\t\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\r\n\treturn null;\r\n}\r\n","import ExpandBox from \"../geometry/ExpandBox.mjs\";\r\nimport Evented from \"../dom/Evented.mjs\";\r\n\r\nimport {\r\n\tmultiply,\r\n\tfromTranslation,\r\n\ttranspose,\r\n\t//str,\r\n} from \"../3rd-party/gl-matrix/mat3.mjs\";\r\nimport { transformMat3 } from \"../3rd-party/gl-matrix/vec3.mjs\";\r\n\r\n/**\r\n * @class Acetate\r\n * @inherits Evented\r\n * @relationship associated ExpandBox\r\n *\r\n * The name `Acetate` refers to the nickname given to transparent foil sheets\r\n * used in old-school projectors.\r\n *\r\n * The end result for the user is a stack of acetates (after they pass through a\r\n * image composition keeping alpha, etc).\r\n *\r\n * In `Gleo`/`Glii` terms, an `Acetate` is a collection of:\r\n * - A `Framebuffer`\r\n * - Including a `Texture` that can (and should/will) be used for composing\r\n * - A `WebGL1Program`\r\n * - An `AttributeSet`\r\n * - A `IndexBuffer`\r\n *\r\n * Typically, an `Acetate` will receive symbols, given as triangles together\r\n * with the neccesary vertex attributes, e.g.:\r\n * - triangulized polygons,\r\n * - extrudable line countours\r\n * - extrudable points\r\n *\r\n * Any given `Acetate` will draw symbols of the same \"kind\". Subclasses shall be\r\n * an acetate for solid-filled polygons, another for line contours, another for circles,\r\n * etc (as long as symbols of the same \"kind\" are to be rendered with the same\r\n * `WebGL1Program`).\r\n *\r\n */\r\n\r\nexport default class Acetate extends Evented {\r\n\t#framebuffer;\r\n\t// #depthAttachment;\r\n\t#outTexture;\r\n\t#glii;\r\n\t#zIndex;\r\n\t#attribution;\r\n\r\n\t// Whether this acetate should be redrawn\r\n\t#dirty = false;\r\n\r\n\t/**\r\n\t * @constructor Acetate(target: GliiFactory, opts: Acetate Options)\r\n\t * @alternative\r\n\t * @constructor Acetate(target: Platina, opts: Acetate Options)\r\n\t * @alternative\r\n\t * @constructor Acetate(target: GleoMap, opts: Acetate Options)\r\n\t */\r\n\tconstructor(\r\n\t\ttarget,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @option queryable: Boolean = false\r\n\t\t\t * If set to `true`, pointer events dispatched by this acetate will\r\n\t\t\t * contain information about the colour of the underlying pixel. This\r\n\t\t\t * can negatively impact performance.\r\n\t\t\t */\r\n\t\t\tqueryable = false,\r\n\r\n\t\t\t/**\r\n\t\t\t * @option zIndex: Number = 0\r\n\t\t\t * The relative position of this acetate in terms of compositing it\r\n\t\t\t * with other acetates. Must be a 16-bit signed integer\r\n\t\t\t * (an integer between -32786 and 32785).\r\n\t\t\t */\r\n\t\t\tzIndex = 0,\r\n\r\n\t\t\t/**\r\n\t\t\t * @option attribution: String = undefined\r\n\t\t\t * The attribution for the acetate, if any. Attribution can be\r\n\t\t\t * delegated to symbols or loaders.\r\n\t\t\t */\r\n\t\t\tattribution = undefined,\r\n\t\t} = {}\r\n\t) {\r\n\t\tsuper();\r\n\r\n\t\tthis.#zIndex = zIndex;\r\n\t\tthis.#attribution = attribution;\r\n\r\n\t\tif (\"glii\" in target) {\r\n\t\t\t// First parameter is a Platina, a GleoMap, or an ScalarField\r\n\t\t\tthis.#glii = target.glii;\r\n\t\t\ttarget.addAcetate(this);\r\n\t\t} else {\r\n\t\t\tthis.#glii = target;\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * @property queryable: Boolean\r\n\t\t * If set to `true`, pointer events dispathed by this acetate will\r\n\t\t * contain information about the colour of the underlying pixel. This\r\n\t\t * can negatively impact performance.\r\n\t\t *\r\n\t\t * Can be updated at runtime. The initial value is given by the\r\n\t\t * homonymous option.\r\n\t\t */\r\n\t\tthis.queryable = queryable;\r\n\r\n\t\t/**\r\n\t\t * @section Subclass interface\r\n\t\t * @uninheritable\r\n\t\t *\r\n\t\t * These properties are meant for internal use of an `Acetate` subclass.\r\n\t\t *\r\n\t\t * @property _coords: SingleAttribute\r\n\t\t * A Glii data structure holding `vec2`s, for vertex CRS coordinates.\r\n\t\t */\r\n\t\tthis._coords = new this.glii.SingleAttribute({\r\n\t\t\tsize: 1,\r\n\t\t\tgrowFactor: 1.2,\r\n\t\t\tusage: this.glii.DYNAMIC_DRAW,\r\n\t\t\tglslType: \"vec2\",\r\n\t\t\ttype: Float32Array,\r\n\t\t});\r\n\r\n\t\t// CRS that is being used.\r\n\t\t// This is automatically updated from `redraw()` calls.\r\n\t\tthis._crs = undefined;\r\n\r\n\t\t// CRS that was previously used.\r\n\t\t// This is relevant for `.reproject()`/`.reprojectAll()` calls, to check\r\n\t\t// whether the reprojection is a CRS offset or a full-fledged reprojection\r\n\t\t// by comparing the names of the CRSs.\r\n\t\tthis._oldCrs = undefined;\r\n\r\n\t\t// Expanding bounding box for the known `_coords`\r\n\t\tthis.bbox = new ExpandBox();\r\n\r\n\t\t// An array of symbols known to this acetate. They are indexed by the base\r\n\t\t// *attribute* of each symbol.\r\n\t\tthis._knownSymbols = [];\r\n\r\n\t\t// An instance of `Glii.MultiProgram`. Some subclasses of acetate will\r\n\t\t// implement several WebGL programs (notably, `AcetateInteractive`) and will\r\n\t\t// need to update their uniforms/attributes at once.\r\n\t\tthis._programs = new this.glii.MultiProgram();\r\n\r\n\t\t// Stores attributions of contained symbols, to check for changes.\r\n\t\tthis._attributions = new Set();\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @property glii: GliiFactory\r\n\t * The underlying Glii instance. Read-only.\r\n\t */\r\n\tget glii() {\r\n\t\treturn this.#glii;\r\n\t}\r\n\r\n\tget platina() {\r\n\t\treturn this._platina ? this._platina : this._inAcetate?._platina;\r\n\t}\r\n\r\n\t/**\r\n\t * @property attribution: String\r\n\t * The attribution text for the acetate (for use with the `Attribution`\r\n\t * control). Can not be updated (consider delegating attribution\r\n\t * to symbols instead)\r\n\t */\r\n\tget attribution() {\r\n\t\treturn this.#attribution;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Static properties\r\n\t * @property PostAcetate: undefined\r\n\t * Static property (implemented in the `Acetate` class prototype, not the\r\n\t * instances). For `Acetate`s that render RGBA8 textures, this is\r\n\t * `undefined`. For acetates that render non-RGBA8 textures, this\r\n\t * shall be an `Acetate` prototype that can post-process this into RGBA8.\r\n\t */\r\n\tstatic get PostAcetate() {\r\n\t\treturn undefined;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Subclass interface\r\n\t * @uninheritable\r\n\t *\r\n\t * Subclasses of `Acetate` must provide/define the following methods:\r\n\t *\r\n\t * @method glProgramDefinition(): Object\r\n\t * Returns a set of `WebGL1Program` options, that will be used to create the WebGL1\r\n\t * program to be run at every refresh.\r\n\t *\r\n\t * Subclasses can rely on the parent class definition, and decorate it as needed.\r\n\t *\r\n\t * The shader code (for both the vertex and fragment shaders) must be split\r\n\t * between `vertex`/`fragmentShaderSource` and `vertex`/`fragmentShaderMain`.\r\n\t * Do not define `void main() {}` in the shader source; this is done automatically\r\n\t * by wrapping `*ShaderMain`.\r\n\t */\r\n\tglProgramDefinition() {\r\n\t\t// This GL program definition in the abstract acetate includes only stuff\r\n\t\t// common to *all* acetates.\r\n\t\treturn {\r\n\t\t\tattributes: {\r\n\t\t\t\taCoords: this._coords,\r\n\t\t\t},\r\n\t\t\tuniforms: {\r\n\t\t\t\tuTransformMatrix: \"mat3\",\r\n\t\t\t},\r\n\t\t\tvertexShaderSource: \"\",\r\n\t\t\tvertexShaderMain: \"\",\r\n\t\t\tfragmentShaderSource: \"\",\r\n\t\t\tfragmentShaderMain: \"\",\r\n\t\t\ttarget: this.#framebuffer,\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @method add(symbol: GleoSymbol): this\r\n\t * Adds the given symbol to self. Typically this will imply a call to\r\n\t * `allocate(symbol)`. However, for symbols with some async load (e.g. `Sprite`s\r\n\t * and `ConformalRaster`s) the call to `allocate()` might happen at a later\r\n\t * time.\r\n\t */\r\n\tadd(symbol) {\r\n\t\treturn this.multiAdd([symbol]);\r\n\t}\r\n\r\n\t/**\r\n\t * @method multiAdd(symbols: Array of GleoSymbol): this\r\n\t * Add the given symbols to self (i.e. start drawing them).\r\n\t *\r\n\t * Since uploading data to the GPU is relatively costly, implementations\r\n\t * should make an effort to pack all the symbols' data together, and\r\n\t * make as few calls to the Glii buffers' `set()`/`multiSet()` methods.\r\n\t *\r\n\t * Subclasses must call the parent `multiAdd` to ensure firing the `symbolsadded`\r\n\t * event.\r\n\t */\r\n\tmultiAdd(symbols) {\r\n\t\tsymbols.forEach((sym) => {\r\n\t\t\tsym._inAcetate = this;\r\n\t\t});\r\n\t\t/**\r\n\t\t * @event symbolsadded\r\n\t\t * Fired whenever symbols are added to the acetate. Event details include\r\n\t\t * such symbols.\r\n\t\t */\r\n\t\tthis.fire(\"symbolsadded\", { symbols });\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @property symbols: Array of GleoSymbol\r\n\t * The symbols being drawn on this acetate.\r\n\t *\r\n\t * This is a shallow copy of the internal structure holding the symbols, so\r\n\t * any changes to it won't affect which symbols are being drawn. Read-only.\r\n\t */\r\n\tget symbols() {\r\n\t\treturn this._knownSymbols.filter((s) => !!s);\r\n\t}\r\n\r\n\t/**\r\n\t * @method multiAdd(symbols: Array of GleoSymbol): this\r\n\t * Add the given symbols to self (i.e. start drawing them).\r\n\t *\r\n\t * Since uploading data to the GPU is relatively costly, implementations\r\n\t * should make an effort to pack all the symbols' data together, and\r\n\t * make as few calls to the Glii buffers' `set()`/`multiSet()` methods.\r\n\t *\r\n\t * Subclasses must call the parent `multiAdd` to ensure firing the `symbolsadded`\r\n\t * event.\r\n\t */\r\n\thas(s) {\r\n\t\treturn this._knownSymbols.includes(s);\r\n\t}\r\n\r\n\t/**\r\n\t * @section Subclass interface\r\n\t * @method multiAllocate(symbols: Array of GleoSymbol): this\r\n\t * Allocates GPU RAM for the symbol, and asks the symbol to fill up that\r\n\t * slice of GPU RAM.\r\n\t *\r\n\t * Whenever possible, use `multiAllocate()` instead of multiple calls to `allocate()`.\r\n\t * Adding *lots* of symbols in a loop might cause *lots* of WebGL calls, which\r\n\t * will impact performance. By contrast, implementations of `allocate()` should be\r\n\t * prepared to make as few WebGL calls as possible.\r\n\t *\r\n\t * Subclasses shall call `multiAllocate` from `multiAdd`, either synchronously\r\n\t * or asynchronously.\r\n\t */\r\n\t// Implemented in `AcetateVertices`, `AcetateDot` and `AcetateFill`, due\r\n\t// to needing different handling of dumping vertex data and triangle index\r\n\t// data.\r\n\r\n\t/**\r\n\t * @section Subclass interface\r\n\t * @uninheritable\r\n\t * Subclasses **must** implement all the following methods related to strided arrays:\r\n\t * @method _getStridedArrays(maxVtx: Number, maxIdx: Number): undefined\r\n\t * Must return a plain array with all the `StridedTypedArrays that a symbol\r\n\t * might need, as well as any other (pseudo-)constants that the symbol's\r\n\t * `_setGlobalStrides()` method might need.\r\n\t *\r\n\t * This must allocate memory for attributes and vertex indices, and so\r\n\t * its parameters are the topmost vertex and index needed.\r\n\t *\r\n\t * @method _commitStridedArrays(baseVtx: Number, vtxCount: Number, baseIdx: Number, idxCount: Number): undefined\r\n\t * Called when committing data to attribute buffers. The default commits\r\n\t * data to `this._extrusions` and `this._attrs`, so there's no need to\r\n\t * redefine this if only those attribute storages are used.\r\n\t *\r\n\t *\r\n\t * @method _getGeometryStridedArrays()\r\n\t * As `_getStridedArrays()`, but applies only to strided arrays that need to be\r\n\t * updated whenever the geometry of a symbol changes.\r\n\t *\r\n\t * @method _commitGeometryStridedArrays()\r\n\t * As per `_commitStridedArrays()`, but applies only to the strided arrays\r\n\t * returned to `_getGeometryStridedArrays()`.\r\n\t *\r\n\t * @method _getPerPointStridedArrays()\r\n\t * As `_getStridedArrays()`, but applies only to strided arrays that contain\r\n\t * data that has to be updated on a per-geometry-point basis.\r\n\t *\r\n\t * @method _commitPerPointStridedArrays()\r\n\t * As per `_commitStridedArrays()`, but applies only to the strided arrays\r\n\t * returned to `_getPerPointStridedArrays()`.\r\n\t *\r\n\t * @method deallocate(symbol: GleoSymbol): this\r\n\t * Deallocate resources for the given symbol (attributes, primitive indices).\r\n\t * Since the primitive indices are deallocated, the symbol will not be drawn.\r\n\t *\r\n\t * Deallocating symbols involves *marking* their primitives as not being used,\r\n\t * in the CPU side of things. Since there is no data to upload into GPU memory,\r\n\t * implementations don't need to worry (much) about efficiency.\r\n\t *\r\n\t * Deallocation must also reset the references to the acetate, base vertex\r\n\t * and base index to `undefined`.\r\n\t *\r\n\t * @method reprojectAll(): this\r\n\t * Triggers a reprojection of all the coordinates of all vertices of all symbols in\r\n\t * the acetate. Called when `this._crs` changes. `AcetateDot` and `AcetateVertices`\r\n\t * provide implementations.\r\n\t */\r\n\treprojectAll() {\r\n\t\tthis.bbox.reset();\r\n\t\tthis.dirty = true;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method remove(symbol: GleoSymbol): this\r\n\t * Removes the given symbol from self (stops drawning it)\r\n\t */\r\n\tremove(symbol) {\r\n\t\tthis.deallocate(symbol);\r\n\r\n\t\tthis.fire(\"symbolsremoved\", { symbols: [symbol] });\r\n\t\tthis.dirty = true;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method multiRemove(symbols: Array of GleoSymbol): this\r\n\t * Removes the given symbols from self (i.e. stops drawing it).\r\n\t */\r\n\tmultiRemove(symbols) {\r\n\t\t/// TODO: *should* clean up this.bbox\r\n\t\t/// Right now it only clears on CRS change\r\n\t\t/// TODO: Check for adjacent symbols in order to do\r\n\t\t/// less deallocation calls.\r\n\r\n\t\t// Mark all symbols as not belonging to any acetate. This is independent\r\n\t\t// of a symbol being allocated or not.\r\n\t\t// This also handles the edge case of adding and removing a symbol\r\n\t\t// (e.g. a `Sprite` that takes time to load due to network or text\r\n\t\t// rendering) before it has been allocated. An unallocated symbol\r\n\t\t// will have its `attrBase` as undefined, but needs to be marked\r\n\t\t// as not belonging to any acetate as well.\r\n\t\tsymbols.forEach((s) => (s._inAcetate = undefined));\r\n\r\n\t\t// Filter out symbols with no `idxBase` or `attrBase` - these haven't\r\n\t\t// been fully loaded before being removed\r\n\t\tthis.multiDeallocate(\r\n\t\t\tsymbols.filter((s) => s.attrBase !== undefined && s.idxBase !== undefined)\r\n\t\t);\r\n\r\n\t\t/**\r\n\t\t * @event symbolsremoved\r\n\t\t * Fired whenever symbols are removed from the acetate. Event details include\r\n\t\t * such symbols.\r\n\t\t */\r\n\t\tthis.fire(\"symbolsremoved\", { symbols });\r\n\t\tthis.dirty = true;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method empty(): this\r\n\t * Removes all symbols currently in this acetate\r\n\t */\r\n\tempty() {\r\n\t\treturn this.multiRemove(this._knownSymbols);\r\n\t}\r\n\r\n\t/**\r\n\t * @method destroy(): this\r\n\t * Destroys all resources used by the acetate and detaches itself from the\r\n\t * containing platina.\r\n\t */\r\n\tdestroy() {\r\n\t\t// No need to individually remove/deallocate symbols - just mark them\r\n\t\t// as not belonging to any acetate, and as unallocated.\r\n\t\tthis._knownSymbols.forEach((s) => s.updateRefs(undefined, undefined, undefined));\r\n\r\n\t\t/// Subclasses that allocate extra resources (e.g. Stroke uses\r\n\t\t/// an extra attribute buffer) must destroy them as well.\r\n\t\tthis._indices?.destroy();\r\n\t\tthis._coords.destroy();\r\n\t\tthis._attrs?.destroy();\r\n\r\n\t\tthis._programs.destroy();\r\n\r\n\t\tconst i = this.platina?._acetates.indexOf(this);\r\n\t\tif (i !== -1) {\r\n\t\t\tthis.platina._acetates.splice(i, 1);\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @method getColourAt(x: Number, y: Number): Array of Number\r\n\t * Returns a 4-element array with the red, green, blue and alpha\r\n\t * values of the pixel at the given coordinates.\r\n\t * The coordinates are relative to the upper-left corner of the acetate.\r\n\t *\r\n\t * Used internally during event handling, so that the event can provide\r\n\t * the pixel colour at the coordinates of the pointer event.\r\n\t *\r\n\t * Returns `undefined` if the coordinates fall outside of the acetate.\r\n\t */\r\n\tgetColourAt(x, y) {\r\n\t\tif (!this.#framebuffer) {\r\n\t\t\treturn undefined;\r\n\t\t}\r\n\r\n\t\tconst h = this.#framebuffer.height;\r\n\t\tconst w = this.#framebuffer.width;\r\n\r\n\t\tif (y < 0 || y > h || x < 0 || x > w) {\r\n\t\t\treturn undefined;\r\n\t\t}\r\n\t\tconst dpr = devicePixelRatio ?? 1;\r\n\r\n\t\t// Textures are inverted in the Y axis because WebGL shenanigans. I know.\r\n\t\treturn this.#framebuffer.readPixels(dpr * x, h - dpr * y, 1, 1);\r\n\t}\r\n\r\n\t/**\r\n\t * @section Internal Methods\r\n\t * @uninheritable\r\n\t * @method multiDeallocate(symbols: Array of GleoSymbol): this\r\n\t * Deallocates all given symbols. Subclasses can (and should!)\r\n\t * provide an alternative implementation that performs only\r\n\t * one deallocation.\r\n\t */\r\n\tmultiDeallocate(symbols) {\r\n\t\tsymbols.forEach(this.deallocate.bind(this));\r\n\t\tsymbols.forEach((symbol) => {\r\n\t\t\tif (symbol.attrBase !== undefined) delete this._knownSymbols[symbol.attrBase];\r\n\t\t});\r\n\r\n\t\t// Check whether the array is composed of only empty slots\r\n\t\t// (this happens when delete()ing the last non-empty item)\r\n\t\t// The check is made via an iterator: the iterator won't run if there aren't\r\n\t\t// any non-empty items, even when the length of the array is non-zero\r\n\t\tif (!this._knownSymbols.some(() => true)) {\r\n\t\t\t// Reset the array so its length is zero (and acetates skip the draw calls)\r\n\t\t\tthis._knownSymbols = [];\r\n\t\t}\r\n\r\n\t\tthis.dirty = true;\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Redrawing methods\r\n\t *\r\n\t * These methods control when the acetate updates its internal texture. They\r\n\t * are meant to be called internally.\r\n\t *\r\n\t * @method resize(x: Number, y: Number): this\r\n\t * Resizes the internal framebuffer to the given size (in device pixels).\r\n\t */\r\n\tresize(x, y) {\r\n\t\tconst glii = this.#glii;\r\n\t\tconst opts = this.glProgramDefinition();\r\n\r\n\t\tif (!this._inAcetate) {\r\n\t\t\tif (!this.#framebuffer) {\r\n\t\t\t\t//this.#outTexture && this.#outTexture.destroy();\r\n\t\t\t\t//this.#framebuffer && this.#framebuffer.destroy();\r\n\t\t\t\tthis.#outTexture = new glii.Texture({\r\n\t\t\t\t\tformat: glii.RGBA,\r\n\t\t\t\t\tinternalFormat: glii.RGBA,\r\n\t\t\t\t});\r\n\r\n\t\t\t\tthis.#framebuffer = new glii.FrameBuffer({\r\n\t\t\t\t\tcolor: [this.#outTexture],\r\n\t\t\t\t\tdepth:\r\n\t\t\t\t\t\topts.depth && opts.depth !== glii.NEVER\r\n\t\t\t\t\t\t\t? new glii.RenderBuffer({\r\n\t\t\t\t\t\t\t\t\twidth: x,\r\n\t\t\t\t\t\t\t\t\theight: y,\r\n\t\t\t\t\t\t\t\t\tinternalFormat:\r\n\t\t\t\t\t\t\t\t\t\tglii.gl instanceof WebGL2RenderingContext\r\n\t\t\t\t\t\t\t\t\t\t\t? glii.DEPTH_COMPONENT24\r\n\t\t\t\t\t\t\t\t\t\t\t: glii.DEPTH_COMPONENT16,\r\n\t\t\t\t\t\t\t })\r\n\t\t\t\t\t\t\t: undefined,\r\n\t\t\t\t\t// stencil: renderbuffer,\r\n\t\t\t\t\twidth: x,\r\n\t\t\t\t\theight: y,\r\n\t\t\t\t});\r\n\t\t\t} else {\r\n\t\t\t\tthis.#framebuffer.resize(x, y);\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tthis.#framebuffer = this._inAcetate.framebuffer;\r\n\t\t}\r\n\r\n\t\tif (this._program) {\r\n\t\t\tthis._program._target = this.#framebuffer;\r\n\t\t} else {\r\n\t\t\topts.vertexShaderSource += opts.vertexShaderMain\r\n\t\t\t\t? `\\nvoid main(){${opts.vertexShaderMain}}`\r\n\t\t\t\t: \"\";\r\n\t\t\topts.fragmentShaderSource += opts.fragmentShaderMain\r\n\t\t\t\t? `\\nvoid main(){${opts.fragmentShaderMain}}`\r\n\t\t\t\t: \"\";\r\n\t\t\topts.target = this.#framebuffer;\r\n\t\t\tthis._program = new glii.WebGL1Program(opts);\r\n\t\t\tthis._programs.addProgram(this._program);\r\n\t\t\t/**\r\n\t\t\t * @section\r\n\t\t\t * @event programlinked: Event\r\n\t\t\t * Fired when the GL program is ready (has been compiled and linked)\r\n\t\t\t */\r\n\t\t\tthis.dispatchEvent(new Event(\"programlinked\"));\r\n\t\t}\r\n\r\n\t\tconst depthClear =\r\n\t\t\topts.depth === glii.LEQUAL || opts.depth === glii.LESS ? 1 : -1;\r\n\r\n\t\tthis._clear =\r\n\t\t\tthis.#outTexture &&\r\n\t\t\tnew glii.WebGL1Clear({\r\n\t\t\t\tcolor: [255, 255, 255, 0],\r\n\t\t\t\ttarget: this.#framebuffer,\r\n\t\t\t\t// depth: 1,\r\n\t\t\t\t//depth: (this.zIndex << 15 )\r\n\t\t\t\tdepth: depthClear,\r\n\t\t\t});\r\n\r\n\t\tthis.dirty = true;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Redrawing methods\r\n\t * @method redraw(crs: BaseCRS, matrix: Array of Number, viewportBbox: ExpandBox): Boolean\r\n\t * Low-level redraw of the `Acetate`.\r\n\t *\r\n\t * The passed `crs` ensures display in that CRS, since it's used to either\r\n\t * - Check that all coordinate data in the `Acetate` is using that CRS, or\r\n\t * - Reproject all coordinate data in the `Acetate` to match the CRS.\r\n\t *\r\n\t * The 9-element matrix is expected to be a 2D transformation matrix, which is\r\n\t * then fed to the shader program as a `mat3`.\r\n\t *\r\n\t * Note that redrawing a single `Acetate` does **not** trigger a re-composition\r\n\t * of all acetates, i.e. the redraw is not visible until re-composition happens.\r\n\t *\r\n\t * Returns `true` when the acetate has been redrawn, or `false` if a redraw\r\n\t * is not deemed needed.\r\n\t */\r\n\tredraw(crs, matrix, viewportBbox) {\r\n\t\tif (!this.dirty) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tthis.#dirty = false;\r\n\t\tthis.clear();\r\n\t\tif (this._knownSymbols.length === 0) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\tif (this._crs !== crs) {\r\n\t\t\tthis._oldCrs = this._crs || {};\r\n\t\t\tthis._crs = crs;\r\n\t\t\t// console.log(\"Acetate \", this.constructor.name, \"reprojecting data into CRS \", this._crs);\r\n\t\t\tthis.bbox = new ExpandBox();\r\n\r\n\t\t\t/**\r\n\t\t\t * @section Subclass interface\r\n\t\t\t * @uninheritable\r\n\t\t\t * Subclasses of `Acetate` must provide/define the following:\r\n\t\t\t * @method reprojectAll(): undefined\r\n\t\t\t * Must dump a new set of values to `this._coords`, based on the known\r\n\t\t\t * set of symbols added to the acetate.\r\n\t\t\t */\r\n\t\t\tthis.reprojectAll();\r\n\t\t}\r\n\r\n\t\tlet x1 = Math.ceil((viewportBbox.minX - this.bbox.maxX) / crs.wrapPeriodX);\r\n\t\tlet x2 = Math.floor((viewportBbox.maxX - this.bbox.minX) / crs.wrapPeriodX);\r\n\r\n\t\tlet y1 = Math.ceil((viewportBbox.minY - this.bbox.maxY) / crs.wrapPeriodY);\r\n\t\tlet y2 = Math.floor((viewportBbox.maxY - this.bbox.minY) / crs.wrapPeriodY);\r\n\r\n\t\t// \t\tconsole.log(\r\n\t\t// \t\t\t\"Drawing acetate\", this.constructor.name,\"; must check viewport wrapping. Data bounds/viewport:\",\r\n\t\t// \t\t\tthis.bbox,\r\n\t\t// \t\t\tviewportBbox\r\n\t\t// \t\t);\r\n\t\t// console.log(\"X-wrap:\", x1, x2, \"Y-wrap\", y1, y2);\r\n\r\n\t\tif (!Number.isFinite(x1) || !Number.isFinite(x2)) {\r\n\t\t\tx1 = 0;\r\n\t\t\tx2 = 0;\r\n\t\t}\r\n\t\tif (!Number.isFinite(y1) || !Number.isFinite(y2)) {\r\n\t\t\ty1 = 0;\r\n\t\t\ty2 = 0;\r\n\t\t}\r\n\r\n\t\tif (x2 > x1 + 10) {\r\n\t\t\tconsole.warn(\r\n\t\t\t\t\"Map repeats more than 10 times horizontally. Check your scale factor.\"\r\n\t\t\t);\r\n\t\t\tx2 = x1 + 10;\r\n\t\t}\r\n\t\tif (y2 > y1 + 10) {\r\n\t\t\tconsole.warn(\r\n\t\t\t\t\"Map repeats more than 10 times horizontally. Check your scale factor.\"\r\n\t\t\t);\r\n\t\t\ty2 = y1 + 10;\r\n\t\t}\r\n\r\n\t\t// Transpose of the CRS matrix to apply `glmatrix` functionality.\r\n\t\t// Damn different notations.\r\n\t\tconst origMatrix = transpose(new Array(9), matrix);\r\n\r\n\t\t// Copy of the map's crsMatrix, but without the translation. Will\r\n\t\t// be used for calculating the wrap offsets. Note glmatrix notation.\r\n\t\t// prettier-ignore\r\n\t\tconst scaleRotationMatrix = [\r\n\t\t\tmatrix[0], matrix[3], 0,\r\n\t\t\tmatrix[1], matrix[4], 0,\r\n\t\t\t 0, 0, 1,\r\n\t\t];\r\n\r\n\t\tconst offsetVector = new Array(3);\r\n\t\tconst offsetMatrix = new Array(9);\r\n\r\n\t\t/// TODO: Leverage instanced rendering instead.\r\n\t\t/// TODO: Does setting a smaller viewport, or a scissor test,\r\n\t\t/// help performance in any way?\r\n\t\tfor (let x = x1; x <= x2; x++) {\r\n\t\t\tfor (let y = y1; y <= y2; y++) {\r\n\t\t\t\tlet offsetX = crs.wrapPeriodX * x;\r\n\t\t\t\tlet offsetY = crs.wrapPeriodY * y;\r\n\r\n\t\t\t\tif (!Number.isFinite(offsetX)) {\r\n\t\t\t\t\toffsetX = 0;\r\n\t\t\t\t}\r\n\t\t\t\tif (!Number.isFinite(offsetY)) {\r\n\t\t\t\t\toffsetY = 0;\r\n\t\t\t\t}\r\n\r\n\t\t\t\toffsetVector[0] = offsetX;\r\n\t\t\t\toffsetVector[1] = offsetY;\r\n\t\t\t\toffsetVector[2] = 0;\r\n\t\t\t\ttransformMat3(offsetVector, offsetVector, scaleRotationMatrix);\r\n\r\n\t\t\t\tfromTranslation(offsetMatrix, offsetVector.slice(0, 2));\r\n\t\t\t\tmultiply(offsetMatrix, offsetMatrix, origMatrix);\r\n\r\n\t\t\t\ttranspose(offsetMatrix, offsetMatrix);\r\n\r\n\t\t\t\tthis._programs.setUniform(\"uTransformMatrix\", offsetMatrix);\r\n\t\t\t\tthis.runProgram();\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Redrawing methods\r\n\t *\r\n\t * @method clear(): this\r\n\t * Clears the acetate: sets all pixels to transparent black.\r\n\t */\r\n\tclear() {\r\n\t\tthis._clear?.run();\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Redrawing properties\r\n\t * @property dirty: Boolean = false\r\n\t * Whether this acetate should be rendered at the next frame. Can only be\r\n\t * set to `true`; a call to `redraw()` will reset this to `false`.\r\n\t */\r\n\tget dirty() {\r\n\t\treturn this.#dirty;\r\n\t}\r\n\tset dirty(d) {\r\n\t\tthis.#dirty ||= d;\r\n\t\tif (this._inAcetate) {\r\n\t\t\tthis._inAcetate.dirty ||= d;\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @section Internal Methods\r\n\t * @uninheritable\r\n\t * Meant to be used only from `GleoMap`.\r\n\t * @method asTexture(): Texture\r\n\t * Returns a reference to the Glii `Texture` holding the visible results of this acetate.\r\n\t */\r\n\tasTexture() {\r\n\t\treturn this.#outTexture;\r\n\t}\r\n\r\n\t/**\r\n\t * @method runProgram(): this\r\n\t * Runs the GL program for this acetate. Might be overwritten by subclasses\r\n\t * when partial runs are needed (e.g. to set per-symbol textures, or\r\n\t * selecting a LoD).\r\n\t */\r\n\trunProgram() {\r\n\t\tthis._programs.run();\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method multiSetCoords(start: Number, coordData: Array of Number): this\r\n\t * Sets a section of the internal `_coords` `SingleAttributeBuffer`, and expands the\r\n\t * bounding box of known coords to cover the new ones.\r\n\t *\r\n\t * The second `coordData` argument must be a *flattened* array of x-y coordinates,\r\n\t * of the form `[x1, y1, x2, y2, x3, y3, .... xn, yn]`.\r\n\t */\r\n\tmultiSetCoords(start, coordData) {\r\n\t\tthis._coords.multiSet(start, coordData);\r\n\r\n\t\treturn this.expandBBox(coordData);\r\n\t}\r\n\r\n\t/**\r\n\t * @method expandBBox(coordData: Array of Number): this\r\n\t * Each acetate keeps a bounding box to keep track of the extents of drawable\r\n\t * items (to calculate antimeridian repetition).\r\n\t *\r\n\t * This expects an argument in the form of `[x1, y1, x2, y2, x3, y3, .... xn, yn]`.\r\n\t */\r\n\texpandBBox(coordData) {\r\n\t\tfor (let i = 0, l = coordData.length; i < l; i += 2) {\r\n\t\t\tif (Number.isFinite(coordData[i]) && Number.isFinite(coordData[i + 1])) {\r\n\t\t\t\tthis.bbox.expandXY(coordData[i], coordData[i + 1]);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method dispatchPointerEvent(ev:GleoPointerEvent): Boolean\r\n\t * Stub for interactive acetate logic. Alias for `dispatchEvent`.\r\n\t */\r\n\tdispatchPointerEvent(ev) {\r\n\t\tif (this.queryable) {\r\n\t\t\t// The current implementation will query the framebuffer/texture\r\n\t\t\t// when the expression is evaluated, which might be too late\r\n\t\t\t// specially if the event is logged into the console. The following\r\n\t\t\t// is the previous implementation, which is immediate but\r\n\t\t\t// potentially very wasteful.\r\n\t\t\t// See https://gitlab.com/IvanSanchez/gleo/-/issues/112\r\n\t\t\t// ev.colour = this.getColourAt(ev.canvasX, ev.canvasY);\r\n\r\n\t\t\tlet colour;\r\n\t\t\tconst getColour = function getColour() {\r\n\t\t\t\tif (colour) {\r\n\t\t\t\t\treturn colour;\r\n\t\t\t\t}\r\n\t\t\t\treturn (colour = this.getColourAt(ev.canvasX, ev.canvasY));\r\n\t\t\t}.bind(this);\r\n\t\t\tObject.defineProperty(ev, \"colour\", { get: getColour });\r\n\t\t}\r\n\t\treturn this.dispatchEvent(ev);\r\n\t}\r\n\r\n\t/**\r\n\t * @section Internal properties\r\n\t * Meant to be used only from within a `Platina`.\r\n\t * @property zIndex: Number; The value of the `zIndex` constructor option. Read-only.\r\n\t */\r\n\tget zIndex() {\r\n\t\treturn this.#zIndex;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Subclass interface\r\n\t * @uninheritable\r\n\t * @property framebuffer: Framebuffer\r\n\t * The output Glii framebuffer for this acetate. Read-only.\r\n\t */\r\n\tget framebuffer() {\r\n\t\treturn this.#framebuffer;\r\n\t}\r\n}\r\n","// import Glii from 'glii';\r\nimport Glii from \"./glii/src/index.mjs\";\r\n// import epsg3857 from \"./crs/epsg3857.mjs\";\r\n// import epsg4326 from \"./crs/epsg4326.mjs\";\r\nimport OffsetCRS from \"./crs/OffsetCRS.mjs\";\r\nimport Geometry from \"./geometry/Geometry.mjs\";\r\nimport Evented from \"./dom/Evented.mjs\";\r\nimport Loader from \"./loaders/Loader.mjs\";\r\n// import GleoSymbol from \"./symbols/Symbol.mjs\";\r\nimport {\r\n\tinvert,\r\n\ttranspose,\r\n\t//fromTranslation,\r\n\t//scale as mat3Scale,\r\n} from \"./3rd-party/gl-matrix/mat3.mjs\";\r\nimport { transformMat3 } from \"./3rd-party/gl-matrix/vec3.mjs\";\r\nimport { getMousePosition } from \"./dom/Dom.mjs\";\r\nimport GleoMouseEvent from \"./dom/GleoMouseEvent.mjs\";\r\nimport GleoPointerEvent from \"./dom/GleoPointerEvent.mjs\";\r\nimport ExpandBox from \"./geometry/ExpandBox.mjs\";\r\nimport { factory } from \"./geometry/DefaultGeometry.mjs\";\r\nimport parseColour from \"./3rd-party/css-colour-parser.mjs\";\r\nimport Acetate from \"./acetates/Acetate.mjs\";\r\n\r\nconst { log2, abs, max } = Math;\r\n\r\nconst pointerEvents = [\r\n\t\"click\",\r\n\t\"dblclick\",\r\n\t\"auxclick\",\r\n\t\"contextmenu\",\r\n\t\"pointerover\",\r\n\t\"pointerenter\",\r\n\t\"pointerdown\",\r\n\t\"pointermove\",\r\n\t\"pointerup\",\r\n\t\"pointercancel\",\r\n\t\"pointerout\",\r\n\t\"pointerleave\",\r\n\t\"gotpointercapture\",\r\n\t\"lostpointercapture\",\r\n];\r\n\r\n/**\r\n * @class Platina\r\n *\r\n * @inherits Evented\r\n * @relationship compositionOf Acetate, 1..1, 0..n\r\n * @relationship compositionOf Loader, 1..1, 0..n\r\n * @relationship compositionOf BaseCRS, 0..n, 1..1\r\n *\r\n * @relationship associated GleoPointerEvent, 1..1, 0..n\r\n * @relationship associated ExpandBox, 1..1, 0..n\r\n * @relationship associated dom\r\n *\r\n * In printing, a \"platen\" (or \"platine\" or \"platina\") is the glass flatbed\r\n * of a photocopier or scanner where pages are laid down, and in an\r\n * overhead projector, it's the glass flatbed where an acetate sheet is laid down.\r\n *\r\n * In Gleo, a `Platina` is the `` where the map is shown (without any\r\n * map controls). The platina has a state similar to a map (center/scale/etc), and\r\n * when it changes it tells all acetates to redraw themselves, then flattens\r\n * all acetates together.\r\n *\r\n * A `Platina` boils down to:\r\n * - A collection of `Acetate`s, stacked and composable\r\n * - A `` and its related WebGL context\r\n * - A view, with:\r\n * - CRS (`BaseCRS`)\r\n * - Center (`Geometry`)\r\n * - Scale factor\r\n * - Yaw rotation angle\r\n *\r\n * A `Platina` can ve used standalone to draw `GleoSymbol`s in `Acetate`s, but\r\n * does not offer interactivity (e.g. map drag, mousewheel zoom, etc); that is\r\n * left to `Actuator`s in a `GleoMap`.\r\n */\r\n\r\nexport default class Platina extends Evented {\r\n\t#glii;\r\n\t#precisionThreshold;\r\n\r\n\t#crs;\r\n\t#yaw = 0;\r\n\t#center;\r\n\t#scale;\r\n\t#map;\r\n\t#canvas;\r\n\t#backgroundColour;\r\n\r\n\t#resizable;\r\n\t#boundOnPointerEvent;\r\n\r\n\t#boundRedraw;\r\n\t#boundMultiAdd;\r\n\t#boundMultiRemove;\r\n\r\n\t#invalidViewWarningTimeout;\r\n\r\n\t/**\r\n\t * @constructor Platina(canvas: HTMLCanvasElement, options?: Platina Options)\r\n\t * @alternative\r\n\t * @constructor Platina(canvasID: string, options?: Platina Options)\r\n\t */\r\n\t/// TODO: Allow a WebGLRenderingContext. This is problematic for\r\n\t/// the ResizeObserver and the DOM events.\r\n\tconstructor(\r\n\t\tcanvas,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @section Platina Options\r\n\t\t\t * @option resizable: Boolean = true\r\n\t\t\t * Whether the map should react to changes in the size of its DOM\r\n\t\t\t * container. Setting to `false` enables some memory optimizations.\r\n\t\t\t */\r\n\t\t\tresizable = true,\r\n\t\t\t/**\r\n\t\t\t * @option backgroundColour: Colour = [0, 0, 0, 0]\r\n\t\t\t * Self-explanatory. The default transparent black should work for\r\n\t\t\t * most use cases.\r\n\t\t\t */\r\n\t\t\tbackgroundColour = [0, 0, 0, 0],\r\n\r\n\t\t\t/**\r\n\t\t\t * @option preserveDrawingBuffer: Boolean = false\r\n\t\t\t * Whether the rendering context created from the canvas shall\r\n\t\t\t * be able to be read back. See the `preserveDrawingBuffer` option\r\n\t\t\t * of [`HTMLCanvasElement.getContext()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext)\r\n\t\t\t */\r\n\t\t\tpreserveDrawingBuffer = false,\r\n\r\n\t\t\t/**\r\n\t\t\t * @option precisionThreshold: Number = *\r\n\t\t\t * The order of magnitude (in terms of significant bits, or base-2\r\n\t\t\t * logarithm) that triggers a CRS offset.\r\n\t\t\t *\r\n\t\t\t * The default value depends on the floating point precision reported\r\n\t\t\t * by the GPU, typically 22 (for GPUs which internally use `float32`)\r\n\t\t\t * or 15 for older GPUs (which internally use `float24`).\r\n\t\t\t *\r\n\t\t\t * Raising this value may prevent spurious CRS offsets and *might*\r\n\t\t\t * alleviate CRS-offset-related delays and artifacts, at the cost\r\n\t\t\t * of possible precision artifacts. A value lower than the default\r\n\t\t\t * has no positive effects.\r\n\t\t\t */\r\n\t\t\tprecisionThreshold = undefined,\r\n\r\n\t\t\t// Hidden option. Only used within GleoMap, and meant to let\r\n\t\t\t// symbols & acetates know what GleoMap instance they belong to,\r\n\t\t\t// if any.\r\n\t\t\tmap = undefined,\r\n\r\n\t\t\t...options\r\n\t\t} = {}\r\n\t) {\r\n\t\tsuper();\r\n\r\n\t\tthis.options = options;\r\n\t\tthis.#backgroundColour = backgroundColour;\r\n\r\n\t\tthis.#canvas =\r\n\t\t\ttypeof canvas === \"string\" ? document.getElementById(canvas) : canvas;\r\n\t\tthis.#map = map;\r\n\r\n\t\tconst glii = (this.#glii = new Glii(this.#canvas, {\r\n\t\t\t//premultipliedAlpha: false,\r\n\t\t\t// premultipliedAlpha: true,\r\n\t\t\tdepth: true,\r\n\t\t\tpreserveDrawingBuffer,\r\n\t\t\talpha: true,\r\n\t\t}));\r\n\r\n\t\tif (!glii) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"No WebGL context: wrong canvas, or no WebGL support on this browser.\"\r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\tthis.#resizable = resizable;\r\n\t\tif (resizable) {\r\n\t\t\tglii.addEventListener(\"resized\", this.#onResize.bind(this));\r\n\t\t}\r\n\r\n\t\tconst gl = glii.gl;\r\n\r\n\t\tif (precisionThreshold) {\r\n\t\t\tthis.#precisionThreshold = precisionThreshold;\r\n\t\t} else {\r\n\t\t\tthis.#precisionThreshold =\r\n\t\t\t\tgl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT).precision -\r\n\t\t\t\t1;\r\n\t\t}\r\n\r\n\t\tthis._acetates = [];\r\n\t\t// Prepare data structs for acetate quads:\r\n\t\t// - Triangle indices (two per quad)\r\n\t\t// - Texture corners and z-index (as *static* attributes)\r\n\t\t// - CRS coords (as dynamic attributes)\r\n\t\tthis._acetateQuads = new glii.TriangleIndices({\r\n\t\t\tsize: 6,\r\n\t\t\tgrowFactor: 1,\r\n\t\t});\r\n\t\tthis._acetateAttrs = new glii.InterleavedAttributes(\r\n\t\t\t{\r\n\t\t\t\tsize: 4,\r\n\t\t\t\tgrowFactor: 1,\r\n\t\t\t\tusage: glii.STATIC_DRAW,\r\n\t\t\t},\r\n\t\t\t[\r\n\t\t\t\t{\r\n\t\t\t\t\t// z-index\r\n\t\t\t\t\tglslType: \"float\",\r\n\t\t\t\t\ttype: Int16Array,\r\n\t\t\t\t\tnormalized: true,\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\t// Texture coords\r\n\t\t\t\t\tglslType: \"vec2\",\r\n\t\t\t\t\ttype: Int8Array,\r\n\t\t\t\t},\r\n\t\t\t]\r\n\t\t);\r\n\t\tthis._acetateCoords = new glii.SingleAttribute({\r\n\t\t\tsize: 4,\r\n\t\t\tgrowFactor: 1,\r\n\t\t\tusage: glii.DYNAMIC_DRAW,\r\n\t\t\tglslType: \"vec2\",\r\n\t\t\ttype: Float32Array,\r\n\t\t});\r\n\r\n\t\tthis._bbox = new ExpandBox();\r\n\t\tthis.#onResize();\r\n\r\n\t\tthis.rebuildCompositor();\r\n\r\n\t\t// Hook up event decorators\r\n\t\t/**\r\n\t\t * @section Pointer events\r\n\t\t *\r\n\t\t * All [DOM `PointerEvent`s](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events)\r\n\t\t * to a platina's `` are handled by Gleo.\r\n\t\t * Besides all of `PointerEvent`'s properties and methods, Gleo adds\r\n\t\t * the `Geometry` corresponding to the pixel the event took place in.\r\n\t\t *\r\n\t\t * Most events are `GleoPointerEvent`s, but some browsers fire\r\n\t\t * exclusively `MouseEvent`s for `click`/`auxclick`/`contextmenu`. In\r\n\t\t * that case, expect a `GleoMouseEvent` instead.\r\n\t\t *\r\n\t\t * @event click: GleoPointerEvent\r\n\t\t * Akin to the [DOM `click` event](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event)\r\n\t\t * @event dblclick: GleoPointerEvent\r\n\t\t * Akin to the [DOM `dblclick` event](https://developer.mozilla.org/en-US/docs/Web/API/Element/dblclick_event)\r\n\t\t * @event auxclick: GleoPointerEvent\r\n\t\t * Akin to the [DOM `auxclick` event](https://developer.mozilla.org/en-US/docs/Web/API/Element/auxclick_event)\r\n\t\t * @event contextmenu: GleoPointerEvent\r\n\t\t * Akin to the [DOM `contextmenu` event](https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event)\r\n\t\t * @event pointerover: GleoPointerEvent\r\n\t\t * Akin to the [DOM `pointerover` event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/pointerover_event)\r\n\t\t * @event pointerenter: GleoPointerEvent\r\n\t\t * Akin to the [DOM `pointerenter` event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/pointerenter_event)\r\n\t\t * @event pointerdown: GleoPointerEvent\r\n\t\t * Akin to the [DOM `pointerdown` event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/pointerdown_event)\r\n\t\t * @event pointermove: GleoPointerEvent\r\n\t\t * Akin to the [DOM `pointermove` event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/pointermove_event)\r\n\t\t * @event pointerup: GleoPointerEvent\r\n\t\t * Akin to the [DOM `pointerup` event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/pointerup_event)\r\n\t\t * @event pointercancel: GleoPointerEvent\r\n\t\t * Akin to the [DOM `pointercancel` event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/pointercancel_event)\r\n\t\t * @event pointerout: GleoPointerEvent\r\n\t\t * Akin to the [DOM `pointerout` event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/pointerout_event)\r\n\t\t * @event pointerleave: GleoPointerEvent\r\n\t\t * Akin to the [DOM `pointerleave` event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/pointerleave_event)\r\n\t\t * @event gotpointercapture: GleoPointerEvent\r\n\t\t * Akin to the [DOM `gotpointercapture` event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/gotpointercapture_event)\r\n\t\t * @event lostpointercapture: GleoPointerEvent\r\n\t\t * Akin to the [DOM `lostpointercapture` event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/lostpointercapture_event)\r\n\t\t */\r\n\t\tthis.#boundOnPointerEvent = this._onPointerEvent.bind(this);\r\n\t\tfor (let evName of pointerEvents) {\r\n\t\t\tthis.#canvas.addEventListener(evName, this.#boundOnPointerEvent);\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * @section View initialization Options\r\n\t\t *\r\n\t\t * A `Platina` can take a set of [`SetView` options](#setview-options),\r\n\t\t * just as the ones for a `setView` call.\r\n\t\t *\r\n\t\t * @option crs: BaseCRS = undefined\r\n\t\t * Initial CRS of the platina.\r\n\t\t * @option yawDegrees: Number = 0\r\n\t\t * Initial yaw rotation of the platina, in clockwise degrees.\r\n\t\t * @option yawRadians: Number = 0\r\n\t\t * Initial yaw rotation of the platina, in counter-clockwise radians.\r\n\t\t * @option center: Geometry = undefined\r\n\t\t * Initial center of the platina.\r\n\t\t * @option scale: Number = undefined\r\n\t\t * Initial scale of the platina, in CRS units per CSS pixel.\r\n\t\t * @option span: Number = undefined\r\n\t\t * Initial span of the platina, in CRS units per diagonal.\r\n\t\t */\r\n\t\tthis.setView(options);\r\n\r\n\t\tthis.#boundRedraw = this.redraw.bind(this);\r\n\t\tthis.#boundMultiAdd = (ev) => {\r\n\t\t\tthis.multiAdd(ev.detail.symbols);\r\n\t\t};\r\n\t\tthis.#boundMultiRemove = (ev) => {\r\n\t\t\tthis.multiRemove(ev.detail.symbols);\r\n\t\t};\r\n\r\n\t\t// Start the main render loop\r\n\t\tthis.#queueRedraw();\r\n\r\n\t\t// Queue a console warning message, for developers who forget to set the\r\n\t\t// platina's center/scale/CRS. 5 seconds should be a nice time.\r\n\t\tif (typeof window !== \"undefined\") {\r\n\t\t\tthis.#invalidViewWarningTimeout = setTimeout(() => {\r\n\t\t\t\tconsole.warn(\"platina does not have crs+center+scale\");\r\n\t\t\t}, 5000);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @method destroy(): this\r\n\t * Destroys the platina, freeing the rendering context. Should free all\r\n\t * used GPU resources.\r\n\t *\r\n\t * No methods should be called on a destroyed platina.\r\n\t */\r\n\tdestroy() {\r\n\t\tcancelAnimationFrame(this.#animFrame);\r\n\r\n\t\tthis._clear.run();\r\n\r\n\t\tthis._acetateQuads.destroy();\r\n\t\tthis._acetateAttrs.destroy();\r\n\t\tthis._acetateCoords.destroy();\r\n\t\tthis._compositor.destroy();\r\n\r\n\t\tthis._acetates.forEach((ac) => ac.destroy());\r\n\r\n\t\tfor (let evName of pointerEvents) {\r\n\t\t\tthis.#canvas.removeEventListener(evName, this.#boundOnPointerEvent);\r\n\t\t}\r\n\r\n\t\tthis.#canvas = undefined;\r\n\t}\r\n\r\n\t/**\r\n\t * @section DOM properties\r\n\t * @property canvas: HTMLCanvasElement\r\n\t * The `` element this platina is attached to. Read-only.\r\n\t */\r\n\tget canvas() {\r\n\t\treturn this.#canvas;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Internal methods\r\n\t * @method addAcetate(ac: Acetate): this\r\n\t * Adds a new `Acetate` to the map.\r\n\t *\r\n\t * There's no need to call this manually - acetates will be added to a\r\n\t * `Platina` (or `GleoMap`) automatically then they're instantiated. Do\r\n\t * remember to pass the `Platina` as the first parameter to the `Acetate`\r\n\t * constructor.\r\n\t */\r\n\taddAcetate(acetate) {\r\n\t\tif (this._acetates.includes(acetate)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// The resize() initialization of an acetate is delayed with a\r\n\t\t// setTimeout() because:\r\n\t\t// - This is called during the base acetate constructor\r\n\t\t// - Acetate subclass code can initialize stuff they need *after*\r\n\t\t// this call\r\n\t\t// - Calling resize() without all data structures ready will throw errors\r\n\t\t// - Here is the place which causes the least distress\r\n\t\t// (Ideally this would use `setImmediate()` instead, if browsers had it).\r\n\t\tthis.once(\"prerender\", () => {\r\n\t\t\tacetate.resize(this._pxWidth, this._pxHeight);\r\n\t\t});\r\n\r\n\t\tif (\r\n\t\t\tacetate.constructor.PostAcetate === undefined ||\r\n\t\t\tacetate.constructor.PostAcetate === Acetate\r\n\t\t) {\r\n\t\t\t/// RGBA acetate, add directly to self\r\n\t\t\tconst zIndex = acetate.zIndex;\r\n\t\t\tlet i = this._acetates.length * 4;\r\n\t\t\tlet quad = new this._acetateQuads.Quad();\r\n\t\t\tquad.setVertices(i, i + 1, i + 2, i + 3);\r\n\t\t\tthis._acetateAttrs.setFields(i, [[zIndex], [0, 0]]);\r\n\t\t\tthis._acetateAttrs.setFields(i + 1, [[zIndex], [0, 1]]);\r\n\t\t\tthis._acetateAttrs.setFields(i + 2, [[zIndex], [1, 1]]);\r\n\t\t\tthis._acetateAttrs.setFields(i + 3, [[zIndex], [1, 0]]);\r\n\r\n\t\t\tthis._acetates.push(acetate);\r\n\t\t\t/// TODO: Leftover initialization of acetates - fetch its `Texture`, maybe more?\r\n\t\t\t/// TODO: Match the acetate with its index, explicitly?\r\n\t\t\t/// TODO: Allow for RGBA acetates to not be bound to the Platina's\r\n\t\t\t/// render loop (when they're meant to be post-processed by another\r\n\t\t\t/// acetate)\r\n\r\n\t\t\tthis._acetates.sort((a, b) => a.zIndex - b.zIndex);\r\n\t\t} else {\r\n\t\t\t// Search for a scalar field acetate (or similar) that can hold\r\n\t\t\t// this acetate.\r\n\t\t\t// console.warn(\"Non-RGBA8 acetate\");\r\n\r\n\t\t\tlet fitScalarField = this._acetates.filter(\r\n\t\t\t\t(candidate) => candidate instanceof acetate.constructor.PostAcetate\r\n\t\t\t)[0];\r\n\r\n\t\t\tif (fitScalarField) {\r\n\t\t\t\tfitScalarField.addAcetate(acetate);\r\n\t\t\t} else {\r\n\t\t\t\tnew acetate.constructor.PostAcetate(this).addAcetate(acetate);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tacetate._map = acetate._platina = this;\r\n\r\n\t\t/// TODO: Save i,quad into acetate\r\n\t\t/// TODO: Method for removing an acetate (dealloc attribs from\r\n\t\t/// i, triangles from quad)\r\n\r\n\t\t/**\r\n\t\t * @section Symbol/loader management events\r\n\t\t * @event acetateadded\r\n\t\t * Fired whenever an `Acetate` is added to the platina.\r\n\t\t * @event symbolsadded\r\n\t\t * Fired whenever symbols are added to any of the platina's acetates.\r\n\t\t * @event symbolsremoved\r\n\t\t * Fired whenever symbols are removed from any of th platina's acetates.\r\n\t\t */\r\n\t\tthis.fire(\"acetateadded\", acetate);\r\n\t\tacetate.on(\"symbolsadded\", (ev) => {\r\n\t\t\tthis.fire(\"symbolsadded\", ev.detail);\r\n\t\t});\r\n\t\tacetate.on(\"symbolsremoved\", (ev) => {\r\n\t\t\tthis.fire(\"symbolsremoved\", ev.detail);\r\n\t\t});\r\n\r\n\t\t// this.#queueRedraw();\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @method getAcetateOfClass(proto: Prototype of Acetate): Acetate\r\n\t * Given a specific `Acetate` class (e.g. `getAcetateOfClass(Sprite.Acetate)`),\r\n\t * returns an acetate instance where that kind of symbol can be drawn.\r\n\t * Will create an acetate of the given class if it doesn't exist in the map yet.\r\n\t */\r\n\tgetAcetateOfClass(acetateClass) {\r\n\t\tfunction recurse(ac) {\r\n\t\t\treturn ac.subAcetates ? [ac, ...ac.subAcetates.map(recurse).flat()] : [ac];\r\n\t\t}\r\n\t\tlet allAcetates = Array.from(this._acetates, recurse).flat();\r\n\r\n\t\tlet ac = allAcetates.find(\r\n\t\t\t(a) => Object.getPrototypeOf(a).constructor === acetateClass\r\n\t\t);\r\n\t\tif (ac) {\r\n\t\t\treturn ac;\r\n\t\t}\r\n\r\n\t\tac = new acetateClass(this.#glii);\r\n\t\tthis.addAcetate(ac);\r\n\t\treturn ac;\r\n\t}\r\n\r\n\t#animFrame;\r\n\r\n\t#queueRedraw() {\r\n\t\tthis.#animFrame ?? cancelAnimationFrame(this.#animFrame);\r\n\t\tthis.#animFrame = requestAnimationFrame(this.#boundRedraw);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method redraw(): this\r\n\t * Redraws acetates that need to do so, and composes them together.\r\n\t *\r\n\t * There is no need to call this manually, since it will be called once per\r\n\t * animation frame.\r\n\t */\r\n\tredraw(timestamp) {\r\n\t\tif (!this.#canvas) {\r\n\t\t\t// Do not redraw if the platina has already been destroyed. Do not queue redraw.\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif (!timestamp) {\r\n\t\t\t// This function is usually called with a timestamp, meaning\r\n\t\t\t// it's been called from a requestAnimationFrame().\r\n\t\t\t// If that's not the case, cancel anim frame to prevent race\r\n\t\t\t// conditions (queuing more than one call per frame)\r\n\t\t\tcancelAnimationFrame(this.#animFrame);\r\n\t\t}\r\n\r\n\t\tif (!this._bbox || !this._crsMatrix) {\r\n\t\t\treturn this.#queueRedraw();\r\n\t\t}\r\n\r\n\t\tlet updatedAcetates = 0;\r\n\r\n\t\tthis.#glii.refreshDrawingBufferSize();\r\n\r\n\t\t/**\r\n\t\t * @section Rendering events\r\n\t\t * @event prerender\r\n\t\t * Fired prior to performing a render (rendering `Acetate`s plus compositing them).\r\n\t\t * Can be used to set the map's viewport during animations (as long as there's\r\n\t\t * only one animation logic running).\r\n\t\t */\r\n\t\tthis.dispatchEvent(new Event(\"prerender\"));\r\n\r\n\t\t/// Trigger a full redraw of all acetates\r\n\t\t/// TODO: Do not redraw all acetates all the times; limit one acetate per frame,\r\n\t\t/// and rely on the acetate quads and compositor.\r\n\t\tthis._acetates.forEach((ac, i) => {\r\n\t\t\tif (ac.dirty && ac.redraw(this.#crs, this._crsMatrix, this._bbox)) {\r\n\t\t\t\t// Reset the CRS coordinates of the just-redrawn quad for this\r\n\t\t\t\t// acetate. This assumes the indices of those quads don't change.\r\n\t\t\t\tthis._acetateCoords.multiSet(i * 4, this._viewportCorners);\r\n\r\n\t\t\t\tupdatedAcetates++;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tif (updatedAcetates === 0) {\r\n\t\t\treturn this.#queueRedraw();\r\n\t\t}\r\n\r\n\t\t// Compose all acetates.\r\n\t\t// Compositor uses a depth buffer, so acetates are composed in their given z-indexes.\r\n\t\t/// FIXME: That's not true. Debug, debug, debug.\r\n\t\tthis._clear.run();\r\n\t\tthis._compositor.setUniform(\"uTransformMatrix\", this._crsMatrix);\r\n\t\tthis._acetates.forEach((ac, i) => {\r\n\t\t\t// The compositor has to run once per acetate due to the\r\n\t\t\t// inability to choose a texture in the frag shader.\r\n\t\t\t// (Ideally composition should be just one draw call,\r\n\t\t\t// and z-composition would be done via the z coordinate of fragments)\r\n\r\n\t\t\tthis._compositor.setTexture(\"uAcetateTex\", ac.asTexture());\r\n\t\t\tthis._compositor.runPartial(i * 6, 6);\r\n\t\t});\r\n\r\n\t\t/**\r\n\t\t * @event render\r\n\t\t * Fired just after performing a render (rendering `Acetate`s plus compositing them).\r\n\t\t */\r\n\t\tthis.dispatchEvent(new Event(\"render\"));\r\n\t\treturn this.#queueRedraw();\r\n\t}\r\n\r\n\t/**\r\n\t * @section Internal methods\r\n\t *\r\n\t * @method rebuildCompositor()\r\n\t * Rebuilds the WebGL program in charge of compositing the acetates.\r\n\t *\r\n\t * Should only be needed to run once.\r\n\t *\r\n\t * The compositor just dumps *one* texture from *one* acetate into the default renderbuffer\r\n\t * (i.e. the target ``). The \"clear\", then \"bind texture\"-\"dump acetate\" logic is\r\n\t * implemented elsewhere.\r\n\t *\r\n\t */\r\n\trebuildCompositor() {\r\n\t\t/// TODO: Somehow set the texture unit as a vertex attribute\r\n\t\t/// and have the frag shader map it to integer, choose the\r\n\t\t/// texture unit from there.\r\n\t\t/// https://stackoverflow.com/questions/51506704/webgl-pass-texture-from-vertex-shader-to-fragment-shader\r\n\t\t/// https://stackoverflow.com/questions/19592850/how-to-bind-an-array-of-textures-to-a-webgl-shader-uniform\r\n\t\t/// TODO: Handle the case of limited texture units!! (if a multi-texture\r\n\t\t/// compositor is viable)\r\n\r\n\t\tconst glii = this.#glii;\r\n\r\n\t\tthis._compositor = new glii.WebGL1Program({\r\n\t\t\tattributes: {\r\n\t\t\t\taZIndex: this._acetateAttrs.getBindableAttribute(0),\r\n\t\t\t\taUV: this._acetateAttrs.getBindableAttribute(1),\r\n\t\t\t\taCoords: this._acetateCoords,\r\n\t\t\t},\r\n\t\t\tuniforms: { uTransformMatrix: \"mat3\" },\r\n\t\t\ttextures: {\r\n\t\t\t\t// uAccumulator: this._accumulatorTexture,\r\n\t\t\t\tuAcetateTex: undefined,\r\n\t\t\t},\r\n\t\t\tvertexShaderSource: `void main() {\r\n\t\t\t\tgl_Position = vec4(\r\n\t\t\t\t\t(vec3(aCoords, 1.0) * uTransformMatrix).xy,\r\n\t\t\t\t\taZIndex + .5,\r\n\t\t\t\t\t1.0);\r\n\t\t\t\tvUV = aUV;\r\n\t\t\t}`,\r\n\t\t\tvaryings: { vUV: \"vec2\" },\r\n\t\t\tfragmentShaderSource: `void main(){\r\n\t\t\t\t// gl_FragColor = texture2D(uAcetateTex, vUV);\r\n\t\t\t\tvec4 texel = texture2D(uAcetateTex, vUV);\r\n\t\t\t\t//if (texel.a > 0.0) {\r\n\t\t\t\t\tgl_FragColor = texel;\r\n\t\t\t\t//}\r\n\t\t\t\t//gl_FragColor.r += gl_FragCoord.z;\r\n\t\t\t}`,\r\n\t\t\tindexBuffer: this._acetateQuads,\r\n\t\t\tdepth: this.#glii.LOWER,\r\n\t\t\tblend: {\r\n\t\t\t\tequationRGB: glii.FUNC_ADD,\r\n\t\t\t\tequationAlpha: glii.FUNC_ADD,\r\n\r\n\t\t\t\tsrcRGB: glii.SRC_ALPHA,\r\n\t\t\t\tdstRGB: glii.ONE_MINUS_SRC_ALPHA,\r\n\t\t\t\tsrcAlpha: glii.ONE,\r\n\t\t\t\tdstAlpha: glii.ONE_MINUS_SRC_ALPHA,\r\n\t\t\t},\r\n\t\t});\r\n\r\n\t\tthis.backgroundColour = this.#backgroundColour;\r\n\t}\r\n\r\n\t/**\r\n\t * @section View setters\r\n\t * @method setView(opts: SetView Options): this\r\n\t *\r\n\t * (Re-)sets the platina view to the given center/crs, scale, and yaw.\r\n\t *\r\n\t * Can trigger a redraw. Changes to the view state (center/crs/scale/yaw)\r\n\t * are atomic.\r\n\t */\r\n\tsetView({\r\n\t\t/**\r\n\t\t * @miniclass SetView Options (Platina)\r\n\t\t * @section\r\n\t\t * Calls to the `setView` method (of `GleoMap` and `Platina`) take an object\r\n\t\t * with any of the following properties. e.g.:\r\n\t\t *\r\n\t\t * ```\r\n\t\t * map.setView({ center: [ 100, 10 ], redraw: false });\r\n\t\t * map.setView({ scale: 1500, yawDegrees: 90 });\r\n\t\t * ```\r\n\t\t *\r\n\t\t * @option center: RawGeometry = undefined\r\n\t\t * The desired map center, as an instantiated `RawGeometry`/`Geometry`.\r\n\t\t * @alternative\r\n\t\t * @option center: Array of Number = undefined\r\n\t\t * The desired map center, as an `Array` of `Number`s. They will be\r\n\t\t * converted into a `Geometry` by means of `DefaultGeometry`.\r\n\t\t *\r\n\t\t * @option scale: Number = undefined\r\n\t\t * The desired map scale (**in CRS units per CSS pixel**). Mutually exclusive\r\n\t\t * with `span`.\r\n\t\t *\r\n\t\t * @option span: Number = undefined\r\n\t\t * The desired span of the map (in **CRS units** on the **diagonal of the viewport**).\r\n\t\t * Mutually exclusive with `scale`.\r\n\t\t *\r\n\t\t * @option yawDegrees: Number = 0\r\n\t\t * The desired yaw rotation, in degrees relative to \"north up\", clockwise.\r\n\t\t * Mutually exclusive with `yawRadians`.\r\n\t\t *\r\n\t\t * @option yawRadians: Number = 0\r\n\t\t * The desired yaw rotation, in radians relative to \"north up\", counterclockwise.\r\n\t\t * Mutually exclusive with `yawDegrees`.\r\n\t\t *\r\n\t\t * @option crs: BaseCRS = undefined\r\n\t\t * The desired CRS of the map.\r\n\t\t **/\r\n\t\tcenter,\r\n\t\tcrs,\r\n\t\tscale,\r\n\t\tspan,\r\n\t\tyawDegrees,\r\n\t\tyawRadians,\r\n\t} = {}) {\r\n\t\tconst [w, h] = this.pxSize;\r\n\r\n\t\tif (span && !scale) {\r\n\t\t\t/// TODO: Set some kind of flag, so that resizing the canvas\r\n\t\t\t/// will keep either the span or the scale.\r\n\t\t\tscale = span / Math.sqrt(w * w + h * h);\r\n\t\t}\r\n\r\n\t\tif (crs && crs !== this.#crs) {\r\n\t\t\t/**\r\n\t\t\t * @class Platina\r\n\t\t\t * @section View change events\r\n\t\t\t * @event crschange\r\n\t\t\t * Dispatched when the CRS changes explicitly (by setting the platina's\r\n\t\t\t * CRS, or passing a `crs` option to a `setView` call)\r\n\t\t\t */\r\n\t\t\tthis.fire(\"crschange\", {\r\n\t\t\t\toldCRS: this.#crs,\r\n\t\t\t\tnewCRS: crs,\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\t//const prevCRS = this.#crs;\r\n\t\tthis.#crs = crs || this.#crs;\r\n\t\tthis.#center = center ? factory(center) : this.#center;\r\n\t\tthis.#scale = scale || this.#scale;\r\n\r\n\t\t// If platina is not fully initialized (crs + center + zoom),\r\n\t\t// fail silently\r\n\t\tif (\r\n\t\t\tthis.#crs === undefined ||\r\n\t\t\tthis.#center === undefined ||\r\n\t\t\tthis.#scale === undefined\r\n\t\t) {\r\n\t\t\treturn this;\r\n\t\t} else if (this.#invalidViewWarningTimeout) {\r\n\t\t\tclearTimeout(this.#invalidViewWarningTimeout);\r\n\t\t\tthis.#invalidViewWarningTimeout = undefined;\r\n\t\t}\r\n\r\n\t\tif (yawRadians !== undefined) {\r\n\t\t\tthis.#yaw = yawRadians;\r\n\t\t} else if (yawDegrees !== undefined) {\r\n\t\t\tthis.#yaw = -yawDegrees * (Math.PI / 180);\r\n\t\t}\r\n\r\n\t\tif (this.#center.crs !== this.#crs) {\r\n\t\t\tthis.#center = this.#center.toCRS(this.#crs);\r\n\t\t}\r\n\r\n\t\tif (!isFinite(this.#scale)) {\r\n\t\t\tthrow new Error(\"Scale must have a finite value\");\r\n\t\t}\r\n\t\tif (this.#scale <= 0) {\r\n\t\t\tthrow new Error(\"Scale must be a positive number\");\r\n\t\t}\r\n\r\n\t\t// Cover edge cases where the center is very near to the CRS's\r\n\t\t// wrapping period.\r\n\t\t// \t\tcenter.xy[0] %= this.#crs.wrapPeriodX;\r\n\t\t// \t\tcenter.xy[1] %= this.#crs.wrapPeriodY;\r\n\t\tthis.#center.coords = this.crs.wrap(this.#center.coords, [0, 0]);\r\n\r\n\t\tif (isNaN(this.#center.coords[0]) || isNaN(this.#center.coords[1])) {\r\n\t\t\tthrow new Error(\"New center must be finite numbers\");\r\n\t\t}\r\n\r\n\t\t/// Check if the center Coord can be represented with enough\r\n\t\t/// precision in its own CRS; as well as whether a coordinate\r\n\t\t/// one pixel away is still within precision (\"the floating point\r\n\t\t/// precision is smaller than the coordinate delta between two\r\n\t\t/// adjacent pixels\").\r\n\t\t/// If not, create a `OffsetCRS`.\r\n\r\n\t\tconst log2scale = log2(this.#scale);\r\n\t\tconst log2size = log2(max(h, w));\r\n\t\tconst log2distance = log2(\r\n\t\t\tmax(abs(this.#center.coords[0]), abs(this.#center.coords[1]))\r\n\t\t);\r\n\r\n\t\tif (\r\n\t\t\tNumber.isFinite(log2distance) &&\r\n\t\t\tlog2distance - log2scale /*+ log2size*/ > this.#precisionThreshold\r\n\t\t) {\r\n\t\t\tconsole.warn(\r\n\t\t\t\t\"Requested CRS center and scale would cause floating point artifacts. An offset CRS shall be created.\"\r\n\t\t\t);\r\n\t\t\tconsole.log(\r\n\t\t\t\t\"scale/size/center log2 / threshold\",\r\n\t\t\t\tlog2scale,\r\n\t\t\t\tlog2size,\r\n\t\t\t\tlog2distance,\r\n\t\t\t\tthis.#precisionThreshold\r\n\t\t\t);\r\n\r\n\t\t\t// The new CRS offset is *absolute* to the base CRS, not relative to it.\r\n\t\t\tlet newOffset = this.#crs.offsetToBase(this.#center.coords);\r\n\r\n\t\t\tconst newCRS = new OffsetCRS(new Geometry(this.#crs, newOffset));\r\n\r\n\t\t\t/**\r\n\t\t\t * @event crsoffset\r\n\t\t\t * Dispatched when the CRS undergoes an implicit offset to avoid precision loss.\r\n\t\t\t */\r\n\t\t\tthis.fire(\"crsoffset\", {\r\n\t\t\t\toldCRS: this.#crs,\r\n\t\t\t\tnewCRS: newCRS,\r\n\t\t\t\toffset: newOffset,\r\n\t\t\t});\r\n\r\n\t\t\tthis.#crs = newCRS;\r\n\t\t\tthis.#center = new Geometry(this.#crs, [0, 0]);\r\n\t\t}\r\n\r\n\t\t// console.log(\"Centers; \", center.xy, center.toCRS(epsg4326).xy, log2distance);\r\n\r\n\t\tlet [cX, cY] = this.#center.coords;\r\n\r\n\t\t// Raster pixel fidelity, assuming raster coordinates are always aligned\r\n\t\t// to the [0,0] origin of CRS coordinates.\r\n\t\t// FIXME: Implement raster fidelity scales, and adjust to them instead\r\n\t\t// to always trusting the current scale.\r\n\t\t// TODO: Fidelity should also work on 90-degree rotations.\r\n\t\tif (this.#yaw > -1e-10 && this.#yaw < 1e-10) {\r\n\t\t\tconst [crsOffsetX, crsOffsetY] = this.#crs.offset ? this.#crs.offset : [0, 0];\r\n\t\t\tcX = cX - (cX % this.#scale) - (crsOffsetX % this.#scale);\r\n\t\t\tif (w % 2) {\r\n\t\t\t\tcX += this.#scale / 2;\r\n\t\t\t}\r\n\r\n\t\t\tcY = cY - (cY % this.#scale) - (crsOffsetY % this.#scale);\r\n\t\t\tif (h % 2) {\r\n\t\t\t\tcY += this.#scale / 2;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst sX = 2 / (w * this.#scale);\r\n\t\tconst sY = 2 / (h * this.#scale);\r\n\r\n\t\t/// Yaw rotation\r\n\t\tconst cosYaw = Math.cos(this.#yaw);\r\n\t\tconst sinYaw = Math.sin(this.#yaw);\r\n\r\n\t\t// Scale, translate, rotate around point\r\n\t\t// See https://www.wolframalpha.com/input/?i2d=true&i=Composition%5C%2840%29+ScalingTransform%5C%2891%29%5C%2840%29%CF%87%5C%2844%29%CF%88%5C%2841%29%5C%2893%29%5C%2844%29+TranslationTransform%5C%2891%29%7B-x%2C-y%7D%5C%2893%29%5C%2844%29+RotationTransform%5C%2891%29alpha%5C%2844%29+%7Bx%2Cy%7D%5C%2893%29%5C%2841%29\r\n\t\t// prettier-ignore\r\n\t\tthis._crsMatrix = [\r\n\t\t\tsX * cosYaw, -sX * sinYaw, sX * cY * sinYaw - sX * cX * cosYaw,\r\n\t\t\tsY * sinYaw, sY * cosYaw, -sY * cY * cosYaw - sY * cX * sinYaw,\r\n\t\t\t 0, 0, 1,\r\n\t\t];\r\n\r\n\t\t// Strip the offset from the CRS matrix. Used for the drag actuator and\r\n\t\t// the acetate wrapping.\r\n\t\t// prettier-ignore\r\n\t\tthis._rotationScaleMatrix = [\r\n\t\t\tthis._crsMatrix[0], this._crsMatrix[1], 0,\r\n\t\t\tthis._crsMatrix[3], this._crsMatrix[4], 0,\r\n\t\t\t 0, 0, 1,\r\n\t\t];\r\n\r\n\t\t// Cache the boundaries of the visible bounds. This will be used for\r\n\t\t// re-setting the acetate's vertex coordinates.\r\n\t\tconst invMatrix = invert(new Array(9), this._crsMatrix);\r\n\r\n\t\t// Damn glmatrix notation difference, again.\r\n\t\ttranspose(invMatrix, invMatrix);\r\n\r\n\t\tconst vec = [];\r\n\t\tthis._bbox.reset();\r\n\r\n\t\t// prettier-ignore\r\n\t\tconst corners = [\r\n\t\t\t[-1, -1, 1],\r\n\t\t\t[-1, 1, 1],\r\n\t\t\t[ 1, 1, 1],\r\n\t\t\t[ 1, -1, 1],\r\n\t\t].map(corner => transformMat3(vec, corner, invMatrix).slice(0,2));\r\n\r\n\t\tcorners.forEach((corner) => this._bbox.expandPair(corner));\r\n\t\tthis._viewportCorners = corners.flat();\r\n\r\n\t\t/**\r\n\t\t * @event viewchanged\r\n\t\t * Fired whenever the viewport changes - center, scale or yaw.\r\n\t\t *\r\n\t\t * Details inclide the center, scale, and the affine matrix for converting\r\n\t\t * CRS coordinates into clipspace coordinates.\r\n\t\t *\r\n\t\t * This event might fire at every frame during interactions and animations.\r\n\t\t */\r\n\t\tthis.fire(\"viewchanged\", {\r\n\t\t\tcenter: this.#center,\r\n\t\t\tscale: this.#scale,\r\n\t\t\tmatrix: this._crsMatrix,\r\n\t\t});\r\n\r\n\t\tthis._acetates.forEach((ac) => (ac.dirty = true));\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section View setters\r\n\t * @method fitBounds(bounds: Array of Number, opts?: SetView Options): this\r\n\t * Sets the platina's center and scale so that the given bounds (given in\r\n\t * `[minX, minY, maxX, maxY]` form, and in the platina's CRS) are fully\r\n\t * visible.\r\n\t *\r\n\t * Any other given `SetView Options` will be merged with the calculated center&scale.\r\n\t * @alternative\r\n\t * @method fitBounds(bounds: ExpandBox, opts?: SetView Options): this\r\n\t * Idem, but using an `ExpandBox` instead.\r\n\t */\r\n\tfitBounds(bounds, opts = {}) {\r\n\t\t/// FIXME: Currently sets the yaw to zero. Instead it should respect it.\r\n\t\tlet minX, minY, maxX, maxY;\r\n\t\tif (bounds instanceof ExpandBox) {\r\n\t\t\t({ minX, minY, maxX, maxY } = bounds);\r\n\t\t} else {\r\n\t\t\t[minX, minY, maxX, maxY] = bounds;\r\n\t\t}\r\n\r\n\t\tconst [w, h] = this.pxSize;\r\n\r\n\t\tconst center = new Geometry(this.crs, [(minX + maxX) / 2, (minY + maxY) / 2]);\r\n\t\tconst scale = Math.max((maxX - minX) / w, (maxY - minY) / h);\r\n\t\treturn this.setView({\r\n\t\t\t...opts,\r\n\t\t\tcenter: center,\r\n\t\t\tscale: scale,\r\n\t\t\tyaw: 0,\r\n\t\t});\r\n\t}\r\n\r\n\t/**\r\n\t * @method zoomInto(geometry: Geometry, scale: Number, opts?: setView Options): this\r\n\t * Performs a `setView` operation so that the given geometry stays at the\r\n\t * same pixel.\r\n\t *\r\n\t * Meant for user interactions on the map, including double-clicking and\r\n\t * zooming into clusters from a `Clusterer`. Akin to Leaflet's `zoomAround`.\r\n\t */\r\n\tzoomInto(geometry, scale, opts = {}) {\r\n\t\tconst [canvasX, canvasY] = this.geomToPx(geometry);\r\n\t\tconst [w, h] = this.pxSize;\r\n\t\tlet clipX = (canvasX * 2) / w - 1;\r\n\t\tlet clipY = (canvasY * -2) / h + 1;\r\n\r\n\t\tconst scaleFactor = scale / this.scale;\r\n\r\n\t\tclipX -= clipX * scaleFactor;\r\n\t\tclipY -= clipY * scaleFactor;\r\n\r\n\t\tconst vec = [clipX, clipY, 1];\r\n\t\tconst invMatrix = invert(new Array(9), this._crsMatrix);\r\n\t\ttranspose(invMatrix, invMatrix);\r\n\t\ttransformMat3(vec, vec, invMatrix);\r\n\r\n\t\tconst targetCenter = new Geometry(this.crs, [vec[0], vec[1]], { wrap: false });\r\n\r\n\t\treturn this.setView({ ...opts, center: targetCenter, scale: scale });\r\n\t}\r\n\r\n\t/**\r\n\t * @section View setter/getter properties\r\n\t * These properties allow to fetch the state of the view then read, *and*\r\n\t * modify it. Setting the value of any of these properties has the same\r\n\t * effect as calling `setView()` with appropriate values.\r\n\t *\r\n\t * Setting a value does not guarantee that the final value will be the\r\n\t * given one. For example, when setting the center and immediatly then\r\n\t * querying the center, the actual center can be a reprojection of the\r\n\t * given one.\r\n\t *\r\n\t * @property center: RawGeometry\r\n\t * The center of the map, as a point geometry.\r\n\t * @property scale: Number\r\n\t * The scale, in CRS units per CSS pixel.\r\n\t * @property span: Number\r\n\t * The span, in CRS units per diagonal.\r\n\t * @property crs: BaseCRS\r\n\t * The CRS being used by the platina.\r\n\t * @property yawDegrees: Number\r\n\t * The yaw rotation angle, in clockwise degrees.\r\n\t * @property yawRadians: Number\r\n\t * The yaw rotation angle, in counter-clockwise radians.\r\n\t * @property backgrounColour: Colour\r\n\t * Self-explanatory\r\n\t */\r\n\tget center() {\r\n\t\treturn this.#center;\r\n\t}\r\n\tset center(c) {\r\n\t\treturn this.setView({ center: c });\r\n\t}\r\n\r\n\tget scale() {\r\n\t\treturn this.#scale;\r\n\t}\r\n\tset scale(s) {\r\n\t\treturn this.setView({ scale: s });\r\n\t}\r\n\r\n\tget span() {\r\n\t\tconst [w, h] = this.pxSize;\r\n\t\treturn this.#scale * Math.sqrt(w * w + h * h);\r\n\t}\r\n\tset span(s) {\r\n\t\treturn this.setView({ span: s });\r\n\t}\r\n\r\n\tget crs() {\r\n\t\treturn this.#crs;\r\n\t}\r\n\tset crs(c) {\r\n\t\treturn this.setView({ crs: c });\r\n\t}\r\n\r\n\tget yawRadians() {\r\n\t\treturn this.#yaw;\r\n\t}\r\n\tget yawDegrees() {\r\n\t\treturn (-this.#yaw * 180) / Math.PI;\r\n\t}\r\n\tset yawRadians(y) {\r\n\t\treturn this.setView({ yawRadians: y });\r\n\t}\r\n\tset yawDegrees(y) {\r\n\t\treturn this.setView({ yawDegrees: y });\r\n\t}\r\n\r\n\tget backgroundColour() {\r\n\t\treturn this.#backgroundColour;\r\n\t}\r\n\tset backgroundColour(c) {\r\n\t\tthis.#backgroundColour = parseColour(c).map((n) => n / 255);\r\n\t\tthis._clear = new this.#glii.WebGL1Clear({\r\n\t\t\tcolor: this.#backgroundColour,\r\n\t\t\t// \t\t\tdepth: -1,\r\n\t\t});\r\n\t}\r\n\r\n\t/**\r\n\t * @property bbox: ExpandBox\r\n\t * A rectangular bounding box that completely covers the map\r\n\t * viewport. This box is aligned to the CRS, not to the viewport. Setting its\r\n\t * value is akin to running `fitBounds`.\r\n\t */\r\n\tget bbox() {\r\n\t\treturn this._bbox;\r\n\t}\r\n\tset bbox(b) {\r\n\t\tthis.fitBounds(b);\r\n\t}\r\n\r\n\t/**\r\n\t * @section View getter properties\r\n\t * @property pxSize: Array of Number\r\n\t * The size of the canvas, in CSS pixels, in `[width, height]` form. Read-only.\r\n\t */\r\n\tget pxSize() {\r\n\t\treturn [this._pxWidth, this._pxHeight];\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @property glii: GliiFactory\r\n\t * The Glii instance used by the platina. Read-only.\r\n\t */\r\n\tget glii() {\r\n\t\treturn this.#glii;\r\n\t}\r\n\r\n\t/**\r\n\t * @property glii: GleoMap\r\n\t * The `GleoMap` instance used to spawn this platina. If the platina\r\n\t * was created stand-alone, this will be `undefined` instead.\r\n\t */\r\n\tget map() {\r\n\t\treturn this.#map;\r\n\t}\r\n\r\n\t/**\r\n\t * @property resizable: Boolean\r\n\t * Whether the platina reacts to changes in its DOM container. Read-only.\r\n\t */\r\n\tget resizable() {\r\n\t\treturn this.#resizable;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Conversion methods\r\n\t * @method pxToGeom(xy: Array of Number, wrap?: Boolean): Geometry\r\n\t * Given a (CSS) pixel coordinate relative to the `` of the map,\r\n\t * in the form `[x, y]`, returns the point `Geometry` (in the map's CRS)\r\n\t * which corresponds to that pixel, at the map's current center/scale.\r\n\t *\r\n\t * The resulting geometry will be wrapped by default. To avoid this,\r\n\t * set `wrap` to `false`.\r\n\t *\r\n\t * This is akin to Leaflet's `containerPointToLatLng()`. Inverse of `geomToPx`.\r\n\t */\r\n\tpxToGeom([x, y], wrap = true) {\r\n\t\tif (!this._crsMatrix) {\r\n\t\t\t// Edge case - pointer events before center/scale has been set.\r\n\t\t\tif (this.#crs) {\r\n\t\t\t\treturn new Geometry(this.#crs, [NaN, NaN]);\r\n\t\t\t} else {\r\n\t\t\t\treturn undefined;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst dpr = devicePixelRatio ?? 1;\r\n\r\n\t\tconst w = this._pxWidth / dpr;\r\n\t\tconst h = this._pxHeight / dpr;\r\n\t\t// Convert the px to clipspace, then multiply by this._crsMatrix.\r\n\t\t//const [w, h] = this.#glii.refreshDrawingBufferSize();\r\n\r\n\t\tconst clipX = (x * 2) / w - 1;\r\n\t\tconst clipY = (y * -2) / h + 1;\r\n\r\n\t\tconst vec = [clipX, clipY, 1];\r\n\r\n\t\tconst invMatrix = invert(new Array(9), this._crsMatrix);\r\n\r\n\t\tif (!invMatrix) {\r\n\t\t\t// There's some NaN values somewhere\r\n\t\t\tdebugger;\r\n\t\t}\r\n\r\n\t\t// The matrix transposition is needed only because gl-matrix's notation\r\n\t\t// is transposed relative to WebGL's (or glii's) notation.\r\n\t\ttranspose(invMatrix, invMatrix);\r\n\t\t// \t\ttransformMat3(vec, vec, this._invMatrix);\r\n\t\ttransformMat3(vec, vec, invMatrix);\r\n\t\treturn new Geometry(this.#crs, [vec[0], vec[1]], { wrap });\r\n\t}\r\n\r\n\t/**\r\n\t * @method geomToPx(Geometry): Array of Number\r\n\t * Given a point `Geometry`, returns the `[x, y]` coordinates of the (CSS)\r\n\t * pixel relative to the `` of the map corresponding to that geometry\r\n\t * (for the map's current center/scale).\r\n\t *\r\n\t * This is akin to Leaflet's `latLngToContainerPoint()`. Inverse of `pxToGeom`.\r\n\t */\r\n\tgeomToPx(geom) {\r\n\t\tif (!this._crsMatrix) {\r\n\t\t\t// Edge case - pins before center/scale have been set\r\n\t\t\treturn [NaN, NaN];\r\n\t\t}\r\n\r\n\t\tlet projectedGeom = geom.toCRS(this.#crs);\r\n\r\n\t\t// Matrix transposition stuff because of\r\n\t\t// https://gitlab.com/IvanSanchez/gleo/-/issues/10\r\n\t\tconst transMatrix = transpose(new Array(9), this._crsMatrix);\r\n\t\tconst vec = [projectedGeom.coords[0], projectedGeom.coords[1], 1];\r\n\t\ttransformMat3(vec, vec, transMatrix);\r\n\r\n\t\tconst dpr = devicePixelRatio ?? 1;\r\n\r\n\t\treturn [\r\n\t\t\t(vec[0] / 2 + 0.5) * this._pxWidth * dpr,\r\n\t\t\t(0.5 - vec[1] / 2) * this._pxHeight * dpr,\r\n\t\t];\r\n\t}\r\n\r\n\t#loaders = [];\r\n\r\n\t/**\r\n\t * @section Symbol/Loader management\r\n\t * @method add(symbol: GleoSymbol): this\r\n\t * Adds the given `GleoSymbol` to the appropriate acetate.\r\n\t *\r\n\t * Users should note that repeated calls to `add()` are, in performance terms,\r\n\t * **much worse** than a single call to `multiAdd()`. Try to avoid repeated calls\r\n\t * to `add()` inside a loop.\r\n\t *\r\n\t * @alternative\r\n\t * @method add(loader: Loader): this\r\n\t * Attaches the given `Loader` to the map.\r\n\t */\r\n\tadd(symbol) {\r\n\t\treturn this.multiAdd([symbol]);\r\n\t}\r\n\r\n\t/**\r\n\t * @method multiAdd(symbols: Array of GleoSymbol): this\r\n\t * Adds the given `GleoSymbol`s to the appropriate acetate(s).\r\n\t * @alternative\r\n\t * @method multiAdd(loaders: Arrary of Loader): this\r\n\t * Adds the given `Loader`s to the platina.\r\n\t */\r\n\tmultiAdd(symbols) {\r\n\t\tconst bins = new Map();\r\n\r\n\t\t// Just for MultiSymbol class\r\n\t\tsymbols = symbols\r\n\t\t\t.map((s) => {\r\n\t\t\t\tif (s instanceof Loader) {\r\n\t\t\t\t\tthis.#loaders.push(s);\r\n\r\n\t\t\t\t\t// Loaders need to trigger their functionality.\r\n\t\t\t\t\t// s.addTo(this);\r\n\t\t\t\t\ts._addToPlatina(this);\r\n\t\t\t\t\ts.on(\"symbolsadded\", this.#boundMultiAdd);\r\n\t\t\t\t\ts.on(\"symbolsremoved\", this.#boundMultiRemove);\r\n\t\t\t\t\treturn []; // Skip from acetate-finding logic.\r\n\t\t\t\t} else if (s.symbols) {\r\n\t\t\t\t\t// MultiSymbol\r\n\t\t\t\t\ts.target = this;\r\n\t\t\t\t\treturn s.symbols;\r\n\t\t\t\t} else {\r\n\t\t\t\t\treturn [s];\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\t.flat();\r\n\r\n\t\tsymbols.forEach((s) => {\r\n\t\t\tconst ac = s.constructor.Acetate;\r\n\t\t\tif (ac) {\r\n\t\t\t\tconst bin = bins.get(ac);\r\n\t\t\t\tif (bin) {\r\n\t\t\t\t\tbin.push(s);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tbins.set(ac, [s]);\r\n\t\t\t\t}\r\n\t\t\t} else if (!!s.expand) {\r\n\t\t\t\t// Very very likely a Spider\r\n\t\t\t\ts.addTo(this);\r\n\t\t\t} else {\r\n\t\t\t\tdebugger;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tfor (let [ac, syms] of bins.entries()) {\r\n\t\t\tthis.getAcetateOfClass(ac).multiAdd(syms);\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method remove(symbol: GleoSymbol): this\r\n\t * Removes one symbol from this map.\r\n\t */\r\n\tremove(symbol) {\r\n\t\tsymbol.remove();\r\n\t\tif (symbol instanceof Loader) {\r\n\t\t\tthis.#loaders = this.#loaders.filter((l) => l !== symbol);\r\n\r\n\t\t\tsymbol.off(\"symbolsadded\", this.#boundMultiAdd);\r\n\t\t\tsymbol.off(\"symbolsremoved\", this.#boundMultiRemove);\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method multiRemove(symbols: Array of GleoSymbol): this\r\n\t * Removes several symbols from this map.\r\n\t */\r\n\tmultiRemove(symbols) {\r\n\t\tconst bins = new Map();\r\n\r\n\t\tsymbols\r\n\t\t\t.filter((s) => s instanceof Loader)\r\n\t\t\t.forEach((s) => {\r\n\t\t\t\tthis.#loaders = this.#loaders.filter((l) => l !== s);\r\n\r\n\t\t\t\ts.off(\"symbolsadded\", this.#boundMultiAdd);\r\n\t\t\t\ts.off(\"symbolsremoved\", this.#boundMultiRemove);\r\n\t\t\t});\r\n\r\n\t\t// Just for MultiSymbol class\r\n\t\tsymbols.forEach((s) => {\r\n\t\t\tif (s.symbols) {\r\n\t\t\t\tsymbols = symbols.concat(s.symbols);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t/// Group by acetate, let the acetate do the multiRemove().\r\n\t\tsymbols\r\n\t\t\t.filter((s) => s._inAcetate)\r\n\t\t\t.forEach((s) => {\r\n\t\t\t\tconst ac = s._inAcetate;\r\n\t\t\t\tif (ac) {\r\n\t\t\t\t\tconst bin = bins.get(ac);\r\n\t\t\t\t\tif (bin) {\r\n\t\t\t\t\t\tbin.push(s);\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tbins.set(ac, [s]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t// Handle Spiders\r\n\t\tsymbols.filter((s) => !!s.expand).forEach((s) => s.remove());\r\n\r\n\t\tbins.forEach((symbols, acetate) => acetate.multiRemove(symbols));\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method has(symbol: GleoSymbol): Boolean\r\n\t * Returns `true` if this platina contains the given symbol, false otherwise.\r\n\t * @alternative\r\n\t * @method has(symbol: Loader): Boolean\r\n\t * Returns `true` if this platina contains the given loader, false otherwise.\r\n\t */\r\n\thas(s) {\r\n\t\tif (s instanceof Loader) {\r\n\t\t\treturn this.#loaders.includes(s);\r\n\t\t} else {\r\n\t\t\tconst matches = this._acetates.filter((a) => a instanceof s.Acetate);\r\n\t\t\treturn matches.some((a) => a.has(s));\r\n\t\t}\r\n\t}\r\n\r\n\t// Internal use only. Called by the resize observer; resizes the framebuffers of\r\n\t// each acetate and triggers a re-render.\r\n\t#onResize(ev) {\r\n\t\tlet x, y;\r\n\r\n\t\tif (!this.#canvas) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif (ev) {\r\n\t\t\tx = ev.detail.x;\r\n\t\t\ty = ev.detail.y;\r\n\t\t} else {\r\n\t\t\tlet rect = this.#canvas.getClientRects && this.#canvas.getClientRects()[0];\r\n\t\t\tif (rect) {\r\n\t\t\t\t// Canvas is in the DOM, possibly with applied CSS\r\n\t\t\t\tx = rect.width;\r\n\t\t\t\ty = rect.height;\r\n\t\t\t} else if (this.#canvas.width) {\r\n\t\t\t\t// Canvas is *not* in the DOM, so trust its width/height\r\n\t\t\t\t/// FIXME: What if this.#canvas is a WebGLRenderingContext?\r\n\t\t\t\tx = this.#canvas.width;\r\n\t\t\t\ty = this.#canvas.height;\r\n\t\t\t} else if (this.#canvas.drawingBufferWidth) {\r\n\t\t\t\tx = this.#canvas.drawingBufferWidth;\r\n\t\t\t\ty = this.#canvas.drawingBufferHeight;\r\n\t\t\t}\r\n\t\t\tconst dpr = devicePixelRatio ?? 1;\r\n\t\t\tx *= dpr;\r\n\t\t\ty *= dpr;\r\n\t\t}\r\n\r\n\t\tif (x === 0 || y === 0) {\r\n\t\t\tthrow new Error(\"Map size is zero\");\r\n\t\t}\r\n\r\n\t\tthis._pxWidth = this.#canvas.width = x = Math.floor(x);\r\n\t\tthis._pxHeight = this.#canvas.height = y = Math.floor(y);\r\n\r\n\t\tthis.fire(\"resize\", { x, y });\r\n\r\n\t\tthis._acetates.forEach((ac) => {\r\n\t\t\tac.resize(x, y);\r\n\t\t});\r\n\r\n\t\tif (this.#center && this.#scale) {\r\n\t\t\tthis.setView({});\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @section Scale and pixel fidelity methods\r\n\t * Several use cases call for re-using a set of scale values.\r\n\t *\r\n\t * In particular, raster symbols (including tiles) have a preferred (or\r\n\t * set of preferred) scale factors to be shown as, so that they are shown at\r\n\t * a 1:1 raster pixel / screen pixel ratio.\r\n\t *\r\n\t * A `Platina` does not enforce these scale values; the usual way to enforce\r\n\t * them is by using a `ZoomYawSnapActuator`.\r\n\t */\r\n\t#scaleStopsPerCRS = new Map();\r\n\t/**\r\n\t * @method setScaleStop(crsName: String, scale: Number): this\r\n\t * Sets a scale stop for the given CRS **name**.\r\n\t */\r\n\tsetScaleStop(crsName, scale) {\r\n\t\tif (!this.#scaleStopsPerCRS.has(crsName)) {\r\n\t\t\tthis.#scaleStopsPerCRS.set(crsName, new Map());\r\n\t\t}\r\n\t\tconst stops = this.#scaleStopsPerCRS.get(crsName);\r\n\t\tif (stops.has(scale)) {\r\n\t\t\tstops.set(scale, stops.get(scale) + 1);\r\n\t\t} else {\r\n\t\t\tstops.set(scale, 1);\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method removeScaleStop(crsName: String, scale: Number): this\r\n\t * Reverse of `setScaleStop`.\r\n\t */\r\n\tremoveScaleStop(crsName, scale) {\r\n\t\tif (!this.#scaleStopsPerCRS.has(crsName)) {\r\n\t\t\treturn this;\r\n\t\t}\r\n\t\tconst stops = this.#scaleStopsPerCRS.get(crsName);\r\n\t\tif (!stops.has(scale)) {\r\n\t\t\treturn this;\r\n\t\t} else {\r\n\t\t\tconst usageCount = stops.get(scale);\r\n\t\t\tif (usageCount === 1) {\r\n\t\t\t\tstops.delete(scale);\r\n\t\t\t} else {\r\n\t\t\t\tstops.set(scale, usageCount - 1);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method getScaleStops(crsName): Array of Number\r\n\t * Returns the scale stops for the given CRS name\r\n\t */\r\n\tgetScaleStops(crsName) {\r\n\t\tconst crsStops = this.#scaleStopsPerCRS.get(crsName);\r\n\t\treturn crsStops ? Array.from(this.#scaleStopsPerCRS.get(crsName).keys()) : [];\r\n\t}\r\n\r\n\t// Will hold the canvas-relative pixel coordinates of the last `pointerDown`\r\n\t// event, on a per-pointer basis.\r\n\t#onPointerDownCoords = {};\r\n\r\n\t// Internal use only. Decorates a DOM event, so that it has the CRS coordinates\r\n\t// in the event's properties.\r\n\t// It then dispatches on all acetates (so they can dispatch on the appropriate\r\n\t// symbol)\r\n\t// Additionally prevents dispatching a `click` event if there's been a drag.\r\n\t// Needs to *not* be private, for leaflet-gleo compatibility\r\n\t_onPointerEvent(ev) {\r\n\t\tif (\"canvasX\" in ev) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tconst [canvasX, canvasY] = getMousePosition(ev, this.#canvas);\r\n\t\tconst geometry = this.pxToGeom([canvasX, canvasY], false);\r\n\r\n\t\t// Prevent click on drag\r\n\t\tif (ev.type === \"pointerdown\") {\r\n\t\t\tthis.#onPointerDownCoords[ev.pointerId] = [canvasX, canvasY];\r\n\t\t} else if (ev.type === \"click\" || ev.type === \"auxclick\") {\r\n\t\t\tif (\"pointerId\" in ev) {\r\n\t\t\t\t// Non-firefox branch: compare against the pointerdown coords\r\n\t\t\t\t// for the event's pointerId\r\n\r\n\t\t\t\tconst [downX, downY] = this.#onPointerDownCoords[ev.pointerId];\r\n\r\n\t\t\t\tif (Math.abs(downX - canvasX) > 3 || Math.abs(downY - canvasY) > 3) {\r\n\t\t\t\t\t// The click happened far away from the pointerdown, ignore this click\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\t// Firefox branch: compare against *all* pointerdown canvas coords\r\n\t\t\t\tif (\r\n\t\t\t\t\tObject.values(this.#onPointerDownCoords).every(\r\n\t\t\t\t\t\t([downX, downY]) =>\r\n\t\t\t\t\t\t\tMath.abs(downX - canvasX) > 3 || Math.abs(downY - canvasY) > 3\r\n\t\t\t\t\t)\r\n\t\t\t\t) {\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Properties of MouseEvents/PointerEvents are not enumerable, so doing {...ev} is\r\n\t\t// unfortunately not an option.\r\n\t\tlet init = {\r\n\t\t\t// EventInit\r\n\t\t\tbubbles: ev.bubbles,\r\n\t\t\tcancelable: ev.cancelable,\r\n\t\t\tcomposed: ev.composed,\r\n\t\t\t// \t\t\ttarget: this,\r\n\r\n\t\t\t// UIEventInit\r\n\t\t\tdetail: ev.detail,\r\n\t\t\tview: ev.view,\r\n\r\n\t\t\t// mouseEventInit\r\n\t\t\tscreenX: ev.screenX,\r\n\t\t\tscreenY: ev.screenY,\r\n\t\t\tclientX: ev.clientX,\r\n\t\t\tclientY: ev.clientY,\r\n\t\t\tctrlKey: ev.ctrlKey,\r\n\t\t\tshiftKey: ev.shiftKey,\r\n\t\t\taltKey: ev.altKey,\r\n\t\t\tmetaKey: ev.metaKey,\r\n\t\t\tbutton: ev.button,\r\n\t\t\tbuttons: ev.buttons,\r\n\t\t\trelatedTarget: ev.relatedTarget,\r\n\t\t\tregion: ev.region,\r\n\r\n\t\t\t// GleoPointerEvent\r\n\t\t\tgeometry,\r\n\t\t\tcanvasX,\r\n\t\t\tcanvasY,\r\n\t\t};\r\n\r\n\t\tif (ev instanceof PointerEvent) {\r\n\t\t\t// i.e. do not add these properties to the `click`/`auxclick`/\r\n\t\t\t// `contextmenu` `MouseEvent`s, even though they *should* be\r\n\t\t\t// `PointerEvent`s as per https://w3c.github.io/pointerevents/#the-click-auxclick-and-contextmenu-events\r\n\t\t\t// (These events are `MouseEvent`s in Firefox; Chrom[ium|e] correctly\r\n\t\t\t// dispatches `PointerEvent`s).\r\n\t\t\tinit = {\r\n\t\t\t\t...init,\r\n\t\t\t\tpointerId: ev.pointerId,\r\n\t\t\t\twidth: ev.width,\r\n\t\t\t\theight: ev.height,\r\n\t\t\t\tpressure: ev.pressure,\r\n\t\t\t\ttangentialPressure: ev.tangentialPressure,\r\n\t\t\t\ttiltX: ev.tiltX,\r\n\t\t\t\ttiltY: ev.tiltY,\r\n\t\t\t\ttwist: ev.twist,\r\n\t\t\t\tpointerType: ev.pointerType,\r\n\t\t\t\tisPrimary: ev.isPrimary,\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\t// Create a new synthetic pointer event with the data from the real one,\r\n\t\t// so that it can be dispatched again with the decorated detail.\r\n\t\tconst EventProto = ev instanceof PointerEvent ? GleoPointerEvent : GleoMouseEvent;\r\n\t\tconst decoratedEvent = new EventProto(ev.type, init);\r\n\r\n\t\tlet canDefault = true;\r\n\t\tthis._acetates.every((ac) => {\r\n\t\t\tif (decoratedEvent._canPropagate) {\r\n\t\t\t\tcanDefault &= ac.dispatchPointerEvent(decoratedEvent, init);\r\n\t\t\t\treturn decoratedEvent._canPropagate;\r\n\t\t\t} else {\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tif (decoratedEvent._canPropagate) {\r\n\t\t\tcanDefault &= this.dispatchEvent(decoratedEvent);\r\n\t\t}\r\n\r\n\t\tif (!decoratedEvent._canPropagate) {\r\n\t\t\tev.stopPropagation();\r\n\t\t}\r\n\t\tif (!canDefault) {\r\n\t\t\tev.preventDefault();\r\n\t\t}\r\n\t\treturn !decoratedEvent._canPropagate;\r\n\t}\r\n\r\n\t#cursorQueue = [];\r\n\t/**\r\n\t * @section Internal methods\r\n\t * @method queueCursor(cursor: String): this\r\n\t * Called when hovering over an interactive symbol with a `cursor`; adds the\r\n\t * given cursor to an internal queue. If there's only one cursor in the queue\r\n\t * the CSS property of the platina's `` will be set to it.\r\n\t */\r\n\tqueueCursor(cursor) {\r\n\t\t/*\r\n\t\t * The reason for having a queue is that there might be several\r\n\t\t * interactive acetates in the platina, and therefore several\r\n\t\t * interactive overlapping symbols in any given pixel. The way that\r\n\t\t * Gleo event handling works means that the pointer will fire\r\n\t\t * `pointerover` and `pointerout` events in all of those overlapping\r\n\t\t * symbols, with possibly conflicting cursors.\r\n\t\t *\r\n\t\t * Having a queue might not be the best way - maybe there's a need for\r\n\t\t * a map of acetate to cursor, ordered by z-index. Race conditions in\r\n\t\t * symbol cursors shouldn't be too problematic, though.\r\n\t\t */\r\n\t\tif (this.#cursorQueue.length === 0) {\r\n\t\t\tthis.canvas.style.cursor = cursor;\r\n\t\t}\r\n\t\tthis.#cursorQueue.push();\r\n\t}\r\n\r\n\t/**\r\n\t * @method unqueueCursor(cursor: String): this\r\n\t * Called when unhovering out of an interactive symbol with a `cursor`; removes\r\n\t * the given cursor from an internal queue. Resets the `cursor` CSS property\r\n\t * of the platina's `` to next item in that queue, or unsets it if the\r\n\t * stack is empty.\r\n\t */\r\n\tunqueueCursor(cursor) {\r\n\t\tthis.#cursorQueue.splice(this.#cursorQueue.indexOf(cursor), 1);\r\n\t\tthis.canvas.style.cursor =\r\n\t\t\tthis.#cursorQueue.length === 0 ? \"\" : this.#cursorQueue[0];\r\n\t}\r\n}\r\n","import BaseCRS from \"./BaseCRS.mjs\";\r\nimport { registerCRS } from \"./knownCRSs.mjs\";\r\n\r\n/**\r\n * @namespace epsg4326\r\n * @inherits BaseCRS\r\n *\r\n * A EPSG:4326 CRS - aka \"latitude-longitude\".\r\n *\r\n * Note that `epsg4326` works as a Singleton pattern - it's already an instance, so\r\n * do **not** call `new epsg4326()`.\r\n *\r\n * @example\r\n *\r\n * ```\r\n * import epsg4326 from 'gleo/src/crs/epsg4326.mjs';\r\n * import Geometry from 'gleo/src/geometry/Geometry.mjs';\r\n *\r\n * let myPoint = new Geometry(epsg4326, [5, 9]);\r\n * ```\r\n *\r\n */\r\n\r\nconst rad = Math.PI / 180;\r\nconst R = 6371000;\r\n\r\nconst epsg4326 = new BaseCRS(\"EPSG:4326\", {\r\n\twrapPeriodX: 360,\r\n\tdistance: function haversineDistance(p1, p2) {\r\n\t\t// Haversine formula for great-circle distance. Based on an implementation by\r\n\t\t// Jussi Mattas (https://github.com/jussimattas / https://github.com/gitjuba)\r\n\t\t// See https://github.com/Leaflet/Leaflet/pull/5935\r\n\t\tconst lat1 = p1.coords[1] * rad,\r\n\t\t\tlat2 = p2.coords[1] * rad,\r\n\t\t\tsinDLat = Math.sin(((p2.coords[1] - p1.coords[1]) * rad) / 2),\r\n\t\t\tsinDLon = Math.sin(((p2.coords[0] - p1.coords[0]) * rad) / 2),\r\n\t\t\ta = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,\r\n\t\t\tc = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\r\n\t\treturn R * c;\r\n\t},\r\n\togcUri: \"http://www.opengis.net/def/crs/EPSG/0/4326\",\r\n\tflipAxes: true,\r\n\tminSpan: 1e-6, // circa 0.1m at equator\r\n\tmaxSpan: 720,\r\n\tviewableBounds: [-Infinity, -90, Infinity, 90],\r\n});\r\n\r\nexport default epsg4326;\r\n\r\n// OGC URI Alias\r\n/// TODO: axis order???!!!\r\nregisterCRS(epsg4326, \"http://www.opengis.net/def/crs/OGC/1.3/CRS84\");\r\n","import BaseCRS from \"./BaseCRS.mjs\";\r\n\r\nimport epsg4326 from \"./epsg4326.mjs\";\r\n\r\n/**\r\n * @namespace epsg3857\r\n * @inherits BaseCRS\r\n *\r\n * A EPSG:3857 CRS - aka \"spherical web mercator\".\r\n *\r\n * Note that `epsg3857` works as a Singleton pattern - it's already an instance, so\r\n * do **not** call `new epsg3857()`.\r\n *\r\n * @example\r\n *\r\n * ```\r\n * import epsg3857 from 'gleo/src/crs/epsg3857.mjs';\r\n * import Geometry from 'gleo/src/geometry/Geometry.mjs';\r\n *\r\n * let myPoint = new Geometry(epsg3857, [5, 9]);\r\n * ```\r\n *\r\n */\r\n\r\nconst limit = 20037508.34;\r\n\r\nconst epsg3857 = new BaseCRS(\"EPSG:3857\", {\r\n\twrapPeriodX: 2 * limit,\r\n\tdistance: epsg4326,\r\n\togcUri: \"http://www.opengis.net/def/crs/EPSG/0/3857\",\r\n\tminSpan: 1,\r\n\tmaxSpan: 4 * limit,\r\n\tviewableBounds: [-Infinity, -limit, Infinity, limit],\r\n});\r\n\r\nexport default epsg3857;\r\n","import { DomUtil, Util, DomEvent, Path } from \"leaflet\";\r\n\r\nimport { BlanketOverlay } from \"./BlanketOverlay.js\";\r\n\r\nimport Platina from \"../gleo/src/Platina.mjs\";\r\nimport RawGeometry from \"../gleo/src/geometry/RawGeometry.mjs\";\r\nimport epsg3857 from \"../gleo/src/crs/epsg3857.mjs\";\r\nimport epsg4326 from \"../gleo/src/crs/epsg4326.mjs\";\r\n\r\nconst CanvasBlanket = BlanketOverlay.extend({\r\n\toptions: {\r\n\t\tattribution: \"Gleo\",\r\n\t},\r\n\r\n\tinitialize(options) {\r\n\t\tUtil.setOptions(this, options);\r\n\t\tthis._container = DomUtil.create(\"canvas\");\r\n\t\tthis._container.classList.add(\"leaflet-zoom-animated\");\r\n\t\tthis._container.style.zIndex = 99;\t// Keep beneath L.Canvas\r\n\t},\r\n\r\n\t_initContainer() {\r\n\t\t// noop, container has already been created in constructor\r\n\t},\r\n\r\n\t_destroyContainer() {\r\n\t\tDomEvent.off(this._container);\r\n\t\tthis._container.remove();\r\n\t},\r\n});\r\n\r\nexport default class LeafletGleo extends Platina {\r\n\tconstructor(leafletMap, { continuous = true, ...options } = {}) {\r\n\t\tconst canvasBlanket = new CanvasBlanket({\r\n\t\t\tcontinuous,\r\n\t\t\t...options,\r\n\t\t}).addTo(leafletMap);\r\n\r\n\t\tsuper(canvasBlanket._container, {\r\n\t\t\t...options,\r\n\t\t\tcrs: epsg3857,\r\n\t\t});\r\n\r\n\t\tcanvasBlanket._onSettled = function _onSettled() {\r\n\t\t\tconst { lat, lng } = leafletMap.getCenter();\r\n\t\t\tthis.setView({\r\n\t\t\t\tcenter: new RawGeometry(epsg4326, [lng, lat]),\r\n\t\t\t\tscale: this.zoomLevelToScale( leafletMap.getZoom()),\r\n\t\t\t});\r\n\t\t\tthis.redraw();\r\n\t\t}.bind(this);\r\n\r\n\t\tleafletMap.whenReady(()=>canvasBlanket._onSettled());\r\n\r\n\t\tif (leafletMap.options.preferCanvas) {\r\n\t\t\t/// NOTE: The Leaflet map currently has a L.Canvas blanketing the map\r\n\t\t\t/// surface, and on top (stacking-context-wise) of the Platina.\r\n\t\t\t/// This means that pointer events are captured by the L.Renderer, and\r\n\t\t\t/// don't reach the Platina's .\r\n\t\t\t/// The workaround is to listen to the pointer events on the L.Map\r\n\t\t\t/// instance, and dispatch those into the platina.\r\n\t\t\t/// Ideally, there should be a check to compare ev.originalEvent.target\r\n\t\t\t/// against the platina's , to really check that the event\r\n\t\t\t/// did not originate from it.\r\n\t\t\tconst mapContainer = leafletMap.getContainer();\r\n\t\t\tfor (let evName of [\r\n\t\t\t\t\"click\",\r\n\t\t\t\t\"dblclick\",\r\n\t\t\t\t\"auxclick\",\r\n\t\t\t\t\"contextmenu\",\r\n\t\t\t\t\"pointerover\",\r\n\t\t\t\t\"pointerenter\",\r\n\t\t\t\t\"pointerdown\",\r\n\t\t\t\t\"pointermove\",\r\n\t\t\t\t\"pointerup\",\r\n\t\t\t\t\"pointercancel\",\r\n\t\t\t\t\"pointerout\",\r\n\t\t\t\t\"pointerleave\",\r\n\t\t\t\t\"gotpointercapture\",\r\n\t\t\t\t\"lostpointercapture\",\r\n\t\t\t]) {\r\n\t\t\t\t// leafletMap.addEventListener(evName, ev=>this._onPointerEvent(ev.originalEvent));\r\n\t\t\t\tmapContainer.addEventListener(evName, ev=>{\r\n\t\t\t\t\tconst handled = this._onPointerEvent(ev);\r\n\r\n\t\t\t\t\t// In addition to the overlay pane (on top of the gleo platina),\r\n\t\t\t\t\t// there's a map pane underneath it.\r\n\t\t\t\t\tif (\r\n\t\t\t\t\t\t// If the event wasn't\r\n\t\t\t\t\t\t// handled (i.e. `stopPropagation()`ed) by a Gleo symbol,\r\n\t\t\t\t\t\t// let the back pane handle it via Leaflet functionality.\r\n\t\t\t\t\t\t!handled &&\r\n\r\n\t\t\t\t\t\t// It's possible that the backpane is not initialized\r\n\t\t\t\t\t\t// (e.g. no fences being drawn at all)\r\n\t\t\t\t\t\tthis.#backpane._map\r\n\t\t\t\t\t) {\r\n\t\t\t\t\t\tif (ev.type === 'click') {\r\n\t\t\t\t\t\t\tthis.#backpane._onClick(ev);\r\n\t\t\t\t\t\t} else if (ev.type === 'pointermove') {\r\n\t\t\t\t\t\t\tthis.#backpane._onMouseMove(ev);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t} );\r\n\t\t\t}\r\n\r\n\t\t\tthis.#backpane = leafletMap._getPaneRenderer('back-lines');\r\n\r\n\t\t\tleafletMap.whenReady(()=>{\r\n\t\t\t\tthis.#cursorTarget = leafletMap.getRenderer(new Path())._container\r\n\t\t\t});\r\n\r\n\r\n\t\t\t// Patch a method of the this.#backpane L.Canvas renderer so it doesn't\r\n\t\t\t// bubble the events up to the map (since the map already handled that\r\n\t\t\t// event, since it's the event that reached Gleo)\r\n\t\t\tthis.#backpane._onClick = ( function _onClick(e) {\r\n\t\t\t\tconst point = this._map.mouseEventToLayerPoint(e);\r\n\t\t\t\tlet layer, clickedLayer;\r\n\r\n\t\t\t\tfor (let order = this._drawFirst; order; order = order.next) {\r\n\t\t\t\t\tlayer = order.layer;\r\n\t\t\t\t\tif (layer.options.interactive && layer._containsPoint(point)) {\r\n\t\t\t\t\t\tif (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) {\r\n\t\t\t\t\t\t\tclickedLayer = layer;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (clickedLayer) {\r\n\t\t\t\t\tthis._fireEvent([clickedLayer], e);\r\n\t\t\t\t}\r\n\t\t\t}).bind(this.#backpane);\r\n\r\n\t\t} else {\r\n\t\t\tthis.#cursorTarget = this.canvas;\r\n\t\t}\r\n\t}\r\n\r\n\t#backpane;\r\n\r\n\t/// As per the Platina implementation of (un)queueCursor, but will affect\r\n\t/// *either* the Platina's `` or the L.Canvas' ``.\r\n\t/// If there's a L.Canvas, the pointer cursor shall be set there, since it\r\n\t/// will be on top of the platina's canvas.\r\n\t#cursorTarget;\r\n\t#cursorQueue = [];\r\n\tqueueCursor(cursor) {\r\n\t\tif (this.#cursorQueue.length === 0) {\r\n\t\t\tthis.#cursorTarget.style.cursor = cursor;\r\n\t\t}\r\n\t\tthis.#cursorQueue.push();\r\n\t}\r\n\r\n\tunqueueCursor(cursor) {\r\n\t\tthis.#cursorQueue.splice(this.#cursorQueue.indexOf(cursor), 1);\r\n\t\tthis.#cursorTarget.style.cursor =\r\n\t\t\tthis.#cursorQueue.length === 0 ? \"\" : this.#cursorQueue[0];\r\n\t}\r\n\r\n\t// Given a Leaflet zoom level, returns the corresponding Gleo scale factor\r\n\t// for EPSG:3857.\r\n\tzoomLevelToScale(level) {\r\n\t\t// zoom 0 is 156543 CRS units per pixel ( ≃ 20037508.34 / 128 )\r\n\t\treturn 156543.03392804097 / Math.pow(2, level) / devicePixelRatio;\r\n\t}\r\n\r\n\t// Given a Gleo scale factor in EPSG:3857, return the corresponding Leaflet\r\n\t// zoom level\r\n\tscaleToZoomLevel(scale) {\r\n\t\treturn Math.log2(156543.03392804097 / scale) * devicePixelRatio;\r\n\t}\r\n\r\n}\r\n","import Loader from \"./Loader.mjs\";\r\n\r\n/**\r\n * @class AbstractSymbolGroup\r\n * @inherits Loader\r\n * @relationship aggregationOf GleoSymbol, 0..1, 0..n\r\n *\r\n * Abstract base class for `Loader`s that can have `GleoSymbol`s added to them\r\n * (e.g. symbols can be added to a `Clusterer` loader instead of a `Platina` or\r\n * `GleoMap`).\r\n *\r\n */\r\n\r\n/// TODO: Should this have events for symbols added / removed??\r\n\r\nexport default class AbstractSymbolGroup extends Loader {\r\n\tconstructor(opts) {\r\n\t\tsuper(opts);\r\n\r\n\t\t/**\r\n\t\t * @section Subclass interface\r\n\t\t * @uninheritable\r\n\t\t * @property symbols: Array of GleoSymbol\r\n\t\t * The `GleoSymbol`s added to this group.\r\n\t\t * @property loaders: Array of Loader\r\n\t\t * The `Loader`s added to this group. These can be loaders that spawn symbols,\r\n\t\t * or\r\n\t\t */\r\n\t\tthis.symbols = [];\r\n\r\n\t\t// this.#boundAddSymbols = this._addSymbols.bind(this);\r\n\t\tthis.#boundAddSymbols = (ev) => {\r\n\t\t\tthis._addSymbols(ev.detail.symbols);\r\n\t\t};\r\n\t\t// this.#boundRemoveSymbols = this._removeSymbols.bind(this);\r\n\t\tthis.#boundRemoveSymbols = (ev) => {\r\n\t\t\tthis._removeSymbols(ev.detail.symbols);\r\n\t\t};\r\n\t}\r\n\r\n\t#boundAddSymbols;\r\n\t#boundRemoveSymbols;\r\n\t#loaders = [];\r\n\r\n\t/**\r\n\t * @section\r\n\t * @method add(symbol: GleoSymbol): this\r\n\t * Adds the given symbol to this symbol group.\r\n\t * @alternative\r\n\t * @method add(loader: Loader): this\r\n\t * Adds the given loader to this symbol group. Symbols from that loader will\r\n\t * be put into this group.\r\n\t */\r\n\tadd(symbol) {\r\n\t\tif (symbol instanceof Loader) {\r\n\t\t\tthis._addLoaders([symbol]);\r\n\t\t} else {\r\n\t\t\tthis._addSymbols([symbol]);\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Subclass interface\r\n\t * @method _addSymbols(Array of GleoSymbol)\r\n\t * Tracks the given symbols. Can be overriden by subclasses.\r\n\t */\r\n\t_addSymbols(symbols) {\r\n\t\tthis.symbols = this.symbols.concat(symbols);\r\n\t}\r\n\r\n\t/**\r\n\t * @section Subclass interface\r\n\t * @method _addLoaders(Array of Loader)\r\n\t * Tracks the given loaders. Can be overriden by subclasses.\r\n\t */\r\n\t_addLoaders(loaders) {\r\n\t\tthis.#loaders = this.#loaders.concat(loaders);\r\n\t\tloaders.forEach((l) => {\r\n\t\t\tl.on(\"symbolsadded\", this.#boundAddSymbols);\r\n\t\t\tl.on(\"symbolsremoved\", this.#boundRemoveSymbols);\r\n\t\t\tif (l.target !== this) {\r\n\t\t\t\tif (!l.target) {\r\n\t\t\t\t\tl.addTo(this);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthrow new Error(\"Cannot add a Loader that already has a target\");\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// l.addTo(this);\r\n\t\t});\r\n\t}\r\n\r\n\t/**\r\n\t * @section Subclass interface\r\n\t * @method _removeSymbols(Array of GleoSymbol)\r\n\t * Stops tracking the given symbols. Can be overriden by subclasses.\r\n\t */\r\n\t_removeSymbols(symbols) {\r\n\t\tthis.symbols = this.symbols.filter((s) => !symbols.includes(s));\r\n\t}\r\n\r\n\t/**\r\n\t * @section Subclass interface\r\n\t * @method _removeLoaders(Array of Loader)\r\n\t * Stops tracking the given loaders. Can be overriden by subclasses.\r\n\t */\r\n\t_removeLoaders(loaders) {\r\n\t\tloaders.forEach((l) => {\r\n\t\t\tif (l.target !== this) {\r\n\t\t\t\tthrow new Error(\"Cannot remove a Loader that hasn't been added here\");\r\n\t\t\t}\r\n\t\t\tl.remove();\r\n\t\t\tl.off(\"symbolsadded\", this.#boundAddSymbols);\r\n\t\t\tl.off(\"symbolsremoved\", this.#boundRemoveSymbols);\r\n\t\t});\r\n\t\tthis.#loaders = this.#loaders.filter((l) => !loaders.includes(l));\r\n\t}\r\n\r\n\t_addToPlatina(platina) {\r\n\t\tsuper._addToPlatina(platina);\r\n\t\tthis.#loaders.forEach((l) => l._addToPlatina(platina));\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @method multiAdd(symbols: Array of GleoSymbol): this\r\n\t * Adds the given symbols to this group loader\r\n\t * @alternative\r\n\t * @method multiAdd(loaders: Array of Loader): this\r\n\t * Adds the given loaders to this group loader\r\n\t */\r\n\tmultiAdd(symbols) {\r\n\t\t/// This implementations is probably inefficient, but ensures that the\r\n\t\t/// a `multiAdd()` call will call the `add()` method from the right subclass.\r\n\t\tthis._addLoaders(symbols.filter((s) => s instanceof Loader));\r\n\t\tthis._addSymbols(symbols.filter((s) => !(s instanceof Loader)));\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method remove(): this\r\n\t * Removes the `Loader` from the map/platina it was in.\r\n\t * @alternative\r\n\t * @method remove(symbol: GleoSymbol): this\r\n\t * Removes the given symbol from this group loader.\r\n\t * @alternative\r\n\t * @method remove(loader: Loader): this\r\n\t * Removes the given `Loader` from this group loader.\r\n\t */\r\n\tremove(symbol) {\r\n\t\tif (symbol) {\r\n\t\t\tif (symbol instanceof Loader) {\r\n\t\t\t\tthis._removeLoaders([symbol]);\r\n\t\t\t} else if (this.symbols.includes(symbol)) {\r\n\t\t\t\tthis._removeSymbols([symbol]);\r\n\t\t\t}\r\n\r\n\t\t\treturn this;\r\n\t\t} else {\r\n\t\t\treturn super.remove();\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method multiRemove(symbols): this\r\n\t * Removes the given symbols from this group loader\r\n\t */\r\n\tmultiRemove(symbols) {\r\n\t\tthis._removeLoaders(symbols.filter((s) => s instanceof Loader));\r\n\t\tthis._removeSymbols(symbols.filter((s) => !(s instanceof Loader)));\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method empty(): this\r\n\t * Empties the symbol group, by removing all known symbols and loaders in it.\r\n\t */\r\n\tempty() {\r\n\t\tthis.symbols.length = 0;\r\n\t\tthis.#loaders.length = 0;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method has(symbol: GleoSymbol): Boolean\r\n\t * Returns `true` if this loader contains the given symbol, false otherwise.\r\n\t * @alternative\r\n\t * @method has(symbol: Loader): Boolean\r\n\t * Returns `true` if this loader contains the given loader, false otherwise.\r\n\t */\r\n\thas(s) {\r\n\t\treturn this.symbols.includes(s) || this.#loaders.includes(s);\r\n\t}\r\n}\r\n","import Platina from \"../Platina.mjs\";\r\nimport { factory } from \"../geometry/DefaultGeometry.mjs\";\r\nimport Evented from \"../dom/Evented.mjs\";\r\nimport AbstractSymbolGroup from \"../loaders/AbstractSymbolGroup.mjs\";\r\n\r\n/**\r\n * @class GleoSymbol\r\n * @inherits Evented\r\n *\r\n * @relationship compositionOf RawGeometry, 0..n, 1..1\r\n *\r\n * An abstract base graphical symbol.\r\n *\r\n * (This would ideally called `Symbol`, but that's a reserved word in ES6 Javascript).\r\n *\r\n * A `GleoSymbol` is closely coupled to a matching `Acetate`. The `Acetate` defines\r\n * the WebGL program that will render symbols in the GPU; whereas `GleoSymbol`s hook\r\n * to an acetate, and fill up some data structures provided by it.\r\n *\r\n */\r\n\r\nexport default class GleoSymbol extends Evented {\r\n\t#attribution;\r\n\t#interactive;\r\n\t#geometry;\r\n\t#cursor;\r\n\r\n\t/**\r\n\t * @constructor GleoSymbol(geom: RawGeometry, opts: GleoSymbol Options)\r\n\t * @alternative\r\n\t * @constructor GleoSymbol(geom: Array of Number, opts: GleoSymbol Options)\r\n\t */\r\n\tconstructor(\r\n\t\tgeom,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @section\r\n\t\t\t * @aka GleoSymbol Options\r\n\t\t\t * @option attribution: String = undefined\r\n\t\t\t * The HTML attribution to be shown in the `AttributionControl`, if any.\r\n\t\t\t */\r\n\t\t\tattribution,\r\n\t\t\t/**\r\n\t\t\t * @option interactive: Boolean = false\r\n\t\t\t * Whether this `GleoSymbol` should fire mouse/pointer events (or not).\r\n\t\t\t *\r\n\t\t\t * Needs the symbol to be drawn in an appropriate `Acetate`.\r\n\t\t\t */\r\n\t\t\tinteractive = false,\r\n\t\t\t/**\r\n\t\t\t * @option cursor: String = undefined\r\n\t\t\t * The pointer cursor to be used when the primary is hovering over\r\n\t\t\t * this symbol.\r\n\t\t\t *\r\n\t\t\t * Possible values are the keywords for the [`cursor` CSS property](https://developer.mozilla.org/docs/Web/CSS/cursor.html)\r\n\t\t\t * (e.g. `\"pointer\"`, `\"wait\"`, `\"move\"`, etc).\r\n\t\t\t *\r\n\t\t\t * Needs `interactive` to be `true`.\r\n\t\t\t */\r\n\t\t\tcursor,\r\n\t\t} = {}\r\n\t) {\r\n\t\tsuper();\r\n\r\n\t\t// The acetate instance this symbol is being drawn in:\r\n\t\tthis._inAcetate = undefined;\r\n\r\n\t\tif (geom) {\r\n\t\t\tthis.geometry = geom;\r\n\t\t}\r\n\t\tthis.#attribution = attribution;\r\n\t\tthis.#interactive = interactive;\r\n\t\tthis.#cursor = cursor;\r\n\r\n\t\t// Used by `MultiSymbol` and such. Events dispatched to this symbol\r\n\t\t// will also be dispatched to all the event parents.\r\n\t\tthis._eventParents = [];\r\n\r\n\t\t/**\r\n\t\t * @section Acetate interface\r\n\t\t * @uninheritable\r\n\t\t *\r\n\t\t * `GleoSymbol`s must expose the following information, so that `Acetate`s can\r\n\t\t * allocate resources properly.\r\n\t\t *\r\n\t\t * `GleoSymbol`s can be thought as a collection of GL vertices (with attributes)\r\n\t\t * and primitive slots (i.e. three vertex indices per triangle). A `GleoSymbol`\r\n\t\t * must expose the amount of attribute and index slots needed, so that `Acetate`\r\n\t\t * functionality can allocate space for attributes and for primitive indices.\r\n\t\t *\r\n\t\t * Note that `Dot`s and `AcetateDot`s ignore `idxBase` and `idxLength`, because\r\n\t\t * they have no need for keeping track of how primitives must be drawn (because of\r\n\t\t * the `POINTS` GL draw mode).\r\n\t\t *\r\n\t\t * @property attrBase: Number = undefined\r\n\t\t * The (0-indexed) base offset for vertex attributes of this symbol. Set\r\n\t\t * by `Acetate`. This is `undefined` until the `Acetate` allocates the\r\n\t\t * GPU resources (vertices) for this symbol.\r\n\t\t * @property idxBase: Number = undefined\r\n\t\t * The (0-indexed) base offset for primitive slots of this symbol. Set\r\n\t\t * by `AcetateVertices`. This is `undefined` until the `AcetateVertices`\r\n\t\t * allocates the GPU resources (triangle primitive indices) for this symbol.\r\n\t\t * @property attrLength: Number\r\n\t\t * The amount of vertex attribute slots this symbol needs. Must be set by the symbol\r\n\t\t * during construction time.\r\n\t\t * @property idxLength: Number\r\n\t\t * The amount of primitive slots this symbol needs. Must be set by the symbol\r\n\t\t * during construction time.\r\n\t\t *\r\n\t\t * @property _id: Number\r\n\t\t * The interactive ID of the symbol, to let `AcetateInteractive`\r\n\t\t * functionality find it to dispatch pointer events.\r\n\t\t */\r\n\r\n\t\tthis.attrBase = undefined;\r\n\t\tthis.idxBase = undefined;\r\n\t\tthis.attrLength = undefined;\r\n\t\tthis.idxLength = undefined;\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @property geometry: Geometry\r\n\t * The symbol's (unprojected) geometry. Can be updated with a new `Geometry`\r\n\t * or nested `Array`s of `Number`s.\r\n\t * @property attribution: String; The symbol's attribution. Read-only.\r\n\t * @property interactive: Boolean\r\n\t * Whether the symbol should be interactive. Read-only.\r\n\t * @property cursor: String\r\n\t * The runtime value of the `cursor` option. Can only be updated when not\r\n\t * being drawn.\r\n\t */\r\n\r\n\tget attribution() {\r\n\t\treturn this.#attribution;\r\n\t}\r\n\tget interactive() {\r\n\t\treturn this.#interactive;\r\n\t}\r\n\tget cursor() {\r\n\t\treturn this.#cursor;\r\n\t}\r\n\tset cursor(c) {\r\n\t\tif (this._inAcetate) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"Cannot update the `cursor` of a Symbol that is in an acetate. Remove it from the map first.\"\r\n\t\t\t);\r\n\t\t}\r\n\t\tthis.#cursor = c;\r\n\t}\r\n\r\n\tget geometry() {\r\n\t\treturn this.#geometry;\r\n\t}\r\n\tset geometry(geom) {\r\n\t\tthis.#geometry = factory(geom);\r\n\t}\r\n\r\n\t// Compatibility alias\r\n\tget geom() {\r\n\t\treturn this.geometry;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Static properties\r\n\t * Any subclasses must implement the following static property.\r\n\t * @property Acetate: Prototype of Acetate\r\n\t * The `Acetate` prototype related to the symbol - the one that fits this class\r\n\t * of symbol by default.\r\n\t *\r\n\t * This is implemented as a static property, i.e. a property of the `Dot` prototype,\r\n\t * not of the `Dot` instances.\r\n\t *\r\n\t * This shall be used when adding a symbol to a map, in order\r\n\t * to detect the default acetate it has to be added to.\r\n\t */\r\n\tstatic Acetate = undefined;\r\n\r\n\t/**\r\n\t * @section Lifecycle Methods\r\n\t * @method addTo(map: GleoMap): this\r\n\t * Adds this symbol to the map.\r\n\t *\r\n\t * The symbol will be added to the appropriate default acetate.\r\n\t *\r\n\t * @alternative\r\n\t * @method addTo(acetate: Acetate): this\r\n\t * Adds this symbol to the given `Acetate` (as long as the acetate fits the symbol).\r\n\t *\r\n\t * @alternative\r\n\t * @method addTo(loader: AbstractSymbolGroup): this\r\n\t * Adds this symbol to a `Loader` that accepts symbols.\r\n\t */\r\n\taddTo(target) {\r\n\t\tconst proto = this.constructor.Acetate;\r\n\t\tlet acet, group;\r\n\t\tif (target instanceof proto) {\r\n\t\t\tacet = target;\r\n\t\t} else if (\r\n\t\t\tthis.constructor.Acetate.PostAcetate &&\r\n\t\t\ttarget instanceof this.constructor.Acetate.PostAcetate\r\n\t\t) {\r\n\t\t\tacet = target.getAcetateOfClass(proto);\r\n\t\t} else if (target instanceof Platina) {\r\n\t\t\tacet = target.getAcetateOfClass(proto);\r\n\t\t} else if (target.platina instanceof Platina) {\r\n\t\t\tacet = target.platina.getAcetateOfClass(proto);\r\n\t\t} else if (target instanceof AbstractSymbolGroup) {\r\n\t\t\tgroup = target;\r\n\t\t}\r\n\r\n\t\tif (this._inAcetate) {\r\n\t\t\tif (acet === this._inAcetate) {\r\n\t\t\t\treturn this;\r\n\t\t\t}\r\n\t\t\tthrow new Error(\r\n\t\t\t\t`Could not add Symbol to ${target}, since symbol is already being drawn elsewhere.`\r\n\t\t\t);\r\n\t\t}\r\n\t\tif (acet) {\r\n\t\t\tacet.add(this);\r\n\t\t} else if (group) {\r\n\t\t\tgroup.add(this);\r\n\t\t} else {\r\n\t\t\tthrow new Error(`Could not add Symbol to ${target}.`);\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method remove(): this\r\n\t * Removes this symbol from its containing `Acetate` (and, therefore, from the\r\n\t * containing `GleoMap`).\r\n\t */\r\n\tremove() {\r\n\t\tif (!this._inAcetate) {\r\n\t\t\tthrow new Error(\"Cannot remove Symbol: it's not being drawn already.\");\r\n\t\t}\r\n\t\tthis._inAcetate.remove(this);\r\n\t\tthis._inAcetate = undefined;\r\n\t\tthis.attrBase = undefined;\r\n\t\tthis.idxBase = undefined;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method isActive(): Boolean\r\n\t * Returns whether the symbol is \"active\": being drawn in any `Acetate`. In\r\n\t * other words, it has correctly allocated all GPU resources needed for it\r\n\t * to be drawn.\r\n\t *\r\n\t * Note that some symbols can take time to allocate their resources, so\r\n\t * they can be \"inactive\" after they've been added to an acetate/platina/map.\r\n\t * (e.g. `Sprite`s when they refer to an image from the network that hasn't\r\n\t * finished loading)\r\n\t *\r\n\t * Note that this returns `true` for transparent or otherwise allocated yet\r\n\t * invisible symbols.\r\n\t */\r\n\tisActive() {\r\n\t\treturn this._inAcetate && this.attrBase !== undefined;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Acetate Interface\r\n\t * @uninheritable\r\n\t * @method updateRefs(ac: Acetate, atb: Number, idx: Number): this\r\n\t * Internal usage only, called from a corresponding `Acetate`. Updates\r\n\t * the acetate that this symbol is being currently drawn on, the base vertex\r\n\t * attribute slot (`atb`), and the base vertex index slot (`idx`).\r\n\t */\r\n\tupdateRefs(ac, atb, idx) {\r\n\t\tthis._inAcetate = ac;\r\n\t\tthis.attrBase = atb;\r\n\t\tthis.idxBase = idx;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method _setGlobalStrides(...): this\r\n\t *\r\n\t * OBSOLETE: use `_setGlobalStrides`/`_setGeometryStrides`/`_setPerPointStrides` instead.\r\n\t *\r\n\t * Should be implemented by subclasses. Acetates shall call this method\r\n\t * with one `StridedTypedArray` per attribute, plus a `TypedArray` for the\r\n\t * index buffer, plus any extra data they need.\r\n\t *\r\n\t * A symbol shall fill up values in the given strided arrays (based on\r\n\t * the `attrBase` property) as well as in the typed array for the index\r\n\t * buffer (based on the `idxBase` property).\r\n\t *\r\n\t *\r\n\t * @method _setGlobalStrides(...): this\r\n\t * Shall be implemented by subclasses. Acetates shall call this method\r\n\t * with any number of `StridedTypedArray`s plus a `TypedArray` fir the\r\n\t * index buffer, plus any extra data they need.\r\n\t *\r\n\t * The acetate shall call this method *once*, when the symbol is allocated.\r\n\t *\r\n\t * @method _setGeometryStrides(geom: Geometry, perPointStrides: Array of StridedArray, ...): this\r\n\t * As `_setGlobalStrides`, but the acetate may call this whenever the\r\n\t * geometry of the symbol changes. It receives the projected geometry and the\r\n\t * per-point strides.\r\n\t *\r\n\t * This is needed for e.g. recalculation of line joins in `Stroke` (and\r\n\t * updating the attribute buffer(s) where the line join data is in).\r\n\t *\r\n\t * @method _setPerPointStrides(n: Number, pointType: Symbol, vtx: Number, vtxCount: Number ...): this\r\n\t * As `_setGlobalStrides`, but the acetate shall call this once per each\r\n\t * point in the symbol's geometry.\r\n\t *\r\n\t * This method will receive the index of the point within the geometry\r\n\t * (0-indexed), the type of point (whether the point is a line join, or\r\n\t * line end, or a standalone point, or part of a mesh), the vertex index\r\n\t * for this point, the amount of vertices spawned for that point,\r\n\t * plus any strided arrays.\r\n\t *\r\n\t */\r\n\r\n\t// TODO: Consider having a _setGlobalStridesGeom, that shall run whenever\r\n\t// the geometry changes. Some attributes depend on the geometry (e.g.\r\n\t// the extrusion in `Stroke`), as do the indices of `Fill`.\r\n\r\n\t/**\r\n\t * @section Pointer events\r\n\t *\r\n\t * All `GleoSymbol`s (set as `interactive`, and being drawn in an appropriate\r\n\t * acetate) fire pointer events, in a way similar to how `HTMLElement`s fire\r\n\t * [DOM `PointerEvent`s](https://developer.mozilla.org/docs/Web/API/Pointer_events).\r\n\t *\r\n\t * Gleo adds the `Geometry` corresponding to the pixel the event took place in.\r\n\t *\r\n\t * Most events are `GleoPointerEvent`s, but some browsers fire\r\n\t * exclusively `MouseEvent`s for `click`/`auxclick`/`contextmenu`. In\r\n\t * that case, expect a `GleoMouseEvent` instead.\r\n\t *\r\n\t * @event click: GleoPointerEvent\r\n\t * Akin to the [DOM `click` event](https://developer.mozilla.org/docs/Web/API/Element/click_event)\r\n\t * @event dblclick: GleoPointerEvent\r\n\t * Akin to the [DOM `dblclick` event](https://developer.mozilla.org/docs/Web/API/Element/dblclick_event)\r\n\t * @event auxclick: GleoPointerEvent\r\n\t * Akin to the [DOM `auxclick` event](https://developer.mozilla.org/docs/Web/API/Element/auxclick_event)\r\n\t * @event contextmenu: GleoPointerEvent\r\n\t * Akin to the [DOM `contextmenu` event](https://developer.mozilla.org/docs/Web/API/Element/contextmenu_event)\r\n\t * @event pointerover: GleoPointerEvent\r\n\t * Akin to the [DOM `pointerover` event](https://developer.mozilla.org/docs/Web/API/HTMLElement/pointerover_event)\r\n\t * @event pointerenter: GleoPointerEvent\r\n\t * Akin to the [DOM `pointerenter` event](https://developer.mozilla.org/docs/Web/API/HTMLElement/pointerenter_event)\r\n\t * @event pointerdown: GleoPointerEvent\r\n\t * Akin to the [DOM `pointerdown` event](https://developer.mozilla.org/docs/Web/API/HTMLElement/pointerdown_event)\r\n\t * @event pointermove: GleoPointerEvent\r\n\t * Akin to the [DOM `pointermove` event](https://developer.mozilla.org/docs/Web/API/HTMLElement/pointermove_event)\r\n\t * @event pointerup: GleoPointerEvent\r\n\t * Akin to the [DOM `pointerup` event](https://developer.mozilla.org/docs/Web/API/HTMLElement/pointerup_event)\r\n\t * @event pointercancel: GleoPointerEvent\r\n\t * Akin to the [DOM `pointercancel` event](https://developer.mozilla.org/docs/Web/API/HTMLElement/pointercancel_event)\r\n\t * @event pointerout: GleoPointerEvent\r\n\t * Akin to the [DOM `pointerout` event](https://developer.mozilla.org/docs/Web/API/HTMLElement/pointerout_event)\r\n\t * @event pointerleave: GleoPointerEvent\r\n\t * Akin to the [DOM `pointerleave` event](https://developer.mozilla.org/docs/Web/API/HTMLElement/pointerleave_event)\r\n\t */\r\n\r\n\t/**\r\n\t * @section Pointer methods\r\n\t * @method setPointerCapture(pointerId: Number): this\r\n\t * Akin to [`Element.setPointerCapture()`](https://developer.mozilla.org/docs/Web/API/Element/setPointerCapture)\r\n\t * @method releasePointerCapture(pointerId: Number): this\r\n\t * Akin to [`Element.releasePointerCapture()`](https://developer.mozilla.org/docs/Web/API/Element/releasePointerCapture)\r\n\t */\r\n\tsetPointerCapture(pointerId) {\r\n\t\tthis._inAcetate?.setPointerCapture(pointerId, this);\r\n\t\treturn this;\r\n\t}\r\n\treleasePointerCapture(pointerId) {\r\n\t\tthis._inAcetate?.releasePointerCapture(pointerId);\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @method debugDump(): Object\r\n\t *\r\n\t * Returns a verbose, human-understandable representation of the underlying\r\n\t * WebGL data (vertex attributes and triangle primitives) for this symbol.\r\n\t *\r\n\t * This is an computationally expensive operation and should only be used for\r\n\t * debugging purposes. Note that this functions just retuns a value, so make\r\n\t * sure to `console.log()` or likewise.\r\n\t */\r\n\tdebugDump() {\r\n\t\tif (!this._inAcetate) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"Cannot dump debug info for symbol since it's not being drawn in any acetate\"\r\n\t\t\t);\r\n\t\t}\r\n\t\tif (!this._inAcetate._program) {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"Cannot dump debug info for symbol since it's being drawn on an acetate that has not yet linked its WebGL program\"\r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\treturn {\r\n\t\t\tidxBase: this.idxBase,\r\n\t\t\tidxLength: this.idxLength,\r\n\t\t\tattrBase: this.attrBase,\r\n\t\t\tattrLength: this.attrLength,\r\n\t\t\tattrs: this._inAcetate._program.debugDumpAttributes(\r\n\t\t\t\tthis.attrBase,\r\n\t\t\t\tthis.attrLength\r\n\t\t\t),\r\n\t\t\tidxs: this._inAcetate._program._indexBuff._ramData.slice(\r\n\t\t\t\tthis.idxBase,\r\n\t\t\t\tthis.idxBase + this.idxLength\r\n\t\t\t),\r\n\t\t};\r\n\t}\r\n}\r\n","import GleoSymbol from \"../symbols/Symbol.mjs\";\r\nimport AbstractSymbolGroup from \"./AbstractSymbolGroup.mjs\";\r\nimport Loader from \"./Loader.mjs\";\r\n\r\n/**\r\n * @class SymbolGroup\r\n * @inherits AbstractSymbolGroup\r\n *\r\n * Akin to Leaflet's `LayerGroup`. Groups symbols together so that they can be\r\n * added to/removed at once by adding/removing the symbol group. Symbols can be\r\n * added to/removed from the group as well.\r\n *\r\n * In addition to symbols, accepts nested `Loader`s.\r\n *\r\n * For grouping symbols relating to the same geographical feature, use `MultiSymbol`\r\n * instead.\r\n */\r\n\r\nexport default class SymbolGroup extends AbstractSymbolGroup {\r\n\taddTo(target) {\r\n\t\tsuper.addTo(target);\r\n\t\tthis.target.multiAdd(this.symbols);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// _addToPlatina(p) {\r\n\t// \tsuper._addToPlatina(p);\r\n\t// \tp.multiAdd(this.symbols);\r\n\t// }\r\n\r\n\t_addSymbols(symbols) {\r\n\t\tsuper._addSymbols(symbols);\r\n\t\tthis.fire(\"symbolsadded\", { symbols });\r\n\t}\r\n\r\n\t_removeSymbols(symbols) {\r\n\t\tsuper._removeSymbols(symbols);\r\n\t\tthis.fire(\"symbolsremoved\", { symbols });\r\n\t}\r\n\r\n\tempty() {\r\n\t\tthis.fire(\"symbolsremoved\", { symbols: this.symbols });\r\n\r\n\t\treturn super.empty();\r\n\t}\r\n\r\n\tremove(s) {\r\n\t\tif (!s && this.target) {\r\n\t\t\tthis.target.multiRemove(this.symbols);\r\n\t\t}\r\n\t\treturn super.remove(s);\r\n\t}\r\n}\r\n","import Geometry from \"./Geometry.mjs\";\r\nimport epsg4326 from \"../crs/epsg4326.mjs\";\r\n\r\n/**\r\n * @class LngLat\r\n * @inherits Geometry\r\n * @relationship dependsOn epsg4326, 0..n, 1..1\r\n *\r\n * A `Geometry` of longitude-latitude coordinates, assuming EPSG:4326.\r\n *\r\n * Note that the order of the axis is longitude-latitude, or x-y: `new LngLat([180, 90])`\r\n * is equivalent to `new Coord(epsg4326, [180, 90])`.\r\n *\r\n * The issue of the order of the axis might be confusing. See also `LatLng` and\r\n * https://macwright.com/lonlat/ .\r\n */\r\n\r\nexport default class LngLat extends Geometry {\r\n\t/**\r\n\t * @constructor LngLat(xy: Array of Number)\r\n\t */\r\n\tconstructor(xy, opts) {\r\n\t\tsuper(epsg4326, xy, opts);\r\n\t}\r\n}\r\n","import LngLat from \"./LngLat.mjs\";\r\n\r\n/**\r\n * @class LatLng\r\n * @inherits LngLat\r\n *\r\n * A `Geometry` of latitude-longitude coordinates, assuming EPSG:4326.\r\n *\r\n * Note that the order of the axis is inverted: `new LatLng([90, 180])`\r\n * is equivalent to `new Geometry(epsg4326, [180, 90])`.\r\n *\r\n * The issue of the order of the axis might be confusing. See also `LatLng` and\r\n * https://macwright.com/lonlat/ .\r\n */\r\n\r\nexport default class LatLng extends LngLat {\r\n\t/**\r\n\t * @constructor LatLng(xy: Array of Number, opts?: Geometry Options)\r\n\t */\r\n\tconstructor(yx, opts) {\r\n\t\tconst xy = flip(yx);\r\n\t\tsuper(xy, opts);\r\n\t}\r\n}\r\n\r\nfunction flip(arr) {\r\n\tif (typeof arr[0] === \"number\") {\r\n\t\treturn [arr[1], arr[0]];\r\n\t} else {\r\n\t\treturn arr.map(flip);\r\n\t}\r\n}\r\n","import GleoSymbol from \"./Symbol.mjs\";\r\nimport { getMousePosition } from \"../dom/Dom.mjs\";\r\n\r\n/**\r\n * @class ExtrudedPoint\r\n * @inherits GleoSymbol\r\n *\r\n * Abstract class, containing functionality common to symbols displayed as\r\n * triangles whose vertices are extruded from a central point, which\r\n * corresponds to the point `Geometry` of the symbol.\r\n *\r\n * i.e. `Sprite`, `CircleFill`, `CircleStroke`, and `Pie` so far.\r\n */\r\n\r\nexport default class ExtrudedPoint extends GleoSymbol {\r\n\tconstructor(\r\n\t\tgeom,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @option draggable: Boolean = false\r\n\t\t\t * When set to `true`, the symbol can be dragged around with the pointer,\r\n\t\t\t * and will fire `dragstart`/`drag`/`dragend` events.\r\n\t\t\t */\r\n\t\t\tdraggable = false,\r\n\t\t\tinteractive = true,\r\n\r\n\t\t\t/**\r\n\t\t\t * @option offset: Array of Number = [0,0]\r\n\t\t\t * The amount of CSS pixels that the symbol will be offset from its\r\n\t\t\t * geometry. The amount is up-right in `[x, y]` or `[up, right]` form.\r\n\t\t\t */\r\n\t\t\toffset = [0, 0],\r\n\r\n\t\t\t...opts\r\n\t\t} = {}\r\n\t) {\r\n\t\tsuper(geom, { ...opts, interactive });\r\n\r\n\t\tthis.#offset = offset;\r\n\r\n\t\tthis._assertGeom(this.geometry);\r\n\r\n\t\tthis.draggable = draggable;\r\n\t}\r\n\r\n\t#offset;\r\n\r\n\tget geometry() {\r\n\t\treturn super.geometry;\r\n\t}\r\n\tset geometry(geom) {\r\n\t\tsuper.geometry = geom;\r\n\t\tif (this._inAcetate && this.attrBase !== undefined) {\r\n\t\t\tthis._inAcetate.reproject(this.attrBase, this.attrLength);\r\n\t\t\tif (\"dirty\" in this._inAcetate) {\r\n\t\t\t\tthis._inAcetate.dirty = true;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @property offset: Array of Number\r\n\t * Getter/setter for the symbol's offset, as per the homonymous option.\r\n\t */\r\n\tget offset() {\r\n\t\treturn this.#offset;\r\n\t}\r\n\tset offset(offset) {\r\n\t\tthis.#offset = offset;\r\n\t\tif (this._inAcetate && this.attrBase !== undefined) {\r\n\t\t\tconst strideExtrusion = this._inAcetate._extrusions.asStridedArray();\r\n\t\t\tthis._setStridedExtrusion(strideExtrusion);\r\n\t\t\tthis._inAcetate._extrusions.commit(this.attrBase, this.attrLength);\r\n\t\t\tthis._inAcetate.dirty = true;\r\n\t\t}\r\n\t}\r\n\r\n\t// Asserts that the geometry passed is a point geometry.\r\n\t_assertGeom(geom) {\r\n\t\tif (geom.coords.length !== geom.dimension) {\r\n\t\t\t/// TODO: Treat multi-point geometries as multipoints. See\r\n\t\t\t/// https://gitlab.com/IvanSanchez/gleo/-/issues/37\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"Geometry passed to ExtrudedPoint symbol is not a single point (is a line or polygon instead).\"\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n\r\n\t#draggable = false;\r\n\r\n\t/**\r\n\t * @section Subclass interface\r\n\t * @uninheritable\r\n\t * @method _setStridedExtrusion(strideExtrusion: StridedTypedArray): undefined\r\n\t * Must be implemented by subclasses; must set values into the given\r\n\t * `StridedTypedArray`.\r\n\t *\r\n\t * @method _setGlobalStrides(*): undefined\r\n\t * Must be implemented by subclasses. Will receive a variable number of\r\n\t * arguments depending on the Acetate implementaion: strided attribute arrays,\r\n\t * strided index buffer, and constants. The implementation must fill the\r\n\t * strided arrays appropriately.\r\n\t */\r\n\r\n\t/**\r\n\t * @section\r\n\t * @property draggable: Boolean\r\n\t * Whether the symbol can be dragged with pointer events. By (re)setting its\r\n\t * value, dragging is enabled or disabled.\r\n\t */\r\n\t/// TODO: Dragging behaviour with multipoint geometries is very tricky (for\r\n\t/// one, all instances of the symbol would share the same internal ID for\r\n\t/// pointer event target detection).\r\n\t/// For now, the dragging logic assumes single point geometry.\r\n\tget draggable() {\r\n\t\treturn this.#draggable;\r\n\t}\r\n\r\n\tset draggable(d) {\r\n\t\td = !!d;\r\n\t\tif (d === this.#draggable) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tif (d) {\r\n\t\t\t// enable\r\n\t\t\tthis.#boundOnPointerDown ??= this.#onPointerDown.bind(this);\r\n\t\t\tthis.#boundOnPointerMove ??= this.#onPointerMove.bind(this);\r\n\t\t\tthis.#boundOnPointerUp ??= this.#onPointerUp.bind(this);\r\n\r\n\t\t\tthis.on(\"pointerdown\", this.#boundOnPointerDown);\r\n\t\t\tthis.on(\"pointerup\", this.#boundOnPointerUp);\r\n\t\t\tthis.on(\"pointercancel\", this.#boundOnPointerUp);\r\n\t\t} else {\r\n\t\t\t// disable\r\n\t\t\tthis.off(\"pointerdown\", this.#boundOnPointerDown);\r\n\t\t\t// this.off('pointermove', this.#boundOnPointerMove);\r\n\t\t\tthis.off(\"pointerup\", this.#boundOnPointerUp);\r\n\t\t\tthis.off(\"pointercancel\", this.#boundOnPointerUp);\r\n\t\t}\r\n\r\n\t\tthis.#draggable = d;\r\n\t}\r\n\r\n\t#boundOnPointerDown;\r\n\t#boundOnPointerMove;\r\n\t#boundOnPointerUp;\r\n\r\n\t#pxDelta; // Delta between the symbol's geometry and the initial pointer event\r\n\r\n\t#onPointerDown(ev) {\r\n\t\tthis.on(\"pointermove\", this.#boundOnPointerMove);\r\n\t\tthis.setPointerCapture(ev.pointerId);\r\n\r\n\t\t// Calculate the delta between the pointer hit point and the\r\n\t\t// symbol's geometry\r\n\t\tconst geomPx = this._inAcetate._platina.geomToPx(this.geom);\r\n\t\tconst evPx = getMousePosition(ev);\r\n\t\tthis.#pxDelta = [evPx[0] - geomPx[0], evPx[1] - geomPx[1]];\r\n\r\n\t\t/**\r\n\t\t * @section\r\n\t\t * @event dragstart\r\n\t\t * Fired whenever the user starts dragging a draggable `ExtrudedPoint`.\r\n\t\t */\r\n\t\tthis.fire(\"dragstart\");\r\n\r\n\t\tev.stopPropagation();\r\n\t}\r\n\t#onPointerMove(ev) {\r\n\t\tev.stopPropagation();\r\n\t\tev.preventDefault();\r\n\r\n\t\tconst evPx = getMousePosition(ev);\r\n\t\tconst geomPx = [evPx[0] - this.#pxDelta[0], evPx[1] - this.#pxDelta[1]];\r\n\t\tthis.geometry = this._inAcetate._platina.pxToGeom(geomPx);\r\n\t\t/**\r\n\t\t * @event drag\r\n\t\t * Fired whenever the user keeps dragging a draggable `ExtrudedPoint`.\r\n\t\t */\r\n\t\tthis.fire(\"drag\");\r\n\t}\r\n\t#onPointerUp(ev) {\r\n\t\tthis.off(\"pointermove\", this.#boundOnPointerMove);\r\n\t\tev.stopPropagation();\r\n\t\t/**\r\n\t\t * @event dragend\r\n\t\t * Fired whenever the user stops dragging a draggable `ExtrudedPoint`.\r\n\t\t */\r\n\t\tthis.fire(\"dragend\");\r\n\t}\r\n}\r\n","/**\r\n * @namespace Util\r\n * @function imagePromise(url: URL, fillCache?: Boolean): Promise of HTMLImageElement\r\n *\r\n * Requests the given `URL`, and returns a `Promise` of an `HTMLImageElement`.\r\n *\r\n * By default it **caches all images**, and uses a hash `Map` internally to\r\n * de-duplicate loading the same URL. Use a `false` value for `fillCache` to\r\n * prevent this.\r\n *\r\n * @alternative\r\n * @function imagePromise(url: String, fillCache?: Boolean): Promise of HTMLImageElement\r\n * Idem, but using a `String` containing a URL.\r\n *\r\n * @alternative\r\n * @function imagePromise(image: HTMLImageElement, fillCache?: Boolean): Promise of HTMLImageElement\r\n * Returns a `Promise` that immediately resolves to the given image. Does not\r\n * cache the image.\r\n */\r\n\r\n// TODO: This cache is ever increasing. There should be a way to clean it up, since\r\n// it will hold references to potentially big unused images.\r\n\r\nconst cache = new Map();\r\n\r\nexport default async function imagePromise(url, fillCache = true) {\r\n\tif (url instanceof HTMLImageElement || url instanceof ImageData) {\r\n\t\treturn Promise.resolve(url);\r\n\t} else if (typeof url === \"string\") {\r\n\t\turl = new URL(url, document.URL);\r\n\t} else if (!(url instanceof URL)) {\r\n\t\tthrow new Error(\r\n\t\t\t\"Bad parameter to imagePromise(): must be either a URL or an image.\"\r\n\t\t);\r\n\t}\r\n\r\n\tconst urlStr = url.toString();\r\n\tconst cached = cache.get(urlStr);\r\n\tif (cached) {\r\n\t\treturn cached;\r\n\t}\r\n\r\n\tconst promise = new Promise((res, rej) => {\r\n\t\tconst img = new Image();\r\n\t\timg.addEventListener(\"load\", (ev) => res(ev.target));\r\n\t\timg.addEventListener(\"error\", rej);\r\n\t\timg.addEventListener(\"abort\", rej);\r\n\t\timg.crossOrigin = true;\r\n\t\timg.src = url;\r\n\t});\r\n\t// const promise = Promise.any(known.map(format=>format.urlToRaster(urlStr)));\r\n\r\n\tif (fillCache) {\r\n\t\tcache.set(urlStr, promise);\r\n\t}\r\n\treturn promise;\r\n}\r\n","/**\r\n * @namespace util\r\n *\r\n * @function imagifyFetchResponse(r: Response): Promise to HTMLImageElement\r\n *\r\n * Given a `Response` from a `fetch` call, wraps it into an image - meant for\r\n * `fetch` operations that are supposed to retrieve an image.\r\n *\r\n * If the parameter is not a `Response`, it will be passed through transparently.\r\n *\r\n */\r\n\r\nexport default function imagifyFetchResponse(r) {\r\n\tif (r instanceof Response) {\r\n\t\treturn new Promise((res,rej)=> {\r\n\t\t\tconst img = new Image();\r\n\t\t\timg.addEventListener(\"load\", (ev) => res(ev.target));\r\n\t\t\timg.addEventListener(\"error\", rej);\r\n\t\t\timg.addEventListener(\"abort\", rej);\r\n\t\t\timg.crossOrigin = true;\r\n\t\t\tr.blob().then(blob=>img.src=URL.createObjectURL(blob));\r\n\t\t});\r\n\t} else {\r\n\t\treturn r;\r\n\t}\r\n}\r\n","import Acetate from \"./Acetate.mjs\";\r\nimport Allocator from \"../glii/src/Allocator.mjs\";\r\n\r\n/**\r\n * @class AcetateVertices\r\n * @inherits Acetate\r\n *\r\n * An abstract `Acetate` that implements multiple vertices per symbol.\r\n *\r\n * Most `Acetate`s draw symbols that must be represented by more than one vertex\r\n * (and typically forming triangles), and should inherit this functionality.\r\n *\r\n * The only exception is acetates that do not need vertex indices at all because\r\n * they do not rely on primitives (i.e. triangles) - the `AcetateDot` being the only\r\n * instance of such.\r\n */\r\n\r\nexport default class AcetateVertices extends Acetate {\r\n\tconstructor(glii, opts) {\r\n\t\tsuper(glii, opts);\r\n\r\n\t\t// The SparseIndices allocates *vertex slots* on primitives, e.g.:\r\n\t\t// * 3 slots per triangle, or\r\n\t\t// * 2 slots per line segment\r\n\t\tthis._indices = new this.glii.SparseIndices({\r\n\t\t\t// Glii defaults to UNSIGNED_SHORT, meaning a max of 2^16=65536\r\n\t\t\t// primitive vertex slots (~32k lines ~21k triangles). It's\r\n\t\t\t// reasonable to expect more, so this asks for 32-bit\r\n\t\t\t// pointers, meaning a max of 2^32 primitive slots.\r\n\t\t\ttype: this.glii.UNSIGNED_INT,\r\n\t\t});\r\n\r\n\t\t// The attribute allocator allocates *attribute slots*,\r\n\t\t// one per needed vertex (even if that vertex is used several times in several\r\n\t\t// slots to be shared between several triangles/segments/primitives)\r\n\t\tthis._attribAllocator = new Allocator();\r\n\t}\r\n\r\n\tglProgramDefinition() {\r\n\t\tconst opts = super.glProgramDefinition();\r\n\t\treturn {\r\n\t\t\t...opts,\r\n\t\t\tindexBuffer: this._indices,\r\n\t\t\tblend: {\r\n\t\t\t\tequationRGB: this.glii.FUNC_ADD,\r\n\t\t\t\tequationAlpha: this.glii.FUNC_ADD,\r\n\r\n\t\t\t\tsrcRGB: this.glii.SRC_ALPHA,\r\n\t\t\t\tdstRGB: this.glii.ONE_MINUS_SRC_ALPHA,\r\n\t\t\t\tsrcAlpha: this.glii.ONE,\r\n\t\t\t\tdstAlpha: this.glii.ONE_MINUS_SRC_ALPHA,\r\n\t\t\t},\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * @section Internal Methods\r\n\t * @uninheritable\r\n\t * @method reproject(): this\r\n\t * Runs `toCRS` on the coordinates of all known symbols, and (re)sets the values in\r\n\t * the coordinates attribute buffer.\r\n\t */\r\n\treprojectAll() {\r\n\t\tthis._attribAllocator.forEachBlock((start, length) => {\r\n\t\t\tthis.reproject(start, length);\r\n\t\t});\r\n\t\treturn this;\r\n\t}\r\n\r\n\t_getStridedArrays(_, maxIdx) {\r\n\t\treturn [\r\n\t\t\t// Vertex indices\r\n\t\t\tthis._indices.asTypedArray(maxIdx),\r\n\t\t];\r\n\t}\r\n\r\n\t_getPerPointStridedArrays(maxVtx, maxIdx) {\r\n\t\treturn [];\r\n\t}\r\n\r\n\t_commitStridedArrays(_, __, baseIdx, idxCount) {\r\n\t\tthis._indices.commit(baseIdx, idxCount);\r\n\t}\r\n\r\n\t_commitPerPointStridedArrays(vtx, vtxCount) {\r\n\t\t// noop\r\n\t}\r\n\r\n\t/**\r\n\t * @method deallocate(symbol: GleoSymbol): this\r\n\t * Deallocate the symbol from this acetate (so it's not drawn on the next refresh)\r\n\t */\r\n\tdeallocate(symbol) {\r\n\t\treturn this.multiDeallocate([symbol]);\r\n\t}\r\n\r\n\tmultiAllocate(symbols) {\r\n\t\t// Skip already added symbols\r\n\t\tsymbols = symbols.filter((s) => isNaN(s.attrBase));\r\n\t\tif (symbols.length === 0) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst totalVertices = symbols.reduce((acc, ext) => acc + ext.attrLength, 0);\r\n\t\tconst baseVtx = this._attribAllocator.allocateBlock(totalVertices);\r\n\t\tlet vtxAcc = baseVtx;\r\n\r\n\t\tconst totalIndices = symbols.reduce((acc, ext) => acc + ext.idxLength, 0);\r\n\t\tconst baseIdx = this._indices.allocateSlots(totalIndices);\r\n\t\tlet idxAcc = baseIdx;\r\n\r\n\t\tlet stridedArrays = this._getStridedArrays(\r\n\t\t\tbaseVtx + totalVertices,\r\n\t\t\tbaseIdx + totalIndices\r\n\t\t);\r\n\r\n\t\tsymbols.forEach((sym) => {\r\n\t\t\tsym._inAcetate = this;\r\n\t\t\tsym.attrBase = vtxAcc;\r\n\t\t\tsym.idxBase = idxAcc;\r\n\t\t\tthis._knownSymbols[vtxAcc] = sym;\r\n\r\n\t\t\tsym._setGlobalStrides(...stridedArrays);\r\n\r\n\t\t\tvtxAcc += sym.attrLength;\r\n\t\t\tidxAcc += sym.idxLength;\r\n\t\t});\r\n\r\n\t\tthis._commitStridedArrays(baseVtx, totalVertices, baseIdx, totalIndices);\r\n\r\n\t\tif (this._crs) {\r\n\t\t\tthis.reproject(baseVtx, totalVertices, symbols);\r\n\t\t}\r\n\r\n\t\t// The AcetateInteractive functionality will assign IDs to symbol vertices.\r\n\t\tthis.multiAddIds?.(symbols, baseVtx, baseVtx + totalVertices);\r\n\r\n\t\tthis.dirty = true;\r\n\t\treturn this;\r\n\t}\r\n\r\n\tmultiDeallocate(symbols) {\r\n\t\tsymbols = symbols.filter((s) => !!s);\r\n\t\tsymbols.sort((a, b) => a.idxBase - b.idxBase);\r\n\r\n\t\tif (symbols.length === 0) {\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\t// let attribBlocks = [];\r\n\t\tlet blockStart = symbols[0].idxBase,\r\n\t\t\tblockLength = 0;\r\n\t\tsymbols.forEach((symbol) => {\r\n\t\t\tif (blockStart + blockLength === symbol.idxBase) {\r\n\t\t\t\tblockLength += symbol.idxLength;\r\n\t\t\t} else {\r\n\t\t\t\t// attribBlocks.push([ blockStart, blockLength ]);\r\n\t\t\t\tthis._indices.deallocateSlots(blockStart, blockLength);\r\n\t\t\t\tblockStart = symbol.idxBase;\r\n\t\t\t\tblockLength = symbol.idxLength;\r\n\t\t\t}\r\n\t\t});\r\n\t\tthis._indices.deallocateSlots(blockStart, blockLength);\r\n\r\n\t\tsymbols.sort((a, b) => a.attrBase - b.attrBase);\r\n\r\n\t\tblockStart = symbols[0].attrBase;\r\n\t\tblockLength = 0;\r\n\r\n\t\tsymbols.forEach((symbol) => {\r\n\t\t\tif (blockStart + blockLength === symbol.attrBase) {\r\n\t\t\t\tblockLength += symbol.attrLength;\r\n\t\t\t} else {\r\n\t\t\t\t// attribBlocks.push([ blockStart, blockLength ]);\r\n\t\t\t\tthis._attribAllocator.deallocateBlock(blockStart, blockLength);\r\n\t\t\t\tblockStart = symbol.attrBase;\r\n\t\t\t\tblockLength = symbol.attrLength;\r\n\t\t\t}\r\n\t\t\tdelete this._knownSymbols[symbol.attrBase];\r\n\t\t\tsymbol.updateRefs(undefined, undefined, undefined);\r\n\t\t});\r\n\t\tthis._attribAllocator.deallocateBlock(blockStart, blockLength);\r\n\r\n\t\t// Edge case for the last symbol. See comments on Acetate.multiDeallocate().\r\n\t\tif (!this._knownSymbols.some(() => true)) {\r\n\t\t\tthis._knownSymbols = [];\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method reproject(start: Number, length: Number, symbols?: Array of GleoSymbol): Array of Number\r\n\t * Dumps a new set of values to the `this._coords` attribute buffer, based\r\n\t * on the known set of symbols added to the acetate (only those which have\r\n\t * their attribute offsets between `start` and `start+length`.\r\n\t *\r\n\t * If the list of symbols is already known, they can be passed as a third\r\n\t * argument for a performance improvement.\r\n\t *\r\n\t * This default implementation **assumes** that the `attrLength` of a\r\n\t * `GleoSymbol` is equal to the length of its `Geometry` (i.e. there's\r\n\t * `one vertex per point in the geometry).\r\n\t *\r\n\t * Returns the data set into the attribute buffer: a ' Float32Array`\r\n\t * in the form `[x1,y1, x2,y2, ... xn,yn]`.\r\n\t */\r\n\treproject(start, length, symbols) {\r\n\t\tconst end = start + length;\r\n\t\tlet maxIdx = -Infinity;\r\n\t\tlet minIdx = Infinity;\r\n\r\n\t\t// In most cases, it's safe to assume that relevant symbols in the same\r\n\t\t// attribute allocation block have their vertex attributes in a\r\n\t\t// compacted manner.\r\n\t\t// The exception is tiles: tile vertex attributes are allocated in bulk\r\n\t\t// (enough to fill a whole texture atlas), before actually instantiating\r\n\t\t// tile symbols. Tile acetates shall overload this method.\r\n\r\n\t\tconst stridedCoords = this._coords.asStridedArray(end);\r\n\t\tconst geomStrides = this._getGeometryStridedArrays(end);\r\n\r\n\t\tconst relevantSymbols =\r\n\t\t\tsymbols ??\r\n\t\t\tthis._knownSymbols.filter((symbol, attrIdx) => {\r\n\t\t\t\treturn attrIdx >= start && attrIdx + symbol.attrLength <= start + length;\r\n\t\t\t});\r\n\r\n\t\trelevantSymbols.forEach((s) => {\r\n\t\t\tconst geom = s.geometry.toCRS(this._crs);\r\n\t\t\tstridedCoords.set(geom.coords, s.attrBase);\r\n\t\t\ts._setGeometryStrides(geom, ...geomStrides);\r\n\t\t\tmaxIdx = Math.max(maxIdx, s.idxBase + s.idxLength);\r\n\t\t\tminIdx = Math.min(maxIdx, s.idxBase);\r\n\t\t});\r\n\r\n\t\tthis._coords.commit(start, length);\r\n\t\tthis._commitGeometryStridedArrays(start, length, minIdx, maxIdx - minIdx);\r\n\r\n\t\tconst coordData = new Float32Array(stridedCoords.buffer, start * 8, length * 2);\r\n\t\tsuper.expandBBox(coordData);\r\n\t\treturn coordData;\r\n\t}\r\n}\r\n","import AcetateVertices from \"./AcetateVertices.mjs\";\r\nimport GleoMouseEvent from \"../dom/GleoMouseEvent.mjs\";\r\nimport GleoPointerEvent from \"../dom/GleoPointerEvent.mjs\";\r\n\r\n/// TODO: Turn this into a mixin. There might be a need for interactive `AcetateDot`s.\r\n\r\n/**\r\n * @class AcetateInteractive\r\n * @inherits AcetateVertices\r\n *\r\n * @relationship associated GleoEvent, 0..1, 0..n\r\n *\r\n * An `Acetate` that renders its symbols into an invisible framebuffer\r\n * so pointer events can query it to know the symbol that has been rendered\r\n * in a given pixel.\r\n */\r\nexport default class AcetateInteractive extends AcetateVertices {\r\n\tconstructor(\r\n\t\ttarget,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @section AcetateInteractive Options\r\n\t\t\t * @option interactive: Boolean = true\r\n\t\t\t * When set to `false`, disables all interactivity of symbols in this\r\n\t\t\t * acetate, regardless of the symgols' settings. *Should* improve\r\n\t\t\t * performance a bit when off.\r\n\t\t\t */\r\n\t\t\tinteractive = true,\r\n\r\n\t\t\t/**\r\n\t\t\t * @option pointerTolerance: Number = 3\r\n\t\t\t * The distance, in CSS pixels, that the pointer can be away from\r\n\t\t\t * a symbol to trigger a pointer event.\r\n\t\t\t *\r\n\t\t\t * (This is achieved internally by extruding vertices this\r\n\t\t\t * extra amount; it does **not** perfectly buffer the visible\r\n\t\t\t * symbol, but rather makes the clickable triangles slightly larger\r\n\t\t\t * than the visible triangles)\r\n\t\t\t *\r\n\t\t\t * (This assumes that the sprite image somehow fills its space;\r\n\t\t\t * transparent regions in the sprite image will shift and may behave\r\n\t\t\t * unintuitively; a transparent border will effectively lower the\r\n\t\t\t * extra tolerance)\r\n\t\t\t */\r\n\t\t\tpointerTolerance = 3,\r\n\r\n\t\t\t...opts\r\n\t\t} = {}\r\n\t) {\r\n\t\tsuper(target, opts);\r\n\r\n\t\tif (interactive) {\r\n\t\t\tthis.#isInteractive = true;\r\n\t\t\tthis.#pointerTolerance = pointerTolerance;\r\n\t\t\ttry {\r\n\t\t\t\tif (!(this.glii.gl instanceof WebGL2RenderingContext)) {\r\n\t\t\t\t\t// This enables the *creation* of floating point textures\r\n\t\t\t\t\tthis.glii.loadExtension(\"OES_texture_float\");\r\n\t\t\t\t}\r\n\t\t\t\t// This enables *rendering* to a floating point texture\r\n\t\t\t\tthis.glii.loadExtension(\"EXT_color_buffer_float\");\r\n\r\n\t\t\t\t// This enables *overlapping* triangles on the floating point texture\r\n\t\t\t\tthis.glii.loadExtension(\"EXT_float_blend\");\r\n\r\n\t\t\t\tthis.#webgl2 = true;\r\n\t\t\t} catch (ex) {\r\n\t\t\t\tthis.#webgl2 = false;\r\n\t\t\t}\r\n\r\n\t\t\tif (this.#webgl2) {\r\n\t\t\t\t// The default is to use R32F textures, which needs WebGL2 or a\r\n\t\t\t\t// bunch of extensions.\r\n\t\t\t\tthis.#ids = new this.glii.SingleAttribute({\r\n\t\t\t\t\tsize: 1,\r\n\t\t\t\t\tgrowFactor: 1.2,\r\n\t\t\t\t\tusage: this.glii.STATIC_DRAW,\r\n\r\n\t\t\t\t\tglslType: \"float\",\r\n\t\t\t\t\ttype: Float32Array,\r\n\t\t\t\t\tnormalized: true,\r\n\t\t\t\t});\r\n\t\t\t} else {\r\n\t\t\t\t// IDs are always integers, and are stored as 1-byte vec4s.\r\n\t\t\t\t// This is done in order to simplify the GL workflow: when rendering\r\n\t\t\t\t// into a texture (into a framebuffer with a texture as its 0th\r\n\t\t\t\t// colour attachment), the texture must be RGBA/UNSIGNED_BYTE.\r\n\t\t\t\t// In order to avoid as many calculations inside the shaders,\r\n\t\t\t\t// the JS layer will hack int32s into int24s into 3x int8s\r\n\t\t\t\t// into 3x 8-bit RGBA, the shaders will only handle 8-bit vec3s,\r\n\t\t\t\t// filling the alpha byte to 0xff; reading\r\n\t\t\t\t// from the framebuffer will return 4x 8-bit RGBA, and the JS\r\n\t\t\t\t// layer will glue together the int24 from that.\r\n\t\t\t\tthis.#ids = new this.glii.SingleAttribute({\r\n\t\t\t\t\tsize: 1,\r\n\t\t\t\t\tgrowFactor: 1.2,\r\n\t\t\t\t\tusage: this.glii.STATIC_DRAW,\r\n\r\n\t\t\t\t\tglslType: \"vec4\",\r\n\t\t\t\t\ttype: Uint8Array,\r\n\t\t\t\t\tnormalized: true,\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\t// The internal `Map` from numerical ID to `GleoSymbol` instance.\r\n\t\t\tthis.#idMap = new Map();\r\n\r\n\t\t\tthis.#nextSymbolId = 1;\r\n\t\t} else {\r\n\t\t\tthis.#isInteractive = false;\r\n\t\t}\r\n\t}\r\n\r\n\t#isInteractive;\r\n\t#pointerTolerance;\r\n\t#webgl2;\r\n\t#ids;\r\n\t#idMap;\r\n\t#nextSymbolId;\r\n\r\n\t#idsTexture;\r\n\t#idsFramebuffer;\r\n\t#idProgram;\r\n\r\n\t// Needed for setPointerCapture/releasePointerCapture functionality.\r\n\t#pointerCaptureMap = new Map();\r\n\r\n\t/**\r\n\t * @property isInteractive: Boolean\r\n\t * Whether the acetate has interactivty (pointer events) enabled. Read-only.\r\n\t */\r\n\tget isInteractive() {\r\n\t\treturn this.#isInteractive;\r\n\t}\r\n\r\n\tresize(x, y) {\r\n\t\tsuper.resize(x, y);\r\n\t\tif (!this.#isInteractive) {\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\tconst glii = this.glii;\r\n\t\tconst opts = this.glIdProgramDefinition();\r\n\r\n\t\tif (!this.#idsFramebuffer) {\r\n\t\t\t// \t\tthis.#idsTexture && this.#idsTexture.destroy();\r\n\t\t\t// \t\tthis.#idsFramebuffer && this.#idsFramebuffer.destroy();\r\n\t\t\tif (this.#webgl2) {\r\n\t\t\t\t// R32F texture\r\n\t\t\t\tthis.#idsTexture = new glii.Texture({\r\n\t\t\t\t\tformat: glii.gl.RED,\r\n\t\t\t\t\tinternalFormat: glii.gl.R32F,\r\n\t\t\t\t\ttype: glii.FLOAT,\r\n\t\t\t\t});\r\n\t\t\t} else {\r\n\t\t\t\t// Default RGBA8 output texture\r\n\t\t\t\tthis.#idsTexture = new glii.Texture({});\r\n\t\t\t}\r\n\r\n\t\t\tthis.#idsFramebuffer = new glii.FrameBuffer({\r\n\t\t\t\tcolor: [this.#idsTexture],\r\n\t\t\t\tdepth: new glii.RenderBuffer({\r\n\t\t\t\t\twidth: x,\r\n\t\t\t\t\theight: y,\r\n\t\t\t\t\tinternalFormat: glii.DEPTH_COMPONENT16,\r\n\t\t\t\t}),\r\n\t\t\t\twidth: x,\r\n\t\t\t\theight: y,\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\tthis.#idsFramebuffer.resize(x, y);\r\n\t\t}\r\n\r\n\t\tif (this.#idProgram) {\r\n\t\t\tthis.#idProgram.setTarget(this.#idsFramebuffer);\r\n\t\t} else {\r\n\t\t\topts.fragmentShaderSource += opts.fragmentShaderMain\r\n\t\t\t\t? `\\nvoid main(){${opts.fragmentShaderMain}}`\r\n\t\t\t\t: \"\";\r\n\t\t\topts.target = this.#idsFramebuffer;\r\n\t\t\tthis.#idProgram = new glii.WebGL1Program(opts);\r\n\r\n\t\t\tthis._programs.addProgram(this.#idProgram);\r\n\t\t}\r\n\r\n\t\t// Assuming that a resize means that the devicePixelRatio\r\n\t\t// might have changed. This is common in desktop browsers with\r\n\t\t// Ctrl+'+' / Ctrl+'-'\r\n\t\tthis.#idProgram.setUniform(\r\n\t\t\t\"uPointerTolerance\",\r\n\t\t\tthis.#pointerTolerance * (devicePixelRatio ?? 1)\r\n\t\t);\r\n\r\n\t\tconst depthClear =\r\n\t\t\topts.depth === glii.LEQUAL || opts.depth === glii.LESS ? 1 : -1;\r\n\r\n\t\tthis._idClear = new glii.WebGL1Clear({\r\n\t\t\tcolor: [0, 0, 0, 0],\r\n\t\t\t// color: [1, 1, 1, 1],\r\n\t\t\ttarget: this.#idsFramebuffer,\r\n\t\t\t// depth: 1 << 15,\r\n\t\t\tdepth: depthClear,\r\n\t\t\t// depth: 0.6,\r\n\t\t});\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @property idsTexture: Texture\r\n\t * Read-only accessor to the Glii `Texture` holding the internal IDs of\r\n\t * interactive symbols.\r\n\t */\r\n\tget idsTexture() {\r\n\t\treturn this.#idsTexture;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Subclass interface\r\n\t * @uninheritable\r\n\t *\r\n\t * Subclasses of `Acetate` can provide/define the following methods:\r\n\t *\r\n\t * @method glIdProgramDefinition(): Object\r\n\t * Returns a set of `WebGL1Program` options, that will be used to create the WebGL1\r\n\t * program to be run at every refresh to create the texture containing the symbol IDs.\r\n\t * This is needed for the `getSymbolAt()` functionality to work.\r\n\t *\r\n\t * By default, the non-interactive program (i.e. the result of `glProgramDefinition`)\r\n\t * is reused. The ID attribute `aId` is added, a new varying `vId` is added,\r\n\t * the vertex shader is modified to dump the new attribute into the new varying,\r\n\t * and the fragment shader is replaced.\r\n\t *\r\n\t * The default interactive fragment shader dumps `vId` to the RGB component and\r\n\t * sets the alpha value. It looks like:\r\n\t * ```glsl\r\n\t * void main() {\r\n\t * \tgl_FragColor.rgb = vId;\r\n\t * \tgl_FragColor.a = 1.0;\r\n\t * }`\r\n\t * ```\r\n\t *\r\n\t * Subclasses can redefine the fragment shader source if desired; a use case is to apply\r\n\t * masking so that transparent fragments of the `GleoSymbol` are `discard`ed\r\n\t * within the interactive fragment shader (see the implementation of `AcetateSprite`).\r\n\t * It's recommended that subclasses don't change any other bits of this program definition.\r\n\t */\r\n\tglIdProgramDefinition() {\r\n\t\tconst def = this.glProgramDefinition();\r\n\r\n\t\t/// TODO: Modify this shader so that if aID.a is (lower than) zero, then\r\n\t\t/// the vertex's gl_Position is (0,0,0,0) and no fragments are spawned.\r\n\r\n\t\tconst regexpExtrude = /(\\W)aExtrude(\\W)/g;\r\n\t\tconst replacementExtrude = function replacement(_, pre, post) {\r\n\t\t\treturn `${pre}(aExtrude + uPointerTolerance * sign(aExtrude))${post}`;\r\n\t\t};\r\n\r\n\t\treturn {\r\n\t\t\t...def,\r\n\t\t\tattributes: {\r\n\t\t\t\taId: this.#ids,\r\n\t\t\t\t...def.attributes,\r\n\t\t\t},\r\n\t\t\tuniforms: {\r\n\t\t\t\tuPointerTolerance: \"float\",\r\n\t\t\t\t...def.uniforms,\r\n\t\t\t},\r\n\t\t\tvertexShaderSource:\r\n\t\t\t\tdef.vertexShaderSource.replace(regexpExtrude, replacementExtrude) +\r\n\t\t\t\t(this.#webgl2\r\n\t\t\t\t\t? `\r\n\t\t\t\tvoid main() {\r\n\t\t\t\t\t// if (aId > 0.0) {\r\n\t\t\t\t\t\tvId = aId;\r\n\t\t\t\t\t\t${def.vertexShaderMain}\r\n\t\t\t\t\t// }\r\n\t\t\t\t}`\r\n\t\t\t\t\t: `\r\n\t\t\t\tvoid main() {\r\n\t\t\t\t\tif (aId.a > 0.0) {\r\n\t\t\t\t\t\tvId = aId;\r\n\t\t\t\t\t\t${def.vertexShaderMain}\r\n\t\t\t\t\t}\r\n\t\t\t\t}`),\r\n\t\t\tvaryings: {\r\n\t\t\t\tvId: this.#webgl2 ? \"float\" : \"vec4\",\r\n\t\t\t\t...def.varyings,\r\n\t\t\t},\r\n\t\t\tfragmentShaderMain: this.#webgl2\r\n\t\t\t\t? `gl_FragColor.r = vId;`\r\n\t\t\t\t: `gl_FragColor = vId;`,\r\n\t\t\t// depth: this.glii.LESS,\r\n\t\t\t// depth: this.glii.GREATER,\r\n\t\t\tunusedWarning: false,\r\n\t\t\tblend: {\r\n\t\t\t\tequationRGB: this.glii.FUNC_ADD,\r\n\t\t\t\tequationAlpha: this.glii.FUNC_ADD,\r\n\r\n\t\t\t\tsrcRGB: this.glii.ONE,\r\n\t\t\t\tsrcAlpha: this.glii.ONE,\r\n\t\t\t\tdstRGB: this.glii.ZERO,\r\n\t\t\t\tdstAlpha: this.glii.ZERO,\r\n\t\t\t},\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * @method multiAddIds(symbols: Array of GleoSymbol, baseVtx: Number, maxVtx?: Number): this\r\n\t * Given an array of symbols (as per `multiAdd`) and a base vertex index,\r\n\t * assigns a numerical ID to each symbol (unique in the acetate), packs\r\n\t * the data, and updates the corresponding attribute buffer.\r\n\t *\r\n\t * This is meant to be called from within the `multiAdd()` method of subclasses.\r\n\t */\r\n\tmultiAddIds(symbols, baseVtx, maxVtx) {\r\n\t\tif (!this.#isInteractive) {\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\tif (!maxVtx) {\r\n\t\t\tmaxVtx = 0;\r\n\t\t\tsymbols.forEach(\r\n\t\t\t\t(s) => (maxVtx = Math.max(maxVtx, s.attrBase + s.attrLength))\r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\t/// TODO: Use a Glii `Allocator` instead of an incrementing counter.\r\n\t\t/// There is no foreseeable case where 2^24 (16m777k216) symbols will\r\n\t\t/// be added to an acetate, though.\r\n\r\n\t\t// const strideIds = this.#ids.asStridedArray(baseVtx + symbols.length);\r\n\t\tconst strideIds = this.#ids.asStridedArray(maxVtx);\r\n\r\n\t\tif (this.#webgl2) {\r\n\t\t\tsymbols.forEach((s) => {\r\n\t\t\t\tif (s.interactive) {\r\n\t\t\t\t\tconst id = s._id ?? (this.#nextSymbolId += 1);\r\n\t\t\t\t\tthis.#idMap.set(id, s);\r\n\t\t\t\t\ts._id = id;\r\n\t\t\t\t\tfor (\r\n\t\t\t\t\t\tlet i = s.attrBase, end = s.attrBase + s.attrLength;\r\n\t\t\t\t\t\ti < end;\r\n\t\t\t\t\t\ti++\r\n\t\t\t\t\t) {\r\n\t\t\t\t\t\tstrideIds.set([id], i);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\tlet packedId = new Uint8Array(4);\r\n\t\t\tsymbols.forEach((s) => {\r\n\t\t\t\tif (s.interactive) {\r\n\t\t\t\t\tconst id = s._id ?? (this.#nextSymbolId += 1);\r\n\t\t\t\t\ts._id = id;\r\n\t\t\t\t\tthis.#idMap.set(id, s);\r\n\t\t\t\t\t// packedId[0] = id & 0x3f;\r\n\t\t\t\t\t// packedId[1] = (id >> 6) & 0x3f;\r\n\t\t\t\t\t// packedId[2] = (id >> 12) & 0x3f;\r\n\t\t\t\t\tpackedId[0] = id & 0xff;\r\n\t\t\t\t\tpackedId[1] = (id & 0xff00) >> 8;\r\n\t\t\t\t\tpackedId[2] = (id & 0xff0000) >> 16;\r\n\t\t\t\t\tpackedId[3] = 0xff;\r\n\t\t\t\t} else {\r\n\t\t\t\t\tpackedId.fill(0);\r\n\t\t\t\t}\r\n\t\t\t\tfor (let i = s.attrBase, end = s.attrBase + s.attrLength; i < end; i++) {\r\n\t\t\t\t\tstrideIds.set(packedId, i);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t\tthis.#ids.commit(baseVtx, maxVtx - baseVtx);\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method deallocate(symbol: GleoSymbol): this\r\n\t * Deallocates the symbol from this acetate (so it's not drawn on the next refresh),\r\n\t * and removes the reference from its numerical ID.\r\n\t */\r\n\tremove(symbol) {\r\n\t\tif (!this.#isInteractive) {\r\n\t\t\treturn super.remove(symbol);\r\n\t\t}\r\n\t\tif (symbol === this.#hoveredSymbol && symbol.cursor) {\r\n\t\t\tthis.platina.unqueueCursor(symbol.cursor);\r\n\t\t\tthis.#hoveredSymbol = undefined;\r\n\t\t}\r\n\t\tthis.#idMap.delete(symbol._id);\r\n\t\tsymbol._id = undefined;\r\n\t\treturn super.remove(symbol);\r\n\t}\r\n\r\n\tclear() {\r\n\t\tif (this.#isInteractive) {\r\n\t\t\tthis._idClear?.run();\r\n\t\t}\r\n\t\treturn super.clear();\r\n\t}\r\n\r\n\t/**\r\n\t * @section Internal Methods\r\n\t * @uninheritable\r\n\t *\r\n\t * @method getSymbolAt(x: Number, y: Number): GleoSymbol\r\n\t * Given the (CSS) pixel coordinates of a point (relative to the upper-left corner of\r\n\t * the `GleoMap`'s ``), returns the `GleoSymbol` that has been drawn\r\n\t * at that pixel.\r\n\t *\r\n\t * Returns `undefined` if there is no `GleoSymbol` being drawn at the given\r\n\t * pixel.\r\n\t */\r\n\tgetSymbolAt(x, y) {\r\n\t\tif (!this.#isInteractive || !this.#idsFramebuffer) {\r\n\t\t\treturn undefined;\r\n\t\t}\r\n\r\n\t\t// Textures are inverted in the Y axis because WebGL shenanigans. I know.\r\n\t\tconst h = this.#idsFramebuffer.height;\r\n\t\tconst dpr = devicePixelRatio ?? 1;\r\n\t\tconst [r, g, b, a] = this.#idsFramebuffer.readPixels(dpr * x, h - dpr * y, 1, 1);\r\n\r\n\t\tif (this.#webgl2) {\r\n\t\t\treturn this.#idMap.get(r);\r\n\t\t} else {\r\n\t\t\tif (!a) {\r\n\t\t\t\treturn undefined;\r\n\t\t\t}\r\n\r\n\t\t\tconst id = r + 0x100 * g + 0x10000 * b;\r\n\t\t\treturn this.#idMap.get(id);\r\n\t\t}\r\n\t}\r\n\r\n\t#hoveredSymbol;\r\n\r\n\t/**\r\n\t * @method setPointerCapture(pointerId: Number, symbol: GleoSymbol): this\r\n\t * Sets pointer capture for the given pointer ID to the given symbol.\r\n\t * When pointer capture is set (for a pointer ID), normal hit detection is\r\n\t * skipped, and the capturing symbol will receive the event instead.\r\n\t *\r\n\t * See [`Element.setPointerCapture()`](https://developer.mozilla.org/docs/Web/API/Element/setPointerCapture)\r\n\t */\r\n\tsetPointerCapture(pointerId, symbol) {\r\n\t\tthis.#pointerCaptureMap.set(pointerId, symbol);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method releasePointerCapture(pointerId: Number): this\r\n\t * Clears pointer capture for the given pointer ID. Inverse of `setPointerCapture`.\r\n\t *\r\n\t * See [`Element.releasePointerCapture()`](https://developer.mozilla.org/docs/Web/API/Element/releasePointerCapture)\r\n\t */\r\n\treleasePointerCapture(pointerId) {\r\n\t\t/// TODO: Should this take a `GleoSymbol` as well, to provide a sanity check?\r\n\t\t/// i.e. only a capturing symbol should release capture on a pointer ID.\r\n\t\tthis.#pointerCaptureMap.delete(pointerId);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method dispatchPointerEvent(ev: GleoPointerEvent, evInit: Object): Boolean\r\n\t * Given a event, finds what symbol in this acetate should receive the event\r\n\t * and, if any, makes that symbol dispatch the event. If the symbol event\r\n\t * is not `preventDefault`ed, the acetate itself dispatches the event afterwards.\r\n\t *\r\n\t * This is meant to be called *only* from the containing `GleoMap`.\r\n\t *\r\n\t * Since a `GleoPointerEvent` of type `pointermove` might mean entering/leaving\r\n\t * a symbol, extra `pointerenter`/`pointerover`/`pointerout`/`pointerleave`\r\n\t * might be dispatched as well. To internally ease that, dispatching an event\r\n\t * requires a `evInit` dictionary with which to instantiate these new\r\n\t * synthetic events.\r\n\t *\r\n\t * Return value as per `EventTarget`'s `dispatchEvent`: Boolean `false`\r\n\t * if the event is `preventDefault()`ed, at either the symbol or the acetate level.\r\n\t *\r\n\t * FIXME: This **assumes** that the bbox of the acetate and the containing map\r\n\t * are the same. It should be needed to modify the logic and rely on the `geom`\r\n\t * property of the decorated `GleoPointerEvent` instead.\r\n\t *\r\n\t * That would involve caching the (direct) CRS affine matrix from the last\r\n\t * time the acetate was drawn, apply it to the event's `geom` and then round the\r\n\t * resulting pixel coordinate.\r\n\t */\r\n\tdispatchPointerEvent(ev, init) {\r\n\t\tif (this.queryable) {\r\n\t\t\tev.colour = init.colour = this.getColourAt(ev.canvasX, ev.canvasY);\r\n\t\t}\r\n\t\tif (!this.#isInteractive) {\r\n\t\t\treturn this.dispatchEvent(ev);\r\n\t\t}\r\n\r\n\t\tlet symbol;\r\n\t\tif (\r\n\t\t\tev.type !== \"pointercancel\" &&\r\n\t\t\tev.type !== \"pointerleave\" &&\r\n\t\t\tev.type !== \"pointerout\"\r\n\t\t) {\r\n\t\t\tconst capturingSymbol = this.#pointerCaptureMap.get(ev.pointerId);\r\n\t\t\tif (capturingSymbol) {\r\n\t\t\t\tsymbol = capturingSymbol;\r\n\t\t\t} else {\r\n\t\t\t\tsymbol = this.getSymbolAt(ev.canvasX, ev.canvasY);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (ev.type === \"pointerup\" || ev.type === \"pointercancel\") {\r\n\t\t\tthis.releasePointerCapture(ev.pointerId);\r\n\t\t}\r\n\r\n\t\t// Same as map's _onPointerEvent, events should always be MouseEvent, but\r\n\t\t// Firefox doesn't respect that bit of the standard (as of now)\r\n\t\tconst EventProto = ev instanceof PointerEvent ? GleoPointerEvent : GleoMouseEvent;\r\n\r\n\t\tif (this.#hoveredSymbol && this.#hoveredSymbol !== symbol) {\r\n\t\t\tthis.#hoveredSymbol.dispatchEvent(new EventProto(\"pointerout\", init));\r\n\t\t\tthis.#hoveredSymbol.dispatchEvent(new EventProto(\"pointerleave\", init));\r\n\t\t\tif (this.#hoveredSymbol.cursor) {\r\n\t\t\t\tthis.platina.unqueueCursor(this.#hoveredSymbol.cursor);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (symbol) {\r\n\t\t\tif (this.#hoveredSymbol !== symbol) {\r\n\t\t\t\tsymbol.dispatchEvent(new EventProto(\"pointerenter\", init));\r\n\t\t\t\tsymbol.dispatchEvent(new EventProto(\"pointerover\", init));\r\n\t\t\t\tif (symbol.cursor) {\r\n\t\t\t\t\tthis.platina.queueCursor(symbol.cursor);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (!symbol.dispatchEvent(ev)) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tif (!symbol._eventParents.every((s) => s.dispatchEvent(ev))) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\t// } else {\r\n\t\t\t// \tthis.releasePointerCapture(ev.pointerId);\r\n\t\t}\r\n\r\n\t\tthis.#hoveredSymbol = symbol;\r\n\r\n\t\treturn this.dispatchEvent(ev);\r\n\t}\r\n\r\n\t/// DEBUG\r\n\tget _idsTexture() {\r\n\t\treturn this.#idsTexture;\r\n\t}\r\n}\r\n","import AcetateInteractive from \"./AcetateInteractive.mjs\";\r\n\r\n/**\r\n * @class AcetateExtrudedPoint\r\n * @inherits AcetateInteractive\r\n *\r\n * Abstract class, containing functionality common to acetates that draw point\r\n * symbols (`Sprite`, `Pie`, etc).\r\n */\r\nexport default class AcetateExtrudedPoint extends AcetateInteractive {\r\n\tconstructor(glii, opts) {\r\n\t\tsuper(glii, opts);\r\n\r\n\t\tthis._extrusions = new this.glii.SingleAttribute({\r\n\t\t\tusage: this.glii.STATIC_DRAW,\r\n\t\t\tsize: 1,\r\n\t\t\tgrowFactor: 1.2,\r\n\r\n\t\t\tglslType: \"vec2\",\r\n\t\t\ttype: Float32Array,\r\n\t\t\tnormalized: false,\r\n\t\t});\r\n\t}\r\n\r\n\tglProgramDefinition() {\r\n\t\tconst opts = super.glProgramDefinition();\r\n\t\treturn {\r\n\t\t\t...opts,\r\n\t\t\tattributes: {\r\n\t\t\t\t...opts.attributes,\r\n\t\t\t\taExtrude: this._extrusions,\r\n\t\t\t},\r\n\t\t};\r\n\t}\r\n\r\n\t_commitStridedArrays(baseVtx, vtxCount, baseIdx, idxCount) {\r\n\t\tthis._extrusions.commit(baseVtx, vtxCount);\r\n\t\tthis._attrs.commit(baseVtx, vtxCount);\r\n\t\treturn super._commitStridedArrays(baseVtx, vtxCount, baseIdx, idxCount);\r\n\t}\r\n\r\n\tmultiAdd(syms) {\r\n\t\tsuper.multiAdd(syms);\r\n\t\treturn super.multiAllocate(syms);\r\n\t}\r\n\r\n\t/**\r\n\t * @section Internal Methods\r\n\t * @uninheritable\r\n\t *\r\n\t * @method reproject(start: Number, length: Number, symbols: Array of GleoSymbol): Array of Number\r\n\t * Dumps a new set of values to the `this._coords` attribute buffer, based on the known\r\n\t * set of symbols added to the acetate (only those which have their attribute offsets\r\n\t * between `start` and `start+length`. Each symbol will spawn as many\r\n\t * coordinate `vec2`s as their `attrLength` property.\r\n\t *\r\n\t * Returns the data set into the attribute buffer: a plain array of coordinates\r\n\t * in the form `[x1,y1, x2,y2, ... xn,yn]`.\r\n\t */\r\n\treproject(start, length, symbols) {\r\n\t\tlet relevantSymbols =\r\n\t\t\tsymbols ??\r\n\t\t\tthis._knownSymbols.filter((symbol, attrIdx) => {\r\n\t\t\t\treturn (\r\n\t\t\t\t\tsymbol.attrBase !== undefined &&\r\n\t\t\t\t\tattrIdx >= start &&\r\n\t\t\t\t\tattrIdx + symbol.attrLength <= start + length\r\n\t\t\t\t);\r\n\t\t\t});\r\n\r\n\t\tlet addr = 0;\r\n\r\n\t\t// In most cases, it's safe to assume that relevant symbols in the same\r\n\t\t// attribute allocation block have their vertex attributes in a\r\n\t\t// compacted manner.\r\n\r\n\t\tconst coordData = new Float64Array(length * 2);\r\n\r\n\t\trelevantSymbols.forEach((symbol) => {\r\n\t\t\tconst projected = symbol.geometry.toCRS(this.platina.crs).coords;\r\n\r\n\t\t\t/// TODO: Debug edge case when offsetting CRS with clusters.\r\n\t\t\t// if (symbol.attrBase !== start + addr/2) {debugger;}\r\n\r\n\t\t\tfor (let i = 0; i < symbol.attrLength; i++) {\r\n\t\t\t\tcoordData.set(projected, addr);\r\n\t\t\t\taddr += 2;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t//console.log(\"Symbol reprojected:\", coordData);\r\n\t\tthis.multiSetCoords(start, coordData);\r\n\r\n\t\tthis.dirty = true;\r\n\r\n\t\treturn coordData;\r\n\t}\r\n}\r\n","/**\r\n * ISC License\r\n *\r\n * Copyright (c) 2016, Mapbox\r\n *\r\n * Permission to use, copy, modify, and/or distribute this software for any purpose\r\n * with or without fee is hereby granted, provided that the above copyright notice\r\n * and this permission notice appear in all copies.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\r\n * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\r\n * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\r\n * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\r\n * THIS SOFTWARE.\r\n */\r\n\r\n/**\r\n * Create a new ShelfPack bin allocator.\r\n *\r\n * Uses the Shelf Best Height Fit algorithm from\r\n * http://clb.demon.fi/files/RectangleBinPack.pdf\r\n *\r\n * @ class ShelfPack\r\n * @ param {number} [w=64] Initial width of the sprite\r\n * @ param {number} [h=64] Initial width of the sprite\r\n * @ param {Object} [options]\r\n * @ param {boolean} [options.autoResize=false] If `true`, the sprite will automatically grow\r\n * @ example\r\n * var sprite = new ShelfPack(64, 64, { autoResize: false });\r\n */\r\nexport default class ShelfPack {\r\n\tconstructor(w, h, options) {\r\n\t\toptions = options || {};\r\n\t\tthis.w = w || 64;\r\n\t\tthis.h = h || 64;\r\n\t\tthis.autoResize = !!options.autoResize;\r\n\t\tthis.shelves = [];\r\n\t\tthis.freebins = [];\r\n\t\tthis.stats = {};\r\n\t\tthis.bins = {};\r\n\t\tthis.maxId = 0;\r\n\t}\r\n\r\n\t/**\r\n\t * Batch pack multiple bins into the sprite.\r\n\t *\r\n\t * @ param {Object[]} bins Array of requested bins - each object should have `width`, `height` (or `w`, `h`) properties\r\n\t * @ param {number} bins[].w Requested bin width\r\n\t * @ param {number} bins[].h Requested bin height\r\n\t * @ param {Object} [options]\r\n\t * @ param {boolean} [options.inPlace=false] If `true`, the supplied bin objects will be updated inplace with `x` and `y` properties\r\n\t * @ returns {Bin[]} Array of allocated Bins - each Bin is an object with `id`, `x`, `y`, `w`, `h` properties\r\n\t * @ example\r\n\t * var bins = [\r\n\t * { id: 1, w: 12, h: 12 },\r\n\t * { id: 2, w: 12, h: 16 },\r\n\t * { id: 3, w: 12, h: 24 }\r\n\t * ];\r\n\t * var results = sprite.pack(bins, { inPlace: false });\r\n\t */\r\n\tpack(bins, options) {\r\n\t\tbins = [].concat(bins);\r\n\t\toptions = options || {};\r\n\r\n\t\tvar results = [],\r\n\t\t\tw,\r\n\t\t\th,\r\n\t\t\tid,\r\n\t\t\tallocation;\r\n\r\n\t\tfor (var i = 0; i < bins.length; i++) {\r\n\t\t\tw = bins[i].w || bins[i].width;\r\n\t\t\th = bins[i].h || bins[i].height;\r\n\t\t\tid = bins[i].id;\r\n\r\n\t\t\tif (w && h) {\r\n\t\t\t\tallocation = this.packOne(w, h, id);\r\n\t\t\t\tif (!allocation) {\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\t\t\t\tif (options.inPlace) {\r\n\t\t\t\t\tbins[i].x = allocation.x;\r\n\t\t\t\t\tbins[i].y = allocation.y;\r\n\t\t\t\t\tbins[i].id = allocation.id;\r\n\t\t\t\t}\r\n\t\t\t\tresults.push(allocation);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Shrink the width/height of the sprite to the bare minimum.\r\n\t\t// Since shelf-pack doubles first width, then height when running out of shelf space\r\n\t\t// this can result in fairly large unused space both in width and height if that happens\r\n\t\t// towards the end of bin packing.\r\n\t\tif (this.shelves.length > 0) {\r\n\t\t\tvar w2 = 0;\r\n\t\t\tvar h2 = 0;\r\n\r\n\t\t\tfor (var j = 0; j < this.shelves.length; j++) {\r\n\t\t\t\tvar shelf = this.shelves[j];\r\n\t\t\t\th2 += shelf.h;\r\n\t\t\t\tw2 = Math.max(shelf.w - shelf.free, w2);\r\n\t\t\t}\r\n\r\n\t\t\tthis.resize(w2, h2);\r\n\t\t}\r\n\r\n\t\treturn results;\r\n\t}\r\n\r\n\t/**\r\n\t * Pack a single bin into the sprite.\r\n\t *\r\n\t * Each bin will have a unique identitifer.\r\n\t * If no identifier is supplied in the `id` parameter, one will be created.\r\n\t * Note: The supplied `id` is used as an object index, so numeric values are fastest!\r\n\t *\r\n\t * Bins are automatically refcounted (i.e. a newly packed Bin will have a refcount of 1).\r\n\t * When a bin is no longer needed, use the `ShelfPack.unref` function to mark it\r\n\t * as unused. When a Bin's refcount decrements to 0, the Bin will be marked\r\n\t * as free and its space may be reused by the packing code.\r\n\t *\r\n\t * @ param {number} w Width of the bin to allocate\r\n\t * @ param {number} h Height of the bin to allocate\r\n\t * @ param {number|string} [id] Unique identifier for this bin, (if unsupplied, assume it's a new bin and create an id)\r\n\t * @ returns {Bin} Bin object with `id`, `x`, `y`, `w`, `h` properties, or `null` if allocation failed\r\n\t * @ example\r\n\t * var results = sprite.packOne(12, 16, 'a');\r\n\t */\r\n\tpackOne(w, h, id) {\r\n\t\tvar best = { freebin: -1, shelf: -1, waste: Infinity },\r\n\t\t\ty = 0,\r\n\t\t\tbin,\r\n\t\t\tshelf,\r\n\t\t\twaste,\r\n\t\t\ti;\r\n\r\n\t\t// if id was supplied, attempt a lookup..\r\n\t\tif (typeof id === \"string\" || typeof id === \"number\") {\r\n\t\t\tbin = this.getBin(id);\r\n\t\t\tif (bin) {\r\n\t\t\t\t// we packed this bin already\r\n\t\t\t\tthis.ref(bin);\r\n\t\t\t\treturn bin;\r\n\t\t\t}\r\n\t\t\tif (typeof id === \"number\") {\r\n\t\t\t\tthis.maxId = Math.max(id, this.maxId);\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tid = ++this.maxId;\r\n\t\t}\r\n\r\n\t\t// First try to reuse a free bin..\r\n\t\tfor (i = 0; i < this.freebins.length; i++) {\r\n\t\t\tbin = this.freebins[i];\r\n\r\n\t\t\t// exactly the right height and width, use it..\r\n\t\t\tif (h === bin.maxh && w === bin.maxw) {\r\n\t\t\t\treturn this.allocFreebin(i, w, h, id);\r\n\t\t\t}\r\n\t\t\t// not enough height or width, skip it..\r\n\t\t\tif (h > bin.maxh || w > bin.maxw) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\t// extra height or width, minimize wasted area..\r\n\t\t\tif (h <= bin.maxh && w <= bin.maxw) {\r\n\t\t\t\twaste = bin.maxw * bin.maxh - w * h;\r\n\t\t\t\tif (waste < best.waste) {\r\n\t\t\t\t\tbest.waste = waste;\r\n\t\t\t\t\tbest.freebin = i;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Next find the best shelf..\r\n\t\tfor (i = 0; i < this.shelves.length; i++) {\r\n\t\t\tshelf = this.shelves[i];\r\n\t\t\ty += shelf.h;\r\n\r\n\t\t\t// not enough width on this shelf, skip it..\r\n\t\t\tif (w > shelf.free) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\t// exactly the right height, pack it..\r\n\t\t\tif (h === shelf.h) {\r\n\t\t\t\treturn this.allocShelf(i, w, h, id);\r\n\t\t\t}\r\n\t\t\t// not enough height, skip it..\r\n\t\t\tif (h > shelf.h) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\t// extra height, minimize wasted area..\r\n\t\t\tif (h < shelf.h) {\r\n\t\t\t\twaste = (shelf.h - h) * w;\r\n\t\t\t\tif (waste < best.waste) {\r\n\t\t\t\t\tbest.freebin = -1;\r\n\t\t\t\t\tbest.waste = waste;\r\n\t\t\t\t\tbest.shelf = i;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (best.freebin !== -1) {\r\n\t\t\treturn this.allocFreebin(best.freebin, w, h, id);\r\n\t\t}\r\n\r\n\t\tif (best.shelf !== -1) {\r\n\t\t\treturn this.allocShelf(best.shelf, w, h, id);\r\n\t\t}\r\n\r\n\t\t// No free bins or shelves.. add shelf..\r\n\t\tif (h <= this.h - y && w <= this.w) {\r\n\t\t\tshelf = new Shelf(y, this.w, h);\r\n\t\t\treturn this.allocShelf(this.shelves.push(shelf) - 1, w, h, id);\r\n\t\t}\r\n\r\n\t\t// No room for more shelves..\r\n\t\t// If `autoResize` option is set, grow the sprite as follows:\r\n\t\t// * double whichever sprite dimension is smaller (`w1` or `h1`)\r\n\t\t// * if sprite dimensions are equal, grow width before height\r\n\t\t// * accomodate very large bin requests (big `w` or `h`)\r\n\t\tif (this.autoResize) {\r\n\t\t\tvar h1, h2, w1, w2;\r\n\r\n\t\t\th1 = h2 = this.h;\r\n\t\t\tw1 = w2 = this.w;\r\n\r\n\t\t\tif (w1 <= h1 || w > w1) {\r\n\t\t\t\t// grow width..\r\n\t\t\t\tw2 = Math.max(w, w1) * 2;\r\n\t\t\t}\r\n\t\t\tif (h1 < w1 || h > h1) {\r\n\t\t\t\t// grow height..\r\n\t\t\t\th2 = Math.max(h, h1) * 2;\r\n\t\t\t}\r\n\r\n\t\t\tthis.resize(w2, h2);\r\n\t\t\treturn this.packOne(w, h, id); // retry\r\n\t\t}\r\n\r\n\t\treturn null;\r\n\t}\r\n\r\n\t/**\r\n\t * Called by packOne() to allocate a bin by reusing an existing freebin\r\n\t *\r\n\t * @private\r\n\t * @ param {number} index Index into the `this.freebins` array\r\n\t * @ param {number} w Width of the bin to allocate\r\n\t * @ param {number} h Height of the bin to allocate\r\n\t * @ param {number|string} id Unique identifier for this bin\r\n\t * @ returns {Bin} Bin object with `id`, `x`, `y`, `w`, `h` properties\r\n\t * @ example\r\n\t * var bin = sprite.allocFreebin(0, 12, 16, 'a');\r\n\t */\r\n\tallocFreebin(index, w, h, id) {\r\n\t\tvar bin = this.freebins.splice(index, 1)[0];\r\n\t\tbin.id = id;\r\n\t\tbin.w = w;\r\n\t\tbin.h = h;\r\n\t\tbin.refcount = 0;\r\n\t\tthis.bins[id] = bin;\r\n\t\tthis.ref(bin);\r\n\t\treturn bin;\r\n\t}\r\n\r\n\t/**\r\n\t * Called by `packOne() to allocate bin on an existing shelf\r\n\t *\r\n\t * @private\r\n\t * @ param {number} index Index into the `this.shelves` array\r\n\t * @ param {number} w Width of the bin to allocate\r\n\t * @ param {number} h Height of the bin to allocate\r\n\t * @ param {number|string} id Unique identifier for this bin\r\n\t * @ returns {Bin} Bin object with `id`, `x`, `y`, `w`, `h` properties\r\n\t * @ example\r\n\t * var results = sprite.allocShelf(0, 12, 16, 'a');\r\n\t */\r\n\tallocShelf(index, w, h, id) {\r\n\t\tvar shelf = this.shelves[index];\r\n\t\tvar bin = shelf.alloc(w, h, id);\r\n\t\tthis.bins[id] = bin;\r\n\t\tthis.ref(bin);\r\n\t\treturn bin;\r\n\t}\r\n\r\n\t/**\r\n\t * Return a packed bin given its id, or undefined if the id is not found\r\n\t *\r\n\t * @ param {number|string} id Unique identifier for this bin,\r\n\t * @ returns {Bin} The requested bin, or undefined if not yet packed\r\n\t * @ example\r\n\t * var b = sprite.getBin('a');\r\n\t */\r\n\tgetBin(id) {\r\n\t\treturn this.bins[id];\r\n\t}\r\n\r\n\t/**\r\n\t * Increment the ref count of a bin and update statistics.\r\n\t *\r\n\t * @ param {Bin} bin Bin instance\r\n\t * @ returns {number} New refcount of the bin\r\n\t * @ example\r\n\t * var bin = sprite.getBin('a');\r\n\t * sprite.ref(bin);\r\n\t */\r\n\tref(bin) {\r\n\t\tif (++bin.refcount === 1) {\r\n\t\t\t// a new Bin.. record height in stats historgram..\r\n\t\t\tvar h = bin.h;\r\n\t\t\tthis.stats[h] = (this.stats[h] | 0) + 1;\r\n\t\t}\r\n\r\n\t\treturn bin.refcount;\r\n\t}\r\n\r\n\t/**\r\n\t * Decrement the ref count of a bin and update statistics.\r\n\t * The bin will be automatically marked as free space once the refcount reaches 0.\r\n\t *\r\n\t * @ param {Bin} bin Bin instance\r\n\t * @ returns {number} New refcount of the bin\r\n\t * @ example\r\n\t * var bin = sprite.getBin('a');\r\n\t * sprite.unref(bin);\r\n\t */\r\n\tunref(bin) {\r\n\t\tif (bin.refcount === 0) {\r\n\t\t\treturn 0;\r\n\t\t}\r\n\r\n\t\tif (--bin.refcount === 0) {\r\n\t\t\tthis.stats[bin.h]--;\r\n\t\t\tdelete this.bins[bin.id];\r\n\t\t\tthis.freebins.push(bin);\r\n\t\t}\r\n\r\n\t\treturn bin.refcount;\r\n\t}\r\n\r\n\t/**\r\n\t * Clear the sprite. Resets everything and resets statistics.\r\n\t *\r\n\t * @ example\r\n\t * sprite.clear();\r\n\t */\r\n\tclear() {\r\n\t\tthis.shelves = [];\r\n\t\tthis.freebins = [];\r\n\t\tthis.stats = {};\r\n\t\tthis.bins = {};\r\n\t\tthis.maxId = 0;\r\n\t}\r\n\r\n\t/**\r\n\t * Resize the sprite.\r\n\t *\r\n\t * @ param {number} w Requested new sprite width\r\n\t * @ param {number} h Requested new sprite height\r\n\t * @ returns {boolean} `true` if resize succeeded, `false` if failed\r\n\t * @ example\r\n\t * sprite.resize(256, 256);\r\n\t */\r\n\tresize = function (w, h) {\r\n\t\tthis.w = w;\r\n\t\tthis.h = h;\r\n\t\tfor (var i = 0; i < this.shelves.length; i++) {\r\n\t\t\tthis.shelves[i].resize(w);\r\n\t\t}\r\n\t\treturn true;\r\n\t};\r\n}\r\n\r\n/**\r\n * Create a new Shelf.\r\n *\r\n * @ private\r\n * @ class Shelf\r\n * @ param {number} y Top coordinate of the new shelf\r\n * @ param {number} w Width of the new shelf\r\n * @ param {number} h Height of the new shelf\r\n * @ example\r\n * var shelf = new Shelf(64, 512, 24);\r\n */\r\nclass Shelf {\r\n\tconstructor(y, w, h) {\r\n\t\tthis.x = 0;\r\n\t\tthis.y = y;\r\n\t\tthis.w = this.free = w;\r\n\t\tthis.h = h;\r\n\t}\r\n\r\n\t/**\r\n\t * Allocate a single bin into the shelf.\r\n\t *\r\n\t * @private\r\n\t * @ param {number} w Width of the bin to allocate\r\n\t * @ param {number} h Height of the bin to allocate\r\n\t * @ param {number|string} id Unique id of the bin to allocate\r\n\t * @ returns {Bin} Bin object with `id`, `x`, `y`, `w`, `h` properties, or `null` if allocation failed\r\n\t * @ example\r\n\t * shelf.alloc(12, 16, 'a');\r\n\t */\r\n\talloc(w, h, id) {\r\n\t\tif (w > this.free || h > this.h) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t\tvar x = this.x;\r\n\t\tthis.x += w;\r\n\t\tthis.free -= w;\r\n\t\treturn new Bin(id, x, this.y, w, h);\r\n\t}\r\n\r\n\t/**\r\n\t * Resize the shelf.\r\n\t *\r\n\t * @private\r\n\t * @ param {number} w Requested new width of the shelf\r\n\t * @ returns {boolean} true\r\n\t * @ example\r\n\t * shelf.resize(512);\r\n\t */\r\n\tresize(w) {\r\n\t\tthis.free += w - this.w;\r\n\t\tthis.w = w;\r\n\t\treturn true;\r\n\t}\r\n}\r\n\r\n/**\r\n * Create a new Bin object.\r\n *\r\n * @ class Bin\r\n * @ param {number|string} id Unique id of the bin\r\n * @ param {number} x Left coordinate of the bin\r\n * @ param {number} y Top coordinate of the bin\r\n * @ param {number} w Width of the bin\r\n * @ param {number} h Height of the bin\r\n * @ example\r\n * var bin = new Bin('a', 0, 0, 12, 16);\r\n */\r\nclass Bin {\r\n\tconstructor(id, x, y, w, h) {\r\n\t\tthis.id = id;\r\n\t\tthis.x = x;\r\n\t\tthis.y = y;\r\n\t\tthis.w = w;\r\n\t\tthis.h = h;\r\n\t\tthis.maxw = w;\r\n\t\tthis.maxh = h;\r\n\t\tthis.refcount = 0;\r\n\t}\r\n}\r\n","import ExtrudedPoint from \"./ExtrudedPoint.mjs\";\r\n\r\nimport imagePromise from \"../util/imagePromise.mjs\";\r\nimport imagifyFetchResponse from \"../util/imagifyFetchResponse.mjs\";\r\n\r\nimport AcetateExtrudedPoint from \"../acetates/AcetateExtrudedPoint.mjs\";\r\nimport Acetate from \"../acetates/Acetate.mjs\";\r\n\r\nimport ShelfPack from \"../3rd-party/shelf-pack.mjs\";\r\n\r\nconst percentageRegexp = /(\\d+)%$/;\r\n\r\n/**\r\n * @class AcetateSprite\r\n * @inherits AcetateExtrudedPoint\r\n * @relationship compositionOf ShelfPack, 1..1, 1..1\r\n *\r\n * An `Acetate` that draws square images anchored on points, at a constant screen\r\n * ratio. The images are \"pinned\" or \"anchored\" to a point `Geometry`.\r\n *\r\n * Since this acetate supports `Sprites` with different images, this acetate\r\n * implements a texture atlas. To build it, this leverages\r\n * [Bryan Housel's `shelf-pack` library](https://github.com/mapbox/shelf-pack).\r\n */\r\n\r\n/// TODO: Angle relative to \"up\"?\r\n\r\nclass AcetateSprite extends AcetateExtrudedPoint {\r\n\tconstructor(\r\n\t\ttarget,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @section AcetateSprite Options\r\n\t\t\t * @option interpolate: true\r\n\t\t\t * Whether to use bilinear pixel interpolation or not.\r\n\t\t\t *\r\n\t\t\t * Enabled by default to provide a alightly better display\r\n\t\t\t * of sprites with yaw rotation applied. If (and only if) all\r\n\t\t\t * sprites will have a yaw rotation of zero, it might be a good\r\n\t\t\t * idea to set this to `false` to have pixel-accurate sprites.\r\n\t\t\t */\r\n\t\t\tinterpolate = true,\r\n\t\t\t/**\r\n\t\t\t * @option maxTextureSize: Number = 2048\r\n\t\t\t * Maximum size of the WebGL texture used to hold tiles. Should be\r\n\t\t\t * at least the width/height of the screen. Texture size is ultimately\r\n\t\t\t * bound by the WebGL capabilities of the browser/OS/GPU, which usually\r\n\t\t\t * can support textures 8192 or 16384 pixels wide/high. Using higher\r\n\t\t\t * values might cause the web browser (chromium/chrome in particular)\r\n\t\t\t * to take a longer time during texture initialization.\r\n\t\t\t */\r\n\t\t\tmaxTextureSize = 2048,\r\n\t\t\t...opts\r\n\t\t} = {}\r\n\t) {\r\n\t\tsuper(target, { zIndex: 4000, ...opts });\r\n\r\n\t\t// Two attributes: vertex extrusion amount and texture UV\r\n\t\t// coordinates (relative to the image atlas for the acetate).\r\n\t\t// Vertex extrusion amount is handled by parent class.\r\n\t\tthis._attrs = new this.glii.SingleAttribute({\r\n\t\t\tusage: this.glii.STATIC_DRAW,\r\n\t\t\tsize: 1,\r\n\t\t\tgrowFactor: 1.2,\r\n\r\n\t\t\t// Texture UV coords (relative to acetate image atlas)\r\n\t\t\tglslType: \"vec2\",\r\n\t\t\ttype: Float32Array,\r\n\t\t\tnormalized: false,\r\n\t\t});\r\n\r\n\t\t// Data structures for the texture atlas\r\n\t\tconst texSize = (this._texSize = Math.min(\r\n\t\t\tthis.glii.Texture.getMaxSize(),\r\n\t\t\tmaxTextureSize\r\n\t\t));\r\n\t\tthis._packer = new ShelfPack(texSize, texSize);\r\n\r\n\t\tconst texFilter = !!interpolate\r\n\t\t\t? this.glii.LINEAR\r\n\t\t\t: this.glii.NEAREST_MIPMAP_LINEAR;\r\n\t\tthis._atlas = new this.glii.Texture({\r\n\t\t\tminFilter: texFilter,\r\n\t\t\tmagFilter: texFilter,\r\n\t\t});\r\n\t\tthis._atlas.texArray(texSize, texSize, new Uint8Array(4 * texSize * texSize));\r\n\r\n\t\t// Map of HTMLImageElement/blob to atlas coordinate box\r\n\t\tthis._images = new Map();\r\n\t}\r\n\r\n\tglProgramDefinition() {\r\n\t\tconst opts = super.glProgramDefinition();\r\n\t\treturn {\r\n\t\t\t...opts,\r\n\t\t\tattributes: {\r\n\t\t\t\t...opts.attributes,\r\n\t\t\t\taUV: this._attrs,\r\n\t\t\t},\r\n\t\t\tuniforms: {\r\n\t\t\t\tuPixelSize: \"vec2\",\r\n\t\t\t\t...opts.uniforms,\r\n\t\t\t},\r\n\t\t\ttextures: {\r\n\t\t\t\tuAtlas: this._atlas,\r\n\t\t\t\t...opts.textures,\r\n\t\t\t},\r\n\t\t\tvertexShaderMain: `\r\n\t\t\t\tvUV = aUV;\r\n\t\t\t\tgl_Position = vec4(\r\n\t\t\t\t\tvec3(aCoords, 1.0) * uTransformMatrix +\r\n\t\t\t\t\tvec3(aExtrude * uPixelSize, 0.0)\r\n\t\t\t\t\t, 1.0);\r\n\t\t\t`,\r\n\t\t\tvaryings: { vUV: \"vec2\" },\r\n\t\t\tfragmentShaderMain: `\r\n\t\t\t\tgl_FragColor = texture2D(uAtlas,vUV);\r\n\t\t\t`,\r\n\t\t\t// \t\t\t\tif (gl_FragColor.a < 1.0) {\r\n\t\t\t// \t\t\t\t\tgl_FragColor = vec4(0.,0.,0.,1.);\r\n\t\t\t// \t\t\t\t}\r\n\t\t};\r\n\t}\r\n\r\n\tglIdProgramDefinition() {\r\n\t\tconst opts = super.glIdProgramDefinition();\r\n\t\treturn {\r\n\t\t\t...opts,\r\n\t\t\tfragmentShaderMain: `if (texture2D(uAtlas, vUV).a > 0.0) {\r\n\t\t\t\t${opts.fragmentShaderMain}\r\n\t\t\t} else {\r\n\t\t\t\tdiscard;\r\n\t\t\t}`,\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * @section Internal Methods\r\n\t * @uninheritable\r\n\t * @method pack(image: HTMLImageElement): Bin\r\n\t * Given an image, returns a ShelfPack `Bin`, containing\r\n\t * the box coordinates for that image inside the acetate's texture atlas.\r\n\t *\r\n\t * Will upload the image to the texture atlas if it's not in the atlas already;\r\n\t * returns a cached result if the image has already been packed in the atlas.\r\n\t * Anyway, it will increase the usage counter for the used portion of the atlas.\r\n\t * @alternative\r\n\t * @method pack(image: ImageData): Bin\r\n\t * Idem, but takes a `ImageData` instance (from e.g. `TextLabel` symbol)\r\n\t */\r\n\tpack(image) {\r\n\t\tconst known = this._images.get(image);\r\n\t\tif (known) {\r\n\t\t\tthis._packer.ref(known);\r\n\t\t\treturn known;\r\n\t\t}\r\n\r\n\t\tlet w, h;\r\n\t\tif (image instanceof ImageData) {\r\n\t\t\th = image.height;\r\n\t\t\tw = image.width;\r\n\t\t} else if (image instanceof HTMLImageElement) {\r\n\t\t\th = image.naturalHeight;\r\n\t\t\tw = image.naturalWidth;\r\n\t\t} else {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t\"Cannot pack symbol's image: must be either HTMLImageElement or ImageData\"\r\n\t\t\t);\r\n\t\t}\r\n\t\tconst bin = this._packer.packOne(w, h);\r\n\r\n\t\tif (bin === null) {\r\n\t\t\tthrow new Error(\"Could not allocate sprite image in atlas\");\r\n\t\t}\r\n\r\n\t\tthis._images.set(image, bin);\r\n\r\n\t\tthis._atlas.texSubImage2D(image, bin.x, bin.y);\r\n\r\n\t\treturn bin;\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @method multiAdd(sprites: Array of Sprite): this\r\n\t * Adds the sprites to this acetate (so they're drawn on the next refresh),\r\n\t * using as few WebGL calls as feasible.\r\n\t *\r\n\t * Note this call can be asynchronous - if any of the sprites' images has not loaded\r\n\t * yet, that will delay this whole call until all of the images have loaded.\r\n\t *\r\n\t * TODO: Alternatively, split the sprites: ones with loaded images and one set\r\n\t * per unique image. Load each set as soon as ready.\r\n\t *\r\n\t */\r\n\tmultiAdd(sprites) {\r\n\t\t// Skip already added symbols\r\n\t\tsprites = sprites.filter((s) => !s._inAcetate);\r\n\t\tif (sprites.length === 0) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tsprites.forEach((s) => s.updateRefs(this, undefined, undefined));\r\n\r\n\t\tPromise.all(sprites.map((s) => s.image))\r\n\t\t\t.then((resolved) => resolved.map(imagifyFetchResponse))\r\n\t\t\t.then((loadedImages) => this._syncMultiAdd(sprites, loadedImages))\r\n\t\t\t.catch((err) => {\r\n\t\t\t\t/**\r\n\t\t\t\t * @event spriteerror: Event\r\n\t\t\t\t * Fired when some of the sprites to be added to this acetate have failed\r\n\t\t\t\t * to load their image.\r\n\t\t\t\t */\r\n\t\t\t\tthis.fire(\"spriteerror\", err);\r\n\r\n\t\t\t\tthrow err;\r\n\t\t\t});\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// Must return a plain array with all the StridedTypedArrays that a symbol\r\n\t// might need, as well as any other (pseudo-)constants that the symbol's\r\n\t// `_setGlobalStrides()` method might need.\r\n\t// Can be overwritten by subclasses.\r\n\t_getStridedArrays(maxVtx, maxIdx) {\r\n\t\treturn [\r\n\t\t\t// UV\r\n\t\t\tthis._attrs.asStridedArray(maxVtx),\r\n\t\t\t// Extrusion\r\n\t\t\tthis._extrusions.asStridedArray(maxVtx),\r\n\t\t\t// Index buffer\r\n\t\t\tthis._indices.asTypedArray(maxIdx),\r\n\t\t\t// Texture size (width and height), in texels\r\n\t\t\tthis._texSize,\r\n\t\t];\r\n\t}\r\n\r\n\t_syncMultiAdd(sprites, loadedImages) {\r\n\t\t// Calculate which symbols did not get removed before their image promise resolved\r\n\t\tconst actualSprites = sprites.filter((s) => s._inAcetate === this);\r\n\t\tif (actualSprites.length === 0) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst idxLength = actualSprites.reduce((acc, ext) => acc + ext.idxLength, 0);\r\n\t\tconst attrLength = actualSprites.reduce((acc, ext) => acc + ext.attrLength, 0);\r\n\r\n\t\tlet baseVtx = this._attribAllocator.allocateBlock(attrLength);\r\n\t\tlet baseIdx = this._indices.allocateSlots(idxLength);\r\n\t\tlet vtxAcc = baseVtx;\r\n\t\tlet idxAcc = baseIdx;\r\n\t\tlet maxVtx = baseVtx + attrLength;\r\n\t\tlet stridedArrays = this._getStridedArrays(maxVtx, baseIdx + idxLength);\r\n\r\n\t\tsprites.forEach((s, i) => {\r\n\t\t\t// Skip those symbols that got removed before their image promise resolved\r\n\t\t\t// (skipping the loaded image as well; using the `actualSprites` array\r\n\t\t\t// would use references to stale images corresponding to unloaded sprites)\r\n\t\t\tif (s._inAcetate !== this) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\ts.updateRefs(this, vtxAcc, idxAcc);\r\n\t\t\tthis._knownSymbols[vtxAcc] = s;\r\n\t\t\tvtxAcc += s.attrLength;\r\n\t\t\tidxAcc += s.idxLength;\r\n\r\n\t\t\t// s.bin = this.pack(s.image);\r\n\t\t\tconst img = loadedImages[i];\r\n\t\t\ts.bin = this.pack(img);\r\n\r\n\t\t\ts._normalizeAnchor();\r\n\r\n\t\t\ts._setGlobalStrides(...stridedArrays);\r\n\t\t});\r\n\r\n\t\tthis._commitStridedArrays(baseVtx, attrLength);\r\n\t\tthis._indices.commit(baseIdx, idxLength);\r\n\r\n\t\tif (this._crs) {\r\n\t\t\tthis.reproject(baseVtx, attrLength, actualSprites);\r\n\t\t}\r\n\r\n\t\t// The AcetateInteractive will assign IDs to symbol vertices.\r\n\t\tsuper.multiAddIds(actualSprites, baseVtx);\r\n\r\n\t\t// AcetateExtrudedPoint also implements allocation logic, so its\r\n\t\t// implementation has to be skipped - go directly to base Acetate so\r\n\t\t// it fires the \"symbolsadded\" event.\r\n\t\tAcetate.prototype.multiAdd.call(this, actualSprites);\r\n\r\n\t\tthis.dirty = true;\r\n\t}\r\n\r\n\t// The map will call resize() on acetates when needed - besides redoing the\r\n\t// framebuffer with the new size, this needs to reset the uniform uPixelSize.\r\n\tresize(w, h) {\r\n\t\tsuper.resize(w, h);\r\n\t\tconst dpr2 = (devicePixelRatio ?? 1) * 2;\r\n\t\tthis._programs.setUniform(\"uPixelSize\", [dpr2 / w, dpr2 / h]);\r\n\t}\r\n\r\n\t/**\r\n\t * @method remove(sprite: Sprite): this\r\n\t * Removes the sprite from this acetate (so it's *not* drawn on the next refresh).\r\n\t *\r\n\t * Also decreases the usage count of the relevant portion of the atlas.\r\n\t */\r\n\tremove(sprite) {\r\n\t\tif (sprite.attrBase === undefined) {\r\n\t\t\t// It's possible that the sprite's image is not ready yet\r\n\t\t\t/// TODO: Abort the image request. This means also replacing\r\n\t\t\t/// the `imagePromise` with an `abortableImagePromise`, but what\r\n\t\t\t/// about the image cache?\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\tconst refcount = this._packer.unref(sprite.bin);\r\n\r\n\t\tif (refcount === 0) {\r\n\t\t\tsprite.image.then((img) => this._images.delete(img));\r\n\t\t}\r\n\r\n\t\tsuper.remove(sprite);\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method debugAtlasIntoCanvas(canvas: HTMLCanvasElement): this\r\n\t *\r\n\t * Dumps the contents of the sprite atlas into the given ``.\r\n\t *\r\n\t * This is an expensive operation and is meant only for debugging purposes.\r\n\t */\r\n\tdebugAtlasIntoCanvas(canvas) {\r\n\t\tconst [maxh, maxw] = Object.values(this._packer.bins).reduce(\r\n\t\t\t([h, w], bin) => [Math.max(h, bin.y + bin.h), Math.max(w, bin.x + bin.w)],\r\n\t\t\t[0, 0]\r\n\t\t);\r\n\r\n\t\t// console.log(maxh, maxw, this._packer);\r\n\r\n\t\tif (maxh === 0 && maxw === 0) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst data = this._atlas.asImageData(0, 0, maxw, maxh);\r\n\t\tcanvas.width = maxw;\r\n\t\tcanvas.height = maxh;\r\n\t\tcanvas.getContext(\"2d\").putImageData(data, 0, 0);\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method debugAtlasIntoConsole(): this\r\n\t *\r\n\t * Dumps the contents of the sprite atlas into the developer tools' console,\r\n\t * with some `` and `console.log(\"%c\")` trickery.\r\n\t *\r\n\t * This is an expensive operation and is meant only for debugging purposes.\r\n\t */\r\n\tdebugAtlasIntoConsole() {\r\n\t\tlet canvas = document.createElement(\"canvas\");\r\n\t\tthis.debugAtlasIntoCanvas(canvas);\r\n\r\n\t\t// canvas.toBlob((blob)=>{\r\n\t\t// let url = URL.createObjectURL(blob);\r\n\t\tlet url = canvas.toDataURL();\r\n\r\n\t\tconsole.log(\r\n\t\t\t\"%c+\",\r\n\t\t\t`\r\n\t\tfont-size: 1px;\r\n\t\tpadding: ${Math.floor(canvas.height / 2)}px ${Math.floor(canvas.width / 2)}px;\r\n\t\tline-height: ${canvas.height}px;\r\n\t\tbackground: url(${url});\r\n\t\tbackground-size: ${canvas.width}px ${canvas.height}px;\r\n\t\tcolor: transparent;\r\n\t\t`\r\n\t\t);\r\n\r\n\t\treturn this;\r\n\t}\r\n}\r\n\r\n/**\r\n * @class Sprite\r\n * @inherits ExtrudedPoint\r\n * @relationship drawnOn AcetateSprite\r\n * @relationship compositionOf Bin, 1..1, 0..1\r\n *\r\n * A rectangular image, displayed at a constant screen ratio.\r\n *\r\n * Synonym of \"marker\"s and \"icon\"s.\r\n *\r\n * Works with point geometries only.\r\n *\r\n * @example\r\n * ```js\r\n * new Sprite([0, 0], {\r\n * \timage: \"img/marker.png\",\r\n * \tspriteAnchor: [13, 41]\r\n * }).addTo(map);\r\n * ```\r\n */\r\n\r\nexport default class Sprite extends ExtrudedPoint {\r\n\t/// @section Static properties\r\n\t/// @property Acetate: Prototype of AcetateSprite\r\n\t// The `Acetate` class that draws this symbol.\r\n\tstatic Acetate = AcetateSprite;\r\n\r\n\t#spriteStart;\r\n\t#spriteScale;\r\n\t#yaw;\r\n\t#image;\r\n\r\n\t/**\r\n\t * @constructor Sprite(geom: Geometry, opts?: Sprite Options)\r\n\t * @alternative\r\n\t * @constructor Sprite(geom: Array of Number, opts?: Sprite Options)\r\n\t */\r\n\tconstructor(\r\n\t\tgeom,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @section\r\n\t\t\t * @aka Sprite Options\r\n\t\t\t * @option image: HTMLImageElement\r\n\t\t\t * The image for the sprite. When given an instance of `HTMLImageElement`, the\r\n\t\t\t * image should be completely loaded.\r\n\t\t\t * @alternative\r\n\t\t\t * @option image: Promise to HTMLImageElement\r\n\t\t\t * It might be convenient to instantiate a `Sprite` without knowning if its image\r\n\t\t\t * has been loaded. Display of the sprite will be delayed until this `Promise`\r\n\t\t\t * resolves.\r\n\t\t\t * @alternative\r\n\t\t\t * @option image: String\r\n\t\t\t * For convenience, a `String` containing a URL can be passed.\r\n\t\t\t * @alternative\r\n\t\t\t * @option image: URL\r\n\t\t\t * For convenience, a `URL` instance can be passed.\r\n\t\t\t * @alternative\r\n\t\t\t * @option image: Promise to Response\r\n\t\t\t * For convenience, the result of a `fetch` call can be passed.\r\n\t\t\t */\r\n\t\t\timage,\r\n\r\n\t\t\t/**\r\n\t\t\t * @option spriteAnchor: Array of Number\r\n\t\t\t * The coordinates of the pixel which shall display directly on the\r\n\t\t\t * sprite's geometry (think \"the tip of the pin\").\r\n\t\t\t *\r\n\t\t\t * These coordinates are to be given in `[x, y]` form, in image pixels\r\n\t\t\t * relative to the top-left corner of the sprite image.\r\n\t\t\t *\r\n\t\t\t * Negative values are interpreted as relative to the bottom-right\r\n\t\t\t * corner of the image, instead. For this purpose, `+0` and `-0`\r\n\t\t\t * are handled as different numbers.\r\n\t\t\t * @alternative\r\n\t\t\t * @option spriteAnchor: Array of String = [\"50%\",\"50%\"]\r\n\t\t\t * The sprite anchor can be given as two numeric strings ending\r\n\t\t\t * with a percent sign (e.g. `[\"50%\", \"100%\"]`). Anchor percentages\r\n\t\t\t * are relative to the size of the image.\r\n\t\t\t */\r\n\t\t\tspriteAnchor = [\"50%\", \"50%\"],\r\n\r\n\t\t\t/**\r\n\t\t\t * @option spriteStart: Array of Number = [0, 0]\r\n\t\t\t * When the image for a `Sprite` is a spritesheet, this is the top-left\r\n\t\t\t * offset of the sprite within the spritesheet.\r\n\t\t\t *\r\n\t\t\t * This is to be given in `[x, y]` form, in image pixels. Defaults to `[0, 0]`,\r\n\t\t\t * the top-left corner of the image.\r\n\t\t\t */\r\n\t\t\tspriteStart = [0, 0],\r\n\r\n\t\t\t/**\r\n\t\t\t * @option spriteSize: Array of Number = *\r\n\t\t\t * The size of the sprite, in `[x, y]` form, in image pixels. Defaults to the\r\n\t\t\t * size of the image.\r\n\t\t\t *\r\n\t\t\t * When the image for a `Sprite` is a spritesheet, this should be set to the\r\n\t\t\t * size of the sprite (always smaller than the image itself).\r\n\t\t\t */\r\n\t\t\tspriteSize,\r\n\r\n\t\t\t/**\r\n\t\t\t * @option spriteScale: Number = 1\r\n\t\t\t * Scale factor between image pixels and CSS pixels. Use `0.5`\r\n\t\t\t * (or rather, `1/window.devicePixelRatio`) for \"hi-DPI\" icons,\r\n\t\t\t * or `2` to double the size of the sprite.\r\n\t\t\t */\r\n\t\t\tspriteScale = 1,\r\n\r\n\t\t\t/**\r\n\t\t\t * @option yaw: Number = 0\r\n\t\t\t * Yaw rotation of the sprite, relative to the \"up\" direction of the\r\n\t\t\t * map's `` (**not** relative to the direction of the CRS's `y`\r\n\t\t\t * coordinate or \"northing\"), in clockwise degrees.\r\n\t\t\t */\r\n\t\t\tyaw = 0,\r\n\t\t\t...opts\r\n\t\t} = {}\r\n\t) {\r\n\t\tsuper(geom, opts);\r\n\r\n\t\t/// TODO: Accept HTMLCanvasElement as image\r\n\t\t/// TODO: Accept ImageBitmap as image\r\n\t\t/// See https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texImage2D.html#pixels\r\n\r\n\t\tthis.#image =\r\n\t\t\timage instanceof Promise\r\n\t\t\t\t? image.then(imagifyFetchResponse)\r\n\t\t\t\t: imagePromise(image);\r\n\r\n\t\tthis._anchor = spriteAnchor;\r\n\t\tthis.#spriteStart = spriteStart;\r\n\t\tthis._spriteSize = spriteSize;\r\n\t\tthis.#spriteScale = spriteScale;\r\n\t\tthis.#yaw = yaw;\r\n\r\n\t\t// Sprites are *always* two triangles (6 primitive slots) from 4 vertices\r\n\t\tthis.attrLength = 4;\r\n\t\tthis.idxLength = 6;\r\n\r\n\t\t/**\r\n\t\t * @section Acetate interface\r\n\t\t * @uninheritable\r\n\t\t * @property bin: Bin\r\n\t\t * An array of the form `[x1,y1, x2,y2]`, containing the coordinates\r\n\t\t * of this sprite's image within the acetate's atlas texture.\r\n\t\t * @property image: HTMLImageElement\r\n\t\t * The actual image for this sprite. The acetate will copy it into\r\n\t\t * a texture atlas.\r\n\t\t */\r\n\t\tthis.bin = undefined;\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @property yaw: Number\r\n\t * Runtime value of the `yaw` option: the yaw rotation of the sprite,\r\n\t * in clockwise degrees. Can be updated.\r\n\t */\r\n\tset yaw(yaw) {\r\n\t\tthis.#yaw = yaw;\r\n\t\tthis._refreshExtrusion();\r\n\t}\r\n\tget yaw() {\r\n\t\treturn this.#yaw;\r\n\t}\r\n\r\n\t/**\r\n\t * @property spriteScale: Number\r\n\t * Runtime value of the `spriteScale` option: image pixel to CSS pixel\r\n\t * ratio. Can be updated.\r\n\t */\r\n\tset spriteScale(s) {\r\n\t\tthis.#spriteScale = s;\r\n\t\tthis._refreshExtrusion();\r\n\t}\r\n\tget spriteScale() {\r\n\t\treturn this.#spriteScale;\r\n\t}\r\n\r\n\t/**\r\n\t * @property image: Promise to HTMLImageElement\r\n\t * A `Promise` to the (possibly not loaded yet) image for this sprite.\r\n\t *\r\n\t * Read-only. Use the `replaceImage` method to change the sprite's image\r\n\t * and associated parameters.\r\n\t */\r\n\tget image() {\r\n\t\treturn this.#image;\r\n\t}\r\n\r\n\t/**\r\n\t * @method replaceImage(opts: Sprite Options, retain: Boolean = false): Promise of this\r\n\t *\r\n\t * Replaces the sprite's image and associated parameters (sprite size,\r\n\t * anchor, origin, scale). The first parameter is an object containing\r\n\t * constructor options (such as `image`, `spriteAnchor`, etc).\r\n\t *\r\n\t * By default, the old image gets expelled from the acetate's texture atlas.\r\n\t * In order to prevent that, set `retain` to true. This is useful in\r\n\t * scenarios where replacing the images of several `Sprite`s in the same\r\n\t * render frame causes artifacts such as the wrong image being displayed. Avoid\r\n\t * retaining large images.\r\n\t *\r\n\t * Returns a `Promise` that resolves when the image has been loaded.\r\n\t */\r\n\tasync replaceImage(\r\n\t\t{ image, spriteAnchor, spriteStart, spriteSize, spriteScale, yaw } = {},\r\n\t\tretain = false\r\n\t) {\r\n\t\tlet oldImage = this.#image;\r\n\t\tlet i = (this.#image =\r\n\t\t\timage instanceof Promise\r\n\t\t\t\t? image.then(imagifyFetchResponse)\r\n\t\t\t\t: imagePromise(image));\r\n\r\n\t\tlet [loadedImg, loadedOldImg] = await Promise.all([this.#image, oldImage]);\r\n\r\n\t\tif (i !== this.#image) {\r\n\t\t\t// Image has already been replaced before it could load\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tthis._anchor = spriteAnchor ?? this._anchor;\r\n\t\tthis.#spriteStart = spriteStart ?? this.#spriteStart;\r\n\t\tthis._spriteSize = spriteSize;\r\n\t\tthis.#spriteScale = spriteScale ?? this.#spriteScale;\r\n\t\tthis.#yaw = yaw ?? this.#yaw;\r\n\r\n\t\tconst ac = this._inAcetate;\r\n\t\tif (!ac || this.attrBase === undefined) {\r\n\t\t\t// If the sprite is not in an acetate, **or** the first image hasn't\r\n\t\t\t// been allocated, skip replacement.\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\t// ac.debugAtlasIntoConsole();\r\n\r\n\t\tif (!retain) {\r\n\t\t\t// Manually unpack the old image from the acetate's atlas. It's important\r\n\t\t\t// to **not** call the parent functionality (i.e. do not call the\r\n\t\t\t// remove interactive symbol code)\r\n\t\t\tconst refcount = ac._packer.unref(this.bin);\r\n\t\t\tif (refcount === 0) {\r\n\t\t\t\tac._images.delete(loadedOldImg);\r\n\t\t\t}\r\n\t\t}\r\n\t\tthis.bin = ac.pack(loadedImg);\r\n\t\tthis._normalizeAnchor();\r\n\r\n\t\tlet strides = ac._getStridedArrays(\r\n\t\t\tthis.attrBase + this.attrLength,\r\n\t\t\tthis.idxBase + this.idxLength\r\n\t\t);\r\n\t\tthis._setGlobalStrides(...strides);\r\n\t\tac._commitStridedArrays(this.attrBase, this.attrLength);\r\n\t\tac.dirty = true;\r\n\r\n\t\t// ac.debugAtlasIntoConsole();\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Acetate interface\r\n\t * @uninheritable\r\n\t * @method _setGlobalStrides(strideUV: StridedTypedArray, strideExtrude: StridedTypedArray, texSize: Number): this\r\n\t * Sets the appropriate values in the `StridedTypedArray`s.\r\n\t */\r\n\t_setGlobalStrides(strideUV, strideExtrude, typedIdxs, texSize) {\r\n\t\t// Texture bounds, normalized 0..1 texture position\r\n\t\t// ss = SpriteStart\r\n\t\tconst ssx = this.bin.x + this.#spriteStart[0];\r\n\t\tconst ssy = this.bin.y + this.#spriteStart[1];\r\n\r\n\t\t// se = SpriteEnd\r\n\t\tconst sex = ssx + this._spriteSize[0];\r\n\t\tconst sey = ssy + this._spriteSize[1];\r\n\r\n\t\t// Texture coords are normalized into 0..1, relative to the size\r\n\t\t// of the texture atlas.\r\n\t\tconst tx1 = ssx / texSize;\r\n\t\tconst ty1 = ssy / texSize;\r\n\t\tconst tx2 = sex / texSize;\r\n\t\tconst ty2 = sey / texSize;\r\n\r\n\t\t// `strideUV` comes from a `SingleAttribute`, so values for the four\r\n\t\t// vertices could be concatenated together.\r\n\t\t// prettier-ignore\r\n\t\tstrideUV.set([tx1, ty1], this.attrBase);\r\n\t\tstrideUV.set([tx1, ty2], this.attrBase + 1);\r\n\t\tstrideUV.set([tx2, ty2], this.attrBase + 2);\r\n\t\tstrideUV.set([tx2, ty1], this.attrBase + 3);\r\n\r\n\t\t// prettier-ignore\r\n\t\ttypedIdxs.set([\r\n\t\t\tthis.attrBase, this.attrBase + 1, this.attrBase + 2,\r\n\t\t\tthis.attrBase, this.attrBase + 2, this.attrBase + 3,\r\n\t\t], this.idxBase);\r\n\r\n\t\treturn this._setStridedExtrusion(strideExtrude);\r\n\t}\r\n\r\n\t_setStridedExtrusion(strideExtrude) {\r\n\t\tconst s = this.#spriteScale;\r\n\t\tconst x1 = s * -this._anchor[0];\r\n\t\tconst y1 = s * this._anchor[1];\r\n\t\tconst x2 = x1 + s * this._spriteSize[0];\r\n\t\tconst y2 = y1 - s * this._spriteSize[1];\r\n\t\tconst [offsetX, offsetY] = this.offset;\r\n\t\tlet offsets;\r\n\r\n\t\tif (this._yaw === 0) {\r\n\t\t\t// prettier-ignore\r\n\t\t\toffsets = [\r\n\t\t\t\toffsetX + x1, offsetY + y1,\r\n\t\t\t\toffsetX + x1, offsetY + y2,\r\n\t\t\t\toffsetX + x2, offsetY + y2,\r\n\t\t\t\toffsetX + x2, offsetY + y1,\r\n\t\t\t];\r\n\t\t} else {\r\n\t\t\tconst yawRadians = (-this.#yaw * Math.PI) / 180;\r\n\t\t\tconst s = Math.sin(yawRadians);\r\n\t\t\tconst c = Math.cos(yawRadians);\r\n\r\n\t\t\t// prettier-ignore\r\n\t\t\toffsets = [\r\n\t\t\t\toffsetX + x1*c-y1*s, offsetY + x1*s+y1*c,\r\n\t\t\t\toffsetX + x1*c-y2*s, offsetY + x1*s+y2*c,\r\n\t\t\t\toffsetX + x2*c-y2*s, offsetY + x2*s+y2*c,\r\n\t\t\t\toffsetX + x2*c-y1*s, offsetY + x2*s+y1*c,\r\n\t\t\t];\r\n\t\t}\r\n\t\tstrideExtrude.set(offsets, this.attrBase);\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// Ensures the anchor values are image pixels, in particular when given\r\n\t// as percentage strings.\r\n\t// Also handles default values for the sprite size.\r\n\t_normalizeAnchor() {\r\n\t\tif (!this._spriteSize) {\r\n\t\t\tthis._spriteSize = [this.bin.w, this.bin.h];\r\n\t\t}\r\n\r\n\t\t// Sprite anchor expressed as percentage\r\n\t\tif (typeof this._anchor[0] === \"string\") {\r\n\t\t\tconst anchorXRegexp = this._anchor[0].match(percentageRegexp);\r\n\t\t\tif (anchorXRegexp) {\r\n\t\t\t\tthis._anchor[0] = (this.bin.w * anchorXRegexp[1]) / 100;\r\n\t\t\t} else {\r\n\t\t\t\tthis._anchor[0] = Number(this._anchor[0]);\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (typeof this._anchor[1] === \"string\") {\r\n\t\t\tconst anchorYRegexp = this._anchor[1].match(percentageRegexp);\r\n\t\t\tif (anchorYRegexp) {\r\n\t\t\t\tthis._anchor[1] = (this.bin.h * anchorYRegexp[1]) / 100;\r\n\t\t\t} else {\r\n\t\t\t\tthis._anchor[1] = Number(this._anchor[1]);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Sprite anchor expressed as negative - use bottom-left instead of\r\n\t\t// top-right\r\n\t\t// Note use of `Object.is()` instead of `===` - needed to tell apart\r\n\t\t// `+0` and `-0`.\r\n\t\tif (this._anchor[0] < 0 || Object.is(this._anchor[0], -0)) {\r\n\t\t\tthis._anchor[0] = this.bin.w + this._anchor[0];\r\n\t\t}\r\n\t\tif (this._anchor[1] < 0 || Object.is(this._anchor[1], -0)) {\r\n\t\t\tthis._anchor[1] = this.bin.h + this._anchor[1];\r\n\t\t}\r\n\t}\r\n\r\n\t_refreshExtrusion() {\r\n\t\tif (!this._inAcetate || this.attrBase === undefined) {\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\tlet strideExtrude = this._inAcetate._extrusions.asStridedArray();\r\n\t\tthis._setStridedExtrusion(strideExtrude);\r\n\r\n\t\tthis._inAcetate._extrusions.commit(this.attrBase, this.attrLength);\r\n\t\tthis._inAcetate.dirty = true;\r\n\t\treturn this;\r\n\t}\r\n}\r\n","import Sprite from \"./Sprite.mjs\";\r\nimport parseColour from \"../3rd-party/css-colour-parser.mjs\";\r\n\r\n/**\r\n * @class AcetateTintedSprite\r\n * @inherits AcetateSprite\r\n *\r\n * As `AcetateSprite`, but aditionally the shader applies a tint to the image.\r\n *\r\n * Meant to be used with `TintedSprite` symbols.\r\n *\r\n */\r\nclass AcetateTintedSprite extends Sprite.Acetate {\r\n\tconstructor(target, opts) {\r\n\t\tsuper(target, { zIndex: 4000, ...opts });\r\n\r\n\t\t// this._indices = new glii.WireframeTriangleIndices({ type: glii.UNSIGNED_INT });\r\n\r\n\t\tthis._attrs = new this.glii.InterleavedAttributes(\r\n\t\t\t{\r\n\t\t\t\tusage: this.glii.STATIC_DRAW,\r\n\t\t\t\tsize: 1,\r\n\t\t\t\tgrowFactor: 1.2,\r\n\t\t\t},\r\n\t\t\t[\r\n\t\t\t\t{\r\n\t\t\t\t\t// Texture UV coords (relative to acetate image atlas)\r\n\t\t\t\t\tglslType: \"vec2\",\r\n\t\t\t\t\ttype: Float32Array,\r\n\t\t\t\t\tnormalized: false,\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\t// Tint colour\r\n\t\t\t\t\tglslType: \"vec4\",\r\n\t\t\t\t\ttype: Uint8Array,\r\n\t\t\t\t\tnormalized: true,\r\n\t\t\t\t},\r\n\t\t\t]\r\n\t\t);\r\n\t}\r\n\r\n\t_getStridedArrays(maxVtx, maxIdx) {\r\n\t\treturn [\r\n\t\t\t// Tint\r\n\t\t\tthis._attrs.asStridedArray(1, maxVtx),\r\n\t\t\t// UV\r\n\t\t\tthis._attrs.asStridedArray(0),\r\n\t\t\t// Extrusion\r\n\t\t\tthis._extrusions.asStridedArray(maxVtx),\r\n\t\t\t// Index buffer\r\n\t\t\tthis._indices.asTypedArray(maxIdx),\r\n\t\t\t// Texture size (width and height), in texels\r\n\t\t\tthis._texSize,\r\n\t\t];\r\n\t}\r\n\r\n\tglProgramDefinition() {\r\n\t\tconst opts = super.glProgramDefinition();\r\n\t\treturn {\r\n\t\t\t...opts,\r\n\t\t\tattributes: {\r\n\t\t\t\t...opts.attributes,\r\n\t\t\t\taUV: this._attrs.getBindableAttribute(0),\r\n\t\t\t\taTint: this._attrs.getBindableAttribute(1),\r\n\t\t\t},\r\n\t\t\tvertexShaderMain: `\r\n\t\t\t\tvUV = aUV;\r\n\t\t\t\tvTint = aTint;\r\n\t\t\t\tgl_Position = vec4(\r\n\t\t\t\t\tvec3(aCoords, 1.0) * uTransformMatrix +\r\n\t\t\t\t\tvec3(aExtrude * uPixelSize, 0.0)\r\n\t\t\t\t\t, 1.0);\r\n\t\t\t`,\r\n\t\t\tvaryings: { vUV: \"vec2\", vTint: \"vec4\" },\r\n\t\t\tfragmentShaderMain: `gl_FragColor = texture2D(uAtlas,vUV) * vTint;`,\r\n\t\t};\r\n\t}\r\n}\r\n\r\n/**\r\n * @class TintedSprite\r\n * @inherits Sprite\r\n * @relationship drawnOn AcetateTintedSprite\r\n *\r\n * As `Sprite`, but with a colour tint applied.\r\n *\r\n * @example\r\n * ```js\r\n * new Sprite([0, 0], {\r\n * \timage: \"img/whitemarker.png\",\r\n * \tspriteAnchor: [13, 41]\r\n * \ttint: \"red\",\r\n * }).addTo(map);\r\n * ```\r\n */\r\n\r\n// export default class Sprite extends GleoSymbol {\r\nexport default class TintedSprite extends Sprite {\r\n\t/// @section Static properties\r\n\t/// @property Acetate: Prototype of AcetateTintedSprite\r\n\t// The `Acetate` class that draws this symbol.\r\n\tstatic Acetate = AcetateTintedSprite;\r\n\t#tintColour;\r\n\r\n\t/**\r\n\t * @constructor TintedSprite(geom: Geometry, opts?: TintedSprite Options)\r\n\t * @alternative\r\n\t * @constructor TintedSprite(geom: Array of Number, opts?: TintedSprite Options)\r\n\t */\r\n\tconstructor(\r\n\t\tgeom,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @section\r\n\t\t\t * @aka TintedSprite Options\r\n\t\t\t * @option tint: Colour = [255,255,255,255]\r\n\t\t\t * The tint colour for the sprite. The result colour is\r\n\t\t\t * the [colour multiplication](https://en.wikipedia.org/wiki/Blend_modes#Multiply)\r\n\t\t\t * of the sprite's pixels and the tint.\r\n\t\t\t */\r\n\r\n\t\t\ttint = [255, 255, 255, 255],\r\n\t\t\t...opts\r\n\t\t} = {}\r\n\t) {\r\n\t\tsuper(geom, opts);\r\n\r\n\t\tthis.#tintColour = parseColour(tint);\r\n\t}\r\n\r\n\t_setGlobalStrides(strideTint, ...strides) {\r\n\t\tstrideTint.set(this.#tintColour, this.attrBase + 0);\r\n\t\tstrideTint.set(this.#tintColour, this.attrBase + 1);\r\n\t\tstrideTint.set(this.#tintColour, this.attrBase + 2);\r\n\t\tstrideTint.set(this.#tintColour, this.attrBase + 3);\r\n\t\treturn super._setGlobalStrides(...strides);\r\n\t}\r\n\r\n\t/**\r\n\t * @property tint: Colour\r\n\t * Gets or sets the tint colour for this sprite\r\n\t */\r\n\tget tint() {\r\n\t\treturn this.#tintColour;\r\n\t}\r\n\tset tint(t) {\r\n\t\tthis.#tintColour = parseColour(t);\r\n\r\n\t\tif (!this._inAcetate || this.attrBase === undefined) {\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\tlet strideTint = this._inAcetate._attrs.asStridedArray(1);\r\n\r\n\t\tstrideTint.set(this.#tintColour, this.attrBase + 0);\r\n\t\tstrideTint.set(this.#tintColour, this.attrBase + 1);\r\n\t\tstrideTint.set(this.#tintColour, this.attrBase + 2);\r\n\t\tstrideTint.set(this.#tintColour, this.attrBase + 3);\r\n\t\tthis._inAcetate._attrs.commit(this.attrBase, this.attrLength);\r\n\t\tthis._inAcetate.dirty = true;\r\n\t}\r\n}\r\n","import LeafletGleo from \"./leaflet.gleo/LeafletGleo.js\";\r\n\r\n// import {MultiSymbol} from './gleo/src/symbols/MultiSymbol.mjs';\r\nimport SymbolGroup from \"./gleo/src/loaders/SymbolGroup.mjs\";\r\nimport { setFactory } from \"./gleo/src/geometry/DefaultGeometry.mjs\";\r\nimport LatLng from \"./gleo/src/geometry/LatLng.mjs\";\r\nimport TintedSprite from \"./gleo/src/symbols/TintedSprite.mjs\";\r\n\r\n// Akin to the Leaflet LayerGroups in trkData.mapLayers.normal &\r\n// trkData.mapLayers.sharedView\r\nexport const normalGleoSymbols = new SymbolGroup();\r\nexport const sharedViewGleoSymbols = new SymbolGroup();\r\n\r\n// Spawns a Gleo `platina` inside an instance of a Leaflet map. Stores a reference\r\n// to such platina in the `.platina` property of the map.\r\nexport function initGleo(leafletMap) {\r\n\tconst platina = new LeafletGleo(leafletMap, {\r\n\t\t/* platina options */\r\n\t});\r\n\r\n\tnew TintedSprite.Acetate(platina, {\r\n\t\t// Set a lot of extra tolerance for clicking on sprites.\r\n\t\t// It'd be better to have a lower extra pointer tolerance, but less\r\n\t\t// transparent pixels around the actual asset icons (i.e. clip the\r\n\t\t// 32x32 images).\r\n\t\tpointerTolerance: 16,\r\n\r\n\t\t// Disable texture linear interpolation lookup - the sprites are from small\r\n\t\t// images and benefit from nearest-neighbour texture lookups.\r\n\t\tinterpolate: false\r\n\t});\r\n\r\n\tsetFactory(function latLngize(coords, opts) {\r\n\t\treturn new LatLng(coords, opts);\r\n\t});\r\n\r\n\tleafletMap.platina = platina;\r\n\r\n\tnormalGleoSymbols.addTo(platina);\r\n\r\n\treturn platina;\r\n}\r\n","import trkData from \"./data.js\";\r\nimport { viewModes } from \"./const.js\";\r\nimport { normalGleoSymbols, sharedViewGleoSymbols } from \"./map-gleo.js\";\r\n\r\nimport L from \"leaflet\";\r\n\r\nimport GleoSymbol from \"./gleo/src/symbols/Symbol.mjs\";\r\nimport Loader from \"./gleo/src/loaders/Loader.mjs\";\r\nimport SymbolGroup from \"./gleo/src/loaders/SymbolGroup.mjs\";\r\n\r\n// Adds the given `item` to the map.\r\n// If `layer` is a L.LayerGroup (e.g. `trkData.mapLayers.other`), adds the item\r\n// to that group.\r\n// Possible groups (as defined in trkData.mapLayers) are 'positions', 'fences',\r\n// 'places', 'other', 'waypoints'.\r\n// Otherwise, adds the item to the `normal` L.LayerGroup or to the `sharedView`\r\n// layergroup, depending on the `viewMode` parameter (defaults to `normal` if\r\n// not given).\r\n\r\nexport function addItemToMap(item, layer, viewMode) {\r\n\tif (item === undefined || item === null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tif (item instanceof L.Layer) {\r\n\t\tif (layer === undefined || layer === null) {\r\n\t\t\tconst baseLayer = viewMode === viewModes.SHARED_VIEW ? trkData.mapLayers.sharedView : trkData.mapLayers.normal;\r\n\r\n\t\t\tif (!baseLayer.hasLayer(item)) {\r\n\t\t\t\tbaseLayer.addLayer(item);\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tif (!layer.hasLayer(item)) {\r\n\t\t\t\tlayer.addLayer(item);\r\n\t\t\t}\r\n\t\t}\r\n\t} else if (item instanceof GleoSymbol || item instanceof Loader) {\r\n\t\t// console.log('add gleosymbol', layer);\r\n\r\n\t\tif (layer === undefined || layer === null || !layer instanceof SymbolGroup) {\r\n\t\t\tconst baseLayer = viewMode === viewModes.SHARED_VIEW ? sharedViewGleoSymbols : normalGleoSymbols;\r\n\r\n\t\t\tif (!baseLayer.has(item)) {\r\n\t\t\t\tbaseLayer.add(item);\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tif (!layer.symbols.includes(item)) {\r\n\t\t\t\tlayer.add(item);\r\n\t\t\t}\r\n\t\t}\r\n\t} else {\r\n\t\tthrow new Error(\"Item must be either a Leaflet Layer or a Gleo Symbol\");\r\n\t}\r\n}\r\n\r\n\r\n// As the singular `addItemToMap`, but adds several items at the same time.\r\n// Assumes items in the input array are either all L.Layers or GleoSymbols\r\n// (i.e. not mixed Leaflet layers and Gleo symbols in the same call)\r\nexport function addItemsToMap(items, layer, viewMode) {\r\n\tif (items === undefined || items === null|| items.length == 0) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tif (items[0] instanceof L.Layer) {\r\n\t\titems.forEach(item=> addItemToMap(item));\r\n\t} else if (items[0] instanceof GleoSymbol || items[0] instanceof Loader) {\r\n\t\tif (layer === undefined || layer === null || !layer instanceof SymbolGroup) {\r\n\t\t\tconst baseLayer = viewMode === viewModes.SHARED_VIEW ? sharedViewGleoSymbols : normalGleoSymbols;\r\n\t\t\tbaseLayer.multiAdd(items);\r\n\t\t} else {\r\n\t\t\tlayer.multiAdd(items);\r\n\t\t}\r\n\t} else {\r\n\t\tthrow new Error(\"Item must be either a Leaflet Layer or a Gleo Symbol\");\r\n\t}\r\n}\r\n\r\nexport function removeItemFromMap(item, layer, viewMode) {\r\n\tif (item === undefined || item === null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tif (item instanceof L.Layer) {\r\n\t\tif (layer === undefined || layer === null) {\r\n\t\t\tconst baseLayer = viewMode === viewModes.SHARED_VIEW ? trkData.mapLayers.sharedView : trkData.mapLayers.normal;\r\n\r\n\t\t\tbaseLayer.removeLayer(item);\r\n\t\t} else {\r\n\t\t\tlayer.removeLayer(item);\r\n\t\t}\r\n\t} else if (item instanceof GleoSymbol || item instanceof Loader) {\r\n\t\t// console.log('remove gleosymbol', layer);\r\n\r\n\t\tif (layer === undefined || layer === null || !layer instanceof SymbolGroup) {\r\n\t\t\tconst baseLayer = viewMode === viewModes.SHARED_VIEW ? sharedViewGleoSymbols : normalGleoSymbols;\r\n\r\n\t\t\tif (baseLayer.has(item)) {\r\n\t\t\t\tbaseLayer.remove(item);\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tif (layer.has(item)) {\r\n\t\t\t\tlayer.remove(item);\r\n\t\t\t}\r\n\t\t}\r\n\t} else {\r\n\t\tthrow new Error(\"Item must be either a Leaflet Layer or a Gleo Symbol\");\r\n\t}\r\n}\r\n","import options from \"./options.js\";\r\nimport { findDeviceById } from \"./devices.js\";\r\nimport { devices } from \"./devices.js\";\r\nimport trkData from \"./data.js\";\r\nimport { mapModes, viewModes } from \"./const.js\";\r\nimport { isSendCommandDisabledForDevice } from \"./devices.js\";\r\nimport state from \"./state.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport _ from \"lodash\";\r\n\r\nexport function isSendCommandEnabledForAsset(asset) {\r\n\tif (options.enabledFeatures.indexOf(\"REMOTE_MANAGEMENT\") === -1) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\tvar device = findDeviceById(asset.DeviceId);\r\n\treturn !isSendCommandDisabledForDevice(device);\r\n}\r\n\r\nexport function isWaypointEnabledForAsset(asset) {\r\n\tif (options.enabledFeatures.indexOf(\"WAYPOINTS\") === -1) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\tvar device = findDeviceById(asset.DeviceId);\r\n\tif (!device.SupportsPositionUpdates) return false;\r\n\tif (\r\n\t\t$.inArray(asset.DeviceId, devices.SKYWAVE_IDP_DUAL_MODE) != -1 ||\r\n\t\t$.inArray(asset.DeviceId, devices.SKYWAVE_IDP) != -1\r\n\t) {\r\n\t\t// IDP devices must have the AVL service enabled\r\n\t\t//if (asset.IDP.AVL !== true)\r\n\t\t// return false;\r\n\t\tif (asset.IDP.Garmin !== true) return false;\r\n\t}\r\n\treturn true;\r\n}\r\n\r\nexport function isMessagingEnabledForAssetGroup(group) {\r\n\tif (options.enabledFeatures.indexOf(\"TWO_WAY_MESSAGING\") === -1) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\tif (group === undefined || group === null) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\tvar groupAssetIds = findAssetIdsUnderGroup(group);\r\n\tvar hasAssetWithMessagingEnabled = false;\r\n\t_.each(groupAssetIds, function (assetId) {\r\n\t\tif (trkData.assetsById[assetId] !== undefined) {\r\n\t\t\tif (isMessagingEnabledForAsset(trkData.assetsById[assetId])) {\r\n\t\t\t\thasAssetWithMessagingEnabled = true;\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n\treturn hasAssetWithMessagingEnabled;\r\n}\r\n\r\nexport function isMessagingEnabledForAsset(asset) {\r\n\tif (options.enabledFeatures.indexOf(\"TWO_WAY_MESSAGING\") === -1) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\tif (asset == null) return false;\r\n\r\n\tvar device = findDeviceById(asset.DeviceId);\r\n\tif (!device.SupportsMessaging) return false;\r\n\r\n\tif (asset.DeviceId === devices.BIVY_STICK) {\r\n\t\t// Bivy Stick must be registered with Messaging enabled\r\n\t\tif (asset.Bivy === null || asset.Bivy.Messages !== true || asset.Bivy.Registered !== true) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\tif (\r\n\t\t$.inArray(asset.DeviceId, devices.SKYWAVE_IDP_DUAL_MODE) != -1 ||\r\n\t\t$.inArray(asset.DeviceId, devices.SKYWAVE_IDP) != -1\r\n\t) {\r\n\t\t//// IDP devices must have the AVL and Garmin services enabled\r\n\t\t//if (asset.IDP.AVL !== true)\r\n\t\t// return false;\r\n\t\t// IDP devices technically only need Garmin enabled\r\n\t\tif (asset.IDP.Garmin !== true) return false;\r\n\t}\r\n\treturn true;\r\n}\r\n\r\nexport function isOutputEnabledForAsset(asset) {\r\n\tif (options.enabledFeatures.indexOf(\"REMOTE_MANAGEMENT\") === -1) {\r\n\t\treturn false;\r\n\t}\r\n\tvar device = findDeviceById(asset.DeviceId);\r\n\tif (device.OutputPinCount == 0) return false;\r\n\tif (\r\n\t\t$.inArray(asset.DeviceId, devices.SKYWAVE_IDP_DUAL_MODE) != -1 ||\r\n\t\t$.inArray(asset.DeviceId, devices.SKYWAVE_IDP) != -1\r\n\t) {\r\n\t\t// IDP devices must have the AVL or ARC service enabled\r\n\t\tif (asset.IDP.AVL !== true && asset.IDP.ARC !== true) return false;\r\n\t}\r\n\treturn true;\r\n}\r\n\r\nexport function isGarminFormsEnabledForAsset(asset) {\r\n\tif (options.enabledFeatures.indexOf(\"GARMIN_INTEGRATION\") === -1) {\r\n\t\treturn false;\r\n\t}\r\n\treturn trkData.devicesById[asset.DeviceId].SupportsGarminForms;\r\n}\r\n\r\nexport function isServiceMeterEnabledForAsset(asset) {\r\n\tif (options.enabledFeatures.indexOf(\"LOGS_SERVICE_METER\") === -1) {\r\n\t\treturn false;\r\n\t}\r\n\tif (\r\n\t\t$.inArray(asset.DeviceId, devices.SKYWAVE_IDP_DUAL_MODE) != -1 ||\r\n\t\t$.inArray(asset.DeviceId, devices.SKYWAVE_IDP) != -1\r\n\t) {\r\n\t\t// IDP devices must have the AVL service enabled\r\n\t\tif (asset.IDP.AVL !== true) return false;\r\n\t} else {\r\n\t\treturn false;\r\n\t}\r\n\treturn true;\r\n}\r\n\r\nexport function getCurrentMarkerForAsset(asset, mode) {\r\n\tif (mode === undefined) {\r\n\t\tmode = state.activeMapMode;\r\n\t}\r\n\tif (mode === mapModes.LIVE) {\r\n\t\tvar latestMarkers = _.filter(trkData.live.markersByAssetId[asset.Id], function (item) {\r\n\t\t\treturn !item.data.location.IsHidden;\r\n\t\t});\r\n\t\tif (latestMarkers !== undefined && latestMarkers.length > 0) {\r\n\t\t\treturn latestMarkers[latestMarkers.length - 1];\r\n\t\t}\r\n\t} else if (mode === mapModes.HISTORY) {\r\n\t\tvar latestHistory = trkData.history.markersByAssetId[asset.Id];\r\n\t\tif (latestHistory !== undefined && latestHistory.length > 0) {\r\n\t\t\treturn latestHistory[0];\r\n\t\t}\r\n\t}\r\n\treturn undefined;\r\n}\r\n\r\nfunction getAssetIdsFromAssets(assets) {\r\n\tvar assetIds = [];\r\n\tfor (var i = 0, len = assets.length; i < len; i++) {\r\n\t\tassetIds.push(assets[i].Id);\r\n\t}\r\n\treturn assetIds;\r\n}\r\n\r\nfunction findAssetsUnderGroup(group) {\r\n\tvar groupAssets = [];\r\n\tif (group.Id === \"all-assets\") {\r\n\t\treturn trkData.assets;\r\n\t}\r\n\t_.each(trkData.assets, function (asset, index, list) {\r\n\t\tif (_.indexOf(asset.GroupIds, group.Id) !== -1) {\r\n\t\t\tgroupAssets.push(asset);\r\n\t\t}\r\n\t});\r\n\r\n\tvar subGroups = _.filter(trkData.groups, { ParentGroupId: group.Id });\r\n\t_.each(subGroups, function (subGroup, index, list) {\r\n\t\t_.each(findAssetsUnderGroup(subGroup), function (asset) {\r\n\t\t\tgroupAssets.push(asset);\r\n\t\t});\r\n\t});\r\n\r\n\treturn _.uniq(groupAssets);\r\n}\r\n\r\nexport function findAssetIdsUnderGroup(group) {\r\n\treturn getAssetIdsFromAssets(findAssetsUnderGroup(group));\r\n}\r\n\r\nexport function findAssetByUniqueId(uniqueId) {\r\n\tvar item = _.find(trkData.assets, { UniqueId: uniqueId });\r\n\treturn item === undefined ? null : item;\r\n}\r\n\r\nexport function findAssetById(id) {\r\n\tvar item = trkData.assetsById[parseInt(id)];\r\n\treturn item === undefined ? null : item;\r\n}\r\n\r\nexport function normalizeAssetData(assetId, type, item, parent) {\r\n\t// TODO normalize upon parsing of the data originally\r\n\t// and pass/store this version OR have the DTO match this better\r\n\tvar normalized = {\r\n\t\tAssetId: assetId,\r\n\t\tEpoch: null,\r\n\t};\r\n\tswitch (type) {\r\n\t\tcase \"position\":\r\n\t\t\tnormalized.Position = item;\r\n\t\t\tbreak;\r\n\t\tcase \"event\":\r\n\t\t\tnormalized.Event = item;\r\n\t\t\tbreak;\r\n\t\t//case 'chat':\r\n\t\t// normalized.Chat = item;\r\n\t\t// break;\r\n\t\t//case 'message':\r\n\t\t// normalized.Message = item;\r\n\t\t// break;\r\n\t\tcase \"message\":\r\n\t\t\tif (trkData.MESSAGES_TEXT.indexOf(item.TypeId) !== -1) {\r\n\t\t\t\tnormalized.Chat = item;\r\n\t\t\t} else {\r\n\t\t\t\tnormalized.Message = item;\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t}\r\n\r\n\tif (item.Epoch !== undefined) {\r\n\t\tnormalized.Epoch = item.Epoch;\r\n\t} else if (item.CreatedEpoch !== undefined) {\r\n\t\tnormalized.Epoch = item.CreatedEpoch;\r\n\t}\r\n\r\n\tvar dataSource = trkData;\r\n\tif (state.activeViewMode === viewModes.SHARED_VIEW) {\r\n\t\tdataSource = trkData.sharedView;\r\n\t}\r\n\r\n\tif (item.Position !== undefined && item.Position !== null) {\r\n\t\tif (dataSource.positionsById[item.Position.Id] === undefined) {\r\n\t\t\tdataSource.positionsById[item.Position.Id] = normalizeAssetData(assetId, \"position\", item.Position);\r\n\t\t}\r\n\t\tnormalized.Position = dataSource.positionsById[item.Position.Id].Position;\r\n\t} else if (item.PositionId !== undefined && item.PositionId !== null) {\r\n\t\tif (dataSource.positionsById[item.PositionId] !== undefined) {\r\n\t\t\tnormalized.Position = dataSource.positionsById[item.PositionId].Position;\r\n\t\t} else if (parent !== undefined && parent.Position !== undefined) {\r\n\t\t\tnormalized.Position = parent.Position;\r\n\t\t}\r\n\t}\r\n\r\n\tif (item.Events !== undefined && item.Events !== null) {\r\n\t\t_.each(item.Events, function (itemEvent) {\r\n\t\t\tif (dataSource.eventsById[itemEvent.Id] === undefined) {\r\n\t\t\t\tdataSource.eventsById[itemEvent.Id] = normalizeAssetData(assetId, \"event\", itemEvent, normalized);\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n\treturn normalized;\r\n}\r\n","import user from \"./user.js\";\r\n\r\nexport function wrapUrl(url) {\r\n\tif (!user.isImpersonated) {\r\n\t\treturn url;\r\n\t}\r\n\tif (url.indexOf(\"?\") === -1) {\r\n\t\turl += \"?\";\r\n\t} else {\r\n\t\turl += \"&\";\r\n\t}\r\n\turl += \"ishr=\" + encodeURIComponent(user.id);\r\n\treturn url;\r\n}\r\n","'use strict';\n\nvar alphabet;\nvar alphabetIndexMap;\nvar alphabetIndexMapLength = 0;\n\nfunction isNumberCode(code) {\n return code >= 48 && code <= 57;\n}\n\nfunction naturalCompare(a, b) {\n var lengthA = (a += '').length;\n var lengthB = (b += '').length;\n var aIndex = 0;\n var bIndex = 0;\n\n while (aIndex < lengthA && bIndex < lengthB) {\n var charCodeA = a.charCodeAt(aIndex);\n var charCodeB = b.charCodeAt(bIndex);\n\n if (isNumberCode(charCodeA)) {\n if (!isNumberCode(charCodeB)) {\n return charCodeA - charCodeB;\n }\n\n var numStartA = aIndex;\n var numStartB = bIndex;\n\n while (charCodeA === 48 && ++numStartA < lengthA) {\n charCodeA = a.charCodeAt(numStartA);\n }\n while (charCodeB === 48 && ++numStartB < lengthB) {\n charCodeB = b.charCodeAt(numStartB);\n }\n\n var numEndA = numStartA;\n var numEndB = numStartB;\n\n while (numEndA < lengthA && isNumberCode(a.charCodeAt(numEndA))) {\n ++numEndA;\n }\n while (numEndB < lengthB && isNumberCode(b.charCodeAt(numEndB))) {\n ++numEndB;\n }\n\n var difference = numEndA - numStartA - numEndB + numStartB; // numA length - numB length\n if (difference) {\n return difference;\n }\n\n while (numStartA < numEndA) {\n difference = a.charCodeAt(numStartA++) - b.charCodeAt(numStartB++);\n if (difference) {\n return difference;\n }\n }\n\n aIndex = numEndA;\n bIndex = numEndB;\n continue;\n }\n\n if (charCodeA !== charCodeB) {\n if (\n charCodeA < alphabetIndexMapLength &&\n charCodeB < alphabetIndexMapLength &&\n alphabetIndexMap[charCodeA] !== -1 &&\n alphabetIndexMap[charCodeB] !== -1\n ) {\n return alphabetIndexMap[charCodeA] - alphabetIndexMap[charCodeB];\n }\n\n return charCodeA - charCodeB;\n }\n\n ++aIndex;\n ++bIndex;\n }\n\n if (aIndex >= lengthA && bIndex < lengthB && lengthA >= lengthB) {\n return -1;\n }\n\n if (bIndex >= lengthB && aIndex < lengthA && lengthB >= lengthA) {\n return 1;\n }\n\n return lengthA - lengthB;\n}\n\nnaturalCompare.caseInsensitive = naturalCompare.i = function(a, b) {\n return naturalCompare(('' + a).toLowerCase(), ('' + b).toLowerCase());\n};\n\nObject.defineProperties(naturalCompare, {\n alphabet: {\n get: function() {\n return alphabet;\n },\n\n set: function(value) {\n alphabet = value;\n alphabetIndexMap = [];\n\n var i = 0;\n\n if (alphabet) {\n for (; i < alphabet.length; i++) {\n alphabetIndexMap[alphabet.charCodeAt(i)] = i;\n }\n }\n\n alphabetIndexMapLength = alphabetIndexMap.length;\n\n for (i = 0; i < alphabetIndexMapLength; i++) {\n if (alphabetIndexMap[i] === undefined) {\n alphabetIndexMap[i] = -1;\n }\n }\n },\n },\n});\n\nmodule.exports = naturalCompare;\n","/**\n * A cross-browser implementation of getElementsByClass.\n * Heavily based on Dustin Diaz's function: http://dustindiaz.com/getelementsbyclass.\n *\n * Find all elements with class `className` inside `container`.\n * Use `single = true` to increase performance in older browsers\n * when only one element is needed.\n *\n * @param {String} className\n * @param {Element} container\n * @param {Boolean} single\n * @api public\n */\n\nvar getElementsByClassName = function(container, className, single) {\n if (single) {\n return container.getElementsByClassName(className)[0];\n } else {\n return container.getElementsByClassName(className);\n }\n};\n\nvar querySelector = function(container, className, single) {\n className = '.' + className;\n if (single) {\n return container.querySelector(className);\n } else {\n return container.querySelectorAll(className);\n }\n};\n\nvar polyfill = function(container, className, single) {\n var classElements = [],\n tag = '*';\n\n var els = container.getElementsByTagName(tag);\n var elsLen = els.length;\n var pattern = new RegExp(\"(^|\\\\s)\"+className+\"(\\\\s|$)\");\n for (var i = 0, j = 0; i < elsLen; i++) {\n if ( pattern.test(els[i].className) ) {\n if (single) {\n return els[i];\n } else {\n classElements[j] = els[i];\n j++;\n }\n }\n }\n return classElements;\n};\n\nmodule.exports = (function() {\n return function(container, className, single, options) {\n options = options || {};\n if ((options.test && options.getElementsByClassName) || (!options.test && document.getElementsByClassName)) {\n return getElementsByClassName(container, className, single);\n } else if ((options.test && options.querySelector) || (!options.test && document.querySelector)) {\n return querySelector(container, className, single);\n } else {\n return polyfill(container, className, single);\n }\n };\n})();\n","/*\n * Source: https://github.com/segmentio/extend\n */\n\nmodule.exports = function extend (object) {\n // Takes an unlimited number of extenders.\n var args = Array.prototype.slice.call(arguments, 1);\n\n // For each extender, copy their properties on our object.\n for (var i = 0, source; source = args[i]; i++) {\n if (!source) continue;\n for (var property in source) {\n object[property] = source[property];\n }\n }\n\n return object;\n};\n","var indexOf = [].indexOf;\n\nmodule.exports = function(arr, obj){\n if (indexOf) return arr.indexOf(obj);\n for (var i = 0; i < arr.length; ++i) {\n if (arr[i] === obj) return i;\n }\n return -1;\n};\n","/**\n * Source: https://github.com/timoxley/to-array\n *\n * Convert an array-like object into an `Array`.\n * If `collection` is already an `Array`, then will return a clone of `collection`.\n *\n * @param {Array | Mixed} collection An `Array` or array-like object to convert e.g. `arguments` or `NodeList`\n * @return {Array} Naive conversion of `collection` to a new `Array`.\n * @api public\n */\n\nmodule.exports = function toArray(collection) {\n if (typeof collection === 'undefined') return [];\n if (collection === null) return [null];\n if (collection === window) return [window];\n if (typeof collection === 'string') return [collection];\n if (isArray(collection)) return collection;\n if (typeof collection.length != 'number') return [collection];\n if (typeof collection === 'function' && collection instanceof Function) return [collection];\n\n var arr = [];\n for (var i = 0; i < collection.length; i++) {\n if (Object.prototype.hasOwnProperty.call(collection, i) || i in collection) {\n arr.push(collection[i]);\n }\n }\n if (!arr.length) return [];\n return arr;\n};\n\nfunction isArray(arr) {\n return Object.prototype.toString.call(arr) === \"[object Array]\";\n}\n","var bind = window.addEventListener ? 'addEventListener' : 'attachEvent',\n unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent',\n prefix = bind !== 'addEventListener' ? 'on' : '',\n toArray = require('./to-array');\n\n/**\n * Bind `el` event `type` to `fn`.\n *\n * @param {Element} el, NodeList, HTMLCollection or Array\n * @param {String} type\n * @param {Function} fn\n * @param {Boolean} capture\n * @api public\n */\n\nexports.bind = function(el, type, fn, capture){\n el = toArray(el);\n for ( var i = 0; i < el.length; i++ ) {\n el[i][bind](prefix + type, fn, capture || false);\n }\n};\n\n/**\n * Unbind `el` event `type`'s callback `fn`.\n *\n * @param {Element} el, NodeList, HTMLCollection or Array\n * @param {String} type\n * @param {Function} fn\n * @param {Boolean} capture\n * @api public\n */\n\nexports.unbind = function(el, type, fn, capture){\n el = toArray(el);\n for ( var i = 0; i < el.length; i++ ) {\n el[i][unbind](prefix + type, fn, capture || false);\n }\n};\n","module.exports = function(s) {\n s = (s === undefined) ? \"\" : s;\n s = (s === null) ? \"\" : s;\n s = s.toString();\n return s;\n};\n","/**\n * Module dependencies.\n */\n\nvar index = require('./index-of');\n\n/**\n * Whitespace regexp.\n */\n\nvar re = /\\s+/;\n\n/**\n * toString reference.\n */\n\nvar toString = Object.prototype.toString;\n\n/**\n * Wrap `el` in a `ClassList`.\n *\n * @param {Element} el\n * @return {ClassList}\n * @api public\n */\n\nmodule.exports = function(el){\n return new ClassList(el);\n};\n\n/**\n * Initialize a new ClassList for `el`.\n *\n * @param {Element} el\n * @api private\n */\n\nfunction ClassList(el) {\n if (!el || !el.nodeType) {\n throw new Error('A DOM element reference is required');\n }\n this.el = el;\n this.list = el.classList;\n}\n\n/**\n * Add class `name` if not already present.\n *\n * @param {String} name\n * @return {ClassList}\n * @api public\n */\n\nClassList.prototype.add = function(name){\n // classList\n if (this.list) {\n this.list.add(name);\n return this;\n }\n\n // fallback\n var arr = this.array();\n var i = index(arr, name);\n if (!~i) arr.push(name);\n this.el.className = arr.join(' ');\n return this;\n};\n\n/**\n * Remove class `name` when present, or\n * pass a regular expression to remove\n * any which match.\n *\n * @param {String|RegExp} name\n * @return {ClassList}\n * @api public\n */\n\nClassList.prototype.remove = function(name){\n // classList\n if (this.list) {\n this.list.remove(name);\n return this;\n }\n\n // fallback\n var arr = this.array();\n var i = index(arr, name);\n if (~i) arr.splice(i, 1);\n this.el.className = arr.join(' ');\n return this;\n};\n\n\n/**\n * Toggle class `name`, can force state via `force`.\n *\n * For browsers that support classList, but do not support `force` yet,\n * the mistake will be detected and corrected.\n *\n * @param {String} name\n * @param {Boolean} force\n * @return {ClassList}\n * @api public\n */\n\nClassList.prototype.toggle = function(name, force){\n // classList\n if (this.list) {\n if (\"undefined\" !== typeof force) {\n if (force !== this.list.toggle(name, force)) {\n this.list.toggle(name); // toggle again to correct\n }\n } else {\n this.list.toggle(name);\n }\n return this;\n }\n\n // fallback\n if (\"undefined\" !== typeof force) {\n if (!force) {\n this.remove(name);\n } else {\n this.add(name);\n }\n } else {\n if (this.has(name)) {\n this.remove(name);\n } else {\n this.add(name);\n }\n }\n\n return this;\n};\n\n/**\n * Return an array of classes.\n *\n * @return {Array}\n * @api public\n */\n\nClassList.prototype.array = function(){\n var className = this.el.getAttribute('class') || '';\n var str = className.replace(/^\\s+|\\s+$/g, '');\n var arr = str.split(re);\n if ('' === arr[0]) arr.shift();\n return arr;\n};\n\n/**\n * Check if class `name` is present.\n *\n * @param {String} name\n * @return {ClassList}\n * @api public\n */\n\nClassList.prototype.has =\nClassList.prototype.contains = function(name){\n return this.list ? this.list.contains(name) : !! ~index(this.array(), name);\n};\n","/**\n * A cross-browser implementation of getAttribute.\n * Source found here: http://stackoverflow.com/a/3755343/361337 written by Vivin Paliath\n *\n * Return the value for `attr` at `element`.\n *\n * @param {Element} el\n * @param {String} attr\n * @api public\n */\n\nmodule.exports = function(el, attr) {\n var result = (el.getAttribute && el.getAttribute(attr)) || null;\n if( !result ) {\n var attrs = el.attributes;\n var length = attrs.length;\n for(var i = 0; i < length; i++) {\n if (attr[i] !== undefined) {\n if(attr[i].nodeName === attr) {\n result = attr[i].nodeValue;\n }\n }\n }\n }\n return result;\n};\n","module.exports = function(list) {\n return function(initValues, element, notCreate) {\n var item = this;\n\n this._values = {};\n\n this.found = false; // Show if list.searched == true and this.found == true\n this.filtered = false;// Show if list.filtered == true and this.filtered == true\n\n var init = function(initValues, element, notCreate) {\n if (element === undefined) {\n if (notCreate) {\n item.values(initValues, notCreate);\n } else {\n item.values(initValues);\n }\n } else {\n item.elm = element;\n var values = list.templater.get(item, initValues);\n item.values(values);\n }\n };\n\n this.values = function(newValues, notCreate) {\n if (newValues !== undefined) {\n for(var name in newValues) {\n item._values[name] = newValues[name];\n }\n if (notCreate !== true) {\n list.templater.set(item, item.values());\n }\n } else {\n return item._values;\n }\n };\n\n this.show = function() {\n list.templater.show(item);\n };\n\n this.hide = function() {\n list.templater.hide(item);\n };\n\n this.matching = function() {\n return (\n (list.filtered && list.searched && item.found && item.filtered) ||\n (list.filtered && !list.searched && item.filtered) ||\n (!list.filtered && list.searched && item.found) ||\n (!list.filtered && !list.searched)\n );\n };\n\n this.visible = function() {\n return (item.elm && (item.elm.parentNode == list.list)) ? true : false;\n };\n\n init(initValues, element, notCreate);\n };\n};\n","module.exports = function(list) {\n var addAsync = function(values, callback, items) {\n var valuesToAdd = values.splice(0, 50);\n items = items || [];\n items = items.concat(list.add(valuesToAdd));\n if (values.length > 0) {\n setTimeout(function() {\n addAsync(values, callback, items);\n }, 1);\n } else {\n list.update();\n callback(items);\n }\n };\n return addAsync;\n};\n","var classes = require('./utils/classes'),\n events = require('./utils/events'),\n List = require('./index');\n\nmodule.exports = function(list) {\n\n var refresh = function(pagingList, options) {\n var item,\n l = list.matchingItems.length,\n index = list.i,\n page = list.page,\n pages = Math.ceil(l / page),\n currentPage = Math.ceil((index / page)),\n innerWindow = options.innerWindow || 2,\n left = options.left || options.outerWindow || 0,\n right = options.right || options.outerWindow || 0;\n\n right = pages - right;\n\n pagingList.clear();\n for (var i = 1; i <= pages; i++) {\n var className = (currentPage === i) ? \"active\" : \"\";\n\n //console.log(i, left, right, currentPage, (currentPage - innerWindow), (currentPage + innerWindow), className);\n\n if (is.number(i, left, right, currentPage, innerWindow)) {\n item = pagingList.add({\n page: i,\n dotted: false\n })[0];\n if (className) {\n classes(item.elm).add(className);\n }\n addEvent(item.elm, i, page);\n } else if (is.dotted(pagingList, i, left, right, currentPage, innerWindow, pagingList.size())) {\n item = pagingList.add({\n page: \"...\",\n dotted: true\n })[0];\n classes(item.elm).add(\"disabled\");\n }\n }\n };\n\n var is = {\n number: function(i, left, right, currentPage, innerWindow) {\n return this.left(i, left) || this.right(i, right) || this.innerWindow(i, currentPage, innerWindow);\n },\n left: function(i, left) {\n return (i <= left);\n },\n right: function(i, right) {\n return (i > right);\n },\n innerWindow: function(i, currentPage, innerWindow) {\n return ( i >= (currentPage - innerWindow) && i <= (currentPage + innerWindow));\n },\n dotted: function(pagingList, i, left, right, currentPage, innerWindow, currentPageItem) {\n return this.dottedLeft(pagingList, i, left, right, currentPage, innerWindow) || (this.dottedRight(pagingList, i, left, right, currentPage, innerWindow, currentPageItem));\n },\n dottedLeft: function(pagingList, i, left, right, currentPage, innerWindow) {\n return ((i == (left + 1)) && !this.innerWindow(i, currentPage, innerWindow) && !this.right(i, right));\n },\n dottedRight: function(pagingList, i, left, right, currentPage, innerWindow, currentPageItem) {\n if (pagingList.items[currentPageItem-1].values().dotted) {\n return false;\n } else {\n return ((i == (right)) && !this.innerWindow(i, currentPage, innerWindow) && !this.right(i, right));\n }\n }\n };\n\n var addEvent = function(elm, i, page) {\n events.bind(elm, 'click', function() {\n list.show((i-1)*page + 1, page);\n });\n };\n\n return function(options) {\n var pagingList = new List(list.listContainer.id, {\n listClass: options.paginationClass || 'pagination',\n item: \"
  • \",\n valueNames: ['page', 'dotted'],\n searchClass: 'pagination-search-that-is-not-supposed-to-exist',\n sortClass: 'pagination-sort-that-is-not-supposed-to-exist'\n });\n\n list.on('updated', function() {\n refresh(pagingList, options);\n });\n refresh(pagingList, options);\n };\n};\n","module.exports = function(list) {\n\n var Item = require('./item')(list);\n\n var getChildren = function(parent) {\n var nodes = parent.childNodes,\n items = [];\n for (var i = 0, il = nodes.length; i < il; i++) {\n // Only textnodes have a data attribute\n if (nodes[i].data === undefined) {\n items.push(nodes[i]);\n }\n }\n return items;\n };\n\n var parse = function(itemElements, valueNames) {\n for (var i = 0, il = itemElements.length; i < il; i++) {\n list.items.push(new Item(valueNames, itemElements[i]));\n }\n };\n var parseAsync = function(itemElements, valueNames) {\n var itemsToIndex = itemElements.splice(0, 50); // TODO: If < 100 items, what happens in IE etc?\n parse(itemsToIndex, valueNames);\n if (itemElements.length > 0) {\n setTimeout(function() {\n parseAsync(itemElements, valueNames);\n }, 1);\n } else {\n list.update();\n list.trigger('parseComplete');\n }\n };\n\n list.handlers.parseComplete = list.handlers.parseComplete || [];\n\n return function() {\n var itemsToIndex = getChildren(list.list),\n valueNames = list.valueNames;\n\n if (list.indexAsync) {\n parseAsync(itemsToIndex, valueNames);\n } else {\n parse(itemsToIndex, valueNames);\n }\n };\n};\n","var Templater = function(list) {\n var itemSource,\n templater = this;\n\n var init = function() {\n itemSource = templater.getItemSource(list.item);\n if (itemSource) {\n itemSource = templater.clearSourceItem(itemSource, list.valueNames);\n }\n };\n\n this.clearSourceItem = function(el, valueNames) {\n for(var i = 0, il = valueNames.length; i < il; i++) {\n var elm;\n if (valueNames[i].data) {\n for (var j = 0, jl = valueNames[i].data.length; j < jl; j++) {\n el.setAttribute('data-'+valueNames[i].data[j], '');\n }\n } else if (valueNames[i].attr && valueNames[i].name) {\n elm = list.utils.getByClass(el, valueNames[i].name, true);\n if (elm) {\n elm.setAttribute(valueNames[i].attr, \"\");\n }\n } else {\n elm = list.utils.getByClass(el, valueNames[i], true);\n if (elm) {\n elm.innerHTML = \"\";\n }\n }\n elm = undefined;\n }\n return el;\n };\n\n this.getItemSource = function(item) {\n if (item === undefined) {\n var nodes = list.list.childNodes,\n items = [];\n\n for (var i = 0, il = nodes.length; i < il; i++) {\n // Only textnodes have a data attribute\n if (nodes[i].data === undefined) {\n return nodes[i].cloneNode(true);\n }\n }\n } else if (/]/g.exec(item)) {\n var tbody = document.createElement('tbody');\n tbody.innerHTML = item;\n return tbody.firstChild;\n } else if (item.indexOf(\"<\") !== -1) {\n var div = document.createElement('div');\n div.innerHTML = item;\n return div.firstChild;\n } else {\n var source = document.getElementById(list.item);\n if (source) {\n return source;\n }\n }\n return undefined;\n };\n\n this.get = function(item, valueNames) {\n templater.create(item);\n var values = {};\n for(var i = 0, il = valueNames.length; i < il; i++) {\n var elm;\n if (valueNames[i].data) {\n for (var j = 0, jl = valueNames[i].data.length; j < jl; j++) {\n values[valueNames[i].data[j]] = list.utils.getAttribute(item.elm, 'data-'+valueNames[i].data[j]);\n }\n } else if (valueNames[i].attr && valueNames[i].name) {\n elm = list.utils.getByClass(item.elm, valueNames[i].name, true);\n values[valueNames[i].name] = elm ? list.utils.getAttribute(elm, valueNames[i].attr) : \"\";\n } else {\n elm = list.utils.getByClass(item.elm, valueNames[i], true);\n values[valueNames[i]] = elm ? elm.innerHTML : \"\";\n }\n elm = undefined;\n }\n return values;\n };\n\n this.set = function(item, values) {\n var getValueName = function(name) {\n for (var i = 0, il = list.valueNames.length; i < il; i++) {\n if (list.valueNames[i].data) {\n var data = list.valueNames[i].data;\n for (var j = 0, jl = data.length; j < jl; j++) {\n if (data[j] === name) {\n return { data: name };\n }\n }\n } else if (list.valueNames[i].attr && list.valueNames[i].name && list.valueNames[i].name == name) {\n return list.valueNames[i];\n } else if (list.valueNames[i] === name) {\n return name;\n }\n }\n };\n var setValue = function(name, value) {\n var elm;\n var valueName = getValueName(name);\n if (!valueName)\n return;\n if (valueName.data) {\n item.elm.setAttribute('data-'+valueName.data, value);\n } else if (valueName.attr && valueName.name) {\n elm = list.utils.getByClass(item.elm, valueName.name, true);\n if (elm) {\n elm.setAttribute(valueName.attr, value);\n }\n } else {\n elm = list.utils.getByClass(item.elm, valueName, true);\n if (elm) {\n elm.innerHTML = value;\n }\n }\n elm = undefined;\n };\n if (!templater.create(item)) {\n for(var v in values) {\n if (values.hasOwnProperty(v)) {\n setValue(v, values[v]);\n }\n }\n }\n };\n\n this.create = function(item) {\n if (item.elm !== undefined) {\n return false;\n }\n if (itemSource === undefined) {\n throw new Error(\"The list need to have at list one item on init otherwise you'll have to add a template.\");\n }\n /* If item source does not exists, use the first item in list as\n source for new items */\n var newItem = itemSource.cloneNode(true);\n newItem.removeAttribute('id');\n item.elm = newItem;\n templater.set(item, item.values());\n return true;\n };\n this.remove = function(item) {\n if (item.elm.parentNode === list.list) {\n list.list.removeChild(item.elm);\n }\n };\n this.show = function(item) {\n templater.create(item);\n list.list.appendChild(item.elm);\n };\n this.hide = function(item) {\n if (item.elm !== undefined && item.elm.parentNode === list.list) {\n list.list.removeChild(item.elm);\n }\n };\n this.clear = function() {\n /* .innerHTML = ''; fucks up IE */\n if (list.list.hasChildNodes()) {\n while (list.list.childNodes.length >= 1)\n {\n list.list.removeChild(list.list.firstChild);\n }\n }\n };\n\n init();\n};\n\nmodule.exports = function(list) {\n return new Templater(list);\n};\n","module.exports = function(list) {\n var item,\n text,\n columns,\n searchString,\n customSearch;\n\n var prepare = {\n resetList: function() {\n list.i = 1;\n list.templater.clear();\n customSearch = undefined;\n },\n setOptions: function(args) {\n if (args.length == 2 && args[1] instanceof Array) {\n columns = args[1];\n } else if (args.length == 2 && typeof(args[1]) == \"function\") {\n columns = undefined;\n customSearch = args[1];\n } else if (args.length == 3) {\n columns = args[1];\n customSearch = args[2];\n } else {\n columns = undefined;\n }\n },\n setColumns: function() {\n if (list.items.length === 0) return;\n if (columns === undefined) {\n columns = (list.searchColumns === undefined) ? prepare.toArray(list.items[0].values()) : list.searchColumns;\n }\n },\n setSearchString: function(s) {\n s = list.utils.toString(s).toLowerCase();\n s = s.replace(/[-[\\]{}()*+?.,\\\\^$|#]/g, \"\\\\$&\"); // Escape regular expression characters\n searchString = s;\n },\n toArray: function(values) {\n var tmpColumn = [];\n for (var name in values) {\n tmpColumn.push(name);\n }\n return tmpColumn;\n }\n };\n var search = {\n list: function() {\n for (var k = 0, kl = list.items.length; k < kl; k++) {\n search.item(list.items[k]);\n }\n },\n item: function(item) {\n item.found = false;\n for (var j = 0, jl = columns.length; j < jl; j++) {\n if (search.values(item.values(), columns[j])) {\n item.found = true;\n return;\n }\n }\n },\n values: function(values, column) {\n if (values.hasOwnProperty(column)) {\n text = list.utils.toString(values[column]).toLowerCase();\n if ((searchString !== \"\") && (text.search(searchString) > -1)) {\n return true;\n }\n }\n return false;\n },\n reset: function() {\n list.reset.search();\n list.searched = false;\n }\n };\n\n var searchMethod = function(str) {\n list.trigger('searchStart');\n\n prepare.resetList();\n prepare.setSearchString(str);\n prepare.setOptions(arguments); // str, cols|searchFunction, searchFunction\n prepare.setColumns();\n\n if (searchString === \"\" ) {\n search.reset();\n } else {\n list.searched = true;\n if (customSearch) {\n customSearch(searchString, columns);\n } else {\n search.list();\n }\n }\n\n list.update();\n list.trigger('searchComplete');\n return list.visibleItems;\n };\n\n list.handlers.searchStart = list.handlers.searchStart || [];\n list.handlers.searchComplete = list.handlers.searchComplete || [];\n\n list.utils.events.bind(list.utils.getByClass(list.listContainer, list.searchClass), 'keyup', function(e) {\n var target = e.target || e.srcElement, // IE have srcElement\n alreadyCleared = (target.value === \"\" && !list.searched);\n if (!alreadyCleared) { // If oninput already have resetted the list, do nothing\n searchMethod(target.value);\n }\n });\n\n // Used to detect click on HTML5 clear button\n list.utils.events.bind(list.utils.getByClass(list.listContainer, list.searchClass), 'input', function(e) {\n var target = e.target || e.srcElement;\n if (target.value === \"\") {\n searchMethod('');\n }\n });\n\n return searchMethod;\n};\n","module.exports = function(list) {\n\n // Add handlers\n list.handlers.filterStart = list.handlers.filterStart || [];\n list.handlers.filterComplete = list.handlers.filterComplete || [];\n\n return function(filterFunction) {\n list.trigger('filterStart');\n list.i = 1; // Reset paging\n list.reset.filter();\n if (filterFunction === undefined) {\n list.filtered = false;\n } else {\n list.filtered = true;\n var is = list.items;\n for (var i = 0, il = is.length; i < il; i++) {\n var item = is[i];\n if (filterFunction(item)) {\n item.filtered = true;\n } else {\n item.filtered = false;\n }\n }\n }\n list.update();\n list.trigger('filterComplete');\n return list.visibleItems;\n };\n};\n","module.exports = function(list) {\n\n var buttons = {\n els: undefined,\n clear: function() {\n for (var i = 0, il = buttons.els.length; i < il; i++) {\n list.utils.classes(buttons.els[i]).remove('asc');\n list.utils.classes(buttons.els[i]).remove('desc');\n }\n },\n getOrder: function(btn) {\n var predefinedOrder = list.utils.getAttribute(btn, 'data-order');\n if (predefinedOrder == \"asc\" || predefinedOrder == \"desc\") {\n return predefinedOrder;\n } else if (list.utils.classes(btn).has('desc')) {\n return \"asc\";\n } else if (list.utils.classes(btn).has('asc')) {\n return \"desc\";\n } else {\n return \"asc\";\n }\n },\n getInSensitive: function(btn, options) {\n var insensitive = list.utils.getAttribute(btn, 'data-insensitive');\n if (insensitive === \"false\") {\n options.insensitive = false;\n } else {\n options.insensitive = true;\n }\n },\n setOrder: function(options) {\n for (var i = 0, il = buttons.els.length; i < il; i++) {\n var btn = buttons.els[i];\n if (list.utils.getAttribute(btn, 'data-sort') !== options.valueName) {\n continue;\n }\n var predefinedOrder = list.utils.getAttribute(btn, 'data-order');\n if (predefinedOrder == \"asc\" || predefinedOrder == \"desc\") {\n if (predefinedOrder == options.order) {\n list.utils.classes(btn).add(options.order);\n }\n } else {\n list.utils.classes(btn).add(options.order);\n }\n }\n }\n };\n\n var sort = function() {\n list.trigger('sortStart');\n var options = {};\n\n var target = arguments[0].currentTarget || arguments[0].srcElement || undefined;\n\n if (target) {\n options.valueName = list.utils.getAttribute(target, 'data-sort');\n buttons.getInSensitive(target, options);\n options.order = buttons.getOrder(target);\n } else {\n options = arguments[1] || options;\n options.valueName = arguments[0];\n options.order = options.order || \"asc\";\n options.insensitive = (typeof options.insensitive == \"undefined\") ? true : options.insensitive;\n }\n\n buttons.clear();\n buttons.setOrder(options);\n\n\n // caseInsensitive\n // alphabet\n var customSortFunction = (options.sortFunction || list.sortFunction || null),\n multi = ((options.order === 'desc') ? -1 : 1),\n sortFunction;\n\n if (customSortFunction) {\n sortFunction = function(itemA, itemB) {\n return customSortFunction(itemA, itemB, options) * multi;\n };\n } else {\n sortFunction = function(itemA, itemB) {\n var sort = list.utils.naturalSort;\n sort.alphabet = list.alphabet || options.alphabet || undefined;\n if (!sort.alphabet && options.insensitive) {\n sort = list.utils.naturalSort.caseInsensitive;\n }\n return sort(itemA.values()[options.valueName], itemB.values()[options.valueName]) * multi;\n };\n }\n\n list.items.sort(sortFunction);\n list.update();\n list.trigger('sortComplete');\n };\n\n // Add handlers\n list.handlers.sortStart = list.handlers.sortStart || [];\n list.handlers.sortComplete = list.handlers.sortComplete || [];\n\n buttons.els = list.utils.getByClass(list.listContainer, list.sortClass);\n list.utils.events.bind(buttons.els, 'click', sort);\n list.on('searchStart', buttons.clear);\n list.on('filterStart', buttons.clear);\n\n return sort;\n};\n","module.exports = function(text, pattern, options) {\n // Aproximately where in the text is the pattern expected to be found?\n var Match_Location = options.location || 0;\n\n //Determines how close the match must be to the fuzzy location (specified above). An exact letter match which is 'distance' characters away from the fuzzy location would score as a complete mismatch. A distance of '0' requires the match be at the exact location specified, a threshold of '1000' would require a perfect match to be within 800 characters of the fuzzy location to be found using a 0.8 threshold.\n var Match_Distance = options.distance || 100;\n\n // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match (of both letters and location), a threshold of '1.0' would match anything.\n var Match_Threshold = options.threshold || 0.4;\n\n if (pattern === text) return true; // Exact match\n if (pattern.length > 32) return false; // This algorithm cannot be used\n\n // Set starting location at beginning text and initialise the alphabet.\n var loc = Match_Location,\n s = (function() {\n var q = {},\n i;\n\n for (i = 0; i < pattern.length; i++) {\n q[pattern.charAt(i)] = 0;\n }\n\n for (i = 0; i < pattern.length; i++) {\n q[pattern.charAt(i)] |= 1 << (pattern.length - i - 1);\n }\n\n return q;\n }());\n\n // Compute and return the score for a match with e errors and x location.\n // Accesses loc and pattern through being a closure.\n\n function match_bitapScore_(e, x) {\n var accuracy = e / pattern.length,\n proximity = Math.abs(loc - x);\n\n if (!Match_Distance) {\n // Dodge divide by zero error.\n return proximity ? 1.0 : accuracy;\n }\n return accuracy + (proximity / Match_Distance);\n }\n\n var score_threshold = Match_Threshold, // Highest score beyond which we give up.\n best_loc = text.indexOf(pattern, loc); // Is there a nearby exact match? (speedup)\n\n if (best_loc != -1) {\n score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold);\n // What about in the other direction? (speedup)\n best_loc = text.lastIndexOf(pattern, loc + pattern.length);\n\n if (best_loc != -1) {\n score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold);\n }\n }\n\n // Initialise the bit arrays.\n var matchmask = 1 << (pattern.length - 1);\n best_loc = -1;\n\n var bin_min, bin_mid;\n var bin_max = pattern.length + text.length;\n var last_rd;\n for (var d = 0; d < pattern.length; d++) {\n // Scan for the best match; each iteration allows for one more error.\n // Run a binary search to determine how far from 'loc' we can stray at this\n // error level.\n bin_min = 0;\n bin_mid = bin_max;\n while (bin_min < bin_mid) {\n if (match_bitapScore_(d, loc + bin_mid) <= score_threshold) {\n bin_min = bin_mid;\n } else {\n bin_max = bin_mid;\n }\n bin_mid = Math.floor((bin_max - bin_min) / 2 + bin_min);\n }\n // Use the result from this iteration as the maximum for the next.\n bin_max = bin_mid;\n var start = Math.max(1, loc - bin_mid + 1);\n var finish = Math.min(loc + bin_mid, text.length) + pattern.length;\n\n var rd = Array(finish + 2);\n rd[finish + 1] = (1 << d) - 1;\n for (var j = finish; j >= start; j--) {\n // The alphabet (s) is a sparse hash, so the following line generates\n // warnings.\n var charMatch = s[text.charAt(j - 1)];\n if (d === 0) { // First pass: exact match.\n rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;\n } else { // Subsequent passes: fuzzy match.\n rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) |\n (((last_rd[j + 1] | last_rd[j]) << 1) | 1) |\n last_rd[j + 1];\n }\n if (rd[j] & matchmask) {\n var score = match_bitapScore_(d, j - 1);\n // This match will almost certainly be better than any existing match.\n // But check anyway.\n if (score <= score_threshold) {\n // Told you so.\n score_threshold = score;\n best_loc = j - 1;\n if (best_loc > loc) {\n // When passing loc, don't exceed our current distance from loc.\n start = Math.max(1, 2 * loc - best_loc);\n } else {\n // Already passed loc, downhill from here on in.\n break;\n }\n }\n }\n }\n // No hope for a (better) match at greater error levels.\n if (match_bitapScore_(d + 1, loc) > score_threshold) {\n break;\n }\n last_rd = rd;\n }\n\n return (best_loc < 0) ? false : true;\n};\n","\nvar classes = require('./utils/classes'),\n events = require('./utils/events'),\n extend = require('./utils/extend'),\n toString = require('./utils/to-string'),\n getByClass = require('./utils/get-by-class'),\n fuzzy = require('./utils/fuzzy');\n\nmodule.exports = function(list, options) {\n options = options || {};\n\n options = extend({\n location: 0,\n distance: 100,\n threshold: 0.4,\n multiSearch: true,\n searchClass: 'fuzzy-search'\n }, options);\n\n\n\n var fuzzySearch = {\n search: function(searchString, columns) {\n // Substract arguments from the searchString or put searchString as only argument\n var searchArguments = options.multiSearch ? searchString.replace(/ +$/, '').split(/ +/) : [searchString];\n\n for (var k = 0, kl = list.items.length; k < kl; k++) {\n fuzzySearch.item(list.items[k], columns, searchArguments);\n }\n },\n item: function(item, columns, searchArguments) {\n var found = true;\n for(var i = 0; i < searchArguments.length; i++) {\n var foundArgument = false;\n for (var j = 0, jl = columns.length; j < jl; j++) {\n if (fuzzySearch.values(item.values(), columns[j], searchArguments[i])) {\n foundArgument = true;\n }\n }\n if(!foundArgument) {\n found = false;\n }\n }\n item.found = found;\n },\n values: function(values, value, searchArgument) {\n if (values.hasOwnProperty(value)) {\n var text = toString(values[value]).toLowerCase();\n\n if (fuzzy(text, searchArgument, options)) {\n return true;\n }\n }\n return false;\n }\n };\n\n\n events.bind(getByClass(list.listContainer, options.searchClass), 'keyup', function(e) {\n var target = e.target || e.srcElement; // IE have srcElement\n list.search(target.value, fuzzySearch.search);\n });\n\n return function(str, columns) {\n list.search(str, columns, fuzzySearch.search);\n };\n};\n","var naturalSort = require('string-natural-compare'),\n getByClass = require('./utils/get-by-class'),\n extend = require('./utils/extend'),\n indexOf = require('./utils/index-of'),\n events = require('./utils/events'),\n toString = require('./utils/to-string'),\n classes = require('./utils/classes'),\n getAttribute = require('./utils/get-attribute'),\n toArray = require('./utils/to-array');\n\nmodule.exports = function(id, options, values) {\n\n var self = this,\n init,\n Item = require('./item')(self),\n addAsync = require('./add-async')(self),\n initPagination = require('./pagination')(self);\n\n init = {\n start: function() {\n self.listClass = \"list\";\n self.searchClass = \"search\";\n self.sortClass = \"sort\";\n self.page = 10000;\n self.i = 1;\n self.items = [];\n self.visibleItems = [];\n self.matchingItems = [];\n self.searched = false;\n self.filtered = false;\n self.searchColumns = undefined;\n self.handlers = { 'updated': [] };\n self.valueNames = [];\n self.utils = {\n getByClass: getByClass,\n extend: extend,\n indexOf: indexOf,\n events: events,\n toString: toString,\n naturalSort: naturalSort,\n classes: classes,\n getAttribute: getAttribute,\n toArray: toArray\n };\n\n self.utils.extend(self, options);\n\n self.listContainer = (typeof(id) === 'string') ? document.getElementById(id) : id;\n if (!self.listContainer) { return; }\n self.list = getByClass(self.listContainer, self.listClass, true);\n\n self.parse = require('./parse')(self);\n self.templater = require('./templater')(self);\n self.search = require('./search')(self);\n self.filter = require('./filter')(self);\n self.sort = require('./sort')(self);\n self.fuzzySearch = require('./fuzzy-search')(self, options.fuzzySearch);\n\n this.handlers();\n this.items();\n this.pagination();\n\n self.update();\n },\n handlers: function() {\n for (var handler in self.handlers) {\n if (self[handler]) {\n self.on(handler, self[handler]);\n }\n }\n },\n items: function() {\n self.parse(self.list);\n if (values !== undefined) {\n self.add(values);\n }\n },\n pagination: function() {\n if (options.pagination !== undefined) {\n if (options.pagination === true) {\n options.pagination = [{}];\n }\n if (options.pagination[0] === undefined){\n options.pagination = [options.pagination];\n }\n for (var i = 0, il = options.pagination.length; i < il; i++) {\n initPagination(options.pagination[i]);\n }\n }\n }\n };\n\n /*\n * Re-parse the List, use if html have changed\n */\n this.reIndex = function() {\n self.items = [];\n self.visibleItems = [];\n self.matchingItems = [];\n self.searched = false;\n self.filtered = false;\n self.parse(self.list);\n };\n\n this.toJSON = function() {\n var json = [];\n for (var i = 0, il = self.items.length; i < il; i++) {\n json.push(self.items[i].values());\n }\n return json;\n };\n\n\n /*\n * Add object to list\n */\n this.add = function(values, callback) {\n if (values.length === 0) {\n return;\n }\n if (callback) {\n addAsync(values, callback);\n return;\n }\n var added = [],\n notCreate = false;\n if (values[0] === undefined){\n values = [values];\n }\n for (var i = 0, il = values.length; i < il; i++) {\n var item = null;\n notCreate = (self.items.length > self.page) ? true : false;\n item = new Item(values[i], undefined, notCreate);\n self.items.push(item);\n added.push(item);\n }\n self.update();\n return added;\n };\n\n\tthis.show = function(i, page) {\n\t\tthis.i = i;\n\t\tthis.page = page;\n\t\tself.update();\n return self;\n\t};\n\n /* Removes object from list.\n * Loops through the list and removes objects where\n * property \"valuename\" === value\n */\n this.remove = function(valueName, value, options) {\n var found = 0;\n for (var i = 0, il = self.items.length; i < il; i++) {\n if (self.items[i].values()[valueName] == value) {\n self.templater.remove(self.items[i], options);\n self.items.splice(i,1);\n il--;\n i--;\n found++;\n }\n }\n self.update();\n return found;\n };\n\n /* Gets the objects in the list which\n * property \"valueName\" === value\n */\n this.get = function(valueName, value) {\n var matchedItems = [];\n for (var i = 0, il = self.items.length; i < il; i++) {\n var item = self.items[i];\n if (item.values()[valueName] == value) {\n matchedItems.push(item);\n }\n }\n return matchedItems;\n };\n\n /*\n * Get size of the list\n */\n this.size = function() {\n return self.items.length;\n };\n\n /*\n * Removes all items from the list\n */\n this.clear = function() {\n self.templater.clear();\n self.items = [];\n return self;\n };\n\n this.on = function(event, callback) {\n self.handlers[event].push(callback);\n return self;\n };\n\n this.off = function(event, callback) {\n var e = self.handlers[event];\n var index = indexOf(e, callback);\n if (index > -1) {\n e.splice(index, 1);\n }\n return self;\n };\n\n this.trigger = function(event) {\n var i = self.handlers[event].length;\n while(i--) {\n self.handlers[event][i](self);\n }\n return self;\n };\n\n this.reset = {\n filter: function() {\n var is = self.items,\n il = is.length;\n while (il--) {\n is[il].filtered = false;\n }\n return self;\n },\n search: function() {\n var is = self.items,\n il = is.length;\n while (il--) {\n is[il].found = false;\n }\n return self;\n }\n };\n\n this.update = function() {\n var is = self.items,\n\t\t\til = is.length;\n\n self.visibleItems = [];\n self.matchingItems = [];\n self.templater.clear();\n for (var i = 0; i < il; i++) {\n if (is[i].matching() && ((self.matchingItems.length+1) >= self.i && self.visibleItems.length < self.page)) {\n is[i].show();\n self.visibleItems.push(is[i]);\n self.matchingItems.push(is[i]);\n } else if (is[i].matching()) {\n self.matchingItems.push(is[i]);\n is[i].hide();\n } else {\n is[i].hide();\n }\n }\n self.trigger('updated');\n return self;\n };\n\n init.start();\n};\n","\r\nimport { el, svg } from 'redom';\r\nimport $ from \"jquery\";\r\n// import * as List from \"list.js/dist/list.js\"; // See https://github.com/javve/list.js\r\nimport List from \"list.js/src/index.js\"; // See https://github.com/javve/list.js\r\n\r\nvar ALIVE_TIME = 60000 * 5; // 5 minutes\r\n\r\nfunction keepAlive() {\r\n $.post('/api/ui/ka');\r\n setTimeout(keepAlive, ALIVE_TIME);\r\n}\r\n\r\nexport function handleWebServiceError(msg) {\r\n var items = [\r\n svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#exclamation-circle' } }))\r\n ];\r\n if (msg && (msg.length > 0)) {\r\n items.push(el('span', msg));\r\n $.jGrowl(items, { header: utilityStrings.MSG_ERROR, life: 10000, theme: 'growl-error' });\r\n } else {\r\n items.push(el('span', utilityStrings.MSG_ERROR_OCCURRED));\r\n $.jGrowl(items, { header: utilityStrings.MSG_ERROR, life: 10000, theme: 'growl-error' });\r\n }\r\n}\r\n\r\nexport function createCheckboxFilterList(containerId, valueNames) {\r\n if (valueNames === undefined) {\r\n valueNames = [\"custom-control-label\"];\r\n }\r\n var container = document.getElementById(containerId);\r\n var itemList = new List(container, {\r\n listClass: \"list\",\r\n searchClass: \"search\",\r\n indexAsync: true,\r\n //page: 20,\r\n //pagination: true,\r\n valueNames: valueNames,\r\n }).on(\"updated\", function (list) {\r\n var helperAll = container.querySelector(\".list-selection-all\");\r\n var helperFiltered = container.querySelector(\".list-selection-filtered\");\r\n\r\n helperAll.classList.remove(\"is-visible\");\r\n helperFiltered.classList.remove(\"is-visible\");\r\n if (list.searched && list.matchingItems.length > 0) {\r\n helperFiltered.classList.add(\"is-visible\");\r\n }\r\n\r\n if (!list.searched && list.items.length > 0) {\r\n helperAll.classList.add(\"is-visible\");\r\n }\r\n\r\n // todo: perhaps also show the number of results for the search?\r\n //var results = container.querySelector('.filter-results');\r\n //if (!list.searched) {\r\n // results.classList.remove('is-visible');\r\n //} else {\r\n // results.classList.add('is-visible');\r\n // var text = results.querySelector('span');\r\n // text.textContent = list.matchingItems.length + ' / ' + list.items.length;\r\n //}\r\n });\r\n\r\n $(container).on(\"click\", \"a.list-select-all\", function (e) {\r\n e.preventDefault();\r\n\r\n var list = this.getAttribute(\"data-visible\") !== null ? itemList.visibleItems : itemList.items;\r\n _.each(list, function (item) {\r\n var checkbox = item.elm.querySelector(\".custom-control-input\");\r\n if (checkbox !== null) {\r\n checkbox.checked = true;\r\n $(checkbox).trigger(\"change\");\r\n //var link = container.querySelector('#' + checkbox.id + '-hidden');\r\n //if (link !== null) {\r\n // link.checked = true;\r\n // $(link).trigger('change');\r\n //}\r\n\r\n //if (checkbox.dataset.enables !== undefined) {\r\n // var linked = container.querySelector(checkbox.dataset.enables);\r\n // if (linked !== null) {\r\n // linked.disabled = !checkbox.checked;\r\n // if (linked.disabled) {\r\n // linked.checked = false;\r\n // }\r\n // }\r\n //}\r\n }\r\n });\r\n });\r\n\r\n $(container).on(\"click\", \"a.list-select-none\", function (e) {\r\n e.preventDefault();\r\n\r\n var list = this.getAttribute(\"data-visible\") !== null ? itemList.visibleItems : itemList.items;\r\n _.each(list, function (item) {\r\n var checkbox = item.elm.querySelector(\".custom-control-input\");\r\n if (checkbox !== null) {\r\n checkbox.checked = false;\r\n $(checkbox).trigger(\"change\");\r\n //var link = container.querySelector('#' + checkbox.id + '-hidden');\r\n //if (link !== null) {\r\n // link.checked = false;\r\n // $(link).trigger('change');\r\n //}\r\n //if (checkbox.dataset.enables !== undefined) {\r\n // var linked = container.querySelector(checkbox.dataset.enables);\r\n // if (linked !== null) {\r\n // linked.disabled = !checkbox.checked;\r\n // if (linked.disabled) {\r\n // linked.checked = false;\r\n // }\r\n // }\r\n //}\r\n }\r\n });\r\n });\r\n\r\n $(container).on(\"change\", \"input.custom-control-input\", function () {\r\n var link = container.querySelector(\"#\" + this.id + \"-hidden\");\r\n if (link !== null) {\r\n link.checked = this.checked;\r\n $(link).trigger(\"change\");\r\n }\r\n\r\n if (this.dataset.enables !== undefined) {\r\n var linked = container.querySelector(this.dataset.enables);\r\n if (linked !== null) {\r\n linked.disabled = !this.checked;\r\n if (linked.disabled) {\r\n linked.checked = false;\r\n }\r\n }\r\n }\r\n });\r\n return itemList;\r\n}\r\n\r\nfunction toggleHeader() {\r\n $('#toggle-header a').toggleClass('up');\r\n $('#head').slideToggle(400, function () {\r\n // resize tracking app if available\r\n if (typeof tracking === 'object') {\r\n tracking.resizeApp();\r\n }\r\n });\r\n}\r\n\r\n//function sizeScrollingSelector() {\r\n// if ($(window).width() <= 768) {\r\n// ht = Math.round($(window).height() * 0.4);\r\n// $('#scrolling-selector-list').css('max-height', ht + 'px');\r\n// }\r\n// else {\r\n// $('#scrolling-selector-list').css('max-height', '20em');\r\n// }\r\n//}\r\n\r\n//window.onresize = function (event) {\r\n// sizeScrollingSelector();\r\n//}\r\n\r\nfunction preventDefaults(e) {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n}\r\n\r\nfunction highlightDropZone(e) {\r\n this.classList.add('highlight');\r\n}\r\n\r\nfunction unhighlightDropZone(e) {\r\n this.classList.remove('highlight');\r\n}\r\n\r\n$(document).ready(function () {\r\n //sizeScrollingSelector();\r\n setTimeout(keepAlive, ALIVE_TIME);\r\n //if (typeof ($().button) === 'function') {\r\n // $('button.command.edit').button({ text: null, icons: { primary: 'ui-icon-pencil' } });\r\n // $('button.command.delete').button({ text: null, icons: { primary: 'ui-icon-trash' } });\r\n // $('button.command.add').button({ icons: { primary: 'ui-icon-plusthick' } });\r\n // $('button.command.upload').button({ icons: { primary: 'ui-icon-circle-arrow-n' } });\r\n // $('button.command.download').button({ icons: { primary: 'ui-icon-circle-arrow-s' } });\r\n // $('button.command.preview').button({ icons: { primary: 'ui-icon-circle-zoomin' } });\r\n // $('button.command.up').button({ text: null, icons: { primary: 'ui-icon-circle-arrow-n' } });\r\n // $('button.command.down').button({ text: null, icons: { primary: 'ui-icon-circle-arrow-s' } });\r\n // $('button.command.test').button({ icons: { primary: 'ui-icon-mail-closed' } });\r\n // $('button.command.disable').button({ icons: { primary: 'ui-icon-closethick' } });\r\n // $('button.command.enable').button({ icons: { primary: 'ui-icon-check' } });\r\n // $('button.command.view').button({ icons: { primary: 'ui-icon-note' } });\r\n\r\n // $('button.command').button();\r\n // $('#PrintPage').button({\r\n // icons: {\r\n // primary: 'ui-icon-print'\r\n // }\r\n // });\r\n // $('#ExportExcel').button({\r\n // icons: {\r\n // primary: 'ui-icon-excel'\r\n // }\r\n // });\r\n // $('#ExportPDF').button({\r\n // icons: {\r\n // primary: 'ui-icon-pdf'\r\n // }\r\n // });\r\n //}\r\n\r\n // header toggle for anonymous users\r\n $('#root').on('click', '#toggle-header a', function (e) {\r\n e.preventDefault();\r\n toggleHeader();\r\n });\r\n\r\n $('#PrintPage').on('click', this, function (e) {\r\n window.print();\r\n });\r\n\r\n // asset selection events, relative and duplicatable\r\n $('body').on('click', 'button.AddItem', function (e) {\r\n e.preventDefault();\r\n\r\n // move selected items from right to left\r\n var cont = $(this).parents('table');\r\n var selects = cont.find('select');\r\n var left = $(selects[0]);\r\n var right = $(selects[1]);\r\n right.children().each(function () {\r\n if ($(this).prop('selected') === true) {\r\n var clone = $(this).clone(true);\r\n $(this).remove();\r\n left.append(clone);\r\n }\r\n });\r\n });\r\n\r\n $('body').on('click', 'button.RemoveItem', function (e) {\r\n e.preventDefault();\r\n\r\n // move selected items from left to left\r\n var cont = $(this).parents('table');\r\n var selects = cont.find('select');\r\n var left = $(selects[0]);\r\n var right = $(selects[1]);\r\n left.children().each(function () {\r\n if ($(this).prop('selected') === true) {\r\n var clone = $(this).clone(true);\r\n $(this).remove();\r\n right.append(clone);\r\n }\r\n });\r\n });\r\n\r\n $('body').on('click', 'a.SelectAllItems', function (e) {\r\n e.preventDefault();\r\n\r\n var cont = $(this).parents('table');\r\n var selects = cont.find('select');\r\n var right = $(selects[1]);\r\n right.find('option').prop('selected', true);\r\n });\r\n\r\n $('body').on('click', 'a.SelectAllCurrentItems', function (e) {\r\n e.preventDefault();\r\n\r\n var cont = $(this).parents('table');\r\n var selects = cont.find('select');\r\n var left = $(selects[0]);\r\n left.find('option').prop('selected', true);\r\n });\r\n\r\n $('body').on('click', 'button.AddAllItems', function (e) {\r\n e.preventDefault();\r\n\r\n // move selected items from right to left\r\n var cont = $(this).parents('table');\r\n var selects = cont.find('select');\r\n var left = $(selects[0]);\r\n var right = $(selects[1]);\r\n right.children().each(function () {\r\n var clone = $(this).clone(true);\r\n $(this).remove();\r\n left.append(clone);\r\n });\r\n });\r\n\r\n $('body').on('click', 'button.RemoveAllItems', function (e) {\r\n e.preventDefault();\r\n\r\n // move selected items from right to left\r\n var cont = $(this).parents('table');\r\n var selects = cont.find('select');\r\n var left = $(selects[0]);\r\n var right = $(selects[1]);\r\n left.children().each(function () {\r\n var clone = $(this).clone(true);\r\n $(this).remove();\r\n right.append(clone);\r\n });\r\n });\r\n\r\n $('body').on('click', 'button.select-all', function (e) {\r\n e.preventDefault();\r\n $(document.getElementById(this.getAttribute('data-select-source'))).find('option').prop('selected', true);\r\n });\r\n $('body').on('click', 'button.move-items', function (e) {\r\n e.preventDefault();\r\n var from = $(document.getElementById(this.getAttribute('data-move-from')));\r\n var to = $(document.getElementById(this.getAttribute('data-move-to')));\r\n var moveAll = this.getAttribute('data-move-all');\r\n if (moveAll === 'true') {\r\n // move all items regardless of selection\r\n from.children().each(function () {\r\n var clone = $(this).clone(true);\r\n $(this).remove();\r\n to.append(clone);\r\n });\r\n } else {\r\n // move selected items only\r\n from.children().each(function () {\r\n if ($(this).prop('selected') === true) {\r\n var clone = $(this).clone(true);\r\n $(this).remove();\r\n to.append(clone);\r\n }\r\n });\r\n }\r\n // update counts, if there\r\n var fromCount = from[0].getAttribute('data-item-count');\r\n if (fromCount !== null) {\r\n var count = document.getElementById(fromCount);\r\n if (count !== null) {\r\n count.textContent = from[0].querySelectorAll('option').length;\r\n }\r\n }\r\n var toCount = to[0].getAttribute('data-item-count');\r\n if (toCount !== null) {\r\n var count = document.getElementById(toCount);\r\n if (count !== null) {\r\n count.textContent = to[0].querySelectorAll('option').length;\r\n }\r\n }\r\n });\r\n $('body').on('click', '.show-upgrade', function (e) {\r\n e.preventDefault();\r\n $('#upgrade-tracking-modal').modal('show');\r\n });\r\n\r\n if (document.body.classList.contains('feature-unavailable')) {\r\n var pageContent = document.getElementById('page-content');\r\n if (pageContent === null) {\r\n return;\r\n }\r\n var items = pageContent.querySelectorAll('button,a,input,select,form,.card,label');\r\n _.each(items, function (item) {\r\n if (!item.classList.contains('custom-file-input')) {\r\n item.classList.add('disabled');\r\n }\r\n item.disabled = true;\r\n });\r\n }\r\n\r\n var unavailableSections = document.querySelectorAll('.feature-unavailable-section');\r\n var excludeIds = ['RoutingCancel'];\r\n _.each(unavailableSections, function (section) {\r\n var items = section.querySelectorAll('button,a,input,select,form,.card,label');\r\n _.each(items, function (item) {\r\n if (excludeIds.indexOf(item.id) !== -1) {\r\n return;\r\n }\r\n if (!item.classList.contains('custom-file-input')) {\r\n item.classList.add('disabled');\r\n }\r\n item.disabled = true;\r\n });\r\n });\r\n\r\n var fileInputs = document.querySelectorAll('input[type=file].custom-file-input');\r\n _.each(fileInputs, function (fileInput) {\r\n var label = document.querySelector('label[for=' + fileInput.id + '].custom-file-label');\r\n if (label !== null) {\r\n label.dataset.originalLabel = label.textContent;\r\n fileInput.addEventListener('change', function () {\r\n // update photo label\r\n var filename = this.value.replace('C:\\\\fakepath\\\\', '');\r\n if (filename == '' && label.dataset.originalLabel !== undefined) {\r\n filename = label.dataset.originalLabel;\r\n }\r\n document.querySelector('label[for=' + fileInput.id + '].custom-file-label').textContent = filename;\r\n });\r\n }\r\n });\r\n\r\n var dropZones = document.querySelectorAll('.upload-zone');\r\n var dragEvents = ['dragenter', 'dragover', 'dragleave', 'drop'];\r\n var highlightEvents = ['dragenter', 'dragover'];\r\n var unhighlightEvents = ['dragleave', 'drop'];\r\n _.each(dropZones, function (dropZone) {\r\n _.each(dragEvents, function (dragEvent) {\r\n dropZone.addEventListener(dragEvent, preventDefaults, false);\r\n });\r\n _.each(highlightEvents, function (highlightEvent) {\r\n dropZone.addEventListener(highlightEvent, highlightDropZone, false);\r\n });\r\n _.each(unhighlightEvents, function (unhighlightEvent) {\r\n dropZone.addEventListener(unhighlightEvent, unhighlightDropZone, false);\r\n });\r\n });\r\n});\r\n\r\n$(window).on('unload', function () {\r\n if (typeof GUnload === 'function') {\r\n GUnload();\r\n }\r\n});","import trkData from \"./data.js\";\r\nimport { getGenericIconUrlForItemType } from \"./marker-path.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport $j from \"jquery\";\r\nimport _ from \"lodash\";\r\nimport { el, text, svg } from \"redom\"; // https://redom.js.org/\r\nimport { createCheckboxFilterList } from \"../utility.mjs\";\r\n\r\nexport function includeRowIfNotNull(header, content, headerOptions) {\r\n\tif (content === null || content === undefined) {\r\n\t\treturn null;\r\n\t}\r\n\tif (content === \"\") {\r\n\t\treturn null;\r\n\t}\r\n\treturn el(\"tr\", [el(\"td.field\", headerOptions, header + \":\"), el(\"td\", content)]);\r\n}\r\n\r\nexport function createAccordionCard(id, parentId, header, body) {\r\n\tvar contentId = \"accordion-\" + id + \"-content\";\r\n\r\n\tvar cardheadertext = el(\"span.mr-auto\", header);\r\n\tvar cardicon = svg(\r\n\t\t\"svg.list-item-action\",\r\n\t\tsvg(\"use\", { xlink: { href: \"/content/svg/tracking.svg?v=15#angle-down\" } })\r\n\t);\r\n\r\n\tvar cardheaderbutton = el(\r\n\t\t\"button.btn.btn-link.d-flex.flex-row.w-100.p-0\",\r\n\t\t{\r\n\t\t\ttype: \"button\",\r\n\t\t\t\"data-toggle\": \"collapse\",\r\n\t\t\t\"data-target\": \"#\" + contentId,\r\n\t\t\t\"aria-expanded\": \"false\",\r\n\t\t\t\"aria-controls\": contentId,\r\n\t\t},\r\n\t\t[cardheadertext, cardicon]\r\n\t);\r\n\tvar cardbody = el(\".card-body\", body);\r\n\tvar cardcontent = el(\"#\" + contentId + \".collapse\", { \"data-parent\": \"#\" + parentId }, cardbody);\r\n\tvar cardheader = el(\".card-header.p-0.pl-2#accordion-\" + id + \"-head\", cardheaderbutton);\r\n\r\n\treturn el(\".card\", [cardheader, cardcontent]);\r\n}\r\n\r\nexport function createDialogTitleFragment(name, subHeading) {\r\n\tvar titleFragment = document.createDocumentFragment();\r\n\ttitleFragment.appendChild(document.createTextNode(name));\r\n\r\n\tif (subHeading !== null && subHeading !== \"\") {\r\n\t\tvar deviceName = document.createElement(\"div\");\r\n\t\tdeviceName.className = \"device-name\";\r\n\t\tdeviceName.textContent = subHeading;\r\n\t\ttitleFragment.appendChild(deviceName);\r\n\t}\r\n\treturn titleFragment;\r\n}\r\n\r\nexport function svgPath(icon) {\r\n\treturn \"/content/svg/tracking.svg?v=15#\" + icon;\r\n}\r\n\r\nexport function getSvgIconForItemType(type, item) {\r\n\tif (type === \"shared-views\") {\r\n\t\treturn svgPath(\"share-alt-square\");\r\n\t} else if (type === \"groups\") {\r\n\t\treturn svgPath(\"folder-open-solid\");\r\n\t} else if (type === \"journeys\") {\r\n\t\treturn svgPath(\"folder-open-solid\");\r\n\t} else if (type === \"drivers\") {\r\n\t\treturn svgPath(\"steering-wheel\");\r\n\t} else if (type == \"fences\") {\r\n\t\treturn svgPath(\"square-full-duo\");\r\n\t}\r\n\treturn null;\r\n}\r\n\r\nexport function populateCheckboxList(\r\n\tcontainerId,\r\n\titems,\r\n\tname,\r\n\tisCheckedFunction,\r\n\tgetNameFunction,\r\n\ttype,\r\n\tgetDescriptionFunction,\r\n\tgetSecondColumnFunction\r\n) {\r\n\tvar container = document.getElementById(containerId);\r\n\tlet cont = container.querySelector(\".list\");\r\n\tvar hiddenCont = container.querySelector(\".list-hidden\");\r\n\tcont.innerHTML = \"\";\r\n\thiddenCont.innerHTML = \"\";\r\n\r\n\tvar assetAssetsContainer = document.createDocumentFragment();\r\n\tvar assetHiddenContainer = document.createDocumentFragment();\r\n\tfor (var k = 0; k < items.length; k++) {\r\n\t\tvar item = items[k];\r\n\t\tvar isChecked = isCheckedFunction(item);\r\n\t\tvar formCheck = document.createElement(\"div\");\r\n\t\tformCheck.className = \"custom-control custom-checkbox\";\r\n\t\tvar input = document.createElement(\"input\");\r\n\t\tinput.setAttribute(\"type\", \"checkbox\");\r\n\t\tinput.setAttribute(\"id\", name + k);\r\n\t\tinput.setAttribute(\"name\", name + \"-name\");\r\n\t\tinput.className = \"custom-control-input\";\r\n\t\tinput.setAttribute(\"value\", item.Id);\r\n\t\tif (isChecked) {\r\n\t\t\tinput.setAttribute(\"checked\", \"checked\");\r\n\t\t}\r\n\t\tformCheck.appendChild(input);\r\n\r\n\t\tvar label = document.createElement(\"label\");\r\n\t\tlabel.setAttribute(\"for\", name + k);\r\n\t\tlabel.className = \"custom-control-label\";\r\n\r\n\t\tif (type !== undefined && type !== null) {\r\n\t\t\tvar icon = getGenericIconUrlForItemType(type, item);\r\n\t\t\tvar svgIcon = getSvgIconForItemType(type, item);\r\n\t\t\tif (svgIcon !== null) {\r\n\t\t\t\tvar svg = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\r\n\t\t\t\tvar iconType = document.createElementNS(\"http://www.w3.org/2000/svg\", \"use\");\r\n\t\t\t\ticonType.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", svgIcon);\r\n\t\t\t\tsvg.appendChild(iconType);\r\n\t\t\t\tif (item.Color !== undefined && item.Color !== null) {\r\n\t\t\t\t\tsvg.setAttribute(\r\n\t\t\t\t\t\t\"style\",\r\n\t\t\t\t\t\t\"color: \" + item.Color + \"; --color-primary: \" + item.Color + \"; --color-secondary: \" + item.Color\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\t\t\t\tlabel.appendChild(svg);\r\n\t\t\t\tlabel.classList.add(\"has-svg-icon\");\r\n\t\t\t} else if (icon !== null) {\r\n\t\t\t\tlabel.classList.add(\"has-icon\");\r\n\t\t\t\tlabel.style.backgroundImage = icon;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar span = document.createElement(\"span\");\r\n\t\tif (getSecondColumnFunction !== undefined && getSecondColumnFunction !== null) {\r\n\t\t\tspan.className = \"col-6 pl-0\";\r\n\t\t}\r\n\t\tvar nameText = document.createTextNode(getNameFunction(item));\r\n\t\tspan.appendChild(nameText);\r\n\r\n\t\tif (getDescriptionFunction !== undefined && getDescriptionFunction !== null) {\r\n\t\t\tvar description = getDescriptionFunction(item);\r\n\t\t\tif (description !== null) {\r\n\t\t\t\tvar desc = document.createElement(\"div\");\r\n\t\t\t\tdesc.textContent = description;\r\n\t\t\t\tdesc.classList.add(\"meta\");\r\n\t\t\t\tspan.appendChild(desc);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tlabel.appendChild(span);\r\n\r\n\t\tif (getSecondColumnFunction !== undefined && getSecondColumnFunction !== null) {\r\n\t\t\tvar second = document.createElement(\"div\");\r\n\t\t\tsecond.className = \"col-6 p-0\";\r\n\t\t\tvar secondText = document.createTextNode(getSecondColumnFunction(item));\r\n\t\t\tsecond.appendChild(secondText);\r\n\t\t\tlabel.appendChild(second);\r\n\t\t}\r\n\r\n\t\tformCheck.appendChild(label);\r\n\t\tassetAssetsContainer.appendChild(formCheck);\r\n\r\n\t\tvar hidden = input.cloneNode();\r\n\t\thidden.setAttribute(\"id\", hidden.id + \"-hidden\");\r\n\t\thidden.setAttribute(\"name\", name);\r\n\t\tassetHiddenContainer.appendChild(hidden);\r\n\t}\r\n\tcont.appendChild(assetAssetsContainer);\r\n\thiddenCont.appendChild(assetHiddenContainer);\r\n\r\n\t// shouldn't we check if it exists first?\r\n\tvar list = createCheckboxFilterList(containerId);\r\n\treturn list;\r\n}\r\n\r\nexport function disabledCheckboxEl(checked) {\r\n\tvar check = el(\"input.form-check-input\", { type: \"checkbox\", disabled: true });\r\n\tif (checked) {\r\n\t\tcheck.setAttribute(\"checked\", \"checked\");\r\n\t}\r\n\tvar box = el(\"div.form-check\", check);\r\n\treturn box;\r\n}\r\n\r\nexport function unrestrictDirectChildren(item, cont) {\r\n\tvar items = cont.querySelectorAll('input[data-parent=\"' + item.value + '\"]');\r\n\t_.each(items, function (item) {\r\n\t\titem.disabled = false;\r\n\t});\r\n\t//$('input[data-parent=' + item.val() + ']').each(function (index, elem) {\r\n\t// var child = $(elem);\r\n\t// child.prop('disabled', false);\r\n\t//});\r\n}\r\n\r\nexport function restrictAllChildren(item, cont) {\r\n\t// all children checkboxes must be checked and disabled\r\n\tvar items = cont.querySelectorAll('input[data-parent=\"' + item.value + '\"]');\r\n\t_.each(items, function (item) {\r\n\t\titem.disabled = true;\r\n\t\titem.checked = true;\r\n\t\trestrictAllChildren(item, cont);\r\n\t});\r\n\t//$('input[data-parent=' + item.val() + ']').each(function (index, elem) {\r\n\t// var child = $(elem);\r\n\t// child.prop('checked', true).prop('disabled', true);\r\n\t// restrictAllChildren(child);\r\n\t//});\r\n}\r\n\r\nexport function getValueIfCheckboxSelected(element) {\r\n\tvar elem = $j(element);\r\n\tvar include = elem.closest(\".parameter-options\").prev(\".parameter-toggle\").find(\"input:checkbox\").prop(\"checked\");\r\n\t//var include = elem.prevAll('input:checkbox').prop('checked');\r\n\tif (include) return elem.val();\r\n\telse return null;\r\n}\r\n\r\nexport function formattedTextToDiv(formatted) {\r\n\t// split the text on newlines and add html breaks instead\r\n\t// returning an array of nodes\r\n\tif (formatted === undefined || formatted === null || formatted === \"\") {\r\n\t\treturn null;\r\n\t}\r\n\r\n\tvar lines = formatted.split(/\\r?\\n/g);\r\n\tvar nodes = [];\r\n\tfor (var i = 0; i < lines.length; i++) {\r\n\t\tvar line = lines[i];\r\n\t\tnodes.push(text(line));\r\n\t\tif (i != lines.length - 1) {\r\n\t\t\tnodes.push(el(\"br\"));\r\n\t\t}\r\n\t}\r\n\treturn el(\"div\", nodes);\r\n}\r\n\r\nexport function htmlEscape(str) {\r\n\treturn String(str)\r\n\t\t.replace(/&/g, \"&\")\r\n\t\t.replace(/\"/g, \""\")\r\n\t\t.replace(/'/g, \"'\")\r\n\t\t.replace(//g, \">\");\r\n}\r\n\r\nfunction fallbackCopyTextToClipboard(text) {\r\n\tvar textArea = document.createElement(\"textarea\");\r\n\ttextArea.value = text;\r\n\tdocument.body.appendChild(textArea);\r\n\ttextArea.focus();\r\n\ttextArea.select();\r\n\r\n\tvar success = false;\r\n\ttry {\r\n\t\tvar successful = document.execCommand(\"copy\");\r\n\t\tvar msg = successful ? \"successful\" : \"unsuccessful\";\r\n\t\tconsole.log(\"Fallback: Copying text command was \" + msg);\r\n\t\tsuccess = true;\r\n\t} catch (err) {\r\n\t\tconsole.error(\"Fallback: Oops, unable to copy\", err);\r\n\t}\r\n\r\n\tdocument.body.removeChild(textArea);\r\n\treturn success;\r\n}\r\n\r\nexport function copyTextToClipboard(text) {\r\n\tvar def = $.Deferred();\r\n\r\n\tif (text === undefined || text === null || text === \"\") {\r\n\t\tdef.reject();\r\n\t\treturn def;\r\n\t}\r\n\r\n\tif (!navigator.clipboard) {\r\n\t\tif (fallbackCopyTextToClipboard(text)) {\r\n\t\t\tconsole.log(\"fallback success\");\r\n\t\t\tdef.resolve(true);\r\n\t\t} else {\r\n\t\t\tconsole.log(\"fallback fail\");\r\n\t\t\tdef.reject();\r\n\t\t}\r\n\t\treturn def;\r\n\t}\r\n\r\n\tnavigator.clipboard.writeText(text).then(\r\n\t\tfunction () {\r\n\t\t\tdef.resolve(true);\r\n\t\t\tconsole.log(\"Async: Copying to clipboard was successful!\");\r\n\t\t},\r\n\t\tfunction (err) {\r\n\t\t\tdef.reject();\r\n\t\t\tconsole.error(\"Async: Could not copy text: \", err);\r\n\t\t}\r\n\t);\r\n\treturn def;\r\n}\r\n","import domNodes from \"./domNodes.js\";\r\nimport trkData from \"./data.js\";\r\nimport { map } from \"./map-base.js\";\r\n\r\nimport $ from \"jquery\";\r\n\r\nexport function getMapLeftOffset() {\r\n\tif (domNodes.content.base.classList.contains(\"is-collapsed\")) {\r\n\t\treturn 770;\r\n\t} else if (domNodes.content.base.classList.contains(\"is-expanded\")) {\r\n\t\treturn 50;\r\n\t} else {\r\n\t\treturn 370;\r\n\t}\r\n}\r\n\r\nexport function resizeApp(isMapUpdated) {\r\n\tvar height = $(window).height();\r\n\t//var topBarHeight = $j('#topbar').height();\r\n\t//var topOffset = 60 + topBarHeight;\r\n\tif ($(\"#nav-header\").is(\":visible\")) {\r\n\t\theight -= 82;\r\n\t}\r\n\tif ($(\"#event-panel-container\").is(\":visible\")) {\r\n\t\theight -= $(\"#event-panel-container\").height() + 4; // 4 = padding not accounted for in .height() for some reason\r\n\t}\r\n\r\n\tvar resizeHeight = height;\r\n\r\n\t// only resize the rest of the app if the height has actually changed\r\n\tif (resizeHeight === trkData.currentHeight) {\r\n\t\tif (isMapUpdated === true) {\r\n\t\t\tmap.invalidateSize();\r\n\t\t}\r\n\t\treturn;\r\n\t}\r\n\r\n\t//if ((user.isAnonymous) || (options.disableHeader)) {\r\n\t// // if we are hiding the header, then re-adjust the height accordingly\r\n\t// if ($j('#head').is(':hidden')) {\r\n\t// resizeHeight += 60;\r\n\t// }\r\n\t//}\r\n\r\n\t//#panel, #spsizer, #map\r\n\r\n\t// BUG: if collapsed in mobile, then we have nav-utility and nav-header above the map\r\n\t$(\"div.set-height\").height(resizeHeight);\r\n\t$(domNodes.infoDialogs.mapItemInformation).dialog(\"option\", \"maxHeight\", resizeHeight - 80);\r\n\t$(domNodes.infoDialogs.positionHistory).dialog(\"option\", \"maxHeight\", resizeHeight - 80);\r\n\t$(domNodes.infoDialogs.sharedViewInformation).dialog(\"option\", \"maxHeight\", resizeHeight - 80);\r\n\r\n\ttrkData.currentHeight = resizeHeight;\r\n\r\n\tif (isMapUpdated) {\r\n\t\tmap.invalidateSize();\r\n\t}\r\n}\r\n","/*! leaflet.geodesic 2.7.1 - (c) Henry Thasler - https://github.com/henrythasler/Leaflet.Geodesic#readme */\nimport * as L from 'leaflet';\n\n/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nfunction __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nvar __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n };\r\n return __assign.apply(this, arguments);\r\n};\r\n\r\nfunction __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\ntypeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\r\n var e = new Error(message);\r\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\r\n};\n\nvar GeodesicCore = /** @class */ (function () {\n function GeodesicCore(options) {\n this.options = { wrap: true, steps: 3 };\n this.ellipsoid = {\n a: 6378137,\n b: 6356752.3142,\n f: 1 / 298.257223563\n }; // WGS-84\n this.options = __assign(__assign({}, this.options), options);\n }\n GeodesicCore.prototype.toRadians = function (degree) {\n return (degree * Math.PI) / 180;\n };\n GeodesicCore.prototype.toDegrees = function (radians) {\n return (radians * 180) / Math.PI;\n };\n /**\n * implements scientific modulus\n * source: http://www.codeavenger.com/2017/05/19/JavaScript-Modulo-operation-and-the-Caesar-Cipher.html\n * @param n\n * @param p\n * @return\n */\n GeodesicCore.prototype.mod = function (n, p) {\n var r = n % p;\n return r < 0 ? r + p : r;\n };\n /**\n * source: https://github.com/chrisveness/geodesy/blob/master/dms.js\n * @param degrees arbitrary value\n * @return degrees between 0..360\n */\n GeodesicCore.prototype.wrap360 = function (degrees) {\n if (0 <= degrees && degrees < 360) {\n return degrees; // avoid rounding due to arithmetic ops if within range\n }\n else {\n return this.mod(degrees, 360);\n }\n };\n /**\n * general wrap function with arbitrary max value\n * @param degrees arbitrary value\n * @param max\n * @return degrees between `-max`..`+max`\n */\n GeodesicCore.prototype.wrap = function (degrees, max) {\n if (max === void 0) { max = 360; }\n if (-max <= degrees && degrees <= max) {\n return degrees;\n }\n else {\n return this.mod(degrees + max, 2 * max) - max;\n }\n };\n /**\n * Vincenty direct calculation.\n * based on the work of Chris Veness (https://github.com/chrisveness/geodesy)\n * source: https://github.com/chrisveness/geodesy/blob/master/latlon-ellipsoidal-vincenty.js\n *\n * @param start starting point\n * @param bearing initial bearing (in degrees)\n * @param distance distance from starting point to calculate along given bearing in meters.\n * @param maxInterations How many iterations can be made to reach the allowed deviation (`ε`), before an error will be thrown.\n * @return Final point (destination point) and bearing (in degrees)\n */\n GeodesicCore.prototype.direct = function (start, bearing, distance, maxInterations) {\n if (maxInterations === void 0) { maxInterations = 100; }\n var φ1 = this.toRadians(start.lat);\n var λ1 = this.toRadians(start.lng);\n var α1 = this.toRadians(bearing);\n var s = distance;\n var ε = Number.EPSILON * 1000;\n var _a = this.ellipsoid, a = _a.a, b = _a.b, f = _a.f;\n var sinα1 = Math.sin(α1);\n var cosα1 = Math.cos(α1);\n var tanU1 = (1 - f) * Math.tan(φ1), cosU1 = 1 / Math.sqrt(1 + tanU1 * tanU1), sinU1 = tanU1 * cosU1;\n var σ1 = Math.atan2(tanU1, cosα1); // σ1 = angular distance on the sphere from the equator to P1\n var sinα = cosU1 * sinα1; // α = azimuth of the geodesic at the equator\n var cosSqα = 1 - sinα * sinα;\n var uSq = (cosSqα * (a * a - b * b)) / (b * b);\n var A = 1 + (uSq / 16384) * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));\n var B = (uSq / 1024) * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));\n var σ = s / (b * A), sinσ = null, cosσ = null, Δσ = null; // σ = angular distance P₁ P₂ on the sphere\n var cos2σₘ = null; // σₘ = angular distance on the sphere from the equator to the midpoint of the line\n var σʹ = null, iterations = 0;\n do {\n cos2σₘ = Math.cos(2 * σ1 + σ);\n sinσ = Math.sin(σ);\n cosσ = Math.cos(σ);\n Δσ =\n B *\n sinσ *\n (cos2σₘ +\n (B / 4) *\n (cosσ * (-1 + 2 * cos2σₘ * cos2σₘ) -\n (B / 6) * cos2σₘ * (-3 + 4 * sinσ * sinσ) * (-3 + 4 * cos2σₘ * cos2σₘ)));\n σʹ = σ;\n σ = s / (b * A) + Δσ;\n } while (Math.abs(σ - σʹ) > ε && ++iterations < maxInterations);\n if (iterations >= maxInterations) {\n throw new EvalError(\"Direct vincenty formula failed to converge after \".concat(maxInterations, \" iterations \\n (start=\").concat(start.lat, \"/\").concat(start.lng, \"; bearing=\").concat(bearing, \"; distance=\").concat(distance, \")\")); // not possible?\n }\n var x = sinU1 * sinσ - cosU1 * cosσ * cosα1;\n var φ2 = Math.atan2(sinU1 * cosσ + cosU1 * sinσ * cosα1, (1 - f) * Math.sqrt(sinα * sinα + x * x));\n var λ = Math.atan2(sinσ * sinα1, cosU1 * cosσ - sinU1 * sinσ * cosα1);\n var C = (f / 16) * cosSqα * (4 + f * (4 - 3 * cosSqα));\n var dL = λ - (1 - C) * f * sinα * (σ + C * sinσ * (cos2σₘ + C * cosσ * (-1 + 2 * cos2σₘ * cos2σₘ)));\n var λ2 = λ1 + dL;\n var α2 = Math.atan2(sinα, -x);\n return {\n lat: this.toDegrees(φ2),\n lng: this.toDegrees(λ2),\n bearing: this.wrap360(this.toDegrees(α2))\n };\n };\n /**\n * Vincenty inverse calculation.\n * based on the work of Chris Veness (https://github.com/chrisveness/geodesy)\n * source: https://github.com/chrisveness/geodesy/blob/master/latlon-ellipsoidal-vincenty.js\n *\n * @param start Latitude/longitude of starting point.\n * @param dest Latitude/longitude of destination point.\n * @return Object including distance, initialBearing, finalBearing.\n */\n GeodesicCore.prototype.inverse = function (start, dest, maxInterations, mitigateConvergenceError) {\n if (maxInterations === void 0) { maxInterations = 100; }\n if (mitigateConvergenceError === void 0) { mitigateConvergenceError = true; }\n var p1 = start, p2 = dest;\n var φ1 = this.toRadians(p1.lat), λ1 = this.toRadians(p1.lng);\n var φ2 = this.toRadians(p2.lat), λ2 = this.toRadians(p2.lng);\n var π = Math.PI;\n var ε = Number.EPSILON;\n // allow alternative ellipsoid to be specified\n var _a = this.ellipsoid, a = _a.a, b = _a.b, f = _a.f;\n var dL = λ2 - λ1; // L = difference in longitude, U = reduced latitude, defined by tan U = (1-f)·tanφ.\n var tanU1 = (1 - f) * Math.tan(φ1), cosU1 = 1 / Math.sqrt(1 + tanU1 * tanU1), sinU1 = tanU1 * cosU1;\n var tanU2 = (1 - f) * Math.tan(φ2), cosU2 = 1 / Math.sqrt(1 + tanU2 * tanU2), sinU2 = tanU2 * cosU2;\n var antipodal = Math.abs(dL) > π / 2 || Math.abs(φ2 - φ1) > π / 2;\n var λ = dL, sinλ = null, cosλ = null; // λ = difference in longitude on an auxiliary sphere\n var σ = antipodal ? π : 0, sinσ = 0, cosσ = antipodal ? -1 : 1, sinSqσ = null; // σ = angular distance P₁ P₂ on the sphere\n var cos2σₘ = 1; // σₘ = angular distance on the sphere from the equator to the midpoint of the line\n var sinα = null, cosSqα = 1; // α = azimuth of the geodesic at the equator\n var C = null;\n var λʹ = null, iterations = 0;\n do {\n sinλ = Math.sin(λ);\n cosλ = Math.cos(λ);\n sinSqσ =\n cosU2 * sinλ * (cosU2 * sinλ) +\n (cosU1 * sinU2 - sinU1 * cosU2 * cosλ) * (cosU1 * sinU2 - sinU1 * cosU2 * cosλ);\n if (Math.abs(sinSqσ) < ε) {\n break; // co-incident/antipodal points (falls back on λ/σ = L)\n }\n sinσ = Math.sqrt(sinSqσ);\n cosσ = sinU1 * sinU2 + cosU1 * cosU2 * cosλ;\n σ = Math.atan2(sinσ, cosσ);\n sinα = (cosU1 * cosU2 * sinλ) / sinσ;\n cosSqα = 1 - sinα * sinα;\n cos2σₘ = cosSqα !== 0 ? cosσ - (2 * sinU1 * sinU2) / cosSqα : 0; // on equatorial line cos²α = 0 (§6)\n C = (f / 16) * cosSqα * (4 + f * (4 - 3 * cosSqα));\n λʹ = λ;\n λ = dL + (1 - C) * f * sinα * (σ + C * sinσ * (cos2σₘ + C * cosσ * (-1 + 2 * cos2σₘ * cos2σₘ)));\n var iterationCheck = antipodal ? Math.abs(λ) - π : Math.abs(λ);\n if (iterationCheck > π) {\n throw new EvalError(\"λ > π\");\n }\n } while (Math.abs(λ - λʹ) > 1e-12 && ++iterations < maxInterations);\n if (iterations >= maxInterations) {\n if (mitigateConvergenceError) {\n return this.inverse(start, new L.LatLng(dest.lat, dest.lng - 0.01), maxInterations, mitigateConvergenceError);\n }\n else {\n throw new EvalError(\"Inverse vincenty formula failed to converge after \".concat(maxInterations, \" iterations \\n (start=\").concat(start.lat, \"/\").concat(start.lng, \"; dest=\").concat(dest.lat, \"/\").concat(dest.lng, \")\"));\n }\n }\n var uSq = (cosSqα * (a * a - b * b)) / (b * b);\n var A = 1 + (uSq / 16384) * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));\n var B = (uSq / 1024) * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));\n var Δσ = B *\n sinσ *\n (cos2σₘ +\n (B / 4) *\n (cosσ * (-1 + 2 * cos2σₘ * cos2σₘ) -\n (B / 6) * cos2σₘ * (-3 + 4 * sinσ * sinσ) * (-3 + 4 * cos2σₘ * cos2σₘ)));\n var s = b * A * (σ - Δσ); // s = length of the geodesic\n // note special handling of exactly antipodal points where sin²σ = 0 (due to discontinuity\n // atan2(0, 0) = 0 but atan2(this.ε, 0) = π/2 / 90°) - in which case bearing is always meridional,\n // due north (or due south!)\n // α = azimuths of the geodesic; α2 the direction P₁ P₂ produced\n var α1 = Math.abs(sinSqσ) < ε ? 0 : Math.atan2(cosU2 * sinλ, cosU1 * sinU2 - sinU1 * cosU2 * cosλ);\n var α2 = Math.abs(sinSqσ) < ε ? π : Math.atan2(cosU1 * sinλ, -sinU1 * cosU2 + cosU1 * sinU2 * cosλ);\n return {\n distance: s,\n initialBearing: Math.abs(s) < ε ? NaN : this.wrap360(this.toDegrees(α1)),\n finalBearing: Math.abs(s) < ε ? NaN : this.wrap360(this.toDegrees(α2))\n };\n };\n /**\n * Returns the point of intersection of two paths defined by position and bearing.\n * This calculation uses a spherical model of the earth. This will lead to small errors compared to an ellipsiod model.\n * based on the work of Chris Veness (https://github.com/chrisveness/geodesy)\n * source: https://github.com/chrisveness/geodesy/blob/master/latlon-spherical.js\n *\n * @param firstPos 1st path: position and bearing\n * @param firstBearing\n * @param secondPos 2nd path: position and bearing\n * @param secondBearing\n */\n GeodesicCore.prototype.intersection = function (firstPos, firstBearing, secondPos, secondBearing) {\n var φ1 = this.toRadians(firstPos.lat);\n var λ1 = this.toRadians(firstPos.lng);\n var φ2 = this.toRadians(secondPos.lat);\n var λ2 = this.toRadians(secondPos.lng);\n var θ13 = this.toRadians(firstBearing);\n var θ23 = this.toRadians(secondBearing);\n var Δφ = φ2 - φ1, Δλ = λ2 - λ1;\n var π = Math.PI;\n var ε = Number.EPSILON;\n // angular distance p1-p2\n var δ12 = 2 *\n Math.asin(Math.sqrt(Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +\n Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)));\n if (Math.abs(δ12) < ε) {\n return firstPos; // coincident points\n }\n // initial/final bearings between points\n var cosθa = (Math.sin(φ2) - Math.sin(φ1) * Math.cos(δ12)) / (Math.sin(δ12) * Math.cos(φ1));\n var cosθb = (Math.sin(φ1) - Math.sin(φ2) * Math.cos(δ12)) / (Math.sin(δ12) * Math.cos(φ2));\n var θa = Math.acos(Math.min(Math.max(cosθa, -1), 1)); // protect against rounding errors\n var θb = Math.acos(Math.min(Math.max(cosθb, -1), 1)); // protect against rounding errors\n var θ12 = Math.sin(λ2 - λ1) > 0 ? θa : 2 * π - θa;\n var θ21 = Math.sin(λ2 - λ1) > 0 ? 2 * π - θb : θb;\n var α1 = θ13 - θ12; // angle 2-1-3\n var α2 = θ21 - θ23; // angle 1-2-3\n if (Math.sin(α1) === 0 && Math.sin(α2) === 0) {\n return null; // infinite intersections\n }\n if (Math.sin(α1) * Math.sin(α2) < 0) {\n return null; // ambiguous intersection (antipodal?)\n }\n var cosα3 = -Math.cos(α1) * Math.cos(α2) + Math.sin(α1) * Math.sin(α2) * Math.cos(δ12);\n var δ13 = Math.atan2(Math.sin(δ12) * Math.sin(α1) * Math.sin(α2), Math.cos(α2) + Math.cos(α1) * cosα3);\n var φ3 = Math.asin(Math.min(Math.max(Math.sin(φ1) * Math.cos(δ13) + Math.cos(φ1) * Math.sin(δ13) * Math.cos(θ13), -1), 1));\n var Δλ13 = Math.atan2(Math.sin(θ13) * Math.sin(δ13) * Math.cos(φ1), Math.cos(δ13) - Math.sin(φ1) * Math.sin(φ3));\n var λ3 = λ1 + Δλ13;\n return new L.LatLng(this.toDegrees(φ3), this.toDegrees(λ3));\n };\n GeodesicCore.prototype.midpoint = function (start, dest) {\n // φm = atan2( sinφ1 + sinφ2, √( (cosφ1 + cosφ2⋅cosΔλ)² + cos²φ2⋅sin²Δλ ) )\n // λm = λ1 + atan2(cosφ2⋅sinΔλ, cosφ1 + cosφ2⋅cosΔλ)\n // midpoint is sum of vectors to two points: mathforum.org/library/drmath/view/51822.html\n var φ1 = this.toRadians(start.lat);\n var λ1 = this.toRadians(start.lng);\n var φ2 = this.toRadians(dest.lat);\n var Δλ = this.toRadians(dest.lng - start.lng);\n // get cartesian coordinates for the two points\n var A = { x: Math.cos(φ1), y: 0, z: Math.sin(φ1) }; // place point A on prime meridian y=0\n var B = { x: Math.cos(φ2) * Math.cos(Δλ), y: Math.cos(φ2) * Math.sin(Δλ), z: Math.sin(φ2) };\n // vector to midpoint is sum of vectors to two points (no need to normalise)\n var C = { x: A.x + B.x, y: A.y + B.y, z: A.z + B.z };\n var φm = Math.atan2(C.z, Math.sqrt(C.x * C.x + C.y * C.y));\n var λm = λ1 + Math.atan2(C.y, C.x);\n return new L.LatLng(this.toDegrees(φm), this.toDegrees(λm));\n };\n return GeodesicCore;\n}());\n\nvar GeodesicGeometry = /** @class */ (function () {\n function GeodesicGeometry(options) {\n var _a;\n this.geodesic = new GeodesicCore();\n this.steps = (_a = options === null || options === void 0 ? void 0 : options.steps) !== null && _a !== void 0 ? _a : 3;\n }\n /**\n * A geodesic line between `start` and `dest` is created with this recursive function.\n * It calculates the geodesic midpoint between `start` and `dest` and uses this midpoint to call itself again (twice!).\n * The results are then merged into one continuous linestring.\n *\n * The number of resulting vertices (incl. `start` and `dest`) depends on the initial value for `iterations`\n * and can be calculated with: vertices == 1 + 2 ** (initialIterations + 1)\n *\n * As this is an exponential function, be extra careful to limit the initial value for `iterations` (8 results in 513 vertices).\n *\n * @param start start position\n * @param dest destination\n * @param iterations\n * @return resulting linestring\n */\n GeodesicGeometry.prototype.recursiveMidpoint = function (start, dest, iterations) {\n var geom = [start, dest];\n var midpoint = this.geodesic.midpoint(start, dest);\n if (iterations > 0) {\n geom.splice.apply(geom, __spreadArray([0, 1], this.recursiveMidpoint(start, midpoint, iterations - 1), false));\n geom.splice.apply(geom, __spreadArray([geom.length - 2, 2], this.recursiveMidpoint(midpoint, dest, iterations - 1), false));\n }\n else {\n geom.splice(1, 0, midpoint);\n }\n return geom;\n };\n /**\n * This is the wrapper-function to generate a geodesic line. It's just for future backwards-compatibility\n * if there is another algorithm used to create the actual line.\n *\n * The `steps`-property is used to define the number of resulting vertices of the linestring: vertices == 1 + 2 ** (steps + 1)\n * The value for `steps` is currently limited to 8 (513 vertices) for performance reasons until another algorithm is found.\n *\n * @param start start position\n * @param dest destination\n * @return resulting linestring\n */\n GeodesicGeometry.prototype.line = function (start, dest) {\n return this.recursiveMidpoint(start, dest, Math.min(8, this.steps));\n };\n GeodesicGeometry.prototype.multiLineString = function (latlngs) {\n var multiLineString = [];\n for (var _i = 0, latlngs_1 = latlngs; _i < latlngs_1.length; _i++) {\n var linestring = latlngs_1[_i];\n var segment = [];\n for (var j = 1; j < linestring.length; j++) {\n segment.splice.apply(segment, __spreadArray([segment.length - 1, 1], this.line(linestring[j - 1], linestring[j]), false));\n }\n multiLineString.push(segment);\n }\n return multiLineString;\n };\n GeodesicGeometry.prototype.lineString = function (latlngs) {\n return this.multiLineString([latlngs])[0];\n };\n /**\n *\n * Is much (10x) faster than the previous implementation:\n *\n * ```\n * Benchmark (no split): splitLine x 459,044 ops/sec ±0.53% (95 runs sampled)\n * Benchmark (split): splitLine x 42,999 ops/sec ±0.51% (97 runs sampled)\n * ```\n *\n * @param startPosition\n * @param destPosition\n */\n GeodesicGeometry.prototype.splitLine = function (startPosition, destPosition) {\n var antimeridianWest = {\n point: new L.LatLng(89.9, -180.0000001),\n bearing: 180\n };\n var antimeridianEast = {\n point: new L.LatLng(89.9, 180.0000001),\n bearing: 180\n };\n // make a copy to work with\n var start = new L.LatLng(startPosition.lat, startPosition.lng, startPosition.alt);\n var dest = new L.LatLng(destPosition.lat, destPosition.lng, destPosition.alt);\n start.lng = this.geodesic.wrap(start.lng, 360);\n dest.lng = this.geodesic.wrap(dest.lng, 360);\n if (dest.lng - start.lng > 180) {\n dest.lng = dest.lng - 360;\n }\n else if (dest.lng - start.lng < -180) {\n dest.lng = dest.lng + 360;\n }\n var result = [\n [\n new L.LatLng(start.lat, this.geodesic.wrap(start.lng, 180), start.alt),\n new L.LatLng(dest.lat, this.geodesic.wrap(dest.lng, 180), dest.alt)\n ]\n ];\n // crossing antimeridian from \"this\" side?\n if (start.lng >= -180 && start.lng <= 180) {\n // crossing the \"western\" antimeridian\n if (dest.lng < -180) {\n var bearing = this.geodesic.inverse(start, dest).initialBearing;\n var intersection = this.geodesic.intersection(start, bearing, antimeridianWest.point, antimeridianWest.bearing);\n if (intersection) {\n result = [\n [start, intersection],\n [\n new L.LatLng(intersection.lat, intersection.lng + 360),\n new L.LatLng(dest.lat, dest.lng + 360, dest.alt)\n ]\n ];\n }\n }\n // crossing the \"eastern\" antimeridian\n else if (dest.lng > 180) {\n var bearing = this.geodesic.inverse(start, dest).initialBearing;\n var intersection = this.geodesic.intersection(start, bearing, antimeridianEast.point, antimeridianEast.bearing);\n if (intersection) {\n result = [\n [start, intersection],\n [\n new L.LatLng(intersection.lat, intersection.lng - 360),\n new L.LatLng(dest.lat, dest.lng - 360, dest.alt)\n ]\n ];\n }\n }\n }\n // coming back over the antimeridian from the \"other\" side?\n else if (dest.lng >= -180 && dest.lng <= 180) {\n // crossing the \"western\" antimeridian\n if (start.lng < -180) {\n var bearing = this.geodesic.inverse(start, dest).initialBearing;\n var intersection = this.geodesic.intersection(start, bearing, antimeridianWest.point, antimeridianWest.bearing);\n if (intersection) {\n result = [\n [\n new L.LatLng(start.lat, start.lng + 360, start.alt),\n new L.LatLng(intersection.lat, intersection.lng + 360)\n ],\n [intersection, dest]\n ];\n }\n }\n // crossing the \"eastern\" antimeridian\n else if (start.lng > 180) {\n var bearing = this.geodesic.inverse(start, dest).initialBearing;\n var intersection = this.geodesic.intersection(start, bearing, antimeridianWest.point, antimeridianWest.bearing);\n if (intersection) {\n result = [\n [\n new L.LatLng(start.lat, start.lng - 360, start.alt),\n new L.LatLng(intersection.lat, intersection.lng - 360)\n ],\n [intersection, dest]\n ];\n }\n }\n }\n return result;\n };\n /**\n * Linestrings of a given multilinestring that cross the antimeridian will be split in two separate linestrings.\n * This function is used to wrap lines around when they cross the antimeridian\n * It iterates over all linestrings and reconstructs the step-by-step if no split is needed.\n * In case the line was split, the linestring ends at the antimeridian and a new linestring is created for the\n * remaining points of the original linestring.\n *\n * @param multilinestring\n * @return another multilinestring where segments crossing the antimeridian are split\n */\n GeodesicGeometry.prototype.splitMultiLineString = function (multilinestring) {\n var result = [];\n for (var _i = 0, multilinestring_1 = multilinestring; _i < multilinestring_1.length; _i++) {\n var linestring = multilinestring_1[_i];\n if (linestring.length === 1) {\n result.push(linestring); // just a single point in linestring, no need to split\n continue;\n }\n var segment = [];\n for (var j = 1; j < linestring.length; j++) {\n var split = this.splitLine(linestring[j - 1], linestring[j]);\n segment.pop();\n segment = segment.concat(split[0]);\n if (split.length > 1) {\n result.push(segment); // the line was split, so we end the linestring right here\n segment = split[1]; // begin the new linestring with the second part of the split line\n }\n }\n result.push(segment);\n }\n return result;\n };\n /**\n * Linestrings of a given multilinestring will be wrapped (+- 360°) to show a continuous line w/o any weird discontinuities\n * when `wrap` is set to `false` in the geodesic class\n * @param multilinestring\n * @returns another multilinestring where the points of each linestring are wrapped accordingly\n */\n GeodesicGeometry.prototype.wrapMultiLineString = function (multilinestring) {\n var result = [];\n for (var _i = 0, multilinestring_2 = multilinestring; _i < multilinestring_2.length; _i++) {\n var linestring = multilinestring_2[_i];\n var resultLine = [];\n var previous = null;\n // iterate over every point and check if it needs to be wrapped\n for (var _a = 0, linestring_1 = linestring; _a < linestring_1.length; _a++) {\n var point = linestring_1[_a];\n if (previous === null) {\n // the first point is the anchor of the linestring from which the line will always start (w/o any wrapping applied)\n resultLine.push(new L.LatLng(point.lat, point.lng));\n previous = new L.LatLng(point.lat, point.lng);\n }\n else {\n // I prefer clearly defined branches over a continue-operation.\n // if the difference between the current and *previous* point is greater than 360°, the current point needs to be shifted\n // to be on the same 'sphere' as the previous one.\n var offset = Math.round((point.lng - previous.lng) / 360);\n // shift the point accordingly and add to the result\n resultLine.push(new L.LatLng(point.lat, point.lng - offset * 360));\n // use the wrapped point as the anchor for the next one\n previous = new L.LatLng(point.lat, point.lng - offset * 360); // Need a new object here, to avoid changing the input data\n }\n }\n result.push(resultLine);\n }\n return result;\n };\n /**\n * Creates a circular (constant radius), closed (1st pos == last pos) geodesic linestring.\n * The number of vertices is calculated with: `vertices == steps + 1` (where 1st == last)\n *\n * @param center\n * @param radius\n * @return resulting linestring\n */\n GeodesicGeometry.prototype.circle = function (center, radius) {\n var vertices = [];\n for (var i = 0; i < this.steps; i++) {\n var point = this.geodesic.direct(center, (360 / this.steps) * i, radius);\n vertices.push(new L.LatLng(point.lat, point.lng));\n }\n // append first vertice to the end to close the linestring\n vertices.push(new L.LatLng(vertices[0].lat, vertices[0].lng));\n return vertices;\n };\n /**\n * Handles splitting of circles at the antimeridian.\n * @param linestring a linestring that resembles the geodesic circle\n * @return a multilinestring that consist of one or two linestrings\n */\n GeodesicGeometry.prototype.splitCircle = function (linestring) {\n var result = this.splitMultiLineString([linestring]);\n // If the circle was split, it results in exactly three linestrings where first and last\n // must be re-assembled because they belong to the same \"side\" of the split circle.\n if (result.length === 3) {\n result[2] = __spreadArray(__spreadArray([], result[2], true), result[0], true);\n result.shift();\n }\n return result;\n };\n /**\n * Calculates the distance between two positions on the earths surface\n * @param start 1st position\n * @param dest 2nd position\n * @return the distance in **meters**\n */\n GeodesicGeometry.prototype.distance = function (start, dest) {\n return this.geodesic.inverse(new L.LatLng(start.lat, this.geodesic.wrap(start.lng, 180)), new L.LatLng(dest.lat, this.geodesic.wrap(dest.lng, 180))).distance;\n };\n GeodesicGeometry.prototype.multilineDistance = function (multilinestring) {\n var dist = [];\n for (var _i = 0, multilinestring_3 = multilinestring; _i < multilinestring_3.length; _i++) {\n var linestring = multilinestring_3[_i];\n var segmentDistance = 0;\n for (var j = 1; j < linestring.length; j++) {\n segmentDistance += this.distance(linestring[j - 1], linestring[j]);\n }\n dist.push(segmentDistance);\n }\n return dist;\n };\n GeodesicGeometry.prototype.updateStatistics = function (points, vertices) {\n var stats = { distanceArray: [], totalDistance: 0, points: 0, vertices: 0 };\n stats.distanceArray = this.multilineDistance(points);\n stats.totalDistance = stats.distanceArray.reduce(function (x, y) { return x + y; }, 0);\n stats.points = 0;\n for (var _i = 0, points_1 = points; _i < points_1.length; _i++) {\n var item = points_1[_i];\n stats.points += item.reduce(function (x) { return x + 1; }, 0);\n }\n stats.vertices = 0;\n for (var _a = 0, vertices_1 = vertices; _a < vertices_1.length; _a++) {\n var item = vertices_1[_a];\n stats.vertices += item.reduce(function (x) { return x + 1; }, 0);\n }\n return stats;\n };\n return GeodesicGeometry;\n}());\n\nfunction instanceOfLatLngLiteral(object) {\n return ((typeof object === \"object\")\n && (object !== null)\n && (\"lat\" in object)\n && (\"lng\" in object)\n && (typeof object.lat === \"number\")\n && (typeof object.lng === \"number\"));\n}\nfunction instanceOfLatLngTuple(object) {\n return ((object instanceof Array)\n && (typeof object[0] === \"number\")\n && (typeof object[1] === \"number\"));\n}\nfunction instanceOfLatLngExpression(object) {\n return object instanceof L.LatLng || instanceOfLatLngTuple(object) || instanceOfLatLngLiteral(object);\n}\nfunction latlngExpressiontoLatLng(input) {\n if (input instanceof L.LatLng) {\n return input;\n }\n else if (instanceOfLatLngTuple(input)) {\n return new L.LatLng(input[0], input[1], input.at(2)); // alt is optional\n }\n else if (instanceOfLatLngLiteral(input)) {\n return new L.LatLng(input.lat, input.lng, input.alt);\n }\n throw new Error(\"L.LatLngExpression expected. Unknown object found.\");\n}\nfunction latlngExpressionArraytoLatLngArray(input) {\n var latlng = [];\n var iterateOver = instanceOfLatLngExpression(input[0]) ? [input] : input;\n var unknownObjectError = new Error(\"L.LatLngExpression[] | L.LatLngExpression[][] expected. Unknown object found.\");\n if (!(iterateOver instanceof Array)) {\n throw unknownObjectError;\n }\n for (var _i = 0, _a = iterateOver; _i < _a.length; _i++) {\n var group = _a[_i];\n if (!(group instanceof Array)) {\n throw unknownObjectError;\n }\n var sub = [];\n for (var _b = 0, group_1 = group; _b < group_1.length; _b++) {\n var point = group_1[_b];\n if (!instanceOfLatLngExpression(point)) {\n throw unknownObjectError;\n }\n sub.push(latlngExpressiontoLatLng(point));\n }\n latlng.push(sub);\n }\n return latlng;\n}\n\n/**\n * Draw geodesic lines based on L.Polyline\n */\nvar GeodesicLine = /** @class */ (function (_super) {\n __extends(GeodesicLine, _super);\n function GeodesicLine(latlngs, options) {\n var _this = _super.call(this, [], options) || this;\n /** these should be good for most use-cases */\n _this.defaultOptions = { wrap: true, steps: 3 };\n /** use this if you need some detailled info about the current geometry */\n _this.statistics = { distanceArray: [], totalDistance: 0, points: 0, vertices: 0 };\n /** stores all positions that are used to create the geodesic line */\n _this.points = [];\n L.Util.setOptions(_this, __assign(__assign({}, _this.defaultOptions), options));\n _this.geom = new GeodesicGeometry(_this.options);\n if (latlngs !== undefined) {\n _this.setLatLngs(latlngs);\n }\n return _this;\n }\n /** calculates the geodesics and update the polyline-object accordingly */\n GeodesicLine.prototype.updateGeometry = function () {\n var geodesic = [];\n geodesic = this.geom.multiLineString(this.points);\n this.statistics = this.geom.updateStatistics(this.points, geodesic);\n if (this.options.wrap) {\n var split = this.geom.splitMultiLineString(geodesic);\n _super.prototype.setLatLngs.call(this, split);\n }\n else {\n _super.prototype.setLatLngs.call(this, this.geom.wrapMultiLineString(geodesic));\n }\n };\n /**\n * overwrites the original function with additional functionality to create a geodesic line\n * @param latlngs an array (or 2d-array) of positions\n */\n GeodesicLine.prototype.setLatLngs = function (latlngs) {\n this.points = latlngExpressionArraytoLatLngArray(latlngs);\n this.updateGeometry();\n return this;\n };\n /**\n * add a given point to the geodesic line object\n * @param latlng point to add. The point will always be added to the last linestring of a multiline\n * @param latlngs define a linestring to add the new point to. Read from points-property before (e.g. `line.addLatLng(Beijing, line.points[0]);`)\n */\n GeodesicLine.prototype.addLatLng = function (latlng, latlngs) {\n var point = latlngExpressiontoLatLng(latlng);\n if (this.points.length === 0) {\n this.points.push([point]);\n }\n else if (latlngs === undefined) {\n this.points[this.points.length - 1].push(point);\n }\n else {\n latlngs.push(point);\n }\n this.updateGeometry();\n return this;\n };\n /**\n * Creates geodesic lines from a given GeoJSON-Object.\n * @param input GeoJSON-Object\n */\n GeodesicLine.prototype.fromGeoJson = function (input) {\n var latlngs = [];\n var features = [];\n if (input.type === \"FeatureCollection\") {\n features = input.features;\n }\n else if (input.type === \"Feature\") {\n features = [input];\n }\n else if ([\"MultiPoint\", \"LineString\", \"MultiLineString\", \"Polygon\", \"MultiPolygon\"].includes(input.type)) {\n features = [\n {\n type: \"Feature\",\n geometry: input,\n properties: {}\n }\n ];\n }\n else {\n console.log(\"[Leaflet.Geodesic] fromGeoJson() - Type \\\"\".concat(input.type, \"\\\" not supported.\"));\n }\n features.forEach(function (feature) {\n switch (feature.geometry.type) {\n case \"MultiPoint\":\n case \"LineString\":\n latlngs = __spreadArray(__spreadArray([], latlngs, true), [L.GeoJSON.coordsToLatLngs(feature.geometry.coordinates, 0)], false);\n break;\n case \"MultiLineString\":\n case \"Polygon\":\n latlngs = __spreadArray(__spreadArray([], latlngs, true), L.GeoJSON.coordsToLatLngs(feature.geometry.coordinates, 1), true);\n break;\n case \"MultiPolygon\":\n feature.geometry.coordinates.forEach(function (item) {\n latlngs = __spreadArray(__spreadArray([], latlngs, true), L.GeoJSON.coordsToLatLngs(item, 1), true);\n });\n break;\n default:\n console.log(\"[Leaflet.Geodesic] fromGeoJson() - Type \\\"\".concat(feature.geometry.type, \"\\\" not supported.\"));\n }\n });\n if (latlngs.length) {\n this.setLatLngs(latlngs);\n }\n return this;\n };\n /**\n * Calculates the distance between two geo-positions\n * @param start 1st position\n * @param dest 2nd position\n * @return the distance in meters\n */\n GeodesicLine.prototype.distance = function (start, dest) {\n return this.geom.distance(latlngExpressiontoLatLng(start), latlngExpressiontoLatLng(dest));\n };\n return GeodesicLine;\n}(L.Polyline));\n\n/**\n * Can be used to create a geodesic circle based on L.Polyline\n */\nvar GeodesicCircleClass = /** @class */ (function (_super) {\n __extends(GeodesicCircleClass, _super);\n function GeodesicCircleClass(center, options) {\n var _a;\n var _this = _super.call(this, [], options) || this;\n _this.defaultOptions = { wrap: true, steps: 24, fill: true, noClip: true };\n _this.statistics = { distanceArray: [], totalDistance: 0, points: 0, vertices: 0 };\n L.Util.setOptions(_this, __assign(__assign({}, _this.defaultOptions), options));\n // merge/set options\n var extendedOptions = _this.options;\n _this.radius = (_a = extendedOptions.radius) !== null && _a !== void 0 ? _a : 1000 * 1000;\n _this.center = center === undefined ? new L.LatLng(0, 0) : latlngExpressiontoLatLng(center);\n _this.geom = new GeodesicGeometry(_this.options);\n // update the geometry\n _this.update();\n return _this;\n }\n /**\n * Updates the geometry and re-calculates some statistics\n */\n GeodesicCircleClass.prototype.update = function () {\n var circle = this.geom.circle(this.center, this.radius);\n this.statistics = this.geom.updateStatistics([[this.center]], [circle]);\n // circumfence must be re-calculated from geodesic\n this.statistics.totalDistance = this.geom.multilineDistance([circle]).reduce(function (x, y) { return x + y; }, 0);\n if (this.options.wrap) {\n var split = this.geom.splitCircle(circle);\n _super.prototype.setLatLngs.call(this, split);\n }\n else {\n _super.prototype.setLatLngs.call(this, circle);\n }\n };\n /**\n * Calculate the distance between the current center and an arbitrary position.\n * @param latlng geo-position to calculate distance to\n * @return distance in meters\n */\n GeodesicCircleClass.prototype.distanceTo = function (latlng) {\n var dest = latlngExpressiontoLatLng(latlng);\n return this.geom.distance(this.center, dest);\n };\n /**\n * Set a new center for the geodesic circle and update the geometry. Radius may also be set.\n * @param center the new center\n * @param radius the new radius\n */\n GeodesicCircleClass.prototype.setLatLng = function (center, radius) {\n this.center = latlngExpressiontoLatLng(center);\n this.radius = radius !== null && radius !== void 0 ? radius : this.radius;\n this.update();\n };\n /**\n * Set a new radius for the geodesic circle and update the geometry. Center may also be set.\n * @param radius the new radius\n * @param center the new center\n */\n GeodesicCircleClass.prototype.setRadius = function (radius, center) {\n this.radius = radius;\n this.center = center ? latlngExpressiontoLatLng(center) : this.center;\n this.update();\n };\n return GeodesicCircleClass;\n}(L.Polyline));\n\nif (typeof window.L !== \"undefined\") {\n window.L.Geodesic = GeodesicLine;\n window.L.geodesic = function () {\n var args = [];\n for (var _i = 0; _i < arguments.length; _i++) {\n args[_i] = arguments[_i];\n }\n return new (GeodesicLine.bind.apply(GeodesicLine, __spreadArray([void 0], args, false)))();\n };\n window.L.GeodesicCircle = GeodesicCircleClass;\n window.L.geodesiccircle = function () {\n var args = [];\n for (var _i = 0; _i < arguments.length; _i++) {\n args[_i] = arguments[_i];\n }\n return new (GeodesicCircleClass.bind.apply(GeodesicCircleClass, __spreadArray([void 0], args, false)))();\n };\n}\n\nexport { GeodesicCircleClass, GeodesicLine };\n","import trkData from \"./data.js\";\r\nimport state from \"./state.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport log from \"./log.js\";\r\nimport strings from \"./strings.js\";\r\nimport { addItemToMap, removeItemFromMap } from \"./map-items.js\";\r\nimport { startChoosingMapLocation, stopChoosingMapLocation } from \"./map-chooselocation.js\";\r\nimport { createMarkerPath } from \"./marker-path.js\";\r\nimport { convertToLatLngPreference, convertFromMetresToUserDistancePreference } from \"./preferences.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport $j from \"jquery\";\r\nimport L from \"leaflet\";\r\nimport { el, mount, text, setChildren } from \"redom\";\r\n\r\nimport { GeodesicLine } from \"leaflet.geodesic/dist/leaflet.geodesic.esm.js\";\r\n\r\nexport function openRuler() {\r\n\t$j(\"#map-ruler\").addClass(\"active\");\r\n\tif (trkData.ruler.isOpen) {\r\n\t\treturn;\r\n\t}\r\n\ttrkData.ruler.isOpen = true;\r\n\tstartChoosingMapLocation(state.mapClickHandlers.RULER);\r\n\ttrkData.ruler.points = [];\r\n\tvar dialog = $j(domNodes.infoDialogs.ruler);\r\n\tdialog.dialog(\"open\");\r\n\t$j(\"#rulerPoints\").find(\"li\").remove();\r\n}\r\n\r\nexport function closeRuler() {\r\n\t$j(\"#map-ruler\").removeClass(\"active\");\r\n\tif (!trkData.ruler.isOpen) {\r\n\t\treturn;\r\n\t}\r\n\ttrkData.ruler.isOpen = false;\r\n\tstopChoosingMapLocation(state.mapClickHandlers.RULER);\r\n\tfor (var i = 0; i < trkData.ruler.points.length; i++) {\r\n\t\tremoveItemFromMap(trkData.ruler.points[i]);\r\n\t}\r\n\ttrkData.ruler.points = [];\r\n\tif (trkData.ruler.polygon != null) {\r\n\t\tremoveItemFromMap(trkData.ruler.polygon);\r\n\t}\r\n\ttrkData.ruler.polygon = null;\r\n\r\n\t$j(\"#rulerPoints\").find(\"li\").remove();\r\n\t$j(\"#rulerTotalDistance\").text(\"\");\r\n}\r\n\r\nexport function addPointToRuler(latlng) {\r\n\tif (!trkData.ruler.isOpen) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar rulerTitle = String.fromCharCode(\"a\".charCodeAt(0) + trkData.ruler.points.length).toUpperCase();\r\n\tvar pointIcon = L.icon({\r\n\t\ticonUrl: createMarkerPath(\"Generic\", \"gold\", null, null, null, false),\r\n\t\ticonSize: [36, 36],\r\n\t\ticonAnchor: [18, 18],\r\n\t});\r\n\tvar rulerPoint = L.marker(latlng, { icon: pointIcon, draggable: true, text: rulerTitle });\r\n\r\n\ttrkData.ruler.points.push(rulerPoint);\r\n\taddItemToMap(rulerPoint);\r\n\r\n\tif (trkData.ruler.polygon == null && trkData.ruler.points.length > 1) {\r\n\t\tlog(\"add geodesic line\");\r\n\t\ttrkData.ruler.polygon = new GeodesicLine([[trkData.ruler.points[0].getLatLng(), trkData.ruler.points[1].getLatLng()]], {\r\n\t\t\tweight: 5,\r\n\t\t\tcolor: \"#cea843\",\r\n\t\t\twrap: true,\r\n\t\t\tsteps: 50,\r\n\t\t\topacity: 0.7,\r\n\t\t});\r\n\t\taddItemToMap(trkData.ruler.polygon);\r\n\t}\r\n\r\n\trulerPoint.on(\"drag\", function (e) {\r\n\t\tupdateRuler();\r\n\t});\r\n\r\n\t// add point to ruler dialog\r\n\tvar point = el(\"li\", [\r\n\t\ttext(rulerTitle + \" - \" + convertToLatLngPreference(rulerPoint.getLatLng().lat, rulerPoint.getLatLng().lng) + \" \"),\r\n\t\tel(\"a.delete\", { href: \"#\" }, strings.DELETE),\r\n\t]);\r\n\tmount(document.getElementById(\"rulerPoints\"), point);\r\n\tupdateRuler();\r\n}\r\n\r\nexport function removeRulerPoint(index) {\r\n\tif (!trkData.ruler.isOpen) {\r\n\t\treturn;\r\n\t}\r\n\tif (trkData.ruler.points.length === 0) {\r\n\t\treturn;\r\n\t}\r\n\tif (trkData.ruler.polygon == null) {\r\n\t\treturn;\r\n\t}\r\n\tremoveItemFromMap(trkData.ruler.points[index]);\r\n\tvar removed = trkData.ruler.points.splice(index, 1);\r\n\t$j(\"#rulerPoints li\").eq(index).remove();\r\n\tupdateRuler();\r\n}\r\n\r\nfunction updateRuler() {\r\n\tif (!trkData.ruler.isOpen) {\r\n\t\treturn;\r\n\t}\r\n\tif (trkData.ruler.polygon == null) {\r\n\t\treturn;\r\n\t}\r\n\tif (trkData.ruler.points.length <= 1) {\r\n\t\tremoveItemFromMap(trkData.ruler.polygon);\r\n\t\t$(\"#rulerTotalDistance\").text(\"\");\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar points = [];\r\n\tvar rulerPointsContainer = document.getElementById(\"rulerPoints\");\r\n\tvar totalDistance = 0;\r\n\tvar rulerPoints = [];\r\n\tfor (var i = 0; i < trkData.ruler.points.length; i++) {\r\n\t\tvar point = trkData.ruler.points[i].getLatLng().wrap();\r\n\t\ttrkData.ruler.points[i].setLatLng(point);\r\n\t\tvar pointDistance = null;\r\n\t\tpoints.push(point);\r\n\t\tvar rulerTitle = String.fromCharCode(\"a\".charCodeAt(0) + i).toUpperCase();\r\n\t\tif (i > 0) {\r\n\t\t\tpointDistance = trkData.ruler.points[i - 1].getLatLng().distanceTo(point);\r\n\t\t\ttotalDistance += pointDistance;\r\n\t\t}\r\n\t\tvar title = rulerTitle + \" - \" + convertToLatLngPreference(point.lat, point.lng) + \" \";\r\n\t\tif (pointDistance != null) {\r\n\t\t\ttitle += \"(\" + convertFromMetresToUserDistancePreference(pointDistance) + \") \";\r\n\t\t}\r\n\t\trulerPoints.push(el(\"li\", [title, el(\"a.delete\", { href: \"#\" }, strings.DELETE)]));\r\n\t}\r\n\tsetChildren(rulerPointsContainer, rulerPoints);\r\n\ttrkData.ruler.polygon.setLatLngs([points]);\r\n\taddItemToMap(trkData.ruler.polygon);\r\n\r\n\t// update total distance\r\n\tif (trkData.ruler.points.length > 1) {\r\n\t\tdocument.getElementById(\"rulerTotalDistance\").textContent =\r\n\t\t\tconvertFromMetresToUserDistancePreference(totalDistance);\r\n\t}\r\n}\r\n","export const intervals = {\r\n\thistory: null,\r\n\tpositionStatus: null,\r\n\tplaybackInterval: null,\r\n\tmapLayers: null,\r\n\tsatelliteInterval: [],\r\n\tnotification: null,\r\n\tnotificationNum: 0,\r\n\temergency: null,\r\n\tversion: null,\r\n\tsnapToRoadsInterval: null,\r\n\tcurrentTime: null,\r\n\tcleanupExpiredData: null,\r\n};\r\n\r\nexport const throttles = {\r\n\tupdateLiveAssets: null,\r\n\tqueryLatestNotifications: null,\r\n\tqueryLatestEvents: null,\r\n\tupdatePositionStatus: null,\r\n\temergencyAlert: null,\r\n};\r\n","import $ from \"jquery\";\r\nimport _ from \"lodash\";\r\n\r\n/*global JsSearch */\r\n// import JsSearch from '../js-search.js';\r\n\r\n/// TODO: convert into an ES5 `class`\r\nvar FilteredList = function (items, container, renderer, options) {\r\n\tthis.renderer = renderer;\r\n\tthis.initialized = false;\r\n\r\n\tthis.options = {\r\n\t\tpaging: { currentPage: 1, pageSize: 20 },\r\n\t\tsorting: {\r\n\t\t\tsortBy: null,\r\n\t\t\tsortDirection: null,\r\n\t\t},\r\n\t\tfiltering: {\r\n\t\t\tsearch: \"\",\r\n\t\t},\r\n\t\tgroupBy: null,\r\n\t};\r\n\r\n\tthis.domNodes = {\r\n\t\tlistContainer: container,\r\n\t\tlistItemsContainer: undefined,\r\n\t\tlist: undefined,\r\n\t\tlistItems: [],\r\n\t\tpagination: [],\r\n\t\tnoItems: undefined,\r\n\t\tfilterResults: undefined,\r\n\t\tsearchBox: undefined,\r\n\t\tfilterContainer: undefined,\r\n\t};\r\n\r\n\tthis.data = {\r\n\t\titems: [],\r\n\t};\r\n\r\n\tvar self = this;\r\n\r\n\tfunction createDataIndex() {\r\n\t\tself.search = new JsSearch.Search(\"Id\");\r\n\t\tself.search.searchIndex = new JsSearch.UnorderedSearchIndex();\r\n\t\tself.search.indexStrategy = new JsSearch.AllSubstringsIndexStrategy();\r\n\t\tself.search.addIndex(\"EventName\");\r\n\t\tself.search.addIndex(\"AssetName\");\r\n\t\tself.search.addIndex(\"Time\");\r\n\t\tself.search.addIndex(\"Details\");\r\n\t\tself.search.addIndex([\"Position\", \"Time\"]);\r\n\t\tself.search.addIndex([\"Position\", \"LatLng\"]);\r\n\t\tself.search.addIndex([\"Position\", \"Address\"]);\r\n\t\tself.search.addIndex(\"Method\");\r\n\t\tself.search.addIndex(\"Status\");\r\n\t\tself.search.addIndex(\"EventTypes\");\r\n\t\tself.search.addIndex(\"Text\");\r\n\t\tself.search.addDocuments(self.data.items);\r\n\t}\r\n\r\n\tfunction init(items, options) {\r\n\t\t$.extend(true, self.options, options);\r\n\t\tself.initialized = true;\r\n\t\tself.data.items = items;\r\n\t\tcreateDataIndex();\r\n\r\n\t\tvar container = self.domNodes.listContainer;\r\n\t\tself.domNodes.pagination = container.querySelectorAll(\".pagination\");\r\n\t\tself.domNodes.noItems = container.querySelector(\".list-none\");\r\n\t\tself.domNodes.listItemsContainer = container.querySelector(\".list-container\");\r\n\t\tself.domNodes.list = container.querySelector(\".list\");\r\n\t\tself.domNodes.filterResults = container.querySelector(\".filter-results\");\r\n\t\tself.domNodes.filterContainer = container.querySelector(\".filter-box\");\r\n\r\n\t\tself.domNodes.searchBox = container.querySelector(\".list-filter\");\r\n\r\n\t\tif (!self.domNodes.filterContainer.classList.contains(\"disabled-feature\")) {\r\n\t\t\tself.domNodes.filterContainer.classList.add(\"is-visible\");\r\n\t\t} else {\r\n\t\t\tself.domNodes.filterContainer.classList.remove(\"is-visible\");\r\n\t\t}\r\n\r\n\t\tvar $container = $(container);\r\n\t\t$container.on(\"click\", \".page-link\", function (e) {\r\n\t\t\te.preventDefault();\r\n\r\n\t\t\tself.changePage(this.getAttribute(\"data-page\"));\r\n\t\t});\r\n\r\n\t\t$(container).on(\"click\", \".panel-options-list a.dropdown-item\", function (e) {\r\n\t\t\te.preventDefault();\r\n\t\t\tswitch (this.getAttribute(\"data-option-type\")) {\r\n\t\t\t\t//case 'sorting':\r\n\t\t\t\t// changeItemSort(this.getAttribute('data-sort-group'), parseInt(this.getAttribute('data-sort')));\r\n\t\t\t\t// break;\r\n\t\t\t\tcase \"list-sort\":\r\n\t\t\t\t\t// for asset notifications, since the sorting options are outside of the list containers\r\n\t\t\t\t\tvar sortBy = this.getAttribute(\"data-sort\");\r\n\t\t\t\t\tvar isActive = this.classList.contains(\"active\");\r\n\t\t\t\t\tvar sortDirection = this.classList.contains(\"desc\") ? \"asc\" : \"desc\"; // toggle sort direction if active\r\n\t\t\t\t\tthis.classList.remove(\"desc\");\r\n\t\t\t\t\tthis.classList.remove(\"asc\");\r\n\t\t\t\t\tthis.classList.add(sortDirection);\r\n\t\t\t\t\tthis.classList.add(\"active\");\r\n\t\t\t\t\t// todo: toggle other active options in this group (when we have more than 1 option!)\r\n\t\t\t\t\tif (!isActive) {\r\n\t\t\t\t\t\tsortDirection = \"asc\";\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis.setAttribute(\"data-sort-dir\", sortDirection);\r\n\t\t\t\t\tself.sort(sortBy, sortDirection);\r\n\t\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tif (self.domNodes.searchBox !== undefined && self.domNodes.searchBox !== null) {\r\n\t\t\t$(self.domNodes.searchBox).on(\"keyup\", function (e) {\r\n\t\t\t\tvar target = e.target || e.srcElement;\r\n\t\t\t\tself.options.filtering.search = target.value;\r\n\t\t\t\tself.redraw();\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tself.redraw();\r\n\t}\r\n\r\n\tfunction isFilteringData() {\r\n\t\tvar filtering = self.options.filtering;\r\n\t\tvar isFiltering = false;\r\n\t\tif (filtering !== undefined && filtering !== null) {\r\n\t\t\t// are we applying a filter?\r\n\t\t\t// apply any pre-search filters first\r\n\r\n\t\t\tif (filtering.search !== undefined && filtering.search !== null && filtering.search !== \"\") {\r\n\t\t\t\t// apply filtering.search to filteredItems\r\n\t\t\t\tisFiltering = true;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn isFiltering;\r\n\t}\r\n\r\n\tfunction filterData() {\r\n\t\tvar filteredItems = self.data.items.slice(0); // work off a copy\r\n\t\tvar filtering = self.options.filtering;\r\n\r\n\t\t// filter\r\n\t\tvar isFiltering = isFilteringData();\r\n\t\tif (filtering !== undefined && filtering !== null) {\r\n\t\t\t// are we applying a filter?\r\n\t\t\t// apply any pre-search filters first\r\n\r\n\t\t\tif (filtering.search !== undefined && filtering.search !== null && filtering.search !== \"\") {\r\n\t\t\t\t// apply filtering.search to filteredItems\r\n\t\t\t\tfilteredItems = self.search.search(filtering.search);\r\n\t\t\t}\r\n\t\t}\r\n\t\tself.data.filtered = filteredItems;\r\n\r\n\t\t// group items\r\n\t\tif (self.options.groupBy !== undefined && self.options.groupBy !== null) {\r\n\t\t\t// TODO generic grouping somehow\r\n\t\t\tvar filteredGroups = [];\r\n\t\t\tvar filteredGroupLookup = {};\r\n\t\t\t_.each(self.data.filtered, function (item) {\r\n\t\t\t\tif (filteredGroupLookup[item[self.options.groupBy]] === undefined) {\r\n\t\t\t\t\tfilteredGroupLookup[item[self.options.groupBy]] = { Epoch: item.Epoch, AssetId: item.AssetId, Items: [] };\r\n\t\t\t\t\tfilteredGroupLookup[item[self.options.groupBy]][self.options.groupBy] = item[self.options.groupBy];\r\n\t\t\t\t\tfilteredGroups.push(filteredGroupLookup[item[self.options.groupBy]]);\r\n\t\t\t\t}\r\n\t\t\t\tfilteredGroupLookup[item[self.options.groupBy]].Items.push(item);\r\n\t\t\t});\r\n\t\t\tself.data.filtered = filteredGroups;\r\n\t\t}\r\n\r\n\t\tif (isFiltering) {\r\n\t\t\tself.domNodes.filterResults.classList.add(\"is-visible\");\r\n\t\t\tself.domNodes.filterResults.querySelector(\"span\").textContent =\r\n\t\t\t\tself.data.filtered.length + \"/\" + self.data.items.length;\r\n\t\t} else {\r\n\t\t\tself.domNodes.filterResults.classList.remove(\"is-visible\");\r\n\t\t}\r\n\t}\r\n\r\n\tfunction sortData() {\r\n\t\tvar sorting = self.options.sorting;\r\n\t\tif (sorting !== undefined && sorting !== null) {\r\n\t\t\tvar sortBy = sorting.sortBy;\r\n\t\t\tvar sortDirection = sorting.sortDirection;\r\n\r\n\t\t\tif (sortBy === undefined || sortBy === null) {\r\n\t\t\t\tvar activeSort = self.domNodes.listContainer.querySelector(\".sort-options.active\");\r\n\t\t\t\tif (activeSort !== null) {\r\n\t\t\t\t\tsortBy = activeSort.getAttribute(\"data-sort\");\r\n\t\t\t\t\tsortDirection = activeSort.getAttribute(\"data-sort-dir\");\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (sortBy !== undefined && sortBy !== null && sortBy !== \"\") {\r\n\t\t\t\t// will likely have to replace this with specific sorting functions\r\n\t\t\t\t// at some point to support multi-sort (e.g. Asset Name asc, Epoch desc)\r\n\t\t\t\tself.data.filtered = _.sortBy(self.data.filtered, sortBy);\r\n\t\t\t\tif (sortDirection === \"desc\") {\r\n\t\t\t\t\tself.data.filtered = self.data.filtered.reverse();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tsorting.sortBy = sortBy;\r\n\t\t\tsorting.sortDirection = sortDirection;\r\n\t\t}\r\n\t}\r\n\r\n\tfunction pageData() {\r\n\t\tvar paging = self.options.paging;\r\n\r\n\t\t// page\r\n\t\tvar currentPage = 1;\r\n\t\tvar pageSize = 20;\r\n\t\tif (paging !== undefined && paging !== null) {\r\n\t\t\t// paging.currentPage?\r\n\t\t\tif (paging.currentPage !== undefined) {\r\n\t\t\t\tcurrentPage = paging.currentPage;\r\n\t\t\t}\r\n\t\t\t// paging.pageSize\r\n\t\t\tif (paging.pageSize !== undefined) {\r\n\t\t\t\tpageSize = paging.pageSize;\r\n\t\t\t}\r\n\t\t}\r\n\t\tvar filteredItemCount = self.data.filtered.length;\r\n\r\n\t\t// apply paging\r\n\t\tvar totalPages = Math.ceil(filteredItemCount / pageSize);\r\n\t\tif (totalPages === 0) {\r\n\t\t\ttotalPages = 1;\r\n\t\t}\r\n\t\tif (currentPage < 1) {\r\n\t\t\tcurrentPage = 1;\r\n\t\t} else if (currentPage > totalPages) {\r\n\t\t\tcurrentPage = totalPages;\r\n\t\t}\r\n\t\tpaging.currentPage = currentPage;\r\n\t\tpaging.totalPages = totalPages;\r\n\t\tvar startIndex = pageSize * (currentPage - 1);\r\n\t\tvar endIndex = startIndex + pageSize - 1;\r\n\t\tif (endIndex > filteredItemCount - 1) {\r\n\t\t\tendIndex = filteredItemCount - 1;\r\n\t\t}\r\n\t\tvar currentItems = endIndex - startIndex;\r\n\t\tvar visibleItems = self.data.filtered.slice(startIndex, endIndex + 1);\r\n\t\tself.data.visible = visibleItems;\r\n\t\tself.data.visibleStartIndex = startIndex;\r\n\t\tself.data.visibleEndIndex = endIndex;\r\n\t}\r\n\r\n\tfunction renderVisibleItems() {\r\n\t\tif (self.renderer === undefined) {\r\n\t\t\tconsole.error(\"No renderer defined for FilteredList.\");\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// remove existing items from list\r\n\t\tvar listNode = self.domNodes.list;\r\n\t\twhile (listNode.firstChild) {\r\n\t\t\tlistNode.removeChild(listNode.firstChild);\r\n\t\t}\r\n\r\n\t\t// render paged items\r\n\t\tvar listNodes = [];\r\n\t\tvar fragmentNode = document.createDocumentFragment();\r\n\t\tvar renderIndex = self.data.visibleStartIndex;\r\n\t\t_.each(self.data.visible, function (item) {\r\n\t\t\t// render list item\r\n\t\t\tvar li = self.renderer(item, renderIndex);\r\n\t\t\tif (li !== undefined) {\r\n\t\t\t\tlistNodes.push(li);\r\n\t\t\t\tfragmentNode.appendChild(li);\r\n\t\t\t\trenderIndex++;\r\n\t\t\t} else {\r\n\t\t\t\tconsole.warn(\"Could not render list item:\");\r\n\t\t\t\tconsole.warn(item);\r\n\t\t\t}\r\n\t\t});\r\n\t\tself.domNodes.listItems = listNodes;\r\n\r\n\t\tself.domNodes.list.appendChild(fragmentNode);\r\n\r\n\t\t// check for pagination nodes\r\n\t\tif (self.domNodes.pagination.length > 0) {\r\n\t\t\tvar showPagination = self.data.filtered.length > self.options.paging.pageSize;\r\n\t\t\tvar pageNodeFragment = createPageNodes();\r\n\t\t\tfor (var i = self.domNodes.pagination.length - 1; i >= 0; i--) {\r\n\t\t\t\tvar paginationContainer = self.domNodes.pagination[i];\r\n\t\t\t\tif (showPagination) {\r\n\t\t\t\t\tpaginationContainer.classList.add(\"is-visible\");\r\n\t\t\t\t} else {\r\n\t\t\t\t\tpaginationContainer.classList.remove(\"is-visible\");\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// empty existing pages\r\n\t\t\t\twhile (paginationContainer.firstChild) {\r\n\t\t\t\t\tpaginationContainer.removeChild(paginationContainer.firstChild);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvar pageNodes = pageNodeFragment;\r\n\t\t\t\tif (i > 0) {\r\n\t\t\t\t\tpageNodes = pageNodes.cloneNode(true);\r\n\t\t\t\t}\r\n\t\t\t\tpaginationContainer.appendChild(pageNodes);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// if no filtered items are visible, hide the list if we're not searching for anything\r\n\t\tif (self.data.filtered.length > 0) {\r\n\t\t\tself.domNodes.noItems.classList.remove(\"is-visible\");\r\n\t\t\tself.domNodes.listItemsContainer.classList.add(\"is-visible\");\r\n\t\t\tif (!self.domNodes.filterContainer.classList.contains(\"disabled-feature\")) {\r\n\t\t\t\tself.domNodes.filterContainer.classList.add(\"is-visible\");\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tself.domNodes.noItems.classList.add(\"is-visible\");\r\n\t\t\tself.domNodes.listItemsContainer.classList.remove(\"is-visible\");\r\n\t\t\tif (!isFilteringData()) {\r\n\t\t\t\tself.domNodes.filterContainer.classList.remove(\"is-visible\");\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (self.options.renderCallback !== undefined) {\r\n\t\t\tself.options.renderCallback(self);\r\n\t\t}\r\n\t}\r\n\r\n\tfunction createPageNodes() {\r\n\t\t// add paging\r\n\t\tvar pages = document.createDocumentFragment();\r\n\r\n\t\tvar paging = self.options.paging;\r\n\r\n\t\t// paging prev,1,2,3 ... last,next\r\n\t\tvar hasPrev = paging.currentPage > 1;\r\n\t\tvar hasNext = paging.currentPage < paging.totalPages;\r\n\t\tvar pagePrev = document.createElement(\"li\");\r\n\t\tpagePrev.className = \"page-item\" + (hasPrev ? \"\" : \" disabled\");\r\n\t\tvar pagePrevLink = document.createElement(\"a\");\r\n\t\tpagePrevLink.className = \"page page-link\";\r\n\t\t//pagePrevLink.textContent = strings.POSITION_PREV;\r\n\t\tpagePrevLink.href = \"#\";\r\n\t\tpagePrevLink.setAttribute(\"data-page\", paging.currentPage - 1);\r\n\t\tvar pagePrevIcon = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\r\n\t\tvar pagePrevIconContents = document.createElementNS(\"http://www.w3.org/2000/svg\", \"use\");\r\n\t\tpagePrevIconContents.setAttributeNS(\r\n\t\t\t\"http://www.w3.org/1999/xlink\",\r\n\t\t\t\"href\",\r\n\t\t\t\"/content/svg/tracking.svg?v=15#caret-left-solid\"\r\n\t\t);\r\n\t\tpagePrevIcon.appendChild(pagePrevIconContents);\r\n\t\tpagePrevLink.appendChild(pagePrevIcon);\r\n\t\tpagePrev.appendChild(pagePrevLink);\r\n\t\tpages.appendChild(pagePrev);\r\n\r\n\t\t// always have first and last page\r\n\t\tvar windowSize = 2;\r\n\t\tvar startPage = paging.currentPage - windowSize;\r\n\t\tvar endPage = paging.currentPage + windowSize;\r\n\t\tvar addedFirstPage = false;\r\n\t\tvar addedLastPage = false;\r\n\t\tif (startPage <= 1) {\r\n\t\t\tstartPage = 1;\r\n\t\t\taddedFirstPage = true;\r\n\t\t}\r\n\t\tif (endPage >= paging.totalPages) {\r\n\t\t\tendPage = paging.totalPages;\r\n\t\t\taddedLastPage = true;\r\n\t\t}\r\n\t\tvar morePagesBeforeFirst = startPage > 2;\r\n\t\tvar morePagesBeforeLast = paging.totalPages - endPage > 1;\r\n\t\tif (!addedFirstPage) {\r\n\t\t\t// add first and ellipsis\r\n\t\t\tvar pageItem = document.createElement(\"li\");\r\n\t\t\tpageItem.className = \"page-item\" + (1 === paging.currentPage ? \" active\" : \"\");\r\n\t\t\tvar pageItemLink = document.createElement(\"a\");\r\n\t\t\tpageItemLink.className = \"page page-link\";\r\n\t\t\tpageItemLink.textContent = \"1\";\r\n\t\t\tpageItemLink.href = \"#\";\r\n\t\t\tpageItemLink.setAttribute(\"data-page\", 1);\r\n\t\t\tpageItem.appendChild(pageItemLink);\r\n\t\t\tpages.appendChild(pageItem);\r\n\r\n\t\t\tif (morePagesBeforeFirst) {\r\n\t\t\t\tvar pageItem = document.createElement(\"li\");\r\n\t\t\t\tpageItem.className = \"page-item disabled\";\r\n\t\t\t\tvar pageItemLink = document.createElement(\"a\");\r\n\t\t\t\tpageItemLink.className = \"page page-link\";\r\n\t\t\t\tpageItemLink.textContent = \"...\";\r\n\t\t\t\tpageItemLink.href = \"#\";\r\n\t\t\t\tpageItem.appendChild(pageItemLink);\r\n\t\t\t\tpages.appendChild(pageItem);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfor (var i = startPage; i <= endPage; i++) {\r\n\t\t\tvar pageItem = document.createElement(\"li\");\r\n\t\t\tpageItem.className = \"page-item\" + (i === paging.currentPage ? \" active\" : \"\");\r\n\t\t\tvar pageItemLink = document.createElement(\"a\");\r\n\t\t\tpageItemLink.className = \"page page-link\";\r\n\t\t\tpageItemLink.textContent = i;\r\n\t\t\tpageItemLink.href = \"#\";\r\n\t\t\tpageItemLink.setAttribute(\"data-page\", i);\r\n\t\t\tpageItem.appendChild(pageItemLink);\r\n\t\t\tpages.appendChild(pageItem);\r\n\t\t}\r\n\r\n\t\tif (!addedLastPage) {\r\n\t\t\t// add last and ellipsis\r\n\t\t\tif (morePagesBeforeLast) {\r\n\t\t\t\tvar pageItem = document.createElement(\"li\");\r\n\t\t\t\tpageItem.className = \"page-item disabled\";\r\n\t\t\t\tvar pageItemLink = document.createElement(\"a\");\r\n\t\t\t\tpageItemLink.className = \"page page-link\";\r\n\t\t\t\tpageItemLink.textContent = \"...\";\r\n\t\t\t\tpageItemLink.href = \"#\";\r\n\t\t\t\tpageItem.appendChild(pageItemLink);\r\n\t\t\t\tpages.appendChild(pageItem);\r\n\t\t\t}\r\n\r\n\t\t\tvar pageItem = document.createElement(\"li\");\r\n\t\t\tpageItem.className = \"page-item\" + (paging.totalPages === paging.currentPage ? \" active\" : \"\");\r\n\t\t\tvar pageItemLink = document.createElement(\"a\");\r\n\t\t\tpageItemLink.className = \"page page-link\";\r\n\t\t\tpageItemLink.textContent = paging.totalPages;\r\n\t\t\tpageItemLink.href = \"#\";\r\n\t\t\tpageItemLink.setAttribute(\"data-page\", paging.totalPages);\r\n\t\t\tpageItem.appendChild(pageItemLink);\r\n\t\t\tpages.appendChild(pageItem);\r\n\t\t}\r\n\r\n\t\tvar pageNext = document.createElement(\"li\");\r\n\t\tpageNext.className = \"page-item\" + (hasNext ? \"\" : \" disabled\");\r\n\t\tvar pageNextLink = document.createElement(\"a\");\r\n\t\tpageNextLink.className = \"page page-link\";\r\n\t\t//pageNextLink.textContent = strings.POSITION_NEXT;\r\n\t\tpageNextLink.href = \"#\";\r\n\t\tpageNextLink.setAttribute(\"data-page\", paging.currentPage + 1);\r\n\t\tvar pageNextIcon = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\r\n\t\tvar pageNextIconContents = document.createElementNS(\"http://www.w3.org/2000/svg\", \"use\");\r\n\t\tpageNextIconContents.setAttributeNS(\r\n\t\t\t\"http://www.w3.org/1999/xlink\",\r\n\t\t\t\"href\",\r\n\t\t\t\"/content/svg/tracking.svg?v=15#caret-right-solid\"\r\n\t\t);\r\n\t\tpageNextIcon.appendChild(pageNextIconContents);\r\n\t\tpageNextLink.appendChild(pageNextIcon);\r\n\t\tpageNext.appendChild(pageNextLink);\r\n\t\tpages.appendChild(pageNext);\r\n\r\n\t\treturn pages;\r\n\t}\r\n\r\n\tthis.reindex = createDataIndex;\r\n\r\n\tthis.addData = function (items) {\r\n\t\tif (self.search !== undefined && self.search !== null) {\r\n\t\t\tself.search.addDocuments(items);\r\n\t\t}\r\n\t\tself.data.items = self.data.items.concat(items);\r\n\t\tself.redraw();\r\n\t};\r\n\r\n\t// TODO is there any benefit to knowing that we've modified data instead of replaced all?\r\n\t// I think just set list.data\r\n\r\n\tthis.redraw = function () {\r\n\t\tfilterData();\r\n\t\tsortData();\r\n\t\tpageData();\r\n\t\trenderVisibleItems();\r\n\t};\r\n\r\n\tthis.sort = function (sortBy, sortDirection) {\r\n\t\tif (self.options.sorting === undefined) {\r\n\t\t\tself.options.sorting = { sortBy: sortBy, sortDirection: sortDirection };\r\n\t\t} else {\r\n\t\t\tself.options.sorting.sortBy = sortBy;\r\n\t\t\tself.options.sorting.sortDirection = sortDirection;\r\n\t\t}\r\n\r\n\t\tsortData();\r\n\t\tpageData();\r\n\t\trenderVisibleItems();\r\n\t};\r\n\r\n\tthis.changePage = function (pageNum) {\r\n\t\tvar toPage = parseInt(pageNum);\r\n\t\tif (toPage === self.options.paging.currentPage) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tself.options.paging.currentPage = parseInt(pageNum);\r\n\t\tpageData();\r\n\r\n\t\t// TODO paging should simply update and not be redrawn\r\n\t\trenderVisibleItems();\r\n\t};\r\n\r\n\t//this.render = function () { // used?\r\n\t// renderVisibleItems();\r\n\t//};\r\n\r\n\tinit(items, options);\r\n};\r\n\r\nexport default FilteredList;\r\n","function convertHexToRgb(hex) {\r\n\tif (hex.indexOf(\"#\") === 0) {\r\n\t\thex = hex.substring(1);\r\n\t}\r\n\tvar r = parseInt(hex.substr(0, 2), 16);\r\n\tvar g = parseInt(hex.substr(2, 2), 16);\r\n\tvar b = parseInt(hex.substr(4, 2), 16);\r\n\treturn { r: r, g: g, b: b };\r\n}\r\n\r\nexport function getBackgroundColorAsHex(elem) {\r\n\tvar bg = null;\r\n\tif (elem.currentStyle) {\r\n\t\tbg = elem.currentStyle[\"backgroundColor\"];\r\n\t} else if (window.getComputedStyle) {\r\n\t\tbg = document.defaultView.getComputedStyle(elem, null).getPropertyValue(\"background-color\");\r\n\t}\r\n\tif (bg.search(\"rgb\") == -1) {\r\n\t\treturn bg;\r\n\t} else {\r\n\t\treturn rgbToHex(bg);\r\n\t}\r\n}\r\n\r\nfunction rgbToHex(rgb) {\r\n\trgb = rgb.match(/^rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)$/);\r\n\tfunction hex(x) {\r\n\t\treturn (\"0\" + parseInt(x).toString(16)).slice(-2);\r\n\t}\r\n\treturn \"#\" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);\r\n}\r\n\r\nexport function convertHexToSortable(hex) {\r\n\tvar rgb = convertHexToRgb(hex);\r\n\tvar lum = Math.sqrt(0.241 * rgb.r + 0.691 * rgb.g + 0.068 * rgb.b);\r\n\tvar hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);\r\n\treturn { h: parseInt(hsl.h), lum: parseInt(lum * 8), l: parseInt(hsl.l * 8) };\r\n}\r\n\r\n// Returns a CSS colour string, used for trajectory lines per asset.\r\nexport function convertNamedColorToHex(color) {\r\n\tswitch (color) {\r\n\t\tcase \"blue\":\r\n\t\t\treturn \"#5781fc\";\r\n\t\tcase \"green\":\r\n\t\t\treturn \"#00e13c\";\r\n\t\tcase \"turquoise\":\r\n\t\t\treturn \"#57dbdb\";\r\n\t\tcase \"orange\":\r\n\t\t\treturn \"#fdbf67\";\r\n\t\tcase \"pink\":\r\n\t\t\treturn \"#e14f9e\";\r\n\t\tcase \"purple\":\r\n\t\t\treturn \"#7e55fc\";\r\n\t\tcase \"yellow\":\r\n\t\t\treturn \"#fcf357\";\r\n\t\tcase \"white\":\r\n\t\t\treturn \"#ffffff\";\r\n\t\tcase \"gold\":\r\n\t\t\treturn \"#e1c400\";\r\n\t\tcase \"darkred\":\r\n\t\t\treturn \"#940000\";\r\n\t\tcase \"darkgreen\":\r\n\t\t\treturn \"#559501\";\r\n\t\tcase \"grey\":\r\n\t\t\treturn \"#d4d4d4\";\r\n\t\tcase \"darkgrey\":\r\n\t\t\treturn \"#535353\";\r\n\t\tcase \"brown\":\r\n\t\t\treturn \"#43312a\";\r\n\t\tcase \"magenta\":\r\n\t\t\treturn \"#bf00f2\";\r\n\t\tcase \"darkorange\":\r\n\t\t\treturn \"#c64102\";\r\n\t\tcase \"darkblue\":\r\n\t\t\treturn \"#034c8b\";\r\n\t\tcase \"red\":\r\n\t\tdefault:\r\n\t\t\treturn \"#fd7567\";\r\n\t}\r\n}\r\n\r\n\r\n// Returns a array of the form [r, g, b, a], used for the tints of the Gleo TintedSprites.\r\nexport function convertNamedColorToArray(color) {\r\n\tswitch (color) {\r\n\t\tcase \"blue\":\r\n\t\t\treturn [87, 129, 252,255];\r\n\t\tcase \"green\":\r\n\t\t\treturn [0, 225, 60 ,255];\r\n\t\tcase \"turquoise\":\r\n\t\t\treturn [87, 219, 219,255];\r\n\t\tcase \"orange\":\r\n\t\t\treturn [253, 191, 103,255];\r\n\t\tcase \"pink\":\r\n\t\t\treturn [225, 79, 158,255];\r\n\t\tcase \"purple\":\r\n\t\t\treturn [126, 85, 252,255];\r\n\t\tcase \"yellow\":\r\n\t\t\treturn [252, 243, 87,255];\r\n\t\tcase \"white\":\r\n\t\t\treturn [255, 255, 255,255];\r\n\t\tcase \"gold\":\r\n\t\t\treturn [225, 196, 0,255];\r\n\t\tcase \"darkred\":\r\n\t\t\treturn [148, 0, 0,255];\r\n\t\tcase \"darkgreen\":\r\n\t\t\treturn [85, 149, 1,255];\r\n\t\tcase \"grey\":\r\n\t\t\treturn [212, 212, 212,255];\r\n\t\tcase \"darkgrey\":\r\n\t\t\treturn [83, 83, 83 ,255];\r\n\t\tcase \"brown\":\r\n\t\t\treturn [67, 49, 42,255];\r\n\t\tcase \"magenta\":\r\n\t\t\treturn [191, 0, 242,255];\r\n\t\tcase \"darkorange\":\r\n\t\t\treturn [198, 65, 2 ,255];\r\n\t\tcase \"darkblue\":\r\n\t\t\treturn [3, 76, 139,255];\r\n\t\tcase \"red\":\r\n\t\t\treturn [253, 117, 103, 255];\r\n\t\tdefault:\r\n\t\t\treturn color;\r\n\t}\r\n}\r\n\r\nfunction rgbToHsl(r, g, b) {\r\n\tvar min,\r\n\t\tmax,\r\n\t\ti,\r\n\t\tl,\r\n\t\ts,\r\n\t\tmaxcolor,\r\n\t\th,\r\n\t\trgb = [];\r\n\trgb[0] = r / 255;\r\n\trgb[1] = g / 255;\r\n\trgb[2] = b / 255;\r\n\tmin = rgb[0];\r\n\tmax = rgb[0];\r\n\tmaxcolor = 0;\r\n\tfor (i = 0; i < rgb.length - 1; i++) {\r\n\t\tif (rgb[i + 1] <= min) {\r\n\t\t\tmin = rgb[i + 1];\r\n\t\t}\r\n\t\tif (rgb[i + 1] >= max) {\r\n\t\t\tmax = rgb[i + 1];\r\n\t\t\tmaxcolor = i + 1;\r\n\t\t}\r\n\t}\r\n\tif (maxcolor == 0) {\r\n\t\th = (rgb[1] - rgb[2]) / (max - min);\r\n\t}\r\n\tif (maxcolor == 1) {\r\n\t\th = 2 + (rgb[2] - rgb[0]) / (max - min);\r\n\t}\r\n\tif (maxcolor == 2) {\r\n\t\th = 4 + (rgb[0] - rgb[1]) / (max - min);\r\n\t}\r\n\tif (isNaN(h)) {\r\n\t\th = 0;\r\n\t}\r\n\th = h * 60;\r\n\tif (h < 0) {\r\n\t\th = h + 360;\r\n\t}\r\n\tl = (min + max) / 2;\r\n\tif (min == max) {\r\n\t\ts = 0;\r\n\t} else {\r\n\t\tif (l < 0.5) {\r\n\t\t\ts = (max - min) / (max + min);\r\n\t\t} else {\r\n\t\t\ts = (max - min) / (2 - max - min);\r\n\t\t}\r\n\t}\r\n\ts = s;\r\n\treturn { h: h.toFixed(0), s: s.toFixed(2), l: l.toFixed(2) };\r\n}\r\n\r\nexport function getItemHexColor(item) {\r\n\tif (item.Color === undefined || item.Color === null || item.Color.length < 7) {\r\n\t\treturn \"ff0000\";\r\n\t}\r\n\r\n\treturn item.Color.substring(1);\r\n}\r\n","import domNodes from \"./domNodes.js\";\r\n\r\nimport Popper from \"popper.js\"; // From ../popper.js . See https://github.com/floating-ui/floating-ui\r\n\r\n/// TODO: Refactor domNodes.mouseTooltip into a module variable.\r\n\r\nexport function initializeMouseTooltip() {\r\n\tvar tip = document.getElementById(\"mouse-tooltip\");\r\n\t//domNodes.map.querySelector('.leaflet-marker-pane').appendChild(tip);\r\n\tdomNodes.mouseTooltip.content = tip;\r\n\tdomNodes.mouseTooltip.title = tip.querySelector(\".tooltip-inner\");\r\n\tdomNodes.mouseTooltip.show = false;\r\n\tdomNodes.mouseTooltip.position = { x: 0, y: 0 };\r\n\tdomNodes.mouseTooltip.reference = {\r\n\t\tgetBoundingClientRect: function () {\r\n\t\t\treturn {\r\n\t\t\t\ttop: domNodes.mouseTooltip.position.y,\r\n\t\t\t\tright: domNodes.mouseTooltip.position.x,\r\n\t\t\t\tbottom: domNodes.mouseTooltip.position.y,\r\n\t\t\t\tleft: domNodes.mouseTooltip.position.x,\r\n\t\t\t\twidth: 0,\r\n\t\t\t\theight: 0,\r\n\t\t\t};\r\n\t\t},\r\n\t\tclientWidth: 0,\r\n\t\tclientHeight: 0,\r\n\t};\r\n\tvar arrow = domNodes.mouseTooltip.content.querySelector(\".arrow\");\r\n\tdomNodes.mouseTooltip.popper = new Popper(domNodes.mouseTooltip.reference, domNodes.mouseTooltip.content, {\r\n\t\tmodifiers: {\r\n\t\t\toffset: {},\r\n\t\t\tarrow: {\r\n\t\t\t\telement: arrow,\r\n\t\t\t},\r\n\t\t},\r\n\t\tplacement: \"right\",\r\n\t});\r\n}\r\n\r\nexport function showMouseTooltip(x, y, title) {\r\n\tdomNodes.mouseTooltip.position = { x: x, y: y };\r\n\tdomNodes.mouseTooltip.show = true;\r\n\tdomNodes.mouseTooltip.title.textContent = title;\r\n\tdomNodes.mouseTooltip.content.classList.add(\"show\");\r\n\tdomNodes.mouseTooltip.content.classList.remove(\"hide\");\r\n\tdomNodes.mouseTooltip.popper.scheduleUpdate();\r\n\t// two calls required as popper has incorrect starting arrow on very first show... maybe due to css transition?\r\n\tdomNodes.mouseTooltip.popper.scheduleUpdate();\r\n}\r\n\r\nexport function hideMouseTooltip() {\r\n\tdomNodes.mouseTooltip.show = false;\r\n\tdomNodes.mouseTooltip.content.classList.remove(\"show\");\r\n\tdomNodes.mouseTooltip.content.classList.add(\"hide\");\r\n}\r\n","// Helpers that are better handled by ES5 JS functionality\r\n\r\n// TODO: Replace with Array.prototype.includes()\r\nexport function isItemIncluded(grouping, id) {\r\n\tif (grouping === undefined) return false;\r\n\treturn grouping.indexOf(id.toString()) !== -1;\r\n}\r\n","import L from \"leaflet\";\r\n\r\n/*global MapToolbar */\r\n// import MapToolbar from '../MapToolbar.js';\r\n\r\nexport function editFenceSegments(fence) {\r\n\tconsole.log(fence);\r\n\tfor (var j = 0; j < fence.Segments.length; j++) {\r\n\t\tvar segment = fence.Segments[j];\r\n\t\tswitch (segment.Type) {\r\n\t\t\tcase 0: // line\r\n\t\t\t\tvar points = [];\r\n\t\t\t\tfor (var k = 0; k < segment.Points.length; k++) {\r\n\t\t\t\t\tvar point = segment.Points[k];\r\n\t\t\t\t\tvar latlng = L.latLng(point.Lat, point.Lng);\r\n\t\t\t\t\t//MapToolbar.addPoint(latlng, MapToolbar.currentFeature);\r\n\t\t\t\t\tpoints.push(latlng);\r\n\t\t\t\t}\r\n\t\t\t\tMapToolbar.initFeature(\"line\", segment.Id, points);\r\n\r\n\t\t\t\tbreak;\r\n\t\t\tcase 1: // shape\r\n\t\t\t\t//trkData.drawing.manager.edit()\r\n\t\t\t\tvar points = [];\r\n\t\t\t\tfor (var k = 0; k < segment.Points.length - 1; k++) {\r\n\t\t\t\t\t// ignore the last point for a polygon as it's the same as the first\r\n\t\t\t\t\tvar point = segment.Points[k];\r\n\t\t\t\t\tvar latlng = L.latLng(point.Lat, point.Lng);\r\n\t\t\t\t\tpoints.push(latlng);\r\n\t\t\t\t}\r\n\t\t\t\tMapToolbar.initFeature(\"shape\", segment.Id, points);\r\n\t\t\t\tbreak;\r\n\t\t\tcase 2: // circle\r\n\t\t\t\tvar center = L.latLng(segment.Center.Lat, segment.Center.Lng);\r\n\t\t\t\tvar radius = L.latLng(segment.Radius.Lat, segment.Radius.Lng);\r\n\t\t\t\t//MapToolbar.addPoint(center, MapToolbar.currentFeature);\r\n\t\t\t\t//MapToolbar.addPoint(radius, MapToolbar.currentFeature);\r\n\t\t\t\tMapToolbar.initFeature(\"circle\", segment.Id, [center, radius]);\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n}\r\n","import strings from \"./strings.js\";\r\nimport options from \"./options.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport trkData from \"./data.js\";\r\nimport user from \"./user.js\";\r\nimport state from \"./state.js\";\r\nimport { changePrimaryButtonLabel } from \"./modal.js\";\r\nimport { openDialogPanel } from \"./panel-nav.js\";\r\nimport { populateGroupList } from \"./group-list.js\";\r\nimport { sortGroups } from \"./item-sorting.js\";\r\nimport { populateCheckboxList } from \"./dom-util.js\";\r\nimport { populateGroupDialog } from \"./asset-group.js\";\r\nimport { displayPreferencesRemove } from \"./user.js\";\r\nimport { updateGroupVisibilityStatus, createGroupColorStyles, addItemToGroup } from \"./asset-group.js\";\r\nimport { findPlaceById } from \"./place.js\";\r\nimport { findFenceById } from \"./fence.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport _ from \"lodash\";\r\n\r\n/** For place and fence groups */\r\nexport function openItemGroupDialog(group, groupFor = group.Type) {\r\n const groups = groupFor === \"places\" ? trkData.placeGroups\r\n : groupFor === \"fences\" ? trkData.fenceGroups\r\n : [];\r\n const groupUsers = groupFor === \"places\" ? trkData.placeGroupUsers\r\n : groupFor === \"fences\" ? trkData.fenceGroupUsers\r\n : [];\r\n const groupIdFieldInGroupUsers = groupFor === \"places\" ? 'PlaceGroupId'\r\n : groupFor === \"fences\" ? 'FenceGroupId'\r\n : [];\r\n const items = groupFor === \"places\" ? trkData.places\r\n : groupFor === \"fences\" ? trkData.fences\r\n : [];\r\n\r\n\tif (options.enabledFeatures.indexOf(\"UI_GROUPS\") === -1) {\r\n\t\treturn;\r\n\t}\r\n const itemType = groupFor.substring(0, groupFor.length-1); // e.g. \"place\" or \"fence\"\r\n\r\n\tconst dialog = domNodes.dialogs.editGroup;\r\n\tconst isEditing = !!group;\r\n\tstate.groupDialog.isEditing = isEditing;\r\n\tstate.groupDialog.type = itemType;\r\n\r\n const itemStrings = groupFor === \"places\" ? { name: strings.PLACES, edit: strings.EDIT_PLACE_GROUP, add: strings.ADD_PLACE_GROUP }\r\n : groupFor === \"fences\" ? { name: strings.GEOFENCES, edit: strings.EDIT_GEOFENCE_GROUP, add: strings.ADD_GEOFENCE_GROUP }\r\n : {}; \r\n\r\n\tconst buttonText = isEditing ? strings.SAVE_CHANGES : strings.CREATE_GROUP;\r\n\tchangePrimaryButtonLabel(dialog, buttonText);\r\n\r\n\tif (!isEditing) {\r\n\t\t$(\"#accordion-edit-asset-group-main-content\").collapse(\"show\");\r\n\t}\r\n\r\n\t$(\".section-asset-group\").hide();\r\n\t$(\"#edit-group-item-name\").text(itemStrings.name);\r\n\r\n\ttrkData.validation.addGroup.resetForm();\r\n\ttrkData.validation.addGroup.currentForm.reset();\r\n\r\n\tpopulateGroupList(sortGroups(groups));\r\n\t$(\"#edit-asset-group-accordion .primary-card button\").removeClass(\"disabled\");\r\n\tconst dialogTitle = isEditing ? itemStrings.edit : itemStrings.add;\r\n\tif (isEditing) {\r\n\t\t$(\"#asset-group-parent\").hide();\r\n\t\t$(dialog).data(\"groupId\", group.Id);\r\n\t} else {\r\n\t\t$(\"#asset-group-parent\").show();\r\n\t}\r\n\r\n\tif (!user.isAdmin) {\r\n\t\t$(\"#accordion-edit-asset-group-users-head button\").addClass(\"disabled\");\r\n\t}\r\n\r\n\t// checkbox lists...\r\n\tvar userList = user.isAdmin ? trkData.users : [];\r\n\tpopulateCheckboxList(\r\n\t\t\"edit-asset-group-users-list\",\r\n\t\tuserList,\r\n\t\t\"EditAssetGroupUserIds\",\r\n\t\tfunction (item) {\r\n\t\t\tif (group === null) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tvar itemUsers = groupUsers.find(gu => gu[groupIdFieldInGroupUsers] === group.Id);\r\n\t\t\tif (itemUsers === undefined) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\treturn itemUsers.UserIds.indexOf(item.Id) >= 0;\r\n\t\t},\r\n\t\tfunction (item) {\r\n\t\t\treturn item.Name;\r\n\t\t},\r\n\t\t\"users\",\r\n\t\tfunction (item) {\r\n\t\t\treturn item.Username;\r\n\t\t}\r\n\t);\r\n\r\n\tpopulateCheckboxList(\r\n\t\t\"edit-asset-group-assets-list\",\r\n\t\titems,\r\n\t\t\"EditAssetGroupAssetIds\",\r\n\t\tfunction (item) {\r\n\t\t\tif (group === null) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\treturn item.GroupIds.indexOf(group.Id) !== -1;\r\n\t\t},\r\n\t\tfunction (item) {\r\n\t\t\treturn item.Name;\r\n\t\t},\r\n\t\tgroupFor,\r\n\t\tfunction (item) {\r\n\t\t\treturn item.Description;\r\n\t\t}\r\n\t);\r\n\r\n\tif (group !== null) {\r\n\t\tpopulateGroupDialog(group);\r\n\t}\r\n\r\n\topenDialogPanel(\r\n\t\tdomNodes.dialogs.editGroup,\r\n\t\tdialogTitle,\r\n\t\tgroup,\r\n\t\tfalse,\r\n\t\tnull,\r\n\t\titemType + \"-group\",\r\n\t\tisEditing ? \"edit-group\" : null,\r\n\t\tisEditing ? openItemGroupDialog : null\r\n\t);\r\n\tdocument.getElementById(\"txtGroupName\").focus();\r\n}\r\n\r\n/** For place and fence groups */\r\nexport function assignItemToGroups(itemId, groupIds, groupFor) {\r\n const item = \r\n groupFor === \"places\" ? findPlaceById(itemId)\r\n : groupFor === \"fences\" ? findFenceById(itemId)\r\n : null;\r\n\tif (item === null) {\r\n\t\treturn;\r\n\t}\r\n const itemType = groupFor.substring(0, groupFor.length-1); // e.g. \"place\" or \"fence\"\r\n\r\n\tconst groupsRemoved = _.difference(item.GroupIds, groupIds);\r\n\t_.each(groupsRemoved, function (groupId) {\r\n\t\tdomNodes.groupContents[groupId].querySelector('.' + itemType + '-' + itemId).remove();\r\n\t});\r\n\r\n\tconst groupsAdded = _.difference(groupIds, item.GroupIds);\r\n\t_.each(groupsAdded, function (groupId) {\r\n\t\taddItemToGroup(item, groupId, groupFor);\r\n\t});\r\n\r\n\titem.GroupIds = groupIds;\r\n\r\n\t_.each(_.concat(groupsRemoved, groupsAdded), function (groupId) {\r\n\t\tupdateGroupVisibilityStatus(groupId, groupFor);\r\n\t});\r\n}\r\n\r\n/** For place and fence groups */\r\nexport function deleteItemGroup(deleted) {\r\n\t// remove group from UI\r\n\tdomNodes.groups[deleted.Id].remove();\r\n\tdelete domNodes.groups[deleted.Id];\r\n\tdelete domNodes.groupColors[deleted.Id];\r\n\tdelete domNodes.groupContents[deleted.Id];\r\n\r\n const [items, groups, groupUsers] = \r\n deleted.Type === \"places\" ? [trkData.places, trkData.placeGroups, trkData.placeGroupUsers]\r\n : deleted.Type === \"fences\" ? [trkData.fences, trkData.fenceGroups, trkData.fenceGroupUsers]\r\n : [[], [], []];\r\n\r\n\t// remove group from trkData.groups\r\n\tconst idx = groups.findIndex(g => g.Id === deleted.Id);\r\n\tif (idx >=0 ) {\r\n\t\tgroups.splice(idx, 1);\r\n\t}\r\n\r\n\tvar modifiedGroupIds = [];\r\n\t_.each(groups, function (group) {\r\n\t\tif (_.indexOf(group.GroupIds, deleted.Id)) {\r\n\t\t\tgroup.GroupIds = _.without(group.GroupIds, deleted.Id);\r\n\t\t\tmodifiedGroupIds.push(group.Id);\r\n\t\t}\r\n\t});\r\n\r\n\tif (groupUsers != null) {\r\n const idx = groupUsers.findIndex(gu => gu.AssetGroupId === deleted.Id);\r\n if (idx >= 0) groupUsers.splice(idx, 1);\r\n\t}\r\n\r\n\tfor (let item of items) {\r\n const idx = item.GroupIds.findIndex(gId => gId === deleted.Id);\r\n if (idx >= 0) item.GroupIds.splice(idx, 1);\r\n\t}\r\n\r\n\t// remove group expanded preference\r\n\tdisplayPreferencesRemove(\"expandedGroups\", deleted.Id);\r\n\r\n\t// update group status for parent groups\r\n\t_.each(modifiedGroupIds, function (groupId) {\r\n\t\tupdateGroupVisibilityStatus(groupId, deleted.Type);\r\n\t});\r\n}\r\n\r\n/** For place and fence groups */\r\nexport function updateItemGroup(updated) {\r\n\tdomNodes.groupColors[updated.Id] = updated.Color;\r\n\r\n\tvar li = domNodes.groups[updated.Id];\r\n\tif (li !== undefined) {\r\n\t\tli.style.borderColor = updated.Color;\r\n\t\tvar name = li.querySelector(\".group-name\");\r\n\t\tname.textContent = updated.Name;\r\n\t}\r\n\r\n\tcreateGroupColorStyles();\r\n}\r\n","import trkData from \"./data.js\";\r\nimport log from \"./log.js\";\r\nimport { includeRowIfNotNull, createAccordionCard } from \"./dom-util.js\";\r\n\r\nimport $j from \"jquery\";\r\nimport _ from \"lodash\";\r\nimport { el } from \"redom\"; // https://redom.js.org/\r\n\r\nexport function getAttributesForType(type, prefix) {\r\n\treturn _.filter(trkData.attributes, { Type: type }).map(function (item) {\r\n\t\treturn {\r\n\t\t\tId: item.Id,\r\n\t\t\tValue: $j(\"#\" + prefix + item.Id).val(),\r\n\t\t};\r\n\t});\r\n}\r\n\r\nexport function populateCustomAttributes(itemAttributes, attributeType, parentId) {\r\n\tvar attributeinformation = [];\r\n\tif (itemAttributes == null || itemAttributes.length == 0 || trkData.attributeGroups == null) {\r\n\t\treturn attributeinformation;\r\n\t}\r\n\r\n\tfor (var i = 0; i < trkData.attributeGroups.length; i++) {\r\n\t\t// attributes by group\r\n\t\tvar group = trkData.attributeGroups[i];\r\n\t\tif (group.Type != attributeType) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tvar attributes = findAttributesForAttributeGroup(group, attributeType);\r\n\t\tif (attributes == null || attributes.length == 0) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tvar groupattributeinformation = [];\r\n\t\t// see if there's a matching value\r\n\t\tfor (var k = 0; k < attributes.length; k++) {\r\n\t\t\tvar attribute = attributes[k];\r\n\t\t\tfor (var j = 0; j < itemAttributes.length; j++) {\r\n\t\t\t\tvar itemAttribute = itemAttributes[j];\r\n\t\t\t\tif (attribute.Id == itemAttribute.Id) {\r\n\t\t\t\t\tgroupattributeinformation.push(includeRowIfNotNull(attribute.Name, itemAttribute.Value));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tgroupattributeinformation = _.compact(groupattributeinformation);\r\n\t\tif (groupattributeinformation.length > 0) {\r\n\t\t\tattributeinformation.push(\r\n\t\t\t\tcreateAccordionCard(\r\n\t\t\t\t\t\"attribute-\" + group.Id,\r\n\t\t\t\t\tparentId,\r\n\t\t\t\t\tgroup.Name,\r\n\t\t\t\t\tel(\"table\", el(\"tbody\", groupattributeinformation))\r\n\t\t\t\t)\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n\treturn attributeinformation;\r\n}\r\n\r\nfunction findAttributesForAttributeGroup(group, type) {\r\n\treturn findAttributesForAttributeGroupId(group.Id, type);\r\n}\r\n\r\nfunction findAttributesForAttributeGroupId(id, type) {\r\n\treturn _.filter(trkData.attributes, { GroupId: parseInt(id), Type: type });\r\n}\r\n\r\nexport function populateCustomAttributesEditable(container, attributeType, prefix) {\r\n\tif (trkData.attributeGroups == null || trkData.attributes == null) {\r\n\t\treturn;\r\n\t}\r\n\tfor (var i = 0; i < trkData.attributeGroups.length; i++) {\r\n\t\tvar group = trkData.attributeGroups[i];\r\n\t\tif (group.Type != attributeType) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tvar attributes = findAttributesForAttributeGroup(group, attributeType); // asset attributes\r\n\t\tif (attributes == null || attributes.length == 0) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\tlog(\"Add Custom Attribute Group: \" + group.Name);\r\n\r\n\t\t// add group and its attributes to accordion edit-asset-extra-accordion\r\n\t\tvar contents = document.createDocumentFragment();\r\n\t\tfor (var j = 0; j < attributes.length; j++) {\r\n\t\t\tvar attribute = attributes[j];\r\n\t\t\tvar item = el(\".form-group\", el(\"label\", { for: prefix + attribute.Id }, attribute.Name));\r\n\r\n\t\t\t//TODO: Setup Regex, PermittedValues\r\n\t\t\tif (attribute.PermittedValues != null && attribute.PermittedValues != \"\") {\r\n\t\t\t\tvar permittedValues = attribute.PermittedValues.split(\",\");\r\n\t\t\t\t// select box for restricted values, check the current\r\n\t\t\t\tconst options = [el(\"option\")];\r\n\t\t\t\tfor (var k = 0; k < permittedValues.length; k++) {\r\n\t\t\t\t\tvar permittedValue = permittedValues[k];\r\n\t\t\t\t\toptions.push(el(\"option\", { value: permittedValue }, permittedValue));\r\n\t\t\t\t}\r\n\t\t\t\tvar select = el(\r\n\t\t\t\t\t\"select#\" + prefix + attribute.Id + \".attrib.form-control\",\r\n\t\t\t\t\t{ \"data-attribute-id\": attribute.Id },\r\n\t\t\t\t\toptions\r\n\t\t\t\t);\r\n\t\t\t\titem.appendChild(select);\r\n\t\t\t} else {\r\n\t\t\t\tswitch (attribute.DataType) {\r\n\t\t\t\t\tcase 0: // string\r\n\t\t\t\t\t//style = ' style=\"width: 20em;\"';\r\n\t\t\t\t\tcase 1: // number\r\n\t\t\t\t\t\tvar itemOptions = { type: \"text\", \"data-attribute-id\": attribute.Id };\r\n\t\t\t\t\t\tif (attribute.MaxLength != null) {\r\n\t\t\t\t\t\t\titemOptions.maxlength = attribute.MaxLength;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\titem.appendChild(el(\"input#\" + prefix + attribute.Id + \".attrib.form-control\", itemOptions));\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 2: // file, TODO: File upload capability (mimic photo?)\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tcontents.appendChild(item);\r\n\t\t}\r\n\r\n\t\t// create accordion and add it\r\n\t\tcontainer.appendChild(createAccordionCard(\"attribute-\" + group.Id + \"-edit\", container.id, group.Name, contents));\r\n\t}\r\n}\r\n","import AcetateExtrudedPoint from \"./AcetateExtrudedPoint.mjs\";\r\n\r\n/**\r\n * @class AcetateHeadingTriangle\r\n * @inherits AcetateExtrudedPoint\r\n *\r\n * An `Acetate` for rendering heading triangles. Much like `AcetateSolidExtrusion`,\r\n * but handles two colours and a feathering between them.\r\n */\r\nexport default class AcetateHeadingTriangle extends AcetateExtrudedPoint {\r\n\tconstructor(target, opts) {\r\n\t\tsuper(target, { zIndex: 2500, opts });\r\n\r\n\t\tthis._attrs = new this.glii.InterleavedAttributes(\r\n\t\t\t{\r\n\t\t\t\tusage: this.glii.STATIC_DRAW,\r\n\t\t\t\tsize: 1,\r\n\t\t\t\tgrowFactor: 1.2,\r\n\t\t\t},\r\n\t\t\t[\r\n\t\t\t\t{\r\n\t\t\t\t\t// Fill RGBA colour\r\n\t\t\t\t\tglslType: \"vec4\",\r\n\t\t\t\t\ttype: Uint8Array,\r\n\t\t\t\t\tnormalized: true,\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\t// Border RGBA colour\r\n\t\t\t\t\tglslType: \"vec4\",\r\n\t\t\t\t\ttype: Uint8Array,\r\n\t\t\t\t\tnormalized: true,\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\t// Per-triangle border width and feather width\r\n\t\t\t\t\tglslType: \"vec2\",\r\n\t\t\t\t\ttype: Float32Array,\r\n\t\t\t\t\tnormalized: false,\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\t// Per-vertex distance from edge. Each vertex in a triangle\r\n\t\t\t\t\t// will have values like N-0-0, 0-N-0, 0-0-N. Units should\r\n\t\t\t\t\t// be CSS pixels.\r\n\t\t\t\t\tglslType: \"vec3\",\r\n\t\t\t\t\ttype: Float32Array,\r\n\t\t\t\t\tnormalized: false,\r\n\t\t\t\t},\r\n\t\t\t]\r\n\t\t);\r\n\r\n\t\t// Yaws are stored as ( cos(yaw), sin(yaw) ) tuples, and we'll assume\r\n\t\t// they might be updated fairly often.\r\n\t\t// this._yaws = new this.glii.SingleAttribute({\r\n\t\t// \tusage: this.glii.DYNAMIC_DRAW,\r\n\t\t// \tsize: 1,\r\n\t\t// \tgrowFactor: 1.2,\r\n\t\t//\r\n\t\t// \tglslType: \"vec2\",\r\n\t\t// \ttype: Float32Array,\r\n\t\t// \tnormalized: false,\r\n\t\t// });\r\n\t}\r\n\r\n\t// _commitStridedArrays(baseVtx, vtxCount) {\r\n\t// \tthis._extrusions.commit(baseVtx, vtxCount);\r\n\t// \tthis._attrs.commit(baseVtx, vtxCount);\r\n\t// \tthis._yaws.commit(baseVtx, vtxCount);\r\n\t// }\r\n\r\n\tglProgramDefinition() {\r\n\t\tconst opts = super.glProgramDefinition();\r\n\t\treturn {\r\n\t\t\t...opts,\r\n\t\t\tattributes: {\r\n\t\t\t\taFillColour: this._attrs.getBindableAttribute(0),\r\n\t\t\t\taBorderColour: this._attrs.getBindableAttribute(1),\r\n\r\n\t\t\t\t// Per-triangle border and feather width\r\n\t\t\t\taBorder: this._attrs.getBindableAttribute(2),\r\n\r\n\t\t\t\t// Per-vertex distance to edge\r\n\t\t\t\taEdge: this._attrs.getBindableAttribute(3),\r\n\r\n\t\t\t\t...opts.attributes,\r\n\t\t\t},\r\n\t\t\tuniforms: {\r\n\t\t\t\tuPixelSize: \"vec2\",\r\n\t\t\t\t...opts.uniforms,\r\n\t\t\t},\r\n\t\t\tvertexShaderMain: `\r\n\t\t\t\tvFillColour = aFillColour;\r\n\t\t\t\tvBorderColour = aBorderColour;\r\n\t\t\t\tvBorder = aBorder;\r\n\t\t\t\tvEdge = aEdge;\r\n\r\n\t\t\t\tgl_Position = vec4(\r\n\t\t\t\t\tvec3(aCoords, 1.0) * uTransformMatrix +\r\n\t\t\t\t\tvec3(aExtrude * uPixelSize, 0.0)\r\n\t\t\t\t\t, 1.0);\r\n\t\t\t`,\r\n\t\t\tvaryings: {\r\n\t\t\t\tvFillColour: \"vec4\",\r\n\t\t\t\tvBorderColour: \"vec4\",\r\n\t\t\t\tvBorder: \"vec2\",\r\n\t\t\t\tvEdge: \"vec3\",\r\n\t\t\t},\r\n\t\t\tfragmentShaderMain: `\r\n\t\t\t\tfloat edgeDistance = min(min(vEdge.x, vEdge.y), vEdge.z);\r\n\r\n\t\t\t\tif (edgeDistance < vBorder.x) {\r\n\t\t\t\t\tgl_FragColor = vBorderColour;\r\n\t\t\t\t\tgl_FragColor.a *= min(1., edgeDistance / vBorder.y);\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// gl_FragColor = vFillColour /** edgeDistance / 16.*/;\r\n\t\t\t\t\tgl_FragColor = mix(vBorderColour, vFillColour, (edgeDistance - vBorder.x) / vBorder.y);\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// gl_FragColor.rgb = vEdge / 16.;\r\n\t\t\t\t// gl_FragColor.a = 1.;\r\n\t\t\t`,\r\n\t\t};\r\n\t}\r\n\r\n\t// glIdProgramDefinition() {\r\n\t// \tconst opts = super.glIdProgramDefinition();\r\n\t// \treturn {\r\n\t// \t\t...opts,\r\n\t// \t\tfragmentShaderSource: `\r\n\t// \t\t\tvoid main() {\r\n\t// \t\t\t\tif (vId.a > 0.0 && vColour.a > 0.0) {\r\n\t// \t\t\t\t\tgl_FragColor = vId;\r\n\t// \t\t\t\t} else {\r\n\t// \t\t\t\t\tdiscard;\r\n\t// \t\t\t\t}\r\n\t// \t\t\t}\r\n\t// \t\t`,\r\n\t// \t};\r\n\t// }\r\n\r\n\t_getStridedArrays(maxVtx, maxIdx) {\r\n\t\treturn [\r\n\t\t\t// Extrusion\r\n\t\t\tthis._extrusions.asStridedArray(maxVtx),\r\n\t\t\t// Fill colour\r\n\t\t\tthis._attrs.asStridedArray(0, maxVtx),\r\n\t\t\t// Border colour\r\n\t\t\tthis._attrs.asStridedArray(1),\r\n\t\t\t// Border+feather\r\n\t\t\tthis._attrs.asStridedArray(2),\r\n\t\t\t// Distance to edge\r\n\t\t\tthis._attrs.asStridedArray(3),\r\n\t\t\t// Triangle indices\r\n\t\t\tthis._indices.asTypedArray(maxIdx),\r\n\t\t];\r\n\t}\r\n\r\n\t// The map will call resize() on acetates when needed - besides redoing the\r\n\t// framebuffer with the new size, this needs to reset the uniform uPixelSize.\r\n\tresize(w, h) {\r\n\t\tsuper.resize(w, h);\r\n\t\tconst dpr2 = (devicePixelRatio ?? 1) * 2;\r\n\t\tthis._programs.setUniform(\"uPixelSize\", [dpr2 / w, dpr2 / h]);\r\n\t}\r\n}\r\n","import AcetateHeadingTriangle from \"../acetates/AcetateHeadingTriangle.mjs\";\r\nimport ExtrudedPoint from \"./ExtrudedPoint.mjs\";\r\nimport parseColour from \"../3rd-party/css-colour-parser.mjs\";\r\n\r\n/**\r\n * @class HeadingTriangle\r\n * @inherits ExtrudedPoint\r\n * @relationship drawnOn AcetateHeadingTriangle\r\n *\r\n * A small triangle, meant to signify heading (or direction, or course) of\r\n * a feature; should be used in conjunction with other symbol to represent\r\n * the feature itself.\r\n *\r\n * Works with point geometries only.\r\n */\r\n\r\nexport default class HeadingTriangle extends ExtrudedPoint {\r\n\tstatic Acetate = AcetateHeadingTriangle;\r\n\r\n\t#distance;\r\n\t#width;\r\n\t#length;\r\n\t#fillColour;\r\n\t#borderColour;\r\n\t#borderWidth;\r\n\t#feather;\r\n\t#yaw;\r\n\r\n\tconstructor(\r\n\t\tgeom,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @section\r\n\t\t\t * @aka HeadingTriangle Options\r\n\t\t\t * @option distance: Number = 16\r\n\t\t\t * The distance from the geometry to the base of the triangle, in CSS pixels.\r\n\t\t\t *\r\n\t\t\t * @option width: Number = 8\r\n\t\t\t * The width of the triangle, in CSS pixels.\r\n\t\t\t *\r\n\t\t\t * @option length: Number = 6\r\n\t\t\t * The length of the triangle, in CSS pixels.\r\n\t\t\t *\r\n\t\t\t * @option fillColour: Colour = 'white'\r\n\t\t\t * The colour for the inside area of the triangle.\r\n\t\t\t *\r\n\t\t\t * @option borderColour: Colour = 'black'\r\n\t\t\t * The colour for the border of the triangle.\r\n\t\t\t *\r\n\t\t\t * @option borderWidth: Number = 1\r\n\t\t\t * The width of the border, in CSS pixels.\r\n\t\t\t *\r\n\t\t\t * @option feather: Number = 0.5\r\n\t\t\t * The width of the antialiasing feather, in CSS pixels.\r\n\t\t\t */\r\n\t\t\tdistance = 16,\r\n\t\t\twidth = 8,\r\n\t\t\tlength = 6,\r\n\t\t\tfillColour = [255, 255, 255, 255],\r\n\t\t\tborderColour = [0, 0, 0, 255],\r\n\t\t\tborderWidth = 1,\r\n\t\t\tfeather = 0.5,\r\n\r\n\t\t\t/**\r\n\t\t\t * @option yaw: Number = 0\r\n\t\t\t * The yaw rotation of the triangle, in clockwise degrees from \"north\"\r\n\t\t\t */\r\n\t\t\tyaw = 0,\r\n\r\n\t\t\t...opts\r\n\t\t}\r\n\t) {\r\n\t\tsuper(geom, opts);\r\n\r\n\t\tthis.#distance = distance - feather / 2;\r\n\t\tthis.#width = width + feather;\r\n\t\tthis.#length = length + feather / 2;\r\n\t\tthis.#fillColour = this.constructor._parseColour(fillColour);\r\n\t\tthis.#borderColour = this.constructor._parseColour(borderColour);\r\n\t\tthis.#borderWidth = borderWidth;\r\n\t\tthis.#feather = feather;\r\n\r\n\t\tthis.#yaw = yaw;\r\n\r\n\t\tthis.attrLength = 3;\r\n\t\tthis.idxLength = 3;\r\n\t}\r\n\r\n\t/**\r\n\t * @section\r\n\t * @property yaw: Number\r\n\t * Runtime value of the `yaw` option: the yaw rotation of the sprite,\r\n\t * in clockwise degrees. Can be updated.\r\n\t */\r\n\tset yaw(yaw) {\r\n\t\tthis.#yaw = yaw;\r\n\t\tthis._refreshExtrusion();\r\n\t}\r\n\tget yaw() {\r\n\t\treturn this.#yaw;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Acetate interface\r\n\t * @method _setGlobalStrides(strideExtrusion: StridedTypedArray, strideFillColour: StridedTypedArray, strideBorderColour: StridedTypedArray, strideBorder: StridedTypedArray, strideEdgeDistance: StridedTypedArray, typedIdxs: TypedArray, feather: Number): undefined\r\n\t * Sets the appropriate values into the strided arrays, based on the\r\n\t * symbol's `attrBase` and `idxBase`.\r\n\t *\r\n\t * Receives the width of the feathering as a parameter, in pixels.\r\n\t */\r\n\t_setGlobalStrides(\r\n\t\tstrideExtrusion,\r\n\t\tstrideFillColour,\r\n\t\tstrideBorderColour,\r\n\t\tstrideBorder,\r\n\t\tstrideEdgeDistance,\r\n\t\ttypedIdxs\r\n\t) {\r\n\t\t/// TODO: Remove yaw code, refresh extrusion instead\r\n\r\n\t\tconst yawRadians = (-this.#yaw * Math.PI) / 180;\r\n\t\tconst s = Math.sin(yawRadians);\r\n\t\tconst c = Math.cos(yawRadians);\r\n\t\tconst l = this.#length;\r\n\t\tconst w = this.#width / 2;\r\n\t\tconst d = this.#distance;\r\n\r\n\t\tlet [Δx, Δy] = this.offset;\r\n\t\tΔx -= s * d;\r\n\t\tΔy += c * d;\r\n\r\n\t\t// prettier-ignore\r\n\t\tstrideExtrusion.set([\r\n\t\t\tΔx - s*l, Δy + c*l,\r\n\t\t\tΔx - c*w, Δy - s*w,\r\n\t\t\tΔx + c*w, Δy + s*w\r\n\t\t], this.attrBase);\r\n\r\n\t\tif (!strideFillColour) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tfor (let i = 0; i < 3; i++) {\r\n\t\t\tstrideFillColour.set(this.#fillColour, this.attrBase + i);\r\n\t\t\tstrideBorderColour.set(this.#borderColour, this.attrBase + i);\r\n\t\t\tstrideBorder.set([this.#borderWidth, this.#feather], this.attrBase + i);\r\n\t\t}\r\n\r\n\t\tstrideEdgeDistance.set([this.#length, 0, 0], this.attrBase);\r\n\r\n\t\t// Relation between length & width; half the angle of the triangle tip;\r\n\t\t// same as relative angle from base vertex to create an orthogonal\r\n\t\t// to a side\r\n\t\tconst α = Math.atan2(this.#length, this.#width);\r\n\r\n\t\tconst dist = Math.cos(α) * this.#width;\r\n\r\n\t\tstrideEdgeDistance.set([0, dist, 0], this.attrBase + 1);\r\n\t\tstrideEdgeDistance.set([0, 0, dist], this.attrBase + 2);\r\n\r\n\t\ttypedIdxs.set(\r\n\t\t\t[this.attrBase, this.attrBase + 1, this.attrBase + 2],\r\n\t\t\tthis.idxBase\r\n\t\t);\r\n\t}\r\n\r\n\t_refreshExtrusion() {\r\n\t\tif (!this._inAcetate) {\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\tlet strideExtrude = this._inAcetate._extrusions.asStridedArray();\r\n\t\tthis._setGlobalStrides(strideExtrude);\r\n\r\n\t\tthis._inAcetate._extrusions.commit(this.attrBase, this.attrLength);\r\n\t\tthis._inAcetate.dirty = true;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t_setStridedExtrusion(strideExtrusion) {\r\n\t\tthis._setGlobalStrides(strideExtrusion);\r\n\t}\r\n\r\n\t// Can be overriden by subclasses or the `intensify` decorator\r\n\tstatic _parseColour = parseColour;\r\n}\r\n","import ExtrudedPoint from \"../symbols/ExtrudedPoint.mjs\";\r\n\r\n/**\r\n * @namespace bouncify\r\n * @inherits Symbol Decorator\r\n * @relationship associated ExtrudedPoint\r\n *\r\n * Modifies an `ExtrudedPoint` class (e.g. `Sprite`s, `CircleFill`s, etc) so that\r\n * they bounce in a time-based animation.\r\n *\r\n * @example\r\n *\r\n * A typical use case is to create bouncing sprites (akin to \"bouncing markers\"\r\n * in Leaflet):\r\n *\r\n * ```\r\n * import bouncify from 'gleo/src/symbols/Bouncify.mjs';\r\n *\r\n * const BouncingSprite = bouncify(Sprite);\t// \"Bouncified\" `Sprite` class\r\n * const BouncingSpriteAcetate = BouncingSprite.Acetate;\t// \"Bouncified\" `AcetateSprite` class\r\n * ```\r\n *\r\n * Declare bouncing sprites, as desired (including the bounce-related constructor\r\n * options):\r\n *\r\n * ```\r\n * const bouncySprite = new BouncingSprite(geom, {\r\n * \tbounceHeight: 10,\r\n * \tbounceSquish: [0.8, 1.2],\r\n * \t...spriteOptions\r\n * };\r\n * ```\r\n *\r\n */\r\n\r\nexport default function bouncify(base) {\r\n\tif (!base instanceof ExtrudedPoint) {\r\n\t\tthrow new Error(\r\n\t\t\t\"The 'bounficy' symbol decorator can only be applied to extruded points\"\r\n\t\t);\r\n\t}\r\n\r\n\tclass BouncifiedAcetate extends base.Acetate {\r\n\t\tconstructor(glii, opts) {\r\n\t\t\tsuper(glii, opts);\r\n\r\n\t\t\tthis._bounceAttr = new this.glii.InterleavedAttributes(\r\n\t\t\t\t{\r\n\t\t\t\t\tusage: this.glii.STATIC_DRAW,\r\n\t\t\t\t\tsize: 1,\r\n\t\t\t\t\tgrowFactor: 1.2,\r\n\t\t\t\t},\r\n\t\t\t\t[\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t// squish X,Y + squash X,Y\r\n\t\t\t\t\t\tglslType: \"vec4\",\r\n\t\t\t\t\t\ttype: Float32Array,\r\n\t\t\t\t\t\tnormalized: false,\r\n\t\t\t\t\t},\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t// bounce height\r\n\t\t\t\t\t\tglslType: \"float\",\r\n\t\t\t\t\t\ttype: Float32Array,\r\n\t\t\t\t\t\tnormalized: false,\r\n\t\t\t\t\t},\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t// per-symbol offset\r\n\t\t\t\t\t\tglslType: \"vec2\",\r\n\t\t\t\t\t\ttype: Float32Array,\r\n\t\t\t\t\t\tnormalized: false,\r\n\t\t\t\t\t},\r\n\t\t\t\t]\r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\tglProgramDefinition() {\r\n\t\t\tconst opts = super.glProgramDefinition();\r\n\r\n\t\t\tconst extrudeRegExp = /(\\W)aExtrude(\\W)/;\r\n\t\t\tfunction extrudeReplacement(_, p1, p2) {\r\n\t\t\t\treturn `${p1}bounce(aExtrude)${p2}`;\r\n\t\t\t}\r\n\t\t\tconst bounceGlslFunction = `\r\n\t\t\t\tvec2 bounce(vec2 extrusion){\r\n\t\t\t\t\tvec2 squish = vec2(\r\n\t\t\t\t\t\tmix(aBounceSquish.z, aBounceSquish.x, uBounceCycle),\r\n\t\t\t\t\t\tmix(aBounceSquish.w, aBounceSquish.y, uBounceCycle)\r\n\t\t\t\t\t);\r\n\t\t\t\t\treturn ((extrusion - aBounceBaseOffset) * squish) +\r\n\t\t\t\t\t aBounceBaseOffset +\r\n\t\t\t\t\t vec2(0., aBounceHeight * uBounceCycle );\r\n\t\t\t\t}`;\r\n\r\n\t\t\treturn {\r\n\t\t\t\t...opts,\r\n\t\t\t\tattributes: {\r\n\t\t\t\t\taBounceSquish: this._bounceAttr.getBindableAttribute(0),\r\n\t\t\t\t\taBounceHeight: this._bounceAttr.getBindableAttribute(1),\r\n\t\t\t\t\taBounceBaseOffset: this._bounceAttr.getBindableAttribute(2),\r\n\t\t\t\t\t...opts.attributes,\r\n\t\t\t\t},\r\n\t\t\t\tuniforms: {\r\n\t\t\t\t\t// uNow: \"float\",\r\n\t\t\t\t\tuBounceCycle: \"float\",\r\n\t\t\t\t\t...opts.uniforms,\r\n\t\t\t\t},\r\n\t\t\t\tvertexShaderSource: opts.vertexShaderSource + bounceGlslFunction,\r\n\t\t\t\tvertexShaderMain: opts.vertexShaderMain.replace(\r\n\t\t\t\t\textrudeRegExp,\r\n\t\t\t\t\textrudeReplacement\r\n\t\t\t\t),\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\t_getStridedArrays(maxVtx, maxIdx) {\r\n\t\t\treturn [\r\n\t\t\t\t// Bounce squish\r\n\t\t\t\tthis._bounceAttr.asStridedArray(0, maxVtx),\r\n\t\t\t\t// Bounce height\r\n\t\t\t\tthis._bounceAttr.asStridedArray(1),\r\n\t\t\t\t// Bounce base offset\r\n\t\t\t\tthis._bounceAttr.asStridedArray(2),\r\n\t\t\t\t// Parent strided arrays\r\n\t\t\t\t...super._getStridedArrays(maxVtx, maxIdx),\r\n\t\t\t];\r\n\t\t}\r\n\r\n\t\t_commitStridedArrays(baseVtx, vtxCount) {\r\n\t\t\tthis._bounceAttr.commit(baseVtx, vtxCount);\r\n\t\t\treturn super._commitStridedArrays(baseVtx, vtxCount);\r\n\t\t}\r\n\r\n\t\tredraw() {\r\n\t\t\t// this._programs.setUniform(\"uNow\", performance.now());\r\n\t\t\tthis._programs.setUniform(\r\n\t\t\t\t\"uBounceCycle\",\r\n\t\t\t\tMath.abs(Math.sin(performance.now() / (1000 / Math.PI)))\r\n\t\t\t);\r\n\t\t\treturn super.redraw.apply(this, arguments);\r\n\t\t}\r\n\r\n\t\t// An animated Acetate is always dirty as long as it has any symbols,\r\n\t\t// meaning it wants to render at every frame.\r\n\t\tget dirty() {\r\n\t\t\treturn super.dirty || this._knownSymbols.length > 0;\r\n\t\t}\r\n\t\tset dirty(d) {\r\n\t\t\treturn (super.dirty = d);\r\n\t\t}\r\n\r\n\t\tdestroy() {\r\n\t\t\tthis._bounceAttr.destroy();\r\n\t\t\treturn super.destroy();\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @miniclass Bouncified GleoSymbol (bouncify)\r\n\t *\r\n\t * A \"bouncified\" symbol accepts these additional constructor options:\r\n\t *\r\n\t * @option bounceHeight: Number = 10\r\n\t * The height of the boucing, in CSS pixels. This will always be vertical\r\n\t * relative to the viewport.\r\n\t *\r\n\t * @option bounceSquish: Array of Number = [0.9, 1.1]\r\n\t * The symbol will \"squish\" when at the apex of the bounce, effectively being\r\n\t * scaled horizontally/vertically; these values control the scale factors.\r\n\t * e.g. the default of `[0.9, 1.1]` means that the symbol will be 90% wide and\r\n\t * 110% high at the apex, compared to its at-rest size.\r\n\t *\r\n\t * @option bounceSquash: Array of Number = [1.1, 0.7]\r\n\t * Akin to `bounceSquish`, but for the bottom instant of the bounce.\r\n\t */\r\n\r\n\t/// TODO: Bounce speed/time\r\n\r\n\treturn class BouncifiedSymbol extends base {\r\n\t\tstatic Acetate = BouncifiedAcetate;\r\n\r\n\t\t#bounceSquish;\r\n\t\t#bounceHeight;\r\n\r\n\t\tconstructor(\r\n\t\t\tgeom,\r\n\t\t\t{\r\n\t\t\t\tbounceSquish = [0.9, 1.1],\r\n\t\t\t\tbounceSquash = [1.1, 0.7],\r\n\t\t\t\tbounceHeight = 10,\r\n\t\t\t\t...opts\r\n\t\t\t}\r\n\t\t) {\r\n\t\t\tsuper(geom, opts);\r\n\t\t\tthis.#bounceSquish = [\r\n\t\t\t\tbounceSquish[0],\r\n\t\t\t\tbounceSquish[1],\r\n\t\t\t\tbounceSquash[0],\r\n\t\t\t\tbounceSquash[1],\r\n\t\t\t];\r\n\t\t\tthis.#bounceHeight = bounceHeight;\r\n\t\t}\r\n\r\n\t\t_setGlobalStrides(\r\n\t\t\tstrideSquish,\r\n\t\t\tstrideBounceHeight,\r\n\t\t\tstrideBounceBaseOffset,\r\n\t\t\t...strides\r\n\t\t) {\r\n\t\t\tfor (let i = this.attrBase, t = this.attrBase + this.attrLength; i < t; i++) {\r\n\t\t\t\tstrideSquish.set(this.#bounceSquish, i);\r\n\t\t\t\tstrideBounceHeight.set([this.#bounceHeight], i);\r\n\t\t\t\tstrideBounceBaseOffset.set(this.offset, i);\r\n\t\t\t}\r\n\r\n\t\t\treturn super._setGlobalStrides(...strides);\r\n\t\t}\r\n\r\n\t\t_setStrideExtrusion(strideExtrusion) {\r\n\t\t\tconst strideBaseOffset = this._inAcetate._bounceAttr.asStridedArray(2);\r\n\t\t\tfor (let i = this.attrBase, t = this.attrBase + this.attrLength; i < t; i++) {\r\n\t\t\t\tstrideBaseOffset.set(this.offset, i);\r\n\t\t\t}\r\n\t\t\tthis._inAcetate._bounceAttr.commit(this.attrBase, this.attrLength);\r\n\r\n\t\t\treturn super._setStrideExtrusion(strideExtrusion);\r\n\t\t}\r\n\t};\r\n}\r\n","import { createMarkerPath } from \"./marker-path.js\";\r\nimport { map } from \"./map-base.js\";\r\nimport {convertNamedColorToArray} from \"./color.js\";\r\n\r\nimport GleoSymbol from \"./gleo/src/symbols/Symbol.mjs\";\r\nimport HeadingTriangle from \"./gleo/src/symbols/HeadingTriangle.mjs\";\r\nimport TintedSprite from \"./gleo/src/symbols/TintedSprite.mjs\";\r\nimport bouncify from \"./gleo/src/symboldecorators/bouncify.mjs\";\r\n// import HTMLPin from \"./gleo/src/pin/HTMLPin.mjs\";\r\n// import LatLng from \"./gleo/src/geometry/LatLng.mjs\";\r\n\r\nimport parseColour from \"./gleo/src/3rd-party/css-colour-parser.mjs\";\r\n\r\nimport L from \"leaflet\";\r\n\r\nconst BouncySprite = bouncify(TintedSprite);\r\n\r\n/**\r\n * Similar to a Gleo MultiSymbol, but specific to assets. Groups together:\r\n * - A sprite for the asset \"at rest\", i.e. not bouncing\r\n * - A sprite for the asset when selected, i.e. bouncing\r\n * - A sprite for the asset's event/status\r\n * - A HeadingTriangle for the heading, only for some asset types\r\n * - Functionality to get a popup/tooltip HTML anchor\r\n *\r\n */\r\n\r\nexport default class AssetSprite extends GleoSymbol {\r\n\t#spriteBase;\r\n\t#spriteBouncing;\r\n\t#headingTriangle;\r\n\t#spriteStatus;\r\n\r\n\t#bouncing = false;\r\n\t#tintBase = false;\r\n\r\n\t#assetId;\r\n\t#isFirst;\r\n\t#isLast;\r\n\t#heading;\r\n\r\n\tconstructor(geom,\r\n\t\t\t\ttype = \"Generic\",\r\n\t\t\t\tcolour = \"red\",\r\n\t\t\t\theading = undefined,\t/// A number from 0 to 360 (inclusive). 0 means no arrow; otherwise degrees clockwise from north. Use 360 for north.\r\n\t\t\t\talpha = 255,\r\n\t\t\t\tid = undefined,\t/// TODO\r\n\t\t\t\tevtType = undefined,\r\n\t\t\t\tisFirst = undefined,\t/// TODO\r\n\t\t\t\tisLast = undefined,\t/// TODO\r\n\t) {\r\n\t\tsuper();\r\n\r\n\t\t// Sanitize parameter - some other parts of the system expect an empty string\r\n\t\tif (heading === \"\") {\r\n\t\t\theading = undefined;\r\n\t\t}\r\n\r\n\t\tthis.#assetId = id;\r\n\t\tthis.#heading = heading;\r\n\t\tthis.#isFirst = isFirst;\r\n\t\tthis.#isLast = isLast;\r\n\t\tthis.geometry = geom;\r\n\r\n\t\tif (evtType) {\r\n\r\n\t\t\tlet statusSpriteOffset;\r\n\r\n\t\t\t// See also the EVENTS_* constants in asset-events.js\r\n\r\n\t\t\tswitch(evtType) {\r\n\r\n\t\t\t\tcase 1: // \"Start\": green circle - no longer used?\r\n\t\t\t\tcase 2: // \"Stop\": red circle - no longer used?\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 133:\t// \"Low Battery\"\r\n\t\t\t\t\tstatusSpriteOffset = [80, 160];\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 274:\t// Eye alert, image response\r\n\t\t\t\tcase 275:\t// Eye alert, immediate image\r\n\t\t\t\t\tstatusSpriteOffset = [0, 160];\t// Camera, grey lens\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 272:\t// Eye alert, driver fatigue\r\n\t\t\t\tcase 273:\t// Eye alert, fatigue image\r\n\t\t\t\t\tstatusSpriteOffset = [240,80];\t// Camera, red lens\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 9:\t// \"Emergency\"\r\n\t\t\t\t\tstatusSpriteOffset = [0,80];\t// Red exclamation sign\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 14:\t// \"Alert triggered\" -\r\n\t\t\t\tcase 126:\t// \"Help\"\r\n\t\t\t\t\tstatusSpriteOffset = [0,0];\t// Yellow exclamation sign\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 7:\t// \"Enter fence\"\r\n\t\t\t\t\tstatusSpriteOffset = [80,80];\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 8:\t// \"Exit fence\"\r\n\t\t\t\t\tstatusSpriteOffset = [160,80];\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 10:\t// \"Reset\" - on/off symbol\r\n\t\t\t\t\tstatusSpriteOffset = [0,240];\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 54:\t// \"Driver ID\"\r\n\t\t\t\tcase 375:\t// \"Encrypted\"\r\n\t\t\t\t\tstatusSpriteOffset = [240,0];\t// Yellow padlock\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 121:\t// \"Driver ID Failed\"\r\n\t\t\t\t\tstatusSpriteOffset = [160,0];\t// Red padlock\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 122:\t// \"Driver Logoff\"\r\n\t\t\t\tcase 376:\t// \"Unencrypted\"\r\n\t\t\t\t\tstatusSpriteOffset = [320,0];\t// Yellow padlock, open\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 50:\t// \"Stop ETA\":\r\n\t\t\t\t\tstatusSpriteOffset = [240, 240];\t// Stop sign with clock hands\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 49:\t// \"Stop status\":\r\n\t\t\t\t\tstatusSpriteOffset = [320, 240];\t// Stop sign with interrogation\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 120:\t// \"Quick message\"\r\n\t\t\t\tcase 123:\t// \"Quick message response\"\r\n\t\t\t\t\tstatusSpriteOffset = [240, 160];\t// Speech bubble, slanted\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 51:\t// \"Text message\"\r\n\t\t\t\t\tstatusSpriteOffset = [160, 160];\t// Speech bubble, straight\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 58:\t// \"Refuel\"\r\n\t\t\t\t\tstatusSpriteOffset = [320, 160];\t// Fuel pump\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 42:\t// \"Service meter\" - Grey wrench\r\n\t\t\t\t\tstatusSpriteOffset = [80,240];\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 15:\t// \"Check-in\" - Green checkmark\r\n\t\t\t\t\tstatusSpriteOffset = [80,0];\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 5:\t// \"Speeding start\"\r\n\t\t\t\t\tstatusSpriteOffset = [160,320];\t// Red speedometer\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 45: // \"Towing start\"\r\n\t\t\t\t\tstatusSpriteOffset = [320,320];\t// Yellow speedometer\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 32: // \"Idling start\"\r\n\t\t\t\tcase 48: // \"Moving stop\"\r\n\t\t\t\t\tstatusSpriteOffset = [320,400];\t// Grey speedometer\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 6: // \"Speeding stop\"\r\n\t\t\t\tcase 46: // \"Towing end\"\r\n\t\t\t\tcase 98: // \"Idling stop\"\r\n\t\t\t\tcase 47: // \"Moving start\"\r\n\t\t\t\t\tstatusSpriteOffset = [240,320];\t// Green speedometer\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 21: \t// \"Ignition on\"\r\n\t\t\t\t\tstatusSpriteOffset = [0,320];\t// Green engine block\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 22: \t// \"Ignition off\"\r\n\t\t\t\t\tstatusSpriteOffset = [80,320];\t// Grey engine block\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 35:\t// \"Antenna cut start\"\r\n\t\t\t\t\tstatusSpriteOffset = [80,480];\t// Antenna crossed out\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 129:\t// \"Antenna cut end\"\r\n\t\t\t\t\tstatusSpriteOffset = [0,480];\t// Antenna _not_ crossed out\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 36:\t// \"GPS jamming start\"\r\n\t\t\t\t\tstatusSpriteOffset = [240,400];\t// circle-crosshair, crossed out\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 37:\t// \"GPS jamming end\"\r\n\t\t\t\t\tstatusSpriteOffset = [160,400];\t// circle-crosshair, _not_ crossed out\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 38:\t// \"Cell jamming start\"\r\n\t\t\t\t\tstatusSpriteOffset = [80,400];\t// cell reception bars, crossed out\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase 39:\t// \"Cell jamming end\"\r\n\t\t\t\t\tstatusSpriteOffset = [0,400];\t// cell reception bars, _not_ crossed out\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tdefault:\r\n\t\t\t\t\t// noop\r\n\t\t\t}\r\n\r\n\t\t\tif (statusSpriteOffset) {\r\n\t\t\t\tthis.#spriteStatus = new TintedSprite(geom, {\r\n\t\t\t\t\timage: \"/content/images/markers/status-spritesheet.png\",\r\n\t\t\t\t\tspriteAnchor: [0, 80],\r\n\t\t\t\t\t// spriteAnchor: [0, 120],\r\n\t\t\t\t\toffset: [6,6],\r\n\t\t\t\t\tspriteSize: [80, 80],\r\n\t\t\t\t\tspriteStart: statusSpriteOffset,\r\n\t\t\t\t\ttint: [255,255,255,255],\r\n\t\t\t\t\tspriteScale: 0.125\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t}\r\n\r\n\t\tthis.replaceIcon(type, colour, alpha);\r\n\r\n\t}\r\n\r\n\tfire(evType, detail) {\r\n\t\t// Hook up propagation of synthetic click events\r\n\t\tif (evType === \"click\") {\r\n\t\t\tthis.#spriteBase.fire(\"click\");\r\n\t\t} else {\r\n\t\t\treturn super.fire(evType, detail);\r\n\t\t}\r\n\t}\r\n\r\n\taddTo(target) {\r\n\t\ttarget.multiAdd(this.symbols);\r\n\t\tthis.target = target;\r\n\t\treturn this;\r\n\t}\r\n\r\n\tremove() {\r\n\t\tthis.symbols.forEach((s) => s.remove());\r\n\t\tthis.target = undefined;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// addTo(target) {\r\n\t// \t// target.multiAdd(this.#symbols);\r\n\t// \tthis.#spriteBase.addTo(target);\r\n\t// \tthis.#target = target;\r\n\t// \treturn this;\r\n\t// }\r\n //\r\n\t// remove() {\r\n\t// \t// this.#symbols.forEach((s) => s.remove());\r\n\t// \tthis.#spriteBase.remove();\r\n\t// \tthis.#spriteBouncing.remove();\r\n\t// \tthis.#spriteHeading.remove();\r\n\t// \tthis.#bouncing = false;\r\n\t// \tthis.#target = undefined;\r\n\t// \treturn this;\r\n\t// }\r\n\r\n\tget geometry() {\r\n\t\treturn super.geometry;\r\n\t}\r\n\tset geometry(geom) {\r\n\t\tsuper.geometry = geom;\r\n\t\tthis.#spriteBase && (this.#spriteBase.geometry = geom);\r\n\t\tthis.#spriteBouncing && (this.#spriteBouncing.geometry = geom);\r\n\t\tthis.#headingTriangle && (this.#headingTriangle.geometry = geom);\r\n\t}\r\n\r\n\tisActive() {\r\n\t\treturn this.#spriteBase.isActive();\r\n\t}\r\n\r\n\r\n\tget offset() {\r\n\t\treturn this.#spriteBase.offset;\r\n\t}\r\n\r\n\tset offset(o){\r\n\t\t// const [oldX, oldY] = this.#spriteBase.offset;\r\n\t\tthis.#spriteBase.offset = o;\r\n\t\tthis.#spriteBouncing && (this.#spriteBouncing.offset = o);\r\n\t\tthis.#headingTriangle && (this.#headingTriangle.offset = o);\r\n\r\n\t\tif (this.#spriteStatus) {\r\n\t\t\t// Status offset from base sprite is always [6, 6]\r\n\t\t\t// If it was different, it would have to be cached or recalculated\r\n\t\t\t// from the old base offset.\r\n\t\t\tthis.#spriteStatus.offset = [o[0] + 6,o[1] + 6];\r\n\t\t}\r\n\r\n\t}\r\n\r\n\tget tint() {\r\n\t\treturn this.#spriteBase.tint;\r\n\t}\r\n\r\n\tset tint(t) {\r\n\t\tif (this.#tintBase) {\r\n\t\t\tthis.#spriteBase.tint = t;\r\n\t\t\tthis.#spriteBouncing.tint = t;\r\n\t\t}\r\n\t\tif (this.#headingTriangle) {\r\n\t\t\tthis.#headingTriangle.fillColour = t;\r\n\t\t}\r\n\t}\r\n\r\n\r\n\t// For duplicating the image when part of a cluster\r\n\tget spriteBase() {\r\n\t\treturn this.#spriteBase;\r\n\t}\r\n\r\n\t// Compat with Leaflet bouncing markers\r\n\tisBouncing() {\r\n\t\treturn this.#bouncing;\r\n\t}\r\n\r\n\tsetBouncingOptions() {\r\n\t\treturn this;\r\n\t}\r\n\r\n\ttoggleBouncing() {\r\n\t\tconst target = this.#spriteBase._inAcetate?.platina;\r\n\t\tif (target) {\r\n\t\t\tthis.#spriteBase.remove();\r\n\t\t\tthis.#spriteBouncing.addTo(target);\r\n\t\t}\r\n\t\tthis.symbols = [this.#spriteBouncing];\r\n\t\tif (this.#headingTriangle) {\r\n\t\t\tthis.symbols.push(this.#headingTriangle);\r\n\t\t}\r\n\t\tif (this.#spriteStatus) {\r\n\t\t\tthis.symbols.push(this.#spriteStatus);\r\n\t\t}\r\n\t\tthis.#bouncing = true;\r\n\t\treturn this;\r\n\t}\r\n\tstopBouncing() {\r\n\t\tconst target = this.#spriteBouncing._inAcetate?.platina;\r\n\t\tif (target) {\r\n\t\t\tthis.#spriteBouncing.remove();\r\n\t\t\tthis.#spriteBase.addTo(target);\r\n\t\t}\r\n\t\tthis.symbols = [this.#spriteBase];\r\n\t\tif (this.#headingTriangle) {\r\n\t\t\tthis.symbols.push(this.#headingTriangle);\r\n\t\t}\r\n\t\tif (this.#spriteStatus) {\r\n\t\t\tthis.symbols.push(this.#spriteStatus);\r\n\t\t}\r\n\t\tthis.#bouncing = false;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// Compat with Leaflet bouncing markers\r\n\tgetLatLng() {\r\n\t\treturn this.geometry.asLatLng();\r\n\t}\r\n\r\n\r\n\t// Event handling as per Gleo MultiSymbol\r\n\ton(evtype, handler) {\r\n\t\tthis.#spriteBase.on(evtype, handler);\r\n\t\tthis.#spriteBouncing.on(evtype, handler);\r\n\t\treturn this;\r\n\t}\r\n\toff(evtype, handler) {\r\n\t\tthis.#spriteBase.off(evtype, handler);\r\n\t\tthis.#spriteBouncing.off(evtype, handler);\r\n\t\treturn this;\r\n\t}\r\n\tonce(evtype, handler) {\r\n\t\tthis.#spriteBase.once(evtype, handler);\r\n\t\tthis.#spriteBouncing.once(evtype, handler);\r\n\t\treturn this;\r\n\t}\r\n\tfire(evtype, detail) {\r\n\t\tconst target =\r\n\t\t\tthis.#spriteBouncing?._inAcetate\r\n\t\t\t? this.#spriteBouncing\r\n\t\t\t: this.#spriteBase;\r\n\r\n\t\ttarget && target.fire(evtype, detail);\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// addEventListener(evtype, handler) {\r\n\t// \tthis.#symbols.forEach((s) => s.addEventListener(evtype, handler));\r\n\t// \treturn this;\r\n\t// }\r\n\t// removeEventListener(evtype, handler) {\r\n\t// \tthis.#symbols.forEach((s) => s.removeEventListener(evtype, handler));\r\n\t// \treturn this;\r\n\t// }\r\n\t// dispatchEvent(evtype, handler) {\r\n\t// \tthis.#symbols.forEach((s) => s.dispatchEvent(evtype, handler));\r\n\t// \treturn this;\r\n\t// }\r\n\r\n\r\n\t// For compatibility with Leaflet functionality that depends on\r\n\t// getting a reference to the DOM element for the L.Marker.\r\n\t// i.e. for the bootstrap tooltips/popups\r\n\t// We'll assume that there'll be only one tooltip/popup at any given time,\r\n\t// and the approach will be to maintain just one L.DivIcon for the entire class,\r\n\t// resetting its geometry as needed.\r\n\r\n\tstatic #popupAnchor;\r\n\r\n\tstatic get popupAnchor() {\r\n\t\tif (this.#popupAnchor ) {\r\n\t\t\treturn this.#popupAnchor;\r\n\t\t}\r\n\t\tthis.#popupAnchor = L.marker([0,0], {icon: L.divIcon(), opacity: 0}).addTo(map);\r\n\t\tconst el = this.#popupAnchor.getElement();\r\n\t\tel.style.pointerEvents = 'none';\r\n\t\tel.style.width = 0;\r\n\t\tel.style.height = 0;\r\n\t\tel.style.border = \"none\";\r\n\t\tel.style.background = \"transparent\";\r\n\r\n\t\treturn this.#popupAnchor;\r\n\t}\r\n\r\n\tgetElement() {\r\n\t\tconst [lng, lat] = this.geometry.toCRS(\"EPSG:4326\").coords;\r\n\r\n\t\tconst anchor = this.constructor.popupAnchor;\r\n\t\t// set width/height based on baseSprite size for offsets/positioning\r\n\t\tanchor.setIcon(L.divIcon({ iconSize: this.#spriteBase._spriteSize }));\r\n\t\tanchor.setLatLng([lat, lng]);\r\n\t\tconst anchorEl = anchor.getElement();\r\n\t\t//anchorEl.style.marginLeft = `${this.offset[0] + 10}px`;\r\n\t\t//anchorEl.style.marginTop = `${-this.offset[1]}px`;\r\n\t\treturn anchorEl;\r\n\t}\r\n\r\n\r\n\t/// Replaces the base icon and colour. This will re-request the sprite image\r\n\t/// (specially if it's set to a user-uploaded one) and re-tint all components.\r\n\treplaceIcon(type, colour, alpha) {\r\n\t\tconst target =\r\n\t\t\tthis.#spriteBouncing?._inAcetate?.platina ||\r\n\t\t\tthis.#spriteBase?._inAcetate?.platina;\r\n\r\n\t\tlet originalColour = colour;\r\n\t\tcolour = parseColour(convertNamedColorToArray(colour));\r\n\t\tif (colour[3] == 255 && alpha !== undefined) {\r\n\t\t\tcolour[3] = alpha;\r\n\t\t}\r\n\r\n\t\t// The base sprite is tinted only when its icon is *not* a user-uploaded\r\n\t\t// image.\r\n\t\t//this.#tintBase = type !== 'Upload';\r\n\t\tthis.#tintBase = false;\r\n\r\n\t\t// this.geometry = geom;\r\n\t\tconst imagePath = createMarkerPath(\r\n\t\t\ttype,\t// Asset class\r\n\t\t\toriginalColour,\t\t// Colour, handled by tint\r\n\t\t\tnull, \t// Course, handled by heading triangle\r\n\t\t\tnull, \t// Alpha, handled by tint\r\n\t\t\tthis.#assetId, \t// Asset ID\r\n\t\t\tfalse,\r\n\t\t\t'',\t// Event type, handled by status sprite\r\n\t\t\tthis.#isFirst,\r\n\t\t\tthis.#isLast\r\n\t\t);\r\n\r\n\t\tif (!this.#spriteBase) {\r\n\t\t\tthis.#spriteBase = new TintedSprite(this.geometry, {\r\n\t\t\t\timage: imagePath,\r\n\t\t\t\tspriteAnchor: [18, 18],\r\n\t\t\t\tspriteSize: [36, 36],\r\n\t\t\t\ttint: this.#tintBase ? colour : [255, 255, 255, alpha] ,\r\n\t\t\t\tinteractive: true,\r\n\t\t\t\tcursor: 'pointer',\r\n\t\t\t\toffset: [0,0],\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\tthis.#spriteBase.tint = this.#tintBase ? colour : [255, 255, 255, alpha];\r\n\t\t\tthis.#spriteBase.replaceImage({\r\n\t\t\t\timage: imagePath,\r\n\t\t\t\tspriteAnchor: [18, 18],\r\n\t\t\t\tspriteSize: [36, 36],\r\n\t\t\t}, true);\r\n\t\t}\r\n\r\n\t\tif (this.#heading !== undefined) {\r\n\t\t\t// TODO: replace triangle's colour instead of removing+readding\r\n\t\t\tif (this.#headingTriangle?._inAcetate) {\r\n\t\t\t\tthis.#headingTriangle?.remove();\r\n\t\t\t}\r\n\t\t\tthis.#headingTriangle = new HeadingTriangle(this.geometry, {\r\n\t\t\t\tdistance: 10,\r\n\t\t\t\tlength: 6,\r\n\t\t\t\twidth: 11,\r\n\t\t\t\tborderWidth: .9,\r\n\t\t\t\tfeather: .7,\r\n\t\t\t\tfillColour: colour,\r\n\t\t\t\tborderColour: [0,0,0, alpha],\r\n\t\t\t\tyaw: this.#heading,\r\n\t\t\t\toffset: this.offset,\r\n\t\t\t});\r\n\r\n\t\t\t/// TODO: If heading is between 40 and 70, and there's an event\r\n\t\t\t/// icon, move the event icon to the bottom-right.\r\n\t\t}\r\n\r\n\t\tif (!this.#spriteBouncing) {\r\n\t\t\tthis.#spriteBouncing = new BouncySprite(this.geometry, {\r\n\t\t\t\timage: imagePath,\r\n\t\t\t\tspriteAnchor: [18, 18],\r\n\t\t\t\tspriteSize: [36, 36],\r\n\t\t\t\ttint: this.#tintBase ? colour : [255, 255, 255, alpha],\r\n\t\t\t\tinteractive: true,\r\n\t\t\t\tcursor: 'pointer',\r\n\t\t\t\tbounceHeight: 15,\r\n\t\t\t\toffset: this.offset,\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\tthis.#spriteBouncing.tint = this.#tintBase ? colour : [255, 255, 255, alpha];\r\n\t\t\tthis.#spriteBouncing.replaceImage({\r\n\t\t\t\timage: imagePath,\r\n\t\t\t\tspriteAnchor: [18, 18],\r\n\t\t\t\tspriteSize: [36, 36],\r\n\t\t\t}, true);\r\n\t\t}\r\n\r\n\t\t// MultiSymbol compat\r\n\t\tthis.symbols = this.#bouncing ? [this.#spriteBouncing] : [this.#spriteBase];\r\n\t\tif (this.#headingTriangle) {\r\n\t\t\tthis.symbols.push(this.#headingTriangle);\r\n\t\t}\r\n\t\tif (this.#spriteStatus) {\r\n\t\t\tthis.symbols.push(this.#spriteStatus);\r\n\t\t}\r\n\r\n\t\t// Hook up dragging events\r\n\t\tthis.#spriteBase.on(\"dragstart\", this.#onDragStart.bind(this));\r\n\t\tthis.#spriteBase.on(\"drag\", this.#onDrag.bind(this));\r\n\t\tthis.#spriteBase.on(\"dragend\", this.#onDragEnd.bind(this));\r\n\t\tthis.#spriteBouncing.on(\"dragstart\", this.#onDragStart.bind(this));\r\n\t\tthis.#spriteBouncing.on(\"drag\", this.#onDrag.bind(this));\r\n\t\tthis.#spriteBouncing.on(\"dragend\", this.#onDragEnd.bind(this));\r\n\r\n\t\tif (target) {\r\n\t\t\ttarget.multiAdd(this.symbols);\r\n\t\t}\r\n\t}\r\n\r\n\r\n\t// Dragging logic: rely on the base & bouncing sprites.\r\n\tget draggable() {\r\n\t\treturn this.#spriteBase.draggable;\r\n\t}\r\n\r\n\tset draggable(d) {\r\n\t\tif (this.#spriteBase.draggable && !d) {\r\n\t\t\tthis.fire(\"dragdisable\");\r\n\t\t} else if (!this.#spriteBase.draggable && d) {\r\n\t\t\tthis.fire(\"dragenable\");\r\n\t\t}\r\n\t\tthis.#spriteBase.draggable = this.#spriteBouncing.draggable = d;\r\n\t}\r\n\r\n\t#onDragStart() {\r\n\t\t// Disable *leaflet* map dragging - the sprite only has access to\r\n\t\t// the gleo platina.\r\n\t\tmap.dragging.disable();\r\n\t}\r\n\t#onDrag(ev) {\r\n\t\t// Whenever the underlying base/bouncing sprite is dragged, update\r\n\t\t// the geometry of all sprites + heading triangle\r\n\t\tthis.geometry = ev.target.geometry;\r\n\r\n\t\t/// Move the anchor element as well, since the leaflet tooltip\r\n\t\t/// might be visible while dragging. But this won't update a jQuery\r\n\t\t/// tooltip / bsTooltip.\r\n\t\tconst anchor = this.constructor.popupAnchor;\r\n\t\tanchor.setLatLng(this.geometry.asLatLng());\r\n\t}\r\n\t#onDragEnd() {\r\n\t\t// Re-enable Leaflet map dragging, idem as onDragStart.\r\n\t\tmap.dragging.enable();\r\n\t}\r\n\r\n}\r\n\r\n","import trkData from \"./data.js\";\r\nimport { convertToLatLngPreference } from \"./preferences.js\";\r\nimport { map } from \"./map-base.js\";\r\nimport { extendBounds } from \"./map-bounds.js\";\r\nimport { markerClick, markerUnhover, markerHover } from \"./marker-click.js\";\r\nimport user from \"./user.js\";\r\nimport { isItemIncluded } from \"./polyfills.js\";\r\nimport { addItemToMap } from \"./map-items.js\";\r\nimport AssetSprite from \"./gleo-asset-sprite.js\";\r\nimport {convertNamedColorToArray} from \"./color.js\";\r\n\r\nimport $ from \"jquery\";\r\n// import L from \"leaflet\";\r\nimport _ from \"lodash\";\r\n\r\nexport function addPlaceMarker(latlng, location, place) {\r\n\tif (latlng == null) {\r\n\t\treturn null;\r\n\t}\r\n\textendBounds(latlng);\r\n\r\n\tvar color = place.Color ?? \"red\";\r\n\t// if (color == null) {\r\n\t// color = \"white\";\r\n\t// }\r\n\t// var imagePath = createMarkerPath(\"Generic\", color, null, null, null, false);\r\n\t// var imagePath = createMarkerPath(\"Generic\", \"white\", null, null, null, false);\r\n\r\n\tvar placeMap = map;\r\n\tvar isCurrentlyActive = !isItemIncluded(user.displayPreferences.hiddenPlaces, place.Id);\r\n\tif (!isCurrentlyActive) {\r\n\t\tplaceMap = null;\r\n\t}\r\n\r\n\tconst marker = new AssetSprite(latlng, \"Generic\", color);\r\n\r\n\tmarker.data = {\r\n\t\tlocation: null,\r\n\t\tplaceId: null,\r\n\t\ttint: null, // convertNamedColorToArray(color), // temporarily remove tint\r\n\t};\r\n\tif (placeMap != null) {\r\n\t\taddItemToMap(marker);\r\n\t}\r\n\r\n\tlocation.marker = marker;\r\n\tif (location != null) {\r\n\t\t//$j.data(marker, 'location', location);\r\n\t\tmarker.data.location = location;\r\n\t}\r\n\tif (place != null) {\r\n\t\t//$j.data(marker, 'placeId', place.Id);\r\n\t\tmarker.data.placeId = place.Id;\r\n\t}\r\n\tmarker.on(\"pointerover\", function (e) {\r\n\t\tmarkerHover(marker);\r\n\t\t//var evt = e.originalEvent;\r\n\t\t//evt.preventDefault = true;\r\n\t\t//$j('#fence-tooltip').tooltip('option', 'position', { of: evt, my: 'left+15 bottom+15', at: 'right center', }).tooltip('option', 'content', place.Name).tooltip('open');\r\n\t});\r\n\tmarker.on(\"pointerout\", function (e) {\r\n\t\tmarkerUnhover(marker);\r\n\t\t//$j('#fence-tooltip').tooltip('close');\r\n\t});\r\n\tmarker.on(\"click\", function (e) {\r\n\t\t//placeClick(marker);\r\n\t\tmarkerClick(marker, \"place\", e.latlng, true);\r\n\t});\r\n\ttrkData.placeMarkers.push(marker);\r\n\treturn marker;\r\n}\r\n\r\nexport function enablePlaceMarkerDraggable(marker) {\r\n\tif (marker === undefined || marker === null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tmarker.draggable = true;\r\n\tmarker.data.isMoved = true;\r\n\r\n\tfunction onDragEnd(ev) {\r\n\t\tvar latLng = ev.target.geometry.asLatLng();\r\n\r\n\t\t$(\"#add-place-location\").text(convertToLatLngPreference(latLng[0], latLng[1]));\r\n\t\t$(\"#hfPlaceLat\").val(latLng[0]);\r\n\t\t$(\"#hfPlaceLng\").val(latLng[1]);\r\n\t};\r\n\r\n\tmarker.on(\"dragend\", onDragEnd);\r\n\tmarker.once(\"dragdisable\", () => {\r\n\t\tmarker.off(\"dragend\", onDragEnd);\r\n\t});\r\n}\r\n","const escape = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n '`': '`',\n '=': '='\n};\n\nconst badChars = /[&<>\"'`=]/g,\n possible = /[&<>\"'`=]/;\n\nfunction escapeChar(chr) {\n return escape[chr];\n}\n\nexport function extend(obj /* , ...source */) {\n for (let i = 1; i < arguments.length; i++) {\n for (let key in arguments[i]) {\n if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {\n obj[key] = arguments[i][key];\n }\n }\n }\n\n return obj;\n}\n\nexport let toString = Object.prototype.toString;\n\n// Sourced from lodash\n// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt\n/* eslint-disable func-style */\nlet isFunction = function(value) {\n return typeof value === 'function';\n};\n// fallback for older versions of Chrome and Safari\n/* istanbul ignore next */\nif (isFunction(/x/)) {\n isFunction = function(value) {\n return (\n typeof value === 'function' &&\n toString.call(value) === '[object Function]'\n );\n };\n}\nexport { isFunction };\n/* eslint-enable func-style */\n\n/* istanbul ignore next */\nexport const isArray =\n Array.isArray ||\n function(value) {\n return value && typeof value === 'object'\n ? toString.call(value) === '[object Array]'\n : false;\n };\n\n// Older IE versions do not directly support indexOf so we must implement our own, sadly.\nexport function indexOf(array, value) {\n for (let i = 0, len = array.length; i < len; i++) {\n if (array[i] === value) {\n return i;\n }\n }\n return -1;\n}\n\nexport function escapeExpression(string) {\n if (typeof string !== 'string') {\n // don't escape SafeStrings, since they're already safe\n if (string && string.toHTML) {\n return string.toHTML();\n } else if (string == null) {\n return '';\n } else if (!string) {\n return string + '';\n }\n\n // Force a string conversion as this will be done by the append regardless and\n // the regex test will do this transparently behind the scenes, causing issues if\n // an object's to string has escaped characters in it.\n string = '' + string;\n }\n\n if (!possible.test(string)) {\n return string;\n }\n return string.replace(badChars, escapeChar);\n}\n\nexport function isEmpty(value) {\n if (!value && value !== 0) {\n return true;\n } else if (isArray(value) && value.length === 0) {\n return true;\n } else {\n return false;\n }\n}\n\nexport function createFrame(object) {\n let frame = extend({}, object);\n frame._parent = object;\n return frame;\n}\n\nexport function blockParams(params, ids) {\n params.path = ids;\n return params;\n}\n\nexport function appendContextPath(contextPath, id) {\n return (contextPath ? contextPath + '.' : '') + id;\n}\n","const errorProps = [\n 'description',\n 'fileName',\n 'lineNumber',\n 'endLineNumber',\n 'message',\n 'name',\n 'number',\n 'stack'\n];\n\nfunction Exception(message, node) {\n let loc = node && node.loc,\n line,\n endLineNumber,\n column,\n endColumn;\n\n if (loc) {\n line = loc.start.line;\n endLineNumber = loc.end.line;\n column = loc.start.column;\n endColumn = loc.end.column;\n\n message += ' - ' + line + ':' + column;\n }\n\n let tmp = Error.prototype.constructor.call(this, message);\n\n // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.\n for (let idx = 0; idx < errorProps.length; idx++) {\n this[errorProps[idx]] = tmp[errorProps[idx]];\n }\n\n /* istanbul ignore else */\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, Exception);\n }\n\n try {\n if (loc) {\n this.lineNumber = line;\n this.endLineNumber = endLineNumber;\n\n // Work around issue under safari where we can't directly set the column value\n /* istanbul ignore next */\n if (Object.defineProperty) {\n Object.defineProperty(this, 'column', {\n value: column,\n enumerable: true\n });\n Object.defineProperty(this, 'endColumn', {\n value: endColumn,\n enumerable: true\n });\n } else {\n this.column = column;\n this.endColumn = endColumn;\n }\n }\n } catch (nop) {\n /* Ignore if the browser is very particular */\n }\n}\n\nException.prototype = new Error();\n\nexport default Exception;\n","import { appendContextPath, createFrame, isArray } from '../utils';\n\nexport default function(instance) {\n instance.registerHelper('blockHelperMissing', function(context, options) {\n let inverse = options.inverse,\n fn = options.fn;\n\n if (context === true) {\n return fn(this);\n } else if (context === false || context == null) {\n return inverse(this);\n } else if (isArray(context)) {\n if (context.length > 0) {\n if (options.ids) {\n options.ids = [options.name];\n }\n\n return instance.helpers.each(context, options);\n } else {\n return inverse(this);\n }\n } else {\n if (options.data && options.ids) {\n let data = createFrame(options.data);\n data.contextPath = appendContextPath(\n options.data.contextPath,\n options.name\n );\n options = { data: data };\n }\n\n return fn(context, options);\n }\n });\n}\n","import {\n appendContextPath,\n blockParams,\n createFrame,\n isArray,\n isFunction\n} from '../utils';\nimport Exception from '../exception';\n\nexport default function(instance) {\n instance.registerHelper('each', function(context, options) {\n if (!options) {\n throw new Exception('Must pass iterator to #each');\n }\n\n let fn = options.fn,\n inverse = options.inverse,\n i = 0,\n ret = '',\n data,\n contextPath;\n\n if (options.data && options.ids) {\n contextPath =\n appendContextPath(options.data.contextPath, options.ids[0]) + '.';\n }\n\n if (isFunction(context)) {\n context = context.call(this);\n }\n\n if (options.data) {\n data = createFrame(options.data);\n }\n\n function execIteration(field, index, last) {\n if (data) {\n data.key = field;\n data.index = index;\n data.first = index === 0;\n data.last = !!last;\n\n if (contextPath) {\n data.contextPath = contextPath + field;\n }\n }\n\n ret =\n ret +\n fn(context[field], {\n data: data,\n blockParams: blockParams(\n [context[field], field],\n [contextPath + field, null]\n )\n });\n }\n\n if (context && typeof context === 'object') {\n if (isArray(context)) {\n for (let j = context.length; i < j; i++) {\n if (i in context) {\n execIteration(i, i, i === context.length - 1);\n }\n }\n } else if (typeof Symbol === 'function' && context[Symbol.iterator]) {\n const newContext = [];\n const iterator = context[Symbol.iterator]();\n for (let it = iterator.next(); !it.done; it = iterator.next()) {\n newContext.push(it.value);\n }\n context = newContext;\n for (let j = context.length; i < j; i++) {\n execIteration(i, i, i === context.length - 1);\n }\n } else {\n let priorKey;\n\n Object.keys(context).forEach(key => {\n // We're running the iterations one step out of sync so we can detect\n // the last iteration without have to scan the object twice and create\n // an itermediate keys array.\n if (priorKey !== undefined) {\n execIteration(priorKey, i - 1);\n }\n priorKey = key;\n i++;\n });\n if (priorKey !== undefined) {\n execIteration(priorKey, i - 1, true);\n }\n }\n }\n\n if (i === 0) {\n ret = inverse(this);\n }\n\n return ret;\n });\n}\n","import Exception from '../exception';\n\nexport default function(instance) {\n instance.registerHelper('helperMissing', function(/* [args, ]options */) {\n if (arguments.length === 1) {\n // A missing field in a {{foo}} construct.\n return undefined;\n } else {\n // Someone is actually trying to call something, blow up.\n throw new Exception(\n 'Missing helper: \"' + arguments[arguments.length - 1].name + '\"'\n );\n }\n });\n}\n","import { isEmpty, isFunction } from '../utils';\nimport Exception from '../exception';\n\nexport default function(instance) {\n instance.registerHelper('if', function(conditional, options) {\n if (arguments.length != 2) {\n throw new Exception('#if requires exactly one argument');\n }\n if (isFunction(conditional)) {\n conditional = conditional.call(this);\n }\n\n // Default behavior is to render the positive path if the value is truthy and not empty.\n // The `includeZero` option may be set to treat the condtional as purely not empty based on the\n // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.\n if ((!options.hash.includeZero && !conditional) || isEmpty(conditional)) {\n return options.inverse(this);\n } else {\n return options.fn(this);\n }\n });\n\n instance.registerHelper('unless', function(conditional, options) {\n if (arguments.length != 2) {\n throw new Exception('#unless requires exactly one argument');\n }\n return instance.helpers['if'].call(this, conditional, {\n fn: options.inverse,\n inverse: options.fn,\n hash: options.hash\n });\n });\n}\n","export default function(instance) {\n instance.registerHelper('log', function(/* message, options */) {\n let args = [undefined],\n options = arguments[arguments.length - 1];\n for (let i = 0; i < arguments.length - 1; i++) {\n args.push(arguments[i]);\n }\n\n let level = 1;\n if (options.hash.level != null) {\n level = options.hash.level;\n } else if (options.data && options.data.level != null) {\n level = options.data.level;\n }\n args[0] = level;\n\n instance.log(...args);\n });\n}\n","export default function(instance) {\n instance.registerHelper('lookup', function(obj, field, options) {\n if (!obj) {\n // Note for 5.0: Change to \"obj == null\" in 5.0\n return obj;\n }\n return options.lookupProperty(obj, field);\n });\n}\n","import {\n appendContextPath,\n blockParams,\n createFrame,\n isEmpty,\n isFunction\n} from '../utils';\nimport Exception from '../exception';\n\nexport default function(instance) {\n instance.registerHelper('with', function(context, options) {\n if (arguments.length != 2) {\n throw new Exception('#with requires exactly one argument');\n }\n if (isFunction(context)) {\n context = context.call(this);\n }\n\n let fn = options.fn;\n\n if (!isEmpty(context)) {\n let data = options.data;\n if (options.data && options.ids) {\n data = createFrame(options.data);\n data.contextPath = appendContextPath(\n options.data.contextPath,\n options.ids[0]\n );\n }\n\n return fn(context, {\n data: data,\n blockParams: blockParams([context], [data && data.contextPath])\n });\n } else {\n return options.inverse(this);\n }\n });\n}\n","import registerBlockHelperMissing from './helpers/block-helper-missing';\nimport registerEach from './helpers/each';\nimport registerHelperMissing from './helpers/helper-missing';\nimport registerIf from './helpers/if';\nimport registerLog from './helpers/log';\nimport registerLookup from './helpers/lookup';\nimport registerWith from './helpers/with';\n\nexport function registerDefaultHelpers(instance) {\n registerBlockHelperMissing(instance);\n registerEach(instance);\n registerHelperMissing(instance);\n registerIf(instance);\n registerLog(instance);\n registerLookup(instance);\n registerWith(instance);\n}\n\nexport function moveHelperToHooks(instance, helperName, keepHelper) {\n if (instance.helpers[helperName]) {\n instance.hooks[helperName] = instance.helpers[helperName];\n if (!keepHelper) {\n delete instance.helpers[helperName];\n }\n }\n}\n","import { extend } from '../utils';\n\nexport default function(instance) {\n instance.registerDecorator('inline', function(fn, props, container, options) {\n let ret = fn;\n if (!props.partials) {\n props.partials = {};\n ret = function(context, options) {\n // Create a new partials stack frame prior to exec.\n let original = container.partials;\n container.partials = extend({}, original, props.partials);\n let ret = fn(context, options);\n container.partials = original;\n return ret;\n };\n }\n\n props.partials[options.args[0]] = options.fn;\n\n return ret;\n });\n}\n","import registerInline from './decorators/inline';\n\nexport function registerDefaultDecorators(instance) {\n registerInline(instance);\n}\n","import { indexOf } from './utils';\n\nlet logger = {\n methodMap: ['debug', 'info', 'warn', 'error'],\n level: 'info',\n\n // Maps a given level value to the `methodMap` indexes above.\n lookupLevel: function(level) {\n if (typeof level === 'string') {\n let levelMap = indexOf(logger.methodMap, level.toLowerCase());\n if (levelMap >= 0) {\n level = levelMap;\n } else {\n level = parseInt(level, 10);\n }\n }\n\n return level;\n },\n\n // Can be overridden in the host environment\n log: function(level, ...message) {\n level = logger.lookupLevel(level);\n\n if (\n typeof console !== 'undefined' &&\n logger.lookupLevel(logger.level) <= level\n ) {\n let method = logger.methodMap[level];\n // eslint-disable-next-line no-console\n if (!console[method]) {\n method = 'log';\n }\n console[method](...message); // eslint-disable-line no-console\n }\n }\n};\n\nexport default logger;\n","import { extend } from '../utils';\n\n/**\n * Create a new object with \"null\"-prototype to avoid truthy results on prototype properties.\n * The resulting object can be used with \"object[property]\" to check if a property exists\n * @param {...object} sources a varargs parameter of source objects that will be merged\n * @returns {object}\n */\nexport function createNewLookupObject(...sources) {\n return extend(Object.create(null), ...sources);\n}\n","import { createNewLookupObject } from './create-new-lookup-object';\nimport logger from '../logger';\n\nconst loggedProperties = Object.create(null);\n\nexport function createProtoAccessControl(runtimeOptions) {\n let defaultMethodWhiteList = Object.create(null);\n defaultMethodWhiteList['constructor'] = false;\n defaultMethodWhiteList['__defineGetter__'] = false;\n defaultMethodWhiteList['__defineSetter__'] = false;\n defaultMethodWhiteList['__lookupGetter__'] = false;\n\n let defaultPropertyWhiteList = Object.create(null);\n // eslint-disable-next-line no-proto\n defaultPropertyWhiteList['__proto__'] = false;\n\n return {\n properties: {\n whitelist: createNewLookupObject(\n defaultPropertyWhiteList,\n runtimeOptions.allowedProtoProperties\n ),\n defaultValue: runtimeOptions.allowProtoPropertiesByDefault\n },\n methods: {\n whitelist: createNewLookupObject(\n defaultMethodWhiteList,\n runtimeOptions.allowedProtoMethods\n ),\n defaultValue: runtimeOptions.allowProtoMethodsByDefault\n }\n };\n}\n\nexport function resultIsAllowed(result, protoAccessControl, propertyName) {\n if (typeof result === 'function') {\n return checkWhiteList(protoAccessControl.methods, propertyName);\n } else {\n return checkWhiteList(protoAccessControl.properties, propertyName);\n }\n}\n\nfunction checkWhiteList(protoAccessControlForType, propertyName) {\n if (protoAccessControlForType.whitelist[propertyName] !== undefined) {\n return protoAccessControlForType.whitelist[propertyName] === true;\n }\n if (protoAccessControlForType.defaultValue !== undefined) {\n return protoAccessControlForType.defaultValue;\n }\n logUnexpecedPropertyAccessOnce(propertyName);\n return false;\n}\n\nfunction logUnexpecedPropertyAccessOnce(propertyName) {\n if (loggedProperties[propertyName] !== true) {\n loggedProperties[propertyName] = true;\n logger.log(\n 'error',\n `Handlebars: Access has been denied to resolve the property \"${propertyName}\" because it is not an \"own property\" of its parent.\\n` +\n `You can add a runtime option to disable the check or this warning:\\n` +\n `See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details`\n );\n }\n}\n\nexport function resetLoggedProperties() {\n Object.keys(loggedProperties).forEach(propertyName => {\n delete loggedProperties[propertyName];\n });\n}\n","import { createFrame, extend, toString } from './utils';\nimport Exception from './exception';\nimport { registerDefaultHelpers } from './helpers';\nimport { registerDefaultDecorators } from './decorators';\nimport logger from './logger';\nimport { resetLoggedProperties } from './internal/proto-access';\n\nexport const VERSION = '4.7.8';\nexport const COMPILER_REVISION = 8;\nexport const LAST_COMPATIBLE_COMPILER_REVISION = 7;\n\nexport const REVISION_CHANGES = {\n 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it\n 2: '== 1.0.0-rc.3',\n 3: '== 1.0.0-rc.4',\n 4: '== 1.x.x',\n 5: '== 2.0.0-alpha.x',\n 6: '>= 2.0.0-beta.1',\n 7: '>= 4.0.0 <4.3.0',\n 8: '>= 4.3.0'\n};\n\nconst objectType = '[object Object]';\n\nexport function HandlebarsEnvironment(helpers, partials, decorators) {\n this.helpers = helpers || {};\n this.partials = partials || {};\n this.decorators = decorators || {};\n\n registerDefaultHelpers(this);\n registerDefaultDecorators(this);\n}\n\nHandlebarsEnvironment.prototype = {\n constructor: HandlebarsEnvironment,\n\n logger: logger,\n log: logger.log,\n\n registerHelper: function(name, fn) {\n if (toString.call(name) === objectType) {\n if (fn) {\n throw new Exception('Arg not supported with multiple helpers');\n }\n extend(this.helpers, name);\n } else {\n this.helpers[name] = fn;\n }\n },\n unregisterHelper: function(name) {\n delete this.helpers[name];\n },\n\n registerPartial: function(name, partial) {\n if (toString.call(name) === objectType) {\n extend(this.partials, name);\n } else {\n if (typeof partial === 'undefined') {\n throw new Exception(\n `Attempting to register a partial called \"${name}\" as undefined`\n );\n }\n this.partials[name] = partial;\n }\n },\n unregisterPartial: function(name) {\n delete this.partials[name];\n },\n\n registerDecorator: function(name, fn) {\n if (toString.call(name) === objectType) {\n if (fn) {\n throw new Exception('Arg not supported with multiple decorators');\n }\n extend(this.decorators, name);\n } else {\n this.decorators[name] = fn;\n }\n },\n unregisterDecorator: function(name) {\n delete this.decorators[name];\n },\n /**\n * Reset the memory of illegal property accesses that have already been logged.\n * @deprecated should only be used in handlebars test-cases\n */\n resetLoggedPropertyAccesses() {\n resetLoggedProperties();\n }\n};\n\nexport let log = logger.log;\n\nexport { createFrame, logger };\n","// Build out our basic SafeString type\nfunction SafeString(string) {\n this.string = string;\n}\n\nSafeString.prototype.toString = SafeString.prototype.toHTML = function() {\n return '' + this.string;\n};\n\nexport default SafeString;\n","export function wrapHelper(helper, transformOptionsFn) {\n if (typeof helper !== 'function') {\n // This should not happen, but apparently it does in https://github.com/wycats/handlebars.js/issues/1639\n // We try to make the wrapper least-invasive by not wrapping it, if the helper is not a function.\n return helper;\n }\n let wrapper = function(/* dynamic arguments */) {\n const options = arguments[arguments.length - 1];\n arguments[arguments.length - 1] = transformOptionsFn(options);\n return helper.apply(this, arguments);\n };\n return wrapper;\n}\n","import * as Utils from './utils';\nimport Exception from './exception';\nimport {\n COMPILER_REVISION,\n createFrame,\n LAST_COMPATIBLE_COMPILER_REVISION,\n REVISION_CHANGES\n} from './base';\nimport { moveHelperToHooks } from './helpers';\nimport { wrapHelper } from './internal/wrapHelper';\nimport {\n createProtoAccessControl,\n resultIsAllowed\n} from './internal/proto-access';\n\nexport function checkRevision(compilerInfo) {\n const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,\n currentRevision = COMPILER_REVISION;\n\n if (\n compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION &&\n compilerRevision <= COMPILER_REVISION\n ) {\n return;\n }\n\n if (compilerRevision < LAST_COMPATIBLE_COMPILER_REVISION) {\n const runtimeVersions = REVISION_CHANGES[currentRevision],\n compilerVersions = REVISION_CHANGES[compilerRevision];\n throw new Exception(\n 'Template was precompiled with an older version of Handlebars than the current runtime. ' +\n 'Please update your precompiler to a newer version (' +\n runtimeVersions +\n ') or downgrade your runtime to an older version (' +\n compilerVersions +\n ').'\n );\n } else {\n // Use the embedded version info since the runtime doesn't know about this revision yet\n throw new Exception(\n 'Template was precompiled with a newer version of Handlebars than the current runtime. ' +\n 'Please update your runtime to a newer version (' +\n compilerInfo[1] +\n ').'\n );\n }\n}\n\nexport function template(templateSpec, env) {\n /* istanbul ignore next */\n if (!env) {\n throw new Exception('No environment passed to template');\n }\n if (!templateSpec || !templateSpec.main) {\n throw new Exception('Unknown template object: ' + typeof templateSpec);\n }\n\n templateSpec.main.decorator = templateSpec.main_d;\n\n // Note: Using env.VM references rather than local var references throughout this section to allow\n // for external users to override these as pseudo-supported APIs.\n env.VM.checkRevision(templateSpec.compiler);\n\n // backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0)\n const templateWasPrecompiledWithCompilerV7 =\n templateSpec.compiler && templateSpec.compiler[0] === 7;\n\n function invokePartialWrapper(partial, context, options) {\n if (options.hash) {\n context = Utils.extend({}, context, options.hash);\n if (options.ids) {\n options.ids[0] = true;\n }\n }\n partial = env.VM.resolvePartial.call(this, partial, context, options);\n\n let extendedOptions = Utils.extend({}, options, {\n hooks: this.hooks,\n protoAccessControl: this.protoAccessControl\n });\n\n let result = env.VM.invokePartial.call(\n this,\n partial,\n context,\n extendedOptions\n );\n\n if (result == null && env.compile) {\n options.partials[options.name] = env.compile(\n partial,\n templateSpec.compilerOptions,\n env\n );\n result = options.partials[options.name](context, extendedOptions);\n }\n if (result != null) {\n if (options.indent) {\n let lines = result.split('\\n');\n for (let i = 0, l = lines.length; i < l; i++) {\n if (!lines[i] && i + 1 === l) {\n break;\n }\n\n lines[i] = options.indent + lines[i];\n }\n result = lines.join('\\n');\n }\n return result;\n } else {\n throw new Exception(\n 'The partial ' +\n options.name +\n ' could not be compiled when running in runtime-only mode'\n );\n }\n }\n\n // Just add water\n let container = {\n strict: function(obj, name, loc) {\n if (!obj || !(name in obj)) {\n throw new Exception('\"' + name + '\" not defined in ' + obj, {\n loc: loc\n });\n }\n return container.lookupProperty(obj, name);\n },\n lookupProperty: function(parent, propertyName) {\n let result = parent[propertyName];\n if (result == null) {\n return result;\n }\n if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {\n return result;\n }\n\n if (resultIsAllowed(result, container.protoAccessControl, propertyName)) {\n return result;\n }\n return undefined;\n },\n lookup: function(depths, name) {\n const len = depths.length;\n for (let i = 0; i < len; i++) {\n let result = depths[i] && container.lookupProperty(depths[i], name);\n if (result != null) {\n return depths[i][name];\n }\n }\n },\n lambda: function(current, context) {\n return typeof current === 'function' ? current.call(context) : current;\n },\n\n escapeExpression: Utils.escapeExpression,\n invokePartial: invokePartialWrapper,\n\n fn: function(i) {\n let ret = templateSpec[i];\n ret.decorator = templateSpec[i + '_d'];\n return ret;\n },\n\n programs: [],\n program: function(i, data, declaredBlockParams, blockParams, depths) {\n let programWrapper = this.programs[i],\n fn = this.fn(i);\n if (data || depths || blockParams || declaredBlockParams) {\n programWrapper = wrapProgram(\n this,\n i,\n fn,\n data,\n declaredBlockParams,\n blockParams,\n depths\n );\n } else if (!programWrapper) {\n programWrapper = this.programs[i] = wrapProgram(this, i, fn);\n }\n return programWrapper;\n },\n\n data: function(value, depth) {\n while (value && depth--) {\n value = value._parent;\n }\n return value;\n },\n mergeIfNeeded: function(param, common) {\n let obj = param || common;\n\n if (param && common && param !== common) {\n obj = Utils.extend({}, common, param);\n }\n\n return obj;\n },\n // An empty object to use as replacement for null-contexts\n nullContext: Object.seal({}),\n\n noop: env.VM.noop,\n compilerInfo: templateSpec.compiler\n };\n\n function ret(context, options = {}) {\n let data = options.data;\n\n ret._setup(options);\n if (!options.partial && templateSpec.useData) {\n data = initData(context, data);\n }\n let depths,\n blockParams = templateSpec.useBlockParams ? [] : undefined;\n if (templateSpec.useDepths) {\n if (options.depths) {\n depths =\n context != options.depths[0]\n ? [context].concat(options.depths)\n : options.depths;\n } else {\n depths = [context];\n }\n }\n\n function main(context /*, options*/) {\n return (\n '' +\n templateSpec.main(\n container,\n context,\n container.helpers,\n container.partials,\n data,\n blockParams,\n depths\n )\n );\n }\n\n main = executeDecorators(\n templateSpec.main,\n main,\n container,\n options.depths || [],\n data,\n blockParams\n );\n return main(context, options);\n }\n\n ret.isTop = true;\n\n ret._setup = function(options) {\n if (!options.partial) {\n let mergedHelpers = Utils.extend({}, env.helpers, options.helpers);\n wrapHelpersToPassLookupProperty(mergedHelpers, container);\n container.helpers = mergedHelpers;\n\n if (templateSpec.usePartial) {\n // Use mergeIfNeeded here to prevent compiling global partials multiple times\n container.partials = container.mergeIfNeeded(\n options.partials,\n env.partials\n );\n }\n if (templateSpec.usePartial || templateSpec.useDecorators) {\n container.decorators = Utils.extend(\n {},\n env.decorators,\n options.decorators\n );\n }\n\n container.hooks = {};\n container.protoAccessControl = createProtoAccessControl(options);\n\n let keepHelperInHelpers =\n options.allowCallsToHelperMissing ||\n templateWasPrecompiledWithCompilerV7;\n moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);\n moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);\n } else {\n container.protoAccessControl = options.protoAccessControl; // internal option\n container.helpers = options.helpers;\n container.partials = options.partials;\n container.decorators = options.decorators;\n container.hooks = options.hooks;\n }\n };\n\n ret._child = function(i, data, blockParams, depths) {\n if (templateSpec.useBlockParams && !blockParams) {\n throw new Exception('must pass block params');\n }\n if (templateSpec.useDepths && !depths) {\n throw new Exception('must pass parent depths');\n }\n\n return wrapProgram(\n container,\n i,\n templateSpec[i],\n data,\n 0,\n blockParams,\n depths\n );\n };\n return ret;\n}\n\nexport function wrapProgram(\n container,\n i,\n fn,\n data,\n declaredBlockParams,\n blockParams,\n depths\n) {\n function prog(context, options = {}) {\n let currentDepths = depths;\n if (\n depths &&\n context != depths[0] &&\n !(context === container.nullContext && depths[0] === null)\n ) {\n currentDepths = [context].concat(depths);\n }\n\n return fn(\n container,\n context,\n container.helpers,\n container.partials,\n options.data || data,\n blockParams && [options.blockParams].concat(blockParams),\n currentDepths\n );\n }\n\n prog = executeDecorators(fn, prog, container, depths, data, blockParams);\n\n prog.program = i;\n prog.depth = depths ? depths.length : 0;\n prog.blockParams = declaredBlockParams || 0;\n return prog;\n}\n\n/**\n * This is currently part of the official API, therefore implementation details should not be changed.\n */\nexport function resolvePartial(partial, context, options) {\n if (!partial) {\n if (options.name === '@partial-block') {\n partial = options.data['partial-block'];\n } else {\n partial = options.partials[options.name];\n }\n } else if (!partial.call && !options.name) {\n // This is a dynamic partial that returned a string\n options.name = partial;\n partial = options.partials[partial];\n }\n return partial;\n}\n\nexport function invokePartial(partial, context, options) {\n // Use the current closure context to save the partial-block if this partial\n const currentPartialBlock = options.data && options.data['partial-block'];\n options.partial = true;\n if (options.ids) {\n options.data.contextPath = options.ids[0] || options.data.contextPath;\n }\n\n let partialBlock;\n if (options.fn && options.fn !== noop) {\n options.data = createFrame(options.data);\n // Wrapper function to get access to currentPartialBlock from the closure\n let fn = options.fn;\n partialBlock = options.data['partial-block'] = function partialBlockWrapper(\n context,\n options = {}\n ) {\n // Restore the partial-block from the closure for the execution of the block\n // i.e. the part inside the block of the partial call.\n options.data = createFrame(options.data);\n options.data['partial-block'] = currentPartialBlock;\n return fn(context, options);\n };\n if (fn.partials) {\n options.partials = Utils.extend({}, options.partials, fn.partials);\n }\n }\n\n if (partial === undefined && partialBlock) {\n partial = partialBlock;\n }\n\n if (partial === undefined) {\n throw new Exception('The partial ' + options.name + ' could not be found');\n } else if (partial instanceof Function) {\n return partial(context, options);\n }\n}\n\nexport function noop() {\n return '';\n}\n\nfunction initData(context, data) {\n if (!data || !('root' in data)) {\n data = data ? createFrame(data) : {};\n data.root = context;\n }\n return data;\n}\n\nfunction executeDecorators(fn, prog, container, depths, data, blockParams) {\n if (fn.decorator) {\n let props = {};\n prog = fn.decorator(\n prog,\n props,\n container,\n depths && depths[0],\n data,\n blockParams,\n depths\n );\n Utils.extend(prog, props);\n }\n return prog;\n}\n\nfunction wrapHelpersToPassLookupProperty(mergedHelpers, container) {\n Object.keys(mergedHelpers).forEach(helperName => {\n let helper = mergedHelpers[helperName];\n mergedHelpers[helperName] = passLookupPropertyOption(helper, container);\n });\n}\n\nfunction passLookupPropertyOption(helper, container) {\n const lookupProperty = container.lookupProperty;\n return wrapHelper(helper, options => {\n return Utils.extend({ lookupProperty }, options);\n });\n}\n","/* global globalThis */\nexport default function(Handlebars) {\n /* istanbul ignore next */\n // https://mathiasbynens.be/notes/globalthis\n (function() {\n if (typeof globalThis === 'object') return;\n Object.prototype.__defineGetter__('__magic__', function() {\n return this;\n });\n __magic__.globalThis = __magic__; // eslint-disable-line no-undef\n delete Object.prototype.__magic__;\n })();\n\n const $Handlebars = globalThis.Handlebars;\n\n /* istanbul ignore next */\n Handlebars.noConflict = function() {\n if (globalThis.Handlebars === Handlebars) {\n globalThis.Handlebars = $Handlebars;\n }\n return Handlebars;\n };\n}\n","import * as base from './handlebars/base';\n\n// Each of these augment the Handlebars object. No need to setup here.\n// (This is done to easily share code between commonjs and browse envs)\nimport SafeString from './handlebars/safe-string';\nimport Exception from './handlebars/exception';\nimport * as Utils from './handlebars/utils';\nimport * as runtime from './handlebars/runtime';\n\nimport noConflict from './handlebars/no-conflict';\n\n// For compatibility and usage outside of module systems, make the Handlebars object a namespace\nfunction create() {\n let hb = new base.HandlebarsEnvironment();\n\n Utils.extend(hb, base);\n hb.SafeString = SafeString;\n hb.Exception = Exception;\n hb.Utils = Utils;\n hb.escapeExpression = Utils.escapeExpression;\n\n hb.VM = runtime;\n hb.template = function(spec) {\n return runtime.template(spec, hb);\n };\n\n return hb;\n}\n\nlet inst = create();\ninst.create = create;\n\nnoConflict(inst);\n\ninst['default'] = inst;\n\nexport default inst;\n","
  • \r\n
    \r\n {{#if isVisible}}\r\n \r\n {{else}}\r\n \r\n {{/if}}\r\n
    \r\n
    {{name}}
    \r\n
    \r\n \r\n
    \r\n
    \r\n \r\n \r\n
    \r\n
    \r\n
    \r\n
  • \r\n","
  • \r\n
    \r\n \r\n
    \r\n \r\n
    {{assets.length}}
    \r\n
    \r\n
    \r\n {{name}}\r\n
    \r\n \r\n
    \r\n
    \r\n \r\n \r\n
    \r\n
    \r\n
      \r\n
      \r\n
    • \r\n","
    • \r\n
      \r\n {{#if isVisible}}\r\n \r\n {{else}}\r\n \r\n {{/if}}\r\n
      \r\n
      {{name}}
      \r\n
      {{description}}
      \r\n
      \r\n \r\n \r\n
      \r\n
    • \r\n","
    • \r\n
      \r\n {{#if isVisible}}\r\n \r\n {{else}}\r\n \r\n {{/if}}\r\n
      \r\n
      {{name}}
      \r\n
      {{description}}
      \r\n
      \r\n \r\n \r\n
      \r\n
    • \r\n","
    • \r\n
      \r\n \r\n
      \r\n
      {{name}}
      \r\n
      {{description}}
      \r\n
      \r\n \r\n \r\n
      \r\n
    • \r\n","
    • \r\n
      \r\n {{#if isVisible}}\r\n \r\n {{else}}\r\n \r\n {{/if}}\r\n
      \r\n
      {{name}}
      \r\n
      {{description}}
      \r\n
      \r\n \r\n
      \r\n
      \r\n \r\n
      \r\n
      \r\n
      \r\n
    • \r\n","// import Handlebars from \"handlebars\";\r\nimport _ from \"lodash\";\r\n\r\nimport asset from \"../templates/asset.hbs\"\r\nimport assetGroup from \"../templates/assetGroup.hbs\"\r\nimport geofence from \"../templates/geofence.hbs\"\r\nimport place from \"../templates/place.hbs\"\r\nimport sharedView from \"../templates/sharedView.hbs\"\r\nimport trip from \"../templates/trip.hbs\"\r\n\r\nconst templates = {\r\n\tasset: asset,\r\n\tassetGroup: assetGroup,\r\n\tgeofence: geofence,\r\n\tplace: place,\r\n\tmodule: null,\r\n\tsharedView: sharedView,\r\n\ttrip: trip,\r\n};\r\n\r\nexport default templates;","import strings from \"./strings.js\";\r\nimport trkData from \"./data.js\";\r\nimport state from \"./state.js\";\r\nimport options from \"./options.js\";\r\nimport { convertToLatLngPreference } from \"./preferences.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport log from \"./log.js\";\r\nimport { map } from \"./map-base.js\";\r\nimport { markerUnhover } from \"./marker-click.js\";\r\nimport { createMarkerPath } from \"./marker-path.js\";\r\nimport { openDialogPanel } from \"./panel-nav.js\";\r\nimport { startChoosingMapLocation, stopChoosingMapLocation, updateChosenLocation } from \"./map-chooselocation.js\";\r\nimport { convertHexToSortable, convertNamedColorToHex } from \"./color.js\";\r\nimport { sortItemsByMode, resortItemGroup, sortByName, toggleItemSorting, sortGroups } from \"./item-sorting.js\";\r\nimport { sortModes } from \"./const.js\";\r\nimport { handleAjaxFormSubmission } from \"./ajax.js\";\r\nimport { isItemIncluded } from \"./polyfills.js\";\r\nimport { findGroupById, updateGroupVisibilityStatus } from \"./asset-group.js\";\r\nimport { assignItemToGroups } from \"./common-group.js\";\r\nimport { addGroupToGroupListInItemEditDialog } from \"./group-list.js\";\r\nimport { changePrimaryButtonLabel } from \"./modal.js\";\r\nimport { includeRowIfNotNull, createAccordionCard, createDialogTitleFragment } from \"./dom-util.js\";\r\nimport { getAttributesForType } from \"./attributes.js\";\r\nimport { uploadFile } from \"./ajax.js\";\r\nimport { addItemToMap, removeItemFromMap } from \"./map-items.js\";\r\nimport { addPlaceMarker, enablePlaceMarkerDraggable } from \"./place-marker.js\";\r\nimport { populateCustomAttributes } from \"./attributes.js\";\r\nimport user, { displayPreferencesAdd, displayPreferencesRemove } from \"./user.js\";\r\nimport { updateItemListingIcon } from \"./item-listing.js\";\r\nimport templates from \"./templates.js\";\r\nimport { closeSecondaryPanel } from \"./panel.js\";\r\nimport { markerClick } from \"./marker-click.js\";\r\nimport { updateItemVisibilityStatus } from \"./asset-select.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport $j from \"jquery\";\r\nimport L from \"leaflet\";\r\nimport _ from \"lodash\";\r\nimport { el, text, setChildren } from \"redom\"; // https://redom.js.org/\r\n\r\n/*global JsSearch */\r\n// import JsSearch from '../js-search.js';\r\n\r\nexport function initPlaces() {\r\n\t$(domNodes.infoDialogs.mapItemInformation).on(\"click\", \"a.location\", addOrUpdatePlaceClick);\r\n\r\n\t$(domNodes.dialogs.addPlace).on(\"click\", \"#RemovePlacePhoto\", function (e) {\r\n\t\te.preventDefault();\r\n\t\t$(this).next().val(\"true\");\r\n\t\t$(this).parent().parent().hide();\r\n\t});\r\n\t$(domNodes.dialogs.addPlace).on(\"click\", \"#AddPlace\", function (e) {\r\n\t\te.preventDefault();\r\n\r\n\t\tvar isFormValid = $(trkData.validation.placeAdd.currentForm).valid();\r\n\t\tif (!isFormValid) {\r\n\t\t\ttrkData.validation.placeAdd.focusInvalid();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// submit position to pending\r\n\t\tvar btn = this;\r\n\t\tvar status = document.getElementById(\"add-place-status\");\r\n\r\n\t\tvar id = $(\"#hfPlaceId\").val();\r\n\t\tvar name = $(\"#txtPlaceName\").val();\r\n\t\tvar description = $(\"#txtPlaceDescription\").val();\r\n\t\tvar contact = $(\"#txtPlaceContact\").val();\r\n\t\tvar uniqueKey = $(\"#txtPlaceUniqueKey\").val();\r\n\t\tvar color = $(\"#form-add-place\").find(\"input[name=rbEditPlaceColor]:checked\").val();\r\n\t\tvar lat = $(\"#hfPlaceLat\").val();\r\n\t\tvar lng = $(\"#hfPlaceLng\").val();\r\n\t\tvar userIds = new Array();\r\n\t\t$(\"#place-users input[name=EditPlaceUserIds]:checked\").each(function (index, elem) {\r\n\t\t\tuserIds.push($(this).val());\r\n\t\t});\r\n\t\tvar attributes = getAttributesForType(2, \"EditPlaceAttribute\");\r\n\t\tvar removePhoto = $(\"#edit-place-remove-photo\").val() === \"true\";\r\n\t\tvar groupIds = new Array();\r\n\t\t$j(\"#edit-place-groups-container input[name=EditGroupIds]:checked\").each(function (index, elem) {\r\n\t\t\tgroupIds.push($j(this).val());\r\n\t\t});\r\n\r\n\t\tif (state.isChoosingPlace) {\r\n\t\t\tvar dataPost = {\r\n\t\t\t\tName: name,\r\n\t\t\t\tDescription: description,\r\n\t\t\t\tContact: contact,\r\n\t\t\t\tColor: color,\r\n\t\t\t\tUniqueKey: uniqueKey,\r\n\t\t\t\tLocation: { Lat: lat, Lng: lng },\r\n\t\t\t\tUserIds: userIds,\r\n\t\t\t\tAttributes: attributes,\r\n\t\t\t\tGroupIds: groupIds,\r\n\t\t\t};\r\n\r\n\t\t\thandleAjaxFormSubmission(\r\n\t\t\t\t\"AddPlace\",\r\n\t\t\t\tdataPost,\r\n\t\t\t\tbtn,\r\n\t\t\t\tstatus,\r\n\t\t\t\tstrings.MSG_ADD_PLACE_SUCCESS,\r\n\t\t\t\tstrings.MSG_ADD_PLACE_ERROR,\r\n\t\t\t\tfunction (result) {\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\t// add place to map\r\n\t\t\t\t\t\tvar place = result.Place;\r\n\t\t\t\t\t\tprocessNewPlace(place);\r\n\r\n\t\t\t\t\t\tif (trkData.placeUsers !== null) {\r\n\t\t\t\t\t\t\ttrkData.placeUsers.push({ PlaceId: place.Id, UserIds: [] });\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t_.each(userIds, function (userId) {\r\n\t\t\t\t\t\t\taddUserToPlace(userId, place.Id);\r\n\t\t\t\t\t\t});\r\n\r\n\t\t\t\t\t\tuploadFile(\"fileEditPlacePhoto\", \"/Home/UploadPlacePhoto\", { placeId: place.Id }, function (res) {\r\n\t\t\t\t\t\t\tif (res == \"\" || res == \"empty\") {\r\n\t\t\t\t\t\t\t\t// plugin does not handle empty result properly\r\n\t\t\t\t\t\t\t\treturn;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tres = JSON.parse(res);\r\n\r\n\t\t\t\t\t\t\tif (res.success == true) {\r\n\t\t\t\t\t\t\t\tif (res.type != null && res.type != \"\") {\r\n\t\t\t\t\t\t\t\t\tvar item = findPlaceById(place.Id);\r\n\t\t\t\t\t\t\t\t\titem.PhotoType = res.type;\r\n\t\t\t\t\t\t\t\t\tif (\r\n\t\t\t\t\t\t\t\t\t\tdomNodes.panels.secondary.getAttribute(\"data-group-for\") === \"dialog\" &&\r\n\t\t\t\t\t\t\t\t\t\tparseInt(domNodes.panels.secondary.getAttribute(\"data-item-id\")) === place.Id\r\n\t\t\t\t\t\t\t\t\t) {\r\n\t\t\t\t\t\t\t\t\t\t$j(\"#edit-place-photo\")\r\n\t\t\t\t\t\t\t\t\t\t\t.find(\"img\")\r\n\t\t\t\t\t\t\t\t\t\t\t.attr(\r\n\t\t\t\t\t\t\t\t\t\t\t\t\"src\",\r\n\t\t\t\t\t\t\t\t\t\t\t\t\"/uploads/images/places/\" + place.Id + \"_thumb.\" + res.type + \"?rnd=\" + new Date().getTime()\r\n\t\t\t\t\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\t\t\t\t$j(\"#edit-place-photo a\").attr(\"href\", \"/uploads/images/places/\" + place.Id + \".\" + res.type);\r\n\t\t\t\t\t\t\t\t\t\t$j(\"#edit-place-photo\").show();\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t$(\"#fileEditPlacePhoto\").val(\"\").next(\".custom-file-label\").text(strings.NO_FILE_SELECTED);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t});\r\n\r\n\t\t\t\t\t\ttrkData.validation.placeAdd.resetForm();\r\n\t\t\t\t\t\ttrkData.validation.placeAdd.currentForm.reset();\r\n\t\t\t\t\t} catch (e) {\r\n\t\t\t\t\t\tconsole.error(e);\r\n\t\t\t\t\t\tthrow e;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t);\r\n\t\t} else {\r\n\t\t\tvar dataPost = {\r\n\t\t\t\tId: id,\r\n\t\t\t\tName: name,\r\n\t\t\t\tDescription: description,\r\n\t\t\t\tContact: contact,\r\n\t\t\t\tColor: color,\r\n\t\t\t\tUniqueKey: uniqueKey,\r\n\t\t\t\tLocation: { Lat: lat, Lng: lng },\r\n\t\t\t\tUserIds: userIds,\r\n\t\t\t\tAttributes: attributes,\r\n\t\t\t\tRemovePhoto: removePhoto,\r\n\t\t\t\tGroupIds: groupIds,\r\n\t\t\t};\r\n\r\n\t\t\thandleAjaxFormSubmission(\r\n\t\t\t\t\"UpdatePlace\",\r\n\t\t\t\tdataPost,\r\n\t\t\t\tbtn,\r\n\t\t\t\tstatus,\r\n\t\t\t\tstrings.MSG_EDIT_PLACE_SUCCESS,\r\n\t\t\t\tstrings.MSG_EDIT_PLACE_ERROR,\r\n\t\t\t\tfunction (result) {\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tvar place = result.Place;\r\n\t\t\t\t\t\tupdatePlace(place);\r\n\t\t\t\t\t\tvar previousUserIds = findPlaceUsersByPlaceId(id);\r\n\t\t\t\t\t\tif (previousUserIds != null) {\r\n\t\t\t\t\t\t\tif (previousUserIds.toString() != userIds.toString()) {\r\n\t\t\t\t\t\t\t\tlet value;\r\n\t\t\t\t\t\t\t\tfor (var i = 0; i < previousUserIds.length; i++) {\r\n\t\t\t\t\t\t\t\t\tvalue = previousUserIds[i];\r\n\t\t\t\t\t\t\t\t\tif ($j.inArray(value, userIds) == -1) {\r\n\t\t\t\t\t\t\t\t\t\tremoveUserFromPlace(value, id);\r\n\t\t\t\t\t\t\t\t\t\ti--;\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\t\t$j.each(userIds, function (index, value) {\r\n\t\t\t\t\t\t\t\t\tif ($j.inArray(value, previousUserIds) == -1) {\r\n\t\t\t\t\t\t\t\t\t\taddUserToPlace(value, id);\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tif (removePhoto) {\r\n\t\t\t\t\t\t\tvar oldPlace = findPlaceById(place.Id);\r\n\t\t\t\t\t\t\toldPlace.PhotoType = null;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tuploadFile(\"fileEditPlacePhoto\", \"/Home/UploadPlacePhoto\", { placeId: place.Id }, function (res) {\r\n\t\t\t\t\t\t\tif (res == \"\" || res == \"empty\") {\r\n\t\t\t\t\t\t\t\t// plugin does not handle empty result properly\r\n\t\t\t\t\t\t\t\treturn;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tres = JSON.parse(res);\r\n\r\n\t\t\t\t\t\t\tif (res.success == true) {\r\n\t\t\t\t\t\t\t\tif (res.type != null && res.type != \"\") {\r\n\t\t\t\t\t\t\t\t\tvar item = findPlaceById(place.Id);\r\n\t\t\t\t\t\t\t\t\titem.PhotoType = res.type;\r\n\t\t\t\t\t\t\t\t\tif (\r\n\t\t\t\t\t\t\t\t\t\tdomNodes.panels.secondary.getAttribute(\"data-group-for\") === \"dialog\" &&\r\n\t\t\t\t\t\t\t\t\t\tparseInt(domNodes.panels.secondary.getAttribute(\"data-item-id\")) === place.Id\r\n\t\t\t\t\t\t\t\t\t) {\r\n\t\t\t\t\t\t\t\t\t\t$j(\"#edit-place-photo\")\r\n\t\t\t\t\t\t\t\t\t\t\t.find(\"img\")\r\n\t\t\t\t\t\t\t\t\t\t\t.attr(\r\n\t\t\t\t\t\t\t\t\t\t\t\t\"src\",\r\n\t\t\t\t\t\t\t\t\t\t\t\t\"/uploads/images/places/\" + place.Id + \"_thumb.\" + res.type + \"?rnd=\" + new Date().getTime()\r\n\t\t\t\t\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\t\t\t\t$j(\"#edit-place-photo a\").attr(\"href\", \"/uploads/images/places/\" + place.Id + \".\" + res.type);\r\n\t\t\t\t\t\t\t\t\t\t$j(\"#edit-place-photo\").show();\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t$(\"#fileEditPlacePhoto\").val(\"\").next(\".custom-file-label\").text(strings.NO_FILE_SELECTED);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t} catch (e) {\r\n\t\t\t\t\t\tconsole.error(e);\r\n\t\t\t\t\t\tthrow e;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t);\r\n\t\t}\r\n\t});\r\n\t$(domNodes.dialogs.addPlace).on(\"click\", \"#AddPlaceCancel\", function (e) {\r\n\t\te.preventDefault();\r\n\t\ttrkData.validation.placeAdd.resetForm();\r\n\t\ttrkData.validation.placeAdd.currentForm.reset();\r\n\r\n\t\t// if we're editing the place, cancel = dialog close\r\n\t\tif (state.isChoosingPlace) {\r\n\t\t\t$(\"#add-place-search\").addClass(\"is-visible\");\r\n\t\t\t$(\"#form-add-place\").removeClass(\"is-visible\");\r\n\r\n\t\t\t// remove the marker from the map\r\n\t\t\tstopChoosingMapLocation(state.mapClickHandlers.PLACE);\r\n\t\t\tstartChoosingMapLocation(state.mapClickHandlers.PLACE);\r\n\t\t} else {\r\n\t\t\tdocument.getElementById(\"panel-dialog-back\").click();\r\n\t\t}\r\n\t});\r\n\r\n\t$(domNodes.dialogs.addPlace).on(\"click\", \"#btnAddPlaceLatLng\", function (e) {\r\n\t\te.preventDefault();\r\n\r\n\t\tif (!state.isChoosingPlace) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar isFormValid = $(trkData.validation.placeFind.currentForm).valid();\r\n\t\tif (!isFormValid) {\r\n\t\t\ttrkData.validation.placeFind.focusInvalid();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar lat = $(\"#txtAddPlaceLat\").val();\r\n\t\tvar lng = $(\"#txtAddPlaceLng\").val();\r\n\r\n\t\tvar latLng = L.latLng(lat, lng);\r\n\t\tif (isNaN(latLng.lat) || isNaN(latLng.lng)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tupdateChosenLocation(latLng, state.mapClickHandlers.PLACE);\r\n\t\tmap.panTo(latLng);\r\n\t});\r\n}\r\n\r\nexport function findPlaceByLatLng(latLng) {\r\n\tvar item = _.find(trkData.places, function (itm) {\r\n\t\treturn itm.Location.Lat === latLng.lat && itm.Location.Lng === latLng.lng;\r\n\t});\r\n\treturn item === undefined ? null : item;\r\n}\r\n\r\nexport function findPlaceByUniqueKey(uniqueKey) {\r\n\tvar item = _.find(trkData.places, { UniqueKey: uniqueKey });\r\n\treturn item === undefined ? null : item;\r\n}\r\n\r\nexport function findPlaceById(id) {\r\n\tvar item = _.find(trkData.places, { Id: parseInt(id) });\r\n\treturn item === undefined ? null : item;\r\n}\r\n\r\nexport function populatePlaceInformation(place) {\r\n\tvar info = [];\r\n\r\n\tif (place.Description !== null && place.Description !== \"\") {\r\n\t\tvar desc = el(\"div.map-item-description\", place.Description);\r\n\t\tinfo.push(desc);\r\n\t}\r\n\r\n\tvar itemPhoto = null;\r\n\tif (place.PhotoType != null && place.PhotoType != \"\") {\r\n\t\titemPhoto = el(\r\n\t\t\t\"a\",\r\n\t\t\t{ href: \"/uploads/images/places/\" + place.Id + \".\" + place.PhotoType, target: \"_blank\" },\r\n\t\t\tel(\"img\", { src: \"/uploads/images/places/\" + place.Id + \"_thumb.\" + place.PhotoType })\r\n\t\t);\r\n\t}\r\n\r\n\t// location table\r\n\tinfo.push(\r\n\t\tel(\r\n\t\t\t\"table.map-item-details\",\r\n\t\t\tel(\r\n\t\t\t\t\"tbody\",\r\n\t\t\t\tel(\"tr\", [\r\n\t\t\t\t\tel(\"td.field\", strings.LAT_LNG),\r\n\t\t\t\t\tel(\"td\", convertToLatLngPreference(place.Location.Lat, place.Location.Lng, place.Grid)),\r\n\t\t\t\t])\r\n\t\t\t)\r\n\t\t)\r\n\t);\r\n\r\n\tvar placeInfo = [];\r\n\tvar generalinformation = _.compact([\r\n\t\tincludeRowIfNotNull(strings.CONTACT, place.Contact),\r\n\t\tincludeRowIfNotNull(strings.PHOTO, itemPhoto),\r\n\t]);\r\n\tif (generalinformation.length > 0) {\r\n\t\tplaceInfo.push(\r\n\t\t\tcreateAccordionCard(\r\n\t\t\t\t\"general\",\r\n\t\t\t\t\"place-information-accordion\",\r\n\t\t\t\tstrings.GENERAL_FIELDS,\r\n\t\t\t\tel(\"table\", el(\"tbody\", generalinformation))\r\n\t\t\t)\r\n\t\t);\r\n\t}\r\n\r\n\tplaceInfo = _.compact(placeInfo.concat(populateCustomAttributes(place.Attributes, 2, \"place-information-accordion\")));\r\n\tif (placeInfo.length > 0) {\r\n\t\tinfo.push(el(\"#place-information-accordion.position-accordion.mt-1\", placeInfo));\r\n\t}\r\n\r\n\treturn el(\".markercontent\", info);\r\n}\r\n\r\nfunction showPlaceInformationOnMap(marker, place, isHover) {\r\n\tconsole.warn(\"showPlaceInformationOnMap\");\r\n\tif (place === undefined || place === null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tif (isHover === undefined || isHover === null) {\r\n\t\tisHover = false;\r\n\t}\r\n\r\n\tvar isMarkerSelected = !isHover;\r\n\r\n\t// if there is already marker selected, and this is not it, do not show the information\r\n\tif (state.selectedMarker !== null) {\r\n\t\tif (state.selectedMarker !== marker) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tisMarkerSelected = true;\r\n\t}\r\n\r\n\tvar latLng = marker.getLatLng();\r\n\tvar dialog = $(domNodes.infoDialogs.mapItemInformation);\r\n\tdialog.data(\"placeId\", place.Id);\r\n\r\n\tdialog.empty();\r\n\tdialog.append(populatePlaceInformation(place));\r\n\r\n\t// set zoom\r\n\tif (map.getZoom() < options.defaultZoom) {\r\n\t\tmap.setView(latLng, options.defaultZoom);\r\n\t} else {\r\n\t\tmap.panTo(latLng);\r\n\t}\r\n\r\n\t// titlebar\r\n\tdialog.dialog(\"option\", \"title\", createDialogTitleFragment(place.Name));\r\n\tvar dialogTitleBar = $(\"div.ui-dialog-titlebar\", dialog.parent());\r\n\tvar titleIcon = \"url(\" + createMarkerPath(\"Generic\", place.Color, null, null, null, false, null, false, false) + \")\";\r\n\tdialogTitleBar[0].style.backgroundImage = titleIcon;\r\n\r\n\t// place dialog\r\n\tvar icon = marker._icon;\r\n\tvar at = \"right center\";\r\n\tif (icon === undefined || icon === null) {\r\n\t\t// if there's no icon for this location then center it on the map\r\n\t\t// todo: place it at the appropriate location on the map instead of in the center by showing its marker\r\n\t\ticon = map._container;\r\n\t\tat = \"center center\";\r\n\t}\r\n\r\n\t// only move the dialog if its not already open\r\n\tif (!dialog.dialog(\"isOpen\")) {\r\n\t\tdialog.dialog(\"option\", \"position\", {\r\n\t\t\tmy: \"left center\",\r\n\t\t\tat: at,\r\n\t\t\tof: icon,\r\n\t\t\tcollision: \"flipfit\",\r\n\t\t\twithin: document.getElementById(\"map\"),\r\n\t\t\tusing: function (param1, param2) {\r\n\t\t\t\t$(this).css(param1);\r\n\t\t\t},\r\n\t\t});\r\n\t\tdialog.dialog(\"open\"); // dialog must be opened away from the marker so it can be moved without triggering\r\n\t} else {\r\n\t\t// dialog was already open but may have been resized due to content changes\r\n\t\tvar parent = dialog.closest(\".ui-dialog\");\r\n\t\tparent.position({\r\n\t\t\tmy: \"left center\",\r\n\t\t\tat: at,\r\n\t\t\tof: icon,\r\n\t\t\tcollision: \"flipfit\",\r\n\t\t\twithin: document.getElementById(\"map\"),\r\n\t\t\tusing: function (param1, param2) {\r\n\t\t\t\t// if the dialog is currently in the same horizontal position as before\r\n\t\t\t\t// then we will assume the user has not manually moved it and it can be\r\n\t\t\t\t// repositioned programmatically to fit centered vertically\r\n\t\t\t\tif (dialog.closest(\".ui-dialog\").position().left == param1.left) {\r\n\t\t\t\t\t// just move the dialog vertically\r\n\t\t\t\t\t$(this).css(param1);\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t});\r\n\t}\r\n\r\n\tif (!isMarkerSelected) {\r\n\t\tdialog.dialog({ dialogClass: \"no-close\" });\r\n\t} else {\r\n\t\tdialog.dialog({ dialogClass: \"\" });\r\n\t}\r\n\r\n\tdialog.dialog(\"moveToTop\");\r\n\tdialog.off(\"dialogclose\");\r\n\r\n\tdialog.on(\"dialogclose\", function (event, ui) {\r\n\t\t// todo: bug here when selecting locations on the map\r\n\t\t// previous, temporary dialog is closed\r\n\t\t// should only mark as selected/unselected if a permanent dialog is opened\r\n\r\n\t\tvar isHover = $j(this).data(\"isHover\");\r\n\t\tif (isHover) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tlog(\"dialog close\");\r\n\t\tmarker.data.selected = false;\r\n\t\tmarkerUnhover(marker); // todo: fix this recursive bug\r\n\r\n\t\tstate.selectedMarker = null;\r\n\t});\r\n}\r\n\r\nexport function addOrUpdatePlaceClick(e) {\r\n\tconsole.warn(\"addOrUpdatePlaceClick\");\r\n\tif (e !== null && e !== undefined) {\r\n\t\te.preventDefault();\r\n\t}\r\n\t// open place marker, if it exists, otherwise new (Garmin Waypoint)\r\n\tvar location = $(this).data(\"location\");\r\n\tif (location !== undefined) {\r\n\t\tvar name = $(this).data(\"name\");\r\n\t\tvar latlng = L.latLng(location.Lat, location.Lng);\r\n\t\tvar place = findPlaceByLatLng(latlng);\r\n\t\tif (place == null) {\r\n\t\t\topenPlaceDialog(null);\r\n\t\t\t$(\"#txtPlaceName\").val(name);\r\n\t\t\tupdateChosenLocation(latlng, state.mapClickHandlers.PLACE);\r\n\t\t} else {\r\n\t\t\tshowPlaceInformationOnMap(latlng, place);\r\n\t\t}\r\n\t} else {\r\n\t\tvar lat = this.getAttribute(\"data-lat\");\r\n\t\tvar lng = this.getAttribute(\"data-lng\");\r\n\t\t//var positionId = this.getAttribute('data-position-id');\r\n\t\t//var assetId = this.getAttribute('data-asset-id');\r\n\t\tvar latlng = L.latLng(lat, lng);\r\n\t\tvar place = findPlaceByLatLng(latlng);\r\n\t\tif (place == null) {\r\n\t\t\topenPlaceDialog(null);\r\n\t\t\tupdateChosenLocation(latlng, state.mapClickHandlers.PLACE);\r\n\t\t} else {\r\n\t\t\tshowPlaceInformationOnMap(latlng, place);\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport function openPlaceDialog(place) {\r\n\ttrkData.validation.placeFind.resetForm();\r\n\ttrkData.validation.placeFind.currentForm.reset();\r\n\ttrkData.validation.placeSearch.resetForm();\r\n\ttrkData.validation.placeSearch.currentForm.reset();\r\n\ttrkData.validation.placeAdd.resetForm();\r\n\ttrkData.validation.placeAdd.currentForm.reset();\r\n\r\n\tvar dialog = domNodes.dialogs.addPlace;\r\n\tvar dialogTitle = place !== null ? strings.EDIT_PLACE : strings.ADD_PLACE;\r\n\r\n\tvar buttonText = place !== null ? strings.SAVE_CHANGES : strings.CREATE_PLACE;\r\n\tchangePrimaryButtonLabel(domNodes.dialogs.addPlace, buttonText);\r\n\r\n\topenDialogPanel(\r\n\t\tdialog,\r\n\t\tdialogTitle,\r\n\t\tplace,\r\n\t\tfalse,\r\n\t\tfunction () {\r\n\t\t\tstate.isChoosingPlace = false;\r\n\t\t\tstopChoosingMapLocation(state.mapClickHandlers.PLACE);\r\n\r\n\t\t\t// disable marker dragging\r\n\t\t\tif (place !== null) {\r\n\t\t\t\tvar updatedPlace = findPlaceById(place.Id);\r\n\t\t\t\tvar location = updatedPlace.Location;\r\n\t\t\t\tif (\r\n\t\t\t\t\tlocation.marker !== undefined &&\r\n\t\t\t\t\tlocation.marker !== null &&\r\n\t\t\t\t\tlocation.marker.data !== undefined &&\r\n\t\t\t\t\tlocation.marker.data.isMoved === true\r\n\t\t\t\t) {\r\n\t\t\t\t\tlocation.marker.draggable = false;\r\n\t\t\t\t\t// restore the marker to the original location, if not saved\r\n\t\t\t\t\tlocation.marker.geometry = [updatedPlace.Location.Lat, updatedPlace.Location.Lng];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\t\"place\",\r\n\t\tplace !== null ? \"edit-place\" : null,\r\n\t\tplace !== null ? openPlaceDialog : null\r\n\t);\r\n\r\n\t$(\"#edit-place-photo\").hide();\r\n\t$(\"#edit-place-remove-photo\").val(\"false\");\r\n\tif (place == null) {\r\n\t\t// new place\r\n\t\tstate.isChoosingPlace = true;\r\n\t\tstartChoosingMapLocation(state.mapClickHandlers.PLACE);\r\n\t\t$(\"#accordion-edit-place-main-content\").collapse(\"show\");\r\n\t\t$(\"#add-place-search\").addClass(\"is-visible\");\r\n\t\t$(\"#form-add-place\").removeClass(\"is-visible\");\r\n\t\t$(\"#rbEditPlaceColor-red\").prop(\"checked\", true);\r\n\t} else {\r\n\t\t// editing existing place\r\n\t\t$(\"#add-place-search\").removeClass(\"is-visible\");\r\n\t\t$(\"#form-add-place\").addClass(\"is-visible\");\r\n\r\n\t\t// pan to current location and make place draggable\r\n\t\tvar location = place.Location;\r\n\t\tif (location.marker !== undefined && location.marker !== null) {\r\n\t\t\tenablePlaceMarkerDraggable(location.marker);\r\n\t\t}\r\n\r\n\t\tvar loc = L.latLng(location.Lat, location.Lng);\r\n\t\tmap.panTo(loc);\r\n\r\n\t\t$(\"#add-place-location\").text(convertToLatLngPreference(loc.lat, loc.lng, location.Grid));\r\n\t\tif (place.PhotoType != null && place.PhotoType != \"\") {\r\n\t\t\t$(\"#edit-place-photo img\").attr(\r\n\t\t\t\t\"src\",\r\n\t\t\t\t\"/uploads/images/places/\" + place.Id + \"_thumb.\" + place.PhotoType + \"?rnd=\" + new Date().getTime()\r\n\t\t\t);\r\n\t\t\t$(\"#edit-place-photo a\").attr(\"href\", \"/uploads/images/places/\" + place.Id + \".\" + place.PhotoType);\r\n\t\t\t$(\"#edit-place-photo\").show();\r\n\t\t}\r\n\t\t$(\"#txtPlaceContact\").val(place.Contact);\r\n\t\t$(\"#txtPlaceUniqueKey\").val(place.UniqueKey);\r\n\t\t$(\"#txtPlaceName\").val(place.Name);\r\n\t\t$(\"#txtPlaceDescription\").val(place.Description);\r\n\t\t$(\"#rbEditPlaceColor-\" + place.Color).prop(\"checked\", true);\r\n\t\t$(\"#hfPlaceLat\").val(place.Location.Lat);\r\n\t\t$(\"#hfPlaceLng\").val(place.Location.Lng);\r\n\t\t$(\"#hfPlaceId\").val(place.Id);\r\n\r\n\t\tif (place.Attributes != null) {\r\n\t\t\tfor (var i = 0; i < place.Attributes.length; i++) {\r\n\t\t\t\tvar attr = place.Attributes[i];\r\n\t\t\t\t$(\"#EditPlaceAttribute\" + attr.Id).val(attr.Value);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// populate groups, along with selecting groups this item currently belongs to\r\n\tvar cont = $j(\"#edit-place-groups-container\");\r\n\tvar groupContainer = document.getElementById(\"edit-place-groups-container\");\r\n\tcont.empty();\r\n\tfor (let group of sortGroups(trkData.placeGroups)) {\r\n\t\taddGroupToGroupListInItemEditDialog(group, 0, groupContainer, place);\r\n\t}\r\n\r\n\t$(\"#accordion-edit-place-users-head button\").prop(\"disabled\", !user.isAdmin);\r\n\tif (user.isAdmin) {\r\n\t\tvar cont = document.getElementById(\"place-users\");\r\n\t\t// populate users\r\n\t\tvar inputs = [];\r\n\t\tfor (var k = 0; k < trkData.users.length; k++) {\r\n\t\t\tconst user = trkData.users[k];\r\n\t\t\tvar isChecked = false;\r\n\t\t\tif (place != null) {\r\n\t\t\t\tfor (var i = 0; i < trkData.placeUsers.length; i++) {\r\n\t\t\t\t\tif (trkData.placeUsers[i].PlaceId != place.Id) {\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tfor (var j = 0; j < trkData.placeUsers[i].UserIds.length; j++) {\r\n\t\t\t\t\t\tif (trkData.placeUsers[i].UserIds[j] == user.Id) {\r\n\t\t\t\t\t\t\tisChecked = true;\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tvar checkbox = el(\"input.custom-control-input\", {\r\n\t\t\t\ttype: \"checkbox\",\r\n\t\t\t\tid: \"EditPlaceUser\" + k,\r\n\t\t\t\tname: \"EditPlaceUserIds\",\r\n\t\t\t\tvalue: user.Id,\r\n\t\t\t\tchecked: isChecked,\r\n\t\t\t});\r\n\t\t\tif (isChecked) {\r\n\t\t\t\tcheckbox.setAttribute(\"checked\", \"checked\");\r\n\t\t\t}\r\n\t\t\tinputs.push(\r\n\t\t\t\tel(\"div.custom-control.custom-checkbox\", [\r\n\t\t\t\t\tcheckbox,\r\n\t\t\t\t\ttext(\" \"),\r\n\t\t\t\t\tel(\"label.custom-control-label\", { for: \"EditPlaceUser\" + k }, user.Name + \" (\" + user.Username + \")\"),\r\n\t\t\t\t])\r\n\t\t\t);\r\n\t\t}\r\n\t\tsetChildren(cont, inputs);\r\n\t}\r\n}\r\n\r\nfunction updatePlace(place) {\r\n\tif (trkData.places === null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar oldPlace = findPlaceById(place.Id);\r\n\tassignItemToGroups(oldPlace.Id, place.GroupIds, \"places\");\r\n\r\n\t// replace place in trkData.places\r\n\tfor (var i = 0; i < trkData.places.length; i++) {\r\n\t\tif (trkData.places[i].Id == place.Id) {\r\n\t\t\ttrkData.places.splice(i, 1, place);\r\n\t\t}\r\n\t}\r\n\r\n\ttrkData.places = trkData.places.sort(sortByName);\r\n\r\n\tvar lis = domNodes.places[place.Id];\r\n\tif (lis !== undefined) {\r\n\t\t_.each(lis, function (li) {\r\n\t\t\tvar name = li.querySelector(\".place-name\");\r\n\t\t\tname.textContent = place.Name;\r\n\t\t\tvar description = li.querySelector(\".place-description\");\r\n\t\t\tdescription.textContent = place.Description;\r\n\t\t});\r\n\t}\r\n\r\n\tplace.ColorSorted = convertHexToSortable(convertNamedColorToHex(place.Color));\r\n\tif (oldPlace.Color != place.Color) {\r\n\t\t// update color and paths\r\n\t\tvar isActive = !isItemIncluded(user.displayPreferences.hiddenPlaces, place.Id);\r\n\t\tupdatePlaceListingIcon(place.Id, place.Color, isActive);\r\n\t}\r\n\r\n\t// update icon on the map as the location may have moved\r\n\t// remove old marker, add new marker\r\n\tvar wasDraggable = false;\r\n\tvar oldMarker = null;\r\n\tif (trkData.placeMarkers != null) {\r\n\t\tfor (var i = trkData.placeMarkers.length - 1; i >= 0; i -= 1) {\r\n\t\t\tvar placeMarker = trkData.placeMarkers[i];\r\n\t\t\tvar placeId = placeMarker.data.placeId;\r\n\t\t\tif (placeId == place.Id) {\r\n\t\t\t\toldMarker = placeMarker;\r\n\r\n\t\t\t\twasDraggable = placeMarker.draggable;\r\n\r\n\t\t\t\tremoveItemFromMap(placeMarker);\r\n\t\t\t\ttrkData.placeMarkers.splice(i, 1);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tvar point = [place.Location.Lat, place.Location.Lng];\r\n\tvar newPlaceMarker = addPlaceMarker(point, place.Location, place);\r\n\tif (wasDraggable) {\r\n\t\tenablePlaceMarkerDraggable(place.Location.marker);\r\n\t}\r\n\r\n\t// if place is highlighted, refresh it\r\n\tif (state.selectedMarker === oldMarker) {\r\n\t\tmarkerClick(newPlaceMarker, \"place\", point, false); // convenience, but really should just reload contents and not pan/zoom\r\n\t}\r\n\r\n\tresortItemGroup(\"all-places\");\r\n\tindexPlacesForSearch();\r\n}\r\n\r\nexport function processNewPlace(place) {\r\n\tif (trkData.places == null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tplace.ColorSorted = convertHexToSortable(convertNamedColorToHex(place.Color));\r\n\ttrkData.places.push(place);\r\n\ttrkData.placesById[place.Id] = place;\r\n\taddPlace(place);\r\n\ttogglePlaceActive(place.Id, true, true);\r\n\r\n\tdocument.getElementById(\"no-places\").classList.remove(\"is-visible\");\r\n\tdocument.getElementById(\"places-all\").classList.add(\"is-visible\");\r\n\tdocument.getElementById(\"filter-places\").querySelector(\".filter-box\").classList.add(\"is-visible\");\r\n}\r\n\r\nfunction addPlace(place) {\r\n\tvar groups = _.map(place.GroupIds ? place.GroupIds : [], function (groupId) {\r\n\t\treturn findGroupById(groupId, \"places\");\r\n\t});\r\n\tgroups.push(findGroupById(\"all-places\", \"places\"));\r\n\r\n\tvar itemNode = createPlaceNode(place);\r\n\tdomNodes.places[place.Id] = [];\r\n\t_.each(groups, function (group) {\r\n\t\tconst nodeCpy = itemNode.cloneNode(true);\r\n\t\tdomNodes.places[place.Id].push(nodeCpy);\r\n\t\tvar groupContentsNode = domNodes.groupContents[group.Id].querySelector(\"ul.group-list-list\");\r\n\r\n\t\tconst groupItems = sortItemsByMode(\"places\", trkData.places, group.Id, \"places\");\r\n\t\tif (groupItems.length > 1) {\r\n\t\t\tvar itemIndex = _.indexOf(groupItems, place);\r\n\t\t\tvar subgroups = group !== null ? groups.length : 0;\r\n\t\t\tgroupContentsNode.insertBefore(nodeCpy, groupContentsNode.children[itemIndex + subgroups]);\r\n\t\t} else {\r\n\t\t\tgroupContentsNode.appendChild(nodeCpy);\r\n\t\t}\r\n\t\tupdateGroupVisibilityStatus(group.Id, \"places\");\r\n\t});\r\n\r\n\tvar point = [place.Location.Lat, place.Location.Lng];\r\n\taddPlaceMarker(point, place.Location, place);\r\n\ttoggleItemSorting(\"places\", user.displayPreferences.sortMode.places === sortModes.CUSTOM);\r\n\r\n\tdocument.getElementById(\"no-places\").classList.remove(\"is-visible\");\r\n\tdocument.getElementById(\"places-all\").classList.add(\"is-visible\");\r\n\tdocument.getElementById(\"filter-places\").querySelector(\".filter-box\").classList.add(\"is-visible\");\r\n}\r\n\r\nexport function togglePlaceActive(placeId, makePlaceActive, updateUI) {\r\n\tvar isCurrentlyActive = !isItemIncluded(user.displayPreferences.hiddenPlaces, placeId);\r\n\tif (makePlaceActive == null) makePlaceActive = !isCurrentlyActive;\r\n\r\n\tif (isCurrentlyActive == makePlaceActive) return;\r\n\r\n\tvar place = findPlaceById(placeId);\r\n\tlet color = place.Color;\r\n\r\n\tif (trkData.placeMarkers != null) {\r\n\t\tfor (var i = 0; i < trkData.placeMarkers.length; i++) {\r\n\t\t\tvar markerPlaceId = trkData.placeMarkers[i].data.placeId;\r\n\t\t\tif (markerPlaceId == placeId) {\r\n\t\t\t\tif (makePlaceActive) {\r\n\t\t\t\t\taddItemToMap(trkData.placeMarkers[i]);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tremoveItemFromMap(trkData.placeMarkers[i]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// change icon and set preferences\r\n\tif (makePlaceActive) {\r\n\t\tupdatePlaceListingIcon(placeId, color, true);\r\n\t\tdisplayPreferencesRemove(\"hiddenPlaces\", placeId);\r\n\t} else {\r\n\t\tupdatePlaceListingIcon(placeId, color, false);\r\n\t\tdisplayPreferencesAdd(\"hiddenPlaces\", placeId);\r\n\t}\r\n\r\n\t// ensure checkbox is proper\r\n\tupdateItemVisibilityStatus(placeId, \"places\", makePlaceActive);\r\n\tif (updateUI) {\r\n\t\t_.each(place.GroupIds, function (assetGroupId) {\r\n\t\t\tupdateGroupVisibilityStatus(assetGroupId, \"places\");\r\n\t\t});\r\n\t\tupdateGroupVisibilityStatus(\"all-places\");\r\n\t}\r\n}\r\n\r\nfunction findPlaceUsersByPlaceId(id) {\r\n\tif (trkData.placeUsers == null) return null;\r\n\tfor (var i = 0, len = trkData.placeUsers.length; i < len; i++) {\r\n\t\tif (trkData.placeUsers[i].PlaceId == id) {\r\n\t\t\treturn trkData.placeUsers[i].UserIds;\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction addUserToPlace(userId, placeId) {\r\n\tif (trkData.placeUsers == null) return;\r\n\tfor (var i = 0; i < trkData.placeUsers.length; i++) {\r\n\t\tif (trkData.placeUsers[i].PlaceId == placeId) {\r\n\t\t\ttrkData.placeUsers[i].UserIds.push(userId);\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction removeUserFromPlace(userId, placeId) {\r\n\tif (trkData.placeUsers == null) return;\r\n\tfor (var i = 0; i < trkData.placeUsers.length; i++) {\r\n\t\tif (trkData.placeUsers[i].PlaceId == placeId) {\r\n\t\t\tfor (var j = 0; j < trkData.placeUsers[i].UserIds.length; j++) {\r\n\t\t\t\tif (userId == trkData.placeUsers[i].UserIds[j]) {\r\n\t\t\t\t\ttrkData.placeUsers[i].UserIds.splice(j, 1);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction updatePlaceListingIcon(id, color, isActive) {\r\n\tupdateItemListingIcon(id, color, isActive, \"Generic\", domNodes.places);\r\n}\r\n\r\nexport function indexPlacesForSearch() {\r\n\ttrkData.search.places = new JsSearch.Search(\"Id\");\r\n\ttrkData.search.places.indexStrategy = new JsSearch.AllSubstringsIndexStrategy();\r\n\tvar attributes = [\"Name\", \"Description\", \"UniqueKey\", \"Contact\"];\r\n\t// todo: location?\r\n\t_.each(attributes, function (attribute) {\r\n\t\ttrkData.search.places.addIndex(attribute);\r\n\t});\r\n\ttrkData.search.places.addDocuments(trkData.places);\r\n\tlog(\"Places indexed for search filtering.\");\r\n}\r\n\r\nexport function createPlaceNode(place) {\r\n\tvar color = \"red\";\r\n\tif (place.Color != null) {\r\n\t\tcolor = place.Color;\r\n\t}\r\n\r\n\tvar isActive = !isItemIncluded(user.displayPreferences.hiddenPlaces, place.Id);\r\n\tif (isActive) {\r\n\t\ttrkData.visible.places.push(place.Id);\r\n\t}\r\n\tvar alpha = isActive ? null : 50;\r\n\tvar imagePath = createMarkerPath(\"Generic\", color, null, alpha, null, true);\r\n\t////var li = $j('
    • ' +\r\n\t//// '' + place.Name + '' +\r\n\t//// '
      ' +\r\n\t//// '
    • ');\r\n\r\n\tvar placeDto = {\r\n\t\tid: place.Id,\r\n\t\tname: place.Name,\r\n\t\tcolor: place.Color,\r\n\t\tdescription: place.Description,\r\n\t\ticon: imagePath,\r\n\t\tisVisible: !isItemIncluded(user.displayPreferences.hiddenPlaces, place.Id),\r\n\t\tvisibilityClass: !isItemIncluded(user.displayPreferences.hiddenPlaces, place.Id) ? \"active\" : \"disabled\",\r\n\t};\r\n\r\n\tvar fragment = templates.place(placeDto);\r\n\treturn fragment.childNodes[0];\r\n\r\n\t// var placeLi = document.createElement(\"li\");\r\n\t// placeLi.className = \"places-item group-item\" + (isActive ? \" active\" : \" disabled\");\r\n\t// placeLi.setAttribute(\"data-place-id\", place.Id);\r\n\t// placeLi.style.cssText = \"background-image: url(\" + imagePath + \");\"; // background-position: 10px -6px; background-repeat: no-repeat;';\r\n\t// var placeShowHide = document.createElement(\"input\");\r\n\t// placeShowHide.setAttribute(\"type\", \"checkbox\");\r\n\t// placeShowHide.className = \"showhide\";\r\n\t// placeShowHide.setAttribute(\"data-place-id\", place.Id);\r\n\t// placeShowHide.setAttribute(\"id\", \"place-\" + place.Id);\r\n\t// if (isActive) {\r\n\t// \tplaceShowHide.setAttribute(\"checked\", \"checked\");\r\n\t// }\r\n\t// var placeName = document.createElement(\"span\");\r\n\t// placeName.className = \"place-name\";\r\n\t// placeName.setAttribute(\"for\", \"place-\" + place.Id);\r\n\t// placeName.textContent = place.Name;\r\n\t// //var placeEdit = document.createElement('div');\r\n\t// //placeEdit.className = 'edit-place';\r\n\t// var placeEditLink = document.createElement(\"a\");\r\n\t// placeEditLink.setAttribute(\"href\", \"#\");\r\n\t// placeEditLink.className = \"edit-place t-icon t-icon-context\";\r\n\t// //var placeEditIcon = document.createElement('span');\r\n\t// //placeEditIcon.className = 't-icon t-icon-context';\r\n\t// //placeEditLink.appendChild(placeEditIcon);\r\n\t// //placeEdit.appendChild(placeEditLink);\r\n\t//\r\n\t// placeLi.appendChild(placeShowHide);\r\n\t// placeLi.appendChild(placeName);\r\n\t// //placeLi.appendChild(placeEdit);\r\n\t// placeLi.appendChild(placeEditLink);\r\n\t//\r\n\t// return placeLi;\r\n}\r\n\r\nexport function deletePlace(place) {\r\n\tif (\r\n\t\tdomNodes.infoDialogs.mapItemInformation.data !== undefined &&\r\n\t\tdomNodes.infoDialogs.mapItemInformation.data !== null &&\r\n\t\tdomNodes.infoDialogs.mapItemInformation.data.placeId === place.Id &&\r\n\t\t$(domNodes.infoDialogs.mapItemInformation).dialog(\"isOpen\")\r\n\t) {\r\n\t\t$(domNodes.infoDialogs.mapItemInformation).dialog(\"close\");\r\n\t}\r\n\r\n\t_.each(domNodes.places[place.Id], function (placeNode) {\r\n\t\tplaceNode.parentNode.removeChild(placeNode);\r\n\t});\r\n\tvar panel = domNodes.panels.secondary;\r\n\tif (panel.getAttribute(\"data-group-for\") === \"places\" && parseInt(panel.getAttribute(\"data-item-id\")) === place.Id) {\r\n\t\tcloseSecondaryPanel();\r\n\t}\r\n\r\n\tif (trkData.placeUsers != null) {\r\n\t\tfor (var i = trkData.placeUsers.length - 1; i >= 0; i -= 1) {\r\n\t\t\tif (trkData.placeUsers[i].PlaceId == place.Id) {\r\n\t\t\t\ttrkData.placeUsers.splice(i, 1);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// remove place marker\r\n\tif (trkData.placeMarkers != null) {\r\n\t\tfor (var i = trkData.placeMarkers.length - 1; i >= 0; i -= 1) {\r\n\t\t\t//var placeId = $j.data(trkData.placeMarkers[i], 'placeId');\r\n\t\t\tvar placeId = trkData.placeMarkers[i].data.placeId;\r\n\t\t\tif (placeId == place.Id) {\r\n\t\t\t\tremoveItemFromMap(trkData.placeMarkers[i]);\r\n\t\t\t\ttrkData.placeMarkers.splice(i, 1);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// remove from trkData.places\r\n\tfor (var i = 0; i < trkData.places.length; i++) {\r\n\t\tif (trkData.places[i].Id == place.Id) trkData.places.splice(i, 1);\r\n\t}\r\n\r\n\t// if last fence, show no places\r\n\tif (trkData.places.length == 0) {\r\n\t\tdocument.getElementById(\"no-places\").classList.add(\"is-visible\");\r\n\t\tdocument.getElementById(\"places-all\").classList.remove(\"is-visible\");\r\n\t\tdocument.getElementById(\"filter-places\").querySelector(\".filter-box\").classList.remove(\"is-visible\");\r\n\t}\r\n\tupdateGroupVisibilityStatus(\"all-places\");\r\n\tindexPlacesForSearch();\r\n}","(function () { function r(e, n, t) { function o(i, f) { if (!n[i]) { if (!e[i]) { var c = \"function\" == typeof require && require; if (!f && c) return c(i, !0); if (u) return u(i, !0); var a = new Error(\"Cannot find module '\" + i + \"'\"); throw a.code = \"MODULE_NOT_FOUND\", a } var p = n[i] = { exports: {} }; e[i][0].call(p.exports, function (r) { var n = e[i][1][r]; return o(n || r) }, p, p.exports, r, e, n, t) } return n[i].exports } for (var u = \"function\" == typeof require && require, i = 0; i < t.length; i++)o(t[i]); return o } return r })()({\r\n 1: [function (_dereq_, module, exports) {\r\nfunction corslite(url, callback, cors) {\r\n var sent = false;\r\n\r\n if (typeof window.XMLHttpRequest === 'undefined') {\r\n return callback(Error('Browser not supported'));\r\n }\r\n\r\n if (typeof cors === 'undefined') {\r\n var m = url.match(/^\\s*https?:\\/\\/[^\\/]*/);\r\n cors = m && (m[0] !== location.protocol + '//' + location.hostname +\r\n (location.port ? ':' + location.port : ''));\r\n }\r\n\r\n var x = new window.XMLHttpRequest();\r\n\r\n function isSuccessful(status) {\r\n return status >= 200 && status < 300 || status === 304;\r\n }\r\n\r\n if (cors && !('withCredentials' in x)) {\r\n // IE8-9\r\n x = new window.XDomainRequest();\r\n\r\n // Ensure callback is never called synchronously, i.e., before\r\n // x.send() returns (this has been observed in the wild).\r\n // See https://github.com/mapbox/mapbox.js/issues/472\r\n var original = callback;\r\n callback = function() {\r\n if (sent) {\r\n original.apply(this, arguments);\r\n } else {\r\n var that = this, args = arguments;\r\n setTimeout(function() {\r\n original.apply(that, args);\r\n }, 0);\r\n }\r\n }\r\n }\r\n\r\n function loaded() {\r\n if (\r\n // XDomainRequest\r\n x.status === undefined ||\r\n // modern browsers\r\n isSuccessful(x.status)) callback.call(x, null, x);\r\n else callback.call(x, x, null);\r\n }\r\n\r\n // Both `onreadystatechange` and `onload` can fire. `onreadystatechange`\r\n // has [been supported for longer](http://stackoverflow.com/a/9181508/229001).\r\n if ('onload' in x) {\r\n x.onload = loaded;\r\n } else {\r\n x.onreadystatechange = function readystate() {\r\n if (x.readyState === 4) {\r\n loaded();\r\n }\r\n };\r\n }\r\n\r\n // Call the callback with the XMLHttpRequest object as an error and prevent\r\n // it from ever being called again by reassigning it to `noop`\r\n x.onerror = function error(evt) {\r\n // XDomainRequest provides no evt parameter\r\n callback.call(this, evt || true, null);\r\n callback = function() { };\r\n };\r\n\r\n // IE9 must have onprogress be set to a unique function.\r\n x.onprogress = function() { };\r\n\r\n x.ontimeout = function(evt) {\r\n callback.call(this, evt, null);\r\n callback = function() { };\r\n };\r\n\r\n x.onabort = function(evt) {\r\n callback.call(this, evt, null);\r\n callback = function() { };\r\n };\r\n\r\n // GET is the only supported HTTP Verb by XDomainRequest and is the\r\n // only one supported here.\r\n x.open('GET', url, true);\r\n\r\n // Send the request. Sending data is not supported.\r\n x.send(null);\r\n sent = true;\r\n\r\n return x;\r\n}\r\n\r\nif (typeof module !== 'undefined') module.exports = corslite;\r\n\r\n}, {}],\r\n 2: [function (_dereq_, module, exports) {\r\n'use strict';\r\n\r\n/**\r\n * Based off of [the offical Google document](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)\r\n *\r\n * Some parts from [this implementation](http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/PolylineEncoder.js)\r\n * by [Mark McClure](http://facstaff.unca.edu/mcmcclur/)\r\n *\r\n * @module polyline\r\n */\r\n\r\nvar polyline = {};\r\n\r\nfunction py2_round(value) {\r\n // Google's polyline algorithm uses the same rounding strategy as Python 2, which is different from JS for negative values\r\n return Math.floor(Math.abs(value) + 0.5) * Math.sign(value);\r\n}\r\n\r\nfunction encode(current, previous, factor) {\r\n current = py2_round(current * factor);\r\n previous = py2_round(previous * factor);\r\n var coordinate = current - previous;\r\n coordinate <<= 1;\r\n if (current - previous < 0) {\r\n coordinate = ~coordinate;\r\n }\r\n var output = '';\r\n while (coordinate >= 0x20) {\r\n output += String.fromCharCode((0x20 | (coordinate & 0x1f)) + 63);\r\n coordinate >>= 5;\r\n }\r\n output += String.fromCharCode(coordinate + 63);\r\n return output;\r\n}\r\n\r\n/**\r\n * Decodes to a [latitude, longitude] coordinates array.\r\n *\r\n * This is adapted from the implementation in Project-OSRM.\r\n *\r\n * @param {String} str\r\n * @param {Number} precision\r\n * @returns {Array}\r\n *\r\n * @see https://github.com/Project-OSRM/osrm-frontend/blob/master/WebContent/routing/OSRM.RoutingGeometry.js\r\n */\r\npolyline.decode = function(str, precision) {\r\n var index = 0,\r\n lat = 0,\r\n lng = 0,\r\n coordinates = [],\r\n shift = 0,\r\n result = 0,\r\n byte = null,\r\n latitude_change,\r\n longitude_change,\r\n factor = Math.pow(10, precision || 5);\r\n\r\n // Coordinates have variable length when encoded, so just keep\r\n // track of whether we've hit the end of the string. In each\r\n // loop iteration, a single coordinate is decoded.\r\n while (index < str.length) {\r\n\r\n // Reset shift, result, and byte\r\n byte = null;\r\n shift = 0;\r\n result = 0;\r\n\r\n do {\r\n byte = str.charCodeAt(index++) - 63;\r\n result |= (byte & 0x1f) << shift;\r\n shift += 5;\r\n } while (byte >= 0x20);\r\n\r\n latitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1));\r\n\r\n shift = result = 0;\r\n\r\n do {\r\n byte = str.charCodeAt(index++) - 63;\r\n result |= (byte & 0x1f) << shift;\r\n shift += 5;\r\n } while (byte >= 0x20);\r\n\r\n longitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1));\r\n\r\n lat += latitude_change;\r\n lng += longitude_change;\r\n\r\n coordinates.push([lat / factor, lng / factor]);\r\n }\r\n\r\n return coordinates;\r\n};\r\n\r\n/**\r\n * Encodes the given [latitude, longitude] coordinates array.\r\n *\r\n * @param {Array.>} coordinates\r\n * @param {Number} precision\r\n * @returns {String}\r\n */\r\npolyline.encode = function(coordinates, precision) {\r\n if (!coordinates.length) { return ''; }\r\n\r\n var factor = Math.pow(10, precision || 5),\r\n output = encode(coordinates[0][0], 0, factor) + encode(coordinates[0][1], 0, factor);\r\n\r\n for (var i = 1; i < coordinates.length; i++) {\r\n var a = coordinates[i], b = coordinates[i - 1];\r\n output += encode(a[0], b[0], factor);\r\n output += encode(a[1], b[1], factor);\r\n }\r\n\r\n return output;\r\n};\r\n\r\nfunction flipped(coords) {\r\n var flipped = [];\r\n for (var i = 0; i < coords.length; i++) {\r\n flipped.push(coords[i].slice().reverse());\r\n }\r\n return flipped;\r\n}\r\n\r\n/**\r\n * Encodes a GeoJSON LineString feature/geometry.\r\n *\r\n * @param {Object} geojson\r\n * @param {Number} precision\r\n * @returns {String}\r\n */\r\npolyline.fromGeoJSON = function(geojson, precision) {\r\n if (geojson && geojson.type === 'Feature') {\r\n geojson = geojson.geometry;\r\n }\r\n if (!geojson || geojson.type !== 'LineString') {\r\n throw new Error('Input must be a GeoJSON LineString');\r\n }\r\n return polyline.encode(flipped(geojson.coordinates), precision);\r\n};\r\n\r\n/**\r\n * Decodes to a GeoJSON LineString geometry.\r\n *\r\n * @param {String} str\r\n * @param {Number} precision\r\n * @returns {Object}\r\n */\r\npolyline.toGeoJSON = function(str, precision) {\r\n var coords = polyline.decode(str, precision);\r\n return {\r\n type: 'LineString',\r\n coordinates: flipped(coords)\r\n };\r\n};\r\n\r\nif (typeof module === 'object' && module.exports) {\r\n module.exports = polyline;\r\n}\r\n\r\n }, {}],\r\n 3: [function (_dereq_, module, exports) {\r\nvar languages = _dereq_('./languages');\r\nvar instructions = languages.instructions;\r\nvar grammars = languages.grammars;\r\nvar abbreviations = languages.abbreviations;\r\n\r\nmodule.exports = function(version) {\r\n Object.keys(instructions).forEach(function(code) {\r\n if (!instructions[code][version]) { throw 'invalid version ' + version + ': ' + code + ' not supported'; }\r\n });\r\n\r\n return {\r\n capitalizeFirstLetter: function(language, string) {\r\n return string.charAt(0).toLocaleUpperCase(language) + string.slice(1);\r\n },\r\n ordinalize: function(language, number) {\r\n // Transform numbers to their translated ordinalized value\r\n if (!language) throw new Error('No language code provided');\r\n\r\n return instructions[language][version].constants.ordinalize[number.toString()] || '';\r\n },\r\n directionFromDegree: function(language, degree) {\r\n // Transform degrees to their translated compass direction\r\n if (!language) throw new Error('No language code provided');\r\n if (!degree && degree !== 0) {\r\n // step had no bearing_after degree, ignoring\r\n return '';\r\n } else if (degree >= 0 && degree <= 20) {\r\n return instructions[language][version].constants.direction.north;\r\n } else if (degree > 20 && degree < 70) {\r\n return instructions[language][version].constants.direction.northeast;\r\n } else if (degree >= 70 && degree <= 110) {\r\n return instructions[language][version].constants.direction.east;\r\n } else if (degree > 110 && degree < 160) {\r\n return instructions[language][version].constants.direction.southeast;\r\n } else if (degree >= 160 && degree <= 200) {\r\n return instructions[language][version].constants.direction.south;\r\n } else if (degree > 200 && degree < 250) {\r\n return instructions[language][version].constants.direction.southwest;\r\n } else if (degree >= 250 && degree <= 290) {\r\n return instructions[language][version].constants.direction.west;\r\n } else if (degree > 290 && degree < 340) {\r\n return instructions[language][version].constants.direction.northwest;\r\n } else if (degree >= 340 && degree <= 360) {\r\n return instructions[language][version].constants.direction.north;\r\n } else {\r\n throw new Error('Degree ' + degree + ' invalid');\r\n }\r\n },\r\n laneConfig: function(step) {\r\n // Reduce any lane combination down to a contracted lane diagram\r\n if (!step.intersections || !step.intersections[0].lanes) throw new Error('No lanes object');\r\n\r\n var config = [];\r\n var currentLaneValidity = null;\r\n\r\n step.intersections[0].lanes.forEach(function (lane) {\r\n if (currentLaneValidity === null || currentLaneValidity !== lane.valid) {\r\n if (lane.valid) {\r\n config.push('o');\r\n } else {\r\n config.push('x');\r\n }\r\n currentLaneValidity = lane.valid;\r\n }\r\n });\r\n\r\n return config.join('');\r\n },\r\n getWayName: function(language, step, options) {\r\n var classes = options ? options.classes || [] : [];\r\n if (typeof step !== 'object') throw new Error('step must be an Object');\r\n if (!language) throw new Error('No language code provided');\r\n if (!Array.isArray(classes)) throw new Error('classes must be an Array or undefined');\r\n\r\n var wayName;\r\n var name = step.name || '';\r\n var ref = (step.ref || '').split(';')[0];\r\n\r\n // Remove hacks from Mapbox Directions mixing ref into name\r\n if (name === step.ref) {\r\n // if both are the same we assume that there used to be an empty name, with the ref being filled in for it\r\n // we only need to retain the ref then\r\n name = '';\r\n }\r\n name = name.replace(' (' + step.ref + ')', '');\r\n\r\n // In attempt to avoid using the highway name of a way,\r\n // check and see if the step has a class which should signal\r\n // the ref should be used instead of the name.\r\n var wayMotorway = classes.indexOf('motorway') !== -1;\r\n\r\n if (name && ref && name !== ref && !wayMotorway) {\r\n var phrase = instructions[language][version].phrase['name and ref'] ||\r\n instructions.en[version].phrase['name and ref'];\r\n wayName = this.tokenize(language, phrase, {\r\n name: name,\r\n ref: ref\r\n }, options);\r\n } else if (name && ref && wayMotorway && (/\\d/).test(ref)) {\r\n wayName = options && options.formatToken ? options.formatToken('ref', ref) : ref;\r\n } else if (!name && ref) {\r\n wayName = options && options.formatToken ? options.formatToken('ref', ref) : ref;\r\n } else {\r\n wayName = options && options.formatToken ? options.formatToken('name', name) : name;\r\n }\r\n\r\n return wayName;\r\n },\r\n\r\n /**\r\n * Formulate a localized text instruction from a step.\r\n *\r\n * @param {string} language Language code.\r\n * @param {object} step Step including maneuver property.\r\n * @param {object} opts Additional options.\r\n * @param {string} opts.legIndex Index of leg in the route.\r\n * @param {string} opts.legCount Total number of legs in the route.\r\n * @param {array} opts.classes List of road classes.\r\n * @param {string} opts.waypointName Name of waypoint for arrival instruction.\r\n *\r\n * @return {string} Localized text instruction.\r\n */\r\n compile: function(language, step, opts) {\r\n if (!language) throw new Error('No language code provided');\r\n if (languages.supportedCodes.indexOf(language) === -1) throw new Error('language code ' + language + ' not loaded');\r\n if (!step.maneuver) throw new Error('No step maneuver provided');\r\n var options = opts || {};\r\n\r\n var type = step.maneuver.type;\r\n var modifier = step.maneuver.modifier;\r\n var mode = step.mode;\r\n // driving_side will only be defined in OSRM 5.14+\r\n var side = step.driving_side;\r\n\r\n if (!type) { throw new Error('Missing step maneuver type'); }\r\n if (type !== 'depart' && type !== 'arrive' && !modifier) { throw new Error('Missing step maneuver modifier'); }\r\n\r\n if (!instructions[language][version][type]) {\r\n // Log for debugging\r\n console.log('Encountered unknown instruction type: ' + type); // eslint-disable-line no-console\r\n // OSRM specification assumes turn types can be added without\r\n // major version changes. Unknown types are to be treated as\r\n // type `turn` by clients\r\n type = 'turn';\r\n }\r\n\r\n // Use special instructions if available, otherwise `defaultinstruction`\r\n var instructionObject;\r\n if (instructions[language][version].modes[mode]) {\r\n instructionObject = instructions[language][version].modes[mode];\r\n } else {\r\n // omit side from off ramp if same as driving_side\r\n // note: side will be undefined if the input is from OSRM <5.14\r\n // but the condition should still evaluate properly regardless\r\n var omitSide = type === 'off ramp' && modifier.indexOf(side) >= 0;\r\n if (instructions[language][version][type][modifier] && !omitSide) {\r\n instructionObject = instructions[language][version][type][modifier];\r\n } else {\r\n instructionObject = instructions[language][version][type].default;\r\n }\r\n }\r\n\r\n // Special case handling\r\n var laneInstruction;\r\n switch (type) {\r\n case 'use lane':\r\n laneInstruction = instructions[language][version].constants.lanes[this.laneConfig(step)];\r\n if (!laneInstruction) {\r\n // If the lane combination is not found, default to continue straight\r\n instructionObject = instructions[language][version]['use lane'].no_lanes;\r\n }\r\n break;\r\n case 'rotary':\r\n case 'roundabout':\r\n if (step.rotary_name && step.maneuver.exit && instructionObject.name_exit) {\r\n instructionObject = instructionObject.name_exit;\r\n } else if (step.rotary_name && instructionObject.name) {\r\n instructionObject = instructionObject.name;\r\n } else if (step.maneuver.exit && instructionObject.exit) {\r\n instructionObject = instructionObject.exit;\r\n } else {\r\n instructionObject = instructionObject.default;\r\n }\r\n break;\r\n default:\r\n // NOOP, since no special logic for that type\r\n }\r\n\r\n // Decide way_name with special handling for name and ref\r\n var wayName = this.getWayName(language, step, options);\r\n\r\n // Decide which instruction string to use\r\n // Destination takes precedence over name\r\n var instruction;\r\n if (step.destinations && step.exits && instructionObject.exit_destination) {\r\n instruction = instructionObject.exit_destination;\r\n } else if (step.destinations && instructionObject.destination) {\r\n instruction = instructionObject.destination;\r\n } else if (step.exits && instructionObject.exit) {\r\n instruction = instructionObject.exit;\r\n } else if (wayName && instructionObject.name) {\r\n instruction = instructionObject.name;\r\n } else if (options.waypointName && instructionObject.named) {\r\n instruction = instructionObject.named;\r\n } else {\r\n instruction = instructionObject.default;\r\n }\r\n\r\n var destinations = step.destinations && step.destinations.split(': ');\r\n var destinationRef = destinations && destinations[0].split(',')[0];\r\n var destination = destinations && destinations[1] && destinations[1].split(',')[0];\r\n var firstDestination;\r\n if (destination && destinationRef) {\r\n firstDestination = destinationRef + ': ' + destination;\r\n } else {\r\n firstDestination = destinationRef || destination || '';\r\n }\r\n\r\n var nthWaypoint = options.legIndex >= 0 && options.legIndex !== options.legCount - 1 ? this.ordinalize(language, options.legIndex + 1) : '';\r\n\r\n // Replace tokens\r\n // NOOP if they don't exist\r\n var replaceTokens = {\r\n 'way_name': wayName,\r\n 'destination': firstDestination,\r\n 'exit': (step.exits || '').split(';')[0],\r\n 'exit_number': this.ordinalize(language, step.maneuver.exit || 1),\r\n 'rotary_name': step.rotary_name,\r\n 'lane_instruction': laneInstruction,\r\n 'modifier': instructions[language][version].constants.modifier[modifier],\r\n 'direction': this.directionFromDegree(language, step.maneuver.bearing_after),\r\n 'nth': nthWaypoint,\r\n 'waypoint_name': options.waypointName\r\n };\r\n\r\n return this.tokenize(language, instruction, replaceTokens, options);\r\n },\r\n grammarize: function(language, name, grammar) {\r\n if (!language) throw new Error('No language code provided');\r\n // Process way/rotary/any name with applying grammar rules if any\r\n if (grammar && grammars && grammars[language] && grammars[language][version]) {\r\n var rules = grammars[language][version][grammar];\r\n if (rules) {\r\n // Pass original name to rules' regular expressions enclosed with spaces for simplier parsing\r\n var n = ' ' + name + ' ';\r\n var flags = grammars[language].meta.regExpFlags || '';\r\n rules.forEach(function(rule) {\r\n var re = new RegExp(rule[0], flags);\r\n n = n.replace(re, rule[1]);\r\n });\r\n\r\n return n.trim();\r\n }\r\n }\r\n\r\n return name;\r\n },\r\n abbreviations: abbreviations,\r\n tokenize: function(language, instruction, tokens, options) {\r\n if (!language) throw new Error('No language code provided');\r\n // Keep this function context to use in inline function below (no arrow functions in ES4)\r\n var that = this;\r\n var startedWithToken = false;\r\n var output = instruction.replace(/\\{(\\w+)(?::(\\w+))?\\}/g, function(token, tag, grammar, offset) {\r\n var value = tokens[tag];\r\n\r\n // Return unknown token unchanged\r\n if (typeof value === 'undefined') {\r\n return token;\r\n }\r\n\r\n value = that.grammarize(language, value, grammar);\r\n\r\n // If this token appears at the beginning of the instruction, capitalize it.\r\n if (offset === 0 && instructions[language].meta.capitalizeFirstLetter) {\r\n startedWithToken = true;\r\n value = that.capitalizeFirstLetter(language, value);\r\n }\r\n\r\n if (options && options.formatToken) {\r\n value = options.formatToken(tag, value);\r\n }\r\n\r\n return value;\r\n })\r\n .replace(/ {2}/g, ' '); // remove excess spaces\r\n\r\n if (!startedWithToken && instructions[language].meta.capitalizeFirstLetter) {\r\n return this.capitalizeFirstLetter(language, output);\r\n }\r\n\r\n return output;\r\n }\r\n };\r\n};\r\n\r\n }, { \"./languages\": 4 }],\r\n 4: [function (_dereq_, module, exports) {\r\n// Load all language files explicitly to allow integration\r\n// with bundling tools like webpack and browserify\r\nvar instructionsDe = _dereq_('./languages/translations/de.json');\r\nvar instructionsEn = _dereq_('./languages/translations/en.json');\r\nvar instructionsEs = _dereq_('./languages/translations/es.json');\r\nvar instructionsEsEs = _dereq_('./languages/translations/es-ES.json');\r\nvar instructionsFr = _dereq_('./languages/translations/fr.json');\r\nvar instructionsIt = _dereq_('./languages/translations/it.json');\r\nvar instructionsJa = _dereq_('./languages/translations/ja.json');\r\nvar instructionsPtBr = _dereq_('./languages/translations/pt-BR.json');\r\nvar instructionsPtPt = _dereq_('./languages/translations/pt-PT.json');\r\nvar instructionsRu = _dereq_('./languages/translations/ru.json');\r\nvar instructionsVi = _dereq_('./languages/translations/vi.json');\r\n\r\n// Load all grammar files\r\nvar grammarFr = _dereq_('./languages/grammar/fr.json');\r\nvar grammarRu = _dereq_('./languages/grammar/ru.json');\r\n\r\n// Load all abbreviations files\r\nvar ebbreviationsDe = _dereq_('./languages/abbreviations/de.json');\r\nvar abbreviationsEn = _dereq_('./languages/abbreviations/en.json');\r\nvar abbreviationsEs = _dereq_('./languages/abbreviations/es.json');\r\nvar abbreviationsFr = _dereq_('./languages/abbreviations/fr.json');\r\nvar abbreviationsRu = _dereq_('./languages/abbreviations/ru.json');\r\nvar abbreviationsVi = _dereq_('./languages/abbreviations/vi.json');\r\n\r\n// Create a list of supported codes\r\nvar instructions = {\r\n 'de': instructionsDe,\r\n 'en': instructionsEn,\r\n 'es': instructionsEs,\r\n 'es-ES': instructionsEsEs,\r\n 'fr': instructionsFr,\r\n 'it': instructionsIt,\r\n 'ja': instructionsJa,\r\n 'pt-BR': instructionsPtBr,\r\n 'pt-PT': instructionsPtPt,\r\n 'ru': instructionsRu,\r\n 'vi': instructionsVi\r\n};\r\n\r\n// Create list of supported grammar\r\nvar grammars = {\r\n 'fr': grammarFr,\r\n 'ru': grammarRu\r\n};\r\n\r\n// Create list of supported abbrevations\r\nvar abbreviations = {\r\n 'de': ebbreviationsDe,\r\n 'en': abbreviationsEn,\r\n 'es': abbreviationsEs,\r\n 'fr': abbreviationsFr,\r\n 'ru': abbreviationsRu,\r\n 'vi': abbreviationsVi\r\n};\r\nmodule.exports = {\r\n supportedCodes: Object.keys(instructions),\r\n instructions: instructions,\r\n grammars: grammars,\r\n abbreviations: abbreviations\r\n};\r\n\r\n }, {\r\n \"./languages/abbreviations/de.json\": 8,\r\n \"./languages/abbreviations/en.json\": 9,\r\n \"./languages/abbreviations/es.json\": 10,\r\n \"./languages/abbreviations/fr.json\": 11,\r\n \"./languages/abbreviations/ru.json\": 16,\r\n \"./languages/abbreviations/vi.json\": 20,\r\n \"./languages/grammar/fr.json\": 22,\r\n \"./languages/grammar/ru.json\": 24,\r\n \"./languages/translations/de.json\": 27,\r\n \"./languages/translations/en.json\": 28,\r\n \"./languages/translations/es-ES.json\": 30,\r\n \"./languages/translations/es.json\": 31,\r\n \"./languages/translations/fr.json\": 33,\r\n \"./languages/translations/it.json\": 37,\r\n \"./languages/translations/ja.json\": 38,\r\n \"./languages/translations/pt-BR.json\": 44,\r\n \"./languages/translations/pt-PT.json\": 45,\r\n \"./languages/translations/ru.json\": 47,\r\n \"./languages/translations/vi.json\": 52\r\n }],\r\n 8: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"abbreviations\": {},\r\n \"classifications\": {},\r\n \"directions\": {\r\n \"osten\": \"O\",\r\n \"nordosten\": \"NO\",\r\n \"süden\": \"S\",\r\n \"nordwest\": \"NW\",\r\n \"norden\": \"N\",\r\n \"südost\": \"SO\",\r\n \"südwest\": \"SW\",\r\n \"westen\": \"W\"\r\n }\r\n}\r\n\r\n }, {}],\r\n 9: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"abbreviations\": {\r\n \"square\": \"Sq\",\r\n \"centre\": \"Ctr\",\r\n \"sister\": \"Sr\",\r\n \"lake\": \"Lk\",\r\n \"fort\": \"Ft\",\r\n \"route\": \"Rte\",\r\n \"william\": \"Wm\",\r\n \"national\": \"Nat’l\",\r\n \"junction\": \"Jct\",\r\n \"center\": \"Ctr\",\r\n \"saint\": \"St\",\r\n \"saints\": \"SS\",\r\n \"station\": \"Sta\",\r\n \"mount\": \"Mt\",\r\n \"junior\": \"Jr\",\r\n \"mountain\": \"Mtn\",\r\n \"heights\": \"Hts\",\r\n \"university\": \"Univ\",\r\n \"school\": \"Sch\",\r\n \"international\": \"Int’l\",\r\n \"apartments\": \"Apts\",\r\n \"crossing\": \"Xing\",\r\n \"creek\": \"Crk\",\r\n \"township\": \"Twp\",\r\n \"downtown\": \"Dtwn\",\r\n \"father\": \"Fr\",\r\n \"senior\": \"Sr\",\r\n \"point\": \"Pt\",\r\n \"river\": \"Riv\",\r\n \"market\": \"Mkt\",\r\n \"village\": \"Vil\",\r\n \"park\": \"Pk\",\r\n \"memorial\": \"Mem\"\r\n },\r\n \"classifications\": {\r\n \"place\": \"Pl\",\r\n \"circle\": \"Cir\",\r\n \"bypass\": \"Byp\",\r\n \"motorway\": \"Mwy\",\r\n \"crescent\": \"Cres\",\r\n \"road\": \"Rd\",\r\n \"cove\": \"Cv\",\r\n \"lane\": \"Ln\",\r\n \"square\": \"Sq\",\r\n \"street\": \"St\",\r\n \"freeway\": \"Fwy\",\r\n \"walk\": \"Wk\",\r\n \"plaza\": \"Plz\",\r\n \"parkway\": \"Pky\",\r\n \"avenue\": \"Ave\",\r\n \"pike\": \"Pk\",\r\n \"drive\": \"Dr\",\r\n \"highway\": \"Hwy\",\r\n \"footway\": \"Ftwy\",\r\n \"point\": \"Pt\",\r\n \"court\": \"Ct\",\r\n \"terrace\": \"Ter\",\r\n \"walkway\": \"Wky\",\r\n \"alley\": \"Aly\",\r\n \"expressway\": \"Expy\",\r\n \"bridge\": \"Br\",\r\n \"boulevard\": \"Blvd\",\r\n \"turnpike\": \"Tpk\"\r\n },\r\n \"directions\": {\r\n \"southeast\": \"SE\",\r\n \"northwest\": \"NW\",\r\n \"south\": \"S\",\r\n \"west\": \"W\",\r\n \"southwest\": \"SW\",\r\n \"north\": \"N\",\r\n \"east\": \"E\",\r\n \"northeast\": \"NE\"\r\n }\r\n}\r\n\r\n }, {}],\r\n 10: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"abbreviations\": {\r\n \"segunda\": \"2ª\",\r\n \"octubre\": \"8bre\",\r\n \"doctores\": \"Drs\",\r\n \"doctora\": \"Dra\",\r\n \"internacional\": \"Intl\",\r\n \"doctor\": \"Dr\",\r\n \"segundo\": \"2º\",\r\n \"señorita\": \"Srta\",\r\n \"doctoras\": \"Drs\",\r\n \"primera\": \"1ª\",\r\n \"primero\": \"1º\",\r\n \"san\": \"S\",\r\n \"colonia\": \"Col\",\r\n \"doña\": \"Dña\",\r\n \"septiembre\": \"7bre\",\r\n \"diciembre\": \"10bre\",\r\n \"señor\": \"Sr\",\r\n \"ayuntamiento\": \"Ayto\",\r\n \"señora\": \"Sra\",\r\n \"tercera\": \"3ª\",\r\n \"tercero\": \"3º\",\r\n \"don\": \"D\",\r\n \"santa\": \"Sta\",\r\n \"ciudad\": \"Cdad\",\r\n \"noviembre\": \"9bre\",\r\n \"departamento\": \"Dep\"\r\n },\r\n \"classifications\": {\r\n \"camino\": \"Cmno\",\r\n \"avenida\": \"Av\",\r\n \"paseo\": \"Pº\",\r\n \"autopista\": \"Auto\",\r\n \"calle\": \"C\",\r\n \"plaza\": \"Pza\",\r\n \"carretera\": \"Crta\"\r\n },\r\n \"directions\": {\r\n \"este\": \"E\",\r\n \"noreste\": \"NE\",\r\n \"sur\": \"S\",\r\n \"suroeste\": \"SO\",\r\n \"noroeste\": \"NO\",\r\n \"oeste\": \"O\",\r\n \"sureste\": \"SE\",\r\n \"norte\": \"N\"\r\n }\r\n}\r\n\r\n }, {}],\r\n 11: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"abbreviations\": {\r\n \"allée\": \"All\",\r\n \"aérodrome\": \"Aérod\",\r\n \"aéroport\": \"Aérop\"\r\n },\r\n \"classifications\": {\r\n \"centrale\": \"Ctrale\",\r\n \"campings\": \"Camp.\",\r\n \"urbains\": \"Urb.\",\r\n \"mineure\": \"Min.\",\r\n \"publique\": \"Publ.\",\r\n \"supérieur\": \"Sup.\",\r\n \"fédération\": \"Féd.\",\r\n \"notre-dame\": \"ND\",\r\n \"saint\": \"St\",\r\n \"centre hospitalier régional\": \"CHR\",\r\n \"exploitation\": \"Exploit.\",\r\n \"général\": \"Gal\",\r\n \"civiles\": \"Civ.\",\r\n \"maritimes\": \"Marit.\",\r\n \"aviation\": \"Aviat.\",\r\n \"iii\": \"3\",\r\n \"archéologique\": \"Archéo.\",\r\n \"musical\": \"Music.\",\r\n \"musicale\": \"Music.\",\r\n \"immeuble\": \"Imm.\",\r\n \"xv\": \"15\",\r\n \"hôtel\": \"Hôt.\",\r\n \"alpine\": \"Alp.\",\r\n \"communale\": \"Commun.\",\r\n \"v\": \"5\",\r\n \"global\": \"Glob.\",\r\n \"université\": \"Univ.\",\r\n \"confédéral\": \"Conféd.\",\r\n \"xx\": \"20\",\r\n \"x\": \"10\",\r\n \"piscine\": \"Pisc.\",\r\n \"dimanche\": \"di.\",\r\n \"fleuve\": \"Flv\",\r\n \"postaux\": \"Post.\",\r\n \"musicienne\": \"Music.\",\r\n \"département\": \"Dépt\",\r\n \"février\": \"Févr.\",\r\n \"municipales\": \"Munic.\",\r\n \"province\": \"Prov.\",\r\n \"communautés\": \"Commtés\",\r\n \"barrage\": \"Barr.\",\r\n \"mercredi\": \"me.\",\r\n \"présidentes\": \"Pdtes\",\r\n \"cafétérias\": \"Cafét.\",\r\n \"théâtral\": \"Thé.\",\r\n \"viticulteur\": \"Vitic.\",\r\n \"poste\": \"Post.\",\r\n \"spécialisée\": \"Spéc.\",\r\n \"agriculture\": \"Agric.\",\r\n \"infirmier\": \"Infirm.\",\r\n \"animation\": \"Anim.\",\r\n \"mondiale\": \"Mond.\",\r\n \"arrêt\": \"Arr.\",\r\n \"zone\": \"zon.\",\r\n \"municipaux\": \"Munic.\",\r\n \"grand\": \"Gd\",\r\n \"janvier\": \"Janv.\",\r\n \"fondateur\": \"Fond.\",\r\n \"première\": \"1re\",\r\n \"municipale\": \"Munic.\",\r\n \"direction\": \"Dir.\",\r\n \"anonyme\": \"Anon.\",\r\n \"départementale\": \"Dépt\",\r\n \"moyens\": \"Moy.\",\r\n \"novembre\": \"Nov.\",\r\n \"jardin\": \"Jard.\",\r\n \"petites\": \"Pet.\",\r\n \"privé\": \"Priv.\",\r\n \"centres\": \"Ctres\",\r\n \"forestier\": \"Forest.\",\r\n \"xiv\": \"14\",\r\n \"africaines\": \"Afric.\",\r\n \"sergent\": \"Sgt\",\r\n \"européenne\": \"Eur.\",\r\n \"privée\": \"Priv.\",\r\n \"café\": \"Cfé\",\r\n \"xix\": \"19\",\r\n \"hautes\": \"Htes\",\r\n \"major\": \"Mjr\",\r\n \"vendredi\": \"ve.\",\r\n \"municipalité\": \"Munic.\",\r\n \"sous-préfecture\": \"Ss-préf.\",\r\n \"spéciales\": \"Spéc.\",\r\n \"secondaires\": \"Second.\",\r\n \"viie\": \"7e\",\r\n \"moyenne\": \"Moy.\",\r\n \"commerciale\": \"Commerc.\",\r\n \"région\": \"Rég.\",\r\n \"américaines\": \"Amér.\",\r\n \"américains\": \"Amér.\",\r\n \"service\": \"Sce\",\r\n \"professeur\": \"Prof.\",\r\n \"départemental\": \"Dépt\",\r\n \"hôtels\": \"Hôt.\",\r\n \"mondiales\": \"Mond.\",\r\n \"ire\": \"1re\",\r\n \"caporal\": \"Capo.\",\r\n \"militaire\": \"Milit.\",\r\n \"lycée d'enseignement professionnel\": \"LEP\",\r\n \"adjudant\": \"Adj.\",\r\n \"médicale\": \"Méd.\",\r\n \"conférences\": \"Confér.\",\r\n \"universelle\": \"Univ.\",\r\n \"xiie\": \"12e\",\r\n \"supérieures\": \"Sup.\",\r\n \"naturel\": \"Natur.\",\r\n \"société nationale\": \"SN\",\r\n \"hospitalier\": \"Hosp.\",\r\n \"culturelle\": \"Cult.\",\r\n \"américain\": \"Amér.\",\r\n \"son altesse royale\": \"S.A.R.\",\r\n \"infirmière\": \"Infirm.\",\r\n \"viii\": \"8\",\r\n \"fondatrice\": \"Fond.\",\r\n \"madame\": \"Mme\",\r\n \"métropolitain\": \"Métrop.\",\r\n \"ophtalmologues\": \"Ophtalmos\",\r\n \"xviie\": \"18e\",\r\n \"viiie\": \"8e\",\r\n \"commerçante\": \"Commerç.\",\r\n \"centre d'enseignement du second degré\": \"CES\",\r\n \"septembre\": \"Sept.\",\r\n \"agriculteur\": \"Agric.\",\r\n \"xiii\": \"13\",\r\n \"pontifical\": \"Pontif.\",\r\n \"cafétéria\": \"Cafét.\",\r\n \"prince\": \"Pce\",\r\n \"vie\": \"6e\",\r\n \"archiduchesse\": \"Archid.\",\r\n \"occidental\": \"Occ.\",\r\n \"spectacles\": \"Spect.\",\r\n \"camping\": \"Camp.\",\r\n \"métro\": \"Mº\",\r\n \"arrondissement\": \"Arrond.\",\r\n \"viticole\": \"Vitic.\",\r\n \"ii\": \"2\",\r\n \"siècle\": \"Si.\",\r\n \"chapelles\": \"Chap.\",\r\n \"centre\": \"Ctre\",\r\n \"sapeur-pompiers\": \"Sap.-pomp.\",\r\n \"établissements\": \"Étabts\",\r\n \"société anonyme\": \"SA\",\r\n \"directeurs\": \"Dir.\",\r\n \"vii\": \"7\",\r\n \"culturel\": \"Cult.\",\r\n \"central\": \"Ctral\",\r\n \"métropolitaine\": \"Métrop.\",\r\n \"administrations\": \"Admin.\",\r\n \"amiraux\": \"Amir.\",\r\n \"sur\": \"s/\",\r\n \"premiers\": \"1ers\",\r\n \"provence-alpes-côte d'azur\": \"PACA\",\r\n \"cathédrale\": \"Cathéd.\",\r\n \"iv\": \"4\",\r\n \"postale\": \"Post.\",\r\n \"social\": \"Soc.\",\r\n \"spécialisé\": \"Spéc.\",\r\n \"district\": \"Distr.\",\r\n \"technologique\": \"Techno.\",\r\n \"viticoles\": \"Vitic.\",\r\n \"ix\": \"9\",\r\n \"protégés\": \"Prot.\",\r\n \"historiques\": \"Hist.\",\r\n \"sous\": \"s/s\",\r\n \"national\": \"Nal\",\r\n \"ambassade\": \"Amb.\",\r\n \"cafés\": \"Cfés\",\r\n \"agronomie\": \"Agro.\",\r\n \"sapeurs\": \"Sap.\",\r\n \"petits\": \"Pet.\",\r\n \"monsieur\": \"M.\",\r\n \"boucher\": \"Bouch.\",\r\n \"restaurant\": \"Restau.\",\r\n \"lycée\": \"Lyc.\",\r\n \"urbaine\": \"Urb.\",\r\n \"préfecture\": \"Préf.\",\r\n \"districts\": \"Distr.\",\r\n \"civil\": \"Civ.\",\r\n \"protégées\": \"Prot.\",\r\n \"sapeur\": \"Sap.\",\r\n \"théâtre\": \"Thé.\",\r\n \"collège\": \"Coll.\",\r\n \"mardi\": \"ma.\",\r\n \"mémorial\": \"Mémor.\",\r\n \"africain\": \"Afric.\",\r\n \"républicaine\": \"Républ.\",\r\n \"sociale\": \"Soc.\",\r\n \"spécial\": \"Spéc.\",\r\n \"technologie\": \"Techno.\",\r\n \"charcuterie\": \"Charc.\",\r\n \"commerces\": \"Commerc.\",\r\n \"fluviale\": \"Flv\",\r\n \"parachutistes\": \"Para.\",\r\n \"primaires\": \"Prim.\",\r\n \"directions\": \"Dir.\",\r\n \"présidentiel\": \"Pdtl\",\r\n \"nationales\": \"Nales\",\r\n \"après\": \"apr.\",\r\n \"samedi\": \"sa.\",\r\n \"unité\": \"U.\",\r\n \"xxiii\": \"23\",\r\n \"associé\": \"Assoc.\",\r\n \"électrique\": \"Électr.\",\r\n \"populaire\": \"Pop.\",\r\n \"asiatique\": \"Asiat.\",\r\n \"navigable\": \"Navig.\",\r\n \"présidente\": \"Pdte\",\r\n \"xive\": \"14e\",\r\n \"associés\": \"Assoc.\",\r\n \"pompiers\": \"Pomp.\",\r\n \"agricoles\": \"Agric.\",\r\n \"élém\": \"Élém.\",\r\n \"décembre\": \"Déc.\",\r\n \"son altesse\": \"S.Alt.\",\r\n \"après-midi\": \"a.-m.\",\r\n \"mineures\": \"Min.\",\r\n \"juillet\": \"Juil.\",\r\n \"aviatrices\": \"Aviat.\",\r\n \"fondation\": \"Fond.\",\r\n \"pontificaux\": \"Pontif.\",\r\n \"temple\": \"Tple\",\r\n \"européennes\": \"Eur.\",\r\n \"régionale\": \"Rég.\",\r\n \"informations\": \"Infos\",\r\n \"mondiaux\": \"Mond.\",\r\n \"infanterie\": \"Infant.\",\r\n \"archéologie\": \"Archéo.\",\r\n \"dans\": \"d/\",\r\n \"hospice\": \"Hosp.\",\r\n \"spectacle\": \"Spect.\",\r\n \"hôtels-restaurants\": \"Hôt.-Rest.\",\r\n \"hôtel-restaurant\": \"Hôt.-Rest.\",\r\n \"hélicoptère\": \"hélico\",\r\n \"xixe\": \"19e\",\r\n \"cliniques\": \"Clin.\",\r\n \"docteur\": \"Dr\",\r\n \"secondaire\": \"Second.\",\r\n \"municipal\": \"Munic.\",\r\n \"générale\": \"Gale\",\r\n \"château\": \"Chât.\",\r\n \"commerçant\": \"Commerç.\",\r\n \"avril\": \"Avr.\",\r\n \"clinique\": \"Clin.\",\r\n \"urbaines\": \"Urb.\",\r\n \"navale\": \"Nav.\",\r\n \"navigation\": \"Navig.\",\r\n \"asiatiques\": \"Asiat.\",\r\n \"pontificales\": \"Pontif.\",\r\n \"administrative\": \"Admin.\",\r\n \"syndicat\": \"Synd.\",\r\n \"lundi\": \"lu.\",\r\n \"petite\": \"Pet.\",\r\n \"maritime\": \"Marit.\",\r\n \"métros\": \"Mº\",\r\n \"enseignement\": \"Enseign.\",\r\n \"fluviales\": \"Flv\",\r\n \"historique\": \"Hist.\",\r\n \"comtés\": \"Ctés\",\r\n \"résidentiel\": \"Résid.\",\r\n \"international\": \"Int.\",\r\n \"supérieure\": \"Sup.\",\r\n \"centre hospitalier universitaire\": \"CHU\",\r\n \"confédération\": \"Conféd.\",\r\n \"boucherie\": \"Bouch.\",\r\n \"fondatrices\": \"Fond.\",\r\n \"médicaux\": \"Méd.\",\r\n \"européens\": \"Eur.\",\r\n \"orientaux\": \"Ori.\",\r\n \"naval\": \"Nav.\",\r\n \"étang\": \"Étg\",\r\n \"provincial\": \"Prov.\",\r\n \"junior\": \"Jr\",\r\n \"départementales\": \"Dépt\",\r\n \"musique\": \"Musiq.\",\r\n \"directrices\": \"Dir.\",\r\n \"maréchal\": \"Mal\",\r\n \"civils\": \"Civ.\",\r\n \"protégé\": \"Prot.\",\r\n \"établissement\": \"Étabt\",\r\n \"trafic\": \"Traf.\",\r\n \"aviateur\": \"Aviat.\",\r\n \"archives\": \"Arch.\",\r\n \"africains\": \"Afric.\",\r\n \"maternelle\": \"Matern.\",\r\n \"industrielle\": \"Ind.\",\r\n \"administratif\": \"Admin.\",\r\n \"oriental\": \"Ori.\",\r\n \"universitaire\": \"Univ.\",\r\n \"majeur\": \"Maj.\",\r\n \"haute\": \"Hte\",\r\n \"communal\": \"Commun.\",\r\n \"petit\": \"Pet.\",\r\n \"commune\": \"Commun.\",\r\n \"exploitant\": \"Exploit.\",\r\n \"conférence\": \"Confér.\",\r\n \"monseigneur\": \"Mgr\",\r\n \"pharmacien\": \"Pharm.\",\r\n \"jeudi\": \"je.\",\r\n \"primaire\": \"Prim.\",\r\n \"hélicoptères\": \"hélicos\",\r\n \"agronomique\": \"Agro.\",\r\n \"médecin\": \"Méd.\",\r\n \"ve\": \"5e\",\r\n \"pontificale\": \"Pontif.\",\r\n \"ier\": \"1er\",\r\n \"cinéma\": \"Ciné\",\r\n \"fluvial\": \"Flv\",\r\n \"occidentaux\": \"Occ.\",\r\n \"commerçants\": \"Commerç.\",\r\n \"banque\": \"Bq\",\r\n \"moyennes\": \"Moy.\",\r\n \"pharmacienne\": \"Pharm.\",\r\n \"démocratique\": \"Dém.\",\r\n \"cinémas\": \"Cinés\",\r\n \"spéciale\": \"Spéc.\",\r\n \"présidents\": \"Pdts\",\r\n \"directrice\": \"Dir.\",\r\n \"vi\": \"6\",\r\n \"basse\": \"Bas.\",\r\n \"xve\": \"15e\",\r\n \"état\": \"É.\",\r\n \"aviateurs\": \"Aviat.\",\r\n \"majeurs\": \"Maj.\",\r\n \"infirmiers\": \"Infirm.\",\r\n \"église\": \"Égl.\",\r\n \"confédérale\": \"Conféd.\",\r\n \"xxie\": \"21e\",\r\n \"comte\": \"Cte\",\r\n \"européen\": \"Eur.\",\r\n \"union\": \"U.\",\r\n \"pharmacie\": \"Pharm.\",\r\n \"infirmières\": \"Infirm.\",\r\n \"comté\": \"Cté\",\r\n \"sportive\": \"Sport.\",\r\n \"deuxième\": \"2e\",\r\n \"xvi\": \"17\",\r\n \"haut\": \"Ht\",\r\n \"médicales\": \"Méd.\",\r\n \"développé\": \"Dévelop.\",\r\n \"bâtiment\": \"Bât.\",\r\n \"commerce\": \"Commerc.\",\r\n \"ive\": \"4e\",\r\n \"associatif\": \"Assoc.\",\r\n \"rural\": \"Rur.\",\r\n \"cimetière\": \"Cim.\",\r\n \"régional\": \"Rég.\",\r\n \"ferroviaire\": \"Ferr.\",\r\n \"vers\": \"v/\",\r\n \"mosquée\": \"Mosq.\",\r\n \"mineurs\": \"Min.\",\r\n \"nautique\": \"Naut.\",\r\n \"châteaux\": \"Chât.\",\r\n \"sportif\": \"Sport.\",\r\n \"mademoiselle\": \"Mle\",\r\n \"école\": \"Éc.\",\r\n \"doyen\": \"Doy.\",\r\n \"industriel\": \"Ind.\",\r\n \"chapelle\": \"Chap.\",\r\n \"sociétés\": \"Stés\",\r\n \"internationale\": \"Int.\",\r\n \"coopératif\": \"Coop.\",\r\n \"hospices\": \"Hosp.\",\r\n \"xxii\": \"22\",\r\n \"parachutiste\": \"Para.\",\r\n \"alpines\": \"Alp.\",\r\n \"civile\": \"Civ.\",\r\n \"xvie\": \"17e\",\r\n \"états\": \"É.\",\r\n \"musée\": \"Msée\",\r\n \"centrales\": \"Ctrales\",\r\n \"globaux\": \"Glob.\",\r\n \"supérieurs\": \"Sup.\",\r\n \"syndicats\": \"Synd.\",\r\n \"archevêque\": \"Archev.\",\r\n \"docteurs\": \"Drs\",\r\n \"bibliothèque\": \"Biblio.\",\r\n \"lieutenant\": \"Lieut.\",\r\n \"république\": \"Rép.\",\r\n \"vétérinaire\": \"Vét.\",\r\n \"départementaux\": \"Dépt\",\r\n \"premier\": \"1er\",\r\n \"fluviaux\": \"Flv\",\r\n \"animé\": \"Anim.\",\r\n \"orientales\": \"Ori.\",\r\n \"technologiques\": \"Techno.\",\r\n \"princesse\": \"Pse\",\r\n \"routière\": \"Rout.\",\r\n \"coopérative\": \"Coop.\",\r\n \"scolaire\": \"Scol.\",\r\n \"écoles\": \"Éc.\",\r\n \"football\": \"Foot\",\r\n \"territoriale\": \"Territ.\",\r\n \"commercial\": \"Commerc.\",\r\n \"mineur\": \"Min.\",\r\n \"millénaires\": \"Mill.\",\r\n \"association\": \"Assoc.\",\r\n \"catholique\": \"Cathol.\",\r\n \"administration\": \"Admin.\",\r\n \"mairie\": \"Mair.\",\r\n \"portuaire\": \"Port.\",\r\n \"tertiaires\": \"Terti.\",\r\n \"théâtrale\": \"Thé.\",\r\n \"palais\": \"Pal.\",\r\n \"troisième\": \"3e\",\r\n \"directeur\": \"Dir.\",\r\n \"vétérinaires\": \"Vét.\",\r\n \"faculté\": \"Fac.\",\r\n \"occidentales\": \"Occ.\",\r\n \"viticulteurs\": \"Vitic.\",\r\n \"xvii\": \"18\",\r\n \"occidentale\": \"Occ.\",\r\n \"amiral\": \"Amir.\",\r\n \"professionnel\": \"Profess.\",\r\n \"administratives\": \"Admin.\",\r\n \"commerciales\": \"Commerc.\",\r\n \"saints\": \"Sts\",\r\n \"agronomes\": \"Agro.\",\r\n \"stade\": \"Std\",\r\n \"sous-préfet\": \"Ss-préf.\",\r\n \"senior\": \"Sr\",\r\n \"agronome\": \"Agro.\",\r\n \"terrain\": \"Terr.\",\r\n \"catholiques\": \"Cathol.\",\r\n \"résidentielle\": \"Résid.\",\r\n \"grands\": \"Gds\",\r\n \"exploitants\": \"Exploit.\",\r\n \"xiiie\": \"13e\",\r\n \"croix\": \"Cx\",\r\n \"généraux\": \"Gaux\",\r\n \"crédit\": \"Créd.\",\r\n \"cimetières\": \"Cim.\",\r\n \"antenne\": \"Ant.\",\r\n \"médical\": \"Méd.\",\r\n \"collèges\": \"Coll.\",\r\n \"musicien\": \"Music.\",\r\n \"apostolique\": \"Apost.\",\r\n \"postal\": \"Post.\",\r\n \"territorial\": \"Territ.\",\r\n \"urbanisme\": \"Urb.\",\r\n \"préfectorale\": \"Préf.\",\r\n \"fondateurs\": \"Fond.\",\r\n \"information\": \"Info.\",\r\n \"églises\": \"Égl.\",\r\n \"ophtalmologue\": \"Ophtalmo\",\r\n \"congrégation\": \"Congrég.\",\r\n \"charcutier\": \"Charc.\",\r\n \"étage\": \"ét.\",\r\n \"consulat\": \"Consul.\",\r\n \"public\": \"Publ.\",\r\n \"ferrée\": \"Ferr.\",\r\n \"matin\": \"mat.\",\r\n \"société anonyme à responsabilité limitée\": \"SARL\",\r\n \"monuments\": \"Mmts\",\r\n \"protection\": \"Prot.\",\r\n \"universel\": \"Univ.\",\r\n \"nationale\": \"Nale\",\r\n \"président\": \"Pdt\",\r\n \"provinciale\": \"Prov.\",\r\n \"agriculteurs\": \"Agric.\",\r\n \"préfectoral\": \"Préf.\",\r\n \"xxe\": \"20e\",\r\n \"alpins\": \"Alp.\",\r\n \"avant\": \"av.\",\r\n \"infirmerie\": \"Infirm.\",\r\n \"deux mil\": \"2000\",\r\n \"rurale\": \"Rur.\",\r\n \"administratifs\": \"Admin.\",\r\n \"octobre\": \"Oct.\",\r\n \"archipel\": \"Archip.\",\r\n \"communauté\": \"Commté\",\r\n \"globales\": \"Glob.\",\r\n \"alpin\": \"Alp.\",\r\n \"numéros\": \"Nºˢ\",\r\n \"lieutenant-colonel\": \"Lieut.-Col.\",\r\n \"jésus-christ\": \"J.-C.\",\r\n \"agricole\": \"Agric.\",\r\n \"sa majesté\": \"S.Maj.\",\r\n \"associative\": \"Assoc.\",\r\n \"xxi\": \"21\",\r\n \"présidentielle\": \"Pdtle\",\r\n \"moyen\": \"Moy.\",\r\n \"fédéral\": \"Féd.\",\r\n \"professionnelle\": \"Profess.\",\r\n \"tertiaire\": \"Terti.\",\r\n \"ixe\": \"9e\",\r\n \"hôpital\": \"Hôp.\",\r\n \"technologies\": \"Techno.\",\r\n \"iiie\": \"3e\",\r\n \"développement\": \"Dévelop.\",\r\n \"monument\": \"Mmt\",\r\n \"forestière\": \"Forest.\",\r\n \"numéro\": \"Nº\",\r\n \"viticulture\": \"Vitic.\",\r\n \"traversière\": \"Traver.\",\r\n \"technique\": \"Tech.\",\r\n \"électriques\": \"Électr.\",\r\n \"militaires\": \"Milit.\",\r\n \"pompier\": \"Pomp.\",\r\n \"américaine\": \"Amér.\",\r\n \"préfet\": \"Préf.\",\r\n \"congrégations\": \"Congrég.\",\r\n \"pâtissier\": \"Pâtiss.\",\r\n \"mondial\": \"Mond.\",\r\n \"ophtalmologie\": \"Ophtalm.\",\r\n \"sainte\": \"Ste\",\r\n \"africaine\": \"Afric.\",\r\n \"aviatrice\": \"Aviat.\",\r\n \"doyens\": \"Doy.\",\r\n \"société\": \"Sté\",\r\n \"majeures\": \"Maj.\",\r\n \"orientale\": \"Ori.\",\r\n \"ministère\": \"Min.\",\r\n \"archiduc\": \"Archid.\",\r\n \"territoire\": \"Territ.\",\r\n \"techniques\": \"Tech.\",\r\n \"île-de-france\": \"IDF\",\r\n \"globale\": \"Glob.\",\r\n \"xe\": \"10e\",\r\n \"xie\": \"11e\",\r\n \"majeure\": \"Maj.\",\r\n \"commerciaux\": \"Commerc.\",\r\n \"maire\": \"Mair.\",\r\n \"spéciaux\": \"Spéc.\",\r\n \"grande\": \"Gde\",\r\n \"messieurs\": \"MM\",\r\n \"colonel\": \"Col.\",\r\n \"millénaire\": \"Mill.\",\r\n \"xi\": \"11\",\r\n \"urbain\": \"Urb.\",\r\n \"fédérale\": \"Féd.\",\r\n \"ferré\": \"Ferr.\",\r\n \"rivière\": \"Riv.\",\r\n \"républicain\": \"Républ.\",\r\n \"grandes\": \"Gdes\",\r\n \"régiment\": \"Régim.\",\r\n \"hauts\": \"Hts\",\r\n \"catégorie\": \"Catég.\",\r\n \"basses\": \"Bas.\",\r\n \"xii\": \"12\",\r\n \"agronomiques\": \"Agro.\",\r\n \"iie\": \"2e\",\r\n \"protégée\": \"Prot.\",\r\n \"sapeur-pompier\": \"Sap.-pomp.\"\r\n },\r\n \"directions\": {\r\n \"est-nord-est\": \"ENE\",\r\n \"nord-est\": \"NE\",\r\n \"ouest\": \"O\",\r\n \"sud-est\": \"SE\",\r\n \"est-sud-est\": \"ESE\",\r\n \"nord-nord-est\": \"NNE\",\r\n \"sud\": \"S\",\r\n \"nord-nord-ouest\": \"NNO\",\r\n \"nord-ouest\": \"NO\",\r\n \"nord\": \"N\",\r\n \"ouest-sud-ouest\": \"OSO\",\r\n \"ouest-nord-ouest\": \"ONO\",\r\n \"sud-ouest\": \"SO\",\r\n \"sud-sud-est\": \"SSE\",\r\n \"sud-sud-ouest\": \"SSO\",\r\n \"est\": \"E\"\r\n }\r\n}\r\n\r\n }, {}],\r\n 16: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"abbreviations\": {\r\n \"апостола\": \"ап.\",\r\n \"апостолов\": \"апп.\",\r\n \"великомученика\": \"вмч\",\r\n \"великомученицы\": \"вмц.\",\r\n \"владение\": \"вл.\",\r\n \"город\": \"г.\",\r\n \"деревня\": \"д.\",\r\n \"имени\": \"им.\",\r\n \"мученика\":\"мч.\",\r\n \"мучеников\": \"мчч.\",\r\n \"мучениц\": \"мцц.\",\r\n \"мученицы\": \"мц.\",\r\n \"озеро\": \"о.\",\r\n \"посёлок\": \"п.\",\r\n \"преподобного\": \"прп.\",\r\n \"преподобных\": \"прпп.\",\r\n \"река\": \"р.\",\r\n \"святителей\": \"свтт.\",\r\n \"святителя\": \"свт.\",\r\n \"священномученика\": \"сщмч.\",\r\n \"священномучеников\": \"сщмчч.\",\r\n \"станция\": \"ст.\",\r\n \"участок\": \"уч.\"\r\n },\r\n \"classifications\": {\r\n \"проезд\": \"пр-д\",\r\n \"проспект\": \"пр.\",\r\n \"переулок\": \"пер.\",\r\n \"набережная\": \"наб.\",\r\n \"площадь\": \"пл.\",\r\n \"шоссе\": \"ш.\",\r\n \"бульвар\": \"б.\",\r\n \"тупик\": \"туп.\",\r\n \"улица\": \"ул.\"\r\n },\r\n \"directions\": {\r\n \"восток\": \"В\",\r\n \"северо-восток\": \"СВ\",\r\n \"юго-восток\": \"ЮВ\",\r\n \"юго-запад\": \"ЮЗ\",\r\n \"северо-запад\": \"СЗ\",\r\n \"север\": \"С\",\r\n \"запад\": \"З\",\r\n \"юг\": \"Ю\"\r\n }\r\n}\r\n\r\n }, {}],\r\n 20: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"abbreviations\": {\r\n \"viện bảo tàng\": \"VBT\",\r\n \"thị trấn\": \"Tt\",\r\n \"đại học\": \"ĐH\",\r\n \"căn cứ không quan\": \"CCKQ\",\r\n \"câu lạc bộ\": \"CLB\",\r\n \"bưu điện\": \"BĐ\",\r\n \"khách sạn\": \"KS\",\r\n \"khu du lịch\": \"KDL\",\r\n \"khu công nghiệp\": \"KCN\",\r\n \"khu nghỉ mát\": \"KNM\",\r\n \"thị xã\": \"Tx\",\r\n \"khu chung cư\": \"KCC\",\r\n \"phi trường\": \"PT\",\r\n \"trung tâm\": \"TT\",\r\n \"tổng công ty\": \"TCty\",\r\n \"trung học cơ sở\": \"THCS\",\r\n \"sân bay quốc tế\": \"SBQT\",\r\n \"trung học phổ thông\": \"THPT\",\r\n \"cao đẳng\": \"CĐ\",\r\n \"công ty\": \"Cty\",\r\n \"sân bay\": \"SB\",\r\n \"thành phố\": \"Tp\",\r\n \"công viên\": \"CV\",\r\n \"sân vận động\": \"SVĐ\",\r\n \"linh mục\": \"LM\",\r\n \"vườn quốc gia\": \"VQG\"\r\n },\r\n \"classifications\": {\r\n \"huyện lộ\": \"HL\",\r\n \"đường tỉnh\": \"ĐT\",\r\n \"quốc lộ\": \"QL\",\r\n \"xa lộ\": \"XL\",\r\n \"hương lộ\": \"HL\",\r\n \"tỉnh lộ\": \"TL\",\r\n \"đường huyện\": \"ĐH\",\r\n \"đường cao tốc\": \"ĐCT\",\r\n \"đại lộ\": \"ĐL\",\r\n \"việt nam\": \"VN\",\r\n \"quảng trường\": \"QT\",\r\n \"đường bộ\": \"ĐB\"\r\n },\r\n \"directions\": {\r\n \"tây\": \"T\",\r\n \"nam\": \"N\",\r\n \"đông nam\": \"ĐN\",\r\n \"đông bắc\": \"ĐB\",\r\n \"tây nam\": \"TN\",\r\n \"đông\": \"Đ\",\r\n \"bắc\": \"B\"\r\n }\r\n}\r\n\r\n }, {}],\r\n 22: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"meta\": {\r\n \"regExpFlags\": \"gi\"\r\n },\r\n \"v5\": {\r\n \"article\": [\r\n [\"^ Acc[èe]s \", \" l’accès \"],\r\n [\"^ Aire \", \" l’aire \"],\r\n [\"^ All[ée]e \", \" l’allée \"],\r\n [\"^ Anse \", \" l’anse \"],\r\n [\"^ (L['’])?Autoroute \", \" l’autoroute \"],\r\n [\"^ Avenue \", \" l’avenue \"],\r\n [\"^ Barreau \", \" le barreau \"],\r\n [\"^ Boulevard \", \" le boulevard \"],\r\n [\"^ Chemin \", \" le chemin \"],\r\n [\"^ Petit[\\\\- ]Chemin \", \" le petit chemin \"],\r\n [\"^ Cit[ée] \", \" la cité \"],\r\n [\"^ Chauss[ée]e \", \" la chaussée \"],\r\n [\"^ Clos \", \" le clos \"],\r\n [\"^ Corniche \", \" la corniche \"],\r\n [\"^ Cour \", \" la cour \"],\r\n [\"^ Cours \", \" le cours \"],\r\n [\"^ D[ée]viation \", \" la déviation \"],\r\n [\"^ Entr[ée]e \", \" l’entrée \"],\r\n [\"^ Esplanade \", \" l’esplanade \"],\r\n [\"^ Galerie \", \" la galerie \"],\r\n [\"^ Impasse \", \" l’impasse \"],\r\n [\"^ Lotissement \", \" le lotissement \"],\r\n [\"^ Mont[ée]e \", \" la montée \"],\r\n [\"^ Parc \", \" le parc \"],\r\n [\"^ Parvis \", \" le parvis \"],\r\n [\"^ Passage \", \" le passage \"],\r\n [\"^ Place \", \" la place \"],\r\n [\"^ Petit[\\\\- ]Pont \", \" le petit-pont \"],\r\n [\"^ Pont \", \" le pont \"],\r\n [\"^ Promenade \", \" la promenade \"],\r\n [\"^ Quai \", \" le quai \"],\r\n [\"^ Rocade \", \" la rocade \"],\r\n [\"^ Rond[\\\\- ]?Point \", \" le rond-point \"],\r\n [\"^ Route \", \" la route \"],\r\n [\"^ Ancienne Route \", \" la ancienne route \"],\r\n [\"^ Rue \", \" la rue \"],\r\n [\"^ Grande Rue \", \" la grande rue \"],\r\n [\"^ Sente \", \" la sente \"],\r\n [\"^ Grande Sente \", \" la grande sente \"],\r\n [\"^ Sentier \", \" le sentier \"],\r\n [\"^ Sortie \", \" la sortie \"],\r\n [\"^ Souterrain \", \" le souterrain \"],\r\n [\"^ Square \", \" le square \"],\r\n [\"^ Terrasse \", \" la terrasse \"],\r\n [\"^ Traverse \", \" la traverse \"],\r\n [\"^ Tunnel \", \" le tunnel \"],\r\n [\"^ Viaduc \", \" le viaduc \"],\r\n [\"^ Villa \", \" la villa \"],\r\n [\"^ Village \", \" le village \"],\r\n [\"^ Voie \", \" la voie \"],\r\n\r\n [\" ([dl])'\", \" $1’\"]\r\n ],\r\n \"preposition\": [\r\n [\"^ Le \", \" du \"],\r\n [\"^ Les \", \" des \"],\r\n [\"^ La \", \" de La \"],\r\n\r\n [\"^ Acc[èe]s \", \" de l’accès \"],\r\n [\"^ Aire \", \" de l’aire \"],\r\n [\"^ All[ée]e \", \" de l’allée \"],\r\n [\"^ Anse \", \" de l’anse \"],\r\n [\"^ (L['’])?Autoroute \", \" de l’autoroute \"],\r\n [\"^ Avenue \", \" de l’avenue \"],\r\n [\"^ Barreau \", \" du barreau \"],\r\n [\"^ Boulevard \", \" du boulevard \"],\r\n [\"^ Chemin \", \" du chemin \"],\r\n [\"^ Petit[\\\\- ]Chemin \", \" du petit chemin \"],\r\n [\"^ Cit[ée] \", \" de la cité \"],\r\n [\"^ Chauss[ée]e \", \" de la chaussée \"],\r\n [\"^ Clos \", \" du clos \"],\r\n [\"^ Corniche \", \" de la corniche \"],\r\n [\"^ Cour \", \" de la cour \"],\r\n [\"^ Cours \", \" du cours \"],\r\n [\"^ D[ée]viation \", \" de la déviation \"],\r\n [\"^ Entr[ée]e \", \" de l’entrée \"],\r\n [\"^ Esplanade \", \" de l’esplanade \"],\r\n [\"^ Galerie \", \" de la galerie \"],\r\n [\"^ Impasse \", \" de l’impasse \"],\r\n [\"^ Lotissement \", \" du lotissement \"],\r\n [\"^ Mont[ée]e \", \" de la montée \"],\r\n [\"^ Parc \", \" du parc \"],\r\n [\"^ Parvis \", \" du parvis \"],\r\n [\"^ Passage \", \" du passage \"],\r\n [\"^ Place \", \" de la place \"],\r\n [\"^ Petit[\\\\- ]Pont \", \" du petit-pont \"],\r\n [\"^ Pont \", \" du pont \"],\r\n [\"^ Promenade \", \" de la promenade \"],\r\n [\"^ Quai \", \" du quai \"],\r\n [\"^ Rocade \", \" de la rocade \"],\r\n [\"^ Rond[\\\\- ]?Point \", \" du rond-point \"],\r\n [\"^ Route \", \" de la route \"],\r\n [\"^ Ancienne Route \", \" de la ancienne route \"],\r\n [\"^ Rue \", \" de la rue \"],\r\n [\"^ Grande Rue \", \" de la grande rue \"],\r\n [\"^ Sente \", \" de la sente \"],\r\n [\"^ Grande Sente \", \" de la grande sente \"],\r\n [\"^ Sentier \", \" du sentier \"],\r\n [\"^ Sortie \", \" de la sortie \"],\r\n [\"^ Souterrain \", \" du souterrain \"],\r\n [\"^ Square \", \" du square \"],\r\n [\"^ Terrasse \", \" de la terrasse \"],\r\n [\"^ Traverse \", \" de la traverse \"],\r\n [\"^ Tunnel \", \" du tunnel \"],\r\n [\"^ Viaduc \", \" du viaduc \"],\r\n [\"^ Villa \", \" de la villa \"],\r\n [\"^ Village \", \" du village \"],\r\n [\"^ Voie \", \" de la voie \"],\r\n\r\n [\"^ ([AÂÀEÈÉÊËIÎÏOÔUÙÛÜYŸÆŒ])\", \" d’$1\"],\r\n [\"^ (\\\\S)\", \" de $1\"],\r\n [\" ([dl])'\", \" $1’\"]\r\n ],\r\n \"rotary\": [\r\n [\"^ Le \", \" le rond-point du \"],\r\n [\"^ Les \", \" le rond-point des \"],\r\n [\"^ La \", \" le rond-point de La \"],\r\n\r\n [\"^ Acc[èe]s \", \" le rond-point de l’accès \"],\r\n [\"^ Aire \", \" le rond-point de l’aire \"],\r\n [\"^ All[ée]e \", \" le rond-point de l’allée \"],\r\n [\"^ Anse \", \" le rond-point de l’anse \"],\r\n [\"^ (L['’])?Autoroute \", \" le rond-point de l’autoroute \"],\r\n [\"^ Avenue \", \" le rond-point de l’avenue \"],\r\n [\"^ Barreau \", \" le rond-point du barreau \"],\r\n [\"^ Boulevard \", \" le rond-point du boulevard \"],\r\n [\"^ Chemin \", \" le rond-point du chemin \"],\r\n [\"^ Petit[\\\\- ]Chemin \", \" le rond-point du petit chemin \"],\r\n [\"^ Cit[ée] \", \" le rond-point de la cité \"],\r\n [\"^ Chauss[ée]e \", \" le rond-point de la chaussée \"],\r\n [\"^ Clos \", \" le rond-point du clos \"],\r\n [\"^ Corniche \", \" le rond-point de la corniche \"],\r\n [\"^ Cour \", \" le rond-point de la cour \"],\r\n [\"^ Cours \", \" le rond-point du cours \"],\r\n [\"^ D[ée]viation \", \" le rond-point de la déviation \"],\r\n [\"^ Entr[ée]e \", \" le rond-point de l’entrée \"],\r\n [\"^ Esplanade \", \" le rond-point de l’esplanade \"],\r\n [\"^ Galerie \", \" le rond-point de la galerie \"],\r\n [\"^ Impasse \", \" le rond-point de l’impasse \"],\r\n [\"^ Lotissement \", \" le rond-point du lotissement \"],\r\n [\"^ Mont[ée]e \", \" le rond-point de la montée \"],\r\n [\"^ Parc \", \" le rond-point du parc \"],\r\n [\"^ Parvis \", \" le rond-point du parvis \"],\r\n [\"^ Passage \", \" le rond-point du passage \"],\r\n [\"^ Place \", \" le rond-point de la place \"],\r\n [\"^ Petit[\\\\- ]Pont \", \" le rond-point du petit-pont \"],\r\n [\"^ Pont \", \" le rond-point du pont \"],\r\n [\"^ Promenade \", \" le rond-point de la promenade \"],\r\n [\"^ Quai \", \" le rond-point du quai \"],\r\n [\"^ Rocade \", \" le rond-point de la rocade \"],\r\n [\"^ Rond[\\\\- ]?Point \", \" le rond-point \"],\r\n [\"^ Route \", \" le rond-point de la route \"],\r\n [\"^ Ancienne Route \", \" le rond-point de la ancienne route \"],\r\n [\"^ Rue \", \" le rond-point de la rue \"],\r\n [\"^ Grande Rue \", \" le rond-point de la grande rue \"],\r\n [\"^ Sente \", \" le rond-point de la sente \"],\r\n [\"^ Grande Sente \", \" le rond-point de la grande sente \"],\r\n [\"^ Sentier \", \" le rond-point du sentier \"],\r\n [\"^ Sortie \", \" le rond-point de la sortie \"],\r\n [\"^ Souterrain \", \" le rond-point du souterrain \"],\r\n [\"^ Square \", \" le rond-point du square \"],\r\n [\"^ Terrasse \", \" le rond-point de la terrasse \"],\r\n [\"^ Traverse \", \" le rond-point de la traverse \"],\r\n [\"^ Tunnel \", \" le rond-point du tunnel \"],\r\n [\"^ Viaduc \", \" le rond-point du viaduc \"],\r\n [\"^ Villa \", \" le rond-point de la villa \"],\r\n [\"^ Village \", \" le rond-point du village \"],\r\n [\"^ Voie \", \" le rond-point de la voie \"],\r\n\r\n [\"^ ([AÂÀEÈÉÊËIÎÏOÔUÙÛÜYŸÆŒ])\", \" le rond-point d’$1\"],\r\n [\"^ (\\\\S)\", \" le rond-point de $1\"],\r\n [\" ([dl])'\", \" $1’\"]\r\n ],\r\n \"arrival\": [\r\n [\"^ Le \", \" au \"],\r\n [\"^ Les \", \" aux \"],\r\n [\"^ La \", \" à La \"],\r\n [\"^ (\\\\S)\", \" à $1\"],\r\n\r\n [\" ([dl])'\", \" $1’\"]\r\n ]\r\n }\r\n}\r\n\r\n }, {}],\r\n 24: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"meta\": {\r\n \"regExpFlags\": \"\"\r\n },\r\n \"v5\": {\r\n \"accusative\": [\r\n [\"^ ([«\\\"])\", \" трасса $1\"],\r\n\r\n [\"^ (\\\\S+)ая [Аа]ллея \", \" $1ую аллею \"],\r\n [\"^ (\\\\S+)ья [Аа]ллея \", \" $1ью аллею \"],\r\n [\"^ (\\\\S+)яя [Аа]ллея \", \" $1юю аллею \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Аа]ллея \", \" $1-ю $2ую аллею \"],\r\n [\"^ [Аа]ллея \", \" аллею \"],\r\n\r\n [\"^ (\\\\S+)ая-(\\\\S+)ая [Уу]лица \", \" $1ую-$2ую улицу \"],\r\n [\"^ (\\\\S+)ая [Уу]лица \", \" $1ую улицу \"],\r\n [\"^ (\\\\S+)ья [Уу]лица \", \" $1ью улицу \"],\r\n [\"^ (\\\\S+)яя [Уу]лица \", \" $1юю улицу \"],\r\n [\"^ (\\\\d+)-я [Уу]лица \", \" $1-ю улицу \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Уу]лица \", \" $1-ю $2ую улицу \"],\r\n [\"^ (\\\\S+)ая (\\\\S+)ая [Уу]лица \", \" $1ую $2ую улицу \"],\r\n [\"^ (\\\\S+[вн])а [Уу]лица \", \" $1у улицу \"],\r\n [\"^ (\\\\S+)ая (\\\\S+[вн])а [Уу]лица \", \" $1ую $2у улицу \"],\r\n [\"^ Даньславля [Уу]лица \", \" Даньславлю улицу \"],\r\n [\"^ Добрыня [Уу]лица \", \" Добрыню улицу \"],\r\n [\"^ Людогоща [Уу]лица \", \" Людогощу улицу \"],\r\n [\"^ [Уу]лица \", \" улицу \"],\r\n\r\n [\"^ (\\\\d+)-я [Лл]иния \", \" $1-ю линию \"],\r\n [\"^ (\\\\d+)-(\\\\d+)-я [Лл]иния \", \" $1-$2-ю линию \"],\r\n [\"^ (\\\\S+)ая [Лл]иния \", \" $1ую линию \"],\r\n [\"^ (\\\\S+)ья [Лл]иния \", \" $1ью линию \"],\r\n [\"^ (\\\\S+)яя [Лл]иния \", \" $1юю линию \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Лл]иния \", \" $1-ю $2ую линию \"],\r\n [\"^ [Лл]иния \", \" линию \"],\r\n\r\n [\"^ (\\\\d+)-(\\\\d+)-я [Лл]инии \", \" $1-$2-ю линии \"],\r\n\r\n [\"^ (\\\\S+)ая [Нн]абережная \", \" $1ую набережную \"],\r\n [\"^ (\\\\S+)ья [Нн]абережная \", \" $1ью набережную \"],\r\n [\"^ (\\\\S+)яя [Нн]абережная \", \" $1юю набережную \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Нн]абережная \", \" $1-ю $2ую набережную \"],\r\n [\"^ [Нн]абережная \", \" набережную \"],\r\n\r\n [\"^ (\\\\S+)ая [Пп]лощадь \", \" $1ую площадь \"],\r\n [\"^ (\\\\S+)ья [Пп]лощадь \", \" $1ью площадь \"],\r\n [\"^ (\\\\S+)яя [Пп]лощадь \", \" $1юю площадь \"],\r\n [\"^ (\\\\S+[вн])а [Пп]лощадь \", \" $1у площадь \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Пп]лощадь \", \" $1-ю $2ую площадь \"],\r\n [\"^ [Пп]лощадь \", \" площадь \"],\r\n\r\n [\"^ (\\\\S+)ая [Пп]росека \", \" $1ую просеку \"],\r\n [\"^ (\\\\S+)ья [Пп]росека \", \" $1ью просеку \"],\r\n [\"^ (\\\\S+)яя [Пп]росека \", \" $1юю просеку \"],\r\n [\"^ (\\\\d+)-я [Пп]росека \", \" $1-ю просеку \"],\r\n [\"^ [Пп]росека \", \" просеку \"],\r\n\r\n [\"^ (\\\\S+)ая [Ээ]стакада \", \" $1ую эстакаду \"],\r\n [\"^ (\\\\S+)ья [Ээ]стакада \", \" $1ью эстакаду \"],\r\n [\"^ (\\\\S+)яя [Ээ]стакада \", \" $1юю эстакаду \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Ээ]стакада \", \" $1-ю $2ую эстакаду \"],\r\n [\"^ [Ээ]стакада \", \" эстакаду \"],\r\n\r\n [\"^ (\\\\S+)ая [Мм]агистраль \", \" $1ую магистраль \"],\r\n [\"^ (\\\\S+)ья [Мм]агистраль \", \" $1ью магистраль \"],\r\n [\"^ (\\\\S+)яя [Мм]агистраль \", \" $1юю магистраль \"],\r\n [\"^ (\\\\S+)ая (\\\\S+)ая [Мм]агистраль \", \" $1ую $2ую магистраль \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Мм]агистраль \", \" $1-ю $2ую магистраль \"],\r\n [\"^ [Мм]агистраль \", \" магистраль \"],\r\n\r\n [\"^ (\\\\S+)ая [Рр]азвязка \", \" $1ую развязку \"],\r\n [\"^ (\\\\S+)ья [Рр]азвязка \", \" $1ью развязку \"],\r\n [\"^ (\\\\S+)яя [Рр]азвязка \", \" $1юю развязку \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Рр]азвязка \", \" $1-ю $2ую развязку \"],\r\n [\"^ [Рр]азвязка \", \" развязку \"],\r\n\r\n [\"^ (\\\\S+)ая [Тт]расса \", \" $1ую трассу \"],\r\n [\"^ (\\\\S+)ья [Тт]расса \", \" $1ью трассу \"],\r\n [\"^ (\\\\S+)яя [Тт]расса \", \" $1юю трассу \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Тт]расса \", \" $1-ю $2ую трассу \"],\r\n [\"^ [Тт]расса \", \" трассу \"],\r\n\r\n [\"^ (\\\\S+)ая ([Аа]вто)?[Дд]орога \", \" $1ую $2дорогу \"],\r\n [\"^ (\\\\S+)ья ([Аа]вто)?[Дд]орога \", \" $1ью $2дорогу \"],\r\n [\"^ (\\\\S+)яя ([Аа]вто)?[Дд]орога \", \" $1юю $2дорогу \"],\r\n [\"^ (\\\\S+)ая (\\\\S+)ая ([Аа]вто)?[Дд]орога \", \" $1ую $2ую $3дорогу \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая ([Аа]вто)?[Дд]орога \", \" $1-ю $2ую $3дорогу \"],\r\n [\"^ ([Аа]вто)?[Дд]орога \", \" $1дорогу \"],\r\n\r\n [\"^ (\\\\S+)ая [Дд]орожка \", \" $1ую дорожку \"],\r\n [\"^ (\\\\S+)ья [Дд]орожка \", \" $1ью дорожку \"],\r\n [\"^ (\\\\S+)яя [Дд]орожка \", \" $1юю дорожку \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Дд]орожка \", \" $1-ю $2ую дорожку \"],\r\n [\"^ [Дд]орожка \", \" дорожку \"],\r\n\r\n [\"^ (\\\\S+)ая [Кк]оса \", \" $1ую косу \"],\r\n [\"^ (\\\\S+)ая [Хх]орда \", \" $1ую хорду \"],\r\n\r\n [\"^ [Дд]убл[её]р \", \" дублёр \"]\r\n ],\r\n \"dative\": [\r\n [\"^ ([«\\\"])\", \" трасса $1\"],\r\n\r\n [\"^ (\\\\S+)ая [Аа]ллея \", \" $1ой аллее \"],\r\n [\"^ (\\\\S+)ья [Аа]ллея \", \" $1ьей аллее \"],\r\n [\"^ (\\\\S+)яя [Аа]ллея \", \" $1ей аллее \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Аа]ллея \", \" $1-й $2ой аллее \"],\r\n [\"^ [Аа]ллея \", \" аллее \"],\r\n\r\n [\"^ (\\\\S+)ая-(\\\\S+)ая [Уу]лица \", \" $1ой-$2ой улице \"],\r\n [\"^ (\\\\S+)ая [Уу]лица \", \" $1ой улице \"],\r\n [\"^ (\\\\S+)ья [Уу]лица \", \" $1ьей улице \"],\r\n [\"^ (\\\\S+)яя [Уу]лица \", \" $1ей улице \"],\r\n [\"^ (\\\\d+)-я [Уу]лица \", \" $1-й улице \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Уу]лица \", \" $1-й $2ой улице \"],\r\n [\"^ (\\\\S+)ая (\\\\S+)ая [Уу]лица \", \" $1ой $2ой улице \"],\r\n [\"^ (\\\\S+[вн])а [Уу]лица \", \" $1ой улице \"],\r\n [\"^ (\\\\S+)ая (\\\\S+[вн])а [Уу]лица \", \" $1ой $2ой улице \"],\r\n [\"^ Даньславля [Уу]лица \", \" Даньславлей улице \"],\r\n [\"^ Добрыня [Уу]лица \", \" Добрыней улице \"],\r\n [\"^ Людогоща [Уу]лица \", \" Людогощей улице \"],\r\n [\"^ [Уу]лица \", \" улице \"],\r\n\r\n [\"^ (\\\\d+)-я [Лл]иния \", \" $1-й линии \"],\r\n [\"^ (\\\\d+)-(\\\\d+)-я [Лл]иния \", \" $1-$2-й линии \"],\r\n [\"^ (\\\\S+)ая [Лл]иния \", \" $1ой линии \"],\r\n [\"^ (\\\\S+)ья [Лл]иния \", \" $1ьей линии \"],\r\n [\"^ (\\\\S+)яя [Лл]иния \", \" $1ей линии \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Лл]иния \", \" $1-й $2ой линии \"],\r\n [\"^ [Лл]иния \", \" линии \"],\r\n\r\n [\"^ (\\\\d+)-(\\\\d+)-я [Лл]инии \", \" $1-$2-й линиям \"],\r\n\r\n [\"^ (\\\\S+)ая [Нн]абережная \", \" $1ой набережной \"],\r\n [\"^ (\\\\S+)ья [Нн]абережная \", \" $1ьей набережной \"],\r\n [\"^ (\\\\S+)яя [Нн]абережная \", \" $1ей набережной \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Нн]абережная \", \" $1-й $2ой набережной \"],\r\n [\"^ [Нн]абережная \", \" набережной \"],\r\n\r\n [\"^ (\\\\S+)ая [Пп]лощадь \", \" $1ой площади \"],\r\n [\"^ (\\\\S+)ья [Пп]лощадь \", \" $1ьей площади \"],\r\n [\"^ (\\\\S+)яя [Пп]лощадь \", \" $1ей площади \"],\r\n [\"^ (\\\\S+[вн])а [Пп]лощадь \", \" $1ой площади \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Пп]лощадь \", \" $1-й $2ой площади \"],\r\n [\"^ [Пп]лощадь \", \" площади \"],\r\n\r\n [\"^ (\\\\S+)ая [Пп]росека \", \" $1ой просеке \"],\r\n [\"^ (\\\\S+)ья [Пп]росека \", \" $1ьей просеке \"],\r\n [\"^ (\\\\S+)яя [Пп]росека \", \" $1ей просеке \"],\r\n [\"^ (\\\\d+)-я [Пп]росека \", \" $1-й просеке \"],\r\n [\"^ [Пп]росека \", \" просеке \"],\r\n\r\n [\"^ (\\\\S+)ая [Ээ]стакада \", \" $1ой эстакаде \"],\r\n [\"^ (\\\\S+)ья [Ээ]стакада \", \" $1ьей эстакаде \"],\r\n [\"^ (\\\\S+)яя [Ээ]стакада \", \" $1ей эстакаде \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Ээ]стакада \", \" $1-й $2ой эстакаде \"],\r\n [\"^ [Ээ]стакада \", \" эстакаде \"],\r\n\r\n [\"^ (\\\\S+)ая [Мм]агистраль \", \" $1ой магистрали \"],\r\n [\"^ (\\\\S+)ья [Мм]агистраль \", \" $1ьей магистрали \"],\r\n [\"^ (\\\\S+)яя [Мм]агистраль \", \" $1ей магистрали \"],\r\n [\"^ (\\\\S+)ая (\\\\S+)ая [Мм]агистраль \", \" $1ой $2ой магистрали \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Мм]агистраль \", \" $1-й $2ой магистрали \"],\r\n [\"^ [Мм]агистраль \", \" магистрали \"],\r\n\r\n [\"^ (\\\\S+)ая [Рр]азвязка \", \" $1ой развязке \"],\r\n [\"^ (\\\\S+)ья [Рр]азвязка \", \" $1ьей развязке \"],\r\n [\"^ (\\\\S+)яя [Рр]азвязка \", \" $1ей развязке \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Рр]азвязка \", \" $1-й $2ой развязке \"],\r\n [\"^ [Рр]азвязка \", \" развязке \"],\r\n\r\n [\"^ (\\\\S+)ая [Тт]расса \", \" $1ой трассе \"],\r\n [\"^ (\\\\S+)ья [Тт]расса \", \" $1ьей трассе \"],\r\n [\"^ (\\\\S+)яя [Тт]расса \", \" $1ей трассе \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Тт]расса \", \" $1-й $2ой трассе \"],\r\n [\"^ [Тт]расса \", \" трассе \"],\r\n\r\n [\"^ (\\\\S+)ая ([Аа]вто)?[Дд]орога \", \" $1ой $2дороге \"],\r\n [\"^ (\\\\S+)ья ([Аа]вто)?[Дд]орога \", \" $1ьей $2дороге \"],\r\n [\"^ (\\\\S+)яя ([Аа]вто)?[Дд]орога \", \" $1ей $2дороге \"],\r\n [\"^ (\\\\S+)ая (\\\\S+)ая ([Аа]вто)?[Дд]орога \", \" $1ой $2ой $3дороге \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая ([Аа]вто)?[Дд]орога \", \" $1-й $2ой $3дороге \"],\r\n [\"^ ([Аа]вто)?[Дд]орога \", \" $1дороге \"],\r\n\r\n [\"^ (\\\\S+)ая [Дд]орожка \", \" $1ой дорожке \"],\r\n [\"^ (\\\\S+)ья [Дд]орожка \", \" $1ьей дорожке \"],\r\n [\"^ (\\\\S+)яя [Дд]орожка \", \" $1ей дорожке \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Дд]орожка \", \" $1-й $2ой дорожке \"],\r\n [\"^ [Дд]орожка \", \" дорожке \"],\r\n\r\n [\"^ (\\\\S+)во [Пп]оле \", \" $1ву полю \"],\r\n [\"^ (\\\\S+)ая [Кк]оса \", \" $1ой косе \"],\r\n [\"^ (\\\\S+)ая [Хх]орда \", \" $1ой хорде \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]роток \", \" $1ому протоку \"],\r\n\r\n [\"^ (\\\\S+н)ий [Бб]ульвар \", \" $1ему бульвару \"],\r\n [\"^ (\\\\S+)[иоы]й [Бб]ульвар \", \" $1ому бульвару \"],\r\n [\"^ (\\\\S+[иы]н) [Бб]ульвар \", \" $1у бульвару \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Бб]ульвар \", \" $1ому $2ему бульвару \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Бб]ульвар \", \" $1ему $2ому бульвару \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Бб]ульвар \", \" $1ому $2ому бульвару \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Бб]ульвар \", \" $1ому $2у бульвару \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Бб]ульвар \", \" $1-му $2ему бульвару \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Бб]ульвар \", \" $1-му $2ому бульвару \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Бб]ульвар \", \" $1-му $2у бульвару \"],\r\n [\"^ [Бб]ульвар \", \" бульвару \"],\r\n\r\n [\"^ [Дд]убл[её]р \", \" дублёру \"],\r\n\r\n [\"^ (\\\\S+н)ий [Зз]аезд \", \" $1ему заезду \"],\r\n [\"^ (\\\\S+)[иоы]й [Зз]аезд \", \" $1ому заезду \"],\r\n [\"^ (\\\\S+[еёо]в) [Зз]аезд \", \" $1у заезду \"],\r\n [\"^ (\\\\S+[иы]н) [Зз]аезд \", \" $1у заезду \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Зз]аезд \", \" $1ому $2ему заезду \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Зз]аезд \", \" $1ему $2ому заезду \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Зз]аезд \", \" $1ому $2ому заезду \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Зз]аезд \", \" $1ому $2у заезду \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Зз]аезд \", \" $1ому $2у заезду \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Зз]аезд \", \" $1-му $2ему заезду \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Зз]аезд \", \" $1-му $2ому заезду \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Зз]аезд \", \" $1-му $2у заезду \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Зз]аезд \", \" $1-му $2у заезду \"],\r\n [\"^ [Зз]аезд \", \" заезду \"],\r\n\r\n [\"^ (\\\\S+н)ий [Мм]ост \", \" $1ему мосту \"],\r\n [\"^ (\\\\S+)[иоы]й [Мм]ост \", \" $1ому мосту \"],\r\n [\"^ (\\\\S+[еёо]в) [Мм]ост \", \" $1у мосту \"],\r\n [\"^ (\\\\S+[иы]н) [Мм]ост \", \" $1у мосту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Мм]ост \", \" $1ому $2ему мосту \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Мм]ост \", \" $1ему $2ому мосту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Мм]ост \", \" $1ому $2ому мосту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Мм]ост \", \" $1ому $2у мосту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Мм]ост \", \" $1ому $2у мосту \"],\r\n [\"^ (\\\\d+)-й [Мм]ост \", \" $1-му мосту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Мм]ост \", \" $1-му $2ему мосту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Мм]ост \", \" $1-му $2ому мосту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Мм]ост \", \" $1-му $2у мосту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Мм]ост \", \" $1-му $2у мосту \"],\r\n [\"^ [Мм]ост \", \" мосту \"],\r\n\r\n [\"^ (\\\\S+н)ий [Оо]бход \", \" $1ему обходу \"],\r\n [\"^ (\\\\S+)[иоы]й [Оо]бход \", \" $1ому обходу \"],\r\n [\"^ [Оо]бход \", \" обходу \"],\r\n\r\n [\"^ (\\\\S+н)ий [Пп]арк \", \" $1ему парку \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]арк \", \" $1ому парку \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]арк \", \" $1у парку \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]арк \", \" $1ому $2ему парку \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]арк \", \" $1ему $2ому парку \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]арк \", \" $1ому $2ому парку \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]арк \", \" $1ому $2у парку \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]арк \", \" $1-му $2ему парку \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]арк \", \" $1-му $2ому парку \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]арк \", \" $1-му $2у парку \"],\r\n [\"^ [Пп]арк \", \" парку \"],\r\n\r\n [\"^ (\\\\S+)[иоы]й-(\\\\S+)[иоы]й [Пп]ереулок \", \" $1ому-$2ому переулку \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й-(\\\\S+)[иоы]й [Пп]ереулок \", \" $1-му $2ому-$3ому переулку \"],\r\n [\"^ (\\\\S+н)ий [Пп]ереулок \", \" $1ему переулку \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]ереулок \", \" $1ому переулку \"],\r\n [\"^ (\\\\S+[еёо]в) [Пп]ереулок \", \" $1у переулку \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]ереулок \", \" $1у переулку \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]ереулок \", \" $1ому $2ему переулку \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]ереулок \", \" $1ему $2ому переулку \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]ереулок \", \" $1ому $2ому переулку \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Пп]ереулок \", \" $1ому $2у переулку \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]ереулок \", \" $1ому $2у переулку \"],\r\n [\"^ (\\\\d+)-й [Пп]ереулок \", \" $1-му переулку \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]ереулок \", \" $1-му $2ему переулку \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]ереулок \", \" $1-му $2ому переулку \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Пп]ереулок \", \" $1-му $2у переулку \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]ереулок \", \" $1-му $2у переулку \"],\r\n [\"^ [Пп]ереулок \", \" переулку \"],\r\n\r\n [\"^ [Пп]одъезд \", \" подъезду \"],\r\n\r\n [\"^ (\\\\S+[еёо]в)-(\\\\S+)[иоы]й [Пп]роезд \", \" $1у-$2ому проезду \"],\r\n [\"^ (\\\\S+н)ий [Пп]роезд \", \" $1ему проезду \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]роезд \", \" $1ому проезду \"],\r\n [\"^ (\\\\S+[еёо]в) [Пп]роезд \", \" $1у проезду \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]роезд \", \" $1у проезду \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]роезд \", \" $1ому $2ему проезду \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]роезд \", \" $1ему $2ому проезду \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]роезд \", \" $1ому $2ому проезду \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Пп]роезд \", \" $1ому $2у проезду \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]роезд \", \" $1ому $2у проезду \"],\r\n [\"^ (\\\\d+)-й [Пп]роезд \", \" $1-му проезду \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]роезд \", \" $1-му $2ему проезду \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]роезд \", \" $1-му $2ому проезду \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Пп]роезд \", \" $1-му $2у проезду \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]роезд \", \" $1-му $2у проезду \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]роезд \", \" $1-му $2ему $3ому проезду \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]роезд \", \" $1-му $2ому $3ому проезду \"],\r\n [\"^ [Пп]роезд \", \" проезду \"],\r\n\r\n [\"^ (\\\\S+н)ий [Пп]роспект \", \" $1ему проспекту \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]роспект \", \" $1ому проспекту \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]роспект \", \" $1у проспекту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]роспект \", \" $1ому $2ему проспекту \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]роспект \", \" $1ему $2ому проспекту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]роспект \", \" $1ому $2ому проспекту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]роспект \", \" $1ому $2у проспекту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]роспект \", \" $1-му $2ему проспекту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]роспект \", \" $1-му $2ому проспекту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]роспект \", \" $1-му $2у проспекту \"],\r\n [\"^ [Пп]роспект \", \" проспекту \"],\r\n\r\n [\"^ (\\\\S+н)ий [Пп]утепровод \", \" $1ему путепроводу \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]утепровод \", \" $1ому путепроводу \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]утепровод \", \" $1у путепроводу \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]утепровод \", \" $1ому $2ему путепроводу \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]утепровод \", \" $1ему $2ому путепроводу \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]утепровод \", \" $1ому $2ому путепроводу \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]утепровод \", \" $1ому $2у путепроводу \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]утепровод \", \" $1-му $2ему путепроводу \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]утепровод \", \" $1-му $2ому путепроводу \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]утепровод \", \" $1-му $2у путепроводу \"],\r\n [\"^ [Пп]утепровод \", \" путепроводу \"],\r\n\r\n [\"^ (\\\\S+н)ий [Сс]пуск \", \" $1ему спуску \"],\r\n [\"^ (\\\\S+)[иоы]й [Сс]пуск \", \" $1ому спуску \"],\r\n [\"^ (\\\\S+[еёо]в) [Сс]пуск \", \" $1у спуску \"],\r\n [\"^ (\\\\S+[иы]н) [Сс]пуск \", \" $1у спуску \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Сс]пуск \", \" $1ому $2ему спуску \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Сс]пуск \", \" $1ему $2ому спуску \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Сс]пуск \", \" $1ому $2ому спуску \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Сс]пуск \", \" $1ому $2у спуску \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Сс]пуск \", \" $1ому $2у спуску \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Сс]пуск \", \" $1-му $2ему спуску \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Сс]пуск \", \" $1-му $2ому спуску \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Сс]пуск \", \" $1-му $2у спуску \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Сс]пуск \", \" $1-му $2у спуску \"],\r\n [\"^ [Сс]пуск \", \" спуску \"],\r\n\r\n [\"^ (\\\\S+н)ий [Сс]ъезд \", \" $1ему съезду \"],\r\n [\"^ (\\\\S+)[иоы]й [Сс]ъезд \", \" $1ому съезду \"],\r\n [\"^ (\\\\S+[иы]н) [Сс]ъезд \", \" $1у съезду \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Сс]ъезд \", \" $1ому $2ему съезду \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Сс]ъезд \", \" $1ему $2ому съезду \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Сс]ъезд \", \" $1ому $2ому съезду \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Сс]ъезд \", \" $1ому $2у съезду \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Сс]ъезд \", \" $1-му $2ему съезду \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Сс]ъезд \", \" $1-му $2ому съезду \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Сс]ъезд \", \" $1-му $2у съезду \"],\r\n [\"^ [Сс]ъезд \", \" съезду \"],\r\n\r\n [\"^ (\\\\S+н)ий [Тт][уо]ннель \", \" $1ему тоннелю \"],\r\n [\"^ (\\\\S+)[иоы]й [Тт][уо]ннель \", \" $1ому тоннелю \"],\r\n [\"^ (\\\\S+[иы]н) [Тт][уо]ннель \", \" $1у тоннелю \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Тт][уо]ннель \", \" $1ому $2ему тоннелю \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Тт][уо]ннель \", \" $1ему $2ому тоннелю \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Тт][уо]ннель \", \" $1ому $2ому тоннелю \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Тт][уо]ннель \", \" $1ому $2у тоннелю \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Тт][уо]ннель \", \" $1-му $2ему тоннелю \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Тт][уо]ннель \", \" $1-му $2ому тоннелю \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Тт][уо]ннель \", \" $1-му $2у тоннелю \"],\r\n [\"^ [Тт][уо]ннель \", \" тоннелю \"],\r\n\r\n [\"^ (\\\\S+н)ий [Тт]ракт \", \" $1ему тракту \"],\r\n [\"^ (\\\\S+)[иоы]й [Тт]ракт \", \" $1ому тракту \"],\r\n [\"^ (\\\\S+[еёо]в) [Тт]ракт \", \" $1у тракту \"],\r\n [\"^ (\\\\S+[иы]н) [Тт]ракт \", \" $1у тракту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Тт]ракт \", \" $1ому $2ему тракту \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Тт]ракт \", \" $1ему $2ому тракту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Тт]ракт \", \" $1ому $2ому тракту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Тт]ракт \", \" $1ому $2у тракту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Тт]ракт \", \" $1ому $2у тракту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Тт]ракт \", \" $1-му $2ему тракту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Тт]ракт \", \" $1-му $2ому тракту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Тт]ракт \", \" $1-му $2у тракту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Тт]ракт \", \" $1-му $2у тракту \"],\r\n [\"^ [Тт]ракт \", \" тракту \"],\r\n\r\n [\"^ (\\\\S+н)ий [Тт]упик \", \" $1ему тупику \"],\r\n [\"^ (\\\\S+)[иоы]й [Тт]упик \", \" $1ому тупику \"],\r\n [\"^ (\\\\S+[еёо]в) [Тт]упик \", \" $1у тупику \"],\r\n [\"^ (\\\\S+[иы]н) [Тт]упик \", \" $1у тупику \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Тт]упик \", \" $1ому $2ему тупику \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Тт]упик \", \" $1ему $2ому тупику \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Тт]упик \", \" $1ому $2ому тупику \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Тт]упик \", \" $1ому $2у тупику \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Тт]упик \", \" $1ому $2у тупику \"],\r\n [\"^ (\\\\d+)-й [Тт]упик \", \" $1-му тупику \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Тт]упик \", \" $1-му $2ему тупику \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Тт]упик \", \" $1-му $2ому тупику \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Тт]упик \", \" $1-му $2у тупику \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Тт]упик \", \" $1-му $2у тупику \"],\r\n [\"^ [Тт]упик \", \" тупику \"],\r\n\r\n [\"^ (\\\\S+[ео])е ([Пп]олу)?[Кк]ольцо \", \" $1му $2кольцу \"],\r\n [\"^ (\\\\S+ье) ([Пп]олу)?[Кк]ольцо \", \" $1му $2кольцу \"],\r\n [\"^ (\\\\S+[ео])е (\\\\S+[ео])е ([Пп]олу)?[Кк]ольцо \", \" $1му $2му $3кольцу \"],\r\n [\"^ (\\\\S+ье) (\\\\S+[ео])е ([Пп]олу)?[Кк]ольцо \", \" $1му $2му $3кольцу \"],\r\n [\"^ (\\\\d+)-е (\\\\S+[ео])е ([Пп]олу)?[Кк]ольцо \", \" $1-му $2му $3кольцу \"],\r\n [\"^ (\\\\d+)-е (\\\\S+ье) ([Пп]олу)?[Кк]ольцо \", \" $1-му $2му $3кольцу \"],\r\n [\"^ ([Пп]олу)?[Кк]ольцо \", \" $1кольцу \"],\r\n\r\n [\"^ (\\\\S+[ео])е [Шш]оссе \", \" $1му шоссе \"],\r\n [\"^ (\\\\S+ье) [Шш]оссе \", \" $1му шоссе \"],\r\n [\"^ (\\\\S+[ео])е (\\\\S+[ео])е [Шш]оссе \", \" $1му $2му шоссе \"],\r\n [\"^ (\\\\S+ье) (\\\\S+[ео])е [Шш]оссе \", \" $1му $2му шоссе \"],\r\n [\"^ (\\\\d+)-е (\\\\S+[ео])е [Шш]оссе \", \" $1-му $2му шоссе \"],\r\n [\"^ (\\\\d+)-е (\\\\S+ье) [Шш]оссе \", \" $1-му $2му шоссе \"],\r\n\r\n [\" ([Тт])ретому \", \" $1ретьему \"],\r\n [\"([жч])ому \", \"$1ьему \"],\r\n [\"([жч])ой \", \"$1ей \"]\r\n ],\r\n \"genitive\": [\r\n [\"^ ([«\\\"])\", \" трасса $1\"],\r\n\r\n [\"^ (\\\\S+)ая [Аа]ллея \", \" $1ой аллеи \"],\r\n [\"^ (\\\\S+)ья [Аа]ллея \", \" $1ьей аллеи \"],\r\n [\"^ (\\\\S+)яя [Аа]ллея \", \" $1ей аллеи \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Аа]ллея \", \" $1-й $2ой аллеи \"],\r\n [\"^ [Аа]ллея \", \" аллеи \"],\r\n\r\n [\"^ (\\\\S+)ая-(\\\\S+)ая [Уу]лица \", \" $1ой-$2ой улицы \"],\r\n [\"^ (\\\\S+)ая [Уу]лица \", \" $1ой улицы \"],\r\n [\"^ (\\\\S+)ья [Уу]лица \", \" $1ьей улицы \"],\r\n [\"^ (\\\\S+)яя [Уу]лица \", \" $1ей улицы \"],\r\n [\"^ (\\\\d+)-я [Уу]лица \", \" $1-й улицы \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Уу]лица \", \" $1-й $2ой улицы \"],\r\n [\"^ (\\\\S+)ая (\\\\S+)ая [Уу]лица \", \" $1ой $2ой улицы \"],\r\n [\"^ (\\\\S+[вн])а [Уу]лица \", \" $1ой улицы \"],\r\n [\"^ (\\\\S+)ая (\\\\S+[вн])а [Уу]лица \", \" $1ой $2ой улицы \"],\r\n [\"^ Даньславля [Уу]лица \", \" Даньславлей улицы \"],\r\n [\"^ Добрыня [Уу]лица \", \" Добрыней улицы \"],\r\n [\"^ Людогоща [Уу]лица \", \" Людогощей улицы \"],\r\n [\"^ [Уу]лица \", \" улицы \"],\r\n\r\n [\"^ (\\\\d+)-я [Лл]иния \", \" $1-й линии \"],\r\n [\"^ (\\\\d+)-(\\\\d+)-я [Лл]иния \", \" $1-$2-й линии \"],\r\n [\"^ (\\\\S+)ая [Лл]иния \", \" $1ой линии \"],\r\n [\"^ (\\\\S+)ья [Лл]иния \", \" $1ьей линии \"],\r\n [\"^ (\\\\S+)яя [Лл]иния \", \" $1ей линии \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Лл]иния \", \" $1-й $2ой линии \"],\r\n [\"^ [Лл]иния \", \" линии \"],\r\n\r\n [\"^ (\\\\d+)-(\\\\d+)-я [Лл]инии \", \" $1-$2-й линий \"],\r\n\r\n [\"^ (\\\\S+)ая [Нн]абережная \", \" $1ой набережной \"],\r\n [\"^ (\\\\S+)ья [Нн]абережная \", \" $1ьей набережной \"],\r\n [\"^ (\\\\S+)яя [Нн]абережная \", \" $1ей набережной \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Нн]абережная \", \" $1-й $2ой набережной \"],\r\n [\"^ [Нн]абережная \", \" набережной \"],\r\n\r\n [\"^ (\\\\S+)ая [Пп]лощадь \", \" $1ой площади \"],\r\n [\"^ (\\\\S+)ья [Пп]лощадь \", \" $1ьей площади \"],\r\n [\"^ (\\\\S+)яя [Пп]лощадь \", \" $1ей площади \"],\r\n [\"^ (\\\\S+[вн])а [Пп]лощадь \", \" $1ой площади \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Пп]лощадь \", \" $1-й $2ой площади \"],\r\n [\"^ [Пп]лощадь \", \" площади \"],\r\n\r\n [\"^ (\\\\S+)ая [Пп]росека \", \" $1ой просеки \"],\r\n [\"^ (\\\\S+)ья [Пп]росека \", \" $1ьей просеки \"],\r\n [\"^ (\\\\S+)яя [Пп]росека \", \" $1ей просеки \"],\r\n [\"^ (\\\\d+)-я [Пп]росека \", \" $1-й просеки \"],\r\n [\"^ [Пп]росека \", \" просеки \"],\r\n\r\n [\"^ (\\\\S+)ая [Ээ]стакада \", \" $1ой эстакады \"],\r\n [\"^ (\\\\S+)ья [Ээ]стакада \", \" $1ьей эстакады \"],\r\n [\"^ (\\\\S+)яя [Ээ]стакада \", \" $1ей эстакады \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Ээ]стакада \", \" $1-й $2ой эстакады \"],\r\n [\"^ [Ээ]стакада \", \" эстакады \"],\r\n\r\n [\"^ (\\\\S+)ая [Мм]агистраль \", \" $1ой магистрали \"],\r\n [\"^ (\\\\S+)ья [Мм]агистраль \", \" $1ьей магистрали \"],\r\n [\"^ (\\\\S+)яя [Мм]агистраль \", \" $1ей магистрали \"],\r\n [\"^ (\\\\S+)ая (\\\\S+)ая [Мм]агистраль \", \" $1ой $2ой магистрали \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Мм]агистраль \", \" $1-й $2ой магистрали \"],\r\n [\"^ [Мм]агистраль \", \" магистрали \"],\r\n\r\n [\"^ (\\\\S+)ая [Рр]азвязка \", \" $1ой развязки \"],\r\n [\"^ (\\\\S+)ья [Рр]азвязка \", \" $1ьей развязки \"],\r\n [\"^ (\\\\S+)яя [Рр]азвязка \", \" $1ей развязки \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Рр]азвязка \", \" $1-й $2ой развязки \"],\r\n [\"^ [Рр]азвязка \", \" развязки \"],\r\n\r\n [\"^ (\\\\S+)ая [Тт]расса \", \" $1ой трассы \"],\r\n [\"^ (\\\\S+)ья [Тт]расса \", \" $1ьей трассы \"],\r\n [\"^ (\\\\S+)яя [Тт]расса \", \" $1ей трассы \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Тт]расса \", \" $1-й $2ой трассы \"],\r\n [\"^ [Тт]расса \", \" трассы \"],\r\n\r\n [\"^ (\\\\S+)ая ([Аа]вто)?[Дд]орога \", \" $1ой $2дороги \"],\r\n [\"^ (\\\\S+)ья ([Аа]вто)?[Дд]орога \", \" $1ьей $2дороги \"],\r\n [\"^ (\\\\S+)яя ([Аа]вто)?[Дд]орога \", \" $1ей $2дороги \"],\r\n [\"^ (\\\\S+)ая (\\\\S+)ая ([Аа]вто)?[Дд]орога \", \" $1ой $2ой $3дороги \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая ([Аа]вто)?[Дд]орога \", \" $1-й $2ой $3дороги \"],\r\n [\"^ ([Аа]вто)?[Дд]орога \", \" $1дороги \"],\r\n\r\n [\"^ (\\\\S+)ая [Дд]орожка \", \" $1ой дорожки \"],\r\n [\"^ (\\\\S+)ья [Дд]орожка \", \" $1ьей дорожки \"],\r\n [\"^ (\\\\S+)яя [Дд]орожка \", \" $1ей дорожки \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Дд]орожка \", \" $1-й $2ой дорожки \"],\r\n [\"^ [Дд]орожка \", \" дорожки \"],\r\n\r\n [\"^ (\\\\S+)во [Пп]оле \", \" $1ва поля \"],\r\n [\"^ (\\\\S+)ая [Кк]оса \", \" $1ой косы \"],\r\n [\"^ (\\\\S+)ая [Хх]орда \", \" $1ой хорды \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]роток \", \" $1ого протока \"],\r\n\r\n [\"^ (\\\\S+н)ий [Бб]ульвар \", \" $1его бульвара \"],\r\n [\"^ (\\\\S+)[иоы]й [Бб]ульвар \", \" $1ого бульвара \"],\r\n [\"^ (\\\\S+[иы]н) [Бб]ульвар \", \" $1ого бульвара \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Бб]ульвар \", \" $1ого $2его бульвара \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Бб]ульвар \", \" $1его $2ого бульвара \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Бб]ульвар \", \" $1ого $2ого бульвара \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Бб]ульвар \", \" $1ого $2ого бульвара \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Бб]ульвар \", \" $1-го $2его бульвара \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Бб]ульвар \", \" $1-го $2ого бульвара \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Бб]ульвар \", \" $1-го $2ого бульвара \"],\r\n [\"^ [Бб]ульвар \", \" бульвара \"],\r\n\r\n [\"^ [Дд]убл[её]р \", \" дублёра \"],\r\n\r\n [\"^ (\\\\S+н)ий [Зз]аезд \", \" $1его заезда \"],\r\n [\"^ (\\\\S+)[иоы]й [Зз]аезд \", \" $1ого заезда \"],\r\n [\"^ (\\\\S+[еёо]в) [Зз]аезд \", \" $1а заезда \"],\r\n [\"^ (\\\\S+[иы]н) [Зз]аезд \", \" $1а заезда \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Зз]аезд \", \" $1ого $2его заезда \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Зз]аезд \", \" $1его $2ого заезда \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Зз]аезд \", \" $1ого $2ого заезда \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Зз]аезд \", \" $1ого $2а заезда \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Зз]аезд \", \" $1ого $2а заезда \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Зз]аезд \", \" $1-го $2его заезда \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Зз]аезд \", \" $1-го $2ого заезда \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Зз]аезд \", \" $1-го $2а заезда \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Зз]аезд \", \" $1-го $2а заезда \"],\r\n [\"^ [Зз]аезд \", \" заезда \"],\r\n\r\n [\"^ (\\\\S+н)ий [Мм]ост \", \" $1его моста \"],\r\n [\"^ (\\\\S+)[иоы]й [Мм]ост \", \" $1ого моста \"],\r\n [\"^ (\\\\S+[еёо]в) [Мм]ост \", \" $1а моста \"],\r\n [\"^ (\\\\S+[иы]н) [Мм]ост \", \" $1а моста \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Мм]ост \", \" $1ого $2его моста \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Мм]ост \", \" $1его $2ого моста \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Мм]ост \", \" $1ого $2ого моста \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Мм]ост \", \" $1ого $2а моста \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Мм]ост \", \" $1ого $2а моста \"],\r\n [\"^ (\\\\d+)-й [Мм]ост \", \" $1-го моста \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Мм]ост \", \" $1-го $2его моста \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Мм]ост \", \" $1-го $2ого моста \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Мм]ост \", \" $1-го $2а моста \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Мм]ост \", \" $1-го $2а моста \"],\r\n [\"^ [Мм]ост \", \" моста \"],\r\n\r\n [\"^ (\\\\S+н)ий [Оо]бход \", \" $1его обхода \"],\r\n [\"^ (\\\\S+)[иоы]й [Оо]бход \", \" $1ого обхода \"],\r\n [\"^ [Оо]бход \", \" обхода \"],\r\n\r\n [\"^ (\\\\S+н)ий [Пп]арк \", \" $1его парка \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]арк \", \" $1ого парка \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]арк \", \" $1ого парка \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]арк \", \" $1ого $2его парка \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]арк \", \" $1его $2ого парка \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]арк \", \" $1ого $2ого парка \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]арк \", \" $1ого $2ого парка \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]арк \", \" $1-го $2его парка \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]арк \", \" $1-го $2ого парка \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]арк \", \" $1-го $2ого парка \"],\r\n [\"^ [Пп]арк \", \" парка \"],\r\n\r\n [\"^ (\\\\S+)[иоы]й-(\\\\S+)[иоы]й [Пп]ереулок \", \" $1ого-$2ого переулка \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й-(\\\\S+)[иоы]й [Пп]ереулок \", \" $1-го $2ого-$3ого переулка \"],\r\n [\"^ (\\\\S+н)ий [Пп]ереулок \", \" $1его переулка \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]ереулок \", \" $1ого переулка \"],\r\n [\"^ (\\\\S+[еёо]в) [Пп]ереулок \", \" $1а переулка \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]ереулок \", \" $1а переулка \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]ереулок \", \" $1ого $2его переулка \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]ереулок \", \" $1его $2ого переулка \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]ереулок \", \" $1ого $2ого переулка \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Пп]ереулок \", \" $1ого $2а переулка \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]ереулок \", \" $1ого $2а переулка \"],\r\n [\"^ (\\\\d+)-й [Пп]ереулок \", \" $1-го переулка \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]ереулок \", \" $1-го $2его переулка \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]ереулок \", \" $1-го $2ого переулка \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Пп]ереулок \", \" $1-го $2а переулка \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]ереулок \", \" $1-го $2а переулка \"],\r\n [\"^ [Пп]ереулок \", \" переулка \"],\r\n\r\n [\"^ [Пп]одъезд \", \" подъезда \"],\r\n\r\n [\"^ (\\\\S+[еёо]в)-(\\\\S+)[иоы]й [Пп]роезд \", \" $1а-$2ого проезда \"],\r\n [\"^ (\\\\S+н)ий [Пп]роезд \", \" $1его проезда \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]роезд \", \" $1ого проезда \"],\r\n [\"^ (\\\\S+[еёо]в) [Пп]роезд \", \" $1а проезда \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]роезд \", \" $1а проезда \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]роезд \", \" $1ого $2его проезда \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]роезд \", \" $1его $2ого проезда \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]роезд \", \" $1ого $2ого проезда \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Пп]роезд \", \" $1ого $2а проезда \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]роезд \", \" $1ого $2а проезда \"],\r\n [\"^ (\\\\d+)-й [Пп]роезд \", \" $1-го проезда \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]роезд \", \" $1-го $2его проезда \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]роезд \", \" $1-го $2ого проезда \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Пп]роезд \", \" $1-го $2а проезда \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]роезд \", \" $1-го $2а проезда \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]роезд \", \" $1-го $2его $3ого проезда \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]роезд \", \" $1-го $2ого $3ого проезда \"],\r\n [\"^ [Пп]роезд \", \" проезда \"],\r\n\r\n [\"^ (\\\\S+н)ий [Пп]роспект \", \" $1его проспекта \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]роспект \", \" $1ого проспекта \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]роспект \", \" $1ого проспекта \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]роспект \", \" $1ого $2его проспекта \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]роспект \", \" $1его $2ого проспекта \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]роспект \", \" $1ого $2ого проспекта \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]роспект \", \" $1ого $2ого проспекта \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]роспект \", \" $1-го $2его проспекта \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]роспект \", \" $1-го $2ого проспекта \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]роспект \", \" $1-го $2ого проспекта \"],\r\n [\"^ [Пп]роспект \", \" проспекта \"],\r\n\r\n [\"^ (\\\\S+н)ий [Пп]утепровод \", \" $1его путепровода \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]утепровод \", \" $1ого путепровода \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]утепровод \", \" $1ого путепровода \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]утепровод \", \" $1ого $2его путепровода \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]утепровод \", \" $1его $2ого путепровода \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]утепровод \", \" $1ого $2ого путепровода \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]утепровод \", \" $1ого $2ого путепровода \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]утепровод \", \" $1-го $2его путепровода \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]утепровод \", \" $1-го $2ого путепровода \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]утепровод \", \" $1-го $2ого путепровода \"],\r\n [\"^ [Пп]утепровод \", \" путепровода \"],\r\n\r\n [\"^ (\\\\S+н)ий [Сс]пуск \", \" $1его спуска \"],\r\n [\"^ (\\\\S+)[иоы]й [Сс]пуск \", \" $1ого спуска \"],\r\n [\"^ (\\\\S+[еёо]в) [Сс]пуск \", \" $1а спуска \"],\r\n [\"^ (\\\\S+[иы]н) [Сс]пуск \", \" $1а спуска \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Сс]пуск \", \" $1ого $2его спуска \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Сс]пуск \", \" $1его $2ого спуска \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Сс]пуск \", \" $1ого $2ого спуска \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Сс]пуск \", \" $1ого $2а спуска \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Сс]пуск \", \" $1ого $2а спуска \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Сс]пуск \", \" $1-го $2его спуска \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Сс]пуск \", \" $1-го $2ого спуска \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Сс]пуск \", \" $1-го $2а спуска \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Сс]пуск \", \" $1-го $2а спуска \"],\r\n [\"^ [Сс]пуск \", \" спуска \"],\r\n\r\n [\"^ (\\\\S+н)ий [Сс]ъезд \", \" $1его съезда \"],\r\n [\"^ (\\\\S+)[иоы]й [Сс]ъезд \", \" $1ого съезда \"],\r\n [\"^ (\\\\S+[иы]н) [Сс]ъезд \", \" $1ого съезда \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Сс]ъезд \", \" $1ого $2его съезда \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Сс]ъезд \", \" $1его $2ого съезда \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Сс]ъезд \", \" $1ого $2ого съезда \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Сс]ъезд \", \" $1ого $2ого съезда \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Сс]ъезд \", \" $1-го $2его съезда \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Сс]ъезд \", \" $1-го $2ого съезда \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Сс]ъезд \", \" $1-го $2ого съезда \"],\r\n [\"^ [Сс]ъезд \", \" съезда \"],\r\n\r\n [\"^ (\\\\S+н)ий [Тт][уо]ннель \", \" $1его тоннеля \"],\r\n [\"^ (\\\\S+)[иоы]й [Тт][уо]ннель \", \" $1ого тоннеля \"],\r\n [\"^ (\\\\S+[иы]н) [Тт][уо]ннель \", \" $1ого тоннеля \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Тт][уо]ннель \", \" $1ого $2его тоннеля \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Тт][уо]ннель \", \" $1его $2ого тоннеля \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Тт][уо]ннель \", \" $1ого $2ого тоннеля \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Тт][уо]ннель \", \" $1ого $2ого тоннеля \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Тт][уо]ннель \", \" $1-го $2его тоннеля \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Тт][уо]ннель \", \" $1-го $2ого тоннеля \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Тт][уо]ннель \", \" $1-го $2ого тоннеля \"],\r\n [\"^ [Тт][уо]ннель \", \" тоннеля \"],\r\n\r\n [\"^ (\\\\S+н)ий [Тт]ракт \", \" $1ем тракта \"],\r\n [\"^ (\\\\S+)[иоы]й [Тт]ракт \", \" $1ого тракта \"],\r\n [\"^ (\\\\S+[еёо]в) [Тт]ракт \", \" $1а тракта \"],\r\n [\"^ (\\\\S+[иы]н) [Тт]ракт \", \" $1а тракта \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Тт]ракт \", \" $1ого $2его тракта \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Тт]ракт \", \" $1его $2ого тракта \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Тт]ракт \", \" $1ого $2ого тракта \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Тт]ракт \", \" $1ого $2а тракта \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Тт]ракт \", \" $1ого $2а тракта \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Тт]ракт \", \" $1-го $2его тракта \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Тт]ракт \", \" $1-го $2ого тракта \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Тт]ракт \", \" $1-го $2а тракта \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Тт]ракт \", \" $1-го $2а тракта \"],\r\n [\"^ [Тт]ракт \", \" тракта \"],\r\n\r\n [\"^ (\\\\S+н)ий [Тт]упик \", \" $1его тупика \"],\r\n [\"^ (\\\\S+)[иоы]й [Тт]упик \", \" $1ого тупика \"],\r\n [\"^ (\\\\S+[еёо]в) [Тт]упик \", \" $1а тупика \"],\r\n [\"^ (\\\\S+[иы]н) [Тт]упик \", \" $1а тупика \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Тт]упик \", \" $1ого $2его тупика \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Тт]упик \", \" $1его $2ого тупика \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Тт]упик \", \" $1ого $2ого тупика \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Тт]упик \", \" $1ого $2а тупика \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Тт]упик \", \" $1ого $2а тупика \"],\r\n [\"^ (\\\\d+)-й [Тт]упик \", \" $1-го тупика \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Тт]упик \", \" $1-го $2его тупика \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Тт]упик \", \" $1-го $2ого тупика \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Тт]упик \", \" $1-го $2а тупика \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Тт]упик \", \" $1-го $2а тупика \"],\r\n [\"^ [Тт]упик \", \" тупика \"],\r\n\r\n [\"^ (\\\\S+[ео])е ([Пп]олу)?[Кк]ольцо \", \" $1го $2кольца \"],\r\n [\"^ (\\\\S+ье) ([Пп]олу)?[Кк]ольцо \", \" $1го $2кольца \"],\r\n [\"^ (\\\\S+[ео])е (\\\\S+[ео])е ([Пп]олу)?[Кк]ольцо \", \" $1го $2го $3кольца \"],\r\n [\"^ (\\\\S+ье) (\\\\S+[ео])е ([Пп]олу)?[Кк]ольцо \", \" $1го $2го $3кольца \"],\r\n [\"^ (\\\\d+)-е (\\\\S+[ео])е ([Пп]олу)?[Кк]ольцо \", \" $1-го $2го $3кольца \"],\r\n [\"^ (\\\\d+)-е (\\\\S+ье) ([Пп]олу)?[Кк]ольцо \", \" $1-го $2го $3кольца \"],\r\n [\"^ ([Пп]олу)?[Кк]ольцо \", \" $1кольца \"],\r\n\r\n [\"^ (\\\\S+[ео])е [Шш]оссе \", \" $1го шоссе \"],\r\n [\"^ (\\\\S+ье) [Шш]оссе \", \" $1го шоссе \"],\r\n [\"^ (\\\\S+[ео])е (\\\\S+[ео])е [Шш]оссе \", \" $1го $2го шоссе \"],\r\n [\"^ (\\\\S+ье) (\\\\S+[ео])е [Шш]оссе \", \" $1го $2го шоссе \"],\r\n [\"^ (\\\\d+)-е (\\\\S+[ео])е [Шш]оссе \", \" $1-го $2го шоссе \"],\r\n [\"^ (\\\\d+)-е (\\\\S+ье) [Шш]оссе \", \" $1-го $2го шоссе \"],\r\n\r\n [\" ([Тт])ретого \", \" $1ретьего \"],\r\n [\"([жч])ого \", \"$1ьего \"]\r\n ],\r\n \"prepositional\": [\r\n [\"^ ([«\\\"])\", \" трасса $1\"],\r\n\r\n [\"^ (\\\\S+)ая [Аа]ллея \", \" $1ой аллее \"],\r\n [\"^ (\\\\S+)ья [Аа]ллея \", \" $1ьей аллее \"],\r\n [\"^ (\\\\S+)яя [Аа]ллея \", \" $1ей аллее \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Аа]ллея \", \" $1-й $2ой аллее \"],\r\n [\"^ [Аа]ллея \", \" аллее \"],\r\n\r\n [\"^ (\\\\S+)ая-(\\\\S+)ая [Уу]лица \", \" $1ой-$2ой улице \"],\r\n [\"^ (\\\\S+)ая [Уу]лица \", \" $1ой улице \"],\r\n [\"^ (\\\\S+)ья [Уу]лица \", \" $1ьей улице \"],\r\n [\"^ (\\\\S+)яя [Уу]лица \", \" $1ей улице \"],\r\n [\"^ (\\\\d+)-я [Уу]лица \", \" $1-й улице \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Уу]лица \", \" $1-й $2ой улице \"],\r\n [\"^ (\\\\S+)ая (\\\\S+)ая [Уу]лица \", \" $1ой $2ой улице \"],\r\n [\"^ (\\\\S+[вн])а [Уу]лица \", \" $1ой улице \"],\r\n [\"^ (\\\\S+)ая (\\\\S+[вн])а [Уу]лица \", \" $1ой $2ой улице \"],\r\n [\"^ Даньславля [Уу]лица \", \" Даньславлей улице \"],\r\n [\"^ Добрыня [Уу]лица \", \" Добрыней улице \"],\r\n [\"^ Людогоща [Уу]лица \", \" Людогощей улице \"],\r\n [\"^ [Уу]лица \", \" улице \"],\r\n\r\n [\"^ (\\\\d+)-я [Лл]иния \", \" $1-й линии \"],\r\n [\"^ (\\\\d+)-(\\\\d+)-я [Лл]иния \", \" $1-$2-й линии \"],\r\n [\"^ (\\\\S+)ая [Лл]иния \", \" $1ой линии \"],\r\n [\"^ (\\\\S+)ья [Лл]иния \", \" $1ьей линии \"],\r\n [\"^ (\\\\S+)яя [Лл]иния \", \" $1ей линии \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Лл]иния \", \" $1-й $2ой линии \"],\r\n [\"^ [Лл]иния \", \" линии \"],\r\n\r\n [\"^ (\\\\d+)-(\\\\d+)-я [Лл]инии \", \" $1-$2-й линиях \"],\r\n\r\n [\"^ (\\\\S+)ая [Нн]абережная \", \" $1ой набережной \"],\r\n [\"^ (\\\\S+)ья [Нн]абережная \", \" $1ьей набережной \"],\r\n [\"^ (\\\\S+)яя [Нн]абережная \", \" $1ей набережной \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Нн]абережная \", \" $1-й $2ой набережной \"],\r\n [\"^ [Нн]абережная \", \" набережной \"],\r\n\r\n [\"^ (\\\\S+)ая [Пп]лощадь \", \" $1ой площади \"],\r\n [\"^ (\\\\S+)ья [Пп]лощадь \", \" $1ьей площади \"],\r\n [\"^ (\\\\S+)яя [Пп]лощадь \", \" $1ей площади \"],\r\n [\"^ (\\\\S+[вн])а [Пп]лощадь \", \" $1ой площади \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Пп]лощадь \", \" $1-й $2ой площади \"],\r\n [\"^ [Пп]лощадь \", \" площади \"],\r\n\r\n [\"^ (\\\\S+)ая [Пп]росека \", \" $1ой просеке \"],\r\n [\"^ (\\\\S+)ья [Пп]росека \", \" $1ьей просеке \"],\r\n [\"^ (\\\\S+)яя [Пп]росека \", \" $1ей просеке \"],\r\n [\"^ (\\\\d+)-я [Пп]росека \", \" $1-й просеке \"],\r\n [\"^ [Пп]росека \", \" просеке \"],\r\n\r\n [\"^ (\\\\S+)ая [Ээ]стакада \", \" $1ой эстакаде \"],\r\n [\"^ (\\\\S+)ья [Ээ]стакада \", \" $1ьей эстакаде \"],\r\n [\"^ (\\\\S+)яя [Ээ]стакада \", \" $1ей эстакаде \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Ээ]стакада \", \" $1-й $2ой эстакаде \"],\r\n [\"^ [Ээ]стакада \", \" эстакаде \"],\r\n\r\n [\"^ (\\\\S+)ая [Мм]агистраль \", \" $1ой магистрали \"],\r\n [\"^ (\\\\S+)ья [Мм]агистраль \", \" $1ьей магистрали \"],\r\n [\"^ (\\\\S+)яя [Мм]агистраль \", \" $1ей магистрали \"],\r\n [\"^ (\\\\S+)ая (\\\\S+)ая [Мм]агистраль \", \" $1ой $2ой магистрали \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Мм]агистраль \", \" $1-й $2ой магистрали \"],\r\n [\"^ [Мм]агистраль \", \" магистрали \"],\r\n\r\n [\"^ (\\\\S+)ая [Рр]азвязка \", \" $1ой развязке \"],\r\n [\"^ (\\\\S+)ья [Рр]азвязка \", \" $1ьей развязке \"],\r\n [\"^ (\\\\S+)яя [Рр]азвязка \", \" $1ей развязке \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Рр]азвязка \", \" $1-й $2ой развязке \"],\r\n [\"^ [Рр]азвязка \", \" развязке \"],\r\n\r\n [\"^ (\\\\S+)ая [Тт]расса \", \" $1ой трассе \"],\r\n [\"^ (\\\\S+)ья [Тт]расса \", \" $1ьей трассе \"],\r\n [\"^ (\\\\S+)яя [Тт]расса \", \" $1ей трассе \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Тт]расса \", \" $1-й $2ой трассе \"],\r\n [\"^ [Тт]расса \", \" трассе \"],\r\n\r\n [\"^ (\\\\S+)ая ([Аа]вто)?[Дд]орога \", \" $1ой $2дороге \"],\r\n [\"^ (\\\\S+)ья ([Аа]вто)?[Дд]орога \", \" $1ьей $2дороге \"],\r\n [\"^ (\\\\S+)яя ([Аа]вто)?[Дд]орога \", \" $1ей $2дороге \"],\r\n [\"^ (\\\\S+)ая (\\\\S+)ая ([Аа]вто)?[Дд]орога \", \" $1ой $2ой $3дороге \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая ([Аа]вто)?[Дд]орога \", \" $1-й $2ой $3дороге \"],\r\n [\"^ ([Аа]вто)?[Дд]орога \", \" $1дороге \"],\r\n\r\n [\"^ (\\\\S+)ая [Дд]орожка \", \" $1ой дорожке \"],\r\n [\"^ (\\\\S+)ья [Дд]орожка \", \" $1ьей дорожке \"],\r\n [\"^ (\\\\S+)яя [Дд]орожка \", \" $1ей дорожке \"],\r\n [\"^ (\\\\d+)-я (\\\\S+)ая [Дд]орожка \", \" $1-й $2ой дорожке \"],\r\n [\"^ [Дд]орожка \", \" дорожке \"],\r\n\r\n [\"^ (\\\\S+)во [Пп]оле \", \" $1вом поле \"],\r\n [\"^ (\\\\S+)ая [Кк]оса \", \" $1ой косе \"],\r\n [\"^ (\\\\S+)ая [Хх]орда \", \" $1ой хорде \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]роток \", \" $1ом протоке \"],\r\n\r\n [\"^ (\\\\S+н)ий [Бб]ульвар \", \" $1ем бульваре \"],\r\n [\"^ (\\\\S+)[иоы]й [Бб]ульвар \", \" $1ом бульваре \"],\r\n [\"^ (\\\\S+[иы]н) [Бб]ульвар \", \" $1ом бульваре \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Бб]ульвар \", \" $1ом $2ем бульваре \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Бб]ульвар \", \" $1ем $2ом бульваре \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Бб]ульвар \", \" $1ом $2ом бульваре \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Бб]ульвар \", \" $1ом $2ом бульваре \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Бб]ульвар \", \" $1-м $2ем бульваре \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Бб]ульвар \", \" $1-м $2ом бульваре \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Бб]ульвар \", \" $1-м $2ом бульваре \"],\r\n [\"^ [Бб]ульвар \", \" бульваре \"],\r\n\r\n [\"^ [Дд]убл[её]р \", \" дублёре \"],\r\n\r\n [\"^ (\\\\S+н)ий [Зз]аезд \", \" $1ем заезде \"],\r\n [\"^ (\\\\S+)[иоы]й [Зз]аезд \", \" $1ом заезде \"],\r\n [\"^ (\\\\S+[еёо]в) [Зз]аезд \", \" $1ом заезде \"],\r\n [\"^ (\\\\S+[иы]н) [Зз]аезд \", \" $1ом заезде \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Зз]аезд \", \" $1ом $2ем заезде \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Зз]аезд \", \" $1ем $2ом заезде \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Зз]аезд \", \" $1ом $2ом заезде \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Зз]аезд \", \" $1ом $2ом заезде \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Зз]аезд \", \" $1ом $2ом заезде \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Зз]аезд \", \" $1-м $2ем заезде \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Зз]аезд \", \" $1-м $2ом заезде \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Зз]аезд \", \" $1-м $2ом заезде \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Зз]аезд \", \" $1-м $2ом заезде \"],\r\n [\"^ [Зз]аезд \", \" заезде \"],\r\n\r\n [\"^ (\\\\S+н)ий [Мм]ост \", \" $1ем мосту \"],\r\n [\"^ (\\\\S+)[иоы]й [Мм]ост \", \" $1ом мосту \"],\r\n [\"^ (\\\\S+[еёо]в) [Мм]ост \", \" $1ом мосту \"],\r\n [\"^ (\\\\S+[иы]н) [Мм]ост \", \" $1ом мосту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Мм]ост \", \" $1ом $2ем мосту \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Мм]ост \", \" $1ем $2ом мосту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Мм]ост \", \" $1ом $2ом мосту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Мм]ост \", \" $1ом $2ом мосту \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Мм]ост \", \" $1ом $2ом мосту \"],\r\n [\"^ (\\\\d+)-й [Мм]ост \", \" $1-м мосту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Мм]ост \", \" $1-м $2ем мосту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Мм]ост \", \" $1-м $2ом мосту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Мм]ост \", \" $1-м $2ом мосту \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Мм]ост \", \" $1-м $2ом мосту \"],\r\n [\"^ [Мм]ост \", \" мосту \"],\r\n\r\n [\"^ (\\\\S+н)ий [Оо]бход \", \" $1ем обходе \"],\r\n [\"^ (\\\\S+)[иоы]й [Оо]бход \", \" $1ом обходе \"],\r\n [\"^ [Оо]бход \", \" обходе \"],\r\n\r\n [\"^ (\\\\S+н)ий [Пп]арк \", \" $1ем парке \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]арк \", \" $1ом парке \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]арк \", \" $1ом парке \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]арк \", \" $1ом $2ем парке \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]арк \", \" $1ем $2ом парке \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]арк \", \" $1ом $2ом парке \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]арк \", \" $1ом $2ом парке \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]арк \", \" $1-м $2ем парке \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]арк \", \" $1-м $2ом парке \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]арк \", \" $1-м $2ом парке \"],\r\n [\"^ [Пп]арк \", \" парке \"],\r\n\r\n [\"^ (\\\\S+)[иоы]й-(\\\\S+)[иоы]й [Пп]ереулок \", \" $1ом-$2ом переулке \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й-(\\\\S+)[иоы]й [Пп]ереулок \", \" $1-м $2ом-$3ом переулке \"],\r\n [\"^ (\\\\S+н)ий [Пп]ереулок \", \" $1ем переулке \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]ереулок \", \" $1ом переулке \"],\r\n [\"^ (\\\\S+[еёо]в) [Пп]ереулок \", \" $1ом переулке \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]ереулок \", \" $1ом переулке \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]ереулок \", \" $1ом $2ем переулке \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]ереулок \", \" $1ем $2ом переулке \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]ереулок \", \" $1ом $2ом переулке \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Пп]ереулок \", \" $1ом $2ом переулке \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]ереулок \", \" $1ом $2ом переулке \"],\r\n [\"^ (\\\\d+)-й [Пп]ереулок \", \" $1-м переулке \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]ереулок \", \" $1-м $2ем переулке \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]ереулок \", \" $1-м $2ом переулке \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Пп]ереулок \", \" $1-м $2ом переулке \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]ереулок \", \" $1-м $2ом переулке \"],\r\n [\"^ [Пп]ереулок \", \" переулке \"],\r\n\r\n [\"^ [Пп]одъезд \", \" подъезде \"],\r\n\r\n [\"^ (\\\\S+[еёо]в)-(\\\\S+)[иоы]й [Пп]роезд \", \" $1ом-$2ом проезде \"],\r\n [\"^ (\\\\S+н)ий [Пп]роезд \", \" $1ем проезде \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]роезд \", \" $1ом проезде \"],\r\n [\"^ (\\\\S+[еёо]в) [Пп]роезд \", \" $1ом проезде \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]роезд \", \" $1ом проезде \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]роезд \", \" $1ом $2ем проезде \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]роезд \", \" $1ем $2ом проезде \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]роезд \", \" $1ом $2ом проезде \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Пп]роезд \", \" $1ом $2ом проезде \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]роезд \", \" $1ом $2ом проезде \"],\r\n [\"^ (\\\\d+)-й [Пп]роезд \", \" $1-м проезде \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]роезд \", \" $1-м $2ем проезде \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]роезд \", \" $1-м $2ом проезде \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Пп]роезд \", \" $1-м $2ом проезде \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]роезд \", \" $1-м $2ом проезде \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]роезд \", \" $1-м $2ем $3ом проезде \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]роезд \", \" $1-м $2ом $3ом проезде \"],\r\n [\"^ [Пп]роезд \", \" проезде \"],\r\n\r\n [\"^ (\\\\S+н)ий [Пп]роспект \", \" $1ем проспекте \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]роспект \", \" $1ом проспекте \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]роспект \", \" $1ом проспекте \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]роспект \", \" $1ом $2ем проспекте \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]роспект \", \" $1ем $2ом проспекте \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]роспект \", \" $1ом $2ом проспекте \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]роспект \", \" $1ом $2ом проспекте \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]роспект \", \" $1-м $2ем проспекте \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]роспект \", \" $1-м $2ом проспекте \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]роспект \", \" $1-м $2ом проспекте \"],\r\n [\"^ [Пп]роспект \", \" проспекте \"],\r\n\r\n [\"^ (\\\\S+н)ий [Пп]утепровод \", \" $1ем путепроводе \"],\r\n [\"^ (\\\\S+)[иоы]й [Пп]утепровод \", \" $1ом путепроводе \"],\r\n [\"^ (\\\\S+[иы]н) [Пп]утепровод \", \" $1ом путепроводе \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Пп]утепровод \", \" $1ом $2ем путепроводе \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Пп]утепровод \", \" $1ем $2ом путепроводе \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Пп]утепровод \", \" $1ом $2ом путепроводе \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Пп]утепровод \", \" $1ом $2ом путепроводе \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Пп]утепровод \", \" $1-м $2ем путепроводе \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Пп]утепровод \", \" $1-м $2ом путепроводе \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Пп]утепровод \", \" $1-м $2ом путепроводе \"],\r\n [\"^ [Пп]утепровод \", \" путепроводе \"],\r\n\r\n [\"^ (\\\\S+н)ий [Сс]пуск \", \" $1ем спуске \"],\r\n [\"^ (\\\\S+)[иоы]й [Сс]пуск \", \" $1ом спуске \"],\r\n [\"^ (\\\\S+[еёо]в) [Сс]пуск \", \" $1ом спуске \"],\r\n [\"^ (\\\\S+[иы]н) [Сс]пуск \", \" $1ом спуске \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Сс]пуск \", \" $1ом $2ем спуске \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Сс]пуск \", \" $1ем $2ом спуске \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Сс]пуск \", \" $1ом $2ом спуске \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Сс]пуск \", \" $1ом $2ом спуске \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Сс]пуск \", \" $1ом $2ом спуске \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Сс]пуск \", \" $1-м $2ем спуске \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Сс]пуск \", \" $1-м $2ом спуске \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Сс]пуск \", \" $1-м $2ом спуске \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Сс]пуск \", \" $1-м $2ом спуске \"],\r\n [\"^ [Сс]пуск \", \" спуске \"],\r\n\r\n [\"^ (\\\\S+н)ий [Сс]ъезд \", \" $1ем съезде \"],\r\n [\"^ (\\\\S+)[иоы]й [Сс]ъезд \", \" $1ом съезде \"],\r\n [\"^ (\\\\S+[иы]н) [Сс]ъезд \", \" $1ом съезде \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Сс]ъезд \", \" $1ом $2ем съезде \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Сс]ъезд \", \" $1ем $2ом съезде \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Сс]ъезд \", \" $1ом $2ом съезде \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Сс]ъезд \", \" $1ом $2ом съезде \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Сс]ъезд \", \" $1-м $2ем съезде \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Сс]ъезд \", \" $1-м $2ом съезде \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Сс]ъезд \", \" $1-м $2ом съезде \"],\r\n [\"^ [Сс]ъезд \", \" съезде \"],\r\n\r\n [\"^ (\\\\S+н)ий [Тт][уо]ннель \", \" $1ем тоннеле \"],\r\n [\"^ (\\\\S+)[иоы]й [Тт][уо]ннель \", \" $1ом тоннеле \"],\r\n [\"^ (\\\\S+[иы]н) [Тт][уо]ннель \", \" $1ом тоннеле \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Тт][уо]ннель \", \" $1ом $2ем тоннеле \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Тт][уо]ннель \", \" $1ем $2ом тоннеле \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Тт][уо]ннель \", \" $1ом $2ом тоннеле \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Тт][уо]ннель \", \" $1ом $2ом тоннеле \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Тт][уо]ннель \", \" $1-м $2ем тоннеле \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Тт][уо]ннель \", \" $1-м $2ом тоннеле \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Тт][уо]ннель \", \" $1-м $2ом тоннеле \"],\r\n [\"^ [Тт][уо]ннель \", \" тоннеле \"],\r\n\r\n [\"^ (\\\\S+н)ий [Тт]ракт \", \" $1ем тракте \"],\r\n [\"^ (\\\\S+)[иоы]й [Тт]ракт \", \" $1ом тракте \"],\r\n [\"^ (\\\\S+[еёо]в) [Тт]ракт \", \" $1ом тракте \"],\r\n [\"^ (\\\\S+[иы]н) [Тт]ракт \", \" $1ом тракте \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Тт]ракт \", \" $1ом $2ем тракте \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Тт]ракт \", \" $1ем $2ом тракте \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Тт]ракт \", \" $1ом $2ом тракте \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Тт]ракт \", \" $1ом $2ом тракте \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Тт]ракт \", \" $1ом $2ом тракте \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Тт]ракт \", \" $1-м $2ем тракте \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Тт]ракт \", \" $1-м $2ом тракте \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Тт]ракт \", \" $1-м $2ом тракте \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Тт]ракт \", \" $1-м $2ом тракте \"],\r\n [\"^ [Тт]ракт \", \" тракте \"],\r\n\r\n [\"^ (\\\\S+н)ий [Тт]упик \", \" $1ем тупике \"],\r\n [\"^ (\\\\S+)[иоы]й [Тт]упик \", \" $1ом тупике \"],\r\n [\"^ (\\\\S+[еёо]в) [Тт]упик \", \" $1ом тупике \"],\r\n [\"^ (\\\\S+[иы]н) [Тт]упик \", \" $1ом тупике \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+н)ий [Тт]упик \", \" $1ом $2ем тупике \"],\r\n [\"^ (\\\\S+н)ий (\\\\S+)[иоы]й [Тт]упик \", \" $1ем $2ом тупике \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+)[иоы]й [Тт]упик \", \" $1ом $2ом тупике \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[еёо]в) [Тт]упик \", \" $1ом $2ом тупике \"],\r\n [\"^ (\\\\S+)[иоы]й (\\\\S+[иы]н) [Тт]упик \", \" $1ом $2ом тупике \"],\r\n [\"^ (\\\\d+)-й [Тт]упик \", \" $1-м тупике \"],\r\n [\"^ (\\\\d+)-й (\\\\S+н)ий [Тт]упик \", \" $1-м $2ем тупике \"],\r\n [\"^ (\\\\d+)-й (\\\\S+)[иоы]й [Тт]упик \", \" $1-м $2ом тупике \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[еёо]в) [Тт]упик \", \" $1-м $2ом тупике \"],\r\n [\"^ (\\\\d+)-й (\\\\S+[иы]н) [Тт]упик \", \" $1-м $2ом тупике \"],\r\n [\"^ [Тт]упик \", \" тупике \"],\r\n\r\n [\"^ (\\\\S+[ео])е ([Пп]олу)?[Кк]ольцо \", \" $1м $2кольце \"],\r\n [\"^ (\\\\S+ье) ([Пп]олу)?[Кк]ольцо \", \" $1м $2кольце \"],\r\n [\"^ (\\\\S+[ео])е (\\\\S+[ео])е ([Пп]олу)?[Кк]ольцо \", \" $1м $2м $3кольце \"],\r\n [\"^ (\\\\S+ье) (\\\\S+[ео])е ([Пп]олу)?[Кк]ольцо \", \" $1м $2м $3кольце \"],\r\n [\"^ (\\\\d+)-е (\\\\S+[ео])е ([Пп]олу)?[Кк]ольцо \", \" $1-м $2м $3кольце \"],\r\n [\"^ (\\\\d+)-е (\\\\S+ье) ([Пп]олу)?[Кк]ольцо \", \" $1-м $2м $3кольце \"],\r\n [\"^ ([Пп]олу)?[Кк]ольцо \", \" $1кольце \"],\r\n\r\n [\"^ (\\\\S+[ео])е [Шш]оссе \", \" $1м шоссе \"],\r\n [\"^ (\\\\S+ье) [Шш]оссе \", \" $1м шоссе \"],\r\n [\"^ (\\\\S+[ео])е (\\\\S+[ео])е [Шш]оссе \", \" $1м $2м шоссе \"],\r\n [\"^ (\\\\S+ье) (\\\\S+[ео])е [Шш]оссе \", \" $1м $2м шоссе \"],\r\n [\"^ (\\\\d+)-е (\\\\S+[ео])е [Шш]оссе \", \" $1-м $2м шоссе \"],\r\n [\"^ (\\\\d+)-е (\\\\S+ье) [Шш]оссе \", \" $1-м $2м шоссе \"],\r\n\r\n [\" ([Тт])ретом \", \" $1ретьем \"],\r\n [\"([жч])ом \", \"$1ьем \"]\r\n ]\r\n }\r\n}\r\n\r\n }, {}],\r\n 27: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"meta\": {\r\n \"capitalizeFirstLetter\": true\r\n },\r\n \"v5\": {\r\n \"constants\": {\r\n \"ordinalize\": {\r\n \"1\": \"erste\",\r\n \"2\": \"zweite\",\r\n \"3\": \"dritte\",\r\n \"4\": \"vierte\",\r\n \"5\": \"fünfte\",\r\n \"6\": \"sechste\",\r\n \"7\": \"siebente\",\r\n \"8\": \"achte\",\r\n \"9\": \"neunte\",\r\n \"10\": \"zehnte\"\r\n },\r\n \"direction\": {\r\n \"north\": \"Norden\",\r\n \"northeast\": \"Nordosten\",\r\n \"east\": \"Osten\",\r\n \"southeast\": \"Südosten\",\r\n \"south\": \"Süden\",\r\n \"southwest\": \"Südwesten\",\r\n \"west\": \"Westen\",\r\n \"northwest\": \"Nordwesten\"\r\n },\r\n \"modifier\": {\r\n \"left\": \"links\",\r\n \"right\": \"rechts\",\r\n \"sharp left\": \"scharf links\",\r\n \"sharp right\": \"scharf rechts\",\r\n \"slight left\": \"leicht links\",\r\n \"slight right\": \"leicht rechts\",\r\n \"straight\": \"geradeaus\",\r\n \"uturn\": \"180°-Wendung\"\r\n },\r\n \"lanes\": {\r\n \"xo\": \"Rechts halten\",\r\n \"ox\": \"Links halten\",\r\n \"xox\": \"Mittlere Spur nutzen\",\r\n \"oxo\": \"Rechts oder links halten\"\r\n }\r\n },\r\n \"modes\": {\r\n \"ferry\": {\r\n \"default\": \"Fähre nehmen\",\r\n \"name\": \"Fähre nehmen {way_name}\",\r\n \"destination\": \"Fähre nehmen Richtung {destination}\"\r\n }\r\n },\r\n \"phrase\": {\r\n \"two linked by distance\": \"{instruction_one} danach in {distance} {instruction_two}\",\r\n \"two linked\": \"{instruction_one} danach {instruction_two}\",\r\n \"one in distance\": \"In {distance}, {instruction_one}\",\r\n \"name and ref\": \"{name} ({ref})\",\r\n \"exit with number\": \"exit {exit}\"\r\n },\r\n \"arrive\": {\r\n \"default\": {\r\n \"default\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"upcoming\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"short\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"short-upcoming\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"named\": \"Sie haben Ihr {waypoint_name}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Sie haben Ihr {nth} Ziel erreicht, es befindet sich links\",\r\n \"upcoming\": \"Sie haben Ihr {nth} Ziel erreicht, es befindet sich links\",\r\n \"short\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"short-upcoming\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"named\": \"Sie haben Ihr {waypoint_name}, es befindet sich links\"\r\n },\r\n \"right\": {\r\n \"default\": \"Sie haben Ihr {nth} Ziel erreicht, es befindet sich rechts\",\r\n \"upcoming\": \"Sie haben Ihr {nth} Ziel erreicht, es befindet sich rechts\",\r\n \"short\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"short-upcoming\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"named\": \"Sie haben Ihr {waypoint_name}, es befindet sich rechts\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Sie haben Ihr {nth} Ziel erreicht, es befindet sich links\",\r\n \"upcoming\": \"Sie haben Ihr {nth} Ziel erreicht, es befindet sich links\",\r\n \"short\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"short-upcoming\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"named\": \"Sie haben Ihr {waypoint_name}, es befindet sich links\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Sie haben Ihr {nth} Ziel erreicht, es befindet sich rechts\",\r\n \"upcoming\": \"Sie haben Ihr {nth} Ziel erreicht, es befindet sich rechts\",\r\n \"short\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"short-upcoming\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"named\": \"Sie haben Ihr {waypoint_name}, es befindet sich rechts\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Sie haben Ihr {nth} Ziel erreicht, es befindet sich rechts\",\r\n \"upcoming\": \"Sie haben Ihr {nth} Ziel erreicht, es befindet sich rechts\",\r\n \"short\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"short-upcoming\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"named\": \"Sie haben Ihr {waypoint_name}, es befindet sich rechts\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Sie haben Ihr {nth} Ziel erreicht, es befindet sich links\",\r\n \"upcoming\": \"Sie haben Ihr {nth} Ziel erreicht, es befindet sich links\",\r\n \"short\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"short-upcoming\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"named\": \"Sie haben Ihr {waypoint_name}, es befindet sich links\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Sie haben Ihr {nth} Ziel erreicht, es befindet sich geradeaus\",\r\n \"upcoming\": \"Sie haben Ihr {nth} Ziel erreicht, es befindet sich geradeaus\",\r\n \"short\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"short-upcoming\": \"Sie haben Ihr {nth} Ziel erreicht\",\r\n \"named\": \"Sie haben Ihr {waypoint_name}, es befindet sich geradeaus\"\r\n }\r\n },\r\n \"continue\": {\r\n \"default\": {\r\n \"default\": \"{modifier} abbiegen\",\r\n \"name\": \"{modifier} weiterfahren auf {way_name}\",\r\n \"destination\": \"{modifier} abbiegen Richtung {destination}\",\r\n \"exit\": \"{modifier} abbiegen auf {way_name}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Geradeaus weiterfahren\",\r\n \"name\": \"Geradeaus weiterfahren auf {way_name}\",\r\n \"destination\": \"Weiterfahren in Richtung {destination}\",\r\n \"distance\": \"Geradeaus weiterfahren für {distance}\",\r\n \"namedistance\": \"Geradeaus weiterfahren auf {way_name} für {distance}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Scharf links\",\r\n \"name\": \"Scharf links weiterfahren auf {way_name}\",\r\n \"destination\": \"Scharf links Richtung {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Scharf rechts\",\r\n \"name\": \"Scharf rechts weiterfahren auf {way_name}\",\r\n \"destination\": \"Scharf rechts Richtung {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Leicht links\",\r\n \"name\": \"Leicht links weiter auf {way_name}\",\r\n \"destination\": \"Leicht links weiter Richtung {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Leicht rechts weiter\",\r\n \"name\": \"Leicht rechts weiter auf {way_name}\",\r\n \"destination\": \"Leicht rechts weiter Richtung {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"180°-Wendung\",\r\n \"name\": \"180°-Wendung auf {way_name}\",\r\n \"destination\": \"180°-Wendung Richtung {destination}\"\r\n }\r\n },\r\n \"depart\": {\r\n \"default\": {\r\n \"default\": \"Fahren Sie Richtung {direction}\",\r\n \"name\": \"Fahren Sie Richtung {direction} auf {way_name}\",\r\n \"namedistance\": \"Fahren Sie Richtung {direction} auf {way_name} für {distance}\"\r\n }\r\n },\r\n \"end of road\": {\r\n \"default\": {\r\n \"default\": \"{modifier} abbiegen\",\r\n \"name\": \"{modifier} abbiegen auf {way_name}\",\r\n \"destination\": \"{modifier} abbiegen Richtung {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Geradeaus weiterfahren\",\r\n \"name\": \"Geradeaus weiterfahren auf {way_name}\",\r\n \"destination\": \"Geradeaus weiterfahren Richtung {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"180°-Wendung am Ende der Straße\",\r\n \"name\": \"180°-Wendung auf {way_name} am Ende der Straße\",\r\n \"destination\": \"180°-Wendung Richtung {destination} am Ende der Straße\"\r\n }\r\n },\r\n \"fork\": {\r\n \"default\": {\r\n \"default\": \"{modifier} halten an der Gabelung\",\r\n \"name\": \"{modifier} halten an der Gabelung auf {way_name}\",\r\n \"destination\": \"{modifier} halten an der Gabelung Richtung {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Links halten an der Gabelung\",\r\n \"name\": \"Links halten an der Gabelung auf {way_name}\",\r\n \"destination\": \"Links halten an der Gabelung Richtung {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Rechts halten an der Gabelung\",\r\n \"name\": \"Rechts halten an der Gabelung auf {way_name}\",\r\n \"destination\": \"Rechts halten an der Gabelung Richtung {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Scharf links abbiegen an der Gabelung\",\r\n \"name\": \"Scharf links auf {way_name}\",\r\n \"destination\": \"Scharf links Richtung {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Scharf rechts abbiegen an der Gabelung\",\r\n \"name\": \"Scharf rechts auf {way_name}\",\r\n \"destination\": \"Scharf rechts Richtung {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"180°-Wendung\",\r\n \"name\": \"180°-Wendung auf {way_name}\",\r\n \"destination\": \"180°-Wendung Richtung {destination}\"\r\n }\r\n },\r\n \"merge\": {\r\n \"default\": {\r\n \"default\": \"{modifier} auffahren\",\r\n \"name\": \"{modifier} auffahren auf {way_name}\",\r\n \"destination\": \"{modifier} auffahren Richtung {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"geradeaus auffahren\",\r\n \"name\": \"geradeaus auffahren auf {way_name}\",\r\n \"destination\": \"geradeaus auffahren Richtung {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Leicht links auffahren\",\r\n \"name\": \"Leicht links auffahren auf {way_name}\",\r\n \"destination\": \"Leicht links auffahren Richtung {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Leicht rechts auffahren\",\r\n \"name\": \"Leicht rechts auffahren auf {way_name}\",\r\n \"destination\": \"Leicht rechts auffahren Richtung {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Scharf links auffahren\",\r\n \"name\": \"Scharf links auffahren auf {way_name}\",\r\n \"destination\": \"Scharf links auffahren Richtung {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Scharf rechts auffahren\",\r\n \"name\": \"Scharf rechts auffahren auf {way_name}\",\r\n \"destination\": \"Scharf rechts auffahren Richtung {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"180°-Wendung\",\r\n \"name\": \"180°-Wendung auf {way_name}\",\r\n \"destination\": \"180°-Wendung Richtung {destination}\"\r\n }\r\n },\r\n \"new name\": {\r\n \"default\": {\r\n \"default\": \"{modifier} weiterfahren\",\r\n \"name\": \"{modifier} weiterfahren auf {way_name}\",\r\n \"destination\": \"{modifier} weiterfahren Richtung {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Geradeaus weiterfahren\",\r\n \"name\": \"Weiterfahren auf {way_name}\",\r\n \"destination\": \"Weiterfahren in Richtung {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Scharf links\",\r\n \"name\": \"Scharf links auf {way_name}\",\r\n \"destination\": \"Scharf links Richtung {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Scharf rechts\",\r\n \"name\": \"Scharf rechts auf {way_name}\",\r\n \"destination\": \"Scharf rechts Richtung {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Leicht links weiter\",\r\n \"name\": \"Leicht links weiter auf {way_name}\",\r\n \"destination\": \"Leicht links weiter Richtung {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Leicht rechts weiter\",\r\n \"name\": \"Leicht rechts weiter auf {way_name}\",\r\n \"destination\": \"Leicht rechts weiter Richtung {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"180°-Wendung\",\r\n \"name\": \"180°-Wendung auf {way_name}\",\r\n \"destination\": \"180°-Wendung Richtung {destination}\"\r\n }\r\n },\r\n \"notification\": {\r\n \"default\": {\r\n \"default\": \"{modifier} weiterfahren\",\r\n \"name\": \"{modifier} weiterfahren auf {way_name}\",\r\n \"destination\": \"{modifier} weiterfahren Richtung {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"180°-Wendung\",\r\n \"name\": \"180°-Wendung auf {way_name}\",\r\n \"destination\": \"180°-Wendung Richtung {destination}\"\r\n }\r\n },\r\n \"off ramp\": {\r\n \"default\": {\r\n \"default\": \"Ausfahrt nehmen\",\r\n \"name\": \"Ausfahrt nehmen auf {way_name}\",\r\n \"destination\": \"Ausfahrt nehmen Richtung {destination}\",\r\n \"exit\": \"Ausfahrt {exit} nehmen\",\r\n \"exit_destination\": \"Ausfahrt {exit} nehmen Richtung {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Ausfahrt links nehmen\",\r\n \"name\": \"Ausfahrt links nehmen auf {way_name}\",\r\n \"destination\": \"Ausfahrt links nehmen Richtung {destination}\",\r\n \"exit\": \"Ausfahrt {exit} links nehmen\",\r\n \"exit_destination\": \"Ausfahrt {exit} links nehmen Richtung {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Ausfahrt rechts nehmen\",\r\n \"name\": \"Ausfahrt rechts nehmen Richtung {way_name}\",\r\n \"destination\": \"Ausfahrt rechts nehmen Richtung {destination}\",\r\n \"exit\": \"Ausfahrt {exit} rechts nehmen\",\r\n \"exit_destination\": \"Ausfahrt {exit} nehmen Richtung {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Ausfahrt links nehmen\",\r\n \"name\": \"Ausfahrt links Seite nehmen auf {way_name}\",\r\n \"destination\": \"Ausfahrt links nehmen Richtung {destination}\",\r\n \"exit\": \"Ausfahrt {exit} links nehmen\",\r\n \"exit_destination\": \"Ausfahrt{exit} links nehmen Richtung {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Ausfahrt rechts nehmen\",\r\n \"name\": \"Ausfahrt rechts nehmen auf {way_name}\",\r\n \"destination\": \"Ausfahrt rechts nehmen Richtung {destination}\",\r\n \"exit\": \"Ausfahrt {exit} rechts nehmen\",\r\n \"exit_destination\": \"Ausfahrt {exit} nehmen Richtung {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Ausfahrt links nehmen\",\r\n \"name\": \"Ausfahrt links nehmen auf {way_name}\",\r\n \"destination\": \"Ausfahrt links nehmen Richtung {destination}\",\r\n \"exit\": \"Ausfahrt {exit} nehmen\",\r\n \"exit_destination\": \"Ausfahrt {exit} links nehmen Richtung {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Ausfahrt rechts nehmen\",\r\n \"name\": \"Ausfahrt rechts nehmen auf {way_name}\",\r\n \"destination\": \"Ausfahrt rechts nehmen Richtung {destination}\",\r\n \"exit\": \"Ausfahrt {exit} rechts nehmen\",\r\n \"exit_destination\": \"Ausfahrt {exit} nehmen Richtung {destination}\"\r\n }\r\n },\r\n \"on ramp\": {\r\n \"default\": {\r\n \"default\": \"Auffahrt nehmen\",\r\n \"name\": \"Auffahrt nehmen auf {way_name}\",\r\n \"destination\": \"Auffahrt nehmen Richtung {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Auffahrt links nehmen\",\r\n \"name\": \"Auffahrt links nehmen auf {way_name}\",\r\n \"destination\": \"Auffahrt links nehmen Richtung {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Auffahrt rechts nehmen\",\r\n \"name\": \"Auffahrt rechts nehmen auf {way_name}\",\r\n \"destination\": \"Auffahrt rechts nehmen Richtung {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Auffahrt links nehmen\",\r\n \"name\": \"Auffahrt links nehmen auf {way_name}\",\r\n \"destination\": \"Auffahrt links nehmen Richtung {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Auffahrt rechts nehmen\",\r\n \"name\": \"Auffahrt rechts nehmen auf {way_name}\",\r\n \"destination\": \"Auffahrt rechts nehmen Richtung {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Auffahrt links Seite nehmen\",\r\n \"name\": \"Auffahrt links nehmen auf {way_name}\",\r\n \"destination\": \"Auffahrt links nehmen Richtung {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Auffahrt rechts nehmen\",\r\n \"name\": \"Auffahrt rechts nehmen auf {way_name}\",\r\n \"destination\": \"Auffahrt rechts nehmen Richtung {destination}\"\r\n }\r\n },\r\n \"rotary\": {\r\n \"default\": {\r\n \"default\": {\r\n \"default\": \"In den Kreisverkehr fahren\",\r\n \"name\": \"Im Kreisverkehr die Ausfahrt auf {way_name} nehmen\",\r\n \"destination\": \"Im Kreisverkehr die Ausfahrt Richtung {destination} nehmen\"\r\n },\r\n \"name\": {\r\n \"default\": \"In {rotary_name} fahren\",\r\n \"name\": \"In {rotary_name} die Ausfahrt auf {way_name} nehmen\",\r\n \"destination\": \"In {rotary_name} die Ausfahrt Richtung {destination} nehmen\"\r\n },\r\n \"exit\": {\r\n \"default\": \"Im Kreisverkehr die {exit_number} Ausfahrt nehmen\",\r\n \"name\": \"Im Kreisverkehr die {exit_number} Ausfahrt nehmen auf {way_name}\",\r\n \"destination\": \"Im Kreisverkehr die {exit_number} Ausfahrt nehmen Richtung {destination}\"\r\n },\r\n \"name_exit\": {\r\n \"default\": \"In den Kreisverkehr fahren und {exit_number} Ausfahrt nehmen\",\r\n \"name\": \"In den Kreisverkehr fahren und {exit_number} Ausfahrt nehmen auf {way_name}\",\r\n \"destination\": \"In den Kreisverkehr fahren und {exit_number} Ausfahrt nehmen Richtung {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout\": {\r\n \"default\": {\r\n \"exit\": {\r\n \"default\": \"Im Kreisverkehr die {exit_number} Ausfahrt nehmen\",\r\n \"name\": \"Im Kreisverkehr die {exit_number} Ausfahrt nehmen auf {way_name}\",\r\n \"destination\": \"Im Kreisverkehr die {exit_number} Ausfahrt nehmen Richtung {destination}\"\r\n },\r\n \"default\": {\r\n \"default\": \"In den Kreisverkehr fahren\",\r\n \"name\": \"Im Kreisverkehr die Ausfahrt auf {way_name} nehmen\",\r\n \"destination\": \"Im Kreisverkehr die Ausfahrt Richtung {destination} nehmen\"\r\n }\r\n }\r\n },\r\n \"roundabout turn\": {\r\n \"default\": {\r\n \"default\": \"{modifier} abbiegen\",\r\n \"name\": \"{modifier} abbiegen auf {way_name}\",\r\n \"destination\": \"{modifier} abbiegen Richtung {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Links abbiegen\",\r\n \"name\": \"Links abbiegen auf {way_name}\",\r\n \"destination\": \"Links abbiegen Richtung {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Rechts abbiegen\",\r\n \"name\": \"Rechts abbiegen auf {way_name}\",\r\n \"destination\": \"Rechts abbiegen Richtung {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Geradeaus weiterfahren\",\r\n \"name\": \"Geradeaus weiterfahren auf {way_name}\",\r\n \"destination\": \"Geradeaus weiterfahren Richtung {destination}\"\r\n }\r\n },\r\n \"exit roundabout\": {\r\n \"default\": {\r\n \"default\": \"{modifier} abbiegen\",\r\n \"name\": \"{modifier} abbiegen auf {way_name}\",\r\n \"destination\": \"{modifier} abbiegen Richtung {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Links abbiegen\",\r\n \"name\": \"Links abbiegen auf {way_name}\",\r\n \"destination\": \"Links abbiegen Richtung {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Rechts abbiegen\",\r\n \"name\": \"Rechts abbiegen auf {way_name}\",\r\n \"destination\": \"Rechts abbiegen Richtung {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Geradeaus weiterfahren\",\r\n \"name\": \"Geradeaus weiterfahren auf {way_name}\",\r\n \"destination\": \"Geradeaus weiterfahren Richtung {destination}\"\r\n }\r\n },\r\n \"exit rotary\": {\r\n \"default\": {\r\n \"default\": \"{modifier} abbiegen\",\r\n \"name\": \"{modifier} abbiegen auf {way_name}\",\r\n \"destination\": \"{modifier} abbiegen Richtung {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Links abbiegen\",\r\n \"name\": \"Links abbiegen auf {way_name}\",\r\n \"destination\": \"Links abbiegen Richtung {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Rechts abbiegen\",\r\n \"name\": \"Rechts abbiegen auf {way_name}\",\r\n \"destination\": \"Rechts abbiegen Richtung {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Geradeaus weiterfahren\",\r\n \"name\": \"Geradeaus weiterfahren auf {way_name}\",\r\n \"destination\": \"Geradeaus weiterfahren Richtung {destination}\"\r\n }\r\n },\r\n \"turn\": {\r\n \"default\": {\r\n \"default\": \"{modifier} abbiegen\",\r\n \"name\": \"{modifier} abbiegen auf {way_name}\",\r\n \"destination\": \"{modifier} abbiegen Richtung {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Links abbiegen\",\r\n \"name\": \"Links abbiegen auf {way_name}\",\r\n \"destination\": \"Links abbiegen Richtung {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Rechts abbiegen\",\r\n \"name\": \"Rechts abbiegen auf {way_name}\",\r\n \"destination\": \"Rechts abbiegen Richtung {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Geradeaus weiterfahren\",\r\n \"name\": \"Geradeaus weiterfahren auf {way_name}\",\r\n \"destination\": \"Geradeaus weiterfahren Richtung {destination}\"\r\n }\r\n },\r\n \"use lane\": {\r\n \"no_lanes\": {\r\n \"default\": \"Geradeaus weiterfahren\"\r\n },\r\n \"default\": {\r\n \"default\": \"{lane_instruction}\"\r\n }\r\n }\r\n }\r\n}\r\n\r\n }, {}],\r\n 28: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"meta\": {\r\n \"capitalizeFirstLetter\": true\r\n },\r\n \"v5\": {\r\n \"constants\": {\r\n \"ordinalize\": {\r\n \"1\": \"1st\",\r\n \"2\": \"2nd\",\r\n \"3\": \"3rd\",\r\n \"4\": \"4th\",\r\n \"5\": \"5th\",\r\n \"6\": \"6th\",\r\n \"7\": \"7th\",\r\n \"8\": \"8th\",\r\n \"9\": \"9th\",\r\n \"10\": \"10th\"\r\n },\r\n \"direction\": {\r\n \"north\": \"north\",\r\n \"northeast\": \"northeast\",\r\n \"east\": \"east\",\r\n \"southeast\": \"southeast\",\r\n \"south\": \"south\",\r\n \"southwest\": \"southwest\",\r\n \"west\": \"west\",\r\n \"northwest\": \"northwest\"\r\n },\r\n \"modifier\": {\r\n \"left\": \"left\",\r\n \"right\": \"right\",\r\n \"sharp left\": \"sharp left\",\r\n \"sharp right\": \"sharp right\",\r\n \"slight left\": \"slight left\",\r\n \"slight right\": \"slight right\",\r\n \"straight\": \"straight\",\r\n \"uturn\": \"U-turn\"\r\n },\r\n \"lanes\": {\r\n \"xo\": \"Keep right\",\r\n \"ox\": \"Keep left\",\r\n \"xox\": \"Keep in the middle\",\r\n \"oxo\": \"Keep left or right\"\r\n }\r\n },\r\n \"modes\": {\r\n \"ferry\": {\r\n \"default\": \"Take the ferry\",\r\n \"name\": \"Take the ferry {way_name}\",\r\n \"destination\": \"Take the ferry towards {destination}\"\r\n }\r\n },\r\n \"phrase\": {\r\n \"two linked by distance\": \"{instruction_one}, then, in {distance}, {instruction_two}\",\r\n \"two linked\": \"{instruction_one}, then {instruction_two}\",\r\n \"one in distance\": \"In {distance}, {instruction_one}\",\r\n \"name and ref\": \"{name} ({ref})\",\r\n \"exit with number\": \"exit {exit}\"\r\n },\r\n \"arrive\": {\r\n \"default\": {\r\n \"default\": \"You have arrived at your {nth} destination\",\r\n \"upcoming\": \"You will arrive at your {nth} destination\",\r\n \"short\": \"You have arrived\",\r\n \"short-upcoming\": \"You will arrive\",\r\n \"named\": \"You have arrived at {waypoint_name}\"\r\n },\r\n \"left\": {\r\n \"default\": \"You have arrived at your {nth} destination, on the left\",\r\n \"upcoming\": \"You will arrive at your {nth} destination, on the left\",\r\n \"short\": \"You have arrived\",\r\n \"short-upcoming\": \"You will arrive\",\r\n \"named\": \"You have arrived at {waypoint_name}, on the left\"\r\n },\r\n \"right\": {\r\n \"default\": \"You have arrived at your {nth} destination, on the right\",\r\n \"upcoming\": \"You will arrive at your {nth} destination, on the right\",\r\n \"short\": \"You have arrived\",\r\n \"short-upcoming\": \"You will arrive\",\r\n \"named\": \"You have arrived at {waypoint_name}, on the right\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"You have arrived at your {nth} destination, on the left\",\r\n \"upcoming\": \"You will arrive at your {nth} destination, on the left\",\r\n \"short\": \"You have arrived\",\r\n \"short-upcoming\": \"You will arrive\",\r\n \"named\": \"You have arrived at {waypoint_name}, on the left\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"You have arrived at your {nth} destination, on the right\",\r\n \"upcoming\": \"You will arrive at your {nth} destination, on the right\",\r\n \"short\": \"You have arrived\",\r\n \"short-upcoming\": \"You will arrive\",\r\n \"named\": \"You have arrived at {waypoint_name}, on the right\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"You have arrived at your {nth} destination, on the right\",\r\n \"upcoming\": \"You will arrive at your {nth} destination, on the right\",\r\n \"short\": \"You have arrived\",\r\n \"short-upcoming\": \"You will arrive\",\r\n \"named\": \"You have arrived at {waypoint_name}, on the right\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"You have arrived at your {nth} destination, on the left\",\r\n \"upcoming\": \"You will arrive at your {nth} destination, on the left\",\r\n \"short\": \"You have arrived\",\r\n \"short-upcoming\": \"You will arrive\",\r\n \"named\": \"You have arrived at {waypoint_name}, on the left\"\r\n },\r\n \"straight\": {\r\n \"default\": \"You have arrived at your {nth} destination, straight ahead\",\r\n \"upcoming\": \"You will arrive at your {nth} destination, straight ahead\",\r\n \"short\": \"You have arrived\",\r\n \"short-upcoming\": \"You will arrive\",\r\n \"named\": \"You have arrived at {waypoint_name}, straight ahead\"\r\n }\r\n },\r\n \"continue\": {\r\n \"default\": {\r\n \"default\": \"Turn {modifier}\",\r\n \"name\": \"Turn {modifier} to stay on {way_name}\",\r\n \"destination\": \"Turn {modifier} towards {destination}\",\r\n \"exit\": \"Turn {modifier} onto {way_name}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continue straight\",\r\n \"name\": \"Continue straight to stay on {way_name}\",\r\n \"destination\": \"Continue towards {destination}\",\r\n \"distance\": \"Continue straight for {distance}\",\r\n \"namedistance\": \"Continue on {way_name} for {distance}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Make a sharp left\",\r\n \"name\": \"Make a sharp left to stay on {way_name}\",\r\n \"destination\": \"Make a sharp left towards {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Make a sharp right\",\r\n \"name\": \"Make a sharp right to stay on {way_name}\",\r\n \"destination\": \"Make a sharp right towards {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Make a slight left\",\r\n \"name\": \"Make a slight left to stay on {way_name}\",\r\n \"destination\": \"Make a slight left towards {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Make a slight right\",\r\n \"name\": \"Make a slight right to stay on {way_name}\",\r\n \"destination\": \"Make a slight right towards {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Make a U-turn\",\r\n \"name\": \"Make a U-turn and continue on {way_name}\",\r\n \"destination\": \"Make a U-turn towards {destination}\"\r\n }\r\n },\r\n \"depart\": {\r\n \"default\": {\r\n \"default\": \"Head {direction}\",\r\n \"name\": \"Head {direction} on {way_name}\",\r\n \"namedistance\": \"Head {direction} on {way_name} for {distance}\"\r\n }\r\n },\r\n \"end of road\": {\r\n \"default\": {\r\n \"default\": \"Turn {modifier}\",\r\n \"name\": \"Turn {modifier} onto {way_name}\",\r\n \"destination\": \"Turn {modifier} towards {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continue straight\",\r\n \"name\": \"Continue straight onto {way_name}\",\r\n \"destination\": \"Continue straight towards {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Make a U-turn at the end of the road\",\r\n \"name\": \"Make a U-turn onto {way_name} at the end of the road\",\r\n \"destination\": \"Make a U-turn towards {destination} at the end of the road\"\r\n }\r\n },\r\n \"fork\": {\r\n \"default\": {\r\n \"default\": \"Keep {modifier} at the fork\",\r\n \"name\": \"Keep {modifier} onto {way_name}\",\r\n \"destination\": \"Keep {modifier} towards {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Keep left at the fork\",\r\n \"name\": \"Keep left onto {way_name}\",\r\n \"destination\": \"Keep left towards {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Keep right at the fork\",\r\n \"name\": \"Keep right onto {way_name}\",\r\n \"destination\": \"Keep right towards {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Take a sharp left at the fork\",\r\n \"name\": \"Take a sharp left onto {way_name}\",\r\n \"destination\": \"Take a sharp left towards {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Take a sharp right at the fork\",\r\n \"name\": \"Take a sharp right onto {way_name}\",\r\n \"destination\": \"Take a sharp right towards {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Make a U-turn\",\r\n \"name\": \"Make a U-turn onto {way_name}\",\r\n \"destination\": \"Make a U-turn towards {destination}\"\r\n }\r\n },\r\n \"merge\": {\r\n \"default\": {\r\n \"default\": \"Merge {modifier}\",\r\n \"name\": \"Merge {modifier} onto {way_name}\",\r\n \"destination\": \"Merge {modifier} towards {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Merge\",\r\n \"name\": \"Merge onto {way_name}\",\r\n \"destination\": \"Merge towards {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Merge left\",\r\n \"name\": \"Merge left onto {way_name}\",\r\n \"destination\": \"Merge left towards {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Merge right\",\r\n \"name\": \"Merge right onto {way_name}\",\r\n \"destination\": \"Merge right towards {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Merge left\",\r\n \"name\": \"Merge left onto {way_name}\",\r\n \"destination\": \"Merge left towards {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Merge right\",\r\n \"name\": \"Merge right onto {way_name}\",\r\n \"destination\": \"Merge right towards {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Make a U-turn\",\r\n \"name\": \"Make a U-turn onto {way_name}\",\r\n \"destination\": \"Make a U-turn towards {destination}\"\r\n }\r\n },\r\n \"new name\": {\r\n \"default\": {\r\n \"default\": \"Continue {modifier}\",\r\n \"name\": \"Continue {modifier} onto {way_name}\",\r\n \"destination\": \"Continue {modifier} towards {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continue straight\",\r\n \"name\": \"Continue onto {way_name}\",\r\n \"destination\": \"Continue towards {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Take a sharp left\",\r\n \"name\": \"Take a sharp left onto {way_name}\",\r\n \"destination\": \"Take a sharp left towards {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Take a sharp right\",\r\n \"name\": \"Take a sharp right onto {way_name}\",\r\n \"destination\": \"Take a sharp right towards {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Continue slightly left\",\r\n \"name\": \"Continue slightly left onto {way_name}\",\r\n \"destination\": \"Continue slightly left towards {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Continue slightly right\",\r\n \"name\": \"Continue slightly right onto {way_name}\",\r\n \"destination\": \"Continue slightly right towards {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Make a U-turn\",\r\n \"name\": \"Make a U-turn onto {way_name}\",\r\n \"destination\": \"Make a U-turn towards {destination}\"\r\n }\r\n },\r\n \"notification\": {\r\n \"default\": {\r\n \"default\": \"Continue {modifier}\",\r\n \"name\": \"Continue {modifier} onto {way_name}\",\r\n \"destination\": \"Continue {modifier} towards {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Make a U-turn\",\r\n \"name\": \"Make a U-turn onto {way_name}\",\r\n \"destination\": \"Make a U-turn towards {destination}\"\r\n }\r\n },\r\n \"off ramp\": {\r\n \"default\": {\r\n \"default\": \"Take the ramp\",\r\n \"name\": \"Take the ramp onto {way_name}\",\r\n \"destination\": \"Take the ramp towards {destination}\",\r\n \"exit\": \"Take exit {exit}\",\r\n \"exit_destination\": \"Take exit {exit} towards {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Take the ramp on the left\",\r\n \"name\": \"Take the ramp on the left onto {way_name}\",\r\n \"destination\": \"Take the ramp on the left towards {destination}\",\r\n \"exit\": \"Take exit {exit} on the left\",\r\n \"exit_destination\": \"Take exit {exit} on the left towards {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Take the ramp on the right\",\r\n \"name\": \"Take the ramp on the right onto {way_name}\",\r\n \"destination\": \"Take the ramp on the right towards {destination}\",\r\n \"exit\": \"Take exit {exit} on the right\",\r\n \"exit_destination\": \"Take exit {exit} on the right towards {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Take the ramp on the left\",\r\n \"name\": \"Take the ramp on the left onto {way_name}\",\r\n \"destination\": \"Take the ramp on the left towards {destination}\",\r\n \"exit\": \"Take exit {exit} on the left\",\r\n \"exit_destination\": \"Take exit {exit} on the left towards {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Take the ramp on the right\",\r\n \"name\": \"Take the ramp on the right onto {way_name}\",\r\n \"destination\": \"Take the ramp on the right towards {destination}\",\r\n \"exit\": \"Take exit {exit} on the right\",\r\n \"exit_destination\": \"Take exit {exit} on the right towards {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Take the ramp on the left\",\r\n \"name\": \"Take the ramp on the left onto {way_name}\",\r\n \"destination\": \"Take the ramp on the left towards {destination}\",\r\n \"exit\": \"Take exit {exit} on the left\",\r\n \"exit_destination\": \"Take exit {exit} on the left towards {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Take the ramp on the right\",\r\n \"name\": \"Take the ramp on the right onto {way_name}\",\r\n \"destination\": \"Take the ramp on the right towards {destination}\",\r\n \"exit\": \"Take exit {exit} on the right\",\r\n \"exit_destination\": \"Take exit {exit} on the right towards {destination}\"\r\n }\r\n },\r\n \"on ramp\": {\r\n \"default\": {\r\n \"default\": \"Take the ramp\",\r\n \"name\": \"Take the ramp onto {way_name}\",\r\n \"destination\": \"Take the ramp towards {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Take the ramp on the left\",\r\n \"name\": \"Take the ramp on the left onto {way_name}\",\r\n \"destination\": \"Take the ramp on the left towards {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Take the ramp on the right\",\r\n \"name\": \"Take the ramp on the right onto {way_name}\",\r\n \"destination\": \"Take the ramp on the right towards {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Take the ramp on the left\",\r\n \"name\": \"Take the ramp on the left onto {way_name}\",\r\n \"destination\": \"Take the ramp on the left towards {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Take the ramp on the right\",\r\n \"name\": \"Take the ramp on the right onto {way_name}\",\r\n \"destination\": \"Take the ramp on the right towards {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Take the ramp on the left\",\r\n \"name\": \"Take the ramp on the left onto {way_name}\",\r\n \"destination\": \"Take the ramp on the left towards {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Take the ramp on the right\",\r\n \"name\": \"Take the ramp on the right onto {way_name}\",\r\n \"destination\": \"Take the ramp on the right towards {destination}\"\r\n }\r\n },\r\n \"rotary\": {\r\n \"default\": {\r\n \"default\": {\r\n \"default\": \"Enter the roundabout\",\r\n \"name\": \"Enter the roundabout and exit onto {way_name}\",\r\n \"destination\": \"Enter the roundabout and exit towards {destination}\"\r\n },\r\n \"name\": {\r\n \"default\": \"Enter {rotary_name}\",\r\n \"name\": \"Enter {rotary_name} and exit onto {way_name}\",\r\n \"destination\": \"Enter {rotary_name} and exit towards {destination}\"\r\n },\r\n \"exit\": {\r\n \"default\": \"Enter the roundabout and take the {exit_number} exit\",\r\n \"name\": \"Enter the roundabout and take the {exit_number} exit onto {way_name}\",\r\n \"destination\": \"Enter the roundabout and take the {exit_number} exit towards {destination}\"\r\n },\r\n \"name_exit\": {\r\n \"default\": \"Enter {rotary_name} and take the {exit_number} exit\",\r\n \"name\": \"Enter {rotary_name} and take the {exit_number} exit onto {way_name}\",\r\n \"destination\": \"Enter {rotary_name} and take the {exit_number} exit towards {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout\": {\r\n \"default\": {\r\n \"exit\": {\r\n \"default\": \"Enter the roundabout and take the {exit_number} exit\",\r\n \"name\": \"Enter the roundabout and take the {exit_number} exit onto {way_name}\",\r\n \"destination\": \"Enter the roundabout and take the {exit_number} exit towards {destination}\"\r\n },\r\n \"default\": {\r\n \"default\": \"Enter the roundabout\",\r\n \"name\": \"Enter the roundabout and exit onto {way_name}\",\r\n \"destination\": \"Enter the roundabout and exit towards {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout turn\": {\r\n \"default\": {\r\n \"default\": \"Make a {modifier}\",\r\n \"name\": \"Make a {modifier} onto {way_name}\",\r\n \"destination\": \"Make a {modifier} towards {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Turn left\",\r\n \"name\": \"Turn left onto {way_name}\",\r\n \"destination\": \"Turn left towards {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Turn right\",\r\n \"name\": \"Turn right onto {way_name}\",\r\n \"destination\": \"Turn right towards {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continue straight\",\r\n \"name\": \"Continue straight onto {way_name}\",\r\n \"destination\": \"Continue straight towards {destination}\"\r\n }\r\n },\r\n \"exit roundabout\": {\r\n \"default\": {\r\n \"default\": \"Exit the roundabout\",\r\n \"name\": \"Exit the roundabout onto {way_name}\",\r\n \"destination\": \"Exit the roundabout towards {destination}\"\r\n }\r\n },\r\n \"exit rotary\": {\r\n \"default\": {\r\n \"default\": \"Exit the roundabout\",\r\n \"name\": \"Exit the roundabout onto {way_name}\",\r\n \"destination\": \"Exit the roundabout towards {destination}\"\r\n }\r\n },\r\n \"turn\": {\r\n \"default\": {\r\n \"default\": \"Make a {modifier}\",\r\n \"name\": \"Make a {modifier} onto {way_name}\",\r\n \"destination\": \"Make a {modifier} towards {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Turn left\",\r\n \"name\": \"Turn left onto {way_name}\",\r\n \"destination\": \"Turn left towards {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Turn right\",\r\n \"name\": \"Turn right onto {way_name}\",\r\n \"destination\": \"Turn right towards {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Go straight\",\r\n \"name\": \"Go straight onto {way_name}\",\r\n \"destination\": \"Go straight towards {destination}\"\r\n }\r\n },\r\n \"use lane\": {\r\n \"no_lanes\": {\r\n \"default\": \"Continue straight\"\r\n },\r\n \"default\": {\r\n \"default\": \"{lane_instruction}\"\r\n }\r\n }\r\n }\r\n}\r\n\r\n }, {}],\r\n 30: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"meta\": {\r\n \"capitalizeFirstLetter\": true\r\n },\r\n \"v5\": {\r\n \"constants\": {\r\n \"ordinalize\": {\r\n \"1\": \"1ª\",\r\n \"2\": \"2ª\",\r\n \"3\": \"3ª\",\r\n \"4\": \"4ª\",\r\n \"5\": \"5ª\",\r\n \"6\": \"6ª\",\r\n \"7\": \"7ª\",\r\n \"8\": \"8ª\",\r\n \"9\": \"9ª\",\r\n \"10\": \"10ª\"\r\n },\r\n \"direction\": {\r\n \"north\": \"norte\",\r\n \"northeast\": \"noreste\",\r\n \"east\": \"este\",\r\n \"southeast\": \"sureste\",\r\n \"south\": \"sur\",\r\n \"southwest\": \"suroeste\",\r\n \"west\": \"oeste\",\r\n \"northwest\": \"noroeste\"\r\n },\r\n \"modifier\": {\r\n \"left\": \"a la izquierda\",\r\n \"right\": \"a la derecha\",\r\n \"sharp left\": \"cerrada a la izquierda\",\r\n \"sharp right\": \"cerrada a la derecha\",\r\n \"slight left\": \"ligeramente a la izquierda\",\r\n \"slight right\": \"ligeramente a la derecha\",\r\n \"straight\": \"recto\",\r\n \"uturn\": \"cambio de sentido\"\r\n },\r\n \"lanes\": {\r\n \"xo\": \"Mantente a la derecha\",\r\n \"ox\": \"Mantente a la izquierda\",\r\n \"xox\": \"Mantente en el medio\",\r\n \"oxo\": \"Mantente a la izquierda o a la derecha\"\r\n }\r\n },\r\n \"modes\": {\r\n \"ferry\": {\r\n \"default\": \"Coge el ferry\",\r\n \"name\": \"Coge el ferry {way_name}\",\r\n \"destination\": \"Coge el ferry hacia {destination}\"\r\n }\r\n },\r\n \"phrase\": {\r\n \"two linked by distance\": \"{instruction_one} y luego en {distance}, {instruction_two}\",\r\n \"two linked\": \"{instruction_one} y luego {instruction_two}\",\r\n \"one in distance\": \"A {distance}, {instruction_one}\",\r\n \"name and ref\": \"{name} ({ref})\",\r\n \"exit with number\": \"salida {exit}\"\r\n },\r\n \"arrive\": {\r\n \"default\": {\r\n \"default\": \"Has llegado a tu {nth} destino\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Has llegado a tu {nth} destino, a la izquierda\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino, a la izquierda\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}, a la izquierda\"\r\n },\r\n \"right\": {\r\n \"default\": \"Has llegado a tu {nth} destino, a la derecha\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino, a la derecha\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}, a la derecha\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Has llegado a tu {nth} destino, a la izquierda\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino, a la izquierda\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}, a la izquierda\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Has llegado a tu {nth} destino, a la derecha\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino, a la derecha\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}, a la derecha\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Has llegado a tu {nth} destino, a la derecha\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino, a la derecha\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}, a la derecha\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Has llegado a tu {nth} destino, a la izquierda\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino, a la izquierda\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}, a la izquierda\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Has llegado a tu {nth} destino, en frente\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino, en frente\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}, en frente\"\r\n }\r\n },\r\n \"continue\": {\r\n \"default\": {\r\n \"default\": \"Gire {modifier}\",\r\n \"name\": \"Cruce {modifier} en {way_name}\",\r\n \"destination\": \"Gire {modifier} hacia {destination}\",\r\n \"exit\": \"Gire {modifier} en {way_name}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continúa recto\",\r\n \"name\": \"Continúa en {way_name}\",\r\n \"destination\": \"Continúa hacia {destination}\",\r\n \"distance\": \"Continúa recto por {distance}\",\r\n \"namedistance\": \"Continúa recto en {way_name} por {distance}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Gire a la izquierda\",\r\n \"name\": \"Gire a la izquierda en {way_name}\",\r\n \"destination\": \"Gire a la izquierda hacia {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Gire a la derecha\",\r\n \"name\": \"Gire a la derecha en {way_name}\",\r\n \"destination\": \"Gire a la derecha hacia {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Gire a la izquierda\",\r\n \"name\": \"Doble levemente a la izquierda en {way_name}\",\r\n \"destination\": \"Gire a la izquierda hacia {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Gire a la izquierda\",\r\n \"name\": \"Doble levemente a la derecha en {way_name}\",\r\n \"destination\": \"Gire a la izquierda hacia {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Haz un cambio de sentido\",\r\n \"name\": \"Haz un cambio de sentido y continúa en {way_name}\",\r\n \"destination\": \"Haz un cambio de sentido hacia {destination}\"\r\n }\r\n },\r\n \"depart\": {\r\n \"default\": {\r\n \"default\": \"Dirígete al {direction}\",\r\n \"name\": \"Dirígete al {direction} por {way_name}\",\r\n \"namedistance\": \"Dirígete al {direction} en {way_name} por {distance}\"\r\n }\r\n },\r\n \"end of road\": {\r\n \"default\": {\r\n \"default\": \"Al final de la calle gira {modifier}\",\r\n \"name\": \"Al final de la calle gira {modifier} por {way_name}\",\r\n \"destination\": \"Al final de la calle gira {modifier} hacia {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Al final de la calle continúa recto\",\r\n \"name\": \"Al final de la calle continúa recto por {way_name}\",\r\n \"destination\": \"Al final de la calle continúa recto hacia {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Al final de la calle haz un cambio de sentido\",\r\n \"name\": \"Al final de la calle haz un cambio de sentido en {way_name}\",\r\n \"destination\": \"Al final de la calle haz un cambio de sentido hacia {destination}\"\r\n }\r\n },\r\n \"fork\": {\r\n \"default\": {\r\n \"default\": \"Mantente {modifier} en el cruce\",\r\n \"name\": \"Mantente {modifier} por {way_name}\",\r\n \"destination\": \"Mantente {modifier} hacia {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Mantente a la izquierda en el cruce\",\r\n \"name\": \"Mantente a la izquierda por {way_name}\",\r\n \"destination\": \"Mantente a la izquierda hacia {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Mantente a la derecha en el cruce\",\r\n \"name\": \"Mantente a la derecha por {way_name}\",\r\n \"destination\": \"Mantente a la derecha hacia {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Gira la izquierda en el cruce\",\r\n \"name\": \"Gira a la izquierda por {way_name}\",\r\n \"destination\": \"Gira a la izquierda hacia {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Gira a la derecha en el cruce\",\r\n \"name\": \"Gira a la derecha por {way_name}\",\r\n \"destination\": \"Gira a la derecha hacia {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Haz un cambio de sentido\",\r\n \"name\": \"Haz un cambio de sentido en {way_name}\",\r\n \"destination\": \"Haz un cambio de sentido hacia {destination}\"\r\n }\r\n },\r\n \"merge\": {\r\n \"default\": {\r\n \"default\": \"Incorpórate {modifier}\",\r\n \"name\": \"Incorpórate {modifier} por {way_name}\",\r\n \"destination\": \"Incorpórate {modifier} hacia {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Incorpórate\",\r\n \"name\": \"Incorpórate por {way_name}\",\r\n \"destination\": \"Incorpórate hacia {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Incorpórate a la izquierda\",\r\n \"name\": \"Incorpórate a la izquierda por {way_name}\",\r\n \"destination\": \"Incorpórate a la izquierda hacia {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Incorpórate a la derecha\",\r\n \"name\": \"Incorpórate a la derecha por {way_name}\",\r\n \"destination\": \"Incorpórate a la derecha hacia {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Incorpórate a la izquierda\",\r\n \"name\": \"Incorpórate a la izquierda por {way_name}\",\r\n \"destination\": \"Incorpórate a la izquierda hacia {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Incorpórate a la derecha\",\r\n \"name\": \"Incorpórate a la derecha por {way_name}\",\r\n \"destination\": \"Incorpórate a la derecha hacia {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Haz un cambio de sentido\",\r\n \"name\": \"Haz un cambio de sentido en {way_name}\",\r\n \"destination\": \"Haz un cambio de sentido hacia {destination}\"\r\n }\r\n },\r\n \"new name\": {\r\n \"default\": {\r\n \"default\": \"Continúa {modifier}\",\r\n \"name\": \"Continúa {modifier} por {way_name}\",\r\n \"destination\": \"Continúa {modifier} hacia {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continúa recto\",\r\n \"name\": \"Continúa por {way_name}\",\r\n \"destination\": \"Continúa hacia {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Gira a la izquierda\",\r\n \"name\": \"Gira a la izquierda por {way_name}\",\r\n \"destination\": \"Gira a la izquierda hacia {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Gira a la derecha\",\r\n \"name\": \"Gira a la derecha por {way_name}\",\r\n \"destination\": \"Gira a la derecha hacia {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Continúa ligeramente por la izquierda\",\r\n \"name\": \"Continúa ligeramente por la izquierda por {way_name}\",\r\n \"destination\": \"Continúa ligeramente por la izquierda hacia {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Continúa ligeramente por la derecha\",\r\n \"name\": \"Continúa ligeramente por la derecha por {way_name}\",\r\n \"destination\": \"Continúa ligeramente por la derecha hacia {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Haz un cambio de sentido\",\r\n \"name\": \"Haz un cambio de sentido en {way_name}\",\r\n \"destination\": \"Haz un cambio de sentido hacia {destination}\"\r\n }\r\n },\r\n \"notification\": {\r\n \"default\": {\r\n \"default\": \"Continúa {modifier}\",\r\n \"name\": \"Continúa {modifier} por {way_name}\",\r\n \"destination\": \"Continúa {modifier} hacia {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Haz un cambio de sentido\",\r\n \"name\": \"Haz un cambio de sentido en {way_name}\",\r\n \"destination\": \"Haz un cambio de sentido hacia {destination}\"\r\n }\r\n },\r\n \"off ramp\": {\r\n \"default\": {\r\n \"default\": \"Coge la cuesta abajo\",\r\n \"name\": \"Coge la cuesta abajo por {way_name}\",\r\n \"destination\": \"Coge la cuesta abajo hacia {destination}\",\r\n \"exit\": \"Coge la cuesta abajo {exit}\",\r\n \"exit_destination\": \"Coge la cuesta abajo {exit} hacia {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Coge la cuesta abajo de la izquierda\",\r\n \"name\": \"Coge la cuesta abajo de la izquierda por {way_name}\",\r\n \"destination\": \"Coge la cuesta abajo de la izquierda hacia {destination}\",\r\n \"exit\": \"Coge la cuesta abajo {exit} a tu izquierda\",\r\n \"exit_destination\": \"Coge la cuesta abajo {exit} a tu izquierda hacia {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Coge la cuesta abajo de la derecha\",\r\n \"name\": \"Coge la cuesta abajo de la derecha por {way_name}\",\r\n \"destination\": \"Coge la cuesta abajo de la derecha hacia {destination}\",\r\n \"exit\": \"Coge la cuesta abajo {exit}\",\r\n \"exit_destination\": \"Coge la cuesta abajo {exit} hacia {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Coge la cuesta abajo de la izquierda\",\r\n \"name\": \"Coge la cuesta abajo de la izquierda por {way_name}\",\r\n \"destination\": \"Coge la cuesta abajo de la izquierda hacia {destination}\",\r\n \"exit\": \"Coge la cuesta abajo {exit} a tu izquierda\",\r\n \"exit_destination\": \"Coge la cuesta abajo {exit} a tu izquierda hacia {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Coge la cuesta abajo de la derecha\",\r\n \"name\": \"Coge la cuesta abajo de la derecha por {way_name}\",\r\n \"destination\": \"Coge la cuesta abajo de la derecha hacia {destination}\",\r\n \"exit\": \"Coge la cuesta abajo {exit}\",\r\n \"exit_destination\": \"Coge la cuesta abajo {exit} hacia {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Coge la cuesta abajo de la izquierda\",\r\n \"name\": \"Coge la cuesta abajo de la izquierda por {way_name}\",\r\n \"destination\": \"Coge la cuesta abajo de la izquierda hacia {destination}\",\r\n \"exit\": \"Coge la cuesta abajo {exit} a tu izquierda\",\r\n \"exit_destination\": \"Coge la cuesta abajo {exit} a tu izquierda hacia {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Coge la cuesta abajo de la derecha\",\r\n \"name\": \"Coge la cuesta abajo de la derecha por {way_name}\",\r\n \"destination\": \"Coge la cuesta abajo de la derecha hacia {destination}\",\r\n \"exit\": \"Coge la cuesta abajo {exit}\",\r\n \"exit_destination\": \"Coge la cuesta abajo {exit} hacia {destination}\"\r\n }\r\n },\r\n \"on ramp\": {\r\n \"default\": {\r\n \"default\": \"Coge la cuesta\",\r\n \"name\": \"Coge la cuesta por {way_name}\",\r\n \"destination\": \"Coge la cuesta hacia {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Coge la cuesta de la izquierda\",\r\n \"name\": \"Coge la cuesta de la izquierda por {way_name}\",\r\n \"destination\": \"Coge la cuesta de la izquierda hacia {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Coge la cuesta de la derecha\",\r\n \"name\": \"Coge la cuesta de la derecha por {way_name}\",\r\n \"destination\": \"Coge la cuesta de la derecha hacia {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Coge la cuesta de la izquierda\",\r\n \"name\": \"Coge la cuesta de la izquierda por {way_name}\",\r\n \"destination\": \"Coge la cuesta de la izquierda hacia {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Coge la cuesta de la derecha\",\r\n \"name\": \"Coge la cuesta de la derecha por {way_name}\",\r\n \"destination\": \"Coge la cuesta de la derecha hacia {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Coge la cuesta de la izquierda\",\r\n \"name\": \"Coge la cuesta de la izquierda por {way_name}\",\r\n \"destination\": \"Coge la cuesta de la izquierda hacia {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Coge la cuesta de la derecha\",\r\n \"name\": \"Coge la cuesta de la derecha por {way_name}\",\r\n \"destination\": \"Coge la cuesta de la derecha hacia {destination}\"\r\n }\r\n },\r\n \"rotary\": {\r\n \"default\": {\r\n \"default\": {\r\n \"default\": \"Incorpórate en la rotonda\",\r\n \"name\": \"En la rotonda sal por {way_name}\",\r\n \"destination\": \"En la rotonda sal hacia {destination}\"\r\n },\r\n \"name\": {\r\n \"default\": \"En {rotary_name}\",\r\n \"name\": \"En {rotary_name} sal por {way_name}\",\r\n \"destination\": \"En {rotary_name} sal hacia {destination}\"\r\n },\r\n \"exit\": {\r\n \"default\": \"En la rotonda toma la {exit_number} salida\",\r\n \"name\": \"En la rotonda toma la {exit_number} salida por {way_name}\",\r\n \"destination\": \"En la rotonda toma la {exit_number} salida hacia {destination}\"\r\n },\r\n \"name_exit\": {\r\n \"default\": \"En {rotary_name} toma la {exit_number} salida\",\r\n \"name\": \"En {rotary_name} toma la {exit_number} salida por {way_name}\",\r\n \"destination\": \"En {rotary_name} toma la {exit_number} salida hacia {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout\": {\r\n \"default\": {\r\n \"exit\": {\r\n \"default\": \"En la rotonda toma la {exit_number} salida\",\r\n \"name\": \"En la rotonda toma la {exit_number} salida por {way_name}\",\r\n \"destination\": \"En la rotonda toma la {exit_number} salida hacia {destination}\"\r\n },\r\n \"default\": {\r\n \"default\": \"Incorpórate en la rotonda\",\r\n \"name\": \"Incorpórate en la rotonda y sal en {way_name}\",\r\n \"destination\": \"Incorpórate en la rotonda y sal hacia {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout turn\": {\r\n \"default\": {\r\n \"default\": \"Siga {modifier}\",\r\n \"name\": \"Siga {modifier} en {way_name}\",\r\n \"destination\": \"Siga {modifier} hacia {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Gire a la izquierda\",\r\n \"name\": \"Gire a la izquierda en {way_name}\",\r\n \"destination\": \"Gire a la izquierda hacia {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Gire a la derecha\",\r\n \"name\": \"Gire a la derecha en {way_name}\",\r\n \"destination\": \"Gire a la derecha hacia {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continúa recto\",\r\n \"name\": \"Continúa recto por {way_name}\",\r\n \"destination\": \"Continúa recto hacia {destination}\"\r\n }\r\n },\r\n \"exit roundabout\": {\r\n \"default\": {\r\n \"default\": \"Sal la rotonda\",\r\n \"name\": \"Toma la salida por {way_name}\",\r\n \"destination\": \"Toma la salida hacia {destination}\"\r\n }\r\n },\r\n \"exit rotary\": {\r\n \"default\": {\r\n \"default\": \"Sal la rotonda\",\r\n \"name\": \"Toma la salida por {way_name}\",\r\n \"destination\": \"Toma la salida hacia {destination}\"\r\n }\r\n },\r\n \"turn\": {\r\n \"default\": {\r\n \"default\": \"Gira {modifier}\",\r\n \"name\": \"Gira {modifier} por {way_name}\",\r\n \"destination\": \"Gira {modifier} hacia {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Gira a la izquierda\",\r\n \"name\": \"Gira a la izquierda por {way_name}\",\r\n \"destination\": \"Gira a la izquierda hacia {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Gira a la derecha\",\r\n \"name\": \"Gira a la derecha por {way_name}\",\r\n \"destination\": \"Gira a la derecha hacia {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continúa recto\",\r\n \"name\": \"Continúa recto por {way_name}\",\r\n \"destination\": \"Continúa recto hacia {destination}\"\r\n }\r\n },\r\n \"use lane\": {\r\n \"no_lanes\": {\r\n \"default\": \"Continúa recto\"\r\n },\r\n \"default\": {\r\n \"default\": \"{lane_instruction}\"\r\n }\r\n }\r\n }\r\n}\r\n\r\n }, {}],\r\n 31: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"meta\": {\r\n \"capitalizeFirstLetter\": true\r\n },\r\n \"v5\": {\r\n \"constants\": {\r\n \"ordinalize\": {\r\n \"1\": \"1ª\",\r\n \"2\": \"2ª\",\r\n \"3\": \"3ª\",\r\n \"4\": \"4ª\",\r\n \"5\": \"5ª\",\r\n \"6\": \"6ª\",\r\n \"7\": \"7ª\",\r\n \"8\": \"8ª\",\r\n \"9\": \"9ª\",\r\n \"10\": \"10ª\"\r\n },\r\n \"direction\": {\r\n \"north\": \"norte\",\r\n \"northeast\": \"noreste\",\r\n \"east\": \"este\",\r\n \"southeast\": \"sureste\",\r\n \"south\": \"sur\",\r\n \"southwest\": \"suroeste\",\r\n \"west\": \"oeste\",\r\n \"northwest\": \"noroeste\"\r\n },\r\n \"modifier\": {\r\n \"left\": \"izquierda\",\r\n \"right\": \"derecha\",\r\n \"sharp left\": \"cerrada a la izquierda\",\r\n \"sharp right\": \"cerrada a la derecha\",\r\n \"slight left\": \"levemente a la izquierda\",\r\n \"slight right\": \"levemente a la derecha\",\r\n \"straight\": \"recto\",\r\n \"uturn\": \"cambio de sentido\"\r\n },\r\n \"lanes\": {\r\n \"xo\": \"Mantente a la derecha\",\r\n \"ox\": \"Mantente a la izquierda\",\r\n \"xox\": \"Mantente en el medio\",\r\n \"oxo\": \"Mantente a la izquierda o derecha\"\r\n }\r\n },\r\n \"modes\": {\r\n \"ferry\": {\r\n \"default\": \"Coge el ferry\",\r\n \"name\": \"Coge el ferry {way_name}\",\r\n \"destination\": \"Coge el ferry a {destination}\"\r\n }\r\n },\r\n \"phrase\": {\r\n \"two linked by distance\": \"{instruction_one} y luego a {distance}, {instruction_two}\",\r\n \"two linked\": \"{instruction_one} y luego {instruction_two}\",\r\n \"one in distance\": \"A {distance}, {instruction_one}\",\r\n \"name and ref\": \"{name} ({ref})\",\r\n \"exit with number\": \"salida {exit}\"\r\n },\r\n \"arrive\": {\r\n \"default\": {\r\n \"default\": \"Has llegado a tu {nth} destino\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Has llegado a tu {nth} destino, a la izquierda\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino, a la izquierda\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}, a la izquierda\"\r\n },\r\n \"right\": {\r\n \"default\": \"Has llegado a tu {nth} destino, a la derecha\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino, a la derecha\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}, a la derecha\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Has llegado a tu {nth} destino, a la izquierda\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino, a la izquierda\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}, a la izquierda\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Has llegado a tu {nth} destino, a la derecha\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino, a la derecha\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}, a la derecha\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Has llegado a tu {nth} destino, a la derecha\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino, a la derecha\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}, a la derecha\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Has llegado a tu {nth} destino, a la izquierda\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino, a la izquierda\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}, a la izquierda\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Has llegado a tu {nth} destino, en frente\",\r\n \"upcoming\": \"Vas a llegar a tu {nth} destino, en frente\",\r\n \"short\": \"Has llegado\",\r\n \"short-upcoming\": \"Vas a llegar\",\r\n \"named\": \"Has llegado a {waypoint_name}, en frente\"\r\n }\r\n },\r\n \"continue\": {\r\n \"default\": {\r\n \"default\": \"Gira a {modifier}\",\r\n \"name\": \"Cruza a la{modifier} en {way_name}\",\r\n \"destination\": \"Gira a {modifier} hacia {destination}\",\r\n \"exit\": \"Gira a {modifier} en {way_name}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continúa recto\",\r\n \"name\": \"Continúa en {way_name}\",\r\n \"destination\": \"Continúa hacia {destination}\",\r\n \"distance\": \"Continúa recto por {distance}\",\r\n \"namedistance\": \"Continúa recto en {way_name} por {distance}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Gira a la izquierda\",\r\n \"name\": \"Gira a la izquierda en {way_name}\",\r\n \"destination\": \"Gira a la izquierda hacia {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Gira a la derecha\",\r\n \"name\": \"Gira a la derecha en {way_name}\",\r\n \"destination\": \"Gira a la derecha hacia {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Gira a la izquierda\",\r\n \"name\": \"Dobla levemente a la izquierda en {way_name}\",\r\n \"destination\": \"Gira a la izquierda hacia {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Gira a la izquierda\",\r\n \"name\": \"Dobla levemente a la derecha en {way_name}\",\r\n \"destination\": \"Gira a la izquierda hacia {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Haz un cambio de sentido\",\r\n \"name\": \"Haz un cambio de sentido y continúa en {way_name}\",\r\n \"destination\": \"Haz un cambio de sentido hacia {destination}\"\r\n }\r\n },\r\n \"depart\": {\r\n \"default\": {\r\n \"default\": \"Ve a {direction}\",\r\n \"name\": \"Ve a {direction} en {way_name}\",\r\n \"namedistance\": \"Ve a {direction} en {way_name} por {distance}\"\r\n }\r\n },\r\n \"end of road\": {\r\n \"default\": {\r\n \"default\": \"Gira a {modifier}\",\r\n \"name\": \"Gira a {modifier} en {way_name}\",\r\n \"destination\": \"Gira a {modifier} hacia {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continúa recto\",\r\n \"name\": \"Continúa recto en {way_name}\",\r\n \"destination\": \"Continúa recto hacia {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Haz un cambio de sentido al final de la via\",\r\n \"name\": \"Haz un cambio de sentido en {way_name} al final de la via\",\r\n \"destination\": \"Haz un cambio de sentido hacia {destination} al final de la via\"\r\n }\r\n },\r\n \"fork\": {\r\n \"default\": {\r\n \"default\": \"Mantente {modifier} en el cruza\",\r\n \"name\": \"Mantente {modifier} en {way_name}\",\r\n \"destination\": \"Mantente {modifier} hacia {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Mantente a la izquierda en el cruza\",\r\n \"name\": \"Mantente a la izquierda en {way_name}\",\r\n \"destination\": \"Mantente a la izquierda hacia {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Mantente a la derecha en el cruza\",\r\n \"name\": \"Mantente a la derecha en {way_name}\",\r\n \"destination\": \"Mantente a la derecha hacia {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Gira a la izquierda en el cruza\",\r\n \"name\": \"Gira a la izquierda en {way_name}\",\r\n \"destination\": \"Gira a la izquierda hacia {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Gira a la derecha en el cruza\",\r\n \"name\": \"Gira a la derecha en {way_name}\",\r\n \"destination\": \"Gira a la derecha hacia {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Haz un cambio de sentido\",\r\n \"name\": \"Haz un cambio de sentido en {way_name}\",\r\n \"destination\": \"Haz un cambio de sentido hacia {destination}\"\r\n }\r\n },\r\n \"merge\": {\r\n \"default\": {\r\n \"default\": \"Incorpórate a {modifier}\",\r\n \"name\": \"Incorpórate a {modifier} en {way_name}\",\r\n \"destination\": \"Incorpórate a {modifier} hacia {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Incorpórate\",\r\n \"name\": \"Incorpórate a {way_name}\",\r\n \"destination\": \"Incorpórate hacia {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Incorpórate a la izquierda\",\r\n \"name\": \"Incorpórate a la izquierda en {way_name}\",\r\n \"destination\": \"Incorpórate a la izquierda hacia {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Incorpórate a la derecha\",\r\n \"name\": \"Incorpórate a la derecha en {way_name}\",\r\n \"destination\": \"Incorpórate a la derecha hacia {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Incorpórate a la izquierda\",\r\n \"name\": \"Incorpórate a la izquierda en {way_name}\",\r\n \"destination\": \"Incorpórate a la izquierda hacia {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Incorpórate a la derecha\",\r\n \"name\": \"Incorpórate a la derecha en {way_name}\",\r\n \"destination\": \"Incorpórate a la derecha hacia {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Haz un cambio de sentido\",\r\n \"name\": \"Haz un cambio de sentido en {way_name}\",\r\n \"destination\": \"Haz un cambio de sentido hacia {destination}\"\r\n }\r\n },\r\n \"new name\": {\r\n \"default\": {\r\n \"default\": \"Continúa {modifier}\",\r\n \"name\": \"Continúa {modifier} en {way_name}\",\r\n \"destination\": \"Continúa {modifier} hacia {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continúa recto\",\r\n \"name\": \"Continúa en {way_name}\",\r\n \"destination\": \"Continúa hacia {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Gira a la izquierda\",\r\n \"name\": \"Gira a la izquierda en {way_name}\",\r\n \"destination\": \"Gira a la izquierda hacia {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Gira a la derecha\",\r\n \"name\": \"Gira a la derecha en {way_name}\",\r\n \"destination\": \"Gira a la derecha hacia {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Continúa levemente a la izquierda\",\r\n \"name\": \"Continúa levemente a la izquierda en {way_name}\",\r\n \"destination\": \"Continúa levemente a la izquierda hacia {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Continúa levemente a la derecha\",\r\n \"name\": \"Continúa levemente a la derecha en {way_name}\",\r\n \"destination\": \"Continúa levemente a la derecha hacia {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Haz un cambio de sentido\",\r\n \"name\": \"Haz un cambio de sentido en {way_name}\",\r\n \"destination\": \"Haz un cambio de sentido hacia {destination}\"\r\n }\r\n },\r\n \"notification\": {\r\n \"default\": {\r\n \"default\": \"Continúa {modifier}\",\r\n \"name\": \"Continúa {modifier} en {way_name}\",\r\n \"destination\": \"Continúa {modifier} hacia {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Haz un cambio de sentido\",\r\n \"name\": \"Haz un cambio de sentido en {way_name}\",\r\n \"destination\": \"Haz un cambio de sentido hacia {destination}\"\r\n }\r\n },\r\n \"off ramp\": {\r\n \"default\": {\r\n \"default\": \"Toma la salida\",\r\n \"name\": \"Toma la salida en {way_name}\",\r\n \"destination\": \"Toma la salida hacia {destination}\",\r\n \"exit\": \"Toma la salida {exit}\",\r\n \"exit_destination\": \"Toma la salida {exit} hacia {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Toma la salida en la izquierda\",\r\n \"name\": \"Toma la salida en la izquierda en {way_name}\",\r\n \"destination\": \"Toma la salida en la izquierda en {destination}\",\r\n \"exit\": \"Toma la salida {exit} en la izquierda\",\r\n \"exit_destination\": \"Toma la salida {exit} en la izquierda hacia {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Toma la salida en la derecha\",\r\n \"name\": \"Toma la salida en la derecha en {way_name}\",\r\n \"destination\": \"Toma la salida en la derecha hacia {destination}\",\r\n \"exit\": \"Toma la salida {exit} en la derecha\",\r\n \"exit_destination\": \"Toma la salida {exit} en la derecha hacia {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Ve cuesta abajo en la izquierda\",\r\n \"name\": \"Ve cuesta abajo en la izquierda en {way_name}\",\r\n \"destination\": \"Ve cuesta abajo en la izquierda hacia {destination}\",\r\n \"exit\": \"Toma la salida {exit} en la izquierda\",\r\n \"exit_destination\": \"Toma la salida {exit} en la izquierda hacia {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Ve cuesta abajo en la derecha\",\r\n \"name\": \"Ve cuesta abajo en la derecha en {way_name}\",\r\n \"destination\": \"Ve cuesta abajo en la derecha hacia {destination}\",\r\n \"exit\": \"Toma la salida {exit} en la derecha\",\r\n \"exit_destination\": \"Toma la salida {exit} en la derecha hacia {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Ve cuesta abajo en la izquierda\",\r\n \"name\": \"Ve cuesta abajo en la izquierda en {way_name}\",\r\n \"destination\": \"Ve cuesta abajo en la izquierda hacia {destination}\",\r\n \"exit\": \"Toma la salida {exit} en la izquierda\",\r\n \"exit_destination\": \"Toma la salida {exit} en la izquierda hacia {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Toma la salida en la derecha\",\r\n \"name\": \"Toma la salida en la derecha en {way_name}\",\r\n \"destination\": \"Toma la salida en la derecha hacia {destination}\",\r\n \"exit\": \"Toma la salida {exit} en la derecha\",\r\n \"exit_destination\": \"Toma la salida {exit} en la derecha hacia {destination}\"\r\n }\r\n },\r\n \"on ramp\": {\r\n \"default\": {\r\n \"default\": \"Toma la rampa\",\r\n \"name\": \"Toma la rampa en {way_name}\",\r\n \"destination\": \"Toma la rampa hacia {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Toma la rampa en la izquierda\",\r\n \"name\": \"Toma la rampa en la izquierda en {way_name}\",\r\n \"destination\": \"Toma la rampa en la izquierda hacia {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Toma la rampa en la derecha\",\r\n \"name\": \"Toma la rampa en la derecha en {way_name}\",\r\n \"destination\": \"Toma la rampa en la derecha hacia {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Toma la rampa en la izquierda\",\r\n \"name\": \"Toma la rampa en la izquierda en {way_name}\",\r\n \"destination\": \"Toma la rampa en la izquierda hacia {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Toma la rampa en la derecha\",\r\n \"name\": \"Toma la rampa en la derecha en {way_name}\",\r\n \"destination\": \"Toma la rampa en la derecha hacia {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Toma la rampa en la izquierda\",\r\n \"name\": \"Toma la rampa en la izquierda en {way_name}\",\r\n \"destination\": \"Toma la rampa en la izquierda hacia {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Toma la rampa en la derecha\",\r\n \"name\": \"Toma la rampa en la derecha en {way_name}\",\r\n \"destination\": \"Toma la rampa en la derecha hacia {destination}\"\r\n }\r\n },\r\n \"rotary\": {\r\n \"default\": {\r\n \"default\": {\r\n \"default\": \"Entra en la rotonda\",\r\n \"name\": \"Entra en la rotonda y sal en {way_name}\",\r\n \"destination\": \"Entra en la rotonda y sal hacia {destination}\"\r\n },\r\n \"name\": {\r\n \"default\": \"Entra en {rotary_name}\",\r\n \"name\": \"Entra en {rotary_name} y sal en {way_name}\",\r\n \"destination\": \"Entra en {rotary_name} y sal hacia {destination}\"\r\n },\r\n \"exit\": {\r\n \"default\": \"Entra en la rotonda y toma la {exit_number} salida\",\r\n \"name\": \"Entra en la rotonda y toma la {exit_number} salida a {way_name}\",\r\n \"destination\": \"Entra en la rotonda y toma la {exit_number} salida hacia {destination}\"\r\n },\r\n \"name_exit\": {\r\n \"default\": \"Entra en {rotary_name} y coge la {exit_number} salida\",\r\n \"name\": \"Entra en {rotary_name} y coge la {exit_number} salida en {way_name}\",\r\n \"destination\": \"Entra en {rotary_name} y coge la {exit_number} salida hacia {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout\": {\r\n \"default\": {\r\n \"exit\": {\r\n \"default\": \"Entra en la rotonda y toma la {exit_number} salida\",\r\n \"name\": \"Entra en la rotonda y toma la {exit_number} salida a {way_name}\",\r\n \"destination\": \"Entra en la rotonda y toma la {exit_number} salida hacia {destination}\"\r\n },\r\n \"default\": {\r\n \"default\": \"Entra en la rotonda\",\r\n \"name\": \"Entra en la rotonda y sal en {way_name}\",\r\n \"destination\": \"Entra en la rotonda y sal hacia {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout turn\": {\r\n \"default\": {\r\n \"default\": \"Sigue {modifier}\",\r\n \"name\": \"Sigue {modifier} en {way_name}\",\r\n \"destination\": \"Sigue {modifier} hacia {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Gira a la izquierda\",\r\n \"name\": \"Gira a la izquierda en {way_name}\",\r\n \"destination\": \"Gira a la izquierda hacia {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Gira a la derecha\",\r\n \"name\": \"Gira a la derecha en {way_name}\",\r\n \"destination\": \"Gira a la derecha hacia {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continúa recto\",\r\n \"name\": \"Continúa recto en {way_name}\",\r\n \"destination\": \"Continúa recto hacia {destination}\"\r\n }\r\n },\r\n \"exit roundabout\": {\r\n \"default\": {\r\n \"default\": \"Sal la rotonda\",\r\n \"name\": \"Sal la rotonda en {way_name}\",\r\n \"destination\": \"Sal la rotonda hacia {destination}\"\r\n }\r\n },\r\n \"exit rotary\": {\r\n \"default\": {\r\n \"default\": \"Sal la rotonda\",\r\n \"name\": \"Sal la rotonda en {way_name}\",\r\n \"destination\": \"Sal la rotonda hacia {destination}\"\r\n }\r\n },\r\n \"turn\": {\r\n \"default\": {\r\n \"default\": \"Sigue {modifier}\",\r\n \"name\": \"Sigue {modifier} en {way_name}\",\r\n \"destination\": \"Sigue {modifier} hacia {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Gira a la izquierda\",\r\n \"name\": \"Gira a la izquierda en {way_name}\",\r\n \"destination\": \"Gira a la izquierda hacia {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Gira a la derecha\",\r\n \"name\": \"Gira a la derecha en {way_name}\",\r\n \"destination\": \"Gira a la derecha hacia {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Ve recto\",\r\n \"name\": \"Ve recto en {way_name}\",\r\n \"destination\": \"Ve recto hacia {destination}\"\r\n }\r\n },\r\n \"use lane\": {\r\n \"no_lanes\": {\r\n \"default\": \"Continúa recto\"\r\n },\r\n \"default\": {\r\n \"default\": \"{lane_instruction}\"\r\n }\r\n }\r\n }\r\n}\r\n\r\n }, {}],\r\n 33: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"meta\": {\r\n \"capitalizeFirstLetter\": true\r\n },\r\n \"v5\": {\r\n \"constants\": {\r\n \"ordinalize\": {\r\n \"1\": \"première\",\r\n \"2\": \"seconde\",\r\n \"3\": \"troisième\",\r\n \"4\": \"quatrième\",\r\n \"5\": \"cinquième\",\r\n \"6\": \"sixième\",\r\n \"7\": \"septième\",\r\n \"8\": \"huitième\",\r\n \"9\": \"neuvième\",\r\n \"10\": \"dixième\"\r\n },\r\n \"direction\": {\r\n \"north\": \"le nord\",\r\n \"northeast\": \"le nord-est\",\r\n \"east\": \"l’est\",\r\n \"southeast\": \"le sud-est\",\r\n \"south\": \"le sud\",\r\n \"southwest\": \"le sud-ouest\",\r\n \"west\": \"l’ouest\",\r\n \"northwest\": \"le nord-ouest\"\r\n },\r\n \"modifier\": {\r\n \"left\": \"à gauche\",\r\n \"right\": \"à droite\",\r\n \"sharp left\": \"franchement à gauche\",\r\n \"sharp right\": \"franchement à droite\",\r\n \"slight left\": \"légèrement à gauche\",\r\n \"slight right\": \"légèrement à droite\",\r\n \"straight\": \"tout droit\",\r\n \"uturn\": \"demi-tour\"\r\n },\r\n \"lanes\": {\r\n \"xo\": \"Tenir la droite\",\r\n \"ox\": \"Tenir la gauche\",\r\n \"xox\": \"Rester au milieu\",\r\n \"oxo\": \"Tenir la gauche ou la droite\"\r\n }\r\n },\r\n \"modes\": {\r\n \"ferry\": {\r\n \"default\": \"Prendre le ferry\",\r\n \"name\": \"Prendre le ferry {way_name:article}\",\r\n \"destination\": \"Prendre le ferry en direction {destination:preposition}\"\r\n }\r\n },\r\n \"phrase\": {\r\n \"two linked by distance\": \"{instruction_one}, puis, dans {distance}, {instruction_two}\",\r\n \"two linked\": \"{instruction_one}, puis {instruction_two}\",\r\n \"one in distance\": \"Dans {distance}, {instruction_one}\",\r\n \"name and ref\": \"{name} ({ref})\",\r\n \"exit with number\": \"sortie n°{exit}\"\r\n },\r\n \"arrive\": {\r\n \"default\": {\r\n \"default\": \"Vous êtes arrivé à votre {nth} destination\",\r\n \"upcoming\": \"Vous arriverez à votre {nth} destination\",\r\n \"short\": \"Vous êtes arrivé\",\r\n \"short-upcoming\": \"Vous arriverez\",\r\n \"named\": \"Vous êtes arrivé {waypoint_name:arrival}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Vous êtes arrivé à votre {nth} destination, sur la gauche\",\r\n \"upcoming\": \"Vous arriverez à votre {nth} destination, sur la gauche\",\r\n \"short\": \"Vous êtes arrivé\",\r\n \"short-upcoming\": \"Vous arriverez\",\r\n \"named\": \"Vous êtes arrivé {waypoint_name:arrival}, sur la gauche\"\r\n },\r\n \"right\": {\r\n \"default\": \"Vous êtes arrivé à votre {nth} destination, sur la droite\",\r\n \"upcoming\": \"Vous arriverez à votre {nth} destination, sur la droite\",\r\n \"short\": \"Vous êtes arrivé\",\r\n \"short-upcoming\": \"Vous arriverez\",\r\n \"named\": \"Vous êtes arrivé {waypoint_name:arrival}, sur la droite\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Vous êtes arrivé à votre {nth} destination, sur la gauche\",\r\n \"upcoming\": \"Vous arriverez à votre {nth} destination, sur la gauche\",\r\n \"short\": \"Vous êtes arrivé\",\r\n \"short-upcoming\": \"Vous arriverez\",\r\n \"named\": \"Vous êtes arrivé {waypoint_name:arrival}, sur la gauche\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Vous êtes arrivé à votre {nth} destination, sur la droite\",\r\n \"upcoming\": \"Vous arriverez à votre {nth} destination, sur la droite\",\r\n \"short\": \"Vous êtes arrivé\",\r\n \"short-upcoming\": \"Vous arriverez\",\r\n \"named\": \"Vous êtes arrivé {waypoint_name:arrival}, sur la droite\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Vous êtes arrivé à votre {nth} destination, sur la droite\",\r\n \"upcoming\": \"Vous arriverez à votre {nth} destination, sur la droite\",\r\n \"short\": \"Vous êtes arrivé\",\r\n \"short-upcoming\": \"Vous arriverez\",\r\n \"named\": \"Vous êtes arrivé {waypoint_name:arrival}, sur la droite\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Vous êtes arrivé à votre {nth} destination, sur la gauche\",\r\n \"upcoming\": \"Vous arriverez à votre {nth} destination, sur la gauche\",\r\n \"short\": \"Vous êtes arrivé\",\r\n \"short-upcoming\": \"Vous êtes arrivé\",\r\n \"named\": \"Vous êtes arrivé {waypoint_name:arrival}, sur la gauche\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Vous êtes arrivé à votre {nth} destination, droit devant\",\r\n \"upcoming\": \"Vous arriverez à votre {nth} destination, droit devant\",\r\n \"short\": \"Vous êtes arrivé\",\r\n \"short-upcoming\": \"Vous êtes arrivé\",\r\n \"named\": \"Vous êtes arrivé {waypoint_name:arrival}, droit devant\"\r\n }\r\n },\r\n \"continue\": {\r\n \"default\": {\r\n \"default\": \"Tourner {modifier}\",\r\n \"name\": \"Tourner {modifier} pour rester sur {way_name:article}\",\r\n \"destination\": \"Tourner {modifier} en direction {destination:preposition}\",\r\n \"exit\": \"Tourner {modifier} sur {way_name:article}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continuer tout droit\",\r\n \"name\": \"Continuer tout droit pour rester sur {way_name:article}\",\r\n \"destination\": \"Continuer tout droit en direction {destination:preposition}\",\r\n \"distance\": \"Continuer tout droit sur {distance}\",\r\n \"namedistance\": \"Continuer sur {way_name:article} sur {distance}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Tourner franchement à gauche\",\r\n \"name\": \"Tourner franchement à gauche pour rester sur {way_name:article}\",\r\n \"destination\": \"Tourner franchement à gauche en direction {destination:preposition}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Tourner franchement à droite\",\r\n \"name\": \"Tourner franchement à droite pour rester sur {way_name:article}\",\r\n \"destination\": \"Tourner franchement à droite en direction {destination:preposition}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Tourner légèrement à gauche\",\r\n \"name\": \"Tourner légèrement à gauche pour rester sur {way_name:article}\",\r\n \"destination\": \"Tourner légèrement à gauche en direction {destination:preposition}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Tourner légèrement à droite\",\r\n \"name\": \"Tourner légèrement à droite pour rester sur {way_name:article}\",\r\n \"destination\": \"Tourner légèrement à droite en direction {destination:preposition}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faire demi-tour\",\r\n \"name\": \"Faire demi-tour et continuer sur {way_name:article}\",\r\n \"destination\": \"Faire demi-tour en direction {destination:preposition}\"\r\n }\r\n },\r\n \"depart\": {\r\n \"default\": {\r\n \"default\": \"Se diriger vers {direction}\",\r\n \"name\": \"Se diriger vers {direction} sur {way_name:article}\",\r\n \"namedistance\": \"Se diriger vers {direction} sur {way_name:article} sur {distance}\"\r\n }\r\n },\r\n \"end of road\": {\r\n \"default\": {\r\n \"default\": \"Tourner {modifier}\",\r\n \"name\": \"Tourner {modifier} sur {way_name:article}\",\r\n \"destination\": \"Tourner {modifier} en direction {destination:preposition}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continuer tout droit\",\r\n \"name\": \"Continuer tout droit sur {way_name:article}\",\r\n \"destination\": \"Continuer tout droit en direction {destination:preposition}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faire demi-tour à la fin de la route\",\r\n \"name\": \"Faire demi-tour à la fin {way_name:preposition}\",\r\n \"destination\": \"Faire demi-tour à la fin de la route en direction {destination:preposition}\"\r\n }\r\n },\r\n \"fork\": {\r\n \"default\": {\r\n \"default\": \"Tenir {modifier} à l’embranchement\",\r\n \"name\": \"Tenir {modifier} sur {way_name:article}\",\r\n \"destination\": \"Tenir {modifier} en direction {destination:preposition}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Tenir la gauche à l’embranchement\",\r\n \"name\": \"Tenir la gauche sur {way_name:article}\",\r\n \"destination\": \"Tenir la gauche en direction {destination:preposition}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Tenir la droite à l’embranchement\",\r\n \"name\": \"Tenir la droite sur {way_name:article}\",\r\n \"destination\": \"Tenir la droite en direction {destination:preposition}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Tourner franchement à gauche à l’embranchement\",\r\n \"name\": \"Tourner franchement à gauche sur {way_name:article}\",\r\n \"destination\": \"Tourner franchement à gauche en direction {destination:preposition}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Tourner franchement à droite à l’embranchement\",\r\n \"name\": \"Tourner franchement à droite sur {way_name:article}\",\r\n \"destination\": \"Tourner franchement à droite en direction {destination:preposition}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faire demi-tour\",\r\n \"name\": \"Faire demi-tour sur {way_name:article}\",\r\n \"destination\": \"Faire demi-tour en direction {destination:preposition}\"\r\n }\r\n },\r\n \"merge\": {\r\n \"default\": {\r\n \"default\": \"S’insérer {modifier}\",\r\n \"name\": \"S’insérer {modifier} sur {way_name:article}\",\r\n \"destination\": \"S’insérer {modifier} en direction {destination:preposition}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"S’insérer\",\r\n \"name\": \"S’insérer sur {way_name:article}\",\r\n \"destination\": \"S’insérer en direction {destination:preposition}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"S’insérer légèrement à gauche\",\r\n \"name\": \"S’insérer légèrement à gauche sur {way_name:article}\",\r\n \"destination\": \"S’insérer légèrement à gauche en direction {destination:preposition}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"S’insérer légèrement à droite\",\r\n \"name\": \"S’insérer légèrement à droite sur {way_name:article}\",\r\n \"destination\": \"S’insérer à droite en direction {destination:preposition}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"S’insérer à gauche\",\r\n \"name\": \"S’insérer à gauche sur {way_name:article}\",\r\n \"destination\": \"S’insérer à gauche en direction {destination:preposition}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"S’insérer à droite\",\r\n \"name\": \"S’insérer à droite sur {way_name:article}\",\r\n \"destination\": \"S’insérer à droite en direction {destination:preposition}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faire demi-tour\",\r\n \"name\": \"Faire demi-tour sur {way_name:article}\",\r\n \"destination\": \"Faire demi-tour en direction {destination:preposition}\"\r\n }\r\n },\r\n \"new name\": {\r\n \"default\": {\r\n \"default\": \"Continuer {modifier}\",\r\n \"name\": \"Continuer {modifier} sur {way_name:article}\",\r\n \"destination\": \"Continuer {modifier} en direction {destination:preposition}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continuer tout droit\",\r\n \"name\": \"Continuer tout droit sur {way_name:article}\",\r\n \"destination\": \"Continuer tout droit en direction {destination:preposition}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Tourner franchement à gauche\",\r\n \"name\": \"Tourner franchement à gauche sur {way_name:article}\",\r\n \"destination\": \"Tourner franchement à gauche en direction {destination:preposition}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Tourner franchement à droite\",\r\n \"name\": \"Tourner franchement à droite sur {way_name:article}\",\r\n \"destination\": \"Tourner franchement à droite en direction {destination:preposition}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Continuer légèrement à gauche\",\r\n \"name\": \"Continuer légèrement à gauche sur {way_name:article}\",\r\n \"destination\": \"Continuer légèrement à gauche en direction {destination:preposition}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Continuer légèrement à droite\",\r\n \"name\": \"Continuer légèrement à droite sur {way_name:article}\",\r\n \"destination\": \"Continuer légèrement à droite en direction {destination:preposition}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faire demi-tour\",\r\n \"name\": \"Faire demi-tour sur {way_name:article}\",\r\n \"destination\": \"Faire demi-tour en direction {destination:preposition}\"\r\n }\r\n },\r\n \"notification\": {\r\n \"default\": {\r\n \"default\": \"Continuer {modifier}\",\r\n \"name\": \"Continuer {modifier} sur {way_name:article}\",\r\n \"destination\": \"Continuer {modifier} en direction {destination:preposition}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faire demi-tour\",\r\n \"name\": \"Faire demi-tour sur {way_name:article}\",\r\n \"destination\": \"Faire demi-tour en direction {destination:preposition}\"\r\n }\r\n },\r\n \"off ramp\": {\r\n \"default\": {\r\n \"default\": \"Prendre la sortie\",\r\n \"name\": \"Prendre la sortie sur {way_name:article}\",\r\n \"destination\": \"Prendre la sortie en direction {destination:preposition}\",\r\n \"exit\": \"Prendre la sortie {exit}\",\r\n \"exit_destination\": \"Prendre la sortie {exit} en direction {destination:preposition}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Prendre la sortie à gauche\",\r\n \"name\": \"Prendre la sortie à gauche sur {way_name:article}\",\r\n \"destination\": \"Prendre la sortie à gauche en direction {destination:preposition}\",\r\n \"exit\": \"Prendre la sortie {exit} sur la gauche\",\r\n \"exit_destination\": \"Prendre la sortie {exit} sur la gauche en direction {destination:preposition}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Prendre la sortie à droite\",\r\n \"name\": \"Prendre la sortie à droite sur {way_name:article}\",\r\n \"destination\": \"Prendre la sortie à droite en direction {destination:preposition}\",\r\n \"exit\": \"Prendre la sortie {exit} sur la droite\",\r\n \"exit_destination\": \"Prendre la sortie {exit} sur la droite en direction {destination:preposition}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Prendre la sortie à gauche\",\r\n \"name\": \"Prendre la sortie à gauche sur {way_name:article}\",\r\n \"destination\": \"Prendre la sortie à gauche en direction {destination:preposition}\",\r\n \"exit\": \"Prendre la sortie {exit} sur la gauche\",\r\n \"exit_destination\": \"Prendre la sortie {exit} sur la gauche en direction {destination:preposition}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Prendre la sortie à droite\",\r\n \"name\": \"Prendre la sortie à droite sur {way_name:article}\",\r\n \"destination\": \"Prendre la sortie à droite en direction {destination:preposition}\",\r\n \"exit\": \"Prendre la sortie {exit} sur la droite\",\r\n \"exit_destination\": \"Prendre la sortie {exit} sur la droite en direction {destination:preposition}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Prendre la sortie à gauche\",\r\n \"name\": \"Prendre la sortie à gauche sur {way_name:article}\",\r\n \"destination\": \"Prendre la sortie à gauche en direction {destination:preposition}\",\r\n \"exit\": \"Prendre la sortie {exit} sur la gauche\",\r\n \"exit_destination\": \"Prendre la sortie {exit} sur la gauche en direction {destination:preposition}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Prendre la sortie à droite\",\r\n \"name\": \"Prendre la sortie à droite sur {way_name:article}\",\r\n \"destination\": \"Prendre la sortie à droite en direction {destination:preposition}\",\r\n \"exit\": \"Prendre la sortie {exit} sur la droite\",\r\n \"exit_destination\": \"Prendre la sortie {exit} sur la droite en direction {destination:preposition}\"\r\n }\r\n },\r\n \"on ramp\": {\r\n \"default\": {\r\n \"default\": \"Prendre la sortie\",\r\n \"name\": \"Prendre la sortie sur {way_name:article}\",\r\n \"destination\": \"Prendre la sortie en direction {destination:preposition}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Prendre la sortie à gauche\",\r\n \"name\": \"Prendre la sortie à gauche sur {way_name:article}\",\r\n \"destination\": \"Prendre la sortie à gauche en direction {destination:preposition}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Prendre la sortie à droite\",\r\n \"name\": \"Prendre la sortie à droite sur {way_name:article}\",\r\n \"destination\": \"Prendre la sortie à droite en direction {destination:preposition}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Prendre la sortie à gauche\",\r\n \"name\": \"Prendre la sortie à gauche sur {way_name:article}\",\r\n \"destination\": \"Prendre la sortie à gauche en direction {destination:preposition}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Prendre la sortie à droite\",\r\n \"name\": \"Prendre la sortie à droite sur {way_name:article}\",\r\n \"destination\": \"Prendre la sortie à droite en direction {destination:preposition}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Prendre la sortie à gauche\",\r\n \"name\": \"Prendre la sortie à gauche sur {way_name:article}\",\r\n \"destination\": \"Prendre la sortie à gauche en direction {destination:preposition}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Prendre la sortie à droite\",\r\n \"name\": \"Prendre la sortie à droite sur {way_name:article}\",\r\n \"destination\": \"Prendre la sortie à droite en direction {destination:preposition}\"\r\n }\r\n },\r\n \"rotary\": {\r\n \"default\": {\r\n \"default\": {\r\n \"default\": \"Prendre le rond-point\",\r\n \"name\": \"Prendre le rond-point, puis sortir sur {way_name:article}\",\r\n \"destination\": \"Prendre le rond-point, puis sortir en direction {destination:preposition}\"\r\n },\r\n \"name\": {\r\n \"default\": \"Prendre {rotary_name:rotary}\",\r\n \"name\": \"Prendre {rotary_name:rotary}, puis sortir par {way_name:article}\",\r\n \"destination\": \"Prendre {rotary_name:rotary}, puis sortir en direction {destination:preposition}\"\r\n },\r\n \"exit\": {\r\n \"default\": \"Prendre le rond-point, puis la {exit_number} sortie\",\r\n \"name\": \"Prendre le rond-point, puis la {exit_number} sortie sur {way_name:article}\",\r\n \"destination\": \"Prendre le rond-point, puis la {exit_number} sortie en direction {destination:preposition}\"\r\n },\r\n \"name_exit\": {\r\n \"default\": \"Prendre {rotary_name:rotary}, puis la {exit_number} sortie\",\r\n \"name\": \"Prendre {rotary_name:rotary}, puis la {exit_number} sortie sur {way_name:article}\",\r\n \"destination\": \"Prendre {rotary_name:rotary}, puis la {exit_number} sortie en direction {destination:preposition}\"\r\n }\r\n }\r\n },\r\n \"roundabout\": {\r\n \"default\": {\r\n \"exit\": {\r\n \"default\": \"Prendre le rond-point, puis la {exit_number} sortie\",\r\n \"name\": \"Prendre le rond-point, puis la {exit_number} sortie sur {way_name:article}\",\r\n \"destination\": \"Prendre le rond-point, puis la {exit_number} sortie en direction {destination:preposition}\"\r\n },\r\n \"default\": {\r\n \"default\": \"Prendre le rond-point\",\r\n \"name\": \"Prendre le rond-point, puis sortir sur {way_name:article}\",\r\n \"destination\": \"Prendre le rond-point, puis sortir en direction {destination:preposition}\"\r\n }\r\n }\r\n },\r\n \"roundabout turn\": {\r\n \"default\": {\r\n \"default\": \"Tourner {modifier}\",\r\n \"name\": \"Tourner {modifier} sur {way_name:article}\",\r\n \"destination\": \"Tourner {modifier} en direction {destination:preposition}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Tourner à gauche\",\r\n \"name\": \"Tourner à gauche sur {way_name:article}\",\r\n \"destination\": \"Tourner à gauche en direction {destination:preposition}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Tourner à droite\",\r\n \"name\": \"Tourner à droite sur {way_name:article}\",\r\n \"destination\": \"Tourner à droite en direction {destination:preposition}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continuer tout droit\",\r\n \"name\": \"Continuer tout droit sur {way_name:article}\",\r\n \"destination\": \"Continuer tout droit en direction {destination:preposition}\"\r\n }\r\n },\r\n \"exit roundabout\": {\r\n \"default\": {\r\n \"default\": \"Sortir du rond-point\",\r\n \"name\": \"Sortir du rond-point sur {way_name:article}\",\r\n \"destination\": \"Sortir du rond-point en direction {destination:preposition}\"\r\n }\r\n },\r\n \"exit rotary\": {\r\n \"default\": {\r\n \"default\": \"Sortir du rond-point\",\r\n \"name\": \"Sortir du rond-point sur {way_name:article}\",\r\n \"destination\": \"Sortir du rond-point en direction {destination:preposition}\"\r\n }\r\n },\r\n \"turn\": {\r\n \"default\": {\r\n \"default\": \"Tourner {modifier}\",\r\n \"name\": \"Tourner {modifier} sur {way_name:article}\",\r\n \"destination\": \"Tourner {modifier} en direction {destination:preposition}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Tourner à gauche\",\r\n \"name\": \"Tourner à gauche sur {way_name:article}\",\r\n \"destination\": \"Tourner à gauche en direction {destination:preposition}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Tourner à droite\",\r\n \"name\": \"Tourner à droite sur {way_name:article}\",\r\n \"destination\": \"Tourner à droite en direction {destination:preposition}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Aller tout droit\",\r\n \"name\": \"Aller tout droit sur {way_name:article}\",\r\n \"destination\": \"Aller tout droit en direction {destination:preposition}\"\r\n }\r\n },\r\n \"use lane\": {\r\n \"no_lanes\": {\r\n \"default\": \"Continuer tout droit\"\r\n },\r\n \"default\": {\r\n \"default\": \"{lane_instruction}\"\r\n }\r\n }\r\n }\r\n}\r\n\r\n }, {}],\r\n 37: [function (_dereq_, module, exports) {\r\n module.exports = {\r\n \"meta\": {\r\n \"capitalizeFirstLetter\": true\r\n },\r\n \"v5\": {\r\n \"constants\": {\r\n \"ordinalize\": {\r\n \"1\": \"1ª\",\r\n \"2\": \"2ª\",\r\n \"3\": \"3ª\",\r\n \"4\": \"4ª\",\r\n \"5\": \"5ª\",\r\n \"6\": \"6ª\",\r\n \"7\": \"7ª\",\r\n \"8\": \"8ª\",\r\n \"9\": \"9ª\",\r\n \"10\": \"10ª\"\r\n },\r\n \"direction\": {\r\n \"north\": \"nord\",\r\n \"northeast\": \"nord-est\",\r\n \"east\": \"est\",\r\n \"southeast\": \"sud-est\",\r\n \"south\": \"sud\",\r\n \"southwest\": \"sud-ovest\",\r\n \"west\": \"ovest\",\r\n \"northwest\": \"nord-ovest\"\r\n },\r\n \"modifier\": {\r\n \"left\": \"sinistra\",\r\n \"right\": \"destra\",\r\n \"sharp left\": \"sinistra\",\r\n \"sharp right\": \"destra\",\r\n \"slight left\": \"sinistra leggermente\",\r\n \"slight right\": \"destra leggermente\",\r\n \"straight\": \"dritto\",\r\n \"uturn\": \"inversione a U\"\r\n },\r\n \"lanes\": {\r\n \"xo\": \"Mantieni la destra\",\r\n \"ox\": \"Mantieni la sinistra\",\r\n \"xox\": \"Rimani in mezzo\",\r\n \"oxo\": \"Mantieni la destra o la sinistra\"\r\n }\r\n },\r\n \"modes\": {\r\n \"ferry\": {\r\n \"default\": \"Prendi il traghetto\",\r\n \"name\": \"Prendi il traghetto {way_name}\",\r\n \"destination\": \"Prendi il traghetto verso {destination}\"\r\n }\r\n },\r\n \"phrase\": {\r\n \"two linked by distance\": \"{instruction_one}, poi tra {distance},{instruction_two}\",\r\n \"two linked\": \"{instruction_one}, poi {instruction_two}\",\r\n \"one in distance\": \"tra {distance} {instruction_one}\",\r\n \"name and ref\": \"{name} ({ref})\",\r\n \"exit with number\": \"exit {exit}\"\r\n },\r\n \"arrive\": {\r\n \"default\": {\r\n \"default\": \"Sei arrivato alla tua {nth} destinazione\",\r\n \"upcoming\": \"Sei arrivato alla tua {nth} destinazione\",\r\n \"short\": \"Sei arrivato alla tua {nth} destinazione\",\r\n \"short-upcoming\": \"Arriverai\",\r\n \"named\": \"Sei arrivato in {waypoint_name}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Sei arrivato alla tua {nth} destinazione, sulla sinistra\",\r\n \"upcoming\": \"Sei arrivato alla tua {nth} destinazione, sulla sinistra\",\r\n \"short\": \"Sei arrivato\",\r\n \"short-upcoming\": \"Arriverai\",\r\n \"named\": \"Sei arrivato in {waypoint_name}, sulla sinistra\"\r\n },\r\n \"right\": {\r\n \"default\": \"Sei arrivato alla tua {nth} destinazione, sulla destra\",\r\n \"upcoming\": \"Sei arrivato alla tua {nth} destinazione, sulla destra\",\r\n \"short\": \"Sei arrivato\",\r\n \"short-upcoming\": \"Arriverai\",\r\n \"named\": \"Sei arrivato in {waypoint_name}, sulla destra\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Sei arrivato alla tua {nth} destinazione, sulla sinistra\",\r\n \"upcoming\": \"Sei arrivato alla tua {nth} destinazione, sulla sinistra\",\r\n \"short\": \"Sei arrivato\",\r\n \"short-upcoming\": \"Arriverai\",\r\n \"named\": \"Sei arrivato in {waypoint_name}, sulla sinistra\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Sei arrivato alla tua {nth} destinazione, sulla destra\",\r\n \"upcoming\": \"Sei arrivato alla tua {nth} destinazione, sulla destra\",\r\n \"short\": \"Sei arrivato\",\r\n \"short-upcoming\": \"Arriverai\",\r\n \"named\": \"Sei arrivato in {waypoint_name}, sulla destra\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Sei arrivato alla tua {nth} destinazione, sulla destra\",\r\n \"upcoming\": \"Sei arrivato alla tua {nth} destinazione, sulla destra\",\r\n \"short\": \"Sei arrivato\",\r\n \"short-upcoming\": \"Arriverai\",\r\n \"named\": \"Sei arrivato in {waypoint_name}, sulla destra\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Sei arrivato alla tua {nth} destinazione, sulla sinistra\",\r\n \"upcoming\": \"Sei arrivato alla tua {nth} destinazione, sulla sinistra\",\r\n \"short\": \"Sei arrivato\",\r\n \"short-upcoming\": \"Arriverai\",\r\n \"named\": \"Sei arrivato in {waypoint_name}, sulla sinistra\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Sei arrivato alla tua {nth} destinazione, si trova davanti a te\",\r\n \"upcoming\": \"Sei arrivato alla tua {nth} destinazione, si trova davanti a te\",\r\n \"short\": \"Sei arrivato\",\r\n \"short-upcoming\": \"Arriverai\",\r\n \"named\": \"Sei arrivato a {waypoint_name}, si trova davanti a te\"\r\n }\r\n },\r\n \"continue\": {\r\n \"default\": {\r\n \"default\": \"Gira a {modifier}\",\r\n \"name\": \"Gira a {modifier} per stare su {way_name}\",\r\n \"destination\": \"Gira a {modifier} verso {destination}\",\r\n \"exit\": \"Gira a {modifier} in {way_name}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continua dritto\",\r\n \"name\": \"Continua dritto per stare su {way_name}\",\r\n \"destination\": \"Continua verso {destination}\",\r\n \"distance\": \"Continua dritto per {distance}\",\r\n \"namedistance\": \"Continua su {way_name} per {distance}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Svolta a sinistra\",\r\n \"name\": \"Fai una curva stretta a sinistra per stare su {way_name}\",\r\n \"destination\": \"Svolta a sinistra verso {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Svolta a destra\",\r\n \"name\": \"Fai una curva stretta a destra per stare su {way_name}\",\r\n \"destination\": \"Svolta a destra verso {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Fai una leggera curva a sinistra\",\r\n \"name\": \"Fai una leggera curva a sinistra per stare su {way_name}\",\r\n \"destination\": \"Fai una leggera curva a sinistra verso {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Fai una leggera curva a destra\",\r\n \"name\": \"Fai una leggera curva a destra per stare su {way_name}\",\r\n \"destination\": \"Fai una leggera curva a destra verso {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Fai un’inversione a U\",\r\n \"name\": \"Fai un’inversione ad U poi continua su {way_name}\",\r\n \"destination\": \"Fai un’inversione a U verso {destination}\"\r\n }\r\n },\r\n \"depart\": {\r\n \"default\": {\r\n \"default\": \"Continua verso {direction}\",\r\n \"name\": \"Continua verso {direction} in {way_name}\",\r\n \"namedistance\": \"Head {direction} on {way_name} for {distance}\"\r\n }\r\n },\r\n \"end of road\": {\r\n \"default\": {\r\n \"default\": \"Gira a {modifier}\",\r\n \"name\": \"Gira a {modifier} in {way_name}\",\r\n \"destination\": \"Gira a {modifier} verso {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continua dritto\",\r\n \"name\": \"Continua dritto in {way_name}\",\r\n \"destination\": \"Continua dritto verso {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Fai un’inversione a U alla fine della strada\",\r\n \"name\": \"Fai un’inversione a U in {way_name} alla fine della strada\",\r\n \"destination\": \"Fai un’inversione a U verso {destination} alla fine della strada\"\r\n }\r\n },\r\n \"fork\": {\r\n \"default\": {\r\n \"default\": \"Mantieni la {modifier} al bivio\",\r\n \"name\": \"Mantieni la {modifier} al bivio in {way_name}\",\r\n \"destination\": \"Mantieni la {modifier} al bivio verso {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Mantieni la sinistra al bivio\",\r\n \"name\": \"Mantieni la sinistra al bivio in {way_name}\",\r\n \"destination\": \"Mantieni la sinistra al bivio verso {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Mantieni la destra al bivio\",\r\n \"name\": \"Mantieni la destra al bivio in {way_name}\",\r\n \"destination\": \"Mantieni la destra al bivio verso {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Svolta a sinistra al bivio\",\r\n \"name\": \"Svolta a sinistra in {way_name}\",\r\n \"destination\": \"Svolta a sinistra verso {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Svolta a destra al bivio\",\r\n \"name\": \"Svolta a destra in {way_name}\",\r\n \"destination\": \"Svolta a destra verso {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Fai un’inversione a U\",\r\n \"name\": \"Fai un’inversione a U in {way_name}\",\r\n \"destination\": \"Fai un’inversione a U verso {destination}\"\r\n }\r\n },\r\n \"merge\": {\r\n \"default\": {\r\n \"default\": \"Immettiti a {modifier}\",\r\n \"name\": \"Immettiti {modifier} in {way_name}\",\r\n \"destination\": \"Immettiti {modifier} verso {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Immettiti a dritto\",\r\n \"name\": \"Immettiti dritto in {way_name}\",\r\n \"destination\": \"Immettiti dritto verso {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Immèttitì a sinistra\",\r\n \"name\": \"Immèttitì a sinistra in {way_name}\",\r\n \"destination\": \"Immèttitì a sinistra verso {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Immèttitì a destra\",\r\n \"name\": \"Immèttitì a destra in {way_name}\",\r\n \"destination\": \"Immèttitì a destra verso {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Immèttitì a sinistra\",\r\n \"name\": \"Immèttitì a sinistra in {way_name}\",\r\n \"destination\": \"Immèttitì a sinistra verso {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Immèttitì a destra\",\r\n \"name\": \"Immèttitì a destra in {way_name}\",\r\n \"destination\": \"Immèttitì a destra verso {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Fai un’inversione a U\",\r\n \"name\": \"Fai un’inversione a U in {way_name}\",\r\n \"destination\": \"Fai un’inversione a U verso {destination}\"\r\n }\r\n },\r\n \"new name\": {\r\n \"default\": {\r\n \"default\": \"Continua a {modifier}\",\r\n \"name\": \"Continua a {modifier} in {way_name}\",\r\n \"destination\": \"Continua a {modifier} verso {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continua dritto\",\r\n \"name\": \"Continua in {way_name}\",\r\n \"destination\": \"Continua verso {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Svolta a sinistra\",\r\n \"name\": \"Svolta a sinistra in {way_name}\",\r\n \"destination\": \"Svolta a sinistra verso {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Svolta a destra\",\r\n \"name\": \"Svolta a destra in {way_name}\",\r\n \"destination\": \"Svolta a destra verso {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Continua leggermente a sinistra\",\r\n \"name\": \"Continua leggermente a sinistra in {way_name}\",\r\n \"destination\": \"Continua leggermente a sinistra verso {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Continua leggermente a destra\",\r\n \"name\": \"Continua leggermente a destra in {way_name} \",\r\n \"destination\": \"Continua leggermente a destra verso {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Fai un’inversione a U\",\r\n \"name\": \"Fai un’inversione a U in {way_name}\",\r\n \"destination\": \"Fai un’inversione a U verso {destination}\"\r\n }\r\n },\r\n \"notification\": {\r\n \"default\": {\r\n \"default\": \"Continua a {modifier}\",\r\n \"name\": \"Continua a {modifier} in {way_name}\",\r\n \"destination\": \"Continua a {modifier} verso {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Fai un’inversione a U\",\r\n \"name\": \"Fai un’inversione a U in {way_name}\",\r\n \"destination\": \"Fai un’inversione a U verso {destination}\"\r\n }\r\n },\r\n \"off ramp\": {\r\n \"default\": {\r\n \"default\": \"Prendi la rampa\",\r\n \"name\": \"Prendi la rampa in {way_name}\",\r\n \"destination\": \"Prendi la rampa verso {destination}\",\r\n \"exit\": \"Prendi l’uscita {exit}\",\r\n \"exit_destination\": \"Prendi l’uscita {exit} verso {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Prendi la rampa a sinistra\",\r\n \"name\": \"Prendi la rampa a sinistra in {way_name}\",\r\n \"destination\": \"Prendi la rampa a sinistra verso {destination}\",\r\n \"exit\": \"Prendi l’uscita {exit} a sinistra\",\r\n \"exit_destination\": \"Prendi la {exit} uscita a sinistra verso {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Prendi la rampa a destra\",\r\n \"name\": \"Prendi la rampa a destra in {way_name}\",\r\n \"destination\": \"Prendi la rampa a destra verso {destination}\",\r\n \"exit\": \"Prendi la {exit} uscita a destra\",\r\n \"exit_destination\": \"Prendi la {exit} uscita a destra verso {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Prendi la rampa a sinistra\",\r\n \"name\": \"Prendi la rampa a sinistra in {way_name}\",\r\n \"destination\": \"Prendi la rampa a sinistra verso {destination}\",\r\n \"exit\": \"Prendi l’uscita {exit} a sinistra\",\r\n \"exit_destination\": \"Prendi la {exit} uscita a sinistra verso {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Prendi la rampa a destra\",\r\n \"name\": \"Prendi la rampa a destra in {way_name}\",\r\n \"destination\": \"Prendi la rampa a destra verso {destination}\",\r\n \"exit\": \"Prendi la {exit} uscita a destra\",\r\n \"exit_destination\": \"Prendi la {exit} uscita a destra verso {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Prendi la rampa a sinistra\",\r\n \"name\": \"Prendi la rampa a sinistra in {way_name}\",\r\n \"destination\": \"Prendi la rampa a sinistra verso {destination}\",\r\n \"exit\": \"Prendi l’uscita {exit} a sinistra\",\r\n \"exit_destination\": \"Prendi la {exit} uscita a sinistra verso {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Prendi la rampa a destra\",\r\n \"name\": \"Prendi la rampa a destra in {way_name}\",\r\n \"destination\": \"Prendi la rampa a destra verso {destination}\",\r\n \"exit\": \"Prendi la {exit} uscita a destra\",\r\n \"exit_destination\": \"Prendi la {exit} uscita a destra verso {destination}\"\r\n }\r\n },\r\n \"on ramp\": {\r\n \"default\": {\r\n \"default\": \"Prendi la rampa\",\r\n \"name\": \"Prendi la rampa in {way_name}\",\r\n \"destination\": \"Prendi la rampa verso {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Prendi la rampa a sinistra\",\r\n \"name\": \"Prendi la rampa a sinistra in {way_name}\",\r\n \"destination\": \"Prendi la rampa a sinistra verso {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Prendi la rampa a destra\",\r\n \"name\": \"Prendi la rampa a destra in {way_name}\",\r\n \"destination\": \"Prendi la rampa a destra verso {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Prendi la rampa a sinistra\",\r\n \"name\": \"Prendi la rampa a sinistra in {way_name}\",\r\n \"destination\": \"Prendi la rampa a sinistra verso {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Prendi la rampa a destra\",\r\n \"name\": \"Prendi la rampa a destra in {way_name}\",\r\n \"destination\": \"Prendi la rampa a destra verso {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Prendi la rampa a sinistra\",\r\n \"name\": \"Prendi la rampa a sinistra in {way_name}\",\r\n \"destination\": \"Prendi la rampa a sinistra verso {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Prendi la rampa a destra\",\r\n \"name\": \"Prendi la rampa a destra in {way_name}\",\r\n \"destination\": \"Prendi la rampa a destra verso {destination}\"\r\n }\r\n },\r\n \"rotary\": {\r\n \"default\": {\r\n \"default\": {\r\n \"default\": \"Immettiti nella rotonda\",\r\n \"name\": \"Immettiti nella ritonda ed esci in {way_name}\",\r\n \"destination\": \"Immettiti nella ritonda ed esci verso {destination}\"\r\n },\r\n \"name\": {\r\n \"default\": \"Immèttitì in {rotary_name}\",\r\n \"name\": \"Immèttitì in {rotary_name} ed esci su {way_name}\",\r\n \"destination\": \"Immèttitì in {rotary_name} ed esci verso {destination}\"\r\n },\r\n \"exit\": {\r\n \"default\": \"Immettiti nella rotonda e prendi la {exit_number} uscita\",\r\n \"name\": \"Immettiti nella rotonda e prendi la {exit_number} uscita in {way_name}\",\r\n \"destination\": \"Immettiti nella rotonda e prendi la {exit_number} uscita verso {destination}\"\r\n },\r\n \"name_exit\": {\r\n \"default\": \"Immèttitì in {rotary_name} e prendi la {exit_number} uscita\",\r\n \"name\": \"Immèttitì in {rotary_name} e prendi la {exit_number} uscita in {way_name}\",\r\n \"destination\": \"Immèttitì in {rotary_name} e prendi la {exit_number} uscita verso {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout\": {\r\n \"default\": {\r\n \"exit\": {\r\n \"default\": \"Immettiti nella rotonda e prendi la {exit_number} uscita\",\r\n \"name\": \"Immettiti nella rotonda e prendi la {exit_number} uscita in {way_name}\",\r\n \"destination\": \"Immettiti nella rotonda e prendi la {exit_number} uscita verso {destination}\"\r\n },\r\n \"default\": {\r\n \"default\": \"Entra nella rotonda\",\r\n \"name\": \"Entra nella rotonda e prendi l’uscita in {way_name}\",\r\n \"destination\": \"Entra nella rotonda e prendi l’uscita verso {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout turn\": {\r\n \"default\": {\r\n \"default\": \"Gira a {modifier}\",\r\n \"name\": \"Gira a {modifier} in {way_name}\",\r\n \"destination\": \"Gira a {modifier} verso {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Svolta a sinistra\",\r\n \"name\": \"Svolta a sinistra in {way_name}\",\r\n \"destination\": \"Svolta a sinistra verso {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Gira a destra\",\r\n \"name\": \"Svolta a destra in {way_name}\",\r\n \"destination\": \"Svolta a destra verso {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continua dritto\",\r\n \"name\": \"Continua dritto in {way_name}\",\r\n \"destination\": \"Continua dritto verso {destination}\"\r\n }\r\n },\r\n \"exit roundabout\": {\r\n \"default\": {\r\n \"default\": \"Fai una {modifier}\",\r\n \"name\": \"Fai una {modifier} in {way_name}\",\r\n \"destination\": \"Fai una {modifier} verso {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Svolta a sinistra\",\r\n \"name\": \"Svolta a sinistra in {way_name}\",\r\n \"destination\": \"Svolta a sinistra verso {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Gira a destra\",\r\n \"name\": \"Svolta a destra in {way_name}\",\r\n \"destination\": \"Svolta a destra verso {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continua dritto\",\r\n \"name\": \"Continua dritto in {way_name}\",\r\n \"destination\": \"Continua dritto verso {destination}\"\r\n }\r\n },\r\n \"exit rotary\": {\r\n \"default\": {\r\n \"default\": \"Fai una {modifier}\",\r\n \"name\": \"Fai una {modifier} in {way_name}\",\r\n \"destination\": \"Fai una {modifier} verso {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Svolta a sinistra\",\r\n \"name\": \"Svolta a sinistra in {way_name}\",\r\n \"destination\": \"Svolta a sinistra verso {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Gira a destra\",\r\n \"name\": \"Svolta a destra in {way_name}\",\r\n \"destination\": \"Svolta a destra verso {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Prosegui dritto\",\r\n \"name\": \"Continua su {way_name}\",\r\n \"destination\": \"Continua verso {destination}\"\r\n }\r\n },\r\n \"turn\": {\r\n \"default\": {\r\n \"default\": \"Gira a {modifier}\",\r\n \"name\": \"Gira a {modifier} in {way_name}\",\r\n \"destination\": \"Gira a {modifier} verso {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Svolta a sinistra\",\r\n \"name\": \"Svolta a sinistra in {way_name}\",\r\n \"destination\": \"Svolta a sinistra verso {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Gira a destra\",\r\n \"name\": \"Svolta a destra in {way_name}\",\r\n \"destination\": \"Svolta a destra verso {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Prosegui dritto\",\r\n \"name\": \"Continua su {way_name}\",\r\n \"destination\": \"Continua verso {destination}\"\r\n }\r\n },\r\n \"use lane\": {\r\n \"no_lanes\": {\r\n \"default\": \"Continua dritto\"\r\n },\r\n \"default\": {\r\n \"default\": \"{lane_instruction}\"\r\n }\r\n }\r\n }\r\n }\r\n }, {}],\r\n 38: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"meta\": {\r\n \"capitalizeFirstLetter\": false\r\n },\r\n \"v5\": {\r\n \"constants\": {\r\n \"ordinalize\": {\r\n \"1\": \"1 つ目の\",\r\n \"2\": \"2 つ目の\",\r\n \"3\": \"3 つ目の\",\r\n \"4\": \"4 つ目の\",\r\n \"5\": \"5 つ目の\",\r\n \"6\": \"6 つ目の\",\r\n \"7\": \"7 つ目の\",\r\n \"8\": \"8 つ目の\",\r\n \"9\": \"9 つ目の\",\r\n \"10\": \"10 番目の\"\r\n },\r\n \"direction\": {\r\n \"north\": \"北\",\r\n \"northeast\": \"北東\",\r\n \"east\": \"東\",\r\n \"southeast\": \"南東\",\r\n \"south\": \"南\",\r\n \"southwest\": \"南西\",\r\n \"west\": \"西\",\r\n \"northwest\": \"北西\"\r\n },\r\n \"modifier\": {\r\n \"left\": \"左\",\r\n \"right\": \"右\",\r\n \"sharp left\": \"大きく左\",\r\n \"sharp right\": \"大きく右\",\r\n \"slight left\": \"斜め左\",\r\n \"slight right\": \"斜め右\",\r\n \"straight\": \"直進\",\r\n \"uturn\": \"Uターン\"\r\n },\r\n \"lanes\": {\r\n \"xo\": \"右車線を進む\",\r\n \"ox\": \"左車線を進む\",\r\n \"xox\": \"真ん中の車線を進む\",\r\n \"oxo\": \"左車線もしくは右車線を進む\"\r\n }\r\n },\r\n \"modes\": {\r\n \"ferry\": {\r\n \"default\": \"フェリーに乗る\",\r\n \"name\": \"{way_name}のフェリーに乗る\",\r\n \"destination\": \"{destination}に向けてフェリーに乗る\"\r\n }\r\n },\r\n \"phrase\": {\r\n \"two linked by distance\": \"{instruction_one} その先 {distance}で{instruction_two}\",\r\n \"two linked\": \"{instruction_one} その先 {instruction_two}\",\r\n \"one in distance\": \"{distance}先、{instruction_one}\",\r\n \"name and ref\": \"{name}({ref})\",\r\n \"exit with number\": \"{exit}出口です\"\r\n },\r\n \"arrive\": {\r\n \"default\": {\r\n \"default\": \"{nth}目の目的地に到着しました\",\r\n \"upcoming\": \"まもなく{nth}目の目的地に到着します\",\r\n \"short\": \"目的地に到着しました\",\r\n \"short-upcoming\": \"まもなく目的地に到着します\",\r\n \"named\": \"{waypoint_name}に到着しました\"\r\n },\r\n \"left\": {\r\n \"default\": \"{nth}目の目的地に到着しました。目的地は左側です\",\r\n \"upcoming\": \"まもなく{nth}目の目的地に到着します。目的地は左側です\",\r\n \"short\": \"目的地に到着しました\",\r\n \"short-upcoming\": \"まもなく目的地に到着します\",\r\n \"named\": \"{waypoint_name}に到着しました。目的地は左側です\"\r\n },\r\n \"right\": {\r\n \"default\": \"{nth}目の目的地に到着しました。目的地は右側です\",\r\n \"upcoming\": \"まもなく{nth}目の目的地に到着します。目的地は右側です\",\r\n \"short\": \"目的地に到着しました\",\r\n \"short-upcoming\": \"まもなく目的地に到着します\",\r\n \"named\": \"{waypoint_name}に到着しました。目的地は右側です\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"{nth}目の目的地に到着しました。目的地は左側です\",\r\n \"upcoming\": \"{nth}目の目的地に到着しました。目的地は左側です\",\r\n \"short\": \"目的地に到着しました\",\r\n \"short-upcoming\": \"まもなく目的地に到着します\",\r\n \"named\": \"{waypoint_name}に到着しました。目的地は左側です\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"{nth}目の目的地に到着しました。目的地は右側です\",\r\n \"upcoming\": \"まもなく{nth}目の目的地に到着します。目的地は右側です\",\r\n \"short\": \"目的地に到着しました\",\r\n \"short-upcoming\": \"まもなく目的地に到着します\",\r\n \"named\": \"{waypoint_name}に到着しました。目的地は右側です\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"{nth}目の目的地に到着しました。目的地は右側です\",\r\n \"upcoming\": \"まもなく{nth}目の目的地に到着します。目的地は右側です\",\r\n \"short\": \"目的地に到着しました\",\r\n \"short-upcoming\": \"まもなく目的地に到着します\",\r\n \"named\": \"{waypoint_name}に到着しました。目的地は右側です\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"{nth}目の目的地に到着しました。目的地は左側です\",\r\n \"upcoming\": \"{nth}目の目的地に到着しました。目的地は左側です\",\r\n \"short\": \"目的地に到着しました\",\r\n \"short-upcoming\": \"まもなく目的地に到着します\",\r\n \"named\": \"{waypoint_name}に到着しました。目的地は左側です\"\r\n },\r\n \"straight\": {\r\n \"default\": \"{nth}目の目的地に到着しました。目的地は前方です\",\r\n \"upcoming\": \"まもなく{nth}目の目的地に到着します。目的地は前方です\",\r\n \"short\": \"目的地に到着しました\",\r\n \"short-upcoming\": \"まもなく目的地に到着します\",\r\n \"named\": \"{waypoint_name}に到着しました。目的地は前方です\"\r\n }\r\n },\r\n \"continue\": {\r\n \"default\": {\r\n \"default\": \"{modifier}に曲がる\",\r\n \"name\": \"{modifier}に曲がり、{way_name}を進む\",\r\n \"destination\": \"{modifier}に曲がり、{destination}へ向かう\",\r\n \"exit\": \"{modifier}に曲がり、{way_name}を進む\"\r\n },\r\n \"straight\": {\r\n \"default\": \"直進する\",\r\n \"name\": \"{way_name}を直進する\",\r\n \"destination\": \"{destination}に向けて直進する\",\r\n \"distance\": \"{distance}直進する\",\r\n \"namedistance\": \"{way_name}を{distance}直進する\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"大きく左に曲がる\",\r\n \"name\": \"大きく左に曲がり、{way_name}を進む\",\r\n \"destination\": \"大きく左に曲がり、{destination}へ向かう\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"大きく右に曲がる\",\r\n \"name\": \"大きく右に曲がり、{way_name}を進む\",\r\n \"destination\": \"大きく右に曲がり、{destination}へ向かう\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"斜め左に曲がる\",\r\n \"name\": \"斜め左に曲がり、{way_name}を進む\",\r\n \"destination\": \"斜め左に曲がり、{destination}へ向かう\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"斜め右に曲がる\",\r\n \"name\": \"斜め右に曲がり、{way_name}を進む\",\r\n \"destination\": \"斜め右に曲がり、{destination}へ向かう\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Uターンする\",\r\n \"name\": \"Uターンし、{way_name}を進む\",\r\n \"destination\": \"Uターンし、{destination}へ向かう\"\r\n }\r\n },\r\n \"depart\": {\r\n \"default\": {\r\n \"default\": \"{direction}に進む\",\r\n \"name\": \"{way_name}を{direction}に進む\",\r\n \"namedistance\": \"{way_name}を{direction}に{distance}進む\"\r\n }\r\n },\r\n \"end of road\": {\r\n \"default\": {\r\n \"default\": \"{modifier}に曲がる\",\r\n \"name\": \"{modifier}に曲がり、{way_name}を進む\",\r\n \"destination\": \"{modifier}に曲がり、{destination}へ向かう\"\r\n },\r\n \"straight\": {\r\n \"default\": \"直進する\",\r\n \"name\": \"{way_name}を直進する\",\r\n \"destination\": \"直進し、{destination}へ向かう\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"突き当りでUターンする\",\r\n \"name\": \"{way_name}の突き当りでUターンする\",\r\n \"destination\": \"突き当たりでUターンし、{destination}へ向かう\"\r\n }\r\n },\r\n \"fork\": {\r\n \"default\": {\r\n \"default\": \"分岐を{modifier}に進む\",\r\n \"name\": \"{way_name}の{modifier}車線を進む\",\r\n \"destination\": \"{modifier}車線を進み、{destination}へ向かう\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"分岐を左方向に進む\",\r\n \"name\": \"{way_name}の左車線を進む\",\r\n \"destination\": \"{destination}へ向けて左車線を進む\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"分岐を右方向に進む\",\r\n \"name\": \"{way_name}の右車線を進む\",\r\n \"destination\": \"{destination}へ向けて右車線を進む\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"分岐を大きく左方向に進む\",\r\n \"name\": \"大きく左方向に曲がり、{way_name}を進む\",\r\n \"destination\": \"大きく左方向に曲がり、{destination}へ向かう\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"分岐を大きく右方向に進む\",\r\n \"name\": \"大きく右方向に曲がり、{way_name}を進む\",\r\n \"destination\": \"大きく右方向に曲がり、{destination}へ向かう\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Uターンする\",\r\n \"name\": \"Uターンし、{way_name}を進む\",\r\n \"destination\": \"Uターンし、{destination}へ向かう\"\r\n }\r\n },\r\n \"merge\": {\r\n \"default\": {\r\n \"default\": \"{modifier}に合流する\",\r\n \"name\": \"{modifier}に合流し、{way_name}を進む\",\r\n \"destination\": \"{modifier}に合流し、{destination}へ向かう\"\r\n },\r\n \"straight\": {\r\n \"default\": \"合流する\",\r\n \"name\": \"合流し、{way_name}を進む\",\r\n \"destination\": \"合流し、{destination}へ向かう\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"左に合流する\",\r\n \"name\": \"左に合流し、{way_name}を進む\",\r\n \"destination\": \"左に合流し、{destination}へ向かう\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"右に合流する\",\r\n \"name\": \"右に合流し、{way_name}を進む\",\r\n \"destination\": \"右に合流し、{destination}へ向かう\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"左に合流する\",\r\n \"name\": \"左に合流し、{way_name}を進む\",\r\n \"destination\": \"左に合流し、{destination}へ向かう\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"右に合流する\",\r\n \"name\": \"右に合流し、{way_name}を進む\",\r\n \"destination\": \"右に合流し、{destination}へ向かう\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Uターンする\",\r\n \"name\": \"Uターンし、{way_name}を進む\",\r\n \"destination\": \"Uターンし、{destination}へ向かう\"\r\n }\r\n },\r\n \"new name\": {\r\n \"default\": {\r\n \"default\": \"{modifier}に進む\",\r\n \"name\": \"{way_name}を{modifier}に進む\",\r\n \"destination\": \"{destination}に向けて{modifier}に進む\"\r\n },\r\n \"straight\": {\r\n \"default\": \"直進する\",\r\n \"name\": \"{way_name}を直進する\",\r\n \"destination\": \"{destination}へ向けて直進する\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"大きく左に曲がる\",\r\n \"name\": \"大きく左方向に曲がり、{way_name}を進む\",\r\n \"destination\": \"大きく左方向に曲がり、{destination}へ向かう\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"大きく右に曲がる\",\r\n \"name\": \"大きく右方向に曲がり、{way_name}を進む\",\r\n \"destination\": \"大きく右方向に曲がり、{destination}へ向かう\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"斜め左方向に進む\",\r\n \"name\": \"{way_name}を斜め左方向に進む\",\r\n \"destination\": \"{destination}に向けて斜め左方向に進む\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"斜め右方向に進む\",\r\n \"name\": \"{way_name}を斜め右方向に進む\",\r\n \"destination\": \"{destination}に向けて斜め右方向に進む\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Uターンする\",\r\n \"name\": \"Uターンし、{way_name}を進む\",\r\n \"destination\": \"Uターンし、{destination}へ向かう\"\r\n }\r\n },\r\n \"notification\": {\r\n \"default\": {\r\n \"default\": \"{modifier}に進む\",\r\n \"name\": \"{way_name}を{modifier}に進む\",\r\n \"destination\": \"{destination}へ向けて{modifier}に進む\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Uターンする\",\r\n \"name\": \"Uターンし、{way_name}を進む\",\r\n \"destination\": \"Uターンし、{destination}へ向かう\"\r\n }\r\n },\r\n \"off ramp\": {\r\n \"default\": {\r\n \"default\": \"ランプに向かう\",\r\n \"name\": \"{way_name}に向けてランプに向かう\",\r\n \"destination\": \"{destination}に向けてランプに向かう\",\r\n \"exit\": \"{exit}出口に向かう\",\r\n \"exit_destination\": \"{exit}出口を出て{destination}へ向かう\"\r\n },\r\n \"left\": {\r\n \"default\": \"左方向のランプに向かう\",\r\n \"name\": \"左方向のランプに向かい、{way_name}を進む\",\r\n \"destination\": \"左方向のランプに向かい、その後{destination}へ向かう\",\r\n \"exit\": \"左方向の{exit}出口を出る\",\r\n \"exit_destination\": \"左方向の{exit}出口を出て{destination}へ向かう\"\r\n },\r\n \"right\": {\r\n \"default\": \"右方向のランプに向かう\",\r\n \"name\": \"右方向のランプに向かい、{way_name}を進む\",\r\n \"destination\": \"右方向のランプに向かい、その後{destination}へ向かう\",\r\n \"exit\": \"右方向の{exit}出口を出る\",\r\n \"exit_destination\": \"右方向の{exit}出口を出て{destination}へ向かう\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"左方向のランプに向かう\",\r\n \"name\": \"左方向のランプに向かい、{way_name}を進む\",\r\n \"destination\": \"左方向のランプに向かい、{destination}へ向かう\",\r\n \"exit\": \"左方向の{exit}出口を出る\",\r\n \"exit_destination\": \"左方向の{exit}出口を出て{destination}へ向かう\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"右方向のランプに向かう\",\r\n \"name\": \"右方向のランプに向かい、{way_name}を進む\",\r\n \"destination\": \"右方向のランプに向かい、{destination}へ向かう\",\r\n \"exit\": \"右方向の{exit}出口を出る\",\r\n \"exit_destination\": \"右方向の{exit}出口を出て{destination}へ向かう\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"左方向のランプへ向かう\",\r\n \"name\": \"左方向のランプに向かい、{way_name}を進む\",\r\n \"destination\": \"左方向のランプに向かい、その後{destination}へ向かう\",\r\n \"exit\": \"左方向の{exit}出口を出る\",\r\n \"exit_destination\": \"左方向の{exit}出口を出て{destination}へ向かう\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"右方向のランプへ向かう\",\r\n \"name\": \"右方向のランプに向かい、{way_name}を進む\",\r\n \"destination\": \"右方向のランプに向かい、その後{destination}へ向かう\",\r\n \"exit\": \"右方向の{exit}出口を出る\",\r\n \"exit_destination\": \"右方向の{exit}出口を出て{destination}へ向かう\"\r\n }\r\n },\r\n \"on ramp\": {\r\n \"default\": {\r\n \"default\": \"ランプへ向かう\",\r\n \"name\": \"ランプへ向かい、{way_name}を進む\",\r\n \"destination\": \"ランプに向かい、その後{destination}へ向かう\"\r\n },\r\n \"left\": {\r\n \"default\": \"左方向のランプへ向かう\",\r\n \"name\": \"左方向のランプに向かい、{way_name}を進む\",\r\n \"destination\": \"左方向のランプに向かい、その後{destination}へ向かう\"\r\n },\r\n \"right\": {\r\n \"default\": \"右方向のランプへ向かう\",\r\n \"name\": \"右方向のランプに向かい、{way_name}を進む\",\r\n \"destination\": \"右方向のランプに向かい、その後{destination}へ向かう\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"左方向のランプへ向かう\",\r\n \"name\": \"左方向のランプに向かい、{way_name}を進む\",\r\n \"destination\": \"左方向のランプに向かい、その後{destination}へ向かう\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"右方向のランプへ向かう\",\r\n \"name\": \"右方向のランプに向かい、{way_name}を進む\",\r\n \"destination\": \"右方向のランプに向かい、その後{destination}へ向かう\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"左方向のランプへ向かう\",\r\n \"name\": \"左方向のランプに向かい、{way_name}を進む\",\r\n \"destination\": \"左方向のランプに向かい、その後{destination}へ向かう\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"右方向のランプへ向かう\",\r\n \"name\": \"右方向のランプに向かい、{way_name}を進む\",\r\n \"destination\": \"右方向のランプに向かい、その後{destination}へ向かう\"\r\n }\r\n },\r\n \"rotary\": {\r\n \"default\": {\r\n \"default\": {\r\n \"default\": \"ランアバウトへ進む\",\r\n \"name\": \"ランアバウトから{way_name}へ進む\",\r\n \"destination\": \"ランアバウトをから{destination}へ向かう\"\r\n },\r\n \"name\": {\r\n \"default\": \"{rotary_name}に入る\",\r\n \"name\": \"{rotary_name}から{way_name}を進む\",\r\n \"destination\": \"{rotary_name}から{destination}へ向かう\"\r\n },\r\n \"exit\": {\r\n \"default\": \"ランアバウト{exit_number}番出口を出る\",\r\n \"name\": \"ランアバウト{exit_number}番出口を出て{way_name}を進む\",\r\n \"destination\": \"ランアバウト{exit_number}番出口を出て{destination}へ向かう\"\r\n },\r\n \"name_exit\": {\r\n \"default\": \"{rotary_name}に入り、{exit_number}番出口を出る\",\r\n \"name\": \"{rotary_name}の{exit_number}番出口から{way_name}を進む\",\r\n \"destination\": \"{rotary_name}の{exit_number}番出口から{destination}へ向かう\"\r\n }\r\n }\r\n },\r\n \"roundabout\": {\r\n \"default\": {\r\n \"exit\": {\r\n \"default\": \"ランアバウト{exit_number}番出口を出る\",\r\n \"name\": \"ランアバウト{exit_number}番出口を出て{way_name}を進む\",\r\n \"destination\": \"ランアバウト{exit_number}番出口を出て{destination}へ向かう\"\r\n },\r\n \"default\": {\r\n \"default\": \"ランアバウトへ進む\",\r\n \"name\": \"ランアバウトを出て{way_name}を進む\",\r\n \"destination\": \"ランアバウトを出て{destination}へ向かう\"\r\n }\r\n }\r\n },\r\n \"roundabout turn\": {\r\n \"default\": {\r\n \"default\": \"{modifier}に向かう\",\r\n \"name\": \"{modifier}に向かい、{way_name}を進む\",\r\n \"destination\": \"{modifier}に向かい、その後{destination}へ向かう\"\r\n },\r\n \"left\": {\r\n \"default\": \"左折する\",\r\n \"name\": \"左折して{way_name}を進む\",\r\n \"destination\": \"左折して{destination}へ向かう\"\r\n },\r\n \"right\": {\r\n \"default\": \"右折する\",\r\n \"name\": \"右折して{way_name}を進む\",\r\n \"destination\": \"右折して{destination}へ向かう\"\r\n },\r\n \"straight\": {\r\n \"default\": \"直進する\",\r\n \"name\": \"{way_name}を直進する\",\r\n \"destination\": \"{destination}へ直進する\"\r\n }\r\n },\r\n \"exit roundabout\": {\r\n \"default\": {\r\n \"default\": \"ランアバウトを出る\",\r\n \"name\": \"ランアバウトを出て{way_name}を進む\",\r\n \"destination\": \"ランアバウトを出て{destination}へ向かう\"\r\n }\r\n },\r\n \"exit rotary\": {\r\n \"default\": {\r\n \"default\": \"ランアバウトを出る\",\r\n \"name\": \"ランアバウトを出て{way_name}を進む\",\r\n \"destination\": \"ランアバウトを出て{destination}へ向かう\"\r\n }\r\n },\r\n \"turn\": {\r\n \"default\": {\r\n \"default\": \"{modifier}に向かう\",\r\n \"name\": \"{modifier}に向かい、{way_name}を進む\",\r\n \"destination\": \"{modifier}に向かい、その後{destination}へ向かう\"\r\n },\r\n \"left\": {\r\n \"default\": \"左折する\",\r\n \"name\": \"左折して{way_name}を進む\",\r\n \"destination\": \"左折して{destination}へ向かう\"\r\n },\r\n \"right\": {\r\n \"default\": \"右折する\",\r\n \"name\": \"右折して{way_name}を進む\",\r\n \"destination\": \"右折して{destination}へ向かう\"\r\n },\r\n \"straight\": {\r\n \"default\": \"直進する\",\r\n \"name\": \"{way_name}を直進する\",\r\n \"destination\": \"{destination}に向けて直進する\"\r\n }\r\n },\r\n \"use lane\": {\r\n \"no_lanes\": {\r\n \"default\": \"直進する\"\r\n },\r\n \"default\": {\r\n \"default\": \"{lane_instruction}\"\r\n }\r\n }\r\n }\r\n}\r\n\r\n }, {}],\r\n 44: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"meta\": {\r\n \"capitalizeFirstLetter\": true\r\n },\r\n \"v5\": {\r\n \"constants\": {\r\n \"ordinalize\": {\r\n \"1\": \"1º\",\r\n \"2\": \"2º\",\r\n \"3\": \"3º\",\r\n \"4\": \"4º\",\r\n \"5\": \"5º\",\r\n \"6\": \"6º\",\r\n \"7\": \"7º\",\r\n \"8\": \"8º\",\r\n \"9\": \"9º\",\r\n \"10\": \"10º\"\r\n },\r\n \"direction\": {\r\n \"north\": \"norte\",\r\n \"northeast\": \"nordeste\",\r\n \"east\": \"leste\",\r\n \"southeast\": \"sudeste\",\r\n \"south\": \"sul\",\r\n \"southwest\": \"sudoeste\",\r\n \"west\": \"oeste\",\r\n \"northwest\": \"noroeste\"\r\n },\r\n \"modifier\": {\r\n \"left\": \"à esquerda\",\r\n \"right\": \"à direita\",\r\n \"sharp left\": \"fechada à esquerda\",\r\n \"sharp right\": \"fechada à direita\",\r\n \"slight left\": \"suave à esquerda\",\r\n \"slight right\": \"suave à direita\",\r\n \"straight\": \"em frente\",\r\n \"uturn\": \"retorno\"\r\n },\r\n \"lanes\": {\r\n \"xo\": \"Mantenha-se à direita\",\r\n \"ox\": \"Mantenha-se à esquerda\",\r\n \"xox\": \"Mantenha-se ao centro\",\r\n \"oxo\": \"Mantenha-se à esquerda ou direita\"\r\n }\r\n },\r\n \"modes\": {\r\n \"ferry\": {\r\n \"default\": \"Pegue a balsa\",\r\n \"name\": \"Pegue a balsa {way_name}\",\r\n \"destination\": \"Pegue a balsa sentido {destination}\"\r\n }\r\n },\r\n \"phrase\": {\r\n \"two linked by distance\": \"{instruction_one}, então, em {distance}, {instruction_two}\",\r\n \"two linked\": \"{instruction_one}, então {instruction_two}\",\r\n \"one in distance\": \"Em {distance}, {instruction_one}\",\r\n \"name and ref\": \"{name} ({ref})\",\r\n \"exit with number\": \"saída {exit}\"\r\n },\r\n \"arrive\": {\r\n \"default\": {\r\n \"default\": \"Você chegou ao seu {nth} destino\",\r\n \"upcoming\": \"Você chegará ao seu {nth} destino\",\r\n \"short\": \"Você chegou\",\r\n \"short-upcoming\": \"Você vai chegar\",\r\n \"named\": \"Você chegou a {waypoint_name}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Você chegou ao seu {nth} destino, à esquerda\",\r\n \"upcoming\": \"Você chegará ao seu {nth} destino, à esquerda\",\r\n \"short\": \"Você chegou\",\r\n \"short-upcoming\": \"Você vai chegar\",\r\n \"named\": \"Você chegou {waypoint_name}, à esquerda\"\r\n },\r\n \"right\": {\r\n \"default\": \"Você chegou ao seu {nth} destino, à direita\",\r\n \"upcoming\": \"Você chegará ao seu {nth} destino, à direita\",\r\n \"short\": \"Você chegou\",\r\n \"short-upcoming\": \"Você vai chegar\",\r\n \"named\": \"Você chegou {waypoint_name}, à direita\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Você chegou ao seu {nth} destino, à esquerda\",\r\n \"upcoming\": \"Você chegará ao seu {nth} destino, à esquerda\",\r\n \"short\": \"Você chegou\",\r\n \"short-upcoming\": \"Você vai chegar\",\r\n \"named\": \"Você chegou {waypoint_name}, à esquerda\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Você chegou ao seu {nth} destino, à direita\",\r\n \"upcoming\": \"Você chegará ao seu {nth} destino, à direita\",\r\n \"short\": \"Você chegou\",\r\n \"short-upcoming\": \"Você vai chegar\",\r\n \"named\": \"Você chegou {waypoint_name}, à direita\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Você chegou ao seu {nth} destino, à direita\",\r\n \"upcoming\": \"Você chegará ao seu {nth} destino, à direita\",\r\n \"short\": \"Você chegou\",\r\n \"short-upcoming\": \"Você vai chegar\",\r\n \"named\": \"Você chegou {waypoint_name}, à direita\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Você chegou ao seu {nth} destino, à esquerda\",\r\n \"upcoming\": \"Você chegará ao seu {nth} destino, à esquerda\",\r\n \"short\": \"Você chegou\",\r\n \"short-upcoming\": \"Você vai chegar\",\r\n \"named\": \"Você chegou {waypoint_name}, à esquerda\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Você chegou ao seu {nth} destino, em frente\",\r\n \"upcoming\": \"Você vai chegar ao seu {nth} destino, em frente\",\r\n \"short\": \"Você chegou\",\r\n \"short-upcoming\": \"Você vai chegar\",\r\n \"named\": \"You have arrived at {waypoint_name}, straight ahead\"\r\n }\r\n },\r\n \"continue\": {\r\n \"default\": {\r\n \"default\": \"Vire {modifier}\",\r\n \"name\": \"Vire {modifier} para manter-se na {way_name}\",\r\n \"destination\": \"Vire {modifier} sentido {destination}\",\r\n \"exit\": \"Vire {modifier} em {way_name}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continue em frente\",\r\n \"name\": \"Continue em frente para manter-se na {way_name}\",\r\n \"destination\": \"Continue em direção à {destination}\",\r\n \"distance\": \"Continue em frente por {distance}\",\r\n \"namedistance\": \"Continue na {way_name} por {distance}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Faça uma curva fechada a esquerda\",\r\n \"name\": \"Faça uma curva fechada a esquerda para manter-se na {way_name}\",\r\n \"destination\": \"Faça uma curva fechada a esquerda sentido {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Faça uma curva fechada a direita\",\r\n \"name\": \"Faça uma curva fechada a direita para manter-se na {way_name}\",\r\n \"destination\": \"Faça uma curva fechada a direita sentido {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Faça uma curva suave a esquerda\",\r\n \"name\": \"Faça uma curva suave a esquerda para manter-se na {way_name}\",\r\n \"destination\": \"Faça uma curva suave a esquerda em direção a {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Faça uma curva suave a direita\",\r\n \"name\": \"Faça uma curva suave a direita para manter-se na {way_name}\",\r\n \"destination\": \"Faça uma curva suave a direita em direção a {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faça o retorno\",\r\n \"name\": \"Faça o retorno e continue em {way_name}\",\r\n \"destination\": \"Faça o retorno sentido {destination}\"\r\n }\r\n },\r\n \"depart\": {\r\n \"default\": {\r\n \"default\": \"Siga {direction}\",\r\n \"name\": \"Siga {direction} em {way_name}\",\r\n \"namedistance\": \"Siga {direction} na {way_name} por {distance}\"\r\n }\r\n },\r\n \"end of road\": {\r\n \"default\": {\r\n \"default\": \"Vire {modifier}\",\r\n \"name\": \"Vire {modifier} em {way_name}\",\r\n \"destination\": \"Vire {modifier} sentido {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continue em frente\",\r\n \"name\": \"Continue em frente em {way_name}\",\r\n \"destination\": \"Continue em frente sentido {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faça o retorno no fim da rua\",\r\n \"name\": \"Faça o retorno em {way_name} no fim da rua\",\r\n \"destination\": \"Faça o retorno sentido {destination} no fim da rua\"\r\n }\r\n },\r\n \"fork\": {\r\n \"default\": {\r\n \"default\": \"Mantenha-se {modifier} na bifurcação\",\r\n \"name\": \"Mantenha-se {modifier} na bifurcação em {way_name}\",\r\n \"destination\": \"Mantenha-se {modifier} na bifurcação sentido {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Mantenha-se à esquerda na bifurcação\",\r\n \"name\": \"Mantenha-se à esquerda na bifurcação em {way_name}\",\r\n \"destination\": \"Mantenha-se à esquerda na bifurcação sentido {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Mantenha-se à direita na bifurcação\",\r\n \"name\": \"Mantenha-se à direita na bifurcação em {way_name}\",\r\n \"destination\": \"Mantenha-se à direita na bifurcação sentido {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Faça uma curva fechada à esquerda na bifurcação\",\r\n \"name\": \"Faça uma curva fechada à esquerda em {way_name}\",\r\n \"destination\": \"Faça uma curva fechada à esquerda sentido {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Faça uma curva fechada à direita na bifurcação\",\r\n \"name\": \"Faça uma curva fechada à direita em {way_name}\",\r\n \"destination\": \"Faça uma curva fechada à direita sentido {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faça o retorno\",\r\n \"name\": \"Faça o retorno em {way_name}\",\r\n \"destination\": \"Faça o retorno sentido {destination}\"\r\n }\r\n },\r\n \"merge\": {\r\n \"default\": {\r\n \"default\": \"Entre {modifier}\",\r\n \"name\": \"Entre {modifier} na {way_name}\",\r\n \"destination\": \"Entre {modifier} em direção à {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Mesclar\",\r\n \"name\": \"Entre reto na {way_name}\",\r\n \"destination\": \"Entre reto em direção à {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Entre à esquerda\",\r\n \"name\": \"Entre à esquerda na {way_name}\",\r\n \"destination\": \"Entre à esquerda em direção à {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Entre à direita\",\r\n \"name\": \"Entre à direita na {way_name}\",\r\n \"destination\": \"Entre à direita em direção à {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Entre à esquerda\",\r\n \"name\": \"Entre à esquerda na {way_name}\",\r\n \"destination\": \"Entre à esquerda em direção à {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Entre à direita\",\r\n \"name\": \"Entre à direita na {way_name}\",\r\n \"destination\": \"Entre à direita em direção à {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faça o retorno\",\r\n \"name\": \"Faça o retorno em {way_name}\",\r\n \"destination\": \"Faça o retorno sentido {destination}\"\r\n }\r\n },\r\n \"new name\": {\r\n \"default\": {\r\n \"default\": \"Continue {modifier}\",\r\n \"name\": \"Continue {modifier} em {way_name}\",\r\n \"destination\": \"Continue {modifier} sentido {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continue em frente\",\r\n \"name\": \"Continue em {way_name}\",\r\n \"destination\": \"Continue em direção à {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Faça uma curva fechada à esquerda\",\r\n \"name\": \"Faça uma curva fechada à esquerda em {way_name}\",\r\n \"destination\": \"Faça uma curva fechada à esquerda sentido {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Faça uma curva fechada à direita\",\r\n \"name\": \"Faça uma curva fechada à direita em {way_name}\",\r\n \"destination\": \"Faça uma curva fechada à direita sentido {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Continue ligeiramente à esquerda\",\r\n \"name\": \"Continue ligeiramente à esquerda em {way_name}\",\r\n \"destination\": \"Continue ligeiramente à esquerda sentido {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Continue ligeiramente à direita\",\r\n \"name\": \"Continue ligeiramente à direita em {way_name}\",\r\n \"destination\": \"Continue ligeiramente à direita sentido {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faça o retorno\",\r\n \"name\": \"Faça o retorno em {way_name}\",\r\n \"destination\": \"Faça o retorno sentido {destination}\"\r\n }\r\n },\r\n \"notification\": {\r\n \"default\": {\r\n \"default\": \"Continue {modifier}\",\r\n \"name\": \"Continue {modifier} em {way_name}\",\r\n \"destination\": \"Continue {modifier} sentido {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faça o retorno\",\r\n \"name\": \"Faça o retorno em {way_name}\",\r\n \"destination\": \"Faça o retorno sentido {destination}\"\r\n }\r\n },\r\n \"off ramp\": {\r\n \"default\": {\r\n \"default\": \"Pegue a rampa\",\r\n \"name\": \"Pegue a rampa em {way_name}\",\r\n \"destination\": \"Pegue a rampa sentido {destination}\",\r\n \"exit\": \"Pegue a saída {exit}\",\r\n \"exit_destination\": \"Pegue a saída {exit} em direção à {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Pegue a rampa à esquerda\",\r\n \"name\": \"Pegue a rampa à esquerda em {way_name}\",\r\n \"destination\": \"Pegue a rampa à esquerda sentido {destination}\",\r\n \"exit\": \"Pegue a saída {exit} à esquerda\",\r\n \"exit_destination\": \"Pegue a saída {exit} à esquerda em direção à {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Pegue a rampa à direita\",\r\n \"name\": \"Pegue a rampa à direita em {way_name}\",\r\n \"destination\": \"Pegue a rampa à direita sentido {destination}\",\r\n \"exit\": \"Pegue a saída {exit} à direita\",\r\n \"exit_destination\": \"Pegue a saída {exit} à direita em direção à {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Pegue a rampa à esquerda\",\r\n \"name\": \"Pegue a rampa à esquerda em {way_name}\",\r\n \"destination\": \"Pegue a rampa à esquerda sentido {destination}\",\r\n \"exit\": \"Pegue a saída {exit} à esquerda\",\r\n \"exit_destination\": \"Pegue a saída {exit} à esquerda em direção à {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Pegue a rampa à direita\",\r\n \"name\": \"Pegue a rampa à direita em {way_name}\",\r\n \"destination\": \"Pegue a rampa à direita sentido {destination}\",\r\n \"exit\": \"Pegue a saída {exit} à direita\",\r\n \"exit_destination\": \"Pegue a saída {exit} à direita em direção à {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Pegue a rampa à esquerda\",\r\n \"name\": \"Pegue a rampa à esquerda em {way_name}\",\r\n \"destination\": \"Pegue a rampa à esquerda sentido {destination}\",\r\n \"exit\": \"Pegue a saída {exit} à esquerda\",\r\n \"exit_destination\": \"Pegue a saída {exit} à esquerda em direção à {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Pegue a rampa à direita\",\r\n \"name\": \"Pegue a rampa à direita em {way_name}\",\r\n \"destination\": \"Pegue a rampa à direita sentido {destination}\",\r\n \"exit\": \"Pegue a saída {exit} à direita\",\r\n \"exit_destination\": \"Pegue a saída {exit} à direita em direção à {destination}\"\r\n }\r\n },\r\n \"on ramp\": {\r\n \"default\": {\r\n \"default\": \"Pegue a rampa\",\r\n \"name\": \"Pegue a rampa em {way_name}\",\r\n \"destination\": \"Pegue a rampa sentido {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Pegue a rampa à esquerda\",\r\n \"name\": \"Pegue a rampa à esquerda em {way_name}\",\r\n \"destination\": \"Pegue a rampa à esquerda sentido {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Pegue a rampa à direita\",\r\n \"name\": \"Pegue a rampa à direita em {way_name}\",\r\n \"destination\": \"Pegue a rampa à direita sentid {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Pegue a rampa à esquerda\",\r\n \"name\": \"Pegue a rampa à esquerda em {way_name}\",\r\n \"destination\": \"Pegue a rampa à esquerda sentido {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Pegue a rampa à direita\",\r\n \"name\": \"Pegue a rampa à direita em {way_name}\",\r\n \"destination\": \"Pegue a rampa à direita sentido {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Pegue a rampa à esquerda\",\r\n \"name\": \"Pegue a rampa à esquerda em {way_name}\",\r\n \"destination\": \"Pegue a rampa à esquerda sentido {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Pegue a rampa à direita\",\r\n \"name\": \"Pegue a rampa à direita em {way_name}\",\r\n \"destination\": \"Pegue a rampa à direita sentido {destination}\"\r\n }\r\n },\r\n \"rotary\": {\r\n \"default\": {\r\n \"default\": {\r\n \"default\": \"Entre na rotatória\",\r\n \"name\": \"Entre na rotatória e saia na {way_name}\",\r\n \"destination\": \"Entre na rotatória e saia sentido {destination}\"\r\n },\r\n \"name\": {\r\n \"default\": \"Entre em {rotary_name}\",\r\n \"name\": \"Entre em {rotary_name} e saia em {way_name}\",\r\n \"destination\": \"Entre em {rotary_name} e saia sentido {destination}\"\r\n },\r\n \"exit\": {\r\n \"default\": \"Entre na rotatória e pegue a {exit_number} saída\",\r\n \"name\": \"Entre na rotatória e pegue a {exit_number} saída na {way_name}\",\r\n \"destination\": \"Entre na rotatória e pegue a {exit_number} saída sentido {destination}\"\r\n },\r\n \"name_exit\": {\r\n \"default\": \"Entre em {rotary_name} e saia na {exit_number} saída\",\r\n \"name\": \"Entre em {rotary_name} e saia na {exit_number} saída em {way_name}\",\r\n \"destination\": \"Entre em {rotary_name} e saia na {exit_number} saída sentido {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout\": {\r\n \"default\": {\r\n \"exit\": {\r\n \"default\": \"Entre na rotatória e pegue a {exit_number} saída\",\r\n \"name\": \"Entre na rotatória e pegue a {exit_number} saída na {way_name}\",\r\n \"destination\": \"Entre na rotatória e pegue a {exit_number} saída sentido {destination}\"\r\n },\r\n \"default\": {\r\n \"default\": \"Entre na rotatória\",\r\n \"name\": \"Entre na rotatória e saia na {way_name}\",\r\n \"destination\": \"Entre na rotatória e saia sentido {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout turn\": {\r\n \"default\": {\r\n \"default\": \"Siga {modifier}\",\r\n \"name\": \"Siga {modifier} em {way_name}\",\r\n \"destination\": \"Siga {modifier} sentido {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Vire à esquerda\",\r\n \"name\": \"Vire à esquerda em {way_name}\",\r\n \"destination\": \"Vire à esquerda sentido {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Vire à direita\",\r\n \"name\": \"Vire à direita em {way_name}\",\r\n \"destination\": \"Vire à direita sentido {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continue em frente\",\r\n \"name\": \"Continue em frente em {way_name}\",\r\n \"destination\": \"Continue em frente sentido {destination}\"\r\n }\r\n },\r\n \"exit roundabout\": {\r\n \"default\": {\r\n \"default\": \"Saia da rotatória\",\r\n \"name\": \"Exit the roundabout onto {way_name}\",\r\n \"destination\": \"Exit the roundabout towards {destination}\"\r\n }\r\n },\r\n \"exit rotary\": {\r\n \"default\": {\r\n \"default\": \"Saia da rotatória\",\r\n \"name\": \"Exit the roundabout onto {way_name}\",\r\n \"destination\": \"Exit the roundabout towards {destination}\"\r\n }\r\n },\r\n \"turn\": {\r\n \"default\": {\r\n \"default\": \"Siga {modifier}\",\r\n \"name\": \"Siga {modifier} em {way_name}\",\r\n \"destination\": \"Siga {modifier} sentido {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Vire à esquerda\",\r\n \"name\": \"Vire à esquerda em {way_name}\",\r\n \"destination\": \"Vire à esquerda sentido {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Vire à direita\",\r\n \"name\": \"Vire à direita em {way_name}\",\r\n \"destination\": \"Vire à direita sentido {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Siga em frente\",\r\n \"name\": \"Siga em frente em {way_name}\",\r\n \"destination\": \"Siga em frente sentido {destination}\"\r\n }\r\n },\r\n \"use lane\": {\r\n \"no_lanes\": {\r\n \"default\": \"Continue em frente\"\r\n },\r\n \"default\": {\r\n \"default\": \"{lane_instruction}\"\r\n }\r\n }\r\n }\r\n}\r\n\r\n }, {}],\r\n 45: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"meta\": {\r\n \"capitalizeFirstLetter\": true\r\n },\r\n \"v5\": {\r\n \"constants\": {\r\n \"ordinalize\": {\r\n \"1\": \"1º\",\r\n \"2\": \"2º\",\r\n \"3\": \"3º\",\r\n \"4\": \"4º\",\r\n \"5\": \"5º\",\r\n \"6\": \"6º\",\r\n \"7\": \"7º\",\r\n \"8\": \"8º\",\r\n \"9\": \"9º\",\r\n \"10\": \"10º\"\r\n },\r\n \"direction\": {\r\n \"north\": \"norte\",\r\n \"northeast\": \"nordeste\",\r\n \"east\": \"este\",\r\n \"southeast\": \"sudeste\",\r\n \"south\": \"sul\",\r\n \"southwest\": \"sudoeste\",\r\n \"west\": \"oeste\",\r\n \"northwest\": \"noroeste\"\r\n },\r\n \"modifier\": {\r\n \"left\": \"à esquerda\",\r\n \"right\": \"à direita\",\r\n \"sharp left\": \"acentuadamente à esquerda\",\r\n \"sharp right\": \"acentuadamente à direita\",\r\n \"slight left\": \"ligeiramente à esquerda\",\r\n \"slight right\": \"ligeiramente à direita\",\r\n \"straight\": \"em frente\",\r\n \"uturn\": \"inversão de marcha\"\r\n },\r\n \"lanes\": {\r\n \"xo\": \"Mantenha-se à direita\",\r\n \"ox\": \"Mantenha-se à esquerda\",\r\n \"xox\": \"Mantenha-se ao meio\",\r\n \"oxo\": \"Mantenha-se à esquerda ou à direita\"\r\n }\r\n },\r\n \"modes\": {\r\n \"ferry\": {\r\n \"default\": \"Apanhe o ferry\",\r\n \"name\": \"Apanhe o ferry {way_name}\",\r\n \"destination\": \"Apanhe o ferry para {destination}\"\r\n }\r\n },\r\n \"phrase\": {\r\n \"two linked by distance\": \"{instruction_one}, depois, a {distance}, {instruction_two}\",\r\n \"two linked\": \"{instruction_one}, depois {instruction_two}\",\r\n \"one in distance\": \"A {distance}, {instruction_one}\",\r\n \"name and ref\": \"{name} ({ref})\",\r\n \"exit with number\": \"saída {exit}\"\r\n },\r\n \"arrive\": {\r\n \"default\": {\r\n \"default\": \"Chegou ao seu {nth} destino\",\r\n \"upcoming\": \"Está a chegar ao seu {nth} destino\",\r\n \"short\": \"Chegou\",\r\n \"short-upcoming\": \"Está a chegar\",\r\n \"named\": \"Chegou a {waypoint_name}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Chegou ao seu {nth} destino, à esquerda\",\r\n \"upcoming\": \"Está a chegar ao seu {nth} destino, à esquerda\",\r\n \"short\": \"Chegou\",\r\n \"short-upcoming\": \"Está a chegar\",\r\n \"named\": \"Chegou a {waypoint_name}, à esquerda\"\r\n },\r\n \"right\": {\r\n \"default\": \"Chegou ao seu {nth} destino, à direita\",\r\n \"upcoming\": \"Está a chegar ao seu {nth} destino, à direita\",\r\n \"short\": \"Chegou\",\r\n \"short-upcoming\": \"Está a chegar\",\r\n \"named\": \"Chegou a {waypoint_name}, à direita\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Chegou ao seu {nth} destino, à esquerda\",\r\n \"upcoming\": \"Está a chegar ao seu {nth} destino, à esquerda\",\r\n \"short\": \"Chegou\",\r\n \"short-upcoming\": \"Está a chegar\",\r\n \"named\": \"Chegou a {waypoint_name}, à esquerda\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Chegou ao seu {nth} destino, à direita\",\r\n \"upcoming\": \"Está a chegar ao seu {nth} destino, à direita\",\r\n \"short\": \"Chegou\",\r\n \"short-upcoming\": \"Está a chegar\",\r\n \"named\": \"Chegou a {waypoint_name}, à direita\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Chegou ao seu {nth} destino, à direita\",\r\n \"upcoming\": \"Está a chegar ao seu {nth} destino, à direita\",\r\n \"short\": \"Chegou\",\r\n \"short-upcoming\": \"Está a chegar\",\r\n \"named\": \"Chegou a {waypoint_name}, à direita\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Chegou ao seu {nth} destino, à esquerda\",\r\n \"upcoming\": \"Está a chegar ao seu {nth} destino, à esquerda\",\r\n \"short\": \"Chegou\",\r\n \"short-upcoming\": \"Está a chegar\",\r\n \"named\": \"Chegou a {waypoint_name}, à esquerda\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Chegou ao seu {nth} destino, em frente\",\r\n \"upcoming\": \"Está a chegar ao seu {nth} destino, em frente\",\r\n \"short\": \"Chegou\",\r\n \"short-upcoming\": \"Está a chegar\",\r\n \"named\": \"Chegou a {waypoint_name}, em frente\"\r\n }\r\n },\r\n \"continue\": {\r\n \"default\": {\r\n \"default\": \"Vire {modifier}\",\r\n \"name\": \"Vire {modifier} para se manter em {way_name}\",\r\n \"destination\": \"Vire {modifier} em direção a {destination}\",\r\n \"exit\": \"Vire {modifier} para {way_name}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continue em frente\",\r\n \"name\": \"Continue em frente para se manter em {way_name}\",\r\n \"destination\": \"Continue em direção a {destination}\",\r\n \"distance\": \"Continue em frente por {distance}\",\r\n \"namedistance\": \"Continue em {way_name} por {distance}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Vire acentuadamente à esquerda\",\r\n \"name\": \"Vire acentuadamente à esquerda para se manter em {way_name}\",\r\n \"destination\": \"Vire acentuadamente à esquerda em direção a {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Vire acentuadamente à direita\",\r\n \"name\": \"Vire acentuadamente à direita para se manter em {way_name}\",\r\n \"destination\": \"Vire acentuadamente à direita em direção a {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Vire ligeiramente à esquerda\",\r\n \"name\": \"Vire ligeiramente à esquerda para se manter em {way_name}\",\r\n \"destination\": \"Vire ligeiramente à esquerda em direção a {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Vire ligeiramente à direita\",\r\n \"name\": \"Vire ligeiramente à direita para se manter em {way_name}\",\r\n \"destination\": \"Vire ligeiramente à direita em direção a {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faça inversão de marcha\",\r\n \"name\": \"Faça inversão de marcha e continue em {way_name}\",\r\n \"destination\": \"Faça inversão de marcha em direção a {destination}\"\r\n }\r\n },\r\n \"depart\": {\r\n \"default\": {\r\n \"default\": \"Dirija-se para {direction}\",\r\n \"name\": \"Dirija-se para {direction} em {way_name}\",\r\n \"namedistance\": \"Dirija-se para {direction} em {way_name} por {distance}\"\r\n }\r\n },\r\n \"end of road\": {\r\n \"default\": {\r\n \"default\": \"Vire {modifier}\",\r\n \"name\": \"Vire {modifier} para {way_name}\",\r\n \"destination\": \"Vire {modifier} em direção a {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continue em frente\",\r\n \"name\": \"Continue em frente para {way_name}\",\r\n \"destination\": \"Continue em frente em direção a {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"No final da estrada faça uma inversão de marcha\",\r\n \"name\": \"No final da estrada faça uma inversão de marcha para {way_name} \",\r\n \"destination\": \"No final da estrada faça uma inversão de marcha em direção a {destination}\"\r\n }\r\n },\r\n \"fork\": {\r\n \"default\": {\r\n \"default\": \"Na bifurcação mantenha-se {modifier}\",\r\n \"name\": \"Mantenha-se {modifier} para {way_name}\",\r\n \"destination\": \"Mantenha-se {modifier} em direção a {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Na bifurcação mantenha-se à esquerda\",\r\n \"name\": \"Mantenha-se à esquerda para {way_name}\",\r\n \"destination\": \"Mantenha-se à esquerda em direção a {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Na bifurcação mantenha-se à direita\",\r\n \"name\": \"Mantenha-se à direita para {way_name}\",\r\n \"destination\": \"Mantenha-se à direita em direção a {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Na bifurcação vire acentuadamente à esquerda\",\r\n \"name\": \"Vire acentuadamente à esquerda para {way_name}\",\r\n \"destination\": \"Vire acentuadamente à esquerda em direção a {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Na bifurcação vire acentuadamente à direita\",\r\n \"name\": \"Vire acentuadamente à direita para {way_name}\",\r\n \"destination\": \"Vire acentuadamente à direita em direção a {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faça inversão de marcha\",\r\n \"name\": \"Faça inversão de marcha para {way_name}\",\r\n \"destination\": \"Faça inversão de marcha em direção a {destination}\"\r\n }\r\n },\r\n \"merge\": {\r\n \"default\": {\r\n \"default\": \"Una-se ao tráfego {modifier}\",\r\n \"name\": \"Una-se ao tráfego {modifier} para {way_name}\",\r\n \"destination\": \"Una-se ao tráfego {modifier} em direção a {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Una-se ao tráfego\",\r\n \"name\": \" Una-se ao tráfego para {way_name}\",\r\n \"destination\": \"Una-se ao tráfego em direção a {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Una-se ao tráfego à esquerda\",\r\n \"name\": \"Una-se ao tráfego à esquerda para {way_name}\",\r\n \"destination\": \"Una-se ao tráfego à esquerda em direção a {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Una-se ao tráfego à direita\",\r\n \"name\": \"Una-se ao tráfego à direita para {way_name}\",\r\n \"destination\": \"Una-se ao tráfego à direita em direção a {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Una-se ao tráfego à esquerda\",\r\n \"name\": \"Una-se ao tráfego à esquerda para {way_name}\",\r\n \"destination\": \"Una-se ao tráfego à esquerda em direção a {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Una-se ao tráfego à direita\",\r\n \"name\": \"Una-se ao tráfego à direita para {way_name}\",\r\n \"destination\": \"Una-se ao tráfego à direita em direção a {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faça inversão de marcha\",\r\n \"name\": \"Faça inversão de marcha para {way_name}\",\r\n \"destination\": \"Faça inversão de marcha em direção a {destination}\"\r\n }\r\n },\r\n \"new name\": {\r\n \"default\": {\r\n \"default\": \"Continue {modifier}\",\r\n \"name\": \"Continue {modifier} para {way_name}\",\r\n \"destination\": \"Continue {modifier} em direção a {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continue em frente\",\r\n \"name\": \"Continue para {way_name}\",\r\n \"destination\": \"Continue em direção a {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Vire acentuadamente à esquerda\",\r\n \"name\": \"Vire acentuadamente à esquerda para {way_name}\",\r\n \"destination\": \"Vire acentuadamente à esquerda em direção a{destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Vire acentuadamente à direita\",\r\n \"name\": \"Vire acentuadamente à direita para {way_name}\",\r\n \"destination\": \"Vire acentuadamente à direita em direção a {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Continue ligeiramente à esquerda\",\r\n \"name\": \"Continue ligeiramente à esquerda para {way_name}\",\r\n \"destination\": \"Continue ligeiramente à esquerda em direção a {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Continue ligeiramente à direita\",\r\n \"name\": \"Continue ligeiramente à direita para {way_name}\",\r\n \"destination\": \"Continue ligeiramente à direita em direção a {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faça inversão de marcha\",\r\n \"name\": \"Faça inversão de marcha para {way_name}\",\r\n \"destination\": \"Faça inversão de marcha em direção a {destination}\"\r\n }\r\n },\r\n \"notification\": {\r\n \"default\": {\r\n \"default\": \"Continue {modifier}\",\r\n \"name\": \"Continue {modifier} para {way_name}\",\r\n \"destination\": \"Continue {modifier} em direção a {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Faça inversão de marcha\",\r\n \"name\": \"Faça inversão de marcha para {way_name}\",\r\n \"destination\": \"Faça inversão de marcha em direção a {destination}\"\r\n }\r\n },\r\n \"off ramp\": {\r\n \"default\": {\r\n \"default\": \"Saia na saída\",\r\n \"name\": \"Saia na saída para {way_name}\",\r\n \"destination\": \"Saia na saída em direção a {destination}\",\r\n \"exit\": \"Saia na saída {exit}\",\r\n \"exit_destination\": \"Saia na saída {exit} em direção a {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Saia na saída à esquerda\",\r\n \"name\": \"Saia na saída à esquerda para {way_name}\",\r\n \"destination\": \"Saia na saída à esquerda em direção a {destination}\",\r\n \"exit\": \"Saia na saída {exit} à esquerda\",\r\n \"exit_destination\": \"Saia na saída {exit} à esquerda em direção a {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Saia na saída à direita\",\r\n \"name\": \"Saia na saída à direita para {way_name}\",\r\n \"destination\": \"Saia na saída à direita em direção a {destination}\",\r\n \"exit\": \"Saia na saída {exit} à direita\",\r\n \"exit_destination\": \"Saia na saída {exit} à direita em direção a {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Saia na saída à esquerda\",\r\n \"name\": \"Saia na saída à esquerda para {way_name}\",\r\n \"destination\": \"Saia na saída à esquerda em direção a {destination}\",\r\n \"exit\": \"Saia na saída {exit} à esquerda\",\r\n \"exit_destination\": \"Saia na saída {exit} à esquerda em direção a {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Saia na saída à direita\",\r\n \"name\": \"Saia na saída à direita para {way_name}\",\r\n \"destination\": \"Saia na saída à direita em direção a {destination}\",\r\n \"exit\": \"Saia na saída {exit} à direita\",\r\n \"exit_destination\": \"Saia na saída {exit} à direita em direção a {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Saia na saída à esquerda\",\r\n \"name\": \"Saia na saída à esquerda para {way_name}\",\r\n \"destination\": \"Saia na saída à esquerda em direção a {destination}\",\r\n \"exit\": \"Saia na saída {exit} à esquerda\",\r\n \"exit_destination\": \"Saia na saída {exit} à esquerda em direção a {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Saia na saída à direita\",\r\n \"name\": \"Saia na saída à direita para {way_name}\",\r\n \"destination\": \"Saia na saída à direita em direção a {destination}\",\r\n \"exit\": \"Saia na saída {exit} à direita\",\r\n \"exit_destination\": \"Saia na saída {exit} à direita em direção a {destination}\"\r\n }\r\n },\r\n \"on ramp\": {\r\n \"default\": {\r\n \"default\": \"Saia na saída\",\r\n \"name\": \"Saia na saída para {way_name}\",\r\n \"destination\": \"Saia na saída em direção a {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Saia na saída à esquerda\",\r\n \"name\": \"Saia na saída à esquerda para {way_name}\",\r\n \"destination\": \"Saia na saída à esquerda em direção a {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Saia na saída à direita\",\r\n \"name\": \"Saia na saída à direita para {way_name}\",\r\n \"destination\": \"Saia na saída à direita em direção a {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Saia na saída à esquerda\",\r\n \"name\": \"Saia na saída à esquerda para {way_name}\",\r\n \"destination\": \"Saia na saída à esquerda em direção a {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Saia na saída à direita\",\r\n \"name\": \"Saia na saída à direita para {way_name}\",\r\n \"destination\": \"Saia na saída à direita em direção a {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Saia na saída à esquerda\",\r\n \"name\": \"Saia na saída à esquerda para {way_name}\",\r\n \"destination\": \"Saia na saída à esquerda em direção a {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Saia na saída à direita\",\r\n \"name\": \"Saia na saída à direita para {way_name}\",\r\n \"destination\": \"Saia na saída à direita em direção a {destination}\"\r\n }\r\n },\r\n \"rotary\": {\r\n \"default\": {\r\n \"default\": {\r\n \"default\": \"Entre na rotunda\",\r\n \"name\": \"Entre na rotunda e saia para {way_name}\",\r\n \"destination\": \"Entre na rotunda e saia em direção a {destination}\"\r\n },\r\n \"name\": {\r\n \"default\": \"Entre em {rotary_name}\",\r\n \"name\": \"Entre em {rotary_name} e saia para {way_name}\",\r\n \"destination\": \"Entre em {rotary_name} e saia em direção a {destination}\"\r\n },\r\n \"exit\": {\r\n \"default\": \"Entre na rotunda e saia na saída {exit_number}\",\r\n \"name\": \"Entre na rotunda e saia na saída {exit_number} para {way_name}\",\r\n \"destination\": \"Entre na rotunda e saia na saída {exit_number} em direção a {destination}\"\r\n },\r\n \"name_exit\": {\r\n \"default\": \"Entre em {rotary_name} e saia na saída {exit_number}\",\r\n \"name\": \"Entre em {rotary_name} e saia na saída {exit_number} para {way_name}\",\r\n \"destination\": \"Entre em{rotary_name} e saia na saída {exit_number} em direção a {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout\": {\r\n \"default\": {\r\n \"exit\": {\r\n \"default\": \"Entre na rotunda e saia na saída {exit_number}\",\r\n \"name\": \"Entre na rotunda e saia na saída {exit_number} para {way_name}\",\r\n \"destination\": \"Entre na rotunda e saia na saída {exit_number} em direção a {destination}\"\r\n },\r\n \"default\": {\r\n \"default\": \"Entre na rotunda\",\r\n \"name\": \"Entre na rotunda e saia para {way_name}\",\r\n \"destination\": \"Entre na rotunda e saia em direção a {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout turn\": {\r\n \"default\": {\r\n \"default\": \"Siga {modifier}\",\r\n \"name\": \"Siga {modifier} para {way_name}\",\r\n \"destination\": \"Siga {modifier} em direção a {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Vire à esquerda\",\r\n \"name\": \"Vire à esquerda para {way_name}\",\r\n \"destination\": \"Vire à esquerda em direção a {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Vire à direita\",\r\n \"name\": \"Vire à direita para {way_name}\",\r\n \"destination\": \"Vire à direita em direção a {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Continue em frente\",\r\n \"name\": \"Continue em frente para {way_name}\",\r\n \"destination\": \"Continue em frente em direção a {destination}\"\r\n }\r\n },\r\n \"exit roundabout\": {\r\n \"default\": {\r\n \"default\": \"Saia da rotunda\",\r\n \"name\": \"Saia da rotunda para {way_name}\",\r\n \"destination\": \"Saia da rotunda em direção a {destination}\"\r\n }\r\n },\r\n \"exit rotary\": {\r\n \"default\": {\r\n \"default\": \"Saia da rotunda\",\r\n \"name\": \"Saia da rotunda para {way_name}\",\r\n \"destination\": \"Saia da rotunda em direção a {destination}\"\r\n }\r\n },\r\n \"turn\": {\r\n \"default\": {\r\n \"default\": \"Siga {modifier}\",\r\n \"name\": \"Siga {modifier} para{way_name}\",\r\n \"destination\": \"Siga {modifier} em direção a {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Vire à esquerda\",\r\n \"name\": \"Vire à esquerda para {way_name}\",\r\n \"destination\": \"Vire à esquerda em direção a {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Vire à direita\",\r\n \"name\": \"Vire à direita para {way_name}\",\r\n \"destination\": \"Vire à direita em direção a {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Vá em frente\",\r\n \"name\": \"Vá em frente para {way_name}\",\r\n \"destination\": \"Vá em frente em direção a {destination}\"\r\n }\r\n },\r\n \"use lane\": {\r\n \"no_lanes\": {\r\n \"default\": \"Continue em frente\"\r\n },\r\n \"default\": {\r\n \"default\": \"{lane_instruction}\"\r\n }\r\n }\r\n }\r\n}\r\n\r\n }, {}],\r\n 47: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"meta\": {\r\n \"capitalizeFirstLetter\": true\r\n },\r\n \"v5\": {\r\n \"constants\": {\r\n \"ordinalize\": {\r\n \"1\": \"первый\",\r\n \"2\": \"второй\",\r\n \"3\": \"третий\",\r\n \"4\": \"четвёртый\",\r\n \"5\": \"пятый\",\r\n \"6\": \"шестой\",\r\n \"7\": \"седьмой\",\r\n \"8\": \"восьмой\",\r\n \"9\": \"девятый\",\r\n \"10\": \"десятый\"\r\n },\r\n \"direction\": {\r\n \"north\": \"северном\",\r\n \"northeast\": \"северо-восточном\",\r\n \"east\": \"восточном\",\r\n \"southeast\": \"юго-восточном\",\r\n \"south\": \"южном\",\r\n \"southwest\": \"юго-западном\",\r\n \"west\": \"западном\",\r\n \"northwest\": \"северо-западном\"\r\n },\r\n \"modifier\": {\r\n \"left\": \"налево\",\r\n \"right\": \"направо\",\r\n \"sharp left\": \"налево\",\r\n \"sharp right\": \"направо\",\r\n \"slight left\": \"левее\",\r\n \"slight right\": \"правее\",\r\n \"straight\": \"прямо\",\r\n \"uturn\": \"на разворот\"\r\n },\r\n \"lanes\": {\r\n \"xo\": \"Держитесь правее\",\r\n \"ox\": \"Держитесь левее\",\r\n \"xox\": \"Держитесь посередине\",\r\n \"oxo\": \"Держитесь слева или справа\"\r\n }\r\n },\r\n \"modes\": {\r\n \"ferry\": {\r\n \"default\": \"Погрузитесь на паром\",\r\n \"name\": \"Погрузитесь на паром {way_name}\",\r\n \"destination\": \"Погрузитесь на паром в направлении {destination}\"\r\n }\r\n },\r\n \"phrase\": {\r\n \"two linked by distance\": \"{instruction_one}, затем через {distance} {instruction_two}\",\r\n \"two linked\": \"{instruction_one}, затем {instruction_two}\",\r\n \"one in distance\": \"Через {distance} {instruction_one}\",\r\n \"name and ref\": \"{name} ({ref})\",\r\n \"exit with number\": \"съезд {exit}\"\r\n },\r\n \"arrive\": {\r\n \"default\": {\r\n \"default\": \"Вы прибыли в {nth} пункт назначения\",\r\n \"upcoming\": \"Вы прибываете в {nth} пункт назначения\",\r\n \"short\": \"Вы прибыли\",\r\n \"short-upcoming\": \"Вы скоро прибудете\",\r\n \"named\": \"Вы прибыли в пункт назначения, {waypoint_name}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Вы прибыли в {nth} пункт назначения, он находится слева\",\r\n \"upcoming\": \"Вы прибываете в {nth} пункт назначения, он будет слева\",\r\n \"short\": \"Вы прибыли\",\r\n \"short-upcoming\": \"Вы скоро прибудете\",\r\n \"named\": \"Вы прибыли в пункт назначения, {waypoint_name}, он находится слева\"\r\n },\r\n \"right\": {\r\n \"default\": \"Вы прибыли в {nth} пункт назначения, он находится справа\",\r\n \"upcoming\": \"Вы прибываете в {nth} пункт назначения, он будет справа\",\r\n \"short\": \"Вы прибыли\",\r\n \"short-upcoming\": \"Вы скоро прибудете\",\r\n \"named\": \"Вы прибыли в пункт назначения, {waypoint_name}, он находится справа\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Вы прибыли в {nth} пункт назначения, он находится слева сзади\",\r\n \"upcoming\": \"Вы прибываете в {nth} пункт назначения, он будет слева сзади\",\r\n \"short\": \"Вы прибыли\",\r\n \"short-upcoming\": \"Вы скоро прибудете\",\r\n \"named\": \"Вы прибыли в пункт назначения, {waypoint_name}, он находится слева сзади\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Вы прибыли в {nth} пункт назначения, он находится справа сзади\",\r\n \"upcoming\": \"Вы прибываете в {nth} пункт назначения, он будет справа сзади\",\r\n \"short\": \"Вы прибыли\",\r\n \"short-upcoming\": \"Вы скоро прибудете\",\r\n \"named\": \"Вы прибыли в пункт назначения, {waypoint_name}, он находится справа сзади\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Вы прибыли в {nth} пункт назначения, он находится справа впереди\",\r\n \"upcoming\": \"Вы прибываете в {nth} пункт назначения, он будет справа впереди\",\r\n \"short\": \"Вы прибыли\",\r\n \"short-upcoming\": \"Вы скоро прибудете\",\r\n \"named\": \"Вы прибыли в пункт назначения, {waypoint_name}, он находится справа впереди\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Вы прибыли в {nth} пункт назначения, он находится слева впереди\",\r\n \"upcoming\": \"Вы прибываете в {nth} пункт назначения, он будет слева впереди\",\r\n \"short\": \"Вы прибыли\",\r\n \"short-upcoming\": \"Вы скоро прибудете\",\r\n \"named\": \"Вы прибыли в пункт назначения, {waypoint_name}, он находится слева впереди\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Вы прибыли в {nth} пункт назначения, он находится перед вами\",\r\n \"upcoming\": \"Вы прибываете в {nth} пункт назначения, он будет перед вами\",\r\n \"short\": \"Вы прибыли\",\r\n \"short-upcoming\": \"Вы скоро прибудете\",\r\n \"named\": \"Вы прибыли в пункт назначения, {waypoint_name}, он находится перед Вами\"\r\n }\r\n },\r\n \"continue\": {\r\n \"default\": {\r\n \"default\": \"Двигайтесь {modifier}\",\r\n \"name\": \"Двигайтесь {modifier} по {way_name:dative}\",\r\n \"destination\": \"Двигайтесь {modifier} в направлении {destination}\",\r\n \"exit\": \"Двигайтесь {modifier} на {way_name:accusative}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Двигайтесь прямо\",\r\n \"name\": \"Продолжите движение по {way_name:dative}\",\r\n \"destination\": \"Продолжите движение в направлении {destination}\",\r\n \"distance\": \"Двигайтесь прямо {distance}\",\r\n \"namedistance\": \"Двигайтесь прямо {distance} по {way_name:dative}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Резко поверните налево\",\r\n \"name\": \"Резко поверните налево на {way_name:accusative}\",\r\n \"destination\": \"Резко поверните налево в направлении {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Резко поверните направо\",\r\n \"name\": \"Резко поверните направо на {way_name:accusative}\",\r\n \"destination\": \"Резко поверните направо в направлении {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Плавно поверните налево\",\r\n \"name\": \"Плавно поверните налево на {way_name:accusative}\",\r\n \"destination\": \"Плавно поверните налево в направлении {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Плавно поверните направо\",\r\n \"name\": \"Плавно поверните направо на {way_name:accusative}\",\r\n \"destination\": \"Плавно поверните направо в направлении {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Развернитесь\",\r\n \"name\": \"Развернитесь и продолжите движение по {way_name:dative}\",\r\n \"destination\": \"Развернитесь в направлении {destination}\"\r\n }\r\n },\r\n \"depart\": {\r\n \"default\": {\r\n \"default\": \"Двигайтесь в {direction} направлении\",\r\n \"name\": \"Двигайтесь в {direction} направлении по {way_name:dative}\",\r\n \"namedistance\": \"Двигайтесь {distance} в {direction} направлении по {way_name:dative}\"\r\n }\r\n },\r\n \"end of road\": {\r\n \"default\": {\r\n \"default\": \"Поверните {modifier}\",\r\n \"name\": \"Поверните {modifier} на {way_name:accusative}\",\r\n \"destination\": \"Поверните {modifier} в направлении {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Двигайтесь прямо\",\r\n \"name\": \"Двигайтесь прямо по {way_name:dative}\",\r\n \"destination\": \"Двигайтесь прямо в направлении {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"В конце дороги развернитесь\",\r\n \"name\": \"Развернитесь в конце {way_name:genitive}\",\r\n \"destination\": \"В конце дороги развернитесь в направлении {destination}\"\r\n }\r\n },\r\n \"fork\": {\r\n \"default\": {\r\n \"default\": \"На развилке двигайтесь {modifier}\",\r\n \"name\": \"На развилке двигайтесь {modifier} на {way_name:accusative}\",\r\n \"destination\": \"На развилке двигайтесь {modifier} в направлении {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"На развилке держитесь левее\",\r\n \"name\": \"На развилке держитесь левее на {way_name:accusative}\",\r\n \"destination\": \"На развилке держитесь левее и продолжите движение в направлении {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"На развилке держитесь правее\",\r\n \"name\": \"На развилке держитесь правее на {way_name:accusative}\",\r\n \"destination\": \"На развилке держитесь правее и продолжите движение в направлении {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"На развилке резко поверните налево\",\r\n \"name\": \"Резко поверните налево на {way_name:accusative}\",\r\n \"destination\": \"Резко поверните налево и продолжите движение в направлении {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"На развилке резко поверните направо\",\r\n \"name\": \"Резко поверните направо на {way_name:accusative}\",\r\n \"destination\": \"Резко поверните направо и продолжите движение в направлении {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"На развилке развернитесь\",\r\n \"name\": \"На развилке развернитесь на {way_name:prepositional}\",\r\n \"destination\": \"На развилке развернитесь и продолжите движение в направлении {destination}\"\r\n }\r\n },\r\n \"merge\": {\r\n \"default\": {\r\n \"default\": \"Перестройтесь {modifier}\",\r\n \"name\": \"Перестройтесь {modifier} на {way_name:accusative}\",\r\n \"destination\": \"Перестройтесь {modifier} в направлении {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Двигайтесь прямо\",\r\n \"name\": \"Продолжите движение по {way_name:dative}\",\r\n \"destination\": \"Продолжите движение в направлении {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Перестройтесь левее\",\r\n \"name\": \"Перестройтесь левее на {way_name:accusative}\",\r\n \"destination\": \"Перестройтесь левее в направлении {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Перестройтесь правее\",\r\n \"name\": \"Перестройтесь правее на {way_name:accusative}\",\r\n \"destination\": \"Перестройтесь правее в направлении {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Перестраивайтесь левее\",\r\n \"name\": \"Перестраивайтесь левее на {way_name:accusative}\",\r\n \"destination\": \"Перестраивайтесь левее в направлении {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Перестраивайтесь правее\",\r\n \"name\": \"Перестраивайтесь правее на {way_name:accusative}\",\r\n \"destination\": \"Перестраивайтесь правее в направлении {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Развернитесь\",\r\n \"name\": \"Развернитесь на {way_name:prepositional}\",\r\n \"destination\": \"Развернитесь в направлении {destination}\"\r\n }\r\n },\r\n \"new name\": {\r\n \"default\": {\r\n \"default\": \"Двигайтесь {modifier}\",\r\n \"name\": \"Двигайтесь {modifier} на {way_name:accusative}\",\r\n \"destination\": \"Двигайтесь {modifier} в направлении {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Двигайтесь прямо\",\r\n \"name\": \"Продолжите движение по {way_name:dative}\",\r\n \"destination\": \"Продолжите движение в направлении {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Резко поверните налево\",\r\n \"name\": \"Резко поверните налево на {way_name:accusative}\",\r\n \"destination\": \"Резко поверните налево и продолжите движение в направлении {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Резко поверните направо\",\r\n \"name\": \"Резко поверните направо на {way_name:accusative}\",\r\n \"destination\": \"Резко поверните направо и продолжите движение в направлении {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Плавно поверните налево\",\r\n \"name\": \"Плавно поверните налево на {way_name:accusative}\",\r\n \"destination\": \"Плавно поверните налево в направлении {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Плавно поверните направо\",\r\n \"name\": \"Плавно поверните направо на {way_name:accusative}\",\r\n \"destination\": \"Плавно поверните направо в направлении {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Развернитесь\",\r\n \"name\": \"Развернитесь на {way_name:prepositional}\",\r\n \"destination\": \"Развернитесь и продолжите движение в направлении {destination}\"\r\n }\r\n },\r\n \"notification\": {\r\n \"default\": {\r\n \"default\": \"Двигайтесь {modifier}\",\r\n \"name\": \"Двигайтесь {modifier} по {way_name:dative}\",\r\n \"destination\": \"Двигайтесь {modifier} в направлении {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Развернитесь\",\r\n \"name\": \"Развернитесь на {way_name:prepositional}\",\r\n \"destination\": \"Развернитесь и продолжите движение в направлении {destination}\"\r\n }\r\n },\r\n \"off ramp\": {\r\n \"default\": {\r\n \"default\": \"Сверните на съезд\",\r\n \"name\": \"Сверните на съезд на {way_name:accusative}\",\r\n \"destination\": \"Сверните на съезд в направлении {destination}\",\r\n \"exit\": \"Сверните на съезд {exit}\",\r\n \"exit_destination\": \"Сверните на съезд {exit} в направлении {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Сверните на левый съезд\",\r\n \"name\": \"Сверните на левый съезд на {way_name:accusative}\",\r\n \"destination\": \"Сверните на левый съезд в направлении {destination}\",\r\n \"exit\": \"Сверните на съезд {exit} слева\",\r\n \"exit_destination\": \"Сверните на съезд {exit} слева в направлении {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Сверните на правый съезд\",\r\n \"name\": \"Сверните на правый съезд на {way_name:accusative}\",\r\n \"destination\": \"Сверните на правый съезд в направлении {destination}\",\r\n \"exit\": \"Сверните на съезд {exit} справа\",\r\n \"exit_destination\": \"Сверните на съезд {exit} справа в направлении {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Поверните налево на съезд\",\r\n \"name\": \"Поверните налево на съезд на {way_name:accusative}\",\r\n \"destination\": \"Поверните налево на съезд в направлении {destination}\",\r\n \"exit\": \"Поверните налево на съезд {exit}\",\r\n \"exit_destination\": \"Поверните налево на съезд {exit} в направлении {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Поверните направо на съезд\",\r\n \"name\": \"Поверните направо на съезд на {way_name:accusative}\",\r\n \"destination\": \"Поверните направо на съезд в направлении {destination}\",\r\n \"exit\": \"Поверните направо на съезд {exit}\",\r\n \"exit_destination\": \"Поверните направо на съезд {exit} в направлении {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Перестройтесь левее на съезд\",\r\n \"name\": \"Перестройтесь левее на съезд на {way_name:accusative}\",\r\n \"destination\": \"Перестройтесь левее на съезд в направлении {destination}\",\r\n \"exit\": \"Перестройтесь левее на {exit}\",\r\n \"exit_destination\": \"Перестройтесь левее на съезд {exit} в направлении {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Перестройтесь правее на съезд\",\r\n \"name\": \"Перестройтесь правее на съезд на {way_name:accusative}\",\r\n \"destination\": \"Перестройтесь правее на съезд в направлении {destination}\",\r\n \"exit\": \"Перестройтесь правее на съезд {exit}\",\r\n \"exit_destination\": \"Перестройтесь правее на съезд {exit} в направлении {destination}\"\r\n }\r\n },\r\n \"on ramp\": {\r\n \"default\": {\r\n \"default\": \"Сверните на автомагистраль\",\r\n \"name\": \"Сверните на въезд на {way_name:accusative}\",\r\n \"destination\": \"Сверните на въезд на автомагистраль в направлении {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Сверните на левый въезд на автомагистраль\",\r\n \"name\": \"Сверните на левый въезд на {way_name:accusative}\",\r\n \"destination\": \"Сверните на левый въезд на автомагистраль в направлении {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Сверните на правый въезд на автомагистраль\",\r\n \"name\": \"Сверните на правый въезд на {way_name:accusative}\",\r\n \"destination\": \"Сверните на правый въезд на автомагистраль в направлении {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Поверните на левый въезд на автомагистраль\",\r\n \"name\": \"Поверните на левый въезд на {way_name:accusative}\",\r\n \"destination\": \"Поверните на левый въезд на автомагистраль в направлении {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Поверните на правый въезд на автомагистраль\",\r\n \"name\": \"Поверните на правый въезд на {way_name:accusative}\",\r\n \"destination\": \"Поверните на правый въезд на автомагистраль в направлении {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Перестройтесь левее на въезд на автомагистраль\",\r\n \"name\": \"Перестройтесь левее на {way_name:accusative}\",\r\n \"destination\": \"Перестройтесь левее на автомагистраль в направлении {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Перестройтесь правее на въезд на автомагистраль\",\r\n \"name\": \"Перестройтесь правее на {way_name:accusative}\",\r\n \"destination\": \"Перестройтесь правее на автомагистраль в направлении {destination}\"\r\n }\r\n },\r\n \"rotary\": {\r\n \"default\": {\r\n \"default\": {\r\n \"default\": \"Продолжите движение по круговой развязке\",\r\n \"name\": \"На круговой развязке сверните на {way_name:accusative}\",\r\n \"destination\": \"На круговой развязке сверните в направлении {destination}\"\r\n },\r\n \"name\": {\r\n \"default\": \"Продолжите движение по {rotary_name:dative}\",\r\n \"name\": \"На {rotary_name:prepositional} сверните на {way_name:accusative}\",\r\n \"destination\": \"На {rotary_name:prepositional} сверните в направлении {destination}\"\r\n },\r\n \"exit\": {\r\n \"default\": \"На круговой развязке сверните на {exit_number} съезд\",\r\n \"name\": \"На круговой развязке сверните на {exit_number} съезд на {way_name:accusative}\",\r\n \"destination\": \"На круговой развязке сверните на {exit_number} съезд в направлении {destination}\"\r\n },\r\n \"name_exit\": {\r\n \"default\": \"На {rotary_name:prepositional} сверните на {exit_number} съезд\",\r\n \"name\": \"На {rotary_name:prepositional} сверните на {exit_number} съезд на {way_name:accusative}\",\r\n \"destination\": \"На {rotary_name:prepositional} сверните на {exit_number} съезд в направлении {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout\": {\r\n \"default\": {\r\n \"exit\": {\r\n \"default\": \"На круговой развязке сверните на {exit_number} съезд\",\r\n \"name\": \"На круговой развязке сверните на {exit_number} съезд на {way_name:accusative}\",\r\n \"destination\": \"На круговой развязке сверните на {exit_number} съезд в направлении {destination}\"\r\n },\r\n \"default\": {\r\n \"default\": \"Продолжите движение по круговой развязке\",\r\n \"name\": \"На круговой развязке сверните на {way_name:accusative}\",\r\n \"destination\": \"На круговой развязке сверните в направлении {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout turn\": {\r\n \"default\": {\r\n \"default\": \"Двигайтесь {modifier}\",\r\n \"name\": \"Двигайтесь {modifier} на {way_name:accusative}\",\r\n \"destination\": \"Двигайтесь {modifier} в направлении {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Сверните налево\",\r\n \"name\": \"Сверните налево на {way_name:accusative}\",\r\n \"destination\": \"Сверните налево в направлении {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Сверните направо\",\r\n \"name\": \"Сверните направо на {way_name:accusative}\",\r\n \"destination\": \"Сверните направо в направлении {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Двигайтесь прямо\",\r\n \"name\": \"Двигайтесь прямо по {way_name:dative}\",\r\n \"destination\": \"Двигайтесь прямо в направлении {destination}\"\r\n }\r\n },\r\n \"exit roundabout\": {\r\n \"default\": {\r\n \"default\": \"Сверните с круговой развязки\",\r\n \"name\": \"Сверните с круговой развязки на {way_name:accusative}\",\r\n \"destination\": \"Сверните с круговой развязки в направлении {destination}\"\r\n }\r\n },\r\n \"exit rotary\": {\r\n \"default\": {\r\n \"default\": \"Сверните с круговой развязки\",\r\n \"name\": \"Сверните с круговой развязки на {way_name:accusative}\",\r\n \"destination\": \"Сверните с круговой развязки в направлении {destination}\"\r\n }\r\n },\r\n \"turn\": {\r\n \"default\": {\r\n \"default\": \"Двигайтесь {modifier}\",\r\n \"name\": \"Двигайтесь {modifier} на {way_name:accusative}\",\r\n \"destination\": \"Двигайтесь {modifier} в направлении {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Поверните налево\",\r\n \"name\": \"Поверните налево на {way_name:accusative}\",\r\n \"destination\": \"Поверните налево в направлении {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Поверните направо\",\r\n \"name\": \"Поверните направо на {way_name:accusative}\",\r\n \"destination\": \"Поверните направо в направлении {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Двигайтесь прямо\",\r\n \"name\": \"Двигайтесь по {way_name:dative}\",\r\n \"destination\": \"Двигайтесь в направлении {destination}\"\r\n }\r\n },\r\n \"use lane\": {\r\n \"no_lanes\": {\r\n \"default\": \"Продолжайте движение прямо\"\r\n },\r\n \"default\": {\r\n \"default\": \"{lane_instruction}\"\r\n }\r\n }\r\n }\r\n}\r\n\r\n }, {}],\r\n 52: [function (_dereq_, module, exports) {\r\nmodule.exports={\r\n \"meta\": {\r\n \"capitalizeFirstLetter\": true\r\n },\r\n \"v5\": {\r\n \"constants\": {\r\n \"ordinalize\": {\r\n \"1\": \"đầu tiên\",\r\n \"2\": \"thứ 2\",\r\n \"3\": \"thứ 3\",\r\n \"4\": \"thứ 4\",\r\n \"5\": \"thứ 5\",\r\n \"6\": \"thú 6\",\r\n \"7\": \"thứ 7\",\r\n \"8\": \"thứ 8\",\r\n \"9\": \"thứ 9\",\r\n \"10\": \"thứ 10\"\r\n },\r\n \"direction\": {\r\n \"north\": \"bắc\",\r\n \"northeast\": \"đông bắc\",\r\n \"east\": \"đông\",\r\n \"southeast\": \"đông nam\",\r\n \"south\": \"nam\",\r\n \"southwest\": \"tây nam\",\r\n \"west\": \"tây\",\r\n \"northwest\": \"tây bắc\"\r\n },\r\n \"modifier\": {\r\n \"left\": \"trái\",\r\n \"right\": \"phải\",\r\n \"sharp left\": \"trái gắt\",\r\n \"sharp right\": \"phải gắt\",\r\n \"slight left\": \"trái nghiêng\",\r\n \"slight right\": \"phải nghiêng\",\r\n \"straight\": \"thẳng\",\r\n \"uturn\": \"ngược\"\r\n },\r\n \"lanes\": {\r\n \"xo\": \"Đi bên phải\",\r\n \"ox\": \"Đi bên trái\",\r\n \"xox\": \"Đi vào giữa\",\r\n \"oxo\": \"Đi bên trái hay bên phải\"\r\n }\r\n },\r\n \"modes\": {\r\n \"ferry\": {\r\n \"default\": \"Lên phà\",\r\n \"name\": \"Lên phà {way_name}\",\r\n \"destination\": \"Lên phà đi {destination}\"\r\n }\r\n },\r\n \"phrase\": {\r\n \"two linked by distance\": \"{instruction_one}, rồi {distance} nữa thì {instruction_two}\",\r\n \"two linked\": \"{instruction_one}, rồi {instruction_two}\",\r\n \"one in distance\": \"{distance} nữa thì {instruction_one}\",\r\n \"name and ref\": \"{name} ({ref})\",\r\n \"exit with number\": \"lối ra {exit}\"\r\n },\r\n \"arrive\": {\r\n \"default\": {\r\n \"default\": \"Đến nơi {nth}\",\r\n \"upcoming\": \"Đến nơi {nth}\",\r\n \"short\": \"Đến nơi\",\r\n \"short-upcoming\": \"Đến nơi\",\r\n \"named\": \"Đến {waypoint_name}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Đến nơi {nth} ở bên trái\",\r\n \"upcoming\": \"Đến nơi {nth} ở bên trái\",\r\n \"short\": \"Đến nơi\",\r\n \"short-upcoming\": \"Đến nơi\",\r\n \"named\": \"Đến {waypoint_name} ở bên trái\"\r\n },\r\n \"right\": {\r\n \"default\": \"Đến nơi {nth} ở bên phải\",\r\n \"upcoming\": \"Đến nơi {nth} ở bên phải\",\r\n \"short\": \"Đến nơi\",\r\n \"short-upcoming\": \"Đến nơi\",\r\n \"named\": \"Đến {waypoint_name} ở bên phải\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Đến nơi {nth} ở bên trái\",\r\n \"upcoming\": \"Đến nơi {nth} ở bên trái\",\r\n \"short\": \"Đến nơi\",\r\n \"short-upcoming\": \"Đến nơi\",\r\n \"named\": \"Đến {waypoint_name} ở bên trái\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Đến nơi {nth} ở bên phải\",\r\n \"upcoming\": \"Đến nơi {nth} ở bên phải\",\r\n \"short\": \"Đến nơi\",\r\n \"short-upcoming\": \"Đến nơi\",\r\n \"named\": \"Đến {waypoint_name} ở bên phải\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Đến nơi {nth} ở bên phải\",\r\n \"upcoming\": \"Đến nơi {nth} ở bên phải\",\r\n \"short\": \"Đến nơi\",\r\n \"short-upcoming\": \"Đến nơi\",\r\n \"named\": \"Đến {waypoint_name} ở bên phải\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Đến nơi {nth} ở bên trái\",\r\n \"upcoming\": \"Đến nơi {nth} ở bên trái\",\r\n \"short\": \"Đến nơi\",\r\n \"short-upcoming\": \"Đến nơi\",\r\n \"named\": \"Đến {waypoint_name} ở bên trái\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Đến nơi {nth} ở trước mặt\",\r\n \"upcoming\": \"Đến nơi {nth} ở trước mặt\",\r\n \"short\": \"Đến nơi\",\r\n \"short-upcoming\": \"Đến nơi\",\r\n \"named\": \"Đến {waypoint_name} ở trước mặt\"\r\n }\r\n },\r\n \"continue\": {\r\n \"default\": {\r\n \"default\": \"Quẹo {modifier}\",\r\n \"name\": \"Quẹo {modifier} để chạy tiếp trên {way_name}\",\r\n \"destination\": \"Quẹo {modifier} về {destination}\",\r\n \"exit\": \"Quẹo {modifier} vào {way_name}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Chạy thẳng\",\r\n \"name\": \"Chạy tiếp trên {way_name}\",\r\n \"destination\": \"Chạy tiếp về {destination}\",\r\n \"distance\": \"Chạy thẳng cho {distance}\",\r\n \"namedistance\": \"Chạy tiếp trên {way_name} cho {distance}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Quẹo gắt bên trái\",\r\n \"name\": \"Quẹo gắt bên trái để chạy tiếp trên {way_name}\",\r\n \"destination\": \"Quẹo gắt bên trái về {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Quẹo gắt bên phải\",\r\n \"name\": \"Quẹo gắt bên phải để chạy tiếp trên {way_name}\",\r\n \"destination\": \"Quẹo gắt bên phải về {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Nghiêng về bên trái\",\r\n \"name\": \"Nghiêng về bên trái để chạy tiếp trên {way_name}\",\r\n \"destination\": \"Nghiêng về bên trái về {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Nghiêng về bên phải\",\r\n \"name\": \"Nghiêng về bên phải để chạy tiếp trên {way_name}\",\r\n \"destination\": \"Nghiêng về bên phải về {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Quẹo ngược lại\",\r\n \"name\": \"Quẹo ngược lại trên {way_name}\",\r\n \"destination\": \"Quẹo ngược về {destination}\"\r\n }\r\n },\r\n \"depart\": {\r\n \"default\": {\r\n \"default\": \"Đi về hướng {direction}\",\r\n \"name\": \"Đi về hướng {direction} trên {way_name}\",\r\n \"namedistance\": \"Đi về hướng {direction} trên {way_name} cho {distance}\"\r\n }\r\n },\r\n \"end of road\": {\r\n \"default\": {\r\n \"default\": \"Quẹo {modifier}\",\r\n \"name\": \"Quẹo {modifier} vào {way_name}\",\r\n \"destination\": \"Quẹo {modifier} về {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Chạy thẳng\",\r\n \"name\": \"Chạy tiếp trên {way_name}\",\r\n \"destination\": \"Chạy tiếp về {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Quẹo ngược lại tại cuối đường\",\r\n \"name\": \"Quẹo ngược vào {way_name} tại cuối đường\",\r\n \"destination\": \"Quẹo ngược về {destination} tại cuối đường\"\r\n }\r\n },\r\n \"fork\": {\r\n \"default\": {\r\n \"default\": \"Đi bên {modifier} ở ngã ba\",\r\n \"name\": \"Giữ bên {modifier} vào {way_name}\",\r\n \"destination\": \"Giữ bên {modifier} về {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Nghiêng về bên trái ở ngã ba\",\r\n \"name\": \"Giữ bên trái vào {way_name}\",\r\n \"destination\": \"Giữ bên trái về {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Nghiêng về bên phải ở ngã ba\",\r\n \"name\": \"Giữ bên phải vào {way_name}\",\r\n \"destination\": \"Giữ bên phải về {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Quẹo gắt bên trái ở ngã ba\",\r\n \"name\": \"Quẹo gắt bên trái vào {way_name}\",\r\n \"destination\": \"Quẹo gắt bên trái về {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Quẹo gắt bên phải ở ngã ba\",\r\n \"name\": \"Quẹo gắt bên phải vào {way_name}\",\r\n \"destination\": \"Quẹo gắt bên phải về {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Quẹo ngược lại\",\r\n \"name\": \"Quẹo ngược lại {way_name}\",\r\n \"destination\": \"Quẹo ngược lại về {destination}\"\r\n }\r\n },\r\n \"merge\": {\r\n \"default\": {\r\n \"default\": \"Nhập sang {modifier}\",\r\n \"name\": \"Nhập sang {modifier} vào {way_name}\",\r\n \"destination\": \"Nhập sang {modifier} về {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Nhập đường\",\r\n \"name\": \"Nhập vào {way_name}\",\r\n \"destination\": \"Nhập đường về {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Nhập sang trái\",\r\n \"name\": \"Nhập sang trái vào {way_name}\",\r\n \"destination\": \"Nhập sang trái về {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Nhập sang phải\",\r\n \"name\": \"Nhập sang phải vào {way_name}\",\r\n \"destination\": \"Nhập sang phải về {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Nhập sang trái\",\r\n \"name\": \"Nhập sang trái vào {way_name}\",\r\n \"destination\": \"Nhập sang trái về {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Nhập sang phải\",\r\n \"name\": \"Nhập sang phải vào {way_name}\",\r\n \"destination\": \"Nhập sang phải về {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Quẹo ngược lại\",\r\n \"name\": \"Quẹo ngược lại {way_name}\",\r\n \"destination\": \"Quẹo ngược lại về {destination}\"\r\n }\r\n },\r\n \"new name\": {\r\n \"default\": {\r\n \"default\": \"Chạy tiếp bên {modifier}\",\r\n \"name\": \"Chạy tiếp bên {modifier} trên {way_name}\",\r\n \"destination\": \"Chạy tiếp bên {modifier} về {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Chạy thẳng\",\r\n \"name\": \"Chạy tiếp trên {way_name}\",\r\n \"destination\": \"Chạy tiếp về {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Quẹo gắt bên trái\",\r\n \"name\": \"Quẹo gắt bên trái vào {way_name}\",\r\n \"destination\": \"Quẹo gắt bên trái về {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Quẹo gắt bên phải\",\r\n \"name\": \"Quẹo gắt bên phải vào {way_name}\",\r\n \"destination\": \"Quẹo gắt bên phải về {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Nghiêng về bên trái\",\r\n \"name\": \"Nghiêng về bên trái vào {way_name}\",\r\n \"destination\": \"Nghiêng về bên trái về {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Nghiêng về bên phải\",\r\n \"name\": \"Nghiêng về bên phải vào {way_name}\",\r\n \"destination\": \"Nghiêng về bên phải về {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Quẹo ngược lại\",\r\n \"name\": \"Quẹo ngược lại {way_name}\",\r\n \"destination\": \"Quẹo ngược lại về {destination}\"\r\n }\r\n },\r\n \"notification\": {\r\n \"default\": {\r\n \"default\": \"Chạy tiếp bên {modifier}\",\r\n \"name\": \"Chạy tiếp bên {modifier} trên {way_name}\",\r\n \"destination\": \"Chạy tiếp bên {modifier} về {destination}\"\r\n },\r\n \"uturn\": {\r\n \"default\": \"Quẹo ngược lại\",\r\n \"name\": \"Quẹo ngược lại {way_name}\",\r\n \"destination\": \"Quẹo ngược lại về {destination}\"\r\n }\r\n },\r\n \"off ramp\": {\r\n \"default\": {\r\n \"default\": \"Đi đường nhánh\",\r\n \"name\": \"Đi đường nhánh {way_name}\",\r\n \"destination\": \"Đi đường nhánh về {destination}\",\r\n \"exit\": \"Đi theo lối ra {exit}\",\r\n \"exit_destination\": \"Đi theo lối ra {exit} về {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Đi đường nhánh bên trái\",\r\n \"name\": \"Đi đường nhánh {way_name} bên trái\",\r\n \"destination\": \"Đi đường nhánh bên trái về {destination}\",\r\n \"exit\": \"Đi theo lối ra {exit} bên trái\",\r\n \"exit_destination\": \"Đi theo lối ra {exit} bên trái về {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Đi đường nhánh bên phải\",\r\n \"name\": \"Đi đường nhánh {way_name} bên phải\",\r\n \"destination\": \"Đi đường nhánh bên phải về {destination}\",\r\n \"exit\": \"Đi theo lối ra {exit} bên phải\",\r\n \"exit_destination\": \"Đi theo lối ra {exit} bên phải về {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Đi đường nhánh bên trái\",\r\n \"name\": \"Đi đường nhánh {way_name} bên trái\",\r\n \"destination\": \"Đi đường nhánh bên trái về {destination}\",\r\n \"exit\": \"Đi theo lối ra {exit} bên trái\",\r\n \"exit_destination\": \"Đi theo lối ra {exit} bên trái về {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Đi đường nhánh bên phải\",\r\n \"name\": \"Đi đường nhánh {way_name} bên phải\",\r\n \"destination\": \"Đi đường nhánh bên phải về {destination}\",\r\n \"exit\": \"Đi theo lối ra {exit} bên phải\",\r\n \"exit_destination\": \"Đi theo lối ra {exit} bên phải về {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Đi đường nhánh bên trái\",\r\n \"name\": \"Đi đường nhánh {way_name} bên trái\",\r\n \"destination\": \"Đi đường nhánh bên trái về {destination}\",\r\n \"exit\": \"Đi theo lối ra {exit} bên trái\",\r\n \"exit_destination\": \"Đi theo lối ra {exit} bên trái về {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Đi đường nhánh bên phải\",\r\n \"name\": \"Đi đường nhánh {way_name} bên phải\",\r\n \"destination\": \"Đi đường nhánh bên phải về {destination}\",\r\n \"exit\": \"Đi theo lối ra {exit} bên phải\",\r\n \"exit_destination\": \"Đi theo lối ra {exit} bên phải về {destination}\"\r\n }\r\n },\r\n \"on ramp\": {\r\n \"default\": {\r\n \"default\": \"Đi đường nhánh\",\r\n \"name\": \"Đi đường nhánh {way_name}\",\r\n \"destination\": \"Đi đường nhánh về {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Đi đường nhánh bên trái\",\r\n \"name\": \"Đi đường nhánh {way_name} bên trái\",\r\n \"destination\": \"Đi đường nhánh bên trái về {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Đi đường nhánh bên phải\",\r\n \"name\": \"Đi đường nhánh {way_name} bên phải\",\r\n \"destination\": \"Đi đường nhánh bên phải về {destination}\"\r\n },\r\n \"sharp left\": {\r\n \"default\": \"Đi đường nhánh bên trái\",\r\n \"name\": \"Đi đường nhánh {way_name} bên trái\",\r\n \"destination\": \"Đi đường nhánh bên trái về {destination}\"\r\n },\r\n \"sharp right\": {\r\n \"default\": \"Đi đường nhánh bên phải\",\r\n \"name\": \"Đi đường nhánh {way_name} bên phải\",\r\n \"destination\": \"Đi đường nhánh bên phải về {destination}\"\r\n },\r\n \"slight left\": {\r\n \"default\": \"Đi đường nhánh bên trái\",\r\n \"name\": \"Đi đường nhánh {way_name} bên trái\",\r\n \"destination\": \"Đi đường nhánh bên trái về {destination}\"\r\n },\r\n \"slight right\": {\r\n \"default\": \"Đi đường nhánh bên phải\",\r\n \"name\": \"Đi đường nhánh {way_name} bên phải\",\r\n \"destination\": \"Đi đường nhánh bên phải về {destination}\"\r\n }\r\n },\r\n \"rotary\": {\r\n \"default\": {\r\n \"default\": {\r\n \"default\": \"Đi vào bùng binh\",\r\n \"name\": \"Đi vào bùng binh và ra tại {way_name}\",\r\n \"destination\": \"Đi vào bùng binh và ra về {destination}\"\r\n },\r\n \"name\": {\r\n \"default\": \"Đi vào {rotary_name}\",\r\n \"name\": \"Đi vào {rotary_name} và ra tại {way_name}\",\r\n \"destination\": \"Đi và {rotary_name} và ra về {destination}\"\r\n },\r\n \"exit\": {\r\n \"default\": \"Đi vào bùng binh và ra tại đường {exit_number}\",\r\n \"name\": \"Đi vào bùng binh và ra tại đường {exit_number} tức {way_name}\",\r\n \"destination\": \"Đi vào bùng binh và ra tại đường {exit_number} về {destination}\"\r\n },\r\n \"name_exit\": {\r\n \"default\": \"Đi vào {rotary_name} và ra tại đường {exit_number}\",\r\n \"name\": \"Đi vào {rotary_name} và ra tại đường {exit_number} tức {way_name}\",\r\n \"destination\": \"Đi vào {rotary_name} và ra tại đường {exit_number} về {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout\": {\r\n \"default\": {\r\n \"exit\": {\r\n \"default\": \"Đi vào bùng binh và ra tại đường {exit_number}\",\r\n \"name\": \"Đi vào bùng binh và ra tại đường {exit_number} tức {way_name}\",\r\n \"destination\": \"Đi vào bùng binh và ra tại đường {exit_number} về {destination}\"\r\n },\r\n \"default\": {\r\n \"default\": \"Đi vào bùng binh\",\r\n \"name\": \"Đi vào bùng binh và ra tại {way_name}\",\r\n \"destination\": \"Đi vào bùng binh và ra về {destination}\"\r\n }\r\n }\r\n },\r\n \"roundabout turn\": {\r\n \"default\": {\r\n \"default\": \"Quẹo {modifier}\",\r\n \"name\": \"Quẹo {modifier} vào {way_name}\",\r\n \"destination\": \"Quẹo {modifier} về {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Quẹo trái\",\r\n \"name\": \"Quẹo trái vào {way_name}\",\r\n \"destination\": \"Quẹo trái về {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Quẹo phải\",\r\n \"name\": \"Quẹo phải vào {way_name}\",\r\n \"destination\": \"Quẹo phải về {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Chạy thẳng\",\r\n \"name\": \"Chạy tiếp trên {way_name}\",\r\n \"destination\": \"Chạy tiếp về {destination}\"\r\n }\r\n },\r\n \"exit roundabout\": {\r\n \"default\": {\r\n \"default\": \"Ra bùng binh\",\r\n \"name\": \"Ra bùng binh vào {way_name}\",\r\n \"destination\": \"Ra bùng binh về {destination}\"\r\n }\r\n },\r\n \"exit rotary\": {\r\n \"default\": {\r\n \"default\": \"Ra bùng binh\",\r\n \"name\": \"Ra bùng binh vào {way_name}\",\r\n \"destination\": \"Ra bùng binh về {destination}\"\r\n }\r\n },\r\n \"turn\": {\r\n \"default\": {\r\n \"default\": \"Quẹo {modifier}\",\r\n \"name\": \"Quẹo {modifier} vào {way_name}\",\r\n \"destination\": \"Quẹo {modifier} về {destination}\"\r\n },\r\n \"left\": {\r\n \"default\": \"Quẹo trái\",\r\n \"name\": \"Quẹo trái vào {way_name}\",\r\n \"destination\": \"Quẹo trái về {destination}\"\r\n },\r\n \"right\": {\r\n \"default\": \"Quẹo phải\",\r\n \"name\": \"Quẹo phải vào {way_name}\",\r\n \"destination\": \"Quẹo phải về {destination}\"\r\n },\r\n \"straight\": {\r\n \"default\": \"Chạy thẳng\",\r\n \"name\": \"Chạy thẳng vào {way_name}\",\r\n \"destination\": \"Chạy thẳng về {destination}\"\r\n }\r\n },\r\n \"use lane\": {\r\n \"no_lanes\": {\r\n \"default\": \"Chạy thẳng\"\r\n },\r\n \"default\": {\r\n \"default\": \"{lane_instruction}\"\r\n }\r\n }\r\n }\r\n}\r\n\r\n }, {}],\r\n 55: [function (_dereq_, module, exports) {\r\n(function (global){\r\n(function() {\r\n\t'use strict';\r\n\r\n\tvar L = (typeof window !== \"undefined\" ? window['L'] : typeof global !== \"undefined\" ? global['L'] : null);\r\n\r\n\tmodule.exports = L.Class.extend({\r\n\t\toptions: {\r\n\t\t\ttimeout: 500,\r\n\t\t\tblurTimeout: 100,\r\n\t\t\tnoResultsMessage: 'No results found.'\r\n\t\t},\r\n\r\n\t\tinitialize: function(elem, callback, context, options) {\r\n\t\t\tL.setOptions(this, options);\r\n\r\n\t\t\tthis._elem = elem;\r\n\t\t\tthis._resultFn = options.resultFn ? L.Util.bind(options.resultFn, options.resultContext) : null;\r\n\t\t\tthis._autocomplete = options.autocompleteFn ? L.Util.bind(options.autocompleteFn, options.autocompleteContext) : null;\r\n\t\t\tthis._selectFn = L.Util.bind(callback, context);\r\n\t\t\tthis._container = L.DomUtil.create('div', 'leaflet-routing-geocoder-result');\r\n\t\t\tthis._resultTable = L.DomUtil.create('table', '', this._container);\r\n\r\n\t\t\t// TODO: looks a bit like a kludge to register same for input and keypress -\r\n\t\t\t// browsers supporting both will get duplicate events; just registering\r\n\t\t\t// input will not catch enter, though.\r\n\t\t\tL.DomEvent.addListener(this._elem, 'input', this._keyPressed, this);\r\n\t\t\tL.DomEvent.addListener(this._elem, 'keypress', this._keyPressed, this);\r\n\t\t\tL.DomEvent.addListener(this._elem, 'keydown', this._keyDown, this);\r\n\t\t\tL.DomEvent.addListener(this._elem, 'blur', function() {\r\n\t\t\t\tif (this._isOpen) {\r\n\t\t\t\t\tthis.close();\r\n\t\t\t\t}\r\n\t\t\t}, this);\r\n\t\t},\r\n\r\n\t\tclose: function() {\r\n\t\t\tL.DomUtil.removeClass(this._container, 'leaflet-routing-geocoder-result-open');\r\n\t\t\tthis._isOpen = false;\r\n\t\t},\r\n\r\n\t\t_open: function() {\r\n\t\t\tvar rect = this._elem.getBoundingClientRect();\r\n\t\t\tif (!this._container.parentElement) {\r\n\t\t\t\t// See notes section under https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollX\r\n\t\t\t\t// This abomination is required to support all flavors of IE\r\n\t\t\t\tvar scrollX = (window.pageXOffset !== undefined) ? window.pageXOffset\r\n\t\t\t\t\t: (document.documentElement || document.body.parentNode || document.body).scrollLeft;\r\n\t\t\t\tvar scrollY = (window.pageYOffset !== undefined) ? window.pageYOffset\r\n\t\t\t\t\t: (document.documentElement || document.body.parentNode || document.body).scrollTop;\r\n\t\t\t\tthis._container.style.left = (rect.left + scrollX) + 'px';\r\n\t\t\t\tthis._container.style.top = (rect.bottom + scrollY) + 'px';\r\n\t\t\t\tthis._container.style.width = (rect.right - rect.left) + 'px';\r\n\t\t\t\tdocument.body.appendChild(this._container);\r\n\t\t\t}\r\n\r\n\t\t\tL.DomUtil.addClass(this._container, 'leaflet-routing-geocoder-result-open');\r\n\t\t\tthis._isOpen = true;\r\n\t\t},\r\n\r\n\t\t_setResults: function(results) {\r\n\t\t\tvar i,\r\n\t\t\t tr,\r\n\t\t\t td,\r\n\t\t\t text;\r\n\r\n\t\t\tdelete this._selection;\r\n\t\t\tthis._results = results;\r\n\r\n\t\t\twhile (this._resultTable.firstChild) {\r\n\t\t\t\tthis._resultTable.removeChild(this._resultTable.firstChild);\r\n\t\t\t}\r\n\r\n\t\t\tfor (i = 0; i < results.length; i++) {\r\n\t\t\t\ttr = L.DomUtil.create('tr', '', this._resultTable);\r\n\t\t\t\ttr.setAttribute('data-result-index', i);\r\n\t\t\t\ttd = L.DomUtil.create('td', '', tr);\r\n\r\n\t\t\t\tif (this.options.formatGeocoderResult) {\r\n\t\t\t\t\ttext = this.options.formatGeocoderResult(results[i]);\r\n\t\t\t\t} else {\r\n\t\t\t\t\ttext = document.createTextNode(results[i].name);\r\n\t\t\t\t}\r\n\r\n\t\t\t\ttd.appendChild(text);\r\n\t\t\t\t// mousedown + click because:\r\n\t\t\t\t// http://stackoverflow.com/questions/10652852/jquery-fire-click-before-blur-event\r\n\t\t\t\tL.DomEvent.addListener(td, 'mousedown', L.DomEvent.preventDefault);\r\n\t\t\t\tL.DomEvent.addListener(td, 'click', this._createClickListener(results[i]));\r\n\t\t\t}\r\n\r\n\t\t\tif (!i) {\r\n\t\t\t\ttr = L.DomUtil.create('tr', '', this._resultTable);\r\n\t\t\t\ttd = L.DomUtil.create('td', 'leaflet-routing-geocoder-no-results', tr);\r\n\t\t\t\ttd.innerHTML = this.options.noResultsMessage;\r\n\t\t\t}\r\n\r\n\t\t\tthis._open();\r\n\r\n\t\t\tif (results.length > 0) {\r\n\t\t\t\t// Select the first entry\r\n\t\t\t\tthis._select(1);\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t_createClickListener: function(r) {\r\n\t\t\tvar resultSelected = this._resultSelected(r);\r\n\t\t\treturn L.bind(function() {\r\n\t\t\t\tthis._elem.blur();\r\n\t\t\t\tresultSelected();\r\n\t\t\t}, this);\r\n\t\t},\r\n\r\n\t\t_resultSelected: function(r) {\r\n\t\t\treturn L.bind(function() {\r\n\t\t\t\tthis.close();\r\n\t\t\t\tthis._elem.value = r.name;\r\n\t\t\t\tthis._lastCompletedText = r.name;\r\n\t\t\t\tthis._selectFn(r);\r\n\t\t\t}, this);\r\n\t\t},\r\n\r\n\t\t_keyPressed: function(e) {\r\n\t\t\tvar index;\r\n\r\n\t\t\tif (this._isOpen && e.keyCode === 13 && this._selection) {\r\n\t\t\t\tindex = parseInt(this._selection.getAttribute('data-result-index'), 10);\r\n\t\t\t\tthis._resultSelected(this._results[index])();\r\n\t\t\t\tL.DomEvent.preventDefault(e);\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif (e.keyCode === 13) {\r\n\t\t\t\tL.DomEvent.preventDefault(e);\r\n\t\t\t\tthis._complete(this._resultFn, true);\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif (this._autocomplete && document.activeElement === this._elem) {\r\n\t\t\t\tif (this._timer) {\r\n\t\t\t\t\tclearTimeout(this._timer);\r\n\t\t\t\t}\r\n\t\t\t\tthis._timer = setTimeout(L.Util.bind(function() { this._complete(this._autocomplete); }, this),\r\n\t\t\t\t\tthis.options.timeout);\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tthis._unselect();\r\n\t\t},\r\n\r\n\t\t_select: function(dir) {\r\n\t\t\tvar sel = this._selection;\r\n\t\t\tif (sel) {\r\n\t\t\t\tL.DomUtil.removeClass(sel.firstChild, 'leaflet-routing-geocoder-selected');\r\n\t\t\t\tsel = sel[dir > 0 ? 'nextSibling' : 'previousSibling'];\r\n\t\t\t}\r\n\t\t\tif (!sel) {\r\n\t\t\t\tsel = this._resultTable[dir > 0 ? 'firstChild' : 'lastChild'];\r\n\t\t\t}\r\n\r\n\t\t\tif (sel) {\r\n\t\t\t\tL.DomUtil.addClass(sel.firstChild, 'leaflet-routing-geocoder-selected');\r\n\t\t\t\tthis._selection = sel;\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t_unselect: function() {\r\n\t\t\tif (this._selection) {\r\n\t\t\t\tL.DomUtil.removeClass(this._selection.firstChild, 'leaflet-routing-geocoder-selected');\r\n\t\t\t}\r\n\t\t\tdelete this._selection;\r\n\t\t},\r\n\r\n\t\t_keyDown: function(e) {\r\n\t\t\tif (this._isOpen) {\r\n\t\t\t\tswitch (e.keyCode) {\r\n\t\t\t\t// Escape\r\n\t\t\t\tcase 27:\r\n\t\t\t\t\tthis.close();\r\n\t\t\t\t\tL.DomEvent.preventDefault(e);\r\n\t\t\t\t\treturn;\r\n\t\t\t\t// Up\r\n\t\t\t\tcase 38:\r\n\t\t\t\t\tthis._select(-1);\r\n\t\t\t\t\tL.DomEvent.preventDefault(e);\r\n\t\t\t\t\treturn;\r\n\t\t\t\t// Down\r\n\t\t\t\tcase 40:\r\n\t\t\t\t\tthis._select(1);\r\n\t\t\t\t\tL.DomEvent.preventDefault(e);\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t_complete: function(completeFn, trySelect) {\r\n\t\t\tvar v = this._elem.value;\r\n\t\t\tfunction completeResults(results) {\r\n\t\t\t\tthis._lastCompletedText = v;\r\n\t\t\t\tif (trySelect && results.length === 1) {\r\n\t\t\t\t\tthis._resultSelected(results[0])();\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis._setResults(results);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (!v) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif (v !== this._lastCompletedText) {\r\n\t\t\t\tcompleteFn(v, completeResults, this);\r\n\t\t\t} else if (trySelect) {\r\n\t\t\t\tcompleteResults.call(this, this._results);\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n})();\r\n\r\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\r\n }, {}],\r\n 56: [function (_dereq_, module, exports) {\r\n(function (global){\r\n(function() {\r\n\t'use strict';\r\n\r\n\tvar L = (typeof window !== \"undefined\" ? window['L'] : typeof global !== \"undefined\" ? global['L'] : null);\r\n\r\n\tvar Itinerary = _dereq_('./itinerary');\r\n\tvar Line = _dereq_('./line');\r\n\tvar Plan = _dereq_('./plan');\r\n\tvar OSRMv1 = _dereq_('./osrm-v1');\r\n\r\n\tmodule.exports = Itinerary.extend({\r\n\t\toptions: {\r\n\t\t\tfitSelectedRoutes: 'smart',\r\n\t\t\trouteLine: function(route, options) { return new Line(route, options); },\r\n\t\t\tautoRoute: true,\r\n\t\t\trouteWhileDragging: false,\r\n\t\t\trouteDragInterval: 500,\r\n\t\t\twaypointMode: 'connect',\r\n\t\t\tshowAlternatives: false,\r\n\t\t\tdefaultErrorHandler: function(e) {\r\n\t\t\t\tconsole.error('Routing error:', e.error);\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tinitialize: function(options) {\r\n\t\t\tL.Util.setOptions(this, options);\r\n\r\n\t\t\tthis._router = this.options.router || new OSRMv1(options);\r\n\t\t\tthis._plan = this.options.plan || new Plan(this.options.waypoints, options);\r\n\t\t\tthis._requestCount = 0;\r\n\r\n\t\t\tItinerary.prototype.initialize.call(this, options);\r\n\r\n\t\t\tthis.on('routeselected', this._routeSelected, this);\r\n\t\t\tif (this.options.defaultErrorHandler) {\r\n\t\t\t\tthis.on('routingerror', this.options.defaultErrorHandler);\r\n\t\t\t}\r\n\t\t\tthis._plan.on('waypointschanged', this._onWaypointsChanged, this);\r\n\t\t\tif (options.routeWhileDragging) {\r\n\t\t\t\tthis._setupRouteDragging();\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t_onZoomEnd: function() {\r\n\t\t\tif (!this._selectedRoute ||\r\n\t\t\t\t!this._router.requiresMoreDetail) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tvar map = this._map;\r\n\t\t\tif (this._router.requiresMoreDetail(this._selectedRoute,\r\n\t\t\t\t\tmap.getZoom(), map.getBounds())) {\r\n\t\t\t\tthis.route({\r\n\t\t\t\t\tcallback: L.bind(function(err, routes) {\r\n\t\t\t\t\t\tvar i;\r\n\t\t\t\t\t\tif (!err) {\r\n\t\t\t\t\t\t\tfor (i = 0; i < routes.length; i++) {\r\n\t\t\t\t\t\t\t\tthis._routes[i].properties = routes[i].properties;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tthis._updateLineCallback(err, routes);\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t}, this),\r\n\t\t\t\t\tsimplifyGeometry: false,\r\n\t\t\t\t\tgeometryOnly: true\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tonAdd: function(map) {\r\n\t\t\tif (this.options.autoRoute) {\r\n\t\t\t\tthis.route();\r\n\t\t\t}\r\n\r\n\t\t\tvar container = Itinerary.prototype.onAdd.call(this, map);\r\n\r\n\t\t\tthis._map = map;\r\n\t\t\tthis._map.addLayer(this._plan);\r\n\r\n\t\t\tthis._map.on('zoomend', this._onZoomEnd, this);\r\n\r\n\t\t\tif (this._plan.options.geocoder) {\r\n\t\t\t\tcontainer.insertBefore(this._plan.createGeocoders(), container.firstChild);\r\n\t\t\t}\r\n\r\n\t\t\treturn container;\r\n\t\t},\r\n\r\n\t\tonRemove: function(map) {\r\n\t\t\tmap.off('zoomend', this._onZoomEnd, this);\r\n\t\t\tif (this._line) {\r\n\t\t\t\tmap.removeLayer(this._line);\r\n\t\t\t}\r\n\t\t\tmap.removeLayer(this._plan);\r\n\t\t\tif (this._alternatives && this._alternatives.length > 0) {\r\n\t\t\t\tfor (var i = 0, len = this._alternatives.length; i < len; i++) {\r\n\t\t\t\t\tmap.removeLayer(this._alternatives[i]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn Itinerary.prototype.onRemove.call(this, map);\r\n\t\t},\r\n\r\n\t\tgetWaypoints: function() {\r\n\t\t\treturn this._plan.getWaypoints();\r\n\t\t},\r\n\r\n\t\tsetWaypoints: function(waypoints) {\r\n\t\t\tthis._plan.setWaypoints(waypoints);\r\n\t\t\treturn this;\r\n\t\t},\r\n\r\n\t\tspliceWaypoints: function() {\r\n\t\t\tvar removed = this._plan.spliceWaypoints.apply(this._plan, arguments);\r\n\t\t\treturn removed;\r\n\t\t},\r\n\r\n\t\tgetPlan: function() {\r\n\t\t\treturn this._plan;\r\n\t\t},\r\n\r\n\t\tgetRouter: function() {\r\n\t\t\treturn this._router;\r\n\t\t},\r\n\r\n\t\t_routeSelected: function(e) {\r\n\t\t\tvar route = this._selectedRoute = e.route,\r\n\t\t\t\talternatives = this.options.showAlternatives && e.alternatives,\r\n\t\t\t\tfitMode = this.options.fitSelectedRoutes,\r\n\t\t\t\tfitBounds =\r\n\t\t\t\t\t(fitMode === 'smart' && !this._waypointsVisible()) ||\r\n\t\t\t\t\t(fitMode !== 'smart' && fitMode);\r\n\r\n\t\t\tthis._updateLines({route: route, alternatives: alternatives});\r\n\r\n\t\t\tif (fitBounds) {\r\n\t\t\t\tthis._map.fitBounds(this._line.getBounds());\r\n\t\t\t}\r\n\r\n\t\t\tif (this.options.waypointMode === 'snap') {\r\n\t\t\t\tthis._plan.off('waypointschanged', this._onWaypointsChanged, this);\r\n\t\t\t\tthis.setWaypoints(route.waypoints);\r\n\t\t\t\tthis._plan.on('waypointschanged', this._onWaypointsChanged, this);\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t_waypointsVisible: function() {\r\n\t\t\tvar wps = this.getWaypoints(),\r\n\t\t\t\tmapSize,\r\n\t\t\t\tbounds,\r\n\t\t\t\tboundsSize,\r\n\t\t\t\ti,\r\n\t\t\t\tp;\r\n\r\n\t\t\ttry {\r\n\t\t\t\tmapSize = this._map.getSize();\r\n\r\n\t\t\t\tfor (i = 0; i < wps.length; i++) {\r\n\t\t\t\t\tp = this._map.latLngToLayerPoint(wps[i].latLng);\r\n\r\n\t\t\t\t\tif (bounds) {\r\n\t\t\t\t\t\tbounds.extend(p);\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tbounds = L.bounds([p]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tboundsSize = bounds.getSize();\r\n\t\t\t\treturn (boundsSize.x > mapSize.x / 5 ||\r\n\t\t\t\t\tboundsSize.y > mapSize.y / 5) && this._waypointsInViewport();\r\n\r\n\t\t\t} catch (e) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t_waypointsInViewport: function() {\r\n\t\t\tvar wps = this.getWaypoints(),\r\n\t\t\t\tmapBounds,\r\n\t\t\t\ti;\r\n\r\n\t\t\ttry {\r\n\t\t\t\tmapBounds = this._map.getBounds();\r\n\t\t\t} catch (e) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\r\n\t\t\tfor (i = 0; i < wps.length; i++) {\r\n\t\t\t\tif (mapBounds.contains(wps[i].latLng)) {\r\n\t\t\t\t\treturn true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\treturn false;\r\n\t\t},\r\n\r\n\t\t_updateLines: function(routes) {\r\n\t\t\tvar addWaypoints = this.options.addWaypoints !== undefined ?\r\n\t\t\t\tthis.options.addWaypoints : true;\r\n\t\t\tthis._clearLines();\r\n\r\n\t\t\t// add alternatives first so they lie below the main route\r\n\t\t\tthis._alternatives = [];\r\n\t\t\tif (routes.alternatives) routes.alternatives.forEach(function(alt, i) {\r\n\t\t\t\tthis._alternatives[i] = this.options.routeLine(alt,\r\n\t\t\t\t\tL.extend({\r\n\t\t\t\t\t\tisAlternative: true\r\n\t\t\t\t\t}, this.options.altLineOptions || this.options.lineOptions));\r\n\t\t\t\tthis._alternatives[i].addTo(this._map);\r\n\t\t\t\tthis._hookAltEvents(this._alternatives[i]);\r\n\t\t\t}, this);\r\n\r\n\t\t\tthis._line = this.options.routeLine(routes.route,\r\n\t\t\t\tL.extend({\r\n\t\t\t\t\taddWaypoints: addWaypoints,\r\n\t\t\t\t\textendToWaypoints: this.options.waypointMode === 'connect'\r\n\t\t\t\t}, this.options.lineOptions));\r\n\t\t\tthis._line.addTo(this._map);\r\n\t\t\tthis._hookEvents(this._line);\r\n\t\t},\r\n\r\n\t\t_hookEvents: function(l) {\r\n\t\t\tl.on('linetouched', function(e) {\r\n\t\t\t\tthis._plan.dragNewWaypoint(e);\r\n\t\t\t}, this);\r\n\t\t},\r\n\r\n\t\t_hookAltEvents: function(l) {\r\n\t\t\tl.on('linetouched', function(e) {\r\n\t\t\t\tvar alts = this._routes.slice();\r\n\t\t\t\tvar selected = alts.splice(e.target._route.routesIndex, 1)[0];\r\n\t\t\t\tthis.fire('routeselected', {route: selected, alternatives: alts});\r\n\t\t\t}, this);\r\n\t\t},\r\n\r\n\t\t_onWaypointsChanged: function(e) {\r\n\t\t\tif (this.options.autoRoute) {\r\n\t\t\t\tthis.route({});\r\n\t\t\t}\r\n\t\t\tif (!this._plan.isReady()) {\r\n\t\t\t\tthis._clearLines();\r\n\t\t\t\tthis._clearAlts();\r\n\t\t\t}\r\n\t\t\tthis.fire('waypointschanged', {waypoints: e.waypoints});\r\n\t\t},\r\n\r\n\t\t_setupRouteDragging: function() {\r\n\t\t\tvar timer = 0,\r\n\t\t\t\twaypoints;\r\n\r\n\t\t\tthis._plan.on('waypointdrag', L.bind(function(e) {\r\n\t\t\t\twaypoints = e.waypoints;\r\n\r\n\t\t\t\tif (!timer) {\r\n\t\t\t\t\ttimer = setTimeout(L.bind(function() {\r\n\t\t\t\t\t\tthis.route({\r\n\t\t\t\t\t\t\twaypoints: waypoints,\r\n\t\t\t\t\t\t\tgeometryOnly: true,\r\n\t\t\t\t\t\t\tcallback: L.bind(this._updateLineCallback, this)\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\ttimer = undefined;\r\n\t\t\t\t\t}, this), this.options.routeDragInterval);\r\n\t\t\t\t}\r\n\t\t\t}, this));\r\n\t\t\tthis._plan.on('waypointdragend', function() {\r\n\t\t\t\tif (timer) {\r\n\t\t\t\t\tclearTimeout(timer);\r\n\t\t\t\t\ttimer = undefined;\r\n\t\t\t\t}\r\n\t\t\t\tthis.route();\r\n\t\t\t}, this);\r\n\t\t},\r\n\r\n\t\t_updateLineCallback: function(err, routes) {\r\n\t\t\tif (!err) {\r\n\t\t\t\troutes = routes.slice();\r\n\t\t\t\tvar selected = routes.splice(this._selectedRoute.routesIndex, 1)[0];\r\n\t\t\t\tthis._updateLines({\r\n\t\t\t\t\troute: selected,\r\n\t\t\t\t\talternatives: this.options.showAlternatives ? routes : []\r\n\t\t\t\t});\r\n\t\t\t} else if (err.type !== 'abort') {\r\n\t\t\t\tthis._clearLines();\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\troute: function(options) {\r\n\t\t\tvar ts = ++this._requestCount,\r\n\t\t\t\twps;\r\n\r\n\t\t\tif (this._pendingRequest && this._pendingRequest.abort) {\r\n\t\t\t\tthis._pendingRequest.abort();\r\n\t\t\t\tthis._pendingRequest = null;\r\n\t\t\t}\r\n\r\n\t\t\toptions = options || {};\r\n\r\n\t\t\tif (this._plan.isReady()) {\r\n\t\t\t\tif (this.options.useZoomParameter) {\r\n\t\t\t\t\toptions.z = this._map && this._map.getZoom();\r\n\t\t\t\t}\r\n\r\n\t\t\t\twps = options && options.waypoints || this._plan.getWaypoints();\r\n\t\t\t\tthis.fire('routingstart', {waypoints: wps});\r\n\t\t\t\tthis._pendingRequest = this._router.route(wps, function(err, routes) {\r\n\t\t\t\t\tthis._pendingRequest = null;\r\n\r\n\t\t\t\t\tif (options.callback) {\r\n\t\t\t\t\t\treturn options.callback.call(this, err, routes);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Prevent race among multiple requests,\r\n\t\t\t\t\t// by checking the current request's count\r\n\t\t\t\t\t// against the last request's; ignore result if\r\n\t\t\t\t\t// this isn't the last request.\r\n\t\t\t\t\tif (ts === this._requestCount) {\r\n\t\t\t\t\t\tthis._clearLines();\r\n\t\t\t\t\t\tthis._clearAlts();\r\n\t\t\t\t\t\tif (err && err.type !== 'abort') {\r\n\t\t\t\t\t\t\tthis.fire('routingerror', {error: err});\r\n\t\t\t\t\t\t\treturn;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\troutes.forEach(function(route, i) { route.routesIndex = i; });\r\n\r\n\t\t\t\t\t\tif (!options.geometryOnly) {\r\n\t\t\t\t\t\t\tthis.fire('routesfound', {waypoints: wps, routes: routes});\r\n\t\t\t\t\t\t\tthis.setAlternatives(routes);\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tvar selectedRoute = routes.splice(0,1)[0];\r\n\t\t\t\t\t\t\tthis._routeSelected({route: selectedRoute, alternatives: routes});\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}, this, options);\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t_clearLines: function() {\r\n\t\t\tif (this._line) {\r\n\t\t\t\tthis._map.removeLayer(this._line);\r\n\t\t\t\tdelete this._line;\r\n\t\t\t}\r\n\t\t\tif (this._alternatives && this._alternatives.length) {\r\n\t\t\t\tfor (var i in this._alternatives) {\r\n\t\t\t\t\tthis._map.removeLayer(this._alternatives[i]);\r\n\t\t\t\t}\r\n\t\t\t\tthis._alternatives = [];\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n})();\r\n\r\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\r\n }, { \"./itinerary\": 62, \"./line\": 63, \"./osrm-v1\": 66, \"./plan\": 67 }],\r\n 57: [function (_dereq_, module, exports) {\r\n(function (global){\r\n(function() {\r\n\t'use strict';\r\n\r\n\tvar L = (typeof window !== \"undefined\" ? window['L'] : typeof global !== \"undefined\" ? global['L'] : null);\r\n\r\n\tmodule.exports = L.Control.extend({\r\n\t\toptions: {\r\n\t\t\theader: 'Routing error',\r\n\t\t\tformatMessage: function(error) {\r\n\t\t\t\tif (error.status < 0) {\r\n\t\t\t\t\treturn 'Calculating the route caused an error. Technical description follows:
      ' +\r\n\t\t\t\t\t\terror.message + '
      = 1000) {\r\n\t\t\t\t\tdata = {\r\n\t\t\t\t\t\tvalue: round(d / 1609.344, sensitivity),\r\n\t\t\t\t\t\tunit: un.miles\r\n\t\t\t\t\t};\r\n\t\t\t\t} else {\r\n\t\t\t\t\tdata = {\r\n\t\t\t\t\t\tvalue: round(yards, sensitivity),\r\n\t\t\t\t\t\tunit: un.yards\r\n\t\t\t\t\t};\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tv = round(d, sensitivity);\r\n\t\t\t\tdata = {\r\n\t\t\t\t\tvalue: v >= 1000 ? (v / 1000) : v,\r\n\t\t\t\t\tunit: v >= 1000 ? un.kilometers : un.meters\r\n\t\t\t\t};\r\n\t\t\t}\r\n\r\n\t\t\tif (simpleRounding) {\r\n\t\t\t\tdata.value = data.value.toFixed(-sensitivity);\r\n\t\t\t}\r\n\r\n\t\t\treturn L.Util.template(this.options.distanceTemplate, data);\r\n\t\t},\r\n\r\n\t\t_round: function(d, sensitivity) {\r\n\t\t\tvar s = sensitivity || this.options.roundingSensitivity,\r\n\t\t\t\tpow10 = Math.pow(10, (Math.floor(d / s) + '').length - 1),\r\n\t\t\t\tr = Math.floor(d / pow10),\r\n\t\t\t\tp = (r > 5) ? pow10 : pow10 / 2;\r\n\r\n\t\t\treturn Math.round(d / p) * p;\r\n\t\t},\r\n\r\n\t\tformatTime: function(t /* Number (seconds) */) {\r\n\t\t\tvar un = this.options.unitNames || this._localization.localize('units');\r\n\t\t\t// More than 30 seconds precision looks ridiculous\r\n\t\t\tt = Math.round(t / 30) * 30;\r\n\r\n\t\t\tif (t > 86400) {\r\n\t\t\t\treturn Math.round(t / 3600) + ' ' + un.hours;\r\n\t\t\t} else if (t > 3600) {\r\n\t\t\t\treturn Math.floor(t / 3600) + ' ' + un.hours + ' ' +\r\n\t\t\t\t\tMath.round((t % 3600) / 60) + ' ' + un.minutes;\r\n\t\t\t} else if (t > 300) {\r\n\t\t\t\treturn Math.round(t / 60) + ' ' + un.minutes;\r\n\t\t\t} else if (t > 60) {\r\n\t\t\t\treturn Math.floor(t / 60) + ' ' + un.minutes +\r\n\t\t\t\t\t(t % 60 !== 0 ? ' ' + (t % 60) + ' ' + un.seconds : '');\r\n\t\t\t} else {\r\n\t\t\t\treturn t + ' ' + un.seconds;\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tformatInstruction: function(instr, i) {\r\n\t\t\tif (instr.text === undefined) {\r\n\t\t\t\treturn this.capitalize(L.Util.template(this._getInstructionTemplate(instr, i),\r\n\t\t\t\t\tL.extend({}, instr, {\r\n\t\t\t\t\t\texitStr: instr.exit ? this._localization.localize('formatOrder')(instr.exit) : '',\r\n\t\t\t\t\t\tdir: this._localization.localize(['directions', instr.direction]),\r\n\t\t\t\t\t\tmodifier: this._localization.localize(['directions', instr.modifier])\r\n\t\t\t\t\t})));\r\n\t\t\t} else {\r\n\t\t\t\treturn instr.text;\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tgetIconName: function(instr, i) {\r\n\t\t\tswitch (instr.type) {\r\n\t\t\tcase 'Head':\r\n\t\t\t\tif (i === 0) {\r\n\t\t\t\t\treturn 'depart';\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\t\t\tcase 'WaypointReached':\r\n\t\t\t\treturn 'via';\r\n\t\t\tcase 'Roundabout':\r\n\t\t\t\treturn 'enter-roundabout';\r\n\t\t\tcase 'DestinationReached':\r\n\t\t\t\treturn 'arrive';\r\n\t\t\t}\r\n\r\n\t\t\tswitch (instr.modifier) {\r\n\t\t\tcase 'Straight':\r\n\t\t\t\treturn 'continue';\r\n\t\t\tcase 'SlightRight':\r\n\t\t\t\treturn 'bear-right';\r\n\t\t\tcase 'Right':\r\n\t\t\t\treturn 'turn-right';\r\n\t\t\tcase 'SharpRight':\r\n\t\t\t\treturn 'sharp-right';\r\n\t\t\tcase 'TurnAround':\r\n\t\t\tcase 'Uturn':\r\n\t\t\t\treturn 'u-turn';\r\n\t\t\tcase 'SharpLeft':\r\n\t\t\t\treturn 'sharp-left';\r\n\t\t\tcase 'Left':\r\n\t\t\t\treturn 'turn-left';\r\n\t\t\tcase 'SlightLeft':\r\n\t\t\t\treturn 'bear-left';\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tcapitalize: function(s) {\r\n\t\t\treturn s.charAt(0).toUpperCase() + s.substring(1);\r\n\t\t},\r\n\r\n\t\t_getInstructionTemplate: function(instr, i) {\r\n\t\t\tvar type = instr.type === 'Straight' ? (i === 0 ? 'Head' : 'Continue') : instr.type,\r\n\t\t\t\tstrings = this._localization.localize(['instructions', type]);\r\n\r\n\t\t\tif (!strings) {\r\n\t\t\t\tstrings = [\r\n\t\t\t\t\tthis._localization.localize(['directions', type]),\r\n\t\t\t\t\t' ' + this._localization.localize(['instructions', 'Onto'])\r\n\t\t\t\t];\r\n\t\t\t}\r\n\r\n\t\t\treturn strings[0] + (strings.length > 1 && instr.road ? strings[1] : '');\r\n\t\t}\r\n\t});\r\n})();\r\n\r\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\r\n }, { \"./localization\": 64 }],\r\n 59: [function (_dereq_, module, exports) {\r\n(function (global){\r\n(function() {\r\n\t'use strict';\r\n\r\n\tvar L = (typeof window !== \"undefined\" ? window['L'] : typeof global !== \"undefined\" ? global['L'] : null);\r\n\tvar Autocomplete = _dereq_('./autocomplete');\r\n\tvar Localization = _dereq_('./localization');\r\n\r\n\tfunction selectInputText(input) {\r\n\t\tif (input.setSelectionRange) {\r\n\t\t\t// On iOS, select() doesn't work\r\n\t\t\tinput.setSelectionRange(0, 9999);\r\n\t\t} else {\r\n\t\t\t// On at least IE8, setSeleectionRange doesn't exist\r\n\t\t\tinput.select();\r\n\t\t}\r\n\t}\r\n\r\n\tmodule.exports = L.Class.extend({\r\n\t\tincludes: ((typeof L.Evented !== 'undefined' && L.Evented.prototype) || L.Mixin.Events),\r\n\r\n\t\toptions: {\r\n\t\t\tcreateGeocoder: function(i, nWps, options) {\r\n\t\t\t\tvar container = L.DomUtil.create('div', 'leaflet-routing-geocoder'),\r\n\t\t\t\t\tinput = L.DomUtil.create('input', '', container),\r\n\t\t\t\t\tremove = options.addWaypoints ? L.DomUtil.create('span', 'leaflet-routing-remove-waypoint', container) : undefined;\r\n\r\n\t\t\t\tinput.disabled = !options.addWaypoints;\r\n\r\n\t\t\t\treturn {\r\n\t\t\t\t\tcontainer: container,\r\n\t\t\t\t\tinput: input,\r\n\t\t\t\t\tcloseButton: remove\r\n\t\t\t\t};\r\n\t\t\t},\r\n\t\t\tgeocoderPlaceholder: function(i, numberWaypoints, geocoderElement) {\r\n\t\t\t\tvar l = new Localization(geocoderElement.options.language).localize('ui');\r\n\t\t\t\treturn i === 0 ?\r\n\t\t\t\t\tl.startPlaceholder :\r\n\t\t\t\t\t(i < numberWaypoints - 1 ?\r\n\t\t\t\t\t\tL.Util.template(l.viaPlaceholder, {viaNumber: i}) :\r\n\t\t\t\t\t\tl.endPlaceholder);\r\n\t\t\t},\r\n\r\n\t\t\tgeocoderClass: function() {\r\n\t\t\t\treturn '';\r\n\t\t\t},\r\n\r\n\t\t\twaypointNameFallback: function(latLng) {\r\n\t\t\t\tvar ns = latLng.lat < 0 ? 'S' : 'N',\r\n\t\t\t\t\tew = latLng.lng < 0 ? 'W' : 'E',\r\n\t\t\t\t\tlat = (Math.round(Math.abs(latLng.lat) * 10000) / 10000).toString(),\r\n\t\t\t\t\tlng = (Math.round(Math.abs(latLng.lng) * 10000) / 10000).toString();\r\n\t\t\t\treturn ns + lat + ', ' + ew + lng;\r\n\t\t\t},\r\n\t\t\tmaxGeocoderTolerance: 200,\r\n\t\t\tautocompleteOptions: {},\r\n\t\t\tlanguage: 'en',\r\n\t\t},\r\n\r\n\t\tinitialize: function(wp, i, nWps, options) {\r\n\t\t\tL.setOptions(this, options);\r\n\r\n\t\t\tvar g = this.options.createGeocoder(i, nWps, this.options),\r\n\t\t\t\tcloseButton = g.closeButton,\r\n\t\t\t\tgeocoderInput = g.input;\r\n\t\t\tgeocoderInput.setAttribute('placeholder', this.options.geocoderPlaceholder(i, nWps, this));\r\n\t\t\tgeocoderInput.className = this.options.geocoderClass(i, nWps);\r\n\r\n\t\t\tthis._element = g;\r\n\t\t\tthis._waypoint = wp;\r\n\r\n\t\t\tthis.update();\r\n\t\t\t// This has to be here, or geocoder's value will not be properly\r\n\t\t\t// initialized.\r\n\t\t\t// TODO: look into why and make _updateWaypointName fix this.\r\n\t\t\tgeocoderInput.value = wp.name;\r\n\r\n\t\t\tL.DomEvent.addListener(geocoderInput, 'click', function() {\r\n\t\t\t\tselectInputText(this);\r\n\t\t\t}, geocoderInput);\r\n\r\n\t\t\tif (closeButton) {\r\n\t\t\t\tL.DomEvent.addListener(closeButton, 'click', function() {\r\n\t\t\t\t\tthis.fire('delete', { waypoint: this._waypoint });\r\n\t\t\t\t}, this);\r\n\t\t\t}\r\n\r\n\t\t\tif (typeof this.options.formatGeocoderResult == 'function') {\r\n\t\t\t\tthis.options.autocompleteOptions.formatGeocoderResult = this.options.formatGeocoderResult;\r\n\t\t\t}\r\n\r\n\t\t\tnew Autocomplete(geocoderInput, function(r) {\r\n\t\t\t\t\tgeocoderInput.value = r.name;\r\n\t\t\t\t\twp.name = r.name;\r\n\t\t\t\t\twp.latLng = r.center;\r\n\t\t\t\t\tthis.fire('geocoded', { waypoint: wp, value: r });\r\n\t\t\t\t}, this, L.extend({\r\n\t\t\t\t\tresultFn: this.options.geocoder.geocode,\r\n\t\t\t\t\tresultContext: this.options.geocoder,\r\n\t\t\t\t\tautocompleteFn: this.options.geocoder.suggest,\r\n\t\t\t\t\tautocompleteContext: this.options.geocoder\r\n\t\t\t\t}, this.options.autocompleteOptions));\r\n\t\t},\r\n\r\n\t\tgetContainer: function() {\r\n\t\t\treturn this._element.container;\r\n\t\t},\r\n\r\n\t\tsetValue: function(v) {\r\n\t\t\tthis._element.input.value = v;\r\n\t\t},\r\n\r\n\t\tupdate: function(force) {\r\n\t\t\tvar wp = this._waypoint,\r\n\t\t\t\twpCoords;\r\n\r\n\t\t\twp.name = wp.name || '';\r\n\r\n\t\t\tif (wp.latLng && (force || !wp.name)) {\r\n\t\t\t\twpCoords = this.options.waypointNameFallback(wp.latLng);\r\n\t\t\t\tif (this.options.geocoder && this.options.geocoder.reverse) {\r\n\t\t\t\t\tthis.options.geocoder.reverse(wp.latLng, 67108864 /* zoom 18 */, function(rs) {\r\n\t\t\t\t\t\tif (rs.length > 0 && rs[0].center.distanceTo(wp.latLng) < this.options.maxGeocoderTolerance) {\r\n\t\t\t\t\t\t\twp.name = rs[0].name;\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\twp.name = wpCoords;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tthis._update();\r\n\t\t\t\t\t}, this);\r\n\t\t\t\t} else {\r\n\t\t\t\t\twp.name = wpCoords;\r\n\t\t\t\t\tthis._update();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tfocus: function() {\r\n\t\t\tvar input = this._element.input;\r\n\t\t\tinput.focus();\r\n\t\t\tselectInputText(input);\r\n\t\t},\r\n\r\n\t\t_update: function() {\r\n\t\t\tvar wp = this._waypoint,\r\n\t\t\t value = wp && wp.name ? wp.name : '';\r\n\t\t\tthis.setValue(value);\r\n\t\t\tthis.fire('reversegeocoded', {waypoint: wp, value: value});\r\n\t\t}\r\n\t});\r\n})();\r\n\r\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\r\n }, { \"./autocomplete\": 55, \"./localization\": 64 }],\r\n 60: [function (_dereq_, module, exports) {\r\n(function (global){\r\nvar L = (typeof window !== \"undefined\" ? window['L'] : typeof global !== \"undefined\" ? global['L'] : null),\r\n Control = _dereq_('./control'),\r\n Itinerary = _dereq_('./itinerary'),\r\n Line = _dereq_('./line'),\r\n OSRMv1 = _dereq_('./osrm-v1'),\r\n Plan = _dereq_('./plan'),\r\n Waypoint = _dereq_('./waypoint'),\r\n Autocomplete = _dereq_('./autocomplete'),\r\n Formatter = _dereq_('./formatter'),\r\n GeocoderElement = _dereq_('./geocoder-element'),\r\n Localization = _dereq_('./localization'),\r\n ItineraryBuilder = _dereq_('./itinerary-builder'),\r\n Mapbox = _dereq_('./mapbox'),\r\n ErrorControl = _dereq_('./error-control');\r\n\r\nL.routing = {\r\n control: function(options) { return new Control(options); },\r\n itinerary: function(options) {\r\n return Itinerary(options);\r\n },\r\n line: function(route, options) {\r\n return new Line(route, options);\r\n },\r\n plan: function(waypoints, options) {\r\n return new Plan(waypoints, options);\r\n },\r\n waypoint: function(latLng, name, options) {\r\n return new Waypoint(latLng, name, options);\r\n },\r\n osrmv1: function(options) {\r\n return new OSRMv1(options);\r\n },\r\n localization: function(options) {\r\n return new Localization(options);\r\n },\r\n formatter: function(options) {\r\n return new Formatter(options);\r\n },\r\n geocoderElement: function(wp, i, nWps, plan) {\r\n return new L.Routing.GeocoderElement(wp, i, nWps, plan);\r\n },\r\n itineraryBuilder: function(options) {\r\n return new ItineraryBuilder(options);\r\n },\r\n mapbox: function(accessToken, options) {\r\n return new Mapbox(accessToken, options);\r\n },\r\n errorControl: function(routingControl, options) {\r\n return new ErrorControl(routingControl, options);\r\n },\r\n autocomplete: function(elem, callback, context, options) {\r\n return new Autocomplete(elem, callback, context, options);\r\n }\r\n};\r\n\r\nmodule.exports = L.Routing = {\r\n Control: Control,\r\n Itinerary: Itinerary,\r\n Line: Line,\r\n OSRMv1: OSRMv1,\r\n Plan: Plan,\r\n Waypoint: Waypoint,\r\n Autocomplete: Autocomplete,\r\n Formatter: Formatter,\r\n GeocoderElement: GeocoderElement,\r\n Localization: Localization,\r\n ItineraryBuilder: ItineraryBuilder,\r\n\r\n // Legacy; remove these in next major release\r\n control: L.routing.control,\r\n itinerary: L.routing.itinerary,\r\n line: L.routing.line,\r\n plan: L.routing.plan,\r\n waypoint: L.routing.waypoint,\r\n osrmv1: L.routing.osrmv1,\r\n geocoderElement: L.routing.geocoderElement,\r\n mapbox: L.routing.mapbox,\r\n errorControl: L.routing.errorControl,\r\n};\r\n\r\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\r\n }, { \"./autocomplete\": 55, \"./control\": 56, \"./error-control\": 57, \"./formatter\": 58, \"./geocoder-element\": 59, \"./itinerary\": 62, \"./itinerary-builder\": 61, \"./line\": 63, \"./localization\": 64, \"./mapbox\": 65, \"./osrm-v1\": 66, \"./plan\": 67, \"./waypoint\": 68 }],\r\n 61: [function (_dereq_, module, exports) {\r\n(function (global){\r\n(function() {\r\n\t'use strict';\r\n\r\n\tvar L = (typeof window !== \"undefined\" ? window['L'] : typeof global !== \"undefined\" ? global['L'] : null);\r\n\r\n\tmodule.exports = L.Class.extend({\r\n\t\toptions: {\r\n\t\t\tcontainerClassName: ''\r\n\t\t},\r\n\r\n\t\tinitialize: function(options) {\r\n\t\t\tL.setOptions(this, options);\r\n\t\t},\r\n\r\n\t\tcreateContainer: function(className) {\r\n\t\t\tvar table = L.DomUtil.create('table', (className || '') + ' ' + this.options.containerClassName),\r\n\t\t\t\tcolgroup = L.DomUtil.create('colgroup', '', table);\r\n\r\n\t\t\tL.DomUtil.create('col', 'leaflet-routing-instruction-icon', colgroup);\r\n\t\t\tL.DomUtil.create('col', 'leaflet-routing-instruction-text', colgroup);\r\n\t\t\tL.DomUtil.create('col', 'leaflet-routing-instruction-distance', colgroup);\r\n\r\n\t\t\treturn table;\r\n\t\t},\r\n\r\n\t\tcreateStepsContainer: function() {\r\n\t\t\treturn L.DomUtil.create('tbody', '');\r\n\t\t},\r\n\r\n\t\tcreateStep: function(text, distance, icon, steps) {\r\n\t\t\tvar row = L.DomUtil.create('tr', '', steps),\r\n\t\t\t\tspan,\r\n\t\t\t\ttd;\r\n\t\t\ttd = L.DomUtil.create('td', '', row);\r\n\t\t\tspan = L.DomUtil.create('span', 'leaflet-routing-icon leaflet-routing-icon-'+icon, td);\r\n\t\t\ttd.appendChild(span);\r\n\t\t\ttd = L.DomUtil.create('td', '', row);\r\n\t\t\ttd.appendChild(document.createTextNode(text));\r\n\t\t\ttd = L.DomUtil.create('td', '', row);\r\n\t\t\ttd.appendChild(document.createTextNode(distance));\r\n\t\t\treturn row;\r\n\t\t}\r\n\t});\r\n})();\r\n\r\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\r\n }, {}],\r\n 62: [function (_dereq_, module, exports) {\r\n(function (global){\r\n(function() {\r\n\t'use strict';\r\n\r\n\tvar L = (typeof window !== \"undefined\" ? window['L'] : typeof global !== \"undefined\" ? global['L'] : null);\r\n\tvar Formatter = _dereq_('./formatter');\r\n\tvar ItineraryBuilder = _dereq_('./itinerary-builder');\r\n\r\n\tmodule.exports = L.Control.extend({\r\n\t\tincludes: ((typeof L.Evented !== 'undefined' && L.Evented.prototype) || L.Mixin.Events),\r\n\r\n\t\toptions: {\r\n\t\t\tpointMarkerStyle: {\r\n\t\t\t\tradius: 5,\r\n\t\t\t\tcolor: '#03f',\r\n\t\t\t\tfillColor: 'white',\r\n\t\t\t\topacity: 1,\r\n\t\t\t\tfillOpacity: 0.7\r\n\t\t\t},\r\n\t\t\tsummaryTemplate: '

      {name}

      {distance}, {time}

      ',\r\n\t\t\ttimeTemplate: '{time}',\r\n\t\t\tcontainerClassName: '',\r\n\t\t\talternativeClassName: '',\r\n\t\t\tminimizedClassName: '',\r\n\t\t\titineraryClassName: '',\r\n\t\t\ttotalDistanceRoundingSensitivity: -1,\r\n\t\t\tshow: true,\r\n\t\t\tcollapsible: undefined,\r\n\t\t\tcollapseBtn: function(itinerary) {\r\n\t\t\t\tvar collapseBtn = L.DomUtil.create('span', itinerary.options.collapseBtnClass);\r\n\t\t\t\tL.DomEvent.on(collapseBtn, 'click', itinerary._toggle, itinerary);\r\n\t\t\t\titinerary._container.insertBefore(collapseBtn, itinerary._container.firstChild);\r\n\t\t\t},\r\n\t\t\tcollapseBtnClass: 'leaflet-routing-collapse-btn'\r\n\t\t},\r\n\r\n\t\tinitialize: function(options) {\r\n\t\t\tL.setOptions(this, options);\r\n\t\t\tthis._formatter = this.options.formatter || new Formatter(this.options);\r\n\t\t\tthis._itineraryBuilder = this.options.itineraryBuilder || new ItineraryBuilder({\r\n\t\t\t\tcontainerClassName: this.options.itineraryClassName\r\n\t\t\t});\r\n\t\t},\r\n\r\n\t\tonAdd: function(map) {\r\n\t\t\tvar collapsible = this.options.collapsible;\r\n\r\n\t\t\tcollapsible = collapsible || (collapsible === undefined && map.getSize().x <= 640);\r\n\r\n\t\t\tthis._container = L.DomUtil.create('div', 'leaflet-routing-container leaflet-bar ' +\r\n\t\t\t\t(!this.options.show ? 'leaflet-routing-container-hide ' : '') +\r\n\t\t\t\t(collapsible ? 'leaflet-routing-collapsible ' : '') +\r\n\t\t\t\tthis.options.containerClassName);\r\n\t\t\tthis._altContainer = this.createAlternativesContainer();\r\n\t\t\tthis._container.appendChild(this._altContainer);\r\n\t\t\tL.DomEvent.disableClickPropagation(this._container);\r\n\t\t\tL.DomEvent.addListener(this._container, 'mousewheel', function(e) {\r\n\t\t\t\tL.DomEvent.stopPropagation(e);\r\n\t\t\t});\r\n\r\n\t\t\tif (collapsible) {\r\n\t\t\t\tthis.options.collapseBtn(this);\r\n\t\t\t}\r\n\r\n\t\t\treturn this._container;\r\n\t\t},\r\n\r\n\t\tonRemove: function() {\r\n\t\t},\r\n\r\n\t\tcreateAlternativesContainer: function() {\r\n\t\t\treturn L.DomUtil.create('div', 'leaflet-routing-alternatives-container');\r\n\t\t},\r\n\r\n\t\tsetAlternatives: function(routes) {\r\n\t\t\tvar i,\r\n\t\t\t alt,\r\n\t\t\t altDiv;\r\n\r\n\t\t\tthis._clearAlts();\r\n\r\n\t\t\tthis._routes = routes;\r\n\r\n\t\t\tfor (i = 0; i < this._routes.length; i++) {\r\n\t\t\t\talt = this._routes[i];\r\n\t\t\t\taltDiv = this._createAlternative(alt, i);\r\n\t\t\t\tthis._altContainer.appendChild(altDiv);\r\n\t\t\t\tthis._altElements.push(altDiv);\r\n\t\t\t}\r\n\r\n\t\t\tthis._selectRoute({route: this._routes[0], alternatives: this._routes.slice(1)});\r\n\r\n\t\t\treturn this;\r\n\t\t},\r\n\r\n\t\tshow: function() {\r\n\t\t\tL.DomUtil.removeClass(this._container, 'leaflet-routing-container-hide');\r\n\t\t},\r\n\r\n\t\thide: function() {\r\n\t\t\tL.DomUtil.addClass(this._container, 'leaflet-routing-container-hide');\r\n\t\t},\r\n\r\n\t\t_toggle: function() {\r\n\t\t\tvar collapsed = L.DomUtil.hasClass(this._container, 'leaflet-routing-container-hide');\r\n\t\t\tthis[collapsed ? 'show' : 'hide']();\r\n\t\t},\r\n\r\n\t\t_createAlternative: function(alt, i) {\r\n\t\t\tvar altDiv = L.DomUtil.create('div', 'leaflet-routing-alt ' +\r\n\t\t\t\tthis.options.alternativeClassName +\r\n\t\t\t\t(i > 0 ? ' leaflet-routing-alt-minimized ' + this.options.minimizedClassName : '')),\r\n\t\t\t\ttemplate = this.options.summaryTemplate,\r\n\t\t\t\tdata = L.extend({\r\n\t\t\t\t\tname: alt.name,\r\n\t\t\t\t\tdistance: this._formatter.formatDistance(alt.summary.totalDistance, this.options.totalDistanceRoundingSensitivity),\r\n\t\t\t\t\ttime: this._formatter.formatTime(alt.summary.totalTime)\r\n\t\t\t\t}, alt);\r\n\t\t\taltDiv.innerHTML = typeof(template) === 'function' ? template(data) : L.Util.template(template, data);\r\n\t\t\tL.DomEvent.addListener(altDiv, 'click', this._onAltClicked, this);\r\n\t\t\tthis.on('routeselected', this._selectAlt, this);\r\n\r\n\t\t\taltDiv.appendChild(this._createItineraryContainer(alt));\r\n\t\t\treturn altDiv;\r\n\t\t},\r\n\r\n\t\t_clearAlts: function() {\r\n\t\t\tvar el = this._altContainer;\r\n\t\t\twhile (el && el.firstChild) {\r\n\t\t\t\tel.removeChild(el.firstChild);\r\n\t\t\t}\r\n\r\n\t\t\tthis._altElements = [];\r\n\t\t},\r\n\r\n\t\t_createItineraryContainer: function(r) {\r\n\t\t\tvar container = this._itineraryBuilder.createContainer(),\r\n\t\t\t steps = this._itineraryBuilder.createStepsContainer(),\r\n\t\t\t i,\r\n\t\t\t instr,\r\n\t\t\t step,\r\n\t\t\t distance,\r\n\t\t\t text,\r\n\t\t\t icon;\r\n\r\n\t\t\tcontainer.appendChild(steps);\r\n\r\n\t\t\tfor (i = 0; i < r.instructions.length; i++) {\r\n\t\t\t\tinstr = r.instructions[i];\r\n\t\t\t\ttext = this._formatter.formatInstruction(instr, i);\r\n\t\t\t\tdistance = this._formatter.formatDistance(instr.distance);\r\n\t\t\t\ticon = this._formatter.getIconName(instr, i);\r\n\t\t\t\tstep = this._itineraryBuilder.createStep(text, distance, icon, steps);\r\n\r\n\t\t\t\tif(instr.index) {\r\n\t\t\t\t\tthis._addRowListeners(step, r.coordinates[instr.index]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\treturn container;\r\n\t\t},\r\n\r\n\t\t_addRowListeners: function(row, coordinate) {\r\n\t\t\tL.DomEvent.addListener(row, 'mouseover', function() {\r\n\t\t\t\tthis._marker = L.circleMarker(coordinate,\r\n\t\t\t\t\tthis.options.pointMarkerStyle).addTo(this._map);\r\n\t\t\t}, this);\r\n\t\t\tL.DomEvent.addListener(row, 'mouseout', function() {\r\n\t\t\t\tif (this._marker) {\r\n\t\t\t\t\tthis._map.removeLayer(this._marker);\r\n\t\t\t\t\tdelete this._marker;\r\n\t\t\t\t}\r\n\t\t\t}, this);\r\n\t\t\tL.DomEvent.addListener(row, 'click', function(e) {\r\n\t\t\t\tthis._map.panTo(coordinate);\r\n\t\t\t\tL.DomEvent.stopPropagation(e);\r\n\t\t\t}, this);\r\n\t\t},\r\n\r\n\t\t_onAltClicked: function(e) {\r\n\t\t\tvar altElem = e.target || window.event.srcElement;\r\n\t\t\twhile (!L.DomUtil.hasClass(altElem, 'leaflet-routing-alt')) {\r\n\t\t\t\taltElem = altElem.parentElement;\r\n\t\t\t}\r\n\r\n\t\t\tvar j = this._altElements.indexOf(altElem);\r\n\t\t\tvar alts = this._routes.slice();\r\n\t\t\tvar route = alts.splice(j, 1)[0];\r\n\r\n\t\t\tthis.fire('routeselected', {\r\n\t\t\t\troute: route,\r\n\t\t\t\talternatives: alts\r\n\t\t\t});\r\n\t\t},\r\n\r\n\t\t_selectAlt: function(e) {\r\n\t\t\tvar altElem,\r\n\t\t\t j,\r\n\t\t\t n,\r\n\t\t\t classFn;\r\n\r\n\t\t\taltElem = this._altElements[e.route.routesIndex];\r\n\r\n\t\t\tif (L.DomUtil.hasClass(altElem, 'leaflet-routing-alt-minimized')) {\r\n\t\t\t\tfor (j = 0; j < this._altElements.length; j++) {\r\n\t\t\t\t\tn = this._altElements[j];\r\n\t\t\t\t\tclassFn = j === e.route.routesIndex ? 'removeClass' : 'addClass';\r\n\t\t\t\t\tL.DomUtil[classFn](n, 'leaflet-routing-alt-minimized');\r\n\t\t\t\t\tif (this.options.minimizedClassName) {\r\n\t\t\t\t\t\tL.DomUtil[classFn](n, this.options.minimizedClassName);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (j !== e.route.routesIndex) n.scrollTop = 0;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tL.DomEvent.stop(e);\r\n\t\t},\r\n\r\n\t\t_selectRoute: function(routes) {\r\n\t\t\tif (this._marker) {\r\n\t\t\t\tthis._map.removeLayer(this._marker);\r\n\t\t\t\tdelete this._marker;\r\n\t\t\t}\r\n\t\t\tthis.fire('routeselected', routes);\r\n\t\t}\r\n\t});\r\n})();\r\n\r\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\r\n }, { \"./formatter\": 58, \"./itinerary-builder\": 61 }],\r\n 63: [function (_dereq_, module, exports) {\r\n(function (global){\r\n(function() {\r\n\t'use strict';\r\n\r\n\tvar L = (typeof window !== \"undefined\" ? window['L'] : typeof global !== \"undefined\" ? global['L'] : null);\r\n\r\n\tmodule.exports = L.LayerGroup.extend({\r\n\t\tincludes: ((typeof L.Evented !== 'undefined' && L.Evented.prototype) || L.Mixin.Events),\r\n\r\n\t\toptions: {\r\n\t\t\tstyles: [\r\n\t\t\t\t{color: 'black', opacity: 0.15, weight: 9},\r\n\t\t\t\t{color: 'white', opacity: 0.8, weight: 6},\r\n\t\t\t\t{color: 'red', opacity: 1, weight: 2}\r\n\t\t\t],\r\n\t\t\tmissingRouteStyles: [\r\n\t\t\t\t{color: 'black', opacity: 0.15, weight: 7},\r\n\t\t\t\t{color: 'white', opacity: 0.6, weight: 4},\r\n\t\t\t\t{color: 'gray', opacity: 0.8, weight: 2, dashArray: '7,12'}\r\n\t\t\t],\r\n\t\t\taddWaypoints: true,\r\n\t\t\textendToWaypoints: true,\r\n\t\t\tmissingRouteTolerance: 10\r\n\t\t},\r\n\r\n\t\tinitialize: function(route, options) {\r\n\t\t\tL.setOptions(this, options);\r\n\t\t\tL.LayerGroup.prototype.initialize.call(this, options);\r\n\t\t\tthis._route = route;\r\n\r\n\t\t\tif (this.options.extendToWaypoints) {\r\n\t\t\t\tthis._extendToWaypoints();\r\n\t\t\t}\r\n\r\n\t\t\tthis._addSegment(\r\n\t\t\t\troute.coordinates,\r\n\t\t\t\tthis.options.styles,\r\n\t\t\t\tthis.options.addWaypoints);\r\n\t\t},\r\n\r\n\t\tgetBounds: function() {\r\n\t\t\treturn L.latLngBounds(this._route.coordinates);\r\n\t\t},\r\n\r\n\t\t_findWaypointIndices: function() {\r\n\t\t\tvar wps = this._route.inputWaypoints,\r\n\t\t\t indices = [],\r\n\t\t\t i;\r\n\t\t\tfor (i = 0; i < wps.length; i++) {\r\n\t\t\t\tindices.push(this._findClosestRoutePoint(wps[i].latLng));\r\n\t\t\t}\r\n\r\n\t\t\treturn indices;\r\n\t\t},\r\n\r\n\t\t_findClosestRoutePoint: function(latlng) {\r\n\t\t\tvar minDist = Number.MAX_VALUE,\r\n\t\t\t\tminIndex,\r\n\t\t\t i,\r\n\t\t\t d;\r\n\r\n\t\t\tfor (i = this._route.coordinates.length - 1; i >= 0 ; i--) {\r\n\t\t\t\t// TODO: maybe do this in pixel space instead?\r\n\t\t\t\td = latlng.distanceTo(this._route.coordinates[i]);\r\n\t\t\t\tif (d < minDist) {\r\n\t\t\t\t\tminIndex = i;\r\n\t\t\t\t\tminDist = d;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\treturn minIndex;\r\n\t\t},\r\n\r\n\t\t_extendToWaypoints: function() {\r\n\t\t\tvar wps = this._route.inputWaypoints,\r\n\t\t\t\twpIndices = this._getWaypointIndices(),\r\n\t\t\t i,\r\n\t\t\t wpLatLng,\r\n\t\t\t routeCoord;\r\n\r\n\t\t\tfor (i = 0; i < wps.length; i++) {\r\n\t\t\t\twpLatLng = wps[i].latLng;\r\n\t\t\t\trouteCoord = L.latLng(this._route.coordinates[wpIndices[i]]);\r\n\t\t\t\tif (wpLatLng.distanceTo(routeCoord) >\r\n\t\t\t\t\tthis.options.missingRouteTolerance) {\r\n\t\t\t\t\tthis._addSegment([wpLatLng, routeCoord],\r\n\t\t\t\t\t\tthis.options.missingRouteStyles);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t_addSegment: function(coords, styles, mouselistener) {\r\n\t\t\tvar i,\r\n\t\t\t\tpl;\r\n\r\n\t\t\tfor (i = 0; i < styles.length; i++) {\r\n\t\t\t\tpl = L.polyline(coords, styles[i]);\r\n\t\t\t\tthis.addLayer(pl);\r\n\t\t\t\tif (mouselistener) {\r\n\t\t\t\t\tpl.on('mousedown', this._onLineTouched, this);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t_findNearestWpBefore: function(i) {\r\n\t\t\tvar wpIndices = this._getWaypointIndices(),\r\n\t\t\t\tj = wpIndices.length - 1;\r\n\t\t\twhile (j >= 0 && wpIndices[j] > i) {\r\n\t\t\t\tj--;\r\n\t\t\t}\r\n\r\n\t\t\treturn j;\r\n\t\t},\r\n\r\n\t\t_onLineTouched: function(e) {\r\n\t\t\tvar afterIndex = this._findNearestWpBefore(this._findClosestRoutePoint(e.latlng));\r\n\t\t\tthis.fire('linetouched', {\r\n\t\t\t\tafterIndex: afterIndex,\r\n\t\t\t\tlatlng: e.latlng\r\n\t\t\t});\r\n\t\t\tL.DomEvent.stop(e);\r\n\t\t},\r\n\r\n\t\t_getWaypointIndices: function() {\r\n\t\t\tif (!this._wpIndices) {\r\n\t\t\t\tthis._wpIndices = this._route.waypointIndices || this._findWaypointIndices();\r\n\t\t\t}\r\n\r\n\t\t\treturn this._wpIndices;\r\n\t\t}\r\n\t});\r\n})();\r\n\r\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\r\n }, {}],\r\n 64: [function (_dereq_, module, exports) {\r\n/* \r\n NOTICE\r\n Since version 3.2.5, the functionality in this file is by\r\n default NOT used for localizing OSRM instructions.\r\n Instead, we rely on the module osrm-text-instructions (https://github.com/Project-OSRM/osrm-text-instructions/).\r\n \r\n This file can still be used for other routing backends, or if you specify the\r\n stepToText option in the OSRMv1 class.\r\n*/\r\n\r\n(function() {\r\n\t'use strict';\r\n\r\n\tvar spanish = {\r\n\t\tdirections: {\r\n\t\t\tN: 'norte',\r\n\t\t\tNE: 'noreste',\r\n\t\t\tE: 'este',\r\n\t\t\tSE: 'sureste',\r\n\t\t\tS: 'sur',\r\n\t\t\tSW: 'suroeste',\r\n\t\t\tW: 'oeste',\r\n\t\t\tNW: 'noroeste',\r\n\t\t\tSlightRight: 'leve giro a la derecha',\r\n\t\t\tRight: 'derecha',\r\n\t\t\tSharpRight: 'giro pronunciado a la derecha',\r\n\t\t\tSlightLeft: 'leve giro a la izquierda',\r\n\t\t\tLeft: 'izquierda',\r\n\t\t\tSharpLeft: 'giro pronunciado a la izquierda',\r\n\t\t\tUturn: 'media vuelta'\r\n\t\t},\r\n\t\tinstructions: {\r\n\t\t\t// instruction, postfix if the road is named\r\n\t\t\t'Head':\r\n\t\t\t\t['Derecho {dir}', ' sobre {road}'],\r\n\t\t\t'Continue':\r\n\t\t\t\t['Continuar {dir}', ' en {road}'],\r\n\t\t\t'TurnAround':\r\n\t\t\t\t['Dar vuelta'],\r\n\t\t\t'WaypointReached':\r\n\t\t\t\t['Llegó a un punto del camino'],\r\n\t\t\t'Roundabout':\r\n\t\t\t\t['Tomar {exitStr} salida en la rotonda', ' en {road}'],\r\n\t\t\t'DestinationReached':\r\n\t\t\t\t['Llegada a destino'],\r\n\t\t\t'Fork': ['En el cruce gira a {modifier}', ' hacia {road}'],\r\n\t\t\t'Merge': ['Incorpórate {modifier}', ' hacia {road}'],\r\n\t\t\t'OnRamp': ['Gira {modifier} en la salida', ' hacia {road}'],\r\n\t\t\t'OffRamp': ['Toma la salida {modifier}', ' hacia {road}'],\r\n\t\t\t'EndOfRoad': ['Gira {modifier} al final de la carretera', ' hacia {road}'],\r\n\t\t\t'Onto': 'hacia {road}'\r\n\t\t},\r\n\t\tformatOrder: function(n) {\r\n\t\t\treturn n + 'º';\r\n\t\t},\r\n\t\tui: {\r\n\t\t\tstartPlaceholder: 'Inicio',\r\n\t\t\tviaPlaceholder: 'Via {viaNumber}',\r\n\t\t\tendPlaceholder: 'Destino'\r\n\t\t},\r\n\t\tunits: {\r\n\t\t\tmeters: 'm',\r\n\t\t\tkilometers: 'km',\r\n\t\t\tyards: 'yd',\r\n\t\t\tmiles: 'mi',\r\n\t\t\thours: 'h',\r\n\t\t\tminutes: 'min',\r\n\t\t\tseconds: 's'\r\n\t\t}\r\n\t};\r\n\r\n\tL.Routing = L.Routing || {};\r\n\r\n\tvar Localization = L.Class.extend({\r\n\t\tinitialize: function(langs) {\r\n\t\t\tthis._langs = L.Util.isArray(langs) ? langs.slice() : [langs, 'en'];\r\n\r\n\t\t\tfor (var i = 0, l = this._langs.length; i < l; i++) {\r\n\t\t\t\tvar generalizedCode = /([A-Za-z]+)/.exec(this._langs[i])[1]\r\n\t\t\t\tif (!Localization[this._langs[i]]) {\r\n\t\t\t\t\tif (Localization[generalizedCode]) {\r\n\t\t\t\t\t\tthis._langs[i] = generalizedCode;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tthrow new Error('No localization for language \"' + this._langs[i] + '\".');\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tlocalize: function(keys) {\r\n\t\t\tvar dict,\r\n\t\t\t\tkey,\r\n\t\t\t\tvalue;\r\n\r\n\t\t\tkeys = L.Util.isArray(keys) ? keys : [keys];\r\n\r\n\t\t\tfor (var i = 0, l = this._langs.length; i < l; i++) {\r\n\t\t\t\tdict = Localization[this._langs[i]];\r\n\t\t\t\tfor (var j = 0, nKeys = keys.length; dict && j < nKeys; j++) {\r\n\t\t\t\t\tkey = keys[j];\r\n\t\t\t\t\tvalue = dict[key];\r\n\t\t\t\t\tdict = value;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (value) {\r\n\t\t\t\t\treturn value;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n\r\n module.exports = L.extend(Localization, {\r\n // language must be defined here to work\r\n 'de': {\r\n directions: {\r\n N: 'Norden',\r\n NE: 'Nordosten',\r\n E: 'Osten',\r\n SE: 'Südosten',\r\n S: 'Süden',\r\n SW: 'Südwesten',\r\n W: 'Westen',\r\n NW: 'Nordwesten',\r\n SlightRight: 'leicht rechts',\r\n Right: 'rechts',\r\n SharpRight: 'scharf rechts',\r\n SlightLeft: 'leicht links',\r\n Left: 'links',\r\n SharpLeft: 'scharf links',\r\n Uturn: 'Wenden'\r\n },\r\n instructions: {\r\n // instruction, postfix if the road is named\r\n 'Head':\r\n ['Richtung {dir}', ' auf {road}'],\r\n 'Continue':\r\n ['Geradeaus Richtung {dir}', ' auf {road}'],\r\n 'SlightRight':\r\n ['Leicht rechts abbiegen', ' auf {road}'],\r\n 'Right':\r\n ['Rechts abbiegen', ' auf {road}'],\r\n 'SharpRight':\r\n ['Scharf rechts abbiegen', ' auf {road}'],\r\n 'TurnAround':\r\n ['Wenden'],\r\n 'SharpLeft':\r\n ['Scharf links abbiegen', ' auf {road}'],\r\n 'Left':\r\n ['Links abbiegen', ' auf {road}'],\r\n 'SlightLeft':\r\n ['Leicht links abbiegen', ' auf {road}'],\r\n 'WaypointReached':\r\n ['Zwischenhalt erreicht'],\r\n 'Roundabout':\r\n ['Nehmen Sie die {exitStr} Ausfahrt im Kreisverkehr', ' auf {road}'],\r\n 'DestinationReached':\r\n ['Sie haben ihr Ziel erreicht'],\r\n 'Fork': ['An der Kreuzung {modifier}', ' auf {road}'],\r\n 'Merge': ['Fahren Sie {modifier} weiter', ' auf {road}'],\r\n 'OnRamp': ['Fahren Sie {modifier} auf die Auffahrt', ' auf {road}'],\r\n 'OffRamp': ['Nehmen Sie die Ausfahrt {modifier}', ' auf {road}'],\r\n 'EndOfRoad': ['Fahren Sie {modifier} am Ende der Straße', ' auf {road}'],\r\n 'Onto': 'auf {road}'\r\n },\r\n formatOrder: function (n) {\r\n return n + '.';\r\n },\r\n ui: {\r\n startPlaceholder: 'Start',\r\n viaPlaceholder: 'Via {viaNumber}',\r\n endPlaceholder: 'Ziel'\r\n }\r\n },\r\n 'en': {\r\n\t\t\tdirections: {\r\n\t\t\t\tN: 'north',\r\n\t\t\t\tNE: 'northeast',\r\n\t\t\t\tE: 'east',\r\n\t\t\t\tSE: 'southeast',\r\n\t\t\t\tS: 'south',\r\n\t\t\t\tSW: 'southwest',\r\n\t\t\t\tW: 'west',\r\n\t\t\t\tNW: 'northwest',\r\n\t\t\t\tSlightRight: 'slight right',\r\n\t\t\t\tRight: 'right',\r\n\t\t\t\tSharpRight: 'sharp right',\r\n\t\t\t\tSlightLeft: 'slight left',\r\n\t\t\t\tLeft: 'left',\r\n\t\t\t\tSharpLeft: 'sharp left',\r\n\t\t\t\tUturn: 'Turn around'\r\n\t\t\t},\r\n\t\t\tinstructions: {\r\n\t\t\t\t// instruction, postfix if the road is named\r\n\t\t\t\t'Head':\r\n\t\t\t\t\t['Head {dir}', ' on {road}'],\r\n\t\t\t\t'Continue':\r\n\t\t\t\t\t['Continue {dir}'],\r\n\t\t\t\t'TurnAround':\r\n\t\t\t\t\t['Turn around'],\r\n\t\t\t\t'WaypointReached':\r\n\t\t\t\t\t['Waypoint reached'],\r\n\t\t\t\t'Roundabout':\r\n\t\t\t\t\t['Take the {exitStr} exit in the roundabout', ' onto {road}'],\r\n\t\t\t\t'DestinationReached':\r\n\t\t\t\t\t['Destination reached'],\r\n\t\t\t\t'Fork': ['At the fork, turn {modifier}', ' onto {road}'],\r\n\t\t\t\t'Merge': ['Merge {modifier}', ' onto {road}'],\r\n\t\t\t\t'OnRamp': ['Turn {modifier} on the ramp', ' onto {road}'],\r\n\t\t\t\t'OffRamp': ['Take the ramp on the {modifier}', ' onto {road}'],\r\n\t\t\t\t'EndOfRoad': ['Turn {modifier} at the end of the road', ' onto {road}'],\r\n\t\t\t\t'Onto': 'onto {road}'\r\n\t\t\t},\r\n\t\t\tformatOrder: function(n) {\r\n\t\t\t\tvar i = n % 10 - 1,\r\n\t\t\t\tsuffix = ['st', 'nd', 'rd'];\r\n\r\n\t\t\t\treturn suffix[i] ? n + suffix[i] : n + 'th';\r\n\t\t\t},\r\n\t\t\tui: {\r\n\t\t\t\tstartPlaceholder: 'Start',\r\n\t\t\t\tviaPlaceholder: 'Via {viaNumber}',\r\n\t\t\t\tendPlaceholder: 'End'\r\n\t\t\t},\r\n\t\t\tunits: {\r\n\t\t\t\tmeters: 'm',\r\n\t\t\t\tkilometers: 'km',\r\n\t\t\t\tyards: 'yd',\r\n\t\t\t\tmiles: 'mi',\r\n\t\t\t\thours: 'h',\r\n\t\t\t\tminutes: 'min',\r\n\t\t\t\tseconds: 's'\r\n\t\t\t}\r\n\t\t},\r\n\t\t'es': spanish,\r\n\t\t'fr': {\r\n\t\t\tdirections: {\r\n\t\t\t\tN: 'nord',\r\n\t\t\t\tNE: 'nord-est',\r\n\t\t\t\tE: 'est',\r\n\t\t\t\tSE: 'sud-est',\r\n\t\t\t\tS: 'sud',\r\n\t\t\t\tSW: 'sud-ouest',\r\n\t\t\t\tW: 'ouest',\r\n\t\t\t\tNW: 'nord-ouest'\r\n\t\t\t},\r\n\t\t\tinstructions: {\r\n\t\t\t\t// instruction, postfix if the road is named\r\n\t\t\t\t'Head':\r\n\t\t\t\t\t['Tout droit au {dir}', ' sur {road}'],\r\n\t\t\t\t'Continue':\r\n\t\t\t\t\t['Continuer au {dir}', ' sur {road}'],\r\n\t\t\t\t'SlightRight':\r\n\t\t\t\t\t['Légèrement à droite', ' sur {road}'],\r\n\t\t\t\t'Right':\r\n\t\t\t\t\t['A droite', ' sur {road}'],\r\n\t\t\t\t'SharpRight':\r\n\t\t\t\t\t['Complètement à droite', ' sur {road}'],\r\n\t\t\t\t'TurnAround':\r\n\t\t\t\t\t['Faire demi-tour'],\r\n\t\t\t\t'SharpLeft':\r\n\t\t\t\t\t['Complètement à gauche', ' sur {road}'],\r\n\t\t\t\t'Left':\r\n\t\t\t\t\t['A gauche', ' sur {road}'],\r\n\t\t\t\t'SlightLeft':\r\n\t\t\t\t\t['Légèrement à gauche', ' sur {road}'],\r\n\t\t\t\t'WaypointReached':\r\n\t\t\t\t\t['Point d\\'étape atteint'],\r\n\t\t\t\t'Roundabout':\r\n\t\t\t\t\t['Au rond-point, prenez la {exitStr} sortie', ' sur {road}'],\r\n\t\t\t\t'DestinationReached':\r\n\t\t\t\t\t['Destination atteinte'],\r\n\t\t\t},\r\n\t\t\tformatOrder: function(n) {\r\n\t\t\t\treturn n + 'º';\r\n\t\t\t},\r\n\t\t\tui: {\r\n\t\t\t\tstartPlaceholder: 'Départ',\r\n\t\t\t\tviaPlaceholder: 'Intermédiaire {viaNumber}',\r\n\t\t\t\tendPlaceholder: 'Arrivée'\r\n\t\t\t}\r\n },\r\n 'it': {\r\n directions: {\r\n N: 'nord',\r\n NE: 'nord-est',\r\n E: 'est',\r\n SE: 'sud-est',\r\n S: 'sud',\r\n SW: 'sud-ovest',\r\n W: 'ovest',\r\n NW: 'nord-ovest'\r\n },\r\n instructions: {\r\n // instruction, postfix if the road is named\r\n 'Head':\r\n ['Dritto verso {dir}', ' su {road}'],\r\n 'Continue':\r\n ['Continuare verso {dir}', ' su {road}'],\r\n 'SlightRight':\r\n ['Mantenere la destra', ' su {road}'],\r\n 'Right':\r\n ['A destra', ' su {road}'],\r\n 'SharpRight':\r\n ['Strettamente a destra', ' su {road}'],\r\n 'TurnAround':\r\n ['Fare inversione di marcia'],\r\n 'SharpLeft':\r\n ['Strettamente a sinistra', ' su {road}'],\r\n 'Left':\r\n ['A sinistra', ' sur {road}'],\r\n 'SlightLeft':\r\n ['Mantenere la sinistra', ' su {road}'],\r\n 'WaypointReached':\r\n ['Punto di passaggio raggiunto'],\r\n 'Roundabout':\r\n ['Alla rotonda, prendere la {exitStr} uscita'],\r\n 'DestinationReached':\r\n ['Destinazione raggiunta'],\r\n },\r\n formatOrder: function (n) {\r\n return n + 'º';\r\n },\r\n ui: {\r\n startPlaceholder: 'Partenza',\r\n viaPlaceholder: 'Intermedia {viaNumber}',\r\n endPlaceholder: 'Destinazione'\r\n }\r\n },\r\n 'ja': {},\r\n 'pt': {\r\n\t\t\tdirections: {\r\n\t\t\t\tN: 'norte',\r\n\t\t\t\tNE: 'nordeste',\r\n\t\t\t\tE: 'leste',\r\n\t\t\t\tSE: 'sudeste',\r\n\t\t\t\tS: 'sul',\r\n\t\t\t\tSW: 'sudoeste',\r\n\t\t\t\tW: 'oeste',\r\n\t\t\t\tNW: 'noroeste',\r\n\t\t\t\tSlightRight: 'curva ligeira a direita',\r\n\t\t\t\tRight: 'direita',\r\n\t\t\t\tSharpRight: 'curva fechada a direita',\r\n\t\t\t\tSlightLeft: 'ligeira a esquerda',\r\n\t\t\t\tLeft: 'esquerda',\r\n\t\t\t\tSharpLeft: 'curva fechada a esquerda',\r\n\t\t\t\tUturn: 'Meia volta'\r\n\t\t\t},\r\n\t\t\tinstructions: {\r\n\t\t\t\t// instruction, postfix if the road is named\r\n\t\t\t\t'Head':\r\n\t\t\t\t\t['Siga {dir}', ' na {road}'],\r\n\t\t\t\t'Continue':\r\n\t\t\t\t\t['Continue {dir}', ' na {road}'],\r\n\t\t\t\t'SlightRight':\r\n\t\t\t\t\t['Curva ligeira a direita', ' na {road}'],\r\n\t\t\t\t'Right':\r\n\t\t\t\t\t['Curva a direita', ' na {road}'],\r\n\t\t\t\t'SharpRight':\r\n\t\t\t\t\t['Curva fechada a direita', ' na {road}'],\r\n\t\t\t\t'TurnAround':\r\n\t\t\t\t\t['Retorne'],\r\n\t\t\t\t'SharpLeft':\r\n\t\t\t\t\t['Curva fechada a esquerda', ' na {road}'],\r\n\t\t\t\t'Left':\r\n\t\t\t\t\t['Curva a esquerda', ' na {road}'],\r\n\t\t\t\t'SlightLeft':\r\n\t\t\t\t\t['Curva ligueira a esquerda', ' na {road}'],\r\n\t\t\t\t'WaypointReached':\r\n\t\t\t\t\t['Ponto de interesse atingido'],\r\n\t\t\t\t'Roundabout':\r\n\t\t\t\t\t['Pegue a {exitStr} saída na rotatória', ' na {road}'],\r\n\t\t\t\t'DestinationReached':\r\n\t\t\t\t\t['Destino atingido'],\r\n\t\t\t\t'Fork': ['Na encruzilhada, vire a {modifier}', ' na {road}'],\r\n\t\t\t\t'Merge': ['Entre à {modifier}', ' na {road}'],\r\n\t\t\t\t'OnRamp': ['Vire {modifier} na rampa', ' na {road}'],\r\n\t\t\t\t'OffRamp': ['Entre na rampa na {modifier}', ' na {road}'],\r\n\t\t\t\t'EndOfRoad': ['Vire {modifier} no fim da rua', ' na {road}'],\r\n\t\t\t\t'Onto': 'na {road}'\r\n\t\t\t},\r\n\t\t\tformatOrder: function(n) {\r\n\t\t\t\treturn n + 'º';\r\n\t\t\t},\r\n\t\t\tui: {\r\n\t\t\t\tstartPlaceholder: 'Origem',\r\n\t\t\t\tviaPlaceholder: 'Intermédio {viaNumber}',\r\n\t\t\t\tendPlaceholder: 'Destino'\r\n\t\t\t}\r\n\t\t},\r\n\t\t'ru': {\r\n\t\t\tdirections: {\r\n\t\t\t\tN: 'север',\r\n\t\t\t\tNE: 'северовосток',\r\n\t\t\t\tE: 'восток',\r\n\t\t\t\tSE: 'юговосток',\r\n\t\t\t\tS: 'юг',\r\n\t\t\t\tSW: 'югозапад',\r\n\t\t\t\tW: 'запад',\r\n\t\t\t\tNW: 'северозапад',\r\n\t\t\t\tSlightRight: 'плавно направо',\r\n\t\t\t\tRight: 'направо',\r\n\t\t\t\tSharpRight: 'резко направо',\r\n\t\t\t\tSlightLeft: 'плавно налево',\r\n\t\t\t\tLeft: 'налево',\r\n\t\t\t\tSharpLeft: 'резко налево',\r\n\t\t\t\tUturn: 'развернуться'\r\n\t\t\t},\r\n\t\t\tinstructions: {\r\n\t\t\t\t'Head':\r\n\t\t\t\t\t['Начать движение на {dir}', ' по {road}'],\r\n\t\t\t\t'Continue':\r\n\t\t\t\t\t['Продолжать движение на {dir}', ' по {road}'],\r\n\t\t\t\t'SlightRight':\r\n\t\t\t\t\t['Плавный поворот направо', ' на {road}'],\r\n\t\t\t\t'Right':\r\n\t\t\t\t\t['Направо', ' на {road}'],\r\n\t\t\t\t'SharpRight':\r\n\t\t\t\t\t['Резкий поворот направо', ' на {road}'],\r\n\t\t\t\t'TurnAround':\r\n\t\t\t\t\t['Развернуться'],\r\n\t\t\t\t'SharpLeft':\r\n\t\t\t\t\t['Резкий поворот налево', ' на {road}'],\r\n\t\t\t\t'Left':\r\n\t\t\t\t\t['Поворот налево', ' на {road}'],\r\n\t\t\t\t'SlightLeft':\r\n\t\t\t\t\t['Плавный поворот налево', ' на {road}'],\r\n\t\t\t\t'WaypointReached':\r\n\t\t\t\t\t['Точка достигнута'],\r\n\t\t\t\t'Roundabout':\r\n\t\t\t\t\t['{exitStr} съезд с кольца', ' на {road}'],\r\n\t\t\t\t'DestinationReached':\r\n\t\t\t\t\t['Окончание маршрута'],\r\n\t\t\t\t'Fork': ['На развилке поверните {modifier}', ' на {road}'],\r\n\t\t\t\t'Merge': ['Перестройтесь {modifier}', ' на {road}'],\r\n\t\t\t\t'OnRamp': ['Поверните {modifier} на съезд', ' на {road}'],\r\n\t\t\t\t'OffRamp': ['Съезжайте на {modifier}', ' на {road}'],\r\n\t\t\t\t'EndOfRoad': ['Поверните {modifier} в конце дороги', ' на {road}'],\r\n\t\t\t\t'Onto': 'на {road}'\r\n\t\t\t},\r\n\t\t\tformatOrder: function(n) {\r\n\t\t\t\treturn n + '-й';\r\n\t\t\t},\r\n\t\t\tui: {\r\n\t\t\t\tstartPlaceholder: 'Начало',\r\n\t\t\t\tviaPlaceholder: 'Через {viaNumber}',\r\n\t\t\t\tendPlaceholder: 'Конец'\r\n\t\t\t},\r\n\t\t\tunits: {\r\n\t\t\t\tmeters: 'м',\r\n\t\t\t\tkilometers: 'км',\r\n\t\t\t\tyards: 'ярд',\r\n\t\t\t\tmiles: 'ми',\r\n\t\t\t\thours: 'ч',\r\n\t\t\t\tminutes: 'м',\r\n\t\t\t\tseconds: 'с'\r\n\t\t\t}\r\n },\r\n 'vi': {}\r\n\t});\r\n})();\r\n\r\n }, {}],\r\n 65: [function (_dereq_, module, exports) {\r\n(function (global){\r\n(function() {\r\n\t'use strict';\r\n\r\n\tvar L = (typeof window !== \"undefined\" ? window['L'] : typeof global !== \"undefined\" ? global['L'] : null);\r\n\r\n\tvar OSRMv1 = _dereq_('./osrm-v1');\r\n\r\n\t/**\r\n\t * Works against OSRM's new API in version 5.0; this has\r\n\t * the API version v1.\r\n\t */\r\n\tmodule.exports = OSRMv1.extend({\r\n\t\toptions: {\r\n\t\t\tserviceUrl: 'https://api.mapbox.com/directions/v5',\r\n\t\t\tprofile: 'mapbox/driving',\r\n\t\t\tuseHints: false\r\n\t\t},\r\n\r\n\t\tinitialize: function(accessToken, options) {\r\n\t\t\tL.Routing.OSRMv1.prototype.initialize.call(this, options);\r\n\t\t\tthis.options.requestParameters = this.options.requestParameters || {};\r\n\t\t\t/* jshint camelcase: false */\r\n\t\t\tthis.options.requestParameters.access_token = accessToken;\r\n\t\t\t/* jshint camelcase: true */\r\n\t\t}\r\n\t});\r\n})();\r\n\r\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\r\n }, { \"./osrm-v1\": 66 }],\r\n 66: [function (_dereq_, module, exports) {\r\n(function (global){\r\n(function() {\r\n\t'use strict';\r\n\r\n\tvar L = (typeof window !== \"undefined\" ? window['L'] : typeof global !== \"undefined\" ? global['L'] : null),\r\n\t\tcorslite = _dereq_('@mapbox/corslite'),\r\n\t\tpolyline = _dereq_('@mapbox/polyline'),\r\n\t\tosrmTextInstructions = _dereq_('osrm-text-instructions')('v5');\r\n\r\n\t// Ignore camelcase naming for this file, since OSRM's API uses\r\n\t// underscores.\r\n\t/* jshint camelcase: false */\r\n\r\n\tvar Waypoint = _dereq_('./waypoint');\r\n\r\n\t/**\r\n\t * Works against OSRM's new API in version 5.0; this has\r\n\t * the API version v1.\r\n\t */\r\n\tmodule.exports = L.Class.extend({\r\n\t\toptions: {\r\n\t\t\tserviceUrl: 'https://router.project-osrm.org/route/v1',\r\n\t\t\tprofile: 'driving',\r\n\t\t\ttimeout: 30 * 1000,\r\n\t\t\troutingOptions: {\r\n\t\t\t\talternatives: true,\r\n\t\t\t\tsteps: true\r\n\t\t\t},\r\n\t\t\tpolylinePrecision: 5,\r\n\t\t\tuseHints: true,\r\n\t\t\tsuppressDemoServerWarning: false,\r\n\t\t\tlanguage: 'en'\r\n\t\t},\r\n\r\n\t\tinitialize: function(options) {\r\n\t\t\tL.Util.setOptions(this, options);\r\n\t\t\tthis._hints = {\r\n\t\t\t\tlocations: {}\r\n\t\t\t};\r\n\r\n\t\t\tif (!this.options.suppressDemoServerWarning &&\r\n\t\t\t\tthis.options.serviceUrl.indexOf('//router.project-osrm.org') >= 0) {\r\n\t\t\t\tconsole.warn('You are using OSRM\\'s demo server. ' +\r\n\t\t\t\t\t'Please note that it is **NOT SUITABLE FOR PRODUCTION USE**.\\n' +\r\n\t\t\t\t\t'Refer to the demo server\\'s usage policy: ' +\r\n\t\t\t\t\t'https://github.com/Project-OSRM/osrm-backend/wiki/Api-usage-policy\\n\\n' +\r\n\t\t\t\t\t'To change, set the serviceUrl option.\\n\\n' +\r\n\t\t\t\t\t'Please do not report issues with this server to neither ' +\r\n\t\t\t\t\t'Leaflet Routing Machine or OSRM - it\\'s for\\n' +\r\n\t\t\t\t\t'demo only, and will sometimes not be available, or work in ' +\r\n\t\t\t\t\t'unexpected ways.\\n\\n' +\r\n\t\t\t\t\t'Please set up your own OSRM server, or use a paid service ' +\r\n\t\t\t\t\t'provider for production.');\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\troute: function(waypoints, callback, context, options) {\r\n\t\t\tvar timedOut = false,\r\n\t\t\t\twps = [],\r\n\t\t\t\turl,\r\n\t\t\t\ttimer,\r\n\t\t\t\twp,\r\n\t\t\t\ti,\r\n\t\t\t\txhr;\r\n\r\n\t\t\toptions = L.extend({}, this.options.routingOptions, options);\r\n\t\t\turl = this.buildRouteUrl(waypoints, options);\r\n\t\t\tif (this.options.requestParameters) {\r\n\t\t\t\turl += L.Util.getParamString(this.options.requestParameters, url);\r\n\t\t\t}\r\n\r\n\t\t\ttimer = setTimeout(function() {\r\n\t\t\t\ttimedOut = true;\r\n\t\t\t\tcallback.call(context || callback, {\r\n\t\t\t\t\tstatus: -1,\r\n\t\t\t\t\tmessage: 'OSRM request timed out.'\r\n\t\t\t\t});\r\n\t\t\t}, this.options.timeout);\r\n\r\n\t\t\t// Create a copy of the waypoints, since they\r\n\t\t\t// might otherwise be asynchronously modified while\r\n\t\t\t// the request is being processed.\r\n\t\t\tfor (i = 0; i < waypoints.length; i++) {\r\n\t\t\t\twp = waypoints[i];\r\n\t\t\t\twps.push(new Waypoint(wp.latLng, wp.name, wp.options));\r\n\t\t\t}\r\n\r\n // modified 2022-02-14 by RR for POST\r\n // changing this to use an Ajax POST to avoid URL length issues\r\n // our requests are on the same domain so we don't have to worry about CORS anyways\r\n var that = this;\r\n return $.ajax({\r\n type: 'POST',\r\n url: url.serviceUrl,\r\n data: JSON.stringify(url),\r\n contentType: 'application/json; charset=utf-8',\r\n dataType: 'json',\r\n success: function (msg) {\r\n var error = {};\r\n clearTimeout(timer);\r\n try {\r\n var data = JSON.parse(msg.response);\r\n return that._routeDone(data, wps, options, callback, context);\r\n } catch (ex) {\r\n error.status = -3;\r\n error.message = ex.toString();\r\n }\r\n callback.call(context || callback, error);\r\n },\r\n error: function (xhr, status, error) {\r\n var data = {};\r\n clearTimeout(timer);\r\n var message = error + (xhr && xhr.status ? ' HTTP ' + xhr.status + ': ' + xhr.statusText : '');\r\n if (xhr.responseText) {\r\n try {\r\n data = JSON.parse(xhr.responseText);\r\n if (data.message)\r\n message = data.message;\r\n } catch (ex) {\r\n }\r\n }\r\n var err = {\r\n message: 'HTTP request failed: ' + message,\r\n url: url.serviceUrl,\r\n status: -1,\r\n target: xhr\r\n };\r\n callback.call(context || callback, err);\r\n }\r\n });\r\n\t\t},\r\n\r\n\t\trequiresMoreDetail: function(route, zoom, bounds) {\r\n\t\t\tif (!route.properties.isSimplified) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\r\n\t\t\tvar waypoints = route.inputWaypoints,\r\n\t\t\t\ti;\r\n\t\t\tfor (i = 0; i < waypoints.length; ++i) {\r\n\t\t\t\tif (!bounds.contains(waypoints[i].latLng)) {\r\n\t\t\t\t\treturn true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\treturn false;\r\n\t\t},\r\n\r\n\t\t_routeDone: function(response, inputWaypoints, options, callback, context) {\r\n\t\t\tvar alts = [],\r\n\t\t\t actualWaypoints,\r\n\t\t\t i,\r\n\t\t\t route;\r\n\r\n\t\t\tcontext = context || callback;\r\n\t\t\tif (response.code !== 'Ok') {\r\n\t\t\t\tcallback.call(context, {\r\n\t\t\t\t\tstatus: response.code\r\n\t\t\t\t});\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tactualWaypoints = this._toWaypoints(inputWaypoints, response.waypoints);\r\n\r\n\t\t\tfor (i = 0; i < response.routes.length; i++) {\r\n\t\t\t\troute = this._convertRoute(response.routes[i]);\r\n\t\t\t\troute.inputWaypoints = inputWaypoints;\r\n\t\t\t\troute.waypoints = actualWaypoints;\r\n\t\t\t\troute.properties = {isSimplified: !options || !options.geometryOnly || options.simplifyGeometry};\r\n\t\t\t\talts.push(route);\r\n\t\t\t}\r\n\r\n\t\t\tthis._saveHintData(response.waypoints, inputWaypoints);\r\n\r\n\t\t\tcallback.call(context, null, alts);\r\n\t\t},\r\n\r\n\t\t_convertRoute: function(responseRoute) {\r\n\t\t\tvar result = {\r\n\t\t\t\t\tname: '',\r\n\t\t\t\t\tcoordinates: [],\r\n\t\t\t\t\tinstructions: [],\r\n\t\t\t\t\tsummary: {\r\n\t\t\t\t\t\ttotalDistance: responseRoute.distance,\r\n\t\t\t\t\t\ttotalTime: responseRoute.duration\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\tlegNames = [],\r\n\t\t\t\twaypointIndices = [],\r\n\t\t\t\tindex = 0,\r\n\t\t\t\tlegCount = responseRoute.legs.length,\r\n\t\t\t\thasSteps = responseRoute.legs[0].steps.length > 0,\r\n\t\t\t\ti,\r\n\t\t\t\tj,\r\n\t\t\t\tleg,\r\n\t\t\t\tstep,\r\n\t\t\t\tgeometry,\r\n\t\t\t\ttype,\r\n\t\t\t\tmodifier,\r\n\t\t\t\ttext,\r\n\t\t\t\tstepToText;\r\n\r\n\t\t\tif (this.options.stepToText) {\r\n\t\t\t\tstepToText = this.options.stepToText;\r\n\t\t\t} else {\r\n\t\t\t\tstepToText = L.bind(osrmTextInstructions.compile, osrmTextInstructions, this.options.language);\r\n\t\t\t}\r\n\r\n\t\t\tfor (i = 0; i < legCount; i++) {\r\n\t\t\t\tleg = responseRoute.legs[i];\r\n\t\t\t\tlegNames.push(leg.summary && leg.summary.charAt(0).toUpperCase() + leg.summary.substring(1));\r\n\t\t\t\tfor (j = 0; j < leg.steps.length; j++) {\r\n\t\t\t\t\tstep = leg.steps[j];\r\n\t\t\t\t\tgeometry = this._decodePolyline(step.geometry);\r\n\t\t\t\t\tresult.coordinates.push.apply(result.coordinates, geometry);\r\n\t\t\t\t\ttype = this._maneuverToInstructionType(step.maneuver, i === legCount - 1);\r\n\t\t\t\t\tmodifier = this._maneuverToModifier(step.maneuver);\r\n\t\t\t\t\ttext = stepToText(step, {legCount: legCount, legIndex: i});\r\n\r\n\t\t\t\t\tif (type) {\r\n\t\t\t\t\t\tif ((i == 0 && step.maneuver.type == 'depart') || step.maneuver.type == 'arrive') {\r\n\t\t\t\t\t\t\twaypointIndices.push(index);\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tresult.instructions.push({\r\n\t\t\t\t\t\t\ttype: type,\r\n\t\t\t\t\t\t\tdistance: step.distance,\r\n\t\t\t\t\t\t\ttime: step.duration,\r\n\t\t\t\t\t\t\troad: step.name,\r\n\t\t\t\t\t\t\tdirection: this._bearingToDirection(step.maneuver.bearing_after),\r\n\t\t\t\t\t\t\texit: step.maneuver.exit,\r\n\t\t\t\t\t\t\tindex: index,\r\n\t\t\t\t\t\t\tmode: step.mode,\r\n\t\t\t\t\t\t\tmodifier: modifier,\r\n\t\t\t\t\t\t\ttext: text\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tindex += geometry.length;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tresult.name = legNames.join(', ');\r\n\t\t\tif (!hasSteps) {\r\n\t\t\t\tresult.coordinates = this._decodePolyline(responseRoute.geometry);\r\n\t\t\t} else {\r\n\t\t\t\tresult.waypointIndices = waypointIndices;\r\n\t\t\t}\r\n\r\n\t\t\treturn result;\r\n\t\t},\r\n\r\n\t\t_bearingToDirection: function(bearing) {\r\n\t\t\tvar oct = Math.round(bearing / 45) % 8;\r\n\t\t\treturn ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'][oct];\r\n\t\t},\r\n\r\n\t\t_maneuverToInstructionType: function(maneuver, lastLeg) {\r\n\t\t\tswitch (maneuver.type) {\r\n\t\t\tcase 'new name':\r\n\t\t\t\treturn 'Continue';\r\n\t\t\tcase 'depart':\r\n\t\t\t\treturn 'Head';\r\n\t\t\tcase 'arrive':\r\n\t\t\t\treturn lastLeg ? 'DestinationReached' : 'WaypointReached';\r\n\t\t\tcase 'roundabout':\r\n\t\t\tcase 'rotary':\r\n\t\t\t\treturn 'Roundabout';\r\n\t\t\tcase 'merge':\r\n\t\t\tcase 'fork':\r\n\t\t\tcase 'on ramp':\r\n\t\t\tcase 'off ramp':\r\n\t\t\tcase 'end of road':\r\n\t\t\t\treturn this._camelCase(maneuver.type);\r\n\t\t\t// These are all reduced to the same instruction in the current model\r\n\t\t\t//case 'turn':\r\n\t\t\t//case 'ramp': // deprecated in v5.1\r\n\t\t\tdefault:\r\n\t\t\t\treturn this._camelCase(maneuver.modifier);\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t_maneuverToModifier: function(maneuver) {\r\n\t\t\tvar modifier = maneuver.modifier;\r\n\r\n\t\t\tswitch (maneuver.type) {\r\n\t\t\tcase 'merge':\r\n\t\t\tcase 'fork':\r\n\t\t\tcase 'on ramp':\r\n\t\t\tcase 'off ramp':\r\n\t\t\tcase 'end of road':\r\n\t\t\t\tmodifier = this._leftOrRight(modifier);\r\n\t\t\t}\r\n\r\n\t\t\treturn modifier && this._camelCase(modifier);\r\n\t\t},\r\n\r\n\t\t_camelCase: function(s) {\r\n\t\t\tvar words = s.split(' '),\r\n\t\t\t\tresult = '';\r\n\t\t\tfor (var i = 0, l = words.length; i < l; i++) {\r\n\t\t\t\tresult += words[i].charAt(0).toUpperCase() + words[i].substring(1);\r\n\t\t\t}\r\n\r\n\t\t\treturn result;\r\n\t\t},\r\n\r\n\t\t_leftOrRight: function(d) {\r\n\t\t\treturn d.indexOf('left') >= 0 ? 'Left' : 'Right';\r\n\t\t},\r\n\r\n\t\t_decodePolyline: function(routeGeometry) {\r\n\t\t\tvar cs = polyline.decode(routeGeometry, this.options.polylinePrecision),\r\n\t\t\t\tresult = new Array(cs.length),\r\n\t\t\t\ti;\r\n\t\t\tfor (i = cs.length - 1; i >= 0; i--) {\r\n\t\t\t\tresult[i] = L.latLng(cs[i]);\r\n\t\t\t}\r\n\r\n\t\t\treturn result;\r\n\t\t},\r\n\r\n\t\t_toWaypoints: function(inputWaypoints, vias) {\r\n\t\t\tvar wps = [],\r\n\t\t\t i,\r\n\t\t\t viaLoc;\r\n\t\t\tfor (i = 0; i < vias.length; i++) {\r\n\t\t\t\tviaLoc = vias[i].location;\r\n\t\t\t\twps.push(new Waypoint(L.latLng(viaLoc[1], viaLoc[0]),\r\n\t\t\t\t inputWaypoints[i].name,\r\n\t\t\t\t\t\t\t\t\t\t\tinputWaypoints[i].options));\r\n\t\t\t}\r\n\r\n\t\t\treturn wps;\r\n\t\t},\r\n\r\n\t\tbuildRouteUrl: function(waypoints, options) {\r\n\t\t\tvar locs = [],\r\n\t\t\t\thints = [],\r\n\t\t\t\twp,\r\n\t\t\t\tlatLng,\r\n\t\t\t computeInstructions,\r\n\t\t\t computeAlternative = true;\r\n\r\n\t\t\tfor (var i = 0; i < waypoints.length; i++) {\r\n\t\t\t\twp = waypoints[i];\r\n\t\t\t\tlatLng = wp.latLng;\r\n\t\t\t\tlocs.push(latLng.lng + ',' + latLng.lat);\r\n\t\t\t\thints.push(this._hints.locations[this._locationKey(latLng)] || '');\r\n\t\t\t}\r\n\r\n\t\t\tcomputeInstructions =\r\n\t\t\t\ttrue;\r\n\r\n // modified 2022-02-14 by RR for POST\r\n var parameters = {\r\n overview: (options.geometryOnly ? (options.simplifyGeometry ? '' : 'full') : 'false'),\r\n alternatives: computeAlternative.toString(),\r\n steps: computeInstructions.toString(),\r\n hints: (this.options.useHints ? hints.join(';') : ''),\r\n continue_straight: (options.allowUTurns ? !options.allowUTurns : '')\r\n };\r\n\r\n var parts = {\r\n serviceUrl: this.options.serviceUrl + '/' + this.options.profile + '/',\r\n coordinates: locs.join(';'),\r\n parameters: parameters\r\n };\r\n return parts;\r\n\t\t},\r\n\r\n\t\t_locationKey: function(location) {\r\n\t\t\treturn location.lat + ',' + location.lng;\r\n\t\t},\r\n\r\n\t\t_saveHintData: function(actualWaypoints, waypoints) {\r\n\t\t\tvar loc;\r\n\t\t\tthis._hints = {\r\n\t\t\t\tlocations: {}\r\n\t\t\t};\r\n\t\t\tfor (var i = actualWaypoints.length - 1; i >= 0; i--) {\r\n\t\t\t\tloc = waypoints[i].latLng;\r\n\t\t\t\tthis._hints.locations[this._locationKey(loc)] = actualWaypoints[i].hint;\r\n\t\t\t}\r\n\t\t},\r\n\t});\r\n})();\r\n\r\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\r\n }, { \"./waypoint\": 68, \"@mapbox/corslite\": 1, \"@mapbox/polyline\": 2, \"osrm-text-instructions\": 3 }],\r\n 67: [function (_dereq_, module, exports) {\r\n(function (global){\r\n(function() {\r\n\t'use strict';\r\n\r\n\tvar L = (typeof window !== \"undefined\" ? window['L'] : typeof global !== \"undefined\" ? global['L'] : null);\r\n\tvar GeocoderElement = _dereq_('./geocoder-element');\r\n\tvar Waypoint = _dereq_('./waypoint');\r\n\r\n\tmodule.exports = (L.Layer || L.Class).extend({\r\n\t\tincludes: ((typeof L.Evented !== 'undefined' && L.Evented.prototype) || L.Mixin.Events),\r\n\r\n\t\toptions: {\r\n\t\t\tdragStyles: [\r\n\t\t\t\t{color: 'black', opacity: 0.15, weight: 9},\r\n\t\t\t\t{color: 'white', opacity: 0.8, weight: 6},\r\n\t\t\t\t{color: 'red', opacity: 1, weight: 2, dashArray: '7,12'}\r\n\t\t\t],\r\n\t\t\tdraggableWaypoints: true,\r\n\t\t\trouteWhileDragging: false,\r\n\t\t\taddWaypoints: true,\r\n\t\t\treverseWaypoints: false,\r\n\t\t\taddButtonClassName: '',\r\n\t\t\tlanguage: 'en',\r\n\t\t\tcreateGeocoderElement: function(wp, i, nWps, plan) {\r\n\t\t\t\treturn new GeocoderElement(wp, i, nWps, plan);\r\n\t\t\t},\r\n\t\t\tcreateMarker: function(i, wp) {\r\n\t\t\t\tvar options = {\r\n\t\t\t\t\t\tdraggable: this.draggableWaypoints\r\n\t\t\t\t\t},\r\n\t\t\t\t marker = L.marker(wp.latLng, options);\r\n\r\n\t\t\t\treturn marker;\r\n\t\t\t},\r\n\t\t\tgeocodersClassName: ''\r\n\t\t},\r\n\r\n\t\tinitialize: function(waypoints, options) {\r\n\t\t\tL.Util.setOptions(this, options);\r\n\t\t\tthis._waypoints = [];\r\n\t\t\tthis.setWaypoints(waypoints);\r\n\t\t},\r\n\r\n\t\tisReady: function() {\r\n\t\t\tvar i;\r\n\t\t\tfor (i = 0; i < this._waypoints.length; i++) {\r\n\t\t\t\tif (!this._waypoints[i].latLng) {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\treturn true;\r\n\t\t},\r\n\r\n\t\tgetWaypoints: function() {\r\n\t\t\tvar i,\r\n\t\t\t\twps = [];\r\n\r\n\t\t\tfor (i = 0; i < this._waypoints.length; i++) {\r\n\t\t\t\twps.push(this._waypoints[i]);\r\n\t\t\t}\r\n\r\n\t\t\treturn wps;\r\n\t\t},\r\n\r\n\t\tsetWaypoints: function(waypoints) {\r\n\t\t\tvar args = [0, this._waypoints.length].concat(waypoints);\r\n\t\t\tthis.spliceWaypoints.apply(this, args);\r\n\t\t\treturn this;\r\n\t\t},\r\n\r\n\t\tspliceWaypoints: function() {\r\n\t\t\tvar args = [arguments[0], arguments[1]],\r\n\t\t\t i;\r\n\r\n\t\t\tfor (i = 2; i < arguments.length; i++) {\r\n\t\t\t\targs.push(arguments[i] && arguments[i].hasOwnProperty('latLng') ? arguments[i] : new Waypoint(arguments[i]));\r\n\t\t\t}\r\n\r\n\t\t\t[].splice.apply(this._waypoints, args);\r\n\r\n\t\t\t// Make sure there's always at least two waypoints\r\n\t\t\twhile (this._waypoints.length < 2) {\r\n\t\t\t\tthis.spliceWaypoints(this._waypoints.length, 0, null);\r\n\t\t\t}\r\n\r\n\t\t\tthis._updateMarkers();\r\n\t\t\tthis._fireChanged.apply(this, args);\r\n\t\t},\r\n\r\n\t\tonAdd: function(map) {\r\n\t\t\tthis._map = map;\r\n\t\t\tthis._updateMarkers();\r\n\t\t},\r\n\r\n\t\tonRemove: function() {\r\n\t\t\tvar i;\r\n\t\t\tthis._removeMarkers();\r\n\r\n\t\t\tif (this._newWp) {\r\n\t\t\t\tfor (i = 0; i < this._newWp.lines.length; i++) {\r\n\t\t\t\t\tthis._map.removeLayer(this._newWp.lines[i]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tdelete this._map;\r\n\t\t},\r\n\r\n\t\tcreateGeocoders: function() {\r\n\t\t\tvar container = L.DomUtil.create('div', 'leaflet-routing-geocoders ' + this.options.geocodersClassName),\r\n\t\t\t\twaypoints = this._waypoints,\r\n\t\t\t addWpBtn,\r\n\t\t\t reverseBtn;\r\n\r\n\t\t\tthis._geocoderContainer = container;\r\n\t\t\tthis._geocoderElems = [];\r\n\r\n\r\n\t\t\tif (this.options.addWaypoints) {\r\n\t\t\t\taddWpBtn = L.DomUtil.create('button', 'leaflet-routing-add-waypoint ' + this.options.addButtonClassName, container);\r\n\t\t\t\taddWpBtn.setAttribute('type', 'button');\r\n\t\t\t\tL.DomEvent.addListener(addWpBtn, 'click', function() {\r\n\t\t\t\t\tthis.spliceWaypoints(waypoints.length, 0, null);\r\n\t\t\t\t}, this);\r\n\t\t\t}\r\n\r\n\t\t\tif (this.options.reverseWaypoints) {\r\n\t\t\t\treverseBtn = L.DomUtil.create('button', 'leaflet-routing-reverse-waypoints', container);\r\n\t\t\t\treverseBtn.setAttribute('type', 'button');\r\n\t\t\t\tL.DomEvent.addListener(reverseBtn, 'click', function() {\r\n\t\t\t\t\tthis._waypoints.reverse();\r\n\t\t\t\t\tthis.setWaypoints(this._waypoints);\r\n\t\t\t\t}, this);\r\n\t\t\t}\r\n\r\n\t\t\tthis._updateGeocoders();\r\n\t\t\tthis.on('waypointsspliced', this._updateGeocoders);\r\n\r\n\t\t\treturn container;\r\n\t\t},\r\n\r\n\t\t_createGeocoder: function(i) {\r\n\t\t\tvar geocoder = this.options.createGeocoderElement(this._waypoints[i], i, this._waypoints.length, this.options);\r\n\t\t\tgeocoder\r\n\t\t\t.on('delete', function() {\r\n\t\t\t\tif (i > 0 || this._waypoints.length > 2) {\r\n\t\t\t\t\tthis.spliceWaypoints(i, 1);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.spliceWaypoints(i, 1, new Waypoint());\r\n\t\t\t\t}\r\n\t\t\t}, this)\r\n\t\t\t.on('geocoded', function(e) {\r\n\t\t\t\tthis._updateMarkers();\r\n\t\t\t\tthis._fireChanged();\r\n\t\t\t\tthis._focusGeocoder(i + 1);\r\n\t\t\t\tthis.fire('waypointgeocoded', {\r\n\t\t\t\t\twaypointIndex: i,\r\n\t\t\t\t\twaypoint: e.waypoint\r\n\t\t\t\t});\r\n\t\t\t}, this)\r\n\t\t\t.on('reversegeocoded', function(e) {\r\n\t\t\t\tthis.fire('waypointgeocoded', {\r\n\t\t\t\t\twaypointIndex: i,\r\n\t\t\t\t\twaypoint: e.waypoint\r\n\t\t\t\t});\r\n\t\t\t}, this);\r\n\r\n\t\t\treturn geocoder;\r\n\t\t},\r\n\r\n\t\t_updateGeocoders: function() {\r\n\t\t\tvar elems = [],\r\n\t\t\t\ti,\r\n\t\t\t geocoderElem;\r\n\r\n\t\t\tfor (i = 0; i < this._geocoderElems.length; i++) {\r\n\t\t\t\tthis._geocoderContainer.removeChild(this._geocoderElems[i].getContainer());\r\n\t\t\t}\r\n\r\n\t\t\tfor (i = this._waypoints.length - 1; i >= 0; i--) {\r\n\t\t\t\tgeocoderElem = this._createGeocoder(i);\r\n\t\t\t\tthis._geocoderContainer.insertBefore(geocoderElem.getContainer(), this._geocoderContainer.firstChild);\r\n\t\t\t\telems.push(geocoderElem);\r\n\t\t\t}\r\n\r\n\t\t\tthis._geocoderElems = elems.reverse();\r\n\t\t},\r\n\r\n\t\t_removeMarkers: function() {\r\n\t\t\tvar i;\r\n\t\t\tif (this._markers) {\r\n\t\t\t\tfor (i = 0; i < this._markers.length; i++) {\r\n\t\t\t\t\tif (this._markers[i]) {\r\n\t\t\t\t\t\tthis._map.removeLayer(this._markers[i]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tthis._markers = [];\r\n\t\t},\r\n\r\n\t\t_updateMarkers: function() {\r\n\t\t\tvar i,\r\n\t\t\t m;\r\n\r\n\t\t\tif (!this._map) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tthis._removeMarkers();\r\n\r\n\t\t\tfor (i = 0; i < this._waypoints.length; i++) {\r\n\t\t\t\tif (this._waypoints[i].latLng) {\r\n\t\t\t\t\tm = this.options.createMarker(i, this._waypoints[i], this._waypoints.length);\r\n\t\t\t\t\tif (m) {\r\n\t\t\t\t\t\tm.addTo(this._map);\r\n\t\t\t\t\t\tif (this.options.draggableWaypoints) {\r\n\t\t\t\t\t\t\tthis._hookWaypointEvents(m, i);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tm = null;\r\n\t\t\t\t}\r\n\t\t\t\tthis._markers.push(m);\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t_fireChanged: function() {\r\n\t\t\tthis.fire('waypointschanged', {waypoints: this.getWaypoints()});\r\n\r\n\t\t\tif (arguments.length >= 2) {\r\n\t\t\t\tthis.fire('waypointsspliced', {\r\n\t\t\t\t\tindex: Array.prototype.shift.call(arguments),\r\n\t\t\t\t\tnRemoved: Array.prototype.shift.call(arguments),\r\n\t\t\t\t\tadded: arguments\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t_hookWaypointEvents: function(m, i, trackMouseMove) {\r\n\t\t\tvar eventLatLng = function(e) {\r\n\t\t\t\t\treturn trackMouseMove ? e.latlng : e.target.getLatLng();\r\n\t\t\t\t},\r\n\t\t\t\tdragStart = L.bind(function(e) {\r\n\t\t\t\t\tthis.fire('waypointdragstart', {index: i, latlng: eventLatLng(e)});\r\n\t\t\t\t}, this),\r\n\t\t\t\tdrag = L.bind(function(e) {\r\n\t\t\t\t\tthis._waypoints[i].latLng = eventLatLng(e);\r\n\t\t\t\t\tthis.fire('waypointdrag', {index: i, latlng: eventLatLng(e)});\r\n\t\t\t\t}, this),\r\n\t\t\t\tdragEnd = L.bind(function(e) {\r\n\t\t\t\t\tthis._waypoints[i].latLng = eventLatLng(e);\r\n\t\t\t\t\tthis._waypoints[i].name = '';\r\n\t\t\t\t\tif (this._geocoderElems) {\r\n\t\t\t\t\t\tthis._geocoderElems[i].update(true);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis.fire('waypointdragend', {index: i, latlng: eventLatLng(e)});\r\n\t\t\t\t\tthis._fireChanged();\r\n\t\t\t\t}, this),\r\n\t\t\t\tmouseMove,\r\n\t\t\t\tmouseUp;\r\n\r\n\t\t\tif (trackMouseMove) {\r\n\t\t\t\tmouseMove = L.bind(function(e) {\r\n\t\t\t\t\tthis._markers[i].setLatLng(e.latlng);\r\n\t\t\t\t\tdrag(e);\r\n\t\t\t\t}, this);\r\n\t\t\t\tmouseUp = L.bind(function(e) {\r\n\t\t\t\t\tthis._map.dragging.enable();\r\n\t\t\t\t\tthis._map.off('mouseup', mouseUp);\r\n\t\t\t\t\tthis._map.off('mousemove', mouseMove);\r\n\t\t\t\t\tdragEnd(e);\r\n\t\t\t\t}, this);\r\n\t\t\t\tthis._map.dragging.disable();\r\n\t\t\t\tthis._map.on('mousemove', mouseMove);\r\n\t\t\t\tthis._map.on('mouseup', mouseUp);\r\n\t\t\t\tdragStart({latlng: this._waypoints[i].latLng});\r\n\t\t\t} else {\r\n\t\t\t\tm.on('dragstart', dragStart);\r\n\t\t\t\tm.on('drag', drag);\r\n\t\t\t\tm.on('dragend', dragEnd);\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tdragNewWaypoint: function(e) {\r\n\t\t\tvar newWpIndex = e.afterIndex + 1;\r\n\t\t\tif (this.options.routeWhileDragging) {\r\n\t\t\t\tthis.spliceWaypoints(newWpIndex, 0, e.latlng);\r\n\t\t\t\tthis._hookWaypointEvents(this._markers[newWpIndex], newWpIndex, true);\r\n\t\t\t} else {\r\n\t\t\t\tthis._dragNewWaypoint(newWpIndex, e.latlng);\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t_dragNewWaypoint: function(newWpIndex, initialLatLng) {\r\n\t\t\tvar wp = new Waypoint(initialLatLng),\r\n\t\t\t\tprevWp = this._waypoints[newWpIndex - 1],\r\n\t\t\t\tnextWp = this._waypoints[newWpIndex],\r\n\t\t\t\tmarker = this.options.createMarker(newWpIndex, wp, this._waypoints.length + 1),\r\n\t\t\t\tlines = [],\r\n\t\t\t\tdraggingEnabled = this._map.dragging.enabled(),\r\n\t\t\t\tmouseMove = L.bind(function(e) {\r\n\t\t\t\t\tvar i,\r\n\t\t\t\t\t\tlatLngs;\r\n\t\t\t\t\tif (marker) {\r\n\t\t\t\t\t\tmarker.setLatLng(e.latlng);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tfor (i = 0; i < lines.length; i++) {\r\n\t\t\t\t\t\tlatLngs = lines[i].getLatLngs();\r\n\t\t\t\t\t\tlatLngs.splice(1, 1, e.latlng);\r\n\t\t\t\t\t\tlines[i].setLatLngs(latLngs);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tL.DomEvent.stop(e);\r\n\t\t\t\t}, this),\r\n\t\t\t\tmouseUp = L.bind(function(e) {\r\n\t\t\t\t\tvar i;\r\n\t\t\t\t\tif (marker) {\r\n\t\t\t\t\t\tthis._map.removeLayer(marker);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tfor (i = 0; i < lines.length; i++) {\r\n\t\t\t\t\t\tthis._map.removeLayer(lines[i]);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis._map.off('mousemove', mouseMove);\r\n\t\t\t\t\tthis._map.off('mouseup', mouseUp);\r\n\t\t\t\t\tthis.spliceWaypoints(newWpIndex, 0, e.latlng);\r\n\t\t\t\t\tif (draggingEnabled) {\r\n\t\t\t\t\t\tthis._map.dragging.enable();\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tL.DomEvent.stop(e);\r\n\t\t\t\t}, this),\r\n\t\t\t\ti;\r\n\r\n\t\t\tif (marker) {\r\n\t\t\t\tmarker.addTo(this._map);\r\n\t\t\t}\r\n\r\n\t\t\tfor (i = 0; i < this.options.dragStyles.length; i++) {\r\n\t\t\t\tlines.push(L.polyline([prevWp.latLng, initialLatLng, nextWp.latLng],\r\n\t\t\t\t\tthis.options.dragStyles[i]).addTo(this._map));\r\n\t\t\t}\r\n\r\n\t\t\tif (draggingEnabled) {\r\n\t\t\t\tthis._map.dragging.disable();\r\n\t\t\t}\r\n\r\n\t\t\tthis._map.on('mousemove', mouseMove);\r\n\t\t\tthis._map.on('mouseup', mouseUp);\r\n\t\t},\r\n\r\n\t\t_focusGeocoder: function(i) {\r\n\t\t\tif (this._geocoderElems[i]) {\r\n\t\t\t\tthis._geocoderElems[i].focus();\r\n\t\t\t} else {\r\n\t\t\t\tdocument.activeElement.blur();\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n})();\r\n\r\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\r\n }, { \"./geocoder-element\": 59, \"./waypoint\": 68 }],\r\n 68: [function (_dereq_, module, exports) {\r\n(function (global){\r\n(function() {\r\n\t'use strict';\r\n\r\n\tvar L = (typeof window !== \"undefined\" ? window['L'] : typeof global !== \"undefined\" ? global['L'] : null);\r\n\r\n\tmodule.exports = L.Class.extend({\r\n\t\toptions: {\r\n\t\t\tallowUTurn: false,\r\n\t\t},\r\n\t\tinitialize: function(latLng, name, options) {\r\n\t\t\tL.Util.setOptions(this, options);\r\n\t\t\tthis.latLng = L.latLng(latLng);\r\n\t\t\tthis.name = name;\r\n\t\t}\r\n\t});\r\n})();\r\n\r\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\r\n},{}]},{},[60]);\r\n","import {\r\n\t// isWaypointEnabledForAsset,\r\n\t// isMessagingEnabledForAssetGroup,\r\n\t// isMessagingEnabledForAsset,\r\n\t// isOutputEnabledForAsset,\r\n\t// isServiceMeterEnabledForAsset,\r\n\tgetCurrentMarkerForAsset,\r\n} from \"./assets.js\";\r\nimport { toggleLoadingMessage } from \"./ajax.js\";\r\nimport { findAssetById } from \"./assets.js\";\r\nimport { language } from \"./const.js\";\r\nimport { map } from \"./map-base.js\";\r\nimport preferences from \"./preferences.js\";\r\nimport strings from \"./strings.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport { handleWebServiceError } from \"./ajax.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\nimport { getOSRMLanguage } from \"./routing.js\";\r\n\r\nimport L from \"leaflet\";\r\nimport $ from \"jquery\";\r\n\r\n//// Also possible: import L_Routing from \"leaflet-routing-machine\"\r\n//import \"leaflet-routing-machine/src/index.js\";\r\nimport \"../legacy/leaflet-routing-machine-all.js\"; // Customized LRM\r\n\r\nconst waypointMarkers = [];\r\nexport { waypointMarkers };\r\n\r\nexport function waypointGetRoute(waypoint) {\r\n\tif (waypoint == null) {\r\n\t\treturn;\r\n\t}\r\n\tvar asset = findAssetById(waypoint.AssetId);\r\n\r\n\tif (asset == null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar assetMarker = getCurrentMarkerForAsset(asset);\r\n\tif (assetMarker === undefined) {\r\n\t\treturn;\r\n\t}\r\n\tvar waypointMarker = null;\r\n\tfor (var i = 0; i < waypointMarkers.length; i++) {\r\n\t\tvar wpId = waypointMarkers[i].data.waypointId;\r\n\t\tif (waypoint.Id == wpId) {\r\n\t\t\twaypointMarker = waypointMarkers[i];\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\tvar assetLocation = assetMarker.data.location;\r\n\tvar waypointLocation = waypointMarker.data.location;\r\n\tvar route = {\r\n\t\torigin: L.latLng(assetLocation.Lat, assetLocation.Lng),\r\n\t\tdestination: L.latLng(waypointLocation.Lat, waypointLocation.Lng),\r\n\t};\r\n\r\n\twaypointClearRoute(waypoint);\r\n\r\n\t// new routing service just for this waypoint and asset\r\n\tvar routingService = L.Routing.control({\r\n\t\trouter: new L.Routing.OSRMv1({\r\n\t\t\tserviceUrl: \"/api/routing/route\",\r\n\t\t\tlanguage: getOSRMLanguage(language),\r\n\t\t}),\r\n\t\tformatter: new L.Routing.Formatter({\r\n\t\t\tlanguage: getOSRMLanguage(language),\r\n\t\t\tunits: preferences.PREFERENCE_SPEED === 1 ? \"metric\" : \"imperial\",\r\n\t\t}),\r\n\t\tuseZoomParameter: false,\r\n\t})\r\n\t\t.on(\"routingerror\", function (e) {\r\n\t\t\t$(\"#WaypointRoute\").show().text(strings.WAYPOINT_ROUTE_ERROR).addClass(\"error\");\r\n\t\t})\r\n\t\t.on(\"routeselected\", function (e) {\r\n\t\t\twaypoint.route = e.route;\r\n\t\t\t$(\"#WaypointRoute\").show().removeClass(\"error\");\r\n\t\t\t// show clear route button\r\n\t\t\t$(\"#WaypointRemoveRoute\").show();\r\n\t\t\t$(\"#WaypointGetRoute\").hide();\r\n\t\t});\r\n\troutingService.control = routingService.onAdd(map);\r\n\troutingService.setWaypoints([route.origin, route.destination]);\r\n\t$(\"#WaypointRoutePanel\").append(routingService.control);\r\n\twaypoint.routingService = routingService;\r\n}\r\n\r\nexport function waypointClearRoute(waypoint) {\r\n\tif (waypoint == null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tif (waypoint.route != null) {\r\n\t\twaypoint.route = null;\r\n\t\tif (waypoint.routingService != null) {\r\n\t\t\twaypoint.routingService.remove();\r\n\t\t}\r\n\t}\r\n\t$(\"#WaypointRemoveRoute\").hide();\r\n\t$(\"#WaypointGetRoute\").show();\r\n}\r\n\r\nexport function waypointMarkComplete(waypoint) {\r\n\tif (waypoint == null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar asset = findAssetById(waypoint.AssetId);\r\n\tif (asset == null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar button = document.getElementById(\"WaypointMarkComplete\");\r\n\r\n\tvar key = \"waypoint-complete\";\r\n\ttoggleLoadingMessage(true, key);\r\n\tif (button !== null) {\r\n\t\tbutton.disabled = true;\r\n\t}\r\n\tvar dataPost = {\r\n\t\tassetId: asset.Id,\r\n\t\tid: waypoint.Id,\r\n\t};\r\n\t$.ajax({\r\n\t\ttype: \"POST\",\r\n\t\turl: wrapUrl(\"/services/GPSService.asmx/WaypointMarkComplete\"),\r\n\t\tdata: JSON.stringify(dataPost),\r\n\t\tcontentType: \"application/json; charset=utf-8\",\r\n\t\tdataType: \"json\",\r\n\t\tsuccess: function (msg) {\r\n\t\t\tvar result = msg.d;\r\n\t\t\tif (result) {\r\n\t\t\t\tif (result.Success != true) {\r\n\t\t\t\t\thandleWebServiceError(strings.WAYPOINT_COMPLETE_ERROR);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\ttoggleLoadingMessage(false, key);\r\n\t\t\t$(domNodes.infoDialogs.mapItemInformation).dialog(\"close\");\r\n\t\t\tif (button !== null) {\r\n\t\t\t\tbutton.disabled = false;\r\n\t\t\t}\r\n\t\t},\r\n\t\terror: function (xhr, status, error) {\r\n\t\t\thandleWebServiceError(strings.WAYPOINT_COMPLETE_ERROR);\r\n\t\t\ttoggleLoadingMessage(false, key);\r\n\t\t\tif (button !== null) {\r\n\t\t\t\tbutton.disabled = false;\r\n\t\t\t}\r\n\t\t},\r\n\t});\r\n}\r\n","import trkData from \"./data.js\";\r\nimport strings from \"./strings.js\";\r\nimport { getCurrentMarkerForAsset } from \"./assets.js\";\r\nimport user from \"./user.js\";\r\nimport { devices } from \"./devices.js\";\r\nimport { findAssetById } from \"./assets.js\";\r\nimport { extendBounds } from \"./map-bounds.js\";\r\nimport state from \"./state.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\nimport { toggleLoadingMessage } from \"./ajax.js\";\r\nimport { handleWebServiceError } from \"./ajax.js\";\r\nimport { addItemToMap, removeItemFromMap } from \"./map-items.js\";\r\nimport AssetSprite from \"./gleo-asset-sprite.js\";\r\nimport { markerClick, markerUnhover, markerHover } from \"./marker-click.js\";\r\nimport { waypointMarkers } from \"./routing-waypoint.js\";\r\nimport { isItemIncluded } from \"./polyfills.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport $j from \"jquery\";\r\nimport L from \"leaflet\";\r\nimport _ from \"lodash\";\r\nimport { el, svg, text, mount, setChildren } from \"redom\"; // https://redom.js.org/\r\n\r\nexport function findWaypointById(id) {\r\n\tvar item = _.find(trkData.waypoints, { Id: parseInt(id) });\r\n\treturn item === undefined ? null : item;\r\n}\r\n\r\nexport function findWaypointByAsset(asset) {\r\n\tvar item = _.find(trkData.waypoints, { AssetId: asset.Id });\r\n\treturn item === undefined ? null : item;\r\n}\r\n\r\nexport function populateWaypointInformation(waypoint, asset) {\r\n\tvar info = [];\r\n\tinfo.push(el(\"div#WaypointRequestETASuccess.success\", { style: { display: \"none\" } }, strings.WAYPOINT_REQUESTED));\r\n\tinfo.push(el(\"div.form-group\", [el(\"label\", strings.ASSET), el(\"div.item\", asset.Name)]));\r\n\tif (waypoint.Message != null && waypoint.Message !== \"\") {\r\n\t\tinfo.push(el(\"div.form-group\", [el(\"label\", strings.MESSAGE), el(\"div.item\", waypoint.Message)]));\r\n\t}\r\n\tinfo.push(\r\n\t\tel(\r\n\t\t\t\"div#WaypointDistanceTo\",\r\n\t\t\t{ style: { display: waypoint.DistanceTo != null ? \"\" : \"none\" }, dataset: { waypointId: waypoint.Id } },\r\n\t\t\tel(\"div.form-group\", [el(\"label\", strings.DISTANCE_TO), el(\"div.item\", waypoint.DistanceTo)])\r\n\t\t)\r\n\t);\r\n\tinfo.push(\r\n\t\tel(\r\n\t\t\t\"div#WaypointETA\",\r\n\t\t\t{ style: { display: waypoint.ETA != null ? \"\" : \"none\" }, dataset: { waypointId: waypoint.Id } },\r\n\t\t\tel(\"div.form-group\", [el(\"label\", strings.ETA), el(\"div.item\", waypoint.ETA)])\r\n\t\t)\r\n\t);\r\n\tvar isRemoveVisible = waypoint.route != null;\r\n\tvar getRouteClass = \"\";\r\n\tif (!isRemoveVisible && !canAssetBeRouted(asset)) {\r\n\t\tgetRouteClass = \".disabled\";\r\n\t}\r\n\r\n\tvar buttonItems = [\r\n\t\tel(\r\n\t\t\t\"button#WaypointGetRoute.btn.btn-primary.mr-1\" + getRouteClass,\r\n\t\t\t{ style: { display: isRemoveVisible ? \"none\" : \"\" }, dataset: { waypointId: waypoint.Id } },\r\n\t\t\t[\r\n\t\t\t\tsvg(\"svg\", svg(\"use\", { xlink: { href: \"/content/svg/tracking.svg?v=15#compass\" } })),\r\n\t\t\t\ttext(\" \"),\r\n\t\t\t\tel(\"span\", strings.GET_ROUTE),\r\n\t\t\t]\r\n\t\t),\r\n\t\tel(\r\n\t\t\t\"button#WaypointRemoveRoute.btn.btn-secondary.mr-1\",\r\n\t\t\t{ style: { display: isRemoveVisible ? \"\" : \"none\" }, dataset: { waypointId: waypoint.Id } },\r\n\t\t\t[\r\n\t\t\t\tsvg(\"svg\", svg(\"use\", { xlink: { href: \"/content/svg/tracking.svg?v=15#times\" } })),\r\n\t\t\t\ttext(\" \"),\r\n\t\t\t\tel(\"span\", strings.CLEAR_ROUTE),\r\n\t\t\t]\r\n\t\t),\r\n\t];\r\n\r\n\t// asset have ETA ability? - not for shared users\r\n\tif (!user.isAnonymous) {\r\n\t\tif (\r\n\t\t\t!waypoint.IsServerSide &&\r\n\t\t\t($.inArray(asset.DeviceId, devices.SKYWAVE_IDP_DUAL_MODE) != -1 ||\r\n\t\t\t\t$.inArray(asset.DeviceId, devices.SKYWAVE_IDP) != -1)\r\n\t\t) {\r\n\t\t\tbuttonItems.push(\r\n\t\t\t\tel(\"button#WaypointRequestETAUpdate.btn.btn-secondary\", { dataset: { waypointId: waypoint.Id } }, [\r\n\t\t\t\t\tsvg(\"svg\", svg(\"use\", { xlink: { href: \"/content/svg/tracking.svg?v=15#clock-solid\" } })),\r\n\t\t\t\t\ttext(\" \"),\r\n\t\t\t\t\tel(\"span\", strings.REQUEST_ETA),\r\n\t\t\t\t])\r\n\t\t\t);\r\n\t\t} else if (waypoint.IsServerSide) {\r\n\t\t\tbuttonItems.push(\r\n\t\t\t\tel(\"button#WaypointMarkComplete.btn.btn-secondary\", { dataset: { waypointId: waypoint.Id } }, [\r\n\t\t\t\t\tsvg(\"svg\", svg(\"use\", { xlink: { href: \"/content/svg/tracking.svg?v=15#check\" } })),\r\n\t\t\t\t\ttext(\" \"),\r\n\t\t\t\t\tel(\"span\", strings.MARK_COMPLETE),\r\n\t\t\t\t])\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n\tinfo.push(el(\"div.form-group\", buttonItems));\r\n\tinfo.push(\r\n\t\tel(\r\n\t\t\t\"div#WaypointRoute\",\r\n\t\t\t{ style: { display: isRemoveVisible ? \"\" : \"none\" }, dataset: { waypointId: waypoint.Id } },\r\n\t\t\tel(\"div#WaypointRoutePanel\")\r\n\t\t)\r\n\t);\r\n\treturn el(\"div#waypoint-information\", info);\r\n}\r\n\r\nexport function canAssetBeRouted(asset) {\r\n\tvar assetMarker = getCurrentMarkerForAsset(asset);\r\n\tif (assetMarker === undefined) {\r\n\t\treturn false;\r\n\t}\r\n\treturn true;\r\n}\r\n\r\nexport function updateWaypoint(waypointId) {\r\n\t// query waypoint Id\r\n\ttoggleLoadingMessage(true, \"waypoint-update\");\r\n\tvar data = { id: waypointId };\r\n\t$j.ajax({\r\n\t\ttype: \"POST\",\r\n\t\turl: wrapUrl(\"/services/GPSService.asmx/GetWaypointById\"),\r\n\t\tdata: JSON.stringify(data),\r\n\t\tcontentType: \"application/json; charset=utf-8\",\r\n\t\tdataType: \"json\",\r\n\t\tsuccess: function (msg) {\r\n\t\t\tvar result = msg.d;\r\n\t\t\tif (result) {\r\n\t\t\t\tif (result.Success == true) {\r\n\t\t\t\t\taddOrUpdateWaypoint(result.Waypoint);\r\n\t\t\t\t\tupdateWaypointListing();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\ttoggleLoadingMessage(false, \"waypoint-update\");\r\n\t\t},\r\n\t\terror: function (xhr, status, error) {\r\n\t\t\thandleWebServiceError(strings.MSG_LOAD_WAYPOINT_ERROR);\r\n\t\t\ttoggleLoadingMessage(false, \"waypoint-update\");\r\n\t\t},\r\n\t});\r\n}\r\n\r\nexport function addOrUpdateWaypoint(updatedWaypoint) {\r\n\tvar index = null;\r\n\tvar existingWaypoint = null;\r\n\tfor (var i = 0; i < trkData.waypoints.length; i++) {\r\n\t\tif (trkData.waypoints[i].Id == updatedWaypoint.Id) {\r\n\t\t\tindex = i;\r\n\t\t\texistingWaypoint = trkData.waypoints[i];\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\tif (existingWaypoint !== null) {\r\n\t\tif (existingWaypoint.Status === \"Active\") {\r\n\t\t\tif (updatedWaypoint.Status !== \"Active\") {\r\n\t\t\t\t// remove it from the list of waypoints we're tracking\r\n\t\t\t\ttrkData.waypoints.splice(index, 1);\r\n\r\n\t\t\t\t// remove marker from map\r\n\t\t\t\tif (waypointMarkers != null) {\r\n\t\t\t\t\tfor (var i = 0; i < waypointMarkers.length; i++) {\r\n\t\t\t\t\t\t// var waypointId = $j.data(waypointMarkers[i], 'waypointId');\r\n\t\t\t\t\t\tvar waypointId = waypointMarkers[i].data.waypointId;\r\n\t\t\t\t\t\tif (waypointId === updatedWaypoint.Id) {\r\n\t\t\t\t\t\t\tremoveItemFromMap(waypointMarkers[i]);\r\n\t\t\t\t\t\t\twaypointMarkers.splice(i, 1);\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// clear route\r\n\t\t\t\tif (existingWaypoint.route != null) {\r\n\t\t\t\t\texistingWaypoint.routingService.remove();\r\n\t\t\t\t\texistingWaypoint.route = null;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (state.openWindow !== null) {\r\n\t\t\t\t\tif ($j(state.openWindow).data(\"waypointId\") === updatedWaypoint.Id) {\r\n\t\t\t\t\t\tstate.openWindow.remove();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\t// replace with updated waypoint, update WP information, if shown\r\n\t\t\t\tvar route = existingWaypoint.route;\r\n\t\t\t\tvar routingService = existingWaypoint.routingService;\r\n\t\t\t\texistingWaypoint = updatedWaypoint;\r\n\t\t\t\texistingWaypoint.routingService = routingService;\r\n\t\t\t\texistingWaypoint.route = route;\r\n\t\t\t}\r\n\t\t}\r\n\t} else {\r\n\t\t// brand new waypoint, only bother with it if it's active\r\n\t\tif (updatedWaypoint.Status !== \"Active\") {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\ttrkData.waypoints.push(updatedWaypoint);\r\n\t\t// add waypoint to map\r\n\t\taddWaypointMarker([updatedWaypoint.Location.Lat, updatedWaypoint.Location.Lng], updatedWaypoint.Location, updatedWaypoint);\r\n\t}\r\n\r\n\tif (updatedWaypoint.DistanceTo !== null) {\r\n\t\t$j(\"#WaypointDistanceTo[data-waypoint-id=\" + updatedWaypoint.Id + \"]\")\r\n\t\t\t.show()\r\n\t\t\t.find(\"div.item\")\r\n\t\t\t.text(updatedWaypoint.DistanceTo);\r\n\t}\r\n\tif (updatedWaypoint.ETA !== null) {\r\n\t\t$j(\"#WaypointETA[data-waypoint-id=\" + updatedWaypoint.Id + \"]\")\r\n\t\t\t.show()\r\n\t\t\t.find(\"div.item\")\r\n\t\t\t.text(updatedWaypoint.ETA);\r\n\t}\r\n}\r\n\r\nexport function addWaypointMarker(latlng, location, waypoint) {\r\n\tif (latlng == null) {\r\n\t\treturn null;\r\n\t}\r\n\textendBounds(latlng);\r\n\r\n\tvar asset = findAssetById(waypoint.AssetId);\r\n\tvar isCurrentlyActive = !isItemIncluded(user.displayPreferences.hiddenAssets, asset.Id);\r\n\r\n\tconst marker = new AssetSprite(latlng, \"Waypoint\", asset.Color);\r\n\tmarker.data = {\r\n\t\tlocation: null,\r\n\t\twaypointId: null,\r\n\t};\r\n\tif (isCurrentlyActive) {\r\n\t\taddItemToMap(marker);\r\n\t}\r\n\tlocation.marker = marker;\r\n\tif (location != null) {\r\n\t\tmarker.data.location = location;\r\n\t}\r\n\tif (waypoint != null) {\r\n\t\tmarker.data.waypointId = waypoint.Id;\r\n\t}\r\n\r\n\tmarker.on(\"click\", function () {\r\n\t\tmarkerClick(marker, \"waypoint\", null, true);\r\n\t});\r\n\tmarker.on(\"mouseover\", function () {\r\n\t\tmarkerHover(marker);\r\n\t});\r\n\tmarker.on(\"mouseout\", function () {\r\n\t\tmarkerUnhover(marker);\r\n\t});\r\n\twaypointMarkers.push(marker);\r\n\treturn marker;\r\n}\r\n\r\nexport function updateWaypointListing() {\r\n\t_.each(domNodes.assets, function (assetNodes, index, list) {\r\n\t\t_.each(assetNodes, function (assetNode, nodeIndex, nodeList) {\r\n\t\t\tvar waypointIndicators = assetNode.querySelectorAll(\".waypoint\");\r\n\t\t\t_.each(waypointIndicators, function (waypointIndicator) {\r\n\t\t\t\tif (waypointIndicator !== null) {\r\n\t\t\t\t\twaypointIndicator.parentNode.removeChild(waypointIndicator);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t});\r\n\t});\r\n\tconsole.log(trkData.waypoints.length);\r\n\t_.each(trkData.waypoints, function (waypoint, index, list) {\r\n\t\tvar asset = findAssetById(waypoint.AssetId);\r\n\r\n\t\tvar assetWaypoint = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\r\n\t\tassetWaypoint.classList.add(\"waypoint\");\r\n\t\tassetWaypoint.classList.add(\"notify-waypoint\");\r\n\t\tassetWaypoint.style.fill = asset.Color;\r\n\t\tassetWaypoint.setAttribute(\"data-waypoint-id\", waypoint.Id);\r\n\t\tvar assetWaypointTitle = document.createElementNS(\"http://www.w3.org/2000/svg\", \"title\");\r\n\t\tassetWaypointTitle.textContent = waypoint.Name;\r\n\t\tassetWaypoint.appendChild(assetWaypointTitle);\r\n\t\tvar assetWaypointType = document.createElementNS(\"http://www.w3.org/2000/svg\", \"use\");\r\n\t\tassetWaypointType.setAttributeNS(\r\n\t\t\t\"http://www.w3.org/1999/xlink\",\r\n\t\t\t\"href\",\r\n\t\t\t\"/content/svg/tracking.svg?v=15#notify-waypoint\"\r\n\t\t);\r\n\t\tassetWaypoint.appendChild(assetWaypointType);\r\n\r\n\t\t//var assetWaypoint = document.createElement('div');\r\n\t\t//assetWaypoint.className = 'waypoint';\r\n\t\t//assetWaypoint.setAttribute('data-waypoint-id', waypoint.Id);\r\n\t\t//var assetWaypointLink = document.createElement('a');\r\n\t\t//assetWaypointLink.setAttribute('href', '#');\r\n\t\t//var assetWaypointIcon = document.createElement('span');\r\n\t\t//assetWaypointIcon.className = 't-icon t-icon-waypoint';\r\n\t\t//assetWaypointIcon.style.backgroundImage = 'url(' + createMarkerPath('Waypoint', asset.Color, null, null, null, false) + ')';\r\n\t\t//assetWaypointIcon.title = waypoint.Name;\r\n\t\t//assetWaypointLink.appendChild(assetWaypointIcon);\r\n\t\t//assetWaypoint.appendChild(assetWaypointLink);\r\n\t\t_.each(domNodes.assets[asset.Id], function (assetNode) {\r\n\t\t\tassetNode.querySelector(\".asset-indicators\").appendChild(assetWaypoint.cloneNode(true));\r\n\t\t});\r\n\t\tassetWaypoint = null;\r\n\t});\r\n}\r\n","import { updateBadgesWithCounts } from \"./badges.js\";\r\nimport domNodes from \"./domNodes\";\r\nimport { trkDataGroups } from \"./const.js\";\r\nimport trkData from \"./data.js\";\r\nimport { getGroupAssetStatus } from \"./asset-group.js\";\r\nimport state from \"./state.js\";\r\nimport { mapModes } from \"./const.js\";\r\nimport { findAssetById } from \"./assets.js\";\r\nimport { findGroupById } from \"./asset-group.js\";\r\nimport { EVENTS_STATUS, EVENTS_TEXT_MESSAGE, EVENTS_EMERGENCY, EVENTS_ALERT } from \"./asset-events.js\";\r\n\r\n\r\nimport _ from \"lodash\";\r\n\r\nexport function updateNotificationsInSecondaryPanel(type, itemId, counts) {\r\n\tvar panel = domNodes.panels.secondary;\r\n\tif (panel.getAttribute(\"data-item-type\") !== type) {\r\n\t\treturn;\r\n\t}\r\n\tif (panel.getAttribute(\"data-item-id\") !== itemId.toString()) {\r\n\t\treturn;\r\n\t}\r\n\r\n\t// update notification counts for tabs in secondary panel\r\n\tvar tabs = document.getElementById(\"panel-secondary-nav-tabs\");\r\n\tvar badgePositions = tabs.querySelector(\"#nav-positions-tab .badge\");\r\n\tvar badgeAlerts = tabs.querySelector(\"#nav-alerts-tab .badge\");\r\n\tvar badgeEvents = tabs.querySelector(\"#nav-events-tab .badge\");\r\n\tvar badgeStatus = tabs.querySelector(\"#nav-status-tab .badge\");\r\n\tvar badgeChat = tabs.querySelector(\"#nav-chat-tab .badge\");\r\n\tvar badgeMessages = tabs.querySelector(\"#nav-messages-tab .badge\");\r\n\r\n\tupdateBadgesWithCounts(badgePositions, badgeAlerts, badgeEvents, badgeStatus, badgeChat, badgeMessages, counts);\r\n}\r\n\r\nexport function getNotificationCountsForGroup(dataGroup, groupId) {\r\n\t//console.log('getNotificationCountsForGroup', groupId, dataGroup);\r\n\t// get all unique assets under group, including subgroups\r\n\tvar itemIds = [];\r\n\tif (groupId === \"all-assets\") {\r\n\t\titemIds = _.map(trkData.assets, \"Id\");\r\n\t} else {\r\n\t\tif (dataGroup === trkDataGroups.JOURNEY_HISTORY) {\r\n\t\t\tgroupId = \"journey-\" + groupId;\r\n\t\t} else if (dataGroup === trkDataGroups.SHARED_VIEW_HISTORY) {\r\n\t\t\tgroupId = \"shared-view-\" + groupId;\r\n\t\t}\r\n\t\tvar status = getGroupAssetStatus(groupId);\r\n\t\tif (dataGroup == trkDataGroups.JOURNEY_HISTORY) {\r\n\t\t\titemIds = status.tripIds;\r\n\t\t} else if (dataGroup === trkDataGroups.SHARED_VIEW_HISTORY) {\r\n\t\t\titemIds = status.sharedViewIds;\r\n\t\t} else {\r\n\t\t\titemIds = status.assetIds;\r\n\t\t}\r\n\t}\r\n\tvar sum = {\r\n\t\tpositions: 0,\r\n\t\tevents: 0,\r\n\t\talerts: 0,\r\n\t\tstatus: 0,\r\n\t\tmessages: 0,\r\n\t\tmessagesChat: 0,\r\n\t\tmessagesDevice: 0,\r\n\t\thasHistoryResult: false,\r\n\t\thasEmergency: false,\r\n\t};\r\n\r\n\titemIds.forEach(function (itemId) {\r\n\t\tvar counts = getNotificationCounts(dataGroup, itemId);\r\n\t\tsum.positions += counts.positions;\r\n\t\tsum.events += counts.events;\r\n\t\tsum.alerts += counts.alerts;\r\n\t\tsum.status += counts.status;\r\n\t\tsum.messages += counts.messages;\r\n\t\tsum.messagesChat += counts.messagesChat;\r\n\t\tsum.messagesDevice += counts.messagesDevice;\r\n\t\tif (counts.hasHistoryResult) {\r\n\t\t\tsum.hasHistoryResult = true;\r\n\t\t}\r\n\t\tif (counts.hasEmergency) {\r\n\t\t\tsum.hasEmergency = true;\r\n\t\t}\r\n\t});\r\n\treturn sum;\r\n\t// get notification counts for each asset in this mode\r\n\t// sum them up for the group\r\n}\r\n\r\nexport function getNotificationCounts(dataGroup, itemId) {\r\n\t//console.log('getNotificationCounts', dataGroup, itemId);\r\n\tvar positions = 0;\r\n\tvar alerts = 0;\r\n\tvar events = 0;\r\n\tvar status = 0;\r\n\tvar messages = 0;\r\n\tvar messagesChat = 0;\r\n\tvar messagesDevice = 0;\r\n\r\n\tvar assetEvents = [];\r\n\tvar hasHistoryResult = true;\r\n\tif (dataGroup === trkDataGroups.NORMAL_LIVE) {\r\n\t\t// todo: this should return unread counts only, not the sum of events that are recorded\r\n\t\tif (trkData.live.normalizedPositionsByAssetId[itemId] !== undefined) {\r\n\t\t\tpositions = trkData.live.normalizedPositionsByAssetId[itemId].length;\r\n\t\t}\r\n\t\tif (trkData.live.normalizedEventsByAssetId[itemId] !== undefined) {\r\n\t\t\tassetEvents = trkData.live.normalizedEventsByAssetId[itemId];\r\n\t\t}\r\n\t\tif (trkData.live.messageCountsByAssetId[itemId] !== undefined) {\r\n\t\t\tmessages =\r\n\t\t\t\ttrkData.live.messageCountsByAssetId[itemId].FromMobile + trkData.live.messageCountsByAssetId[itemId].ToMobile;\r\n\t\t\tmessagesChat =\r\n\t\t\t\ttrkData.live.messageCountsByAssetId[itemId].FromMobileChats +\r\n\t\t\t\ttrkData.live.messageCountsByAssetId[itemId].ToMobileChats;\r\n\t\t\tmessagesDevice =\r\n\t\t\t\ttrkData.live.messageCountsByAssetId[itemId].FromMobileDevice +\r\n\t\t\t\ttrkData.live.messageCountsByAssetId[itemId].ToMobileDevice;\r\n\t\t}\r\n\t} else if (dataGroup === trkDataGroups.NORMAL_HISTORY) {\r\n\t\tif (trkData.history.normalizedPositionsByAssetId[itemId] !== undefined) {\r\n\t\t\tpositions = trkData.history.normalizedPositionsByAssetId[itemId].length;\r\n\t\t\thasHistoryResult = trkData.history.assetIdsWithResults[itemId] !== undefined;\r\n\t\t}\r\n\t\tif (trkData.history.normalizedEventsByAssetId[itemId] !== undefined) {\r\n\t\t\tassetEvents = trkData.history.normalizedEventsByAssetId[itemId];\r\n\t\t}\r\n\t\tif (trkData.history.messageCountsByAssetId[itemId] !== undefined) {\r\n\t\t\tmessages =\r\n\t\t\t\ttrkData.history.messageCountsByAssetId[itemId].FromMobile +\r\n\t\t\t\ttrkData.history.messageCountsByAssetId[itemId].ToMobile;\r\n\t\t\tmessagesChat =\r\n\t\t\t\ttrkData.history.messageCountsByAssetId[itemId].FromMobileChats +\r\n\t\t\t\ttrkData.history.messageCountsByAssetId[itemId].ToMobileChats;\r\n\t\t\tmessagesDevice =\r\n\t\t\t\ttrkData.history.messageCountsByAssetId[itemId].FromMobileDevice +\r\n\t\t\t\ttrkData.history.messageCountsByAssetId[itemId].ToMobileDevice;\r\n\t\t}\r\n\t} else if (dataGroup === trkDataGroups.JOURNEY_HISTORY) {\r\n\t\tif (trkData.trips.normalizedPositionsByTripId[itemId] !== undefined) {\r\n\t\t\tpositions = trkData.trips.normalizedPositionsByTripId[itemId].length;\r\n\t\t\thasHistoryResult = trkData.trips.tripIdsWithResults[itemId] !== undefined;\r\n\t\t}\r\n\t\tif (trkData.trips.normalizedEventsByTripId[itemId] !== undefined) {\r\n\t\t\tassetEvents = trkData.trips.normalizedEventsByTripId[itemId];\r\n\t\t}\r\n\t\tif (trkData.trips.messageCountsByTripId[itemId] !== undefined) {\r\n\t\t\tmessages =\r\n\t\t\t\ttrkData.trips.messageCountsByTripId[itemId].FromMobile + trkData.trips.messageCountsByTripId[itemId].ToMobile;\r\n\t\t\tmessagesChat =\r\n\t\t\t\ttrkData.trips.messageCountsByTripId[itemId].FromMobileChats +\r\n\t\t\t\ttrkData.trips.messageCountsByTripId[itemId].ToMobileChats;\r\n\t\t\tmessagesDevice =\r\n\t\t\t\ttrkData.trips.messageCountsByTripId[itemId].FromMobileDevice +\r\n\t\t\t\ttrkData.trips.messageCountsByTripId[itemId].ToMobileDevice;\r\n\t\t}\r\n\t} else if (dataGroup === trkDataGroups.SHARED_VIEW_HISTORY) {\r\n\t\tpositions = trkData.sharedView.normalizedPositions.length;\r\n\t\tassetEvents = trkData.sharedView.normalizedEvents;\r\n\t\tmessages = trkData.sharedView.messageCounts.FromMobile + trkData.sharedView.messageCounts.ToMobile;\r\n\t\tmessagesChat = trkData.sharedView.messageCounts.FromMobileChats + trkData.sharedView.messageCounts.ToMobileChats;\r\n\t\tmessagesDevice =\r\n\t\t\ttrkData.sharedView.messageCounts.FromMobileDevice + trkData.sharedView.messageCounts.ToMobileDevice;\r\n\t}\r\n\r\n\tassetEvents = _.filter(assetEvents, function (event) {\r\n\t\treturn !event.Event.Hide;\r\n\t});\r\n\tevents = assetEvents.length;\r\n\t// alerts\r\n\tvar textEvents = _.filter(assetEvents, function (event) {\r\n\t\treturn !event.Event.Hide && EVENTS_TEXT_MESSAGE.indexOf(event.Event.Type) !== -1;\r\n\t});\r\n\tvar alertEvents = _.filter(assetEvents, function (event) {\r\n\t\treturn !event.Event.Hide && EVENTS_ALERT.indexOf(event.Event.Type) !== -1;\r\n\t});\r\n\tvar emergencyEvents = _.filter(assetEvents, function (event) {\r\n\t\treturn !event.Event.Hide && EVENTS_EMERGENCY.indexOf(event.Event.Type) !== -1;\r\n\t});\r\n\tvar hasEmergency = emergencyEvents.length > 0;\r\n\talerts = alertEvents.length + emergencyEvents.length;\r\n\t// status - really a change in position .State\r\n\t// but we'll just go with specific event types for now as the event may not have an associated position\r\n\tvar statusEvents = _.filter(assetEvents, function (event) {\r\n\t\treturn !event.Event.Hide && EVENTS_STATUS.indexOf(event.Event.Type) !== -1;\r\n\t});\r\n\tstatus = statusEvents.length;\r\n\r\n\tevents = events - alerts - status - textEvents.length;\r\n\r\n\tvar result = {\r\n\t\tpositions: positions,\r\n\t\tevents: events,\r\n\t\talerts: alerts,\r\n\t\tstatus: status,\r\n\t\tmessages: messages,\r\n\t\tmessagesChat: messagesChat,\r\n\t\tmessagesDevice: messagesDevice,\r\n\t\thasHistoryResult: hasHistoryResult,\r\n\t\thasEmergency: hasEmergency,\r\n\t};\r\n\treturn result;\r\n}\r\n\r\nexport const NOTIFICATION_TYPES = [\"positions\", \"events\", \"alerts\", \"messages\", \"status\"];\r\nexport const NOTIFICATION_PREFIXES = {\r\n\tpositions: {\r\n\t\tprefix: \"op\",\r\n\t\tclasses: [\"op-10\", \"op-20\", \"op-30\", \"op-40\", \"op-50\", \"op-60\", \"op-70\", \"op-80\", \"op-90\", \"op-100\"],\r\n\t},\r\n\talerts: {\r\n\t\tprefix: \"oa\",\r\n\t\tclasses: [\"oa-10\", \"oa-20\", \"oa-30\", \"oa-40\", \"oa-50\", \"oa-60\", \"oa-70\", \"oa-80\", \"oa-90\", \"oa-100\"],\r\n\t},\r\n\tevents: {\r\n\t\tprefix: \"oe\",\r\n\t\tclasses: [\"oe-10\", \"oe-20\", \"oe-30\", \"oe-40\", \"oe-50\", \"oe-60\", \"oe-70\", \"oe-80\", \"oe-90\", \"oe-100\"],\r\n\t},\r\n\tmessages: {\r\n\t\tprefix: \"om\",\r\n\t\tclasses: [\"om-10\", \"om-20\", \"om-30\", \"om-40\", \"om-50\", \"om-60\", \"om-70\", \"om-80\", \"om-90\", \"om-100\"],\r\n\t},\r\n\tstatus: {\r\n\t\tprefix: \"os\",\r\n\t\tclasses: [\"os-10\", \"os-20\", \"os-30\", \"os-40\", \"os-50\", \"os-60\", \"os-70\", \"os-80\", \"os-90\", \"os-100\"],\r\n\t},\r\n};\r\n\r\nexport function getNotificationOpacityForTime(time) {\r\n\tif (time === null) {\r\n\t\treturn null;\r\n\t}\r\n\t//var serverTime = new Date().getTime() - user.tickOffset;\r\n\tvar currentTime = new Date().getTime();\r\n\tvar difference = Math.abs(currentTime - time);\r\n\tif (difference > 3600000) {\r\n\t\treturn null;\r\n\t}\r\n\r\n\tvar percent = 100 - Math.floor((difference / 3600000) * 100);\r\n\tvar opacity = Math.round(percent / 10) * 10; // round to nearest 10\r\n\treturn opacity;\r\n}\r\n\r\nexport function updateTimeBasedNotificationIndicatorsForNode(item, opacity, type) {\r\n\tvar recentType = \"recent-\" + type;\r\n\r\n\t//var classesToRemove = _.union([recentType], NOTIFICATION_PREFIXES[type].classes);\r\n\tvar classesToRemove = [recentType].concat(NOTIFICATION_PREFIXES[type].classes);\r\n\tvar classNames = [recentType];\r\n\tif (opacity !== null) {\r\n\t\tclassNames.push(NOTIFICATION_PREFIXES[type].prefix + \"-\" + opacity);\r\n\t}\r\n\r\n\tif (opacity === null) {\r\n\t\t// remove all indicators for type, if any\r\n\t\tif (!item.classList.contains(recentType + \"-no\")) {\r\n\t\t\tclassesToRemove.forEach(function (cls) {\r\n\t\t\t\tif (item.classList.contains(cls)) {\r\n\t\t\t\t\titem.classList.remove(cls);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\titem.classList.add(recentType + \"-no\");\r\n\t\t}\r\n\r\n\t\t// bugfix here, remove this by ensuring recent-type-no is not added when recent-type is active\r\n\t\tif (item.classList.contains(recentType)) {\r\n\t\t\titem.classList.remove(recentType);\r\n\t\t}\r\n\t} else {\r\n\t\t// ensure classNames are added and others removed\r\n\t\tvar isRemoved = false;\r\n\t\tclassesToRemove = _.difference(classesToRemove, classNames);\r\n\t\tclassNames.forEach(function (className) {\r\n\t\t\tif (!item.classList.contains(className)) {\r\n\t\t\t\tif (!isRemoved) {\r\n\t\t\t\t\tif (!item.classList.contains(recentType + \"-no\")) {\r\n\t\t\t\t\t\tclassesToRemove.forEach(function (cls) {\r\n\t\t\t\t\t\t\tif (item.classList.contains(cls)) {\r\n\t\t\t\t\t\t\t\titem.classList.remove(cls);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\titem.classList.remove(recentType + \"-no\");\r\n\t\t\t\t\t}\r\n\t\t\t\t\tisRemoved = true;\r\n\t\t\t\t}\r\n\t\t\t\titem.classList.add(className);\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n}\r\n\r\nexport function updateTimeBasedNotificationIndicatorsForGroup(groupId) {\r\n\t//console.log('update time notification indicator for ' + groupId);\r\n\t// get max times for all assets within the group\r\n\tvar node = domNodes.groups[groupId];\r\n\tif (node === undefined) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tlet item = node.querySelector(\".notifications\");\r\n\r\n\tvar assetIds = [];\r\n\tvar assets = [];\r\n\tvar group = null;\r\n\tif (groupId === \"all-assets\") {\r\n\t\tassetIds = _.map(trkData.assets, \"Id\");\r\n\t} else {\r\n\t\tgroup = findGroupById(groupId);\r\n\t\tassetIds = group.AssetIds;\r\n\t}\r\n\r\n\tassetIds.forEach(function (assetId) {\r\n\t\tassets.push(findAssetById(assetId));\r\n\t});\r\n\r\n\tvar notificationTypes = NOTIFICATION_TYPES;\r\n\r\n\tnotificationTypes.forEach(function (itemType) {\r\n\t\tvar recentType = \"recent-\" + itemType;\r\n\r\n\t\tif (state.activeMapMode !== mapModes.LIVE) {\r\n\t\t\t// history mode does not support fading icons\r\n\t\t\t// remove any opacity classes from history mode\r\n\t\t\tif (!item.classList.contains(recentType + \"-no\")) {\r\n\t\t\t\tNOTIFICATION_PREFIXES[itemType].classes.forEach(function (cls) {\r\n\t\t\t\t\tif (item.classList.contains(cls)) {\r\n\t\t\t\t\t\titem.classList.remove(cls);\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t\titem.classList.add(recentType + \"-no\");\r\n\t\t\t}\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// add/upate indicators for LIVE mode\r\n\t\tvar opacity = null;\r\n\r\n\t\t// check times for all assets within this group\r\n\t\tassets.forEach(function (asset) {\r\n\t\t\tif (trkData.live.notificationTimesByAssetId[asset.Id] !== undefined) {\r\n\t\t\t\tvar assetOpacity = getNotificationOpacityForTime(trkData.live.notificationTimesByAssetId[asset.Id][itemType]);\r\n\t\t\t\tif (assetOpacity !== null && (opacity === null || assetOpacity > opacity)) {\r\n\t\t\t\t\topacity = assetOpacity;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tupdateTimeBasedNotificationIndicatorsForNode(item, opacity, itemType);\r\n\t});\r\n}\r\n","import { trkDataGroups } from \"./const.js\";\r\nimport {\r\n\tupdateNotificationsInSecondaryPanel,\r\n\tgetNotificationCounts,\r\n\tgetNotificationCountsForGroup,\r\n} from \"./notifications.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport trkData from \"./data.js\";\r\nimport { findTripById } from \"./trips.js\";\r\n\r\nimport _ from \"lodash\";\r\n\r\nfunction updateBadgesForNode(node, dataGroup, counts) {\r\n\tvar badgePositions = node.querySelector('[data-action=\"asset-positions\"] .badge');\r\n\tvar badgeAlerts = node.querySelector('[data-action=\"asset-alerts\"] .badge');\r\n\tvar badgeEvents = node.querySelector('[data-action=\"asset-events\"] .badge');\r\n\tvar badgeStatus = node.querySelector('[data-action=\"asset-status\"] .badge');\r\n\tvar badgeMessages = node.querySelector('[data-action=\"asset-messages\"] .badge');\r\n\tvar badgeChat = node.querySelector('[data-action=\"asset-chat\"] .badge');\r\n\tupdateBadgesWithCounts(badgePositions, badgeAlerts, badgeEvents, badgeStatus, badgeChat, badgeMessages, counts);\r\n\r\n\tvar notifications = node.querySelector(\".notifications\");\r\n\t// in history mode there can be a position returned outside of the queried time, so hasHistoryResult is used to differentiate\r\n\tif (\r\n\t\t(dataGroup === trkDataGroups.NORMAL_HISTORY ||\r\n\t\t\tdataGroup === trkDataGroups.JOURNEY_HISTORY ||\r\n\t\t\tdataGroup === trkDataGroups.SHARED_VIEW_HISTORY) &&\r\n\t\tcounts.positions > 0 &&\r\n\t\tcounts.hasHistoryResult\r\n\t) {\r\n\t\tnotifications.classList.add(\"recent-positions\");\r\n\t} else if (dataGroup === trkDataGroups.NORMAL_LIVE && counts.positions > 0) {\r\n\t\tnotifications.classList.add(\"recent-positions\");\r\n\t} else {\r\n\t\tnotifications.classList.remove(\"recent-positions\");\r\n\t}\r\n\r\n\tif (counts.alerts > 0) {\r\n\t\tnotifications.classList.add(\"recent-alerts\");\r\n\t} else {\r\n\t\tnotifications.classList.remove(\"recent-alerts\");\r\n\t}\r\n\r\n\tif (counts.hasEmergency) {\r\n\t\tnotifications.classList.add(\"has-emergency\");\r\n\t} else {\r\n\t\tnotifications.classList.remove(\"has-emergency\");\r\n\t}\r\n\r\n\tif (counts.events > 0) {\r\n\t\tnotifications.classList.add(\"recent-events\");\r\n\t} else {\r\n\t\tnotifications.classList.remove(\"recent-events\");\r\n\t}\r\n\r\n\tif (counts.status > 0) {\r\n\t\tnotifications.classList.add(\"recent-status\");\r\n\t} else {\r\n\t\tnotifications.classList.remove(\"recent-status\");\r\n\t}\r\n\r\n\tif (counts.messages > 0) {\r\n\t\tnotifications.classList.add(\"recent-messages\");\r\n\t} else {\r\n\t\tnotifications.classList.remove(\"recent-messages\");\r\n\t}\r\n}\r\n\r\nexport function updateBadgesWithCounts(\r\n\tbadgePositions,\r\n\tbadgeAlerts,\r\n\tbadgeEvents,\r\n\tbadgeStatus,\r\n\tbadgeChat,\r\n\tbadgeMessages,\r\n\tcounts\r\n) {\r\n\tif (badgePositions !== null) {\r\n\t\tif (counts.positions > 0) {\r\n\t\t\tbadgePositions.classList.add(\"active\");\r\n\t\t} else {\r\n\t\t\tbadgePositions.classList.remove(\"active\");\r\n\t\t}\r\n\t\tbadgePositions.textContent = counts.positions;\r\n\t}\r\n\r\n\tif (badgeAlerts !== null) {\r\n\t\tif (counts.alerts > 0) {\r\n\t\t\tbadgeAlerts.classList.add(\"active\");\r\n\t\t} else {\r\n\t\t\tbadgeAlerts.classList.remove(\"active\");\r\n\t\t}\r\n\t\tif (counts.hasEmergency) {\r\n\t\t\tbadgeAlerts.classList.remove(\"bg-alert\");\r\n\t\t\tbadgeAlerts.classList.add(\"bg-emergency\");\r\n\t\t} else {\r\n\t\t\tbadgeAlerts.classList.add(\"bg-alert\");\r\n\t\t\tbadgeAlerts.classList.remove(\"bg-emergency\");\r\n\t\t}\r\n\t\tvar parent = badgeAlerts.parentNode;\r\n\t\tif (parent.classList.contains(\"nav-item\")) {\r\n\t\t\tif (counts.hasEmergency) {\r\n\t\t\t\tparent.classList.add(\"notify-emergency-tab\");\r\n\t\t\t\tparent.classList.remove(\"notify-alert-tab\");\r\n\t\t\t} else {\r\n\t\t\t\tparent.classList.remove(\"notify-emergency-tab\");\r\n\t\t\t\tparent.classList.add(\"notify-alert-tab\");\r\n\t\t\t}\r\n\t\t}\r\n\t\tbadgeAlerts.textContent = counts.alerts;\r\n\t}\r\n\r\n\tif (badgeEvents !== null) {\r\n\t\tif (counts.events > 0) {\r\n\t\t\tbadgeEvents.classList.add(\"active\");\r\n\t\t} else {\r\n\t\t\tbadgeEvents.classList.remove(\"active\");\r\n\t\t}\r\n\t\tbadgeEvents.textContent = counts.events;\r\n\t}\r\n\r\n\tif (badgeStatus !== null) {\r\n\t\tif (counts.status > 0) {\r\n\t\t\tbadgeStatus.classList.add(\"active\");\r\n\t\t} else {\r\n\t\t\tbadgeStatus.classList.remove(\"active\");\r\n\t\t}\r\n\t\tbadgeStatus.textContent = counts.status;\r\n\t}\r\n\r\n\tif (badgeMessages !== null) {\r\n\t\tif (counts.messagesDevice > 0) {\r\n\t\t\tbadgeMessages.classList.add(\"active\");\r\n\t\t} else {\r\n\t\t\tbadgeMessages.classList.remove(\"active\");\r\n\t\t}\r\n\t\tbadgeMessages.textContent = counts.messagesDevice;\r\n\t}\r\n\r\n\tif (badgeChat !== null) {\r\n\t\tif (counts.messagesChat > 0) {\r\n\t\t\tbadgeChat.classList.add(\"active\");\r\n\t\t} else {\r\n\t\t\tbadgeChat.classList.remove(\"active\");\r\n\t\t}\r\n\t\tbadgeChat.textContent = counts.messagesChat;\r\n\t}\r\n}\r\n\r\nexport function updateGroupFunctionBadges(dataGroup, itemIds, type) {\r\n\t//console.log('update group badges ' + type + ': ' + (itemIds !== null ? itemIds.join(',') : 'all'));\r\n\tvar nodeList = domNodes.groups; // todo: group types\r\n\tvar groupIds = [];\r\n\tswitch (type) {\r\n\t\tcase \"asset\":\r\n\t\t\tgroupIds.push(\"all-assets\");\r\n\t\t\t// update appropriate asset groups and all-assets\r\n\t\t\t// get group ids that contain asset\r\n\t\t\tif (itemIds === null) {\r\n\t\t\t\titemIds = _.map(trkData.assets, \"Id\");\r\n\t\t\t}\r\n\t\t\titemIds.forEach(function (itemId) {\r\n\t\t\t\ttrkData.groups.forEach(function (group) {\r\n\t\t\t\t\tif (group.AssetIds.indexOf(itemId) !== -1) {\r\n\t\t\t\t\t\tgroupIds.push(group.Id);\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t\t\tbreak;\r\n\t\tcase \"trip\":\r\n\t\t\t// update related journeys\r\n\t\t\tif (itemIds === null) {\r\n\t\t\t}\r\n\t\t\tdataGroup = trkDataGroups.JOURNEY_HISTORY;\r\n\t\t\titemIds.forEach(function (tripId) {\r\n\t\t\t\tvar trip = findTripById(tripId);\r\n\t\t\t\tgroupIds.push(trip.JourneyId);\r\n\t\t\t});\r\n\t\t\tbreak;\r\n\t}\r\n\tgroupIds = _.uniq(groupIds);\r\n\tgroupIds.forEach(function (groupId) {\r\n\t\tvar groupCounts = getNotificationCountsForGroup(dataGroup, groupId);\r\n\t\tif (dataGroup === trkDataGroups.JOURNEY_HISTORY) {\r\n\t\t\tgroupId = \"journey-\" + groupId;\r\n\t\t}\r\n\t\tupdateBadgesForNode(nodeList[groupId].querySelector(\".group-info\"), dataGroup, groupCounts);\r\n\t\tvar notificationType = \"groups\";\r\n\t\tif (dataGroup === trkDataGroups.JOURNEY_HISTORY) {\r\n\t\t\tnotificationType = \"journeys\";\r\n\t\t}\r\n\t\tupdateNotificationsInSecondaryPanel(notificationType, groupId, groupCounts);\r\n\t});\r\n}\r\n\r\nexport function updateAssetFunctionBadges(dataGroup, itemId) {\r\n\t// todo: performance, these should be stored instead of recalculated every time\r\n\tvar counts = getNotificationCounts(dataGroup, itemId);\r\n\r\n\tif (dataGroup !== trkDataGroups.SHARED_VIEW_HISTORY) {\r\n\t\tvar nodeList = domNodes.assets;\r\n\t\tif (dataGroup === trkDataGroups.JOURNEY_HISTORY) {\r\n\t\t\tnodeList = domNodes.trips;\r\n\t\t}\r\n\r\n\t\tvar items = nodeList[itemId];\r\n\t\tif (!_.isArray(items)) {\r\n\t\t\titems = [items];\r\n\t\t}\r\n\r\n\t\titems.forEach(function (node) {\r\n\t\t\tupdateBadgesForNode(node, dataGroup, counts);\r\n\t\t});\r\n\t}\r\n\r\n\tvar notificationType = \"assets\";\r\n\tif (dataGroup === trkDataGroups.JOURNEY_HISTORY) {\r\n\t\tnotificationType = \"trips\";\r\n\t} else if (dataGroup === trkDataGroups.SHARED_VIEW_HISTORY) {\r\n\t\tnotificationType = \"shared-views\";\r\n\t}\r\n\tupdateNotificationsInSecondaryPanel(notificationType, itemId, counts);\r\n}\r\n","/**\r\n * ISC License.\r\n *\r\n * Copyright (c) 2016, Vladimir Agafonkin\r\n *\r\n * Permission to use, copy, modify, and/or distribute this software for any purpose\r\n * with or without fee is hereby granted, provided that the above copyright notice\r\n * and this permission notice appear in all copies.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\r\n * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\r\n * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\r\n * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\r\n * THIS SOFTWARE.\r\n */\r\n\r\nexport default function quickselect(arr, k, left, right, compare) {\r\n quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);\r\n}\r\n\r\nfunction quickselectStep(arr, k, left, right, compare) {\r\n\r\n while (right > left) {\r\n if (right - left > 600) {\r\n var n = right - left + 1;\r\n var m = k - left + 1;\r\n var z = Math.log(n);\r\n var s = 0.5 * Math.exp(2 * z / 3);\r\n var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);\r\n var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));\r\n var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));\r\n quickselectStep(arr, k, newLeft, newRight, compare);\r\n }\r\n\r\n var t = arr[k];\r\n var i = left;\r\n var j = right;\r\n\r\n swap(arr, left, k);\r\n if (compare(arr[right], t) > 0) swap(arr, left, right);\r\n\r\n while (i < j) {\r\n swap(arr, i, j);\r\n i++;\r\n j--;\r\n while (compare(arr[i], t) < 0) i++;\r\n while (compare(arr[j], t) > 0) j--;\r\n }\r\n\r\n if (compare(arr[left], t) === 0) swap(arr, left, j);\r\n else {\r\n j++;\r\n swap(arr, j, right);\r\n }\r\n\r\n if (j <= k) left = j + 1;\r\n if (k <= j) right = j - 1;\r\n }\r\n}\r\n\r\nfunction swap(arr, i, j) {\r\n var tmp = arr[i];\r\n arr[i] = arr[j];\r\n arr[j] = tmp;\r\n}\r\n\r\nfunction defaultCompare(a, b) {\r\n return a < b ? -1 : a > b ? 1 : 0;\r\n}\r\n","/**\r\n * MIT License\r\n *\r\n * Copyright (c) 2016 Vladimir Agafonkin\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"Software\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\nimport quickselect from './quickselect.mjs';\r\n\r\nexport default class RBush {\r\n constructor(maxEntries = 9) {\r\n // max entries in a node is 9 by default; min node fill is 40% for best performance\r\n this._maxEntries = Math.max(4, maxEntries);\r\n this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));\r\n this.clear();\r\n }\r\n\r\n all() {\r\n return this._all(this.data, []);\r\n }\r\n\r\n search(bbox) {\r\n let node = this.data;\r\n const result = [];\r\n\r\n if (!intersects(bbox, node)) return result;\r\n\r\n const toBBox = this.toBBox;\r\n const nodesToSearch = [];\r\n\r\n while (node) {\r\n for (let i = 0; i < node.children.length; i++) {\r\n const child = node.children[i];\r\n const childBBox = node.leaf ? toBBox(child) : child;\r\n\r\n if (intersects(bbox, childBBox)) {\r\n if (node.leaf) result.push(child);\r\n else if (contains(bbox, childBBox)) this._all(child, result);\r\n else nodesToSearch.push(child);\r\n }\r\n }\r\n node = nodesToSearch.pop();\r\n }\r\n\r\n return result;\r\n }\r\n\r\n collides(bbox) {\r\n let node = this.data;\r\n\r\n if (!intersects(bbox, node)) return false;\r\n\r\n const nodesToSearch = [];\r\n while (node) {\r\n for (let i = 0; i < node.children.length; i++) {\r\n const child = node.children[i];\r\n const childBBox = node.leaf ? this.toBBox(child) : child;\r\n\r\n if (intersects(bbox, childBBox)) {\r\n if (node.leaf || contains(bbox, childBBox)) return true;\r\n nodesToSearch.push(child);\r\n }\r\n }\r\n node = nodesToSearch.pop();\r\n }\r\n\r\n return false;\r\n }\r\n\r\n load(data) {\r\n if (!(data && data.length)) return this;\r\n\r\n if (data.length < this._minEntries) {\r\n for (let i = 0; i < data.length; i++) {\r\n this.insert(data[i]);\r\n }\r\n return this;\r\n }\r\n\r\n // recursively build the tree with the given data from scratch using OMT algorithm\r\n let node = this._build(data.slice(), 0, data.length - 1, 0);\r\n\r\n if (!this.data.children.length) {\r\n // save as is if tree is empty\r\n this.data = node;\r\n\r\n } else if (this.data.height === node.height) {\r\n // split root if trees have the same height\r\n this._splitRoot(this.data, node);\r\n\r\n } else {\r\n if (this.data.height < node.height) {\r\n // swap trees if inserted one is bigger\r\n const tmpNode = this.data;\r\n this.data = node;\r\n node = tmpNode;\r\n }\r\n\r\n // insert the small tree into the large tree at appropriate level\r\n this._insert(node, this.data.height - node.height - 1, true);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n insert(item) {\r\n if (item) this._insert(item, this.data.height - 1);\r\n return this;\r\n }\r\n\r\n clear() {\r\n this.data = createNode([]);\r\n return this;\r\n }\r\n\r\n remove(item, equalsFn) {\r\n if (!item) return this;\r\n\r\n let node = this.data;\r\n const bbox = this.toBBox(item);\r\n const path = [];\r\n const indexes = [];\r\n let i, parent, goingUp;\r\n\r\n // depth-first iterative tree traversal\r\n while (node || path.length) {\r\n\r\n if (!node) { // go up\r\n node = path.pop();\r\n parent = path[path.length - 1];\r\n i = indexes.pop();\r\n goingUp = true;\r\n }\r\n\r\n if (node.leaf) { // check current node\r\n const index = findItem(item, node.children, equalsFn);\r\n\r\n if (index !== -1) {\r\n // item found, remove the item and condense tree upwards\r\n node.children.splice(index, 1);\r\n path.push(node);\r\n this._condense(path);\r\n return this;\r\n }\r\n }\r\n\r\n if (!goingUp && !node.leaf && contains(node, bbox)) { // go down\r\n path.push(node);\r\n indexes.push(i);\r\n i = 0;\r\n parent = node;\r\n node = node.children[0];\r\n\r\n } else if (parent) { // go right\r\n i++;\r\n node = parent.children[i];\r\n goingUp = false;\r\n\r\n } else node = null; // nothing found\r\n }\r\n\r\n return this;\r\n }\r\n\r\n toBBox(item) { return item; }\r\n\r\n compareMinX(a, b) { return a.minX - b.minX; }\r\n compareMinY(a, b) { return a.minY - b.minY; }\r\n\r\n toJSON() { return this.data; }\r\n\r\n fromJSON(data) {\r\n this.data = data;\r\n return this;\r\n }\r\n\r\n _all(node, result) {\r\n const nodesToSearch = [];\r\n while (node) {\r\n if (node.leaf) result.push(...node.children);\r\n else nodesToSearch.push(...node.children);\r\n\r\n node = nodesToSearch.pop();\r\n }\r\n return result;\r\n }\r\n\r\n _build(items, left, right, height) {\r\n\r\n const N = right - left + 1;\r\n let M = this._maxEntries;\r\n let node;\r\n\r\n if (N <= M) {\r\n // reached leaf level; return leaf\r\n node = createNode(items.slice(left, right + 1));\r\n calcBBox(node, this.toBBox);\r\n return node;\r\n }\r\n\r\n if (!height) {\r\n // target height of the bulk-loaded tree\r\n height = Math.ceil(Math.log(N) / Math.log(M));\r\n\r\n // target number of root entries to maximize storage utilization\r\n M = Math.ceil(N / Math.pow(M, height - 1));\r\n }\r\n\r\n node = createNode([]);\r\n node.leaf = false;\r\n node.height = height;\r\n\r\n // split the items into M mostly square tiles\r\n\r\n const N2 = Math.ceil(N / M);\r\n const N1 = N2 * Math.ceil(Math.sqrt(M));\r\n\r\n multiSelect(items, left, right, N1, this.compareMinX);\r\n\r\n for (let i = left; i <= right; i += N1) {\r\n\r\n const right2 = Math.min(i + N1 - 1, right);\r\n\r\n multiSelect(items, i, right2, N2, this.compareMinY);\r\n\r\n for (let j = i; j <= right2; j += N2) {\r\n\r\n const right3 = Math.min(j + N2 - 1, right2);\r\n\r\n // pack each entry recursively\r\n node.children.push(this._build(items, j, right3, height - 1));\r\n }\r\n }\r\n\r\n calcBBox(node, this.toBBox);\r\n\r\n return node;\r\n }\r\n\r\n _chooseSubtree(bbox, node, level, path) {\r\n while (true) {\r\n path.push(node);\r\n\r\n if (node.leaf || path.length - 1 === level) break;\r\n\r\n let minArea = Infinity;\r\n let minEnlargement = Infinity;\r\n let targetNode;\r\n\r\n for (let i = 0; i < node.children.length; i++) {\r\n const child = node.children[i];\r\n const area = bboxArea(child);\r\n const enlargement = enlargedArea(bbox, child) - area;\r\n\r\n // choose entry with the least area enlargement\r\n if (enlargement < minEnlargement) {\r\n minEnlargement = enlargement;\r\n minArea = area < minArea ? area : minArea;\r\n targetNode = child;\r\n\r\n } else if (enlargement === minEnlargement) {\r\n // otherwise choose one with the smallest area\r\n if (area < minArea) {\r\n minArea = area;\r\n targetNode = child;\r\n }\r\n }\r\n }\r\n\r\n node = targetNode || node.children[0];\r\n }\r\n\r\n return node;\r\n }\r\n\r\n _insert(item, level, isNode) {\r\n const bbox = isNode ? item : this.toBBox(item);\r\n const insertPath = [];\r\n\r\n // find the best node for accommodating the item, saving all nodes along the path too\r\n const node = this._chooseSubtree(bbox, this.data, level, insertPath);\r\n\r\n // put the item into the node\r\n node.children.push(item);\r\n extend(node, bbox);\r\n\r\n // split on node overflow; propagate upwards if necessary\r\n while (level >= 0) {\r\n if (insertPath[level].children.length > this._maxEntries) {\r\n this._split(insertPath, level);\r\n level--;\r\n } else break;\r\n }\r\n\r\n // adjust bboxes along the insertion path\r\n this._adjustParentBBoxes(bbox, insertPath, level);\r\n }\r\n\r\n // split overflowed node into two\r\n _split(insertPath, level) {\r\n const node = insertPath[level];\r\n const M = node.children.length;\r\n const m = this._minEntries;\r\n\r\n this._chooseSplitAxis(node, m, M);\r\n\r\n const splitIndex = this._chooseSplitIndex(node, m, M);\r\n\r\n const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));\r\n newNode.height = node.height;\r\n newNode.leaf = node.leaf;\r\n\r\n calcBBox(node, this.toBBox);\r\n calcBBox(newNode, this.toBBox);\r\n\r\n if (level) insertPath[level - 1].children.push(newNode);\r\n else this._splitRoot(node, newNode);\r\n }\r\n\r\n _splitRoot(node, newNode) {\r\n // split root node\r\n this.data = createNode([node, newNode]);\r\n this.data.height = node.height + 1;\r\n this.data.leaf = false;\r\n calcBBox(this.data, this.toBBox);\r\n }\r\n\r\n _chooseSplitIndex(node, m, M) {\r\n let index;\r\n let minOverlap = Infinity;\r\n let minArea = Infinity;\r\n\r\n for (let i = m; i <= M - m; i++) {\r\n const bbox1 = distBBox(node, 0, i, this.toBBox);\r\n const bbox2 = distBBox(node, i, M, this.toBBox);\r\n\r\n const overlap = intersectionArea(bbox1, bbox2);\r\n const area = bboxArea(bbox1) + bboxArea(bbox2);\r\n\r\n // choose distribution with minimum overlap\r\n if (overlap < minOverlap) {\r\n minOverlap = overlap;\r\n index = i;\r\n\r\n minArea = area < minArea ? area : minArea;\r\n\r\n } else if (overlap === minOverlap) {\r\n // otherwise choose distribution with minimum area\r\n if (area < minArea) {\r\n minArea = area;\r\n index = i;\r\n }\r\n }\r\n }\r\n\r\n return index || M - m;\r\n }\r\n\r\n // sorts node children by the best axis for split\r\n _chooseSplitAxis(node, m, M) {\r\n const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;\r\n const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;\r\n const xMargin = this._allDistMargin(node, m, M, compareMinX);\r\n const yMargin = this._allDistMargin(node, m, M, compareMinY);\r\n\r\n // if total distributions margin value is minimal for x, sort by minX,\r\n // otherwise it's already sorted by minY\r\n if (xMargin < yMargin) node.children.sort(compareMinX);\r\n }\r\n\r\n // total margin of all possible split distributions where each node is at least m full\r\n _allDistMargin(node, m, M, compare) {\r\n node.children.sort(compare);\r\n\r\n const toBBox = this.toBBox;\r\n const leftBBox = distBBox(node, 0, m, toBBox);\r\n const rightBBox = distBBox(node, M - m, M, toBBox);\r\n let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);\r\n\r\n for (let i = m; i < M - m; i++) {\r\n const child = node.children[i];\r\n extend(leftBBox, node.leaf ? toBBox(child) : child);\r\n margin += bboxMargin(leftBBox);\r\n }\r\n\r\n for (let i = M - m - 1; i >= m; i--) {\r\n const child = node.children[i];\r\n extend(rightBBox, node.leaf ? toBBox(child) : child);\r\n margin += bboxMargin(rightBBox);\r\n }\r\n\r\n return margin;\r\n }\r\n\r\n _adjustParentBBoxes(bbox, path, level) {\r\n // adjust bboxes along the given tree path\r\n for (let i = level; i >= 0; i--) {\r\n extend(path[i], bbox);\r\n }\r\n }\r\n\r\n _condense(path) {\r\n // go through the path, removing empty nodes and updating bboxes\r\n for (let i = path.length - 1, siblings; i >= 0; i--) {\r\n if (path[i].children.length === 0) {\r\n if (i > 0) {\r\n siblings = path[i - 1].children;\r\n siblings.splice(siblings.indexOf(path[i]), 1);\r\n\r\n } else this.clear();\r\n\r\n } else calcBBox(path[i], this.toBBox);\r\n }\r\n }\r\n}\r\n\r\nfunction findItem(item, items, equalsFn) {\r\n if (!equalsFn) return items.indexOf(item);\r\n\r\n for (let i = 0; i < items.length; i++) {\r\n if (equalsFn(item, items[i])) return i;\r\n }\r\n return -1;\r\n}\r\n\r\n// calculate node's bbox from bboxes of its children\r\nfunction calcBBox(node, toBBox) {\r\n distBBox(node, 0, node.children.length, toBBox, node);\r\n}\r\n\r\n// min bounding rectangle of node children from k to p-1\r\nfunction distBBox(node, k, p, toBBox, destNode) {\r\n if (!destNode) destNode = createNode(null);\r\n destNode.minX = Infinity;\r\n destNode.minY = Infinity;\r\n destNode.maxX = -Infinity;\r\n destNode.maxY = -Infinity;\r\n\r\n for (let i = k; i < p; i++) {\r\n const child = node.children[i];\r\n extend(destNode, node.leaf ? toBBox(child) : child);\r\n }\r\n\r\n return destNode;\r\n}\r\n\r\nfunction extend(a, b) {\r\n a.minX = Math.min(a.minX, b.minX);\r\n a.minY = Math.min(a.minY, b.minY);\r\n a.maxX = Math.max(a.maxX, b.maxX);\r\n a.maxY = Math.max(a.maxY, b.maxY);\r\n return a;\r\n}\r\n\r\nfunction compareNodeMinX(a, b) { return a.minX - b.minX; }\r\nfunction compareNodeMinY(a, b) { return a.minY - b.minY; }\r\n\r\nfunction bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }\r\nfunction bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }\r\n\r\nfunction enlargedArea(a, b) {\r\n return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *\r\n (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));\r\n}\r\n\r\nfunction intersectionArea(a, b) {\r\n const minX = Math.max(a.minX, b.minX);\r\n const minY = Math.max(a.minY, b.minY);\r\n const maxX = Math.min(a.maxX, b.maxX);\r\n const maxY = Math.min(a.maxY, b.maxY);\r\n\r\n return Math.max(0, maxX - minX) *\r\n Math.max(0, maxY - minY);\r\n}\r\n\r\nfunction contains(a, b) {\r\n return a.minX <= b.minX &&\r\n a.minY <= b.minY &&\r\n b.maxX <= a.maxX &&\r\n b.maxY <= a.maxY;\r\n}\r\n\r\nfunction intersects(a, b) {\r\n return b.minX <= a.maxX &&\r\n b.minY <= a.maxY &&\r\n b.maxX >= a.minX &&\r\n b.maxY >= a.minY;\r\n}\r\n\r\nfunction createNode(children) {\r\n return {\r\n children,\r\n height: 1,\r\n leaf: true,\r\n minX: Infinity,\r\n minY: Infinity,\r\n maxX: -Infinity,\r\n maxY: -Infinity\r\n };\r\n}\r\n\r\n// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;\r\n// combines selection algorithm with binary divide & conquer approach\r\n\r\nfunction multiSelect(arr, left, right, n, compare) {\r\n const stack = [left, right];\r\n\r\n while (stack.length) {\r\n right = stack.pop();\r\n left = stack.pop();\r\n\r\n if (right - left <= n) continue;\r\n\r\n const mid = left + Math.ceil((right - left) / n / 2) * n;\r\n quickselect(arr, mid, left, right, compare);\r\n\r\n stack.push(left, mid, mid, right);\r\n }\r\n}\r\n","/**\r\n * ISC License.\r\n *\r\n * Copyright (c) 2017, Vladimir Agafonkin\r\n *\r\n * Permission to use, copy, modify, and/or distribute this software for any purpose\r\n * with or without fee is hereby granted, provided that the above copyright notice\r\n * and this permission notice appear in all copies.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\r\n * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\r\n * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\r\n * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\r\n * THIS SOFTWARE.\r\n */\r\n\r\nexport default class TinyQueue {\r\n constructor(data = [], compare = defaultCompare) {\r\n this.data = data;\r\n this.length = this.data.length;\r\n this.compare = compare;\r\n\r\n if (this.length > 0) {\r\n for (let i = (this.length >> 1) - 1; i >= 0; i--) this._down(i);\r\n }\r\n }\r\n\r\n push(item) {\r\n this.data.push(item);\r\n this._up(this.length++);\r\n }\r\n\r\n pop() {\r\n if (this.length === 0) return undefined;\r\n\r\n const top = this.data[0];\r\n const bottom = this.data.pop();\r\n\r\n if (--this.length > 0) {\r\n this.data[0] = bottom;\r\n this._down(0);\r\n }\r\n\r\n return top;\r\n }\r\n\r\n peek() {\r\n return this.data[0];\r\n }\r\n\r\n _up(pos) {\r\n const {data, compare} = this;\r\n const item = data[pos];\r\n\r\n while (pos > 0) {\r\n const parent = (pos - 1) >> 1;\r\n const current = data[parent];\r\n if (compare(item, current) >= 0) break;\r\n data[pos] = current;\r\n pos = parent;\r\n }\r\n\r\n data[pos] = item;\r\n }\r\n\r\n _down(pos) {\r\n const {data, compare} = this;\r\n const halfLength = this.length >> 1;\r\n const item = data[pos];\r\n\r\n while (pos < halfLength) {\r\n let bestChild = (pos << 1) + 1; // initially it is the left child\r\n const right = bestChild + 1;\r\n\r\n if (right < this.length && compare(data[right], data[bestChild]) < 0) {\r\n bestChild = right;\r\n }\r\n if (compare(data[bestChild], item) >= 0) break;\r\n\r\n data[pos] = data[bestChild];\r\n pos = bestChild;\r\n }\r\n\r\n data[pos] = item;\r\n }\r\n}\r\n\r\nfunction defaultCompare(a, b) {\r\n return a < b ? -1 : a > b ? 1 : 0;\r\n}\r\n","/**\r\n * ISC License.\r\n *\r\n * Copyright (c) 2016, Vladimir Agafonkin\r\n *\r\n * Permission to use, copy, modify, and/or distribute this software for any purpose\r\n * with or without fee is hereby granted, provided that the above copyright notice\r\n * and this permission notice appear in all copies.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\r\n * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\r\n * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\r\n * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\r\n * THIS SOFTWARE.\r\n */\r\n\r\nimport Queue from './tinyqueue.mjs';\r\n\r\nexport default function knn(tree, x, y, n, predicate, maxDistance) {\r\n var node = tree.data,\r\n result = [],\r\n toBBox = tree.toBBox,\r\n i, child, dist, candidate;\r\n\r\n var queue = new Queue(undefined, compareDist);\r\n\r\n while (node) {\r\n for (i = 0; i < node.children.length; i++) {\r\n child = node.children[i];\r\n dist = boxDist(x, y, node.leaf ? toBBox(child) : child);\r\n if (!maxDistance || dist <= maxDistance * maxDistance) {\r\n queue.push({\r\n node: child,\r\n isItem: node.leaf,\r\n dist: dist\r\n });\r\n }\r\n }\r\n\r\n while (queue.length && queue.peek().isItem) {\r\n candidate = queue.pop().node;\r\n if (!predicate || predicate(candidate))\r\n result.push(candidate);\r\n if (n && result.length === n) return result;\r\n }\r\n\r\n node = queue.pop();\r\n if (node) node = node.node;\r\n }\r\n\r\n return result;\r\n}\r\n\r\nfunction compareDist(a, b) {\r\n return a.dist - b.dist;\r\n}\r\n\r\nfunction boxDist(x, y, box) {\r\n var dx = axisDist(x, box.minX, box.maxX),\r\n dy = axisDist(y, box.minY, box.maxY);\r\n return dx * dx + dy * dy;\r\n}\r\n\r\nfunction axisDist(k, min, max) {\r\n return k < min ? min - k : k <= max ? 0 : k - max;\r\n}\r\n","import AcetateExtrudedPoint from \"./AcetateExtrudedPoint.mjs\";\r\n\r\n/**\r\n * @class AcetateSolidExtrusion\r\n * @inherits AcetateExtrudedPoint\r\n *\r\n * An `Acetate` for rendering solid colors on extrusions of point geometries;\r\n * this is common for `Pie`, `CircleFill` and `CircleStroke` symbols.\r\n */\r\n\r\nexport default class AcetateSolidExtrusion extends AcetateExtrudedPoint {\r\n\t/**\r\n\t * @constructor AcetateSolidExtrusion(target: Platina, opts?: AcetateSolidExtrusion Options)\r\n\t */\r\n\tconstructor(\r\n\t\ttarget,\r\n\t\t{\r\n\t\t\t/// @option feather: Number = 1.5\r\n\t\t\t/// The feather distance (in CSS pixels)\r\n\t\t\tfeather = 1.5,\r\n\r\n\t\t\t...opts\r\n\t\t} = {}\r\n\t) {\r\n\t\tsuper(target, { zIndex: 2500, opts });\r\n\r\n\t\tthis._attrs = new this.glii.InterleavedAttributes(\r\n\t\t\t{\r\n\t\t\t\tusage: this.glii.STATIC_DRAW,\r\n\t\t\t\tsize: 1,\r\n\t\t\t\tgrowFactor: 1.2,\r\n\t\t\t},\r\n\t\t\t[\r\n\t\t\t\t{\r\n\t\t\t\t\t// RGBA colour\r\n\t\t\t\t\tglslType: \"vec4\",\r\n\t\t\t\t\ttype: Uint8Array,\r\n\t\t\t\t\tnormalized: true,\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\t// Feathering value (extrusion distance) plus feather limit\r\n\t\t\t\t\t// (max absolute value of extrusion), as 1/256ths of CSS pixel\r\n\t\t\t\t\tglslType: \"vec2\",\r\n\t\t\t\t\ttype: Int16Array,\r\n\t\t\t\t\tnormalized: false,\r\n\t\t\t\t},\r\n\t\t\t]\r\n\t\t);\r\n\r\n\t\tthis.#feather = feather;\r\n\t}\r\n\r\n\t// Width of feathering, in pixels\r\n\t#feather = 0.5;\r\n\r\n\t/**\r\n\t * @property feather: Number\r\n\t * Read-only getter for the value given to the `feather` option at instantiation time.\r\n\t */\r\n\tget feather() {\r\n\t\treturn this.#feather;\r\n\t}\r\n\r\n\tglProgramDefinition() {\r\n\t\tconst opts = super.glProgramDefinition();\r\n\t\treturn {\r\n\t\t\t...opts,\r\n\t\t\tattributes: {\r\n\t\t\t\taColour: this._attrs.getBindableAttribute(0),\r\n\t\t\t\taFeather: this._attrs.getBindableAttribute(1),\r\n\t\t\t\t...opts.attributes,\r\n\t\t\t},\r\n\t\t\tuniforms: {\r\n\t\t\t\tuPixelSize: \"vec2\",\r\n\t\t\t\tuFeatherAmount: \"float\",\r\n\t\t\t\t...opts.uniforms,\r\n\t\t\t},\r\n\t\t\tvertexShaderMain: `\r\n\t\t\t\tvColour = aColour;\r\n\t\t\t\tvFeather = aFeather;\r\n\t\t\t\tgl_Position = vec4(\r\n\t\t\t\t\tvec3(aCoords, 1.0) * uTransformMatrix +\r\n\t\t\t\t\tvec3(aExtrude * uPixelSize, 0.0)\r\n\t\t\t\t\t, 1.0);\r\n\t\t\t`,\r\n\t\t\tvaryings: { vColour: \"vec4\", vFeather: \"vec2\" },\r\n\t\t\tfragmentShaderMain: `\r\n\t\t\t\tgl_FragColor = vColour;\r\n\t\t\t\tfloat alpha = smoothstep(\r\n\t\t\t\t\tvFeather.y,\r\n\t\t\t\t\tvFeather.y - uFeatherAmount,\r\n\t\t\t\t\tabs(vFeather.x)\r\n\t\t\t\t);\r\n\t\t\t\tgl_FragColor.a *= alpha;\r\n\t\t\t`,\r\n\t\t};\r\n\t}\r\n\r\n\tglIdProgramDefinition() {\r\n\t\tconst opts = super.glIdProgramDefinition();\r\n\t\treturn {\r\n\t\t\t...opts,\r\n\t\t\tfragmentShaderMain: `\r\n\t\t\t\tif (vColour.a > 0.0) {\r\n\t\t\t\t\t${opts.fragmentShaderMain};\r\n\t\t\t\t} else {\r\n\t\t\t\t\tdiscard;\r\n\t\t\t\t}\r\n\t\t\t`,\r\n\t\t};\r\n\t}\r\n\r\n\t_getStridedArrays(maxVtx, maxIdx) {\r\n\t\treturn [\r\n\t\t\t// Extrusion\r\n\t\t\tthis._extrusions.asStridedArray(maxVtx),\r\n\t\t\t// Colour\r\n\t\t\tthis._attrs.asStridedArray(0, maxVtx),\r\n\t\t\t// Feather\r\n\t\t\tthis._attrs.asStridedArray(1),\r\n\t\t\t// Triangle indices\r\n\t\t\tthis._indices.asTypedArray(maxIdx),\r\n\t\t\t// // Feather constant\r\n\t\t\t// this.#feather,\r\n\t\t];\r\n\t}\r\n\r\n\t_commitStridedArrays(baseVtx, vtxCount, baseIdx, idxCount) {\r\n\t\tthis._extrusions.commit(baseVtx, vtxCount);\r\n\t\tthis._attrs.commit(baseVtx, vtxCount);\r\n\t\tthis._indices.commit(baseIdx, idxCount);\r\n\t}\r\n\r\n\t// The map will call resize() on acetates when needed - besides redoing the\r\n\t// framebuffer with the new size, this needs to reset the uniform uPixelSize.\r\n\tresize(w, h) {\r\n\t\tsuper.resize(w, h);\r\n\t\tconst dpr2 = (devicePixelRatio ?? 1) * 2;\r\n\t\tthis._programs.setUniform(\"uPixelSize\", [dpr2 / w, dpr2 / h]);\r\n\t\t// \t\tthis._programs.setUniform(\"uFeatherAmount\", .5 * 256);\t// Half a pixel\r\n\t\tthis._programs.setUniform(\"uFeatherAmount\", this.#feather * 256);\r\n\t}\r\n}\r\n","import AcetateSolidExtrusion from \"../acetates/AcetateSolidExtrusion.mjs\";\r\nimport ExtrudedPoint from \"./ExtrudedPoint.mjs\";\r\nimport parseColour from \"../3rd-party/css-colour-parser.mjs\";\r\n\r\n/**\r\n * @class Callout\r\n * @inherits ExtrudedPoint\r\n * @relationship drawnOn AcetateSolidExtrusion\r\n *\r\n * A line segment symbol. One end of the segment is always placed at the symbol's\r\n * point geometry; the dimensions of the line are defined by the symbol's `offset`\r\n * (measured in CSS pixels).\r\n *\r\n * @example\r\n * ```js\r\n * new Callout([0, 0], {\r\n * \tcolour: \"red\",\r\n * \toffset: [40, 10],\r\n * \twidth: 4,\r\n * }).addTo(map);\r\n * ```\r\n */\r\n\r\nexport default class Callout extends ExtrudedPoint {\r\n\t/// @section Static properties\r\n\t/// @property Acetate: Prototype of AcetateSolidExtrusion\r\n\t// The `Acetate` class that draws this symbol.\r\n\tstatic Acetate = AcetateSolidExtrusion;\r\n\r\n\t#width;\r\n\t#colour;\r\n\r\n\t/**\r\n\t * @constructor Callout(geom: Geometry, opts?: Callout Options)\r\n\t */\r\n\tconstructor(\r\n\t\tgeom,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @option width: Number = 2; The line segment's width, in CSS pixels\r\n\t\t\t * @option colour: Colour = '#3388ff33'; The line segment's colour\r\n\t\t\t */\r\n\t\t\twidth = 2,\r\n\t\t\tcolour = \"#3388ff\",\r\n\t\t\t...opts\r\n\t\t}\r\n\t) {\r\n\t\tsuper(geom, opts);\r\n\r\n\t\tthis.#width = width;\r\n\t\tthis.#colour = parseColour(colour);\r\n\r\n\t\t// A `Callout` is just four vertices in two triangles. One pair of\r\n\t\t// vertices follows the `offset`, while the other doesn't.\r\n\r\n\t\tthis.attrLength = 4;\r\n\t\tthis.idxLength = 6;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Acetate interface\r\n\t * @method _setGlobalStrides(strideExtrusion: StridedTypedArray, strideColour: StridedTypedArray, strideFeather: StridedTypedArray, typedIdxs: TypedArray): undefined\r\n\t * Sets the appropriate values into the strided arrays, based on the\r\n\t * symbol's `attrBase` and `idxBase`.\r\n\t *\r\n\t * Receives the width of the feathering as a parameter, in pixels.\r\n\t */\r\n\t_setGlobalStrides(strideExtrusion, strideColour, strideFeather, typedIdxs) {\r\n\t\tconst [oX, oY] = this.offset;\r\n\t\tconst feather = this._inAcetate.feather;\r\n\r\n\t\t// Calculate components of unit vector perpendicular to the offset,\r\n\t\t// create a half-width-length vector from that.\r\n\t\tconst l = Math.sqrt(oX * oX + oY * oY);\r\n\t\tconst w = (this.#width + feather) / 2;\r\n\t\tconst f = w * 256; // Feather max\r\n\t\tconst eX = (w * oX) / l;\r\n\t\tconst eY = (w * oY) / l;\r\n\r\n\t\t// prettier-ignore\r\n\t\tstrideExtrusion.set([\r\n\t\t\t+eY, -eX,\r\n\t\t\t-eY, eX,\r\n\t\t\toX + eY, oY - eX,\r\n\t\t\toX - eY, oY + eX\r\n\t\t], this.attrBase);\r\n\r\n\t\tconst vtx = this.attrBase;\r\n\t\tstrideColour?.set(this.#colour, vtx);\r\n\t\tstrideFeather?.set([f, f], vtx);\r\n\t\tstrideColour?.set(this.#colour, vtx + 1);\r\n\t\tstrideFeather?.set([-f, f], vtx + 1);\r\n\t\tstrideColour?.set(this.#colour, vtx + 2);\r\n\t\tstrideFeather?.set([f, f], vtx + 2);\r\n\t\tstrideColour?.set(this.#colour, vtx + 3);\r\n\t\tstrideFeather?.set([-f, f], vtx + 3);\r\n\r\n\t\t// prettier-ignore\r\n\t\ttypedIdxs?.set([\r\n\t\t\tvtx+ 0, vtx+ 1, vtx+ 2,\r\n\t\t\tvtx+ 1, vtx+ 2, vtx+ 3\r\n\t\t], this.idxBase);\r\n\t}\r\n\r\n\t_setStridedExtrusion(strideExtrusion) {\r\n\t\tthis._setGlobalStrides(\r\n\t\t\tstrideExtrusion,\r\n\t\t\tundefined,\r\n\t\t\tundefined,\r\n\t\t\tundefined,\r\n\t\t\tthis._inAcetate?.feather\r\n\t\t);\r\n\t}\r\n}\r\n","import GleoSymbol from \"./Symbol.mjs\";\r\nimport Callout from \"./Callout.mjs\";\r\n// import ExtrudedPoint from \"./ExtrudedPoint.mjs\";\r\n\r\n/**\r\n * @class Spider\r\n * @inherits GleoSymbol\r\n * @relationship compositionOf GleoSymbol, 0..1, 0..n\r\n *\r\n * Similar to `MultiSymbol`, a `Spider` is a logical grouping of `GleoSymbol`s.\r\n *\r\n * A `Spider` has *two* sets of symbols: one collapsed, and one expanded; and\r\n * at any given time it will be displayed as either.\r\n *\r\n * When `click`ing on any of the collapsed symbols, they will be replaced by\r\n * the expanded ones. Clicking on the map will switch to the collapsed ones again.\r\n *\r\n * In addition, symbols in the expanded set will be automatically offset, and\r\n * `Callout`s will be added - these are the \"legs\" of the `Spider`.\r\n *\r\n */\r\n\r\nconst τ = Math.PI * 2; // Tau\r\n// const halfπ = Math.PI / 2;\r\n\r\nexport default class Spider extends GleoSymbol {\r\n\t#collapsed = [];\r\n\t#expanded = [];\r\n\t#callouts = [];\r\n\t#expandedState = false;\r\n\r\n\t#boundOnMapClick;\r\n\t#target;\r\n\t#calloutOptions = {};\r\n\t#calloutLength = 0;\r\n\r\n\t/**\r\n\t * @constructor Spider(collapsedSymbols: Array of GleoSymbol, expandedSymbols: Array of GleoSymbol, opts?: Spider Options)\r\n\t */\r\n\tconstructor(\r\n\t\tcollapsed,\r\n\t\texpanded,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @option width: Number = undefined\r\n\t\t\t * The width of the `Callout`s. If not specified, the `Callout` default is used.\r\n\t\t\t * @option colour: Colour = undefined\r\n\t\t\t * The colour of the `Callout`s. If not specified, the `Callout` default is used.\r\n\t\t\t * @option length: Number = 60\r\n\t\t\t * The length of the `Callout`s, in CSS pixels.\r\n\t\t\t * @option expandAnimationDuration\r\n\t\t\t * The animation of the leg expansion animation, in milliseconds. Set to\r\n\t\t\t * zero to disable.\r\n\t\t\t */\r\n\t\t\twidth = undefined,\r\n\t\t\tcolour = undefined,\r\n\t\t\tlength = 60,\r\n\t\t\texpandAnimationDuration = 500,\r\n\t\t} = {}\r\n\t) {\r\n\t\tsuper();\r\n\t\tthis.#collapsed = collapsed;\r\n\t\t// this.#expanded = expanded;\r\n\t\tthis.#boundOnMapClick = this.collapse.bind(this);\r\n\t\tconst onCollapseClick = this.expand.bind(this);\r\n\r\n\t\tthis.#collapsed.forEach((s) => {\r\n\t\t\ts.on(\"click\", onCollapseClick);\r\n\t\t\ts.cursor = \"pointer\";\r\n\t\t});\r\n\r\n\t\tthis.#expanded = expanded.filter((s) => !!s.geometry);\r\n\r\n\t\tthis.#calloutOptions = { width, colour };\r\n\t\tthis.#expandAnimationDuration = expandAnimationDuration;\r\n\t\tthis.#calloutLength = length;\r\n\t}\r\n\r\n\taddTo(target) {\r\n\t\tif (!target.crs) {\r\n\t\t\tconsole.warn(\"Cannot add a spider unless the target platina has a known CRS\");\r\n\t\t}\r\n\r\n\t\t// Calculate simplistic centroid of expanded symbols\r\n\t\tlet [x, y] = [0, 0];\r\n\t\tthis.#expanded.forEach((ext, i) => {\r\n\t\t\tconst geom = ext.geometry.toCRS(target.crs);\r\n\t\t\tx += geom.coords[0];\r\n\t\t\ty += geom.coords[1];\r\n\t\t});\r\n\t\tconst l = this.#expanded.length;\r\n\t\tx /= l;\r\n\t\ty /= l;\r\n\r\n\t\t// Order expanded symbols by their angle relative to the spider's centroid\r\n\t\t// - Wrap symbols in a data structure\r\n\t\t// - Calculate delta to centroid and store its arcTan on the data structure\r\n\t\t// - Sort\r\n\t\t// - Unwrap\r\n\t\tconst items = this.#expanded\r\n\t\t\t.map((s) => {\r\n\t\t\t\tconst geom = s.geometry.toCRS(target.crs);\r\n\t\t\t\tconst Δx = geom.coords[0] - x;\r\n\t\t\t\tconst Δy = geom.coords[1] - y;\r\n\t\t\t\tconst θ = Math.atan2(Δx, Δy);\r\n\t\t\t\treturn {\r\n\t\t\t\t\tsymbol: s,\r\n\t\t\t\t\tθ: θ >= 0 ? θ : θ + τ,\r\n\t\t\t\t};\r\n\t\t\t})\r\n\t\t\t.sort((a, b) => a.θ - b.θ);\r\n\r\n\t\tconst Δ = τ / l;\r\n\r\n\t\tlet bias = (this.#bias = items.reduce((b, item, i) => b + item.θ - Δ * i, 0) / l);\r\n\r\n\t\tthis.#expanded = items.map((w) => w.symbol);\r\n\r\n\t\tthis.#callouts = this.#expanded.map((ext, i) => {\r\n\t\t\tconst θ = i * Δ + bias;\r\n\t\t\text.offset = [\r\n\t\t\t\tthis.#calloutLength * Math.sin(θ),\r\n\t\t\t\tthis.#calloutLength * Math.cos(θ),\r\n\t\t\t];\r\n\t\t\treturn new Callout(ext.geom, { ...this.#calloutOptions, offset: ext.offset });\r\n\t\t});\r\n\r\n\t\tif (this.#expandedState) {\r\n\t\t\ttarget.multiAdd(this.#expanded);\r\n\t\t\ttarget.multiAdd(this.#callouts);\r\n\t\t\ttarget.once(\"click\", this.#boundOnMapClick);\r\n\t\t} else {\r\n\t\t\ttarget.multiAdd(this.#collapsed);\r\n\t\t}\r\n\t\tthis.#target = target;\r\n\t\treturn this;\r\n\t}\r\n\r\n\tremove() {\r\n\t\tthis.#expandedState\r\n\t\t\t? (this.#target.multiRemove(this.#expanded),\r\n\t\t\t this.#target.multiRemove(this.#callouts))\r\n\t\t\t: this.#target.multiRemove(this.#collapsed);\r\n\t\tthis.#target.off(\"click\", this.#boundOnMapClick);\r\n\t\tthis.#target = undefined;\r\n\t\treturn this;\r\n\t}\r\n\r\n\tsetGeometry(geom) {\r\n\t\tthis.geom = geom;\r\n\t\tthis.#expanded.forEach((s) => s.setGeometry(geom));\r\n\t\tthis.#callouts.forEach((s) => s.setGeometry(geom));\r\n\t\tthis.#collapsed.forEach((s) => s.setGeometry(geom));\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Lifetime methods\r\n\t * @method expand(): this\r\n\t * Displays the \"expanded\" set of symbols, and removes the \"collapsed\" ones.\r\n\t */\r\n\texpand(ev) {\r\n\t\tif (this.#expandedState) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tthis.#expandedState = true;\r\n\t\tif (this.#target) {\r\n\t\t\tthis.#target.multiRemove(this.#collapsed);\r\n\t\t\tthis.#target.multiAdd(this.#expanded);\r\n\t\t\tthis.#target.multiAdd(this.#callouts);\r\n\t\t\tthis.#target.on(\"click\", this.#boundOnMapClick);\r\n\r\n\t\t\tif (this.#expandAnimationDuration > 0) {\r\n\t\t\t\tthis.#expandStartTimestamp = performance.now();\r\n\t\t\t\tthis.#expandFrame();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (ev) {\r\n\t\t\tev.stopPropagation();\r\n\t\t}\r\n\t\t/// @event expand: CustomEvent\r\n\t\t/// Fired when the spider expands for any reason\r\n\t\tthis.fire(\"expand\");\r\n\t\treturn this;\r\n\t}\r\n\r\n\t#expandStartTimestamp;\r\n\t#expandAnimationDuration;\r\n\t#bias;\r\n\t#expandFrame() {\r\n\t\tconst elapsed = Math.min(\r\n\t\t\t1,\r\n\t\t\t(performance.now() - this.#expandStartTimestamp) /\r\n\t\t\t\tthis.#expandAnimationDuration\r\n\t\t);\r\n\r\n\t\tconst l = this.#expanded.length;\r\n\t\tconst Δ = τ / l;\r\n\r\n\t\tthis.#callouts.forEach((callout, i) => {\r\n\t\t\tconst θ = i * Δ + this.#bias;\r\n\t\t\tthis.#expanded[i].offset = callout.offset = [\r\n\t\t\t\tthis.#calloutLength * Math.sin(θ) * elapsed,\r\n\t\t\t\tthis.#calloutLength * Math.cos(θ) * elapsed,\r\n\t\t\t];\r\n\t\t});\r\n\r\n\t\tif (elapsed < 1) {\r\n\t\t\trequestAnimationFrame(this.#expandFrame.bind(this));\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @method collapse(): this\r\n\t * Displays the \"collapsed\" set of symbols, and removes the \"expanded\" ones.\r\n\t */\r\n\tcollapse() {\r\n\t\tif (!this.#expandedState) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tthis.#expandedState = false;\r\n\t\tif (this.#target) {\r\n\t\t\tthis.#target.multiAdd(this.#collapsed);\r\n\t\t\tthis.#target.multiRemove(this.#expanded);\r\n\t\t\tthis.#target.multiRemove(this.#callouts);\r\n\t\t\tthis.#target.off(\"click\", this.#boundOnMapClick);\r\n\t\t}\r\n\t\t/// @event collapse: CustomEvent\r\n\t\t/// Fired when the spider collapses for any reason\r\n\t\tthis.fire(\"collapse\");\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method toggle(): this\r\n\t */\r\n\ttoggle() {\r\n\t\tif ((this.#expandedState = !this.#expandedState)) {\r\n\t\t\treturn this.expand();\r\n\t\t} else {\r\n\t\t\treturn this.collapse();\r\n\t\t}\r\n\t}\r\n}\r\n","import GleoSymbol from \"./Symbol.mjs\";\r\nimport ExpandBox from \"../geometry/ExpandBox.mjs\";\r\n\r\n/**\r\n * @class MultiSymbol\r\n * @inherits GleoSymbol\r\n *\r\n * @relationship compositionOf GleoSymbol, 0..1, 0..n\r\n *\r\n * A logical grouping of `GleoSymbol`s, to ease the task of managing them at once.\r\n *\r\n * Adding/removing it from a map, will add/remove all of the component symbols at once.\r\n * Idem for (re-)setting its geometry. Idem for pointer events: events\r\n * defined for the `MultiSymbol` will be trigger on any of its components.\r\n *\r\n * This is meant for static sets of symbols which represent the same geographical\r\n * feature, and share the same geometry (or close geometries). Once created,\r\n * no new symbols can be added to a `MultiSymbol`.\r\n *\r\n * For a counterpart where symbols can be added/removed, see the `SymbolGroup` loader.\r\n */\r\n\r\n/// TODO: Offer an iterator, **if** needed.\r\n\r\nexport default class MultiSymbol extends GleoSymbol {\r\n\t#symbols = [];\r\n\r\n\t/**\r\n\t * @constructor MultiSymbol(syms: Array of GleoSymbol)\r\n\t */\r\n\tconstructor(syms) {\r\n\t\tsuper();\r\n\t\tthis.geometry = syms[0]?.geometry;\r\n\t\tthis.#symbols = syms;\r\n\t\tsyms.forEach((s) => s._eventParents.push(this));\r\n\t}\r\n\r\n\tget symbols() {\r\n\t\treturn this.#symbols;\r\n\t}\r\n\r\n\taddTo(target) {\r\n\t\ttarget.multiAdd(this.#symbols);\r\n\t\treturn this;\r\n\t}\r\n\r\n\tremove() {\r\n\t\t// TODO: Bin into similar acetates, call MultiRemove. Low priority.\r\n\t\tthis.#symbols.forEach((s) => s.remove());\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @property bbox\r\n\t * Returns a bounding box which covers all the geometries of all component\r\n\t * symbols.\r\n\t */\r\n\tget bbox() {\r\n\t\tif (!this.#bbox) {\r\n\t\t\tthis.#bbox = new ExpandBox();\r\n\t\t\tthis.#symbols.forEach((s) => {\r\n\t\t\t\tthis.#bbox.expandGeometry(s.geometry.toCRS(this.geometry.crs));\r\n\t\t\t});\r\n\t\t}\r\n\t\treturn this.#bbox;\r\n\t}\r\n\t#bbox;\r\n\r\n\tget geometry() {\r\n\t\treturn super.geometry;\r\n\t}\r\n\tset geometry(geom) {\r\n\t\tsuper.geometry = geom;\r\n\t\tthis.#symbols?.forEach((s) => (s.geometry = geom));\r\n\t}\r\n\r\n\tset cursor(c) {\r\n\t\tthis.#symbols.forEach((s) => (s.cursor = c));\r\n\t}\r\n\tget cursor() {\r\n\t\treturn this.#symbols[0].cursor;\r\n\t}\r\n\r\n\tisActive() {\r\n\t\treturn this.#symbols.some((s) => s.isActive());\r\n\t}\r\n}\r\n","import AcetateSolidExtrusion from \"../acetates/AcetateSolidExtrusion.mjs\";\r\nimport ExtrudedPoint from \"./ExtrudedPoint.mjs\";\r\nimport parseColour from \"../3rd-party/css-colour-parser.mjs\";\r\n\r\n/**\r\n * @class CircleStroke\r\n * @inherits ExtrudedPoint\r\n * @relationship drawnOn AcetateSolidExtrusion\r\n *\r\n * The \"stroke\" part of a circle symbol - a line of constant width\r\n * (in CSS pixels), going around the circumference of a circle with its center in\r\n * the given `Geometry`.\r\n *\r\n * @example\r\n * ```js\r\n * new CircleStroke([0, 0], {\r\n * \tcolour: \"red\",\r\n * \tradius: 40,\r\n * \twidth: 3\r\n * }).addTo(map);\r\n * ```\r\n */\r\n\r\nexport default class CircleStroke extends ExtrudedPoint {\r\n\t/// @section Static properties\r\n\t/// @property Acetate: Prototype of AcetateSolidExtrusion\r\n\t// The `Acetate` class that draws this symbol.\r\n\tstatic Acetate = AcetateSolidExtrusion;\r\n\r\n\t#radius;\r\n\t#colour;\r\n\t#width;\r\n\r\n\t/**\r\n\t * @constructor CircleStroke(geom: Geometry, opts?: CircleStroke Options)\r\n\t */\r\n\tconstructor(\r\n\t\tgeom,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @section\r\n\t\t\t * @aka CircleStroke Options\r\n\t\t\t * @option radius: Number = 20; Radius of the circle, in CSS pixels\r\n\t\t\t * @option colour: Colour = '#3388ff'; The stroke colour\r\n\t\t\t * @option width: Number = 2; The width of the stroke, in CSS pixels\r\n\t\t\t */\r\n\t\t\tradius = 20,\r\n\t\t\tcolour = \"#3388ff\",\r\n\t\t\twidth = 2,\r\n\r\n\t\t\t...opts\r\n\t\t} = {}\r\n\t) {\r\n\t\tsuper(geom, opts);\r\n\r\n\t\tthis.#radius = radius;\r\n\t\tthis.#colour = this.constructor._parseColour(colour);\r\n\t\tthis.#width = width;\r\n\r\n\t\t// Length of circumference\r\n\t\tconst length = Math.PI * 2 * this.#radius;\r\n\t\t// Divide in triangles so there's a triangle per...\r\n\t\t// 6 pixels of circumference length. That should be enough.\r\n\t\tthis.steps = Math.max(7, Math.ceil(length / 6));\r\n\r\n\t\tthis.attrLength = this.steps * 2;\r\n\t\tthis.idxLength = this.steps * 6;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Acetate interface\r\n\t * @method _setGlobalStrides(strideExtrusion: StridedTypedArray, strideColour: StridedTypedArray, strideFeather: StridedTypedArray, typedIdxs: TypedArray): undefined\r\n\t * Sets the appropriate values into the strided arrays, based on the\r\n\t * symbol's `attrBase` and `idxBase`.\r\n\t *\r\n\t * Receives the width of the feathering as a parameter, in pixels.\r\n\t */\r\n\t_setGlobalStrides(strideExtrusion, strideColour, strideFeather, typedIdxs) {\r\n\t\t// Radian increment per step\r\n\t\tconst ɛ = (Math.PI * 2) / this.steps;\r\n\r\n\t\tconst ρ = this.#radius;\r\n\t\tconst w = (this.#width + this._inAcetate.feather) / 2;\r\n\t\tconst f = w * 256; // Feather max\r\n\t\tconst [Δx, Δy] = this.offset;\r\n\r\n\t\tlet θ = 0;\r\n\t\tconst steps2 = this.steps * 2;\r\n\t\tlet vtx = this.attrBase;\r\n\t\tlet idx = this.idxBase;\r\n\t\tfor (let i = 0; i < steps2; i += 2) {\r\n\t\t\tconst sinθ = Math.sin(θ);\r\n\t\t\tconst cosθ = Math.cos(θ);\r\n\r\n\t\t\t// Two vertices per step: inner and outer\r\n\t\t\tstrideExtrusion.set(\r\n\t\t\t\t[\r\n\t\t\t\t\tsinθ * (ρ - w) + Δx,\r\n\t\t\t\t\tcosθ * (ρ - w) + Δy,\r\n\t\t\t\t\tsinθ * (ρ + w) + Δx,\r\n\t\t\t\t\tcosθ * (ρ + w) + Δy,\r\n\t\t\t\t],\r\n\t\t\t\tvtx\r\n\t\t\t);\r\n\r\n\t\t\tstrideColour?.set(this.#colour, vtx);\r\n\t\t\tstrideFeather?.set([-f, f], vtx);\r\n\t\t\tstrideColour?.set(this.#colour, vtx + 1);\r\n\t\t\tstrideFeather?.set([+f, f], vtx + 1);\r\n\r\n\t\t\t// Two triangles per step, forming a quad to the vertices of the\r\n\t\t\t// next step.\r\n\t\t\tif (i !== steps2 - 2) {\r\n\t\t\t\t// prettier-ignore\r\n\t\t\t\ttypedIdxs?.set([\r\n\t\t\t\t\tvtx+0, vtx+1, vtx+2,\r\n\t\t\t\t\tvtx+2, vtx+1, vtx+3\r\n\t\t\t\t], idx);\r\n\t\t\t} else {\r\n\t\t\t\t// prettier-ignore\r\n\t\t\t\ttypedIdxs?.set([\r\n\t\t\t\t\tvtx, vtx+1, this.attrBase,\r\n\t\t\t\t\tthis.attrBase, vtx+1, this.attrBase + 1\r\n\t\t\t\t], idx);\r\n\t\t\t}\r\n\r\n\t\t\tθ += ɛ;\r\n\t\t\tvtx += 2;\r\n\t\t\tidx += 6;\r\n\t\t}\r\n\t}\r\n\t_setStridedExtrusion(strideExtrusion) {\r\n\t\tthis._setGlobalStrides(strideExtrusion);\r\n\t}\r\n\r\n\t// Can be overriden by subclasses or the `intensify` decorator\r\n\tstatic _parseColour = parseColour;\r\n}\r\n","import AcetateSolidExtrusion from \"../acetates/AcetateSolidExtrusion.mjs\";\r\nimport ExtrudedPoint from \"./ExtrudedPoint.mjs\";\r\nimport parseColour from \"../3rd-party/css-colour-parser.mjs\";\r\n\r\n/**\r\n * @class CircleFill\r\n * @inherits ExtrudedPoint\r\n * @relationship drawnOn AcetateSolidExtrusion\r\n *\r\n * The \"fill\" part of a circle symbol - a circle of constant radius\r\n * (measured in CSS pixels), spawning from a point `Geometry` in the circle center.\r\n *\r\n * @example\r\n * ```js\r\n * new CircleFill([0, 0], {\r\n * \tcolour: \"red\",\r\n * \tradius: 40\r\n * }).addTo(map);\r\n * ```\r\n */\r\n\r\nexport default class CircleFill extends ExtrudedPoint {\r\n\t/// @section Static properties\r\n\t/// @property Acetate: Prototype of AcetateSolidExtrusion\r\n\t// The `Acetate` class that draws this symbol.\r\n\tstatic Acetate = AcetateSolidExtrusion;\r\n\r\n\t#radius;\r\n\t#colour;\r\n\r\n\t/**\r\n\t * @constructor CircleFill(geom: Geometry, opts?: CircleFill Options)\r\n\t */\r\n\tconstructor(\r\n\t\tgeom,\r\n\t\t{\r\n\t\t\t/**\r\n\t\t\t * @section\r\n\t\t\t * @aka CircleFill Options\r\n\t\t\t * @option radius: Number = 20; Radius of the circle, in CSS pixels\r\n\t\t\t * @option colour: Colour = '#3388ff33'; The fill colour\r\n\t\t\t */\r\n\t\t\tradius = 20,\r\n\r\n\t\t\tcolour = \"#3388ff33\",\r\n\r\n\t\t\t...opts\r\n\t\t} = {}\r\n\t) {\r\n\t\tsuper(geom, opts);\r\n\r\n\t\tthis.#radius = radius;\r\n\t\tthis.#colour = this.constructor._parseColour(colour);\r\n\r\n\t\t// Length of circumference\r\n\t\tconst length = Math.PI * 2 * this.#radius;\r\n\t\t// Divide in triangles so there's a triangle per...\r\n\t\t// 6 pixels of circumference length. That should be enough.\r\n\t\tthis.steps = Math.max(7, Math.ceil(length / 6));\r\n\r\n\t\tthis.attrLength = this.steps + 1;\r\n\t\tthis.idxLength = this.steps * 3;\r\n\t}\r\n\r\n\t/**\r\n\t * @property colour\r\n\t * The colour of this `CircleFill`. Can be updated.\r\n\t */\r\n\tget colour() {\r\n\t\treturn this.#colour;\r\n\t}\r\n\r\n\tset colour(c) {\r\n\t\tthis.#colour = parseColour(c);\r\n\t\tif (!this._inAcetate) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst stridedArrays = this._inAcetate._getStridedArrays(\r\n\t\t\tthis.attrBase + this.attrLength,\r\n\t\t\tthis.idxBase + this.idxLength\r\n\t\t);\r\n\t\tthis._setGlobalStrides(...stridedArrays);\r\n\t\tthis._inAcetate._commitStridedArrays(\r\n\t\t\tthis.attrBase,\r\n\t\t\tthis.attrLength,\r\n\t\t\tthis.idxBase,\r\n\t\t\tthis.idxLength\r\n\t\t);\r\n\t\tthis._inAcetate.dirty = true;\r\n\t}\r\n\r\n\t/**\r\n\t * @section Acetate interface\r\n\t * @method _setGlobalStrides(strideExtrusion: StridedTypedArray, strideColour: StridedTypedArray, strideFeather: StridedTypedArray, typedIdxs: TypedArray): undefined\r\n\t * Sets the appropriate values into the strided arrays, based on the\r\n\t * symbol's `attrBase` and `idxBase`.\r\n\t *\r\n\t * Receives the width of the feathering as a parameter, in pixels.\r\n\t */\r\n\t_setGlobalStrides(strideExtrusion, strideColour, strideFeather, typedIdxs) {\r\n\t\tconst feather = this._inAcetate.feather;\r\n\r\n\t\t// Radian increment per step\r\n\t\tconst ɛ = (Math.PI * 2) / this.steps;\r\n\r\n\t\tconst ρ = this.#radius + feather / 2;\r\n\t\tconst f = ρ * 256; // Feather max\r\n\t\tconst [Δx, Δy] = this.offset;\r\n\r\n\t\t// Attributes start with the center point\r\n\t\tstrideExtrusion.set([Δx, Δy], this.attrBase);\r\n\t\tstrideColour?.set(this.#colour, this.attrBase);\r\n\t\tstrideFeather?.set([0, f], this.attrBase);\r\n\r\n\t\tlet θ = 0;\r\n\t\tlet vtx = this.attrBase + 1;\r\n\t\tlet idx = this.idxBase;\r\n\t\tfor (let i = 0; i < this.steps; i++) {\r\n\t\t\tstrideExtrusion.set([Math.sin(θ) * ρ + Δx, Math.cos(θ) * ρ + Δy], vtx);\r\n\t\t\tstrideColour?.set(this.#colour, vtx);\r\n\t\t\tstrideFeather?.set([f, f], vtx);\r\n\r\n\t\t\t// Vertices of the i-th triangle are: center, current, next\r\n\t\t\tif (i !== this.steps - 1) {\r\n\t\t\t\ttypedIdxs?.set([this.attrBase, vtx, vtx + 1], idx);\r\n\t\t\t} else {\r\n\t\t\t\ttypedIdxs?.set([this.attrBase, vtx, this.attrBase + 1], idx);\r\n\t\t\t}\r\n\r\n\t\t\tθ += ɛ;\r\n\t\t\tvtx++;\r\n\t\t\tidx += 3;\r\n\t\t}\r\n\t}\r\n\r\n\t_setStridedExtrusion(strideExtrusion) {\r\n\t\tthis._setGlobalStrides(strideExtrusion);\r\n\t}\r\n\r\n\t// Can be overriden by subclasses or the `intensify` decorator\r\n\tstatic _parseColour = parseColour;\r\n}\r\n","import Sprite from \"./Sprite.mjs\";\r\n\r\nlet textWorker;\r\nlet workId = 0;\r\nlet textDrawer, textDrawerCanvas;\r\nlet canUseExternalWorker;\r\ntry {\r\n\t// Use the external-file web worker for text rendering only if:\r\n\t// - OffscreenCanvas is a thing, and...\r\n\tconst a = globalThis?.OffscreenCanvas;\r\n\t// - ...the name of this module is TextLabel.mjs (which won't happen if it's\r\n\t// been bundled)\r\n\tconst b = import.meta?.url?.match(/TextLabel\\.mjs/);\r\n\tcanUseExternalWorker = !!a && !!b;\r\n} catch (ex) {\r\n\tcanUseExternalWorker = false;\r\n}\r\n\r\nif (canUseExternalWorker) {\r\n\tconst ownURL = import.meta.url;\r\n\ttextWorker = new Worker(ownURL.replace(/TextLabel.mjs$/, \"text/textWorker.js\"));\r\n} else {\r\n\tif (globalThis?.OffscreenCanvas) {\r\n\t\ttry {\r\n\t\t\t// If external workers are not available but offscreen canvases are,\r\n\t\t\t// then spawn a blob worker. The code for the blob is copy-pasted\r\n\t\t\t// from the external worker code.\r\n\t\t\tconst blob = new Blob(\r\n\t\t\t\t[\r\n\t\t\t\t\t`\r\nconst textDrawerCanvas = new OffscreenCanvas(16, 16);\r\nconst textDrawer = textDrawerCanvas.getContext(\"2d\", { willReadFrequently: true });\r\n\r\nlet devicePixelRatio = 1;\r\n\r\nonmessage = function onmessage({\r\n\tdata: {\r\n\t\tcommand, // either \"render\" or \"loadFontFace\"\r\n\t\tworkId,\r\n\r\n\t\tstr,\r\n\t\tfont,\r\n\t\tcolour,\r\n\t\talign,\r\n\t\tbaseline,\r\n\t\toutlineWidth,\r\n\t\toutlineColour,\r\n\t\t// cache = false,\r\n\r\n\t\tfamily,\r\n\t\tsource,\r\n\t\tdescriptors,\r\n\r\n\t\t...data\r\n\t},\r\n\t...msg\r\n}) {\r\n\t//console.log('Worker: Message received from main script', workId, str);\r\n\r\n\tif (command === \"render\") {\r\n\t\tfont = font.replace(/\\d+/, (n) => n * devicePixelRatio);\r\n\r\n\t\ttextDrawer.font = font;\r\n\t\ttextDrawer.textAlign = align;\r\n\t\ttextDrawer.textBaseline = baseline;\r\n\r\n\t\tlet metrics = textDrawer.measureText(str);\r\n\r\n\t\tconst left = metrics.actualBoundingBoxLeft + outlineWidth + 1;\r\n\t\tconst right = metrics.actualBoundingBoxRight + outlineWidth + 1;\r\n\t\tconst up = metrics.actualBoundingBoxAscent + outlineWidth + 1;\r\n\t\tconst down = metrics.actualBoundingBoxDescent + outlineWidth + 1;\r\n\r\n\t\tconst width = Math.ceil(left + right) + 1;\r\n\t\tconst height = Math.ceil((textDrawerCanvas.height = up + down)) + 1;\r\n\r\n\t\tif (textDrawerCanvas.width < width || textDrawerCanvas.height < height) {\r\n\t\t\ttextDrawerCanvas.width = Math.max(width, textDrawerCanvas.width);\r\n\t\t\ttextDrawerCanvas.height = Math.max(height, textDrawerCanvas.height);\r\n\t\t\ttextDrawer.font = font;\r\n\t\t\ttextDrawer.textAlign = align;\r\n\t\t\ttextDrawer.textBaseline = baseline;\r\n\t\t} else {\r\n\t\t\ttextDrawer.clearRect(0, 0, width, height);\r\n\t\t}\r\n\r\n\t\tif (outlineWidth > 0) {\r\n\t\t\ttextDrawer.lineWidth = devicePixelRatio * outlineWidth * 2;\r\n\t\t\ttextDrawer.strokeStyle = outlineColour;\r\n\t\t\ttextDrawer.strokeText(str, left, up);\r\n\t\t}\r\n\r\n\t\ttextDrawer.fillStyle = colour;\r\n\t\ttextDrawer.fillText(str, left, up);\r\n\r\n\t\tconst imageData = textDrawer.getImageData(0, 0, width, height);\r\n\r\n\t\tconst returnMsg = {\r\n\t\t\tworkId,\r\n\t\t\timageData,\r\n\t\t\tleft: left,\r\n\t\t\tup: up,\r\n\t\t\twidth: width,\r\n\t\t\theight: height,\r\n\t\t\tscale: 1 / devicePixelRatio,\r\n\t\t};\r\n\r\n\t\treturn postMessage(returnMsg);\r\n\t} else if (command === \"loadFontFace\") {\r\n\t\tconst font = new FontFace(family, source, descriptors);\r\n\t\tself.fonts.add(font);\r\n\t\tfont.load()\r\n\t\t\t.catch((ex) =>\r\n\t\t\t\tpostMessage({\r\n\t\t\t\t\tworkId,\r\n\t\t\t\t\terror: ex,\r\n\t\t\t\t})\r\n\t\t\t)\r\n\t\t\t.then(() => {\r\n\t\t\t\tconsole.log(self.fonts);\r\n\r\n\t\t\t\tpostMessage({\r\n\t\t\t\t\tworkId,\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t} else if (command === \"setDevicePixelRatio\") {\r\n\t\tdevicePixelRatio = data.devicePixelRatio;\r\n\t}\r\n};\r\n\t\t`,\r\n\t\t\t\t],\r\n\t\t\t\t{ type: \"text/javascript\" }\r\n\t\t\t);\r\n\r\n\t\t\ttextWorker = new Worker(window.URL.createObjectURL(blob));\r\n\t\t} catch (ex) {}\r\n\t}\r\n\r\n\ttry {\r\n\t\tif (!textWorker || !textWorker?.onmessage) {\r\n\t\t\ttextDrawerCanvas = document.createElement(\"canvas\");\r\n\t\t\ttextDrawerCanvas.width = 1;\r\n\t\t\ttextDrawerCanvas.height = 1;\r\n\t\t\ttextDrawer = textDrawerCanvas.getContext(\"2d\", { willReadFrequently: true });\r\n\r\n\t\t\t// document.body.appendChild(textDrawerCanvas);\r\n\t\t\t// textDrawerCanvas.style.border = \"2px solid blue\";\r\n\t\t}\r\n\t} catch (ex) {\r\n\t\tconsole.warn(\"Cannot use TextLabel on headless environments\");\r\n\t}\r\n}\r\n\r\nif (textWorker && textWorker?.postMessage) {\r\n\ttextWorker?.postMessage({\r\n\t\tcommand: \"setDevicePixelRatio\",\r\n\t\t// devicePixelRatio: .1,\r\n\t\tdevicePixelRatio: window.devicePixelRatio,\r\n\t});\r\n\r\n\twindow.addEventListener(\"resize\", () => {\r\n\t\ttextWorker.postMessage({\r\n\t\t\tcommand: \"setDevicePixelRatio\",\r\n\t\t\tdevicePixelRatio: window.devicePixelRatio,\r\n\t\t});\r\n\t});\r\n}\r\n\r\nconst imageCache = new Map();\r\n\r\n/**\r\n * @class TextLabel\r\n * @inherits Sprite\r\n * @relationship drawnOn AcetateSprite\r\n * @relationship compositionOf Bin, 1..1, 0..1\r\n *\r\n * A text label anchored to a point geometry.\r\n *\r\n * Internally treated like a `Sprite`, by rasterizing the text via 2D Canvas.\r\n *\r\n * @example\r\n * ```js\r\n * new TextLabel([0, 0], {\r\n * \tstr: \"Hello world!\",\r\n * }).addTo(map);\r\n * ```\r\n */\r\n\r\nexport default class TextLabel extends Sprite {\r\n\t/**\r\n\t * @constructor TextLabel(geom: Geometry, opts?: TextLabel Options)\r\n\t */\r\n\tconstructor(\r\n\t\tgeom,\r\n\t\t{\r\n\t\t\tstr,\r\n\t\t\tfont = \"16px Sans\",\r\n\t\t\tcolour,\r\n\t\t\tcolor,\r\n\t\t\talign = \"start\",\r\n\t\t\tbaseline = \"alphabetic\",\r\n\t\t\toutlineWidth = 0,\r\n\t\t\toutlineColour = \"white\",\r\n\t\t\tcache = false,\r\n\t\t\t...opts\r\n\t\t} = {}\r\n\t) {\r\n\t\t/**\r\n\t\t * @section\r\n\t\t * @aka TextLabel options\r\n\t\t * @option str: String\r\n\t\t * The text itself\r\n\t\t * @option font: String = \"16px Sans\"\r\n\t\t * A definition of a [CSS font](https://developer.mozilla.org/docs/Web/CSS/font)\r\n\t\t * @option colour: String = \"black\"\r\n\t\t * The CSS colour for the text fill (**not** a gleo `Colour`!).\r\n\t\t * @option align: String = \"start\"\r\n\t\t * Text alignment, as per [2D canvas' `textAlign`](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/textAlign).\r\n\t\t * @option baseline: String = \"alphabetic\"\r\n\t\t * Text baseline, as per [2D canvas' `textBaseline`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline).\r\n\t\t * @option outlineWidth: Number = 0\r\n\t\t * Size, in CSS pixels, of the text outline\r\n\t\t * @option outlineColour: String = \"white\"\r\n\t\t * The CSS colour for the text outline (**not** a gleo `Colour`!)\r\n\t\t * @option cache: Boolean = false\r\n\t\t * Whether to cache the rendered text for later use. Should be\r\n\t\t * set to `true` if the text label is expected to be removed/re-added,\r\n\t\t * or if several `TextLabel`s with the exact same data exist.\r\n\t\t */\r\n\r\n\t\tlet key;\r\n\t\tif (cache) {\r\n\t\t\tkey = JSON.stringify({\r\n\t\t\t\tstr,\r\n\t\t\t\tfont,\r\n\t\t\t\tcolour,\r\n\t\t\t\talign,\r\n\t\t\t\tbaseline,\r\n\t\t\t\toutlineWidth,\r\n\t\t\t\toutlineColour,\r\n\t\t\t});\r\n\t\t\tconst cached = imageCache.get(key);\r\n\t\t\tif (cached) {\r\n\t\t\t\treturn super(geom, {\r\n\t\t\t\t\timage: cached.imageData,\r\n\t\t\t\t\tspriteAnchor: [cached.left, cached.up],\r\n\t\t\t\t\tspriteSize: [cached.width, cached.height],\r\n\t\t\t\t\tspriteScale: cached.scale,\r\n\t\t\t\t\t...opts,\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (textWorker) {\r\n\t\t\tconst myWorkId = workId++;\r\n\r\n\t\t\ttextWorker.postMessage({\r\n\t\t\t\tcommand: \"render\",\r\n\t\t\t\tworkId: myWorkId,\r\n\t\t\t\tstr,\r\n\t\t\t\tfont,\r\n\t\t\t\tcolour: colour ?? color ?? \"black\",\r\n\t\t\t\talign,\r\n\t\t\t\tbaseline,\r\n\t\t\t\toutlineWidth,\r\n\t\t\t\toutlineColour,\r\n\t\t\t\tcache,\r\n\t\t\t\t...opts,\r\n\t\t\t});\r\n\r\n\t\t\tconst imageReady = new Promise((res, rej) => {\r\n\t\t\t\tfunction waitMessage(msg) {\r\n\t\t\t\t\tif (msg.data.workId === myWorkId) {\r\n\t\t\t\t\t\t//console.log(\"Done\", myWorkId, msg);\r\n\t\t\t\t\t\ttextWorker.removeEventListener(\"message\", waitMessage);\r\n\t\t\t\t\t\tres(msg.data);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\ttextWorker.addEventListener(\"message\", waitMessage);\r\n\t\t\t});\r\n\r\n\t\t\tsuper(geom, {\r\n\t\t\t\timage: imageReady.then((i) => {\r\n\t\t\t\t\tif (cache) {\r\n\t\t\t\t\t\timageCache.set(key, i);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis._anchor = [i.left, i.up];\r\n\t\t\t\t\tthis._spriteSize = [i.width, i.height];\r\n\t\t\t\t\tthis.spriteScale = i.scale;\r\n\r\n\t\t\t\t\treturn i.imageData;\r\n\t\t\t\t}),\r\n\t\t\t\tspriteAnchor: [0, 0],\r\n\t\t\t\tspriteSize: [16, 16],\r\n\t\t\t\t...opts,\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\tfont = font.replace(/\\d+/, (n) => n * window.devicePixelRatio);\r\n\r\n\t\t\ttextDrawer.font = font;\r\n\t\t\ttextDrawer.textAlign = align;\r\n\t\t\ttextDrawer.textBaseline = baseline;\r\n\r\n\t\t\tlet metrics = textDrawer.measureText(str);\r\n\r\n\t\t\tconst left = metrics.actualBoundingBoxLeft + outlineWidth + 1;\r\n\t\t\tconst right = metrics.actualBoundingBoxRight + outlineWidth + 1;\r\n\t\t\tconst up = metrics.actualBoundingBoxAscent + outlineWidth + 1;\r\n\t\t\tconst down = metrics.actualBoundingBoxDescent + outlineWidth + 1;\r\n\r\n\t\t\tconst width = Math.ceil(left + right) + 1;\r\n\t\t\tconst height = Math.ceil((textDrawerCanvas.height = up + down)) + 1;\r\n\r\n\t\t\tif (textDrawerCanvas.width < width || textDrawerCanvas.height < height) {\r\n\t\t\t\ttextDrawerCanvas.width = Math.max(width, textDrawerCanvas.width);\r\n\t\t\t\ttextDrawerCanvas.height = Math.max(height, textDrawerCanvas.height);\r\n\t\t\t\ttextDrawer.font = font;\r\n\t\t\t\ttextDrawer.textAlign = align;\r\n\t\t\t\ttextDrawer.textBaseline = baseline;\r\n\t\t\t} else {\r\n\t\t\t\ttextDrawer.clearRect(0, 0, width, height);\r\n\t\t\t}\r\n\r\n\t\t\tif (outlineWidth > 0) {\r\n\t\t\t\ttextDrawer.lineWidth = window.devicePixelRatio * outlineWidth * 2;\r\n\t\t\t\ttextDrawer.strokeStyle = outlineColour;\r\n\t\t\t\ttextDrawer.strokeText(str, left, up);\r\n\t\t\t}\r\n\r\n\t\t\ttextDrawer.fillStyle = colour ?? color ?? \"black\";\r\n\t\t\ttextDrawer.fillText(str, left, up);\r\n\r\n\t\t\tconst imageData = textDrawer.getImageData(0, 0, width, height);\r\n\t\t\tif (cache) {\r\n\t\t\t\timageCache.set(key, {\r\n\t\t\t\t\timageData,\r\n\t\t\t\t\tleft,\r\n\t\t\t\t\tup,\r\n\t\t\t\t\twidth,\r\n\t\t\t\t\theight,\r\n\t\t\t\t\tscale: 1 / window.devicePixelRatio,\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\tsuper(geom, {\r\n\t\t\t\timage: imageData,\r\n\t\t\t\tspriteAnchor: [left, up],\r\n\t\t\t\tspriteSize: [width, height],\r\n\t\t\t\tspriteScale: 1 / window.devicePixelRatio,\r\n\t\t\t\t...opts,\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * @function addFontFace(family: String, source: String, descriptors?: Object): Promise\r\n\t *\r\n\t * Registers a new font face (AKA typeface) for use with `TextLabel`.\r\n\t *\r\n\t * Note that for technical reasons (i.e. \"text is rendered inside a web worker\"),\r\n\t * typefaces defined in the document's CSS via `@font-face` are not available\r\n\t * to Gleo.\r\n\t *\r\n\t * The parameters to this static function are the same as the\r\n\t * [`FontFace` constructor](https://developer.mozilla.org/en-US/docs/Web/API/FontFace/FontFace).\r\n\t *\r\n\t * Beware: any relative URLs used in the `source` will be interpreted as\r\n\t * being relative to *the URL of the web worker code module*. Usage of\r\n\t * absolute URLs is therefore highly encouraged.\r\n\t *\r\n\t * Returns a `Promise` that resolves when the font face has been loaded.\r\n\t */\r\n\tstatic addFontFace(family, source, descriptors) {\r\n\t\tif (textWorker) {\r\n\t\t\tconst myWorkId = workId++;\r\n\r\n\t\t\tconst fontReady = new Promise((res, rej) => {\r\n\t\t\t\tfunction waitMessage(msg) {\r\n\t\t\t\t\tif (msg.data.workId === myWorkId) {\r\n\t\t\t\t\t\t//console.log(\"Done\", myWorkId, msg);\r\n\t\t\t\t\t\ttextWorker.removeEventListener(\"message\", waitMessage);\r\n\t\t\t\t\t\tif (msg.data.error) {\r\n\t\t\t\t\t\t\trej(msg.data.error);\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tres();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\ttextWorker.addEventListener(\"message\", waitMessage);\r\n\t\t\t});\r\n\r\n\t\t\ttextWorker.postMessage({\r\n\t\t\t\tcommand: \"loadFontFace\",\r\n\t\t\t\tworkId: myWorkId,\r\n\t\t\t\tfamily,\r\n\t\t\t\tsource,\r\n\t\t\t\tdescriptors,\r\n\t\t\t});\r\n\r\n\t\t\treturn fontReady;\r\n\t\t} else {\r\n\t\t\tconst font = new FontFace(family, source, descriptors);\r\n\t\t\tdocument.fonts.add(font);\r\n\t\t\treturn font.load();\r\n\t\t}\r\n\t}\r\n}\r\n","/**\r\n * Performs a segment intersection with modulo: intersects the segment a1-a2\r\n * with all occurences of segment b modulo m (for all i in Z, segment b1+im to b2+im)\r\n *\r\n * Assumes a2>a1, b2>b1, and either m>0 or m=Infinity\r\n */\r\nexport default function intersectSegments(a1, a2, b1, b2, m) {\r\n\tif (b2 - b1 >= m) {\r\n\t\t// Edge case: the b segment is larger than the modulo therefore\r\n\t\t// all its occurences spans the whole R, therefore the intersection is\r\n\t\t// the identity function.\r\n\t\treturn [[a1, a2]];\r\n\t}\r\n\r\n\tlet minShift, maxShift;\r\n\tlet modulo;\r\n\tif (isFinite(m)) {\r\n\t\t// How many times do we need to sum the modulo so that the end of the\r\n\t\t// b segment overlaps a?\r\n\t\tminShift = -Math.floor((b2 - a1) / m);\r\n\t\tmaxShift = Math.floor((a2 - b1) / m);\r\n\t\tmodulo = m;\r\n\t} else {\r\n\t\tminShift = maxShift = 0;\r\n\t\tmodulo = 0;\r\n\t}\r\n\r\n\tif (b1 + minShift * m > a2) {\r\n\t\t// The segments do not intersect\r\n\t\treturn [];\r\n\t}\r\n\r\n\tconst intersections = [];\r\n\tfor (let shift = minShift; shift <= maxShift; shift++) {\r\n\t\tconst offset = shift * modulo;\r\n\t\tintersections.push([Math.max(a1, b1 + offset), Math.min(a2, b2 + offset)]);\r\n\t}\r\n\treturn intersections;\r\n}\r\n","import intersectSegments from \"./intersectSegments.mjs\";\r\nimport ExpandBox from \"../../geometry/ExpandBox.mjs\";\r\n\r\n/**\r\n * Runs intersectSegments on both vertical and horizontal components of the bboxes\r\n * Returns an array of bboxes, which might be empty.\r\n */\r\nexport default function intersectBboxes(a, b, crs) {\r\n\tlet horizontal = intersectSegments(a.minX, a.maxX, b.minX, b.maxX, crs.wrapPeriodX);\r\n\tlet vertical = intersectSegments(a.minY, a.maxY, b.minY, b.maxY, crs.wrapPeriodY);\r\n\r\n\tlet boxes = horizontal\r\n\t\t.map(([x1, x2]) =>\r\n\t\t\tvertical.map(([y1, y2]) => new ExpandBox().expandXY(x1, y1).expandXY(x2, y2))\r\n\t\t)\r\n\t\t.flat(2);\r\n\r\n\treturn boxes;\r\n}\r\n","import AbstractSymbolGroup from \"./AbstractSymbolGroup.mjs\";\r\nimport Loader from \"./Loader.mjs\";\r\n\r\nimport RBush from \"../3rd-party/rbush.mjs\";\r\nimport knn from \"../3rd-party/rbush-knn.mjs\";\r\n\r\nimport ExpandBox from \"../geometry/ExpandBox.mjs\";\r\nimport Geometry from \"../geometry/Geometry.mjs\";\r\n\r\nimport Spider from \"../symbols/Spider.mjs\";\r\nimport MultiSymbol from \"../symbols/MultiSymbol.mjs\";\r\nimport CircleStroke from \"../symbols/CircleStroke.mjs\";\r\nimport CircleFill from \"../symbols/CircleFill.mjs\";\r\nimport TextLabel from \"../symbols/TextLabel.mjs\";\r\n\r\nimport intersectBboxes from \"./cluster/intersectBboxes.mjs\";\r\n\r\nfunction defaultSymbolizer(symbols) {\r\n\tconst geom = symbols[0].geometry;\r\n\r\n\treturn [\r\n\t\tnew CircleStroke(geom, { radius: 40 }),\r\n\t\tnew CircleFill(geom, { radius: 40, colour: \"#3388ff80\" }),\r\n\t\tnew TextLabel(geom, {\r\n\t\t\tstr: symbols.length,\r\n\t\t\talign: \"center\",\r\n\t\t\tbaseline: \"middle\",\r\n\t\t\tcache: true,\r\n\t\t\tinteractive: false,\r\n\t\t\toutlineColour: \"black\",\r\n\t\t\toutlineWidth: 0.15,\r\n\t\t}),\r\n\t];\r\n}\r\n\r\n// The default action when clicking on a (non-spider) cluster is to zoom\r\n// to the bounding box of the components of that cluster, but no closer\r\n// than the clusterer's `scaleLimit`.\r\nfunction defaultOnClusterClick(ev, clusterer, items, bbox) {\r\n\tlet platina = ev.target.symbols[0]._inAcetate?.platina;\r\n\tlet map = platina?.map;\r\n\r\n\tconst { minX, minY, maxX, maxY } = bbox;\r\n\tconst [w, h] = platina.pxSize;\r\n\r\n\tconst center = new Geometry(ev.target.geometry.crs, [\r\n\t\t(minX + maxX) / 2,\r\n\t\t(minY + maxY) / 2,\r\n\t]);\r\n\tconst scale = Math.max((maxX - minX) / w, (maxY - minY) / h, clusterer.scaleLimit);\r\n\r\n\treturn (map ?? platina).setView({ center, scale });\r\n}\r\n\r\nclass PointRBush extends RBush {\r\n\ttoBBox({ x, y }) {\r\n\t\treturn { minX: x, minY: y, maxX: x, maxY: y };\r\n\t}\r\n\tcompareMinX(a, b) {\r\n\t\treturn a.x - b.x;\r\n\t}\r\n\tcompareMinY(a, b) {\r\n\t\treturn a.y - b.y;\r\n\t}\r\n}\r\n\r\n/**\r\n * @class Clusterer\r\n * @inherits AbstractSymbolGroup\r\n *\r\n * Handles symbols with point geometries, and clusters them together whenever\r\n * they're too close to each other.\r\n */\r\n\r\n/*\r\n * This implements a naïve algorithm:\r\n * - Discrete steps of clustering, depending on scale. Similar to \"zoom levels\".\r\n * - One r-bush per \"zoom level\"\r\n * - Contains clusters for that grouping\r\n * - A cluster of just one symbol is passed through\r\n * - A cluster of several symbols gets replaced with a cluster symbol\r\n * - Symbols can be added **and** removed from the clusterer\r\n * - Adding a symbol shall do a kNN search on the r-bush for any close cluster\r\n * - All known r-bushes will add/remove symbols being added/removed.\r\n * - Changing the geometry of a smbol shall remove and re-add it\r\n */\r\n\r\n// TODO: Somehow move the rbush generation code to a worker: when the log2scale\r\n// changes, do all the work of calculating the clusters in a worker.\r\n\r\nexport default class Clusterer extends AbstractSymbolGroup {\r\n\tconstructor({\r\n\t\t/**\r\n\t\t * @option clusterSymbolizer: Function\r\n\t\t * Defines how to spawn symbols for the clusters. The function will\r\n\t\t * receive an `Array` of `GleoSymbol`s as its first parameter, and must\r\n\t\t * return an `Array` of `GleoSymbol`s which must represent the cluster.\r\n\t\t */\r\n\t\tclusterSymbolizer = defaultSymbolizer,\r\n\r\n\t\t/**\r\n\t\t * @option distance: Number = 80\r\n\t\t * The minimum distance, in CSS pixels, for two `GleoSymbol`s to not be\r\n\t\t * clustered. By implication, that's also the maximum diameter of a cluster.\r\n\t\t */\r\n\t\tdistance = 80,\r\n\r\n\t\t/**\r\n\t\t * @option clusterSetFactor: Number = 1\r\n\t\t * How many cluster sets to calculate per every doubling/halving\r\n\t\t * of the scale.\r\n\t\t *\r\n\t\t * e.g. The default value of 1 will create a set of clusters for every\r\n\t\t * doubling of the scale. A value of 2 will create a set of clusters\r\n\t\t * every time the scale varies by a factor of square root of 2, and\r\n\t\t * a value of e.g. 0.5 will create a set of clusters every time the\r\n\t\t * scale quadruples.\r\n\t\t */\r\n\t\tclusterSetFactor = 1,\r\n\r\n\t\t/**\r\n\t\t * @option scaleLimit: Number = 0\r\n\t\t * Clusters will not be calculated past this scale factor. Instead, the\r\n\t\t * most detailed clusters will be expandable `Spider`s.\r\n\t\t *\r\n\t\t * TODO: Default to `undefined`, and calculate from the platina's `minSpan`.\r\n\t\t */\r\n\t\tscaleLimit = 1000,\r\n\r\n\t\t/**\r\n\t\t * @option onClusterClick: Function\r\n\t\t * An event handler that will run when clicking on a non-spider cluster.\r\n\t\t *\r\n\t\t * This function receives as parameters: the event, a reference to this\r\n\t\t * `Clusterer`, and `Array` of `GleoSymbol`s with the items in the cluster,\r\n\t\t * and an `ExpandBox` covering those items.\r\n\t\t *\r\n\t\t * The default is to perform a `fitBounds` to the bounding box of the\r\n\t\t * items in that cluster, zooming up to `scaleLimit` at most.\r\n\t\t * @alternative\r\n\t\t * @option onClusterClick: Boolean\r\n\t\t * Setting this to `false` will disable cluster click events.\r\n\t\t */\r\n\t\tonClusterClick = defaultOnClusterClick,\r\n\r\n\t\t/**\r\n\t\t * @option spiderOptions: Spider Options\r\n\t\t *\r\n\t\t * A set of options for the `Spider` constructor, that shall be applied\r\n\t\t * to any `Spider`s spawned by this clusterer.\r\n\t\t */\r\n\t\tspiderOptions = {},\r\n\r\n\t\t...opts\r\n\t} = {}) {\r\n\t\tsuper(opts);\r\n\r\n\t\tthis.#boundOnViewChange = this.#onViewChange.bind(this);\r\n\t\tthis.#boundOnCrsChange = this.#onCrsChange.bind(this);\r\n\t\tthis.#distance = distance;\r\n\t\tthis.#clusterSymbolizer = clusterSymbolizer;\r\n\t\tthis.#clusterSetFactor = clusterSetFactor;\r\n\t\tthis.#onClusterClick = onClusterClick;\r\n\t\tthis.#scaleLimit = scaleLimit;\r\n\t\tthis.#boundRelayEvent = this.#relayEvent.bind(this);\r\n\t\tthis.#spiderOptions = spiderOptions;\r\n\t}\r\n\r\n\t#distance;\r\n\t#rbushes = new Map();\r\n\t#log2scale;\r\n\t#log2offset = 0;\r\n\t#boundOnViewChange;\r\n\t#boundOnCrsChange;\r\n\t#crs;\r\n\t#onClusterClick;\r\n\t#clusterSymbolizer;\r\n\t#clusterSetFactor;\r\n\t#visibleSymbols = [];\r\n\t#bbox; // Last platina bbox where visibility was (re)calculated\r\n\t#dataBbox = new ExpandBox(); // Extents of the contained symbols\r\n\t#scaleLimit;\r\n\t#spiderScaleLog = -Infinity;\r\n\t#spiderOptions;\r\n\r\n\t_addToPlatina(platina) {\r\n\t\tsuper._addToPlatina(platina);\r\n\r\n\t\tthis.platina.on(\"viewchanged\", this.#boundOnViewChange);\r\n\t\tthis.platina.on(\"crsoffset\", this.#boundOnCrsChange);\r\n\t\tthis.platina.on(\"crschange\", this.#boundOnCrsChange);\r\n\t\tthis.#boundOnViewChange();\r\n\r\n\t\t// Calculate an offset to (later) snap the log2 of the scale factor to round numbers.\r\n\t\t// This is an optimization for a common use case: snapping to the scale factor\r\n\t\t// of a tile pyramid. It's a somehow naïf approach since it assumes the pyramid\r\n\t\t// uses power-of-two scaling.\r\n\t\tconst zoomSnapActuator = (\r\n\t\t\tthis.target.actuators ?? this.target.map?.actuators\r\n\t\t)?.get(\"zoomsnap\");\r\n\t\tif (zoomSnapActuator) {\r\n\t\t\tthis.#log2offset =\r\n\t\t\t\t(Math.log2(zoomSnapActuator.snapScale(this.target.scale)) *\r\n\t\t\t\t\tthis.#clusterSetFactor) %\r\n\t\t\t\t1;\r\n\t\t}\r\n\r\n\t\t// Calculate the minimum log2(scale), which is when clusters are\r\n\t\t// spiderified.\r\n\t\tif (this.#scaleLimit) {\r\n\t\t\tthis.#spiderScaleLog = Math.ceil(\r\n\t\t\t\tMath.log2(this.#scaleLimit) * this.#clusterSetFactor - this.#log2offset\r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\tthis.#crs = this.platina.crs;\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t#animFrame;\r\n\r\n\t/// @property bbox: Array of Number\r\n\t/// The bounding box of the data for the clusterer, in the CRS of the\r\n\t/// platina this clusterer is in, in the form `[minX, minY, maxX, maxY]`.\r\n\t/// Read-only.\r\n\tget bbox() {\r\n\t\tif (!this.#crs) {\r\n\t\t\tthrow new Error(\"The clusterer needs to be in a platina with a CRS\");\r\n\t\t}\r\n\t\tconst log2scale = this.getCurrentLog2scale();\r\n\t\tif (!this.#rbushes.has(log2scale)) {\r\n\t\t\tthis.#buildBush(log2scale);\r\n\t\t}\r\n\t\tconst bush = this.#rbushes.get(log2scale).data;\r\n\r\n\t\treturn [bush.minX, bush.minY, bush.maxX, bush.maxY];\r\n\t}\r\n\r\n\t_addSymbols(symbols) {\r\n\t\tcancelAnimationFrame(this.#animFrame);\r\n\t\tthis.#animFrame = requestAnimationFrame(() => this.#resetBushes());\r\n\t\tsymbols.forEach((s) => this.#dataBbox.expandGeometry(s.geometry));\r\n\t\treturn super._addSymbols(symbols);\r\n\t}\r\n\r\n\t// Does *not* shrink this.#dataBbox\r\n\tremove(symbol) {\r\n\t\tif (symbol) {\r\n\t\t\tif (symbol instanceof Loader) {\r\n\t\t\t\treturn super.remove(symbol);\r\n\t\t\t} else {\r\n\t\t\t\tcancelAnimationFrame(this.#animFrame);\r\n\t\t\t\tthis.#animFrame = requestAnimationFrame(() => this.#resetBushes());\r\n\t\t\t\treturn super.remove(symbol);\r\n\t\t\t}\r\n\t\t} else if (this.platina) {\r\n\t\t\tthis.platina.off(\"viewchanged\", this.#boundOnViewChange);\r\n\t\t\tthis.platina.off(\"crsoffset\", this.#boundOnCrsChange);\r\n\t\t\tthis.platina.off(\"crschange\", this.#boundOnCrsChange);\r\n\r\n\t\t\t// Remove currently visible clusters, by removing all clusters with\r\n\t\t\t// the r-bush for the current scale (skipping `undefined` cluster\r\n\t\t\t// symbols that haven't been needed yet)\r\n\t\t\tthis.fire(\"symbolsremoved\", {\r\n\t\t\t\tsymbols: this.#rbushes\r\n\t\t\t\t\t.get(this.getCurrentLog2scale())\r\n\t\t\t\t\t.all()\r\n\t\t\t\t\t.map((item) => {\r\n\t\t\t\t\t\treturn item.symbol;\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.filter((i) => !!i)\r\n\t\t\t\t\t.flat(),\r\n\t\t\t});\r\n\r\n\t\t\t// Reset state, so removing & re-adding the clusterer does a refresh\r\n\t\t\tthis.#visibleSymbols = [];\r\n\t\t\tthis.#bbox = undefined;\r\n\t\t\tthis.#log2scale = undefined;\r\n\r\n\t\t\tsuper.remove();\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\t_removeSymbols(symbols) {\r\n\t\tcancelAnimationFrame(this.#animFrame);\r\n\t\tthis.#animFrame = requestAnimationFrame(() => this.#resetBushes());\r\n\t\treturn super._removeSymbols(symbols);\r\n\t}\r\n\r\n\tempty() {\r\n\t\tcancelAnimationFrame(this.#animFrame);\r\n\t\tthis.#animFrame = requestAnimationFrame(() => this.#resetBushes());\r\n\t\treturn super.empty();\r\n\t}\r\n\r\n\t// Aux, intended for internal use\r\n\tgetCurrentLog2scale() {\r\n\t\treturn Math.max(\r\n\t\t\tMath.floor(\r\n\t\t\t\tMath.log2(this.platina.scale) * this.#clusterSetFactor - this.#log2offset\r\n\t\t\t),\r\n\t\t\tthis.#spiderScaleLog\r\n\t\t);\r\n\t}\r\n\r\n\t/**\r\n\t * @property scaleLimit\r\n\t * Value of the `scaleLimit` option. Read-only.\r\n\t */\r\n\tget scaleLimit() {\r\n\t\treturn this.#scaleLimit;\r\n\t}\r\n\r\n\t#onViewChange(ev) {\r\n\t\tif (!this.platina?.scale) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tif (!this.#crs) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tconst log2scale = this.getCurrentLog2scale();\r\n\r\n\t\tconst crs = this.#crs;\r\n\t\tconst rawBBox = this.platina.bbox;\r\n\t\tconst platinaBBox = new ExpandBox();\r\n\t\t// platinaBBox.expandPair(crs.offsetToBase([rawBBox.minX, rawBBox.minY]));\r\n\t\t// platinaBBox.expandPair(crs.offsetToBase([rawBBox.maxX, rawBBox.maxY]));\r\n\t\tplatinaBBox.expandPair([rawBBox.minX, rawBBox.minY]);\r\n\t\tplatinaBBox.expandPair([rawBBox.maxX, rawBBox.maxY]);\r\n\r\n\t\tif (log2scale !== this.#log2scale) {\r\n\t\t\t// console.info(\"Clusterer scale change: \", this.#log2scale, \"→\", log2scale);\r\n\r\n\t\t\tif (!this.#rbushes.has(log2scale)) {\r\n\t\t\t\tthis.#buildBush(log2scale);\r\n\t\t\t}\r\n\t\t} else if (this.#bbox?.containsBox(platinaBBox)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tthis.#log2scale = log2scale;\r\n\t\tthis.#bbox = platinaBBox.clone().expandPercentage(0.2);\r\n\r\n\t\tlet removableSymbols = this.#visibleSymbols;\r\n\t\tconst bush = this.#rbushes.get(log2scale);\r\n\r\n\t\t// Usually, the cluster domain and the viewport have a simple intersection\r\n\t\t// (`bush.search(this.#bbox)`), but edge cases\r\n\t\t// involving the antimeridian call for calculating multiple intersections\r\n\t\tconst intersections = intersectBboxes(bush.data, this.#bbox, crs);\r\n\t\tconst spiders = this.#spiderScaleLog === log2scale;\r\n\r\n\t\tlet addableSymbols = intersections\r\n\t\t\t.map((bushbox) =>\r\n\t\t\t\tbush.search(bushbox).map((item) => this.#symbolizeCluster(item, spiders))\r\n\t\t\t)\r\n\t\t\t.flat();\r\n\r\n\t\tlet uniqueAddableSymbols = addableSymbols.filter(\r\n\t\t\t(s) => !removableSymbols.includes(s)\r\n\t\t);\r\n\t\tlet uniqueRemovableSymbols = removableSymbols.filter(\r\n\t\t\t(s) => !addableSymbols.includes(s)\r\n\t\t);\r\n\r\n\t\tthis.fire(\"symbolsremoved\", { symbols: uniqueRemovableSymbols });\r\n\t\tthis.fire(\"symbolsadded\", { symbols: uniqueAddableSymbols });\r\n\r\n\t\tthis.#visibleSymbols = addableSymbols;\r\n\t}\r\n\r\n\t// Expects a rbush item as parameter\r\n\t#symbolizeCluster(item, spiders) {\r\n\t\tif (item.symbol) {\r\n\t\t\treturn item.symbol;\r\n\t\t}\r\n\r\n\t\tlet symbol;\r\n\t\tif (item.sources.length === 1) {\r\n\t\t\t// Clusters of only one symbol don't need symbolization; reuse that single symbol\r\n\t\t\tsymbol = item.sources[0];\r\n\t\t} else if (spiders) {\r\n\t\t\t// At the highest zoom level (lowest scale), clickable spiders are used\r\n\t\t\tsymbol = new Spider(\r\n\t\t\t\tthis.#clusterSymbolizer(item.sources),\r\n\t\t\t\titem.sources,\r\n\t\t\t\tthis.#spiderOptions\r\n\t\t\t);\r\n\r\n\t\t\t/**\r\n\t\t\t * @event expand: CustomEvent\r\n\t\t\t * Fired when one of the `Spider`s of the clusterer expands.\r\n\t\t\t * The `Spider` in question is in the event's `detail`\r\n\t\t\t * @event collapse: CustomEvent\r\n\t\t\t * Fired when one of the `Spider`s of the clusterer collapses.\r\n\t\t\t * The `Spider` in question is in the event's `detail`\r\n\t\t\t */\r\n\t\t\tsymbol.on(\"collapse\", this.#boundRelayEvent);\r\n\t\t\tsymbol.on(\"expand\", this.#boundRelayEvent);\r\n\t\t} else {\r\n\t\t\t// At any other zoom levels, use a MultiSymbol\r\n\t\t\tsymbol = new MultiSymbol(this.#clusterSymbolizer(item.sources));\r\n\r\n\t\t\tif (this.#onClusterClick) {\r\n\t\t\t\tsymbol.cursor = \"pointer\";\r\n\r\n\t\t\t\tlet bbox = new ExpandBox();\r\n\t\t\t\titem.sources.forEach((s) =>\r\n\t\t\t\t\tbbox.expandGeometry(s.geometry.toCRS(this.platina.crs))\r\n\t\t\t\t);\r\n\r\n\t\t\t\tsymbol.on(\"click\", (ev) =>\r\n\t\t\t\t\tthis.#onClusterClick(ev, this, item.sources, bbox)\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn (item.symbol = symbol);\r\n\t}\r\n\r\n\t#boundRelayEvent;\r\n\r\n\t#relayEvent(ev) {\r\n\t\tconst myEv = new ev.constructor(ev.type, { ...ev, detail: ev.target });\r\n\t\tthis.dispatchEvent(myEv);\r\n\t}\r\n\r\n\t#buildBush(log2scale) {\r\n\t\t// Max distance between points to cluster together, in CRS units\r\n\t\tconst dist = Math.pow(2, log2scale / this.#clusterSetFactor) * this.#distance;\r\n\t\tconst crs = this.#crs;\r\n\r\n\t\tconst bush = new PointRBush();\r\n\r\n\t\tthis.symbols.forEach((s) => {\r\n\t\t\t// if (s.geometry.crs.name !== crs.name) {\r\n\t\t\t/// FIXME: This can lead to a chain of reprojections, and a\r\n\t\t\t/// subsequent loss of precision, if the map/platina changes\r\n\t\t\t/// CRSs frequently.\r\n\t\t\t/// TODO: Maybe use a `WeakMap` to hold the reprojected geometries?\r\n\t\t\ts.geometry = s.geometry.toCRS(crs);\r\n\t\t\t// }\r\n\t\t\tconst [x, y] = s.geometry.coords;\r\n\t\t\tif (!isFinite(x) || !isFinite(y)) {\r\n\t\t\t\treturn console.warn(\r\n\t\t\t\t\t`Could not add symbol to cluster: non-finite coordinates`\r\n\t\t\t\t);\r\n\t\t\t}\r\n\r\n\t\t\t/// TODO: Consider implementing wrapping logic in the clusterer.\r\n\t\t\t/// Take the first known point and store as wrapping reference;\r\n\t\t\t/// Any subsequent points undergo wrapping logic: if away more than\r\n\t\t\t/// half a period, point gets wrapped.\r\n\r\n\t\t\tconst nearest = knn(bush, x, y, 1, undefined, dist);\r\n\r\n\t\t\tif (nearest?.length) {\r\n\t\t\t\t// Add to existing cluster\r\n\t\t\t\tnearest[0].sources.push(s);\r\n\t\t\t} else {\r\n\t\t\t\t// Create a new cluster with no symbol\r\n\t\t\t\tbush.insert({\r\n\t\t\t\t\tx,\r\n\t\t\t\t\ty,\r\n\t\t\t\t\tsources: [s],\r\n\t\t\t\t\tsymbol: undefined,\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tthis.#rbushes.set(log2scale, bush);\r\n\t\t/**\r\n\t\t * @event build: CustomEvent\r\n\t\t * Fired when one of the the internal data structures have been built (due to\r\n\t\t * data being added or removed).\r\n\t\t */\r\n\t\tthis.fire(\"build\", log2scale);\r\n\t\treturn bush;\r\n\t}\r\n\r\n\t// Removes all of the bushes, forcing their (re)building\r\n\t#resetBushes() {\r\n\t\t/**\r\n\t\t * @event reset: CustomEvent\r\n\t\t * Fired when the internal data structures have been reset (due to\r\n\t\t * data being added or removed).\r\n\t\t */\r\n\t\tthis.fire(\"reset\");\r\n\t\tthis.#rbushes.clear();\r\n\t\tthis.#log2scale = NaN;\r\n\t\treturn this.#onViewChange();\r\n\t}\r\n\r\n\t#onCrsChange(ev) {\r\n\t\tthis.#crs = ev.detail.newCRS;\r\n\t\treturn this.#resetBushes();\r\n\t}\r\n\r\n\t/**\r\n\t * @method getParent(symbol: GleoSymbol, scale?: Number): GleoSymbol\r\n\t * Returns the symbol representing the cluster that the given symbol belongs to,\r\n\t * at the given scale. If scale is not given, the current scale will be\r\n\t * used.\r\n\t *\r\n\t * Akin to leaflet-markercluster's `getVisibleParent`.\r\n\t */\r\n\tgetParent(symbol, scale) {\r\n\t\tconst log2scale = Math.max(\r\n\t\t\tMath.floor(\r\n\t\t\t\tMath.log2(scale ?? this.platina.scale) * this.#clusterSetFactor -\r\n\t\t\t\t\tthis.#log2offset\r\n\t\t\t),\r\n\t\t\tthis.#spiderScaleLog\r\n\t\t);\r\n\r\n\t\tconst [x, y] = symbol.geometry.toCRS(this.#crs).coords;\r\n\r\n\t\tconst dist = Math.pow(2, log2scale / this.#clusterSetFactor) * this.#distance;\r\n\t\tconst bush = this.#rbushes.get(log2scale) ?? this.#buildBush(log2scale);\r\n\t\tconst nearest = knn(bush, x, y, 1, undefined, dist)[0];\r\n\r\n\t\tthis.#symbolizeCluster(nearest, this.#spiderScaleLog === log2scale);\r\n\t\treturn nearest.symbol;\r\n\t}\r\n\r\n\t/**\r\n\t * @method getUnclusterScaleFor(symbol: GleoSymbol): Number\r\n\t * Returns the minimum scale (in the map's CRS units) where the given symbol\r\n\t * is not in a cluster.\r\n\t *\r\n\t * If the symbol cannot be shown outside of a cluster (i.e. it belongs to a\r\n\t * `Spider` at the `Clusterer`s `scaleLimit`), then the return value will\r\n\t * be zero.\r\n\t */\r\n\tgetUnclusterScaleFor(symbol) {\r\n\t\tconst [x, y] = symbol.geometry.toCRS(this.#crs).coords;\r\n\r\n\t\tconst log2scale = this.getCurrentLog2scale();\r\n\r\n\t\tfor (let i = log2scale; i >= this.#spiderScaleLog; i--) {\r\n\t\t\t// Max distance between points to cluster together, in CRS units\r\n\t\t\tconst dist = Math.pow(2, i / this.#clusterSetFactor) * this.#distance;\r\n\r\n\t\t\tconst bush = this.#rbushes.get(i) ?? this.#buildBush(i);\r\n\r\n\t\t\t// Get *one* cluster. It'll be the one containing the symbol.\r\n\t\t\tconst nearest = knn(bush, x, y, 1, undefined, dist)[0];\r\n\r\n\t\t\tif (nearest.sources.length === 1) {\r\n\t\t\t\t// The cluster has only one symbol in it - this is how far to\r\n\t\t\t\t// zoom in.\r\n\r\n\t\t\t\treturn Math.pow(2, (i + this.#log2offset) / this.#clusterSetFactor);\r\n\t\t\t} else if (i === this.#spiderScaleLog) {\r\n\t\t\t\treturn 0;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * @method zoomToShowSymbol(symbol: GleoSymbol, setViewOpts?: SetView Options): this\r\n\t * Akin to leaflet-markercluster's `zoomToShowLayer()` - zooms into the map\r\n\t * far enough so the given symbol is not clustered; if it's in a spider\r\n\t * it will zoom into it and expand the spider.\r\n\t */\r\n\tzoomToShowSymbol(symbol, setViewOpts = {}) {\r\n\t\tconst scale = this.getUnclusterScaleFor(symbol);\r\n\r\n\t\t// Find a target that has setView (i.e. traverse through possible nested\r\n\t\t// SymbolGroups)\r\n\t\tlet target = this.target;\r\n\t\twhile (!target.setView) {\r\n\t\t\tif (!target) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\ttarget = target.target;\r\n\t\t}\r\n\r\n\t\tif (scale > 0) {\r\n\t\t\ttarget.setView({\r\n\t\t\t\tduration: 2000,\r\n\t\t\t\t...setViewOpts,\r\n\t\t\t\tcenter: symbol.geometry,\r\n\t\t\t\tscale: scale,\r\n\t\t\t});\r\n\t\t\treturn this;\r\n\t\t} else {\r\n\t\t\t// const scale = Math.pow( 2, (this.#spiderScaleLog + this.#log2offset) / this.#clusterSetFactor );\r\n\r\n\t\t\ttarget.setView({\r\n\t\t\t\tduration: 2000,\r\n\t\t\t\t...setViewOpts,\r\n\t\t\t\tcenter: symbol.geometry,\r\n\t\t\t\tscale: this.#scaleLimit,\r\n\t\t\t});\r\n\r\n\t\t\t// Delay the spider expansion by one frame, to prevent a race\r\n\t\t\t// condition with a delayed #resetBushes(). This can trigger when\r\n\t\t\t// `zoomToShowSymbol` is called just after adding data to a clusterer.\r\n\t\t\trequestAnimationFrame(() => {\r\n\t\t\t\tconst spider = this.getParent(symbol, scale);\r\n\t\t\t\tspider.expand();\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n}\r\n","import { map } from \"./map-base.js\";\r\n\r\nimport Clusterer from \"./gleo/src/loaders/Clusterer.mjs\";\r\nimport TintedSprite from \"./gleo/src/symbols/TintedSprite.mjs\";\r\nimport TextLabel from \"./gleo/src/symbols/TextLabel.mjs\";\r\nimport Geometry from \"./gleo/src/geometry/Geometry.mjs\";\r\n\r\nimport L from \"leaflet\";\r\n\r\n// This is the default behaviour of the Gleo defaultOnClusterClick(), but\r\n// adapted for leaflet-gleo.\r\nfunction onClusterClick(ev, clusterer, items, bbox) {\r\n\r\n\tconst { minX, minY, maxX, maxY } = bbox;\r\n\r\n\t// Reproject bbox into epsg4326\r\n\tconst crs = ev.target.symbols[0]._inAcetate.platina.crs;\r\n\tconst sw = new Geometry(crs, [minX, minY]).asLatLng();\r\n\tconst ne = new Geometry(crs, [maxX, maxY]).asLatLng();\r\n\tconst bounds = L.latLngBounds([sw, ne]);\r\n\tmap.fitBounds(bounds, { maxZoom: 17 });\r\n}\r\n\r\n// Note: Loading TextLabel font faces is async.\r\nconst fontfaceURL = new URL(\"/fonts/open-sans-v34-latin_vietnamese-regular\", document.URL).toString();\r\nTextLabel.addFontFace(\"Open Sans\", `\r\nlocal('Open Sans Regular'),\r\nlocal('OpenSans-Regular'),\r\nurl('${fontfaceURL}.woff2') format('woff2'),\r\nurl('${fontfaceURL}.woff') format('woff')\r\n`);\r\n\r\nfunction clusterSymbolizer(symbols) {\r\n\tconst geom = symbols[0].geometry;\r\n\tconst base = symbols[0].spriteBase;\r\n\r\n\treturn [\r\n\t\tnew TintedSprite(geom, {\r\n\t\t\timage: base.image,\r\n\t\t\ttint: base.tint,\r\n\t\t}),\r\n\t\t// new CircleStroke(geom, { radius: 16, colour: \"#3388ff80\", width: 4, cursor: 'pointer' }),\r\n\t\t// new CircleFill(geom, {\r\n\t\t// \tradius: 14,\r\n\t\t// \tcolour: \"#3388ffc0\" ,\r\n\t\t// \tcursor: \"pointer\",\r\n\t\t// }),\r\n\t\t// drop shadow\r\n\t\tnew TextLabel(geom, {\r\n\t\t\tstr: symbols.length,\r\n\t\t\talign: \"center\",\r\n\t\t\tbaseline: \"middle\",\r\n\t\t\tcache: true,\r\n\t\t\tinteractive: false,\r\n\t\t\tcolour: 'white',\r\n\t\t\t//outlineWidth: 0.15,\r\n\t\t\t//outlineColour: [255, 255, 255, 255],\r\n\t\t\toffset: [1, -19],\r\n\t\t\tfont: \"14px 'Open Sans'\"\r\n\t\t}),\r\n\t\t// cluster label\r\n\t\tnew TextLabel(geom, {\r\n\t\t\tstr: symbols.length,\r\n\t\t\talign: \"center\",\r\n\t\t\tbaseline: \"middle\",\r\n\t\t\tcache: true,\r\n\t\t\tinteractive: false,\r\n\t\t\toutlineWidth: 0.15,\r\n\t\t\toutlineColour: [255, 255, 255, 255],\r\n\t\t\toffset: [0, -18],\r\n\t\t\tfont: \"14px 'Open Sans'\"\r\n\t\t}),\r\n\t\t// new L.Marker(geom.coords, { icon: L.divIcon({html: symbols.length})})\r\n\t];\r\n}\r\n\r\nexport function createMarkerCluster(asset, trip, sharedViewId) {\r\n\treturn new Clusterer({\r\n\t\tscaleLimit: 1,\r\n\t\tdistance: 72,\r\n\t\tclusterSymbolizer: clusterSymbolizer,\r\n\t\tonClusterClick: onClusterClick,\r\n\t\toutlineColour: \"white\",\r\n\t\toutlineWidth: 0.5\r\n\t});\r\n}\r\n","import trkData from \"./data.js\";\r\nimport log from \"./log.js\";\r\nimport state from \"./state.js\";\r\nimport { intervals } from \"./timers.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\n\r\nimport L from \"leaflet\";\r\nimport $j from \"jquery\";\r\n\r\n// Snap To Roads Queueing\r\nfunction addSnapToRoadsRequestToQueue(item, ignoreMode) {\r\n\ttrkData.snapToRoadsQueue.push(item);\r\n\tif (!state.isSnapToRoadsQueueRunning) {\r\n\t\tstate.isSnapToRoadsQueueRunning = true;\r\n\t\tintervals.snapToRoadsInterval = setInterval(processSnapToRoadsQueue, 250); // 10 per second maximum, 50 ms cushion\r\n\t}\r\n}\r\n\r\nfunction clearSnapToRoadsQueue() {\r\n\tif (intervals.snapToRoadsInterval != null) {\r\n\t\tclearInterval(intervals.snapToRoadsInterval);\r\n\t}\r\n\tstate.isSnapToRoadsQueueRunning = false;\r\n\tstate.isSnapToRoadsQueueProcessing = false;\r\n\ttrkData.snapToRoadsQueue = [];\r\n}\r\n\r\nfunction processSnapToRoadsQueue() {\r\n\tif (state.isSnapToRoadsQueueProcessing) {\r\n\t\treturn;\r\n\t}\r\n\tstate.isSnapToRoadsQueueProcessing = true;\r\n\tif (!state.isSnapToRoadsQueueRunning) {\r\n\t\treturn;\r\n\t}\r\n\r\n\t// singleton that can only be started once\r\n\tif (trkData.snapToRoadsQueue.length == 0) {\r\n\t\tclearSnapToRoadsQueue();\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar item = trkData.snapToRoadsQueue[0];\r\n\r\n\t// call snap.ashx with p in the format of lng,lat;\r\n\t// todo: include bearings\r\n\tvar points = \"\";\r\n\tvar timestamps = \"\";\r\n\tvar radiuses = \"\";\r\n\tfor (var i = 0; i < item.points.length; i++) {\r\n\t\tif (points != \"\") {\r\n\t\t\tpoints += \";\";\r\n\t\t\ttimestamps += \";\";\r\n\t\t\tradiuses += \";\";\r\n\t\t}\r\n\t\tvar point = item.points[i].latLng;\r\n\t\tvar timestamp = item.points[i].timestamp;\r\n\t\tvar accuracy = item.points[i].accuracy;\r\n\t\tpoints += point.lng + \",\" + point.lat;\r\n\t\ttimestamps += timestamp;\r\n\t\tradiuses += accuracy != null ? (accuracy > 50 ? 50 : accuracy) : \"10\";\r\n\t}\r\n\r\n\tvar data = {\r\n\t\tp: points,\r\n\t\tt: timestamps,\r\n\t\tr: radiuses,\r\n\t};\r\n\r\n\t$j.ajax({\r\n\t\ttype: \"POST\",\r\n\t\turl: wrapUrl(\"/api/routing/snap\"),\r\n\t\tdataType: \"json\",\r\n\t\tdata: JSON.stringify(data),\r\n\t\tcontentType: \"application/json; charset=utf-8\",\r\n\t\tsuccess: function (results) {\r\n\t\t\tvar stopProcessing = true;\r\n\t\t\tvar pathToAdd = [];\r\n\t\t\tif (results.code == \"Ok\") {\r\n\t\t\t\tvar newPoints = decode_polyline(results.polyline);\r\n\t\t\t\tfor (var i = 0; i < newPoints.length; i++) {\r\n\t\t\t\t\tpathToAdd.push(L.latLng(newPoints[i][0], newPoints[i][1]));\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tfor (var i = 0; i < item.points.length; i++) {\r\n\t\t\t\t\tpathToAdd.push(item.points[i]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\ttrkData.snapToRoadsQueue.shift();\r\n\r\n\t\t\tif (item.target != null) {\r\n\t\t\t\tvar locations = item.target.getLatLngs();\r\n\t\t\t\tfor (var i = 0; i < pathToAdd.length; i++) {\r\n\t\t\t\t\tlocations.push(pathToAdd[i]);\r\n\t\t\t\t}\r\n\t\t\t\titem.target.setLatLngs(locations);\r\n\t\t\t}\r\n\t\t\tif (stopProcessing) {\r\n\t\t\t\tstate.isSnapToRoadsQueueProcessing = false;\r\n\t\t\t}\r\n\t\t},\r\n\t\terror: function () {\r\n\t\t\tvar pathToAdd = [];\r\n\t\t\tfor (var i = 0; i < item.points.length; i++) {\r\n\t\t\t\tpathToAdd.push(item.points[i].latLng);\r\n\t\t\t}\r\n\t\t\tif (item.target != null) {\r\n\t\t\t\tvar locations = item.target.getLatLngs();\r\n\t\t\t\tfor (var i = 0; i < pathToAdd.length; i++) {\r\n\t\t\t\t\tlocations.push(pathToAdd[i]);\r\n\t\t\t\t}\r\n\t\t\t\titem.target.setLatLngs(locations);\r\n\t\t\t}\r\n\t\t\ttrkData.snapToRoadsQueue.shift();\r\n\t\t\tstate.isSnapToRoadsQueueProcessing = false;\r\n\t\t},\r\n\t});\r\n}\r\n\r\n// End Snap To Roads Queueing\r\n\r\nexport function snapLinePathsToRoads(target, linePath) {\r\n\t// this function will queue and update target's mapLine with the snapped polylines\r\n\t// directions service only handles 10 waypoints at a time\r\n\t// take sections of 10 at a time from the positions\r\n\t// must tie sections together as well (8 waypoints max)\r\n\tvar priorEndingPosition = null;\r\n\tvar routePositions = [];\r\n\tfor (var i = 0; i < linePath.length; i++) {\r\n\t\tvar section = linePath[i];\r\n\t\tvar positionPoints = section.reverse();\r\n\t\tvar isValidRoute = section.length > 1;\r\n\r\n\t\tif (isValidRoute) {\r\n\t\t\tlog(\"Creating directions route from \" + positionPoints.length + \" positions.\");\r\n\t\t\t// todo: never leave one position leftover\r\n\t\t\tfor (var j = 0; j < positionPoints.length; j += 15) {\r\n\t\t\t\t// construct the routes and QUEUE them up due to processing limits\r\n\t\t\t\tvar startIndex = j;\r\n\t\t\t\tvar endIndex = positionPoints.length > j + 14 ? j + 14 : positionPoints.length;\r\n\t\t\t\troutePositions = positionPoints.slice(startIndex, endIndex + 1);\r\n\t\t\t\tif (priorEndingPosition != null) {\r\n\t\t\t\t\t// add prior ending position as first position (origin)\r\n\t\t\t\t\t// in order to have a continuous, connected path\r\n\t\t\t\t\troutePositions.unshift(priorEndingPosition);\r\n\t\t\t\t}\r\n\t\t\t\tvar originalRoutePositions = routePositions.slice();\r\n\t\t\t\tvar origin = routePositions.shift();\r\n\t\t\t\tvar destination = routePositions.pop();\r\n\t\t\t\tvar routeWaypoints = [];\r\n\t\t\t\tfor (var k = 0; k < routePositions.length; k++) {\r\n\t\t\t\t\trouteWaypoints.push({\r\n\t\t\t\t\t\tlocation: routePositions[k],\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t\tvar route = {\r\n\t\t\t\t\torigin: origin,\r\n\t\t\t\t\tdestination: destination,\r\n\t\t\t\t\twaypoints: routeWaypoints,\r\n\t\t\t\t};\r\n\t\t\t\tpriorEndingPosition = destination;\r\n\t\t\t\tvar queueItem = {\r\n\t\t\t\t\ttarget: target,\r\n\t\t\t\t\tpoints: originalRoutePositions,\r\n\t\t\t\t\troute: route,\r\n\t\t\t\t};\r\n\t\t\t\taddSnapToRoadsRequestToQueue(queueItem);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction decode_polyline(str, precision) {\r\n\tvar index = 0,\r\n\t\tlat = 0,\r\n\t\tlng = 0,\r\n\t\tcoordinates = [],\r\n\t\tshift = 0,\r\n\t\tresult = 0,\r\n\t\tbyte = null,\r\n\t\tlatitude_change,\r\n\t\tlongitude_change,\r\n\t\tfactor = Math.pow(10, precision || 5);\r\n\r\n\t// Coordinates have variable length when encoded, so just keep\r\n\t// track of whether we've hit the end of the string. In each\r\n\t// loop iteration, a single coordinate is decoded.\r\n\twhile (index < str.length) {\r\n\t\t// Reset shift, result, and byte\r\n\t\tbyte = null;\r\n\t\tshift = 0;\r\n\t\tresult = 0;\r\n\r\n\t\tdo {\r\n\t\t\tbyte = str.charCodeAt(index++) - 63;\r\n\t\t\tresult |= (byte & 0x1f) << shift;\r\n\t\t\tshift += 5;\r\n\t\t} while (byte >= 0x20);\r\n\r\n\t\tlatitude_change = result & 1 ? ~(result >> 1) : result >> 1;\r\n\r\n\t\tshift = result = 0;\r\n\r\n\t\tdo {\r\n\t\t\tbyte = str.charCodeAt(index++) - 63;\r\n\t\t\tresult |= (byte & 0x1f) << shift;\r\n\t\t\tshift += 5;\r\n\t\t} while (byte >= 0x20);\r\n\r\n\t\tlongitude_change = result & 1 ? ~(result >> 1) : result >> 1;\r\n\r\n\t\tlat += latitude_change;\r\n\t\tlng += longitude_change;\r\n\r\n\t\tcoordinates.push([lat / factor, lng / factor]);\r\n\t}\r\n\r\n\treturn coordinates;\r\n}\r\n","import { convertNamedColorToHex } from \"./color.js\";\r\nimport { findAssetById } from \"./assets.js\";\r\nimport { findTripById } from \"./trips.js\";\r\nimport { createPositionHistorySummary } from \"./position-history.js\";\r\nimport trkData from \"./data.js\";\r\nimport options from \"./options.js\";\r\nimport { snapLinePathsToRoads } from \"./geometry-snap.js\";\r\nimport { addItemToMap } from \"./map-items.js\";\r\nimport { viewModes } from \"./const.js\";\r\n\r\nimport L from \"leaflet\";\r\nimport $j from \"jquery\";\r\n\r\nexport function createPolylineFromLinePaths(linePath, asset, trip, dashArray) {\r\n\tvar lineColor = convertNamedColorToHex(asset.Color);\r\n\tvar sequentialLatLngs = [];\r\n\tfor (var i = 0; i < linePath.length; i++) {\r\n\t\tvar sequentialSection = [];\r\n\t\tfor (var j = 0; j < linePath[i].length; j++) {\r\n\t\t\tsequentialSection.push(linePath[i][j].latLng);\r\n\t\t}\r\n\t\tsequentialLatLngs.push(sequentialSection);\r\n\t}\r\n\r\n\tvar line = L.polyline(sequentialLatLngs, {\r\n\t\tweight: 3,\r\n\t\tcolor: lineColor,\r\n\t\topacity: 0.75,\r\n\t\tdashArray: dashArray,\r\n\t\tpane: \"back-lines\"\r\n\t});\r\n\tline.assetId = asset.Id;\r\n\tline.tripId = trip !== undefined && trip !== null ? trip.Id : undefined;\r\n\tline.on(\"click\", function (e) {\r\n\t\tvar asset = findAssetById(this.assetId);\r\n\t\tif (asset == null) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tvar trip = findTripById(this.tripId);\r\n\t\tcreatePositionHistorySummary(asset, trip, undefined, undefined, e);\r\n\t});\r\n\treturn line;\r\n}\r\n\r\nexport function createLinePathsFromPositions(positions) {\r\n\tvar linePath = [];\r\n\tfor (var j = 0; j < positions.length; j++) {\r\n\t\t// add position to map and coposition filters\r\n\t\tvar position = positions[j];\r\n\t\tif (position.IsHidden === true) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tvar point = {\r\n\t\t\tid: position.Id,\r\n\t\t\tlatLng: L.latLng(position.Lat, position.Lng),\r\n\t\t\ttimestamp: position.Epoch,\r\n\t\t\taccuracy: position.Accuracy,\r\n\t\t};\r\n\t\tlinePath.push(point);\r\n\t}\r\n\r\n\t// create line connecting positions\r\n\t// split the line across the antimeridian so that lines don't \"cross\" the globe and look wrong\r\n\tlinePath = splitPathAcrossAntiMeridian(linePath);\r\n\treturn linePath;\r\n}\r\n\r\nfunction splitPathAcrossAntiMeridian(latLons) {\r\n\t// should return one array if not split, or more if yes split\r\n\t// will be split everytime the path crosses the antimeridian\r\n\tvar normalizeIndexes = []; // indexes of points where the anti meridian is crossed\r\n\tfor (var i = 0; i < latLons.length; i++) {\r\n\t\tvar latLon = latLons[i].latLng;\r\n\t\tvar previous = latLons[i - 1];\r\n\t\tif (previous != null) {\r\n\t\t\t// compare longitudes to determine if antimerdian has been crossed\r\n\t\t\tif (Math.abs(latLon.lng - previous.latLng.lng) > 180) {\r\n\t\t\t\tnormalizeIndexes.push(i);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tvar normalizeIndexesRef = normalizeIndexes.slice(0); // duplicate normalize indexes array\r\n\tfor (var i = 0; i < normalizeIndexes.length; i++) {\r\n\t\tvar flipDirection = latLons[normalizeIndexesRef[i]].latLng.lng < 0 ? -1 : 1;\r\n\t\t// calculate latitude of break point instead of relying on chosen latitude\r\n\t\tvar latLng0 = latLons[normalizeIndexesRef[i] - 1].latLng;\r\n\t\tvar latLng1 = latLons[normalizeIndexesRef[i]].latLng;\r\n\t\tvar lng1 = latLng1.lng < 0 ? latLng1.lng + 360 : latLng1.lng;\r\n\t\tvar lng0 = latLng0.lng < 0 ? latLng0.lng + 360 : latLng0.lng;\r\n\t\tvar slope = (latLng1.lat - latLng0.lat) / (lng1 - lng0);\r\n\t\tvar intercept = latLng0.lat - slope * lng0;\r\n\t\tvar newLatitude = slope * 180 + intercept;\r\n\t\t// using basic 2D geometry (not worth the spherical calculations I don't think)\r\n\t\t// todo: position ids need to be populated at least as well\r\n\t\tlatLons.splice(normalizeIndexesRef[i], 0, { latLng: L.latLng(newLatitude, flipDirection * -179.99999) });\r\n\t\tlatLons.splice(normalizeIndexesRef[i] + 1, 0, { latLng: L.latLng(newLatitude, flipDirection * 179.99999) });\r\n\t\tconst normalizeIndexesUpdate = [];\r\n\t\tfor (var j = 0; j < normalizeIndexesRef.length; j++) {\r\n\t\t\tnormalizeIndexesUpdate[j] = normalizeIndexesRef[j] + 2;\r\n\t\t}\r\n\t\tnormalizeIndexesRef = normalizeIndexesUpdate;\r\n\t}\r\n\tvar sections = splitAtEvery(latLons);\r\n\treturn sections;\r\n}\r\n\r\nfunction shouldSplit(array, i, val) {\r\n\tif (array.length <= i + 1) return false;\r\n\treturn Math.abs(val.latLng.lng) == 179.99999 && Math.abs(array[i + 1].latLng.lng) == 179.99999;\r\n}\r\n\r\nfunction splitAtEvery(latLons) {\r\n\tvar sections = [];\r\n\tvar arrayClone = latLons.slice(0);\r\n\t$j.each(arrayClone, function (i, item) {\r\n\t\tvar sectionsLength = 0;\r\n\t\tfor (var j = 0; j < sections.length; j++) {\r\n\t\t\tvar section = sections[j];\r\n\t\t\tsectionsLength += section.length;\r\n\t\t}\r\n\t\tif (shouldSplit(latLons, i, item) == true) {\r\n\t\t\tsections.push(arrayClone.slice(0, i + 1 - sectionsLength));\r\n\t\t\tarrayClone = arrayClone.slice(i + 1 - sectionsLength, arrayClone.length);\r\n\t\t}\r\n\t});\r\n\tsections.push(arrayClone);\r\n\treturn sections;\r\n}\r\n\r\nexport function createPositionLinesForTrip(trip, asset, positions) {\r\n\t// still draw the lines even if the preference is not set if we're only including start/end positions\r\n\tif (!asset.DrawLinesBetweenPositions && trip.IncludeAllPositions) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar linePaths = createLinePathsFromPositions(positions);\r\n\tvar line = createPolylineFromLinePaths(linePaths, asset, trip, \"4 8\");\r\n\ttrkData.trips.mapLinesByTripId[trip.Id] = line;\r\n\tif (asset.SnapLinesToRoads && options.enabledFeatures.indexOf(\"UI_SNAP_LINES_TO_ROADS\") !== -1) {\r\n\t\t// kind of hacky to set the first position of the line this way\r\n\t\t// as the first position may not be here?\r\n\t\ttrkData.trips.mapLinesByTripId[trip.Id].setLatLngs(linePaths[0][0].latLng); // what problem is this hack fixing?\r\n\t\tsnapLinePathsToRoads(trkData.trips.mapLinesByTripId[trip.Id], linePaths);\r\n\t}\r\n\taddItemToMap(line);\r\n}\r\n\r\nexport function createPositionLinesForAsset(asset, positions, viewMode) {\r\n\tif (!asset.DrawLinesBetweenPositions) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tconsole.log(\"createPositionLinesForAsset\");\r\n\tvar dataSource = trkData.history;\r\n\tif (viewMode === viewModes.SHARED_VIEW) {\r\n\t\tdataSource = trkData.sharedView;\r\n\t} else {\r\n\t\tviewMode = viewModes.NORMAL;\r\n\t}\r\n\r\n\tvar linePaths = createLinePathsFromPositions(positions);\r\n\tvar line = createPolylineFromLinePaths(linePaths, asset);\r\n\tdataSource.mapLinesByAssetId[asset.Id] = line;\r\n\r\n\tif (asset.SnapLinesToRoads && options.enabledFeatures.indexOf(\"UI_SNAP_LINES_TO_ROADS\") !== -1) {\r\n\t\t// kind of hacky to set the first position of the line this way\r\n\t\t// as the first position may not be here?\r\n\t\tif (linePaths.length > 0 && linePaths[0].length > 0) {\r\n\t\t\tdataSource.mapLinesByAssetId[asset.Id].setLatLngs(linePaths[0][0].latLng); // what problem is this hack fixing?\r\n\t\t\tsnapLinePathsToRoads(dataSource.mapLinesByAssetId[asset.Id], linePaths);\r\n\t\t} else {\r\n\t\t\tdataSource.mapLinesByAssetId[asset.Id].setLatLngs([]);\r\n\t\t}\r\n\t}\r\n\taddItemToMap(line, null, viewMode);\r\n}\r\n\r\n//function createPositionLinesForAsset(asset, positions) {\r\n// if (!asset.DrawLinesBetweenPositions) {\r\n// return;\r\n// }\r\n\r\n// var linePaths = createLinePathsFromPositions(positions);\r\n// var line = createPolylineFromLinePaths(linePaths, asset);\r\n// trkData.history.mapLinesByAssetId[asset.Id] = line;\r\n\r\n// if (asset.SnapLinesToRoads && options.enabledFeatures.indexOf('UI_SNAP_LINES_TO_ROADS') !== -1) {\r\n// // kind of hacky to set the first position of the line this way\r\n// // as the first position may not be here?\r\n// if (linePaths.length > 0 && linePaths[0].length > 0) {\r\n// trkData.history.mapLinesByAssetId[asset.Id].setLatLngs(linePaths[0][0].latLng); // what problem is this hack fixing?\r\n// snapLinePathsToRoads(trkData.history.mapLinesByAssetId[asset.Id], linePaths);\r\n// } else {\r\n// trkData.history.mapLinesByAssetId[asset.Id].setLatLngs([]);\r\n// }\r\n// }\r\n// if (state.activeMapMode === mapModes.HISTORY) {\r\n// addItemToMap(line);\r\n// }\r\n//}\r\n\r\n//function createPositionLinesForSharedViewAsset(asset, positions) {\r\n// if (!asset.DrawLinesBetweenPositions) {\r\n// return;\r\n// }\r\n\r\n// var linePaths = createLinePathsFromPositions(positions);\r\n// var line = createPolylineFromLinePaths(linePaths, asset);\r\n// trkData.sharedView.mapLinesByAssetId[asset.Id] = line;\r\n\r\n// if (asset.SnapLinesToRoads && options.enabledFeatures.indexOf('UI_SNAP_LINES_TO_ROADS') !== -1) {\r\n// // kind of hacky to set the first position of the line this way\r\n// // as the first position may not be here?\r\n// if (linePaths.length > 0 && linePaths[0].length > 0) {\r\n// trkData.sharedView.mapLinesByAssetId[asset.Id].setLatLngs(linePaths[0][0].latLng); // what problem is this hack fixing?\r\n// snapLinePathsToRoads(trkData.sharedView.mapLinesByAssetId[asset.Id], linePaths);\r\n// } else {\r\n// trkData.sharedView.mapLinesByAssetId[asset.Id].setLatLngs([]);\r\n// }\r\n// }\r\n// addItemToMap(line, null, viewModes.SHARED_VIEW);\r\n//}\r\n","import trkData from \"./data.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport { viewModes, mapModes, trkDataGroups } from \"./const.js\";\r\nimport strings from \"./strings.js\";\r\nimport state from \"./state.js\";\r\nimport { findAssetById } from \"./assets.js\";\r\nimport preferences from \"./preferences.js\";\r\nimport { updateActiveAssetInformation } from \"./assets-active.js\";\r\nimport { addItemToMap, addItemsToMap } from \"./map-items.js\";\r\nimport { updateGroupFunctionBadges, updateAssetFunctionBadges } from \"./badges.js\";\r\nimport { normalizeAssetData } from \"./assets.js\";\r\nimport { addPositionMarkerToPoint } from \"./marker.js\";\r\nimport { createMarkerCluster } from \"./marker-cluster.js\";\r\nimport { createFilteredEventsList, EVENTS_EMERGENCY, EVENTS_TEXT_MESSAGE, EVENTS_STATUS, EVENTS_ALERT } from \"./asset-events.js\";\r\nimport { includeRowIfNotNull } from \"./dom-util.js\";\r\nimport {\r\n\tconvertToLatLngPreference,\r\n\tconvertSpeedToPreference,\r\n\tconvertFromMetresToUserDistancePreference,\r\n} from \"./preferences.js\";\r\nimport { createMarkerPath } from \"./marker-path.js\";\r\nimport { createDialogTitleFragment } from \"./dom-util.js\";\r\nimport { getAssetDataGroupForCurrentViewMode } from \"./map-viewmode.js\";\r\nimport { createPositionLinesForAsset, createPositionLinesForTrip } from \"./geometry-create.js\";\r\nimport { toggleAssetActive } from \"./asset-select.js\";\r\nimport { switchMapMode, map } from \"./map-base.js\";\r\nimport { setMapBounds } from \"./map-bounds.js\";\r\nimport { openMarkerForPosition, playbackStart } from \"./playback.js\";\r\nimport {getHighestPriorityEventType} from \"./marker.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport $j from \"jquery\";\r\nimport _ from \"lodash\";\r\nimport L from \"leaflet\";\r\nimport { el, text, setChildren } from \"redom\"; // https://redom.js.org/\r\nimport moment from \"moment\"; // https://www.npmjs.com/package/moment\r\n\r\n\r\nexport function createHistoryPositionResults() {\r\n\ttrkData.assets.forEach(function (asset) {\r\n\t\tupdateAssetFunctionBadges(getAssetDataGroupForCurrentViewMode(), asset.Id);\r\n\t});\r\n\tupdateGroupFunctionBadges(getAssetDataGroupForCurrentViewMode(), null, \"asset\");\r\n\r\n\t_.each(trkData.history.positionsByAssetId, function (assetHistoryPositions, assetId) {\r\n\t\tvar isWithinSearchRange = assetHistoryPositions !== undefined && assetHistoryPositions.ResultsForSearch === true;\r\n\t\tif (isWithinSearchRange) {\r\n\t\t\tdomNodes.assets[assetId].forEach(function (assetNode) {\r\n\t\t\t\tvar timeIndicator = assetNode.querySelector(\".notifications\");\r\n\t\t\t\ttimeIndicator.classList.add(\"recent-positions\");\r\n\t\t\t});\r\n\t\t}\r\n\t});\r\n\r\n\tupdateActiveAssetInformation(viewModes.NORMAL);\r\n\r\n\tcreateFilteredEventsList();\r\n}\r\n\r\n// Spawns clusters for the historic positions of an asset, given the data structure\r\n// for an asset.\r\n// Typically called with either live data, or data from /GPSService.asmx/GetAssetPositionsForDateRange\r\nexport function processAssetHistoryPositionsResult(assetResult) {\r\n\r\n\t// console.log(assetResult);\r\n\r\n\t// add the results to the bounds\r\n\tvar asset = findAssetById(assetResult.Id);\r\n\ttrkData.history.positions.push(assetResult);\r\n\ttrkData.history.positionsByAssetId[asset.Id] = assetResult;\r\n\r\n\tassetResult.Positions.forEach(function (position) {\r\n\t\tif (trkData.positionsById[position.Id] === undefined) {\r\n\t\t\ttrkData.positionsById[position.Id] = normalizeAssetData(asset.Id, \"position\", position);\r\n\t\t}\r\n\t\ttrkData.history.normalizedPositions.push(trkData.positionsById[position.Id]);\r\n\t\tif (trkData.history.normalizedPositionsByAssetId[asset.Id] === undefined) {\r\n\t\t\ttrkData.history.normalizedPositionsByAssetId[asset.Id] = [];\r\n\t\t}\r\n\t\ttrkData.history.normalizedPositionsByAssetId[asset.Id].push(trkData.positionsById[position.Id]);\r\n\t});\r\n\r\n\tif (assetResult.MessageCounts !== null) {\r\n\t\ttrkData.history.messageCounts.push(assetResult.MessageCounts);\r\n\t\ttrkData.history.messageCountsByAssetId[asset.Id] = assetResult.MessageCounts;\r\n\t}\r\n\r\n\tif (assetResult.ResultsForSearch) {\r\n\t\ttrkData.history.assetIdsWithResults[asset.Id] = true;\r\n\t}\r\n\r\n\tvar assetPositions = assetResult.Positions;\r\n\tvar assetVisiblePositions = _.filter(assetResult.Positions, function (item) {\r\n\t\treturn !item.IsHidden;\r\n\t});\r\n\tvar lastVisiblePositionId = null;\r\n\tvar firstVisiblePositionId = null;\r\n\tif (assetVisiblePositions.length > 0) {\r\n\t\tlastVisiblePositionId = assetVisiblePositions[0].Id;\r\n\t\tfirstVisiblePositionId = assetVisiblePositions[assetVisiblePositions.length - 1].Id;\r\n\t}\r\n\r\n\t// console.log(\"visible positions: \" + lastVisiblePositionId + \", \" + firstVisiblePositionId);\r\n\tif (assetPositions.length > 0) {\r\n\t\t//const eventlessFilter = document.querySelector('#form-history-date-range input#filter-eventless')?.checked;\r\n\r\n\t\tif (preferences.PREFERENCE_GROUP_POSITIONS && trkData.history.markerClustersByAssetId[asset.Id] === undefined) {\r\n\t\t\ttrkData.history.markerClustersByAssetId[asset.Id] = createMarkerCluster(asset, null);\r\n\t\t}\r\n\r\n\t\t// The opacity of the positions will get lower as the positions get older\r\n\t\t// to a minimum of 70/255 opacity.\r\n\t\t// The opacity will always be 1 if the user has not checked the\r\n\t\t// \"Fade historic positions by time\" preference in their user settings.\r\n\t\tlet alpha = 255;\r\n\t\tconst alphaIncrement =\r\n\t\t\tpreferences.PREFERENCE_ALPHA_POSITIONS ?\r\n\t\t\t185 / assetVisiblePositions.length\r\n\t\t\t: 0;\r\n\r\n\t\t// positions are sorted from newest->last\r\n\t\tvar totalAssetVisiblePositions = assetVisiblePositions.length;\r\n\t\tvar clusterMarkers = [];\r\n\t\tfor (var j = 0; j < assetPositions.length; j++) {\r\n\t\t\t// add position to map and position filters\r\n\t\t\tvar position = assetPositions[j];\r\n\r\n\t\t\tif (trkData.positionsById[position.Id] === undefined) {\r\n\t\t\t\ttrkData.positionsById[position.Id] = normalizeAssetData(asset.Id, \"position\", position);\r\n\t\t\t}\r\n\r\n\t\t\tif (trkData.history.latestPosition == null || trkData.history.latestPosition.Epoch < position.Epoch) {\r\n\t\t\t\ttrkData.history.latestPosition = position;\r\n\t\t\t}\r\n\r\n\t\t\t// (Inefficiently) fetch the highest-priority event from each position\r\n\t\t\tconst evtType = getHighestPriorityEventType(position.Events);\r\n\r\n\t\t\tconst isFirst = totalAssetVisiblePositions > 1 && firstVisiblePositionId === position.Id;\r\n\t\t\tconst isLast = totalAssetVisiblePositions > 1 && lastVisiblePositionId === position.Id;\r\n\r\n\t\t\t// TODO: Display markers only for \"important\" events when requested\r\n\t\t\t// Skip this logic when we only have one position (i.e. on live mode)\r\n\t\t\tif (\r\n\t\t\t\t\ttrue || // TODO: !eventlessFilter ||\r\n\t\t\t\t\tassetPositions.length == 1 ||\r\n\t\t\t\t\tisFirst ||\r\n\t\t\t\t\tisLast ||\r\n\t\t\t\t\tEVENTS_STATUS.includes(evtType) ||\r\n\t\t\t\t\tEVENTS_ALERT.includes(evtType) ||\r\n\t\t\t\t\tEVENTS_EMERGENCY.includes(evtType) ||\r\n\t\t\t\t\tEVENTS_TEXT_MESSAGE.includes(evtType)\r\n\t\t\t) {\r\n\t\t\t\tconst marker = addPositionMarkerToPoint(\r\n\t\t\t\t\t[position.Lat, position.Lng],\r\n\t\t\t\t\tfalse,\r\n\t\t\t\t\tposition,\r\n\t\t\t\t\tasset,\r\n\t\t\t\t\talpha,\r\n\t\t\t\t\tpreferences.PREFERENCE_GROUP_POSITIONS,\r\n\t\t\t\t\tisFirst,\r\n\t\t\t\t\tisLast,\r\n\t\t\t\t\ttrkDataGroups.NORMAL_HISTORY,\r\n\t\t\t\t\tundefined,\r\n\t\t\t\t);\r\n\t\t\t\tif (!position.IsHidden) {\r\n\t\t\t\t\tclusterMarkers.push(marker);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t//markersAdded++;\r\n\t\t\t//updateLoadingMarkerProgress(markersAdded, totalMarkers);\r\n\r\n\t\t\talpha -= alphaIncrement;\r\n\t\t}\r\n\r\n\t\ttrkData.history.markersByAssetId = _.groupBy(trkData.history.markers, function (marker) {\r\n\t\t\treturn marker.data.assetId;\r\n\t\t});\r\n\t\ttrkData.history.markersByPositionId = _.keyBy(trkData.history.markers, function (marker) {\r\n\t\t\treturn marker.data.location.Id;\r\n\t\t});\r\n\r\n\t\t// addItemsToMap(clusterMarkers)\r\n\r\n\t\t/// TODO: create one clusterer per asset ID\r\n\t\t// if (preferences.PREFERENCE_GROUP_POSITIONS && trkData.history.markerClustersByAssetId[asset.Id] !== undefined) {\r\n\t\t// \ttrkData.history.markerClustersByAssetId[asset.Id].clearLayers();\r\n\t\t// \ttrkData.history.markerClustersByAssetId[asset.Id].addLayers(clusterMarkers);\r\n\t\t// \tif (state.activeMapMode === mapModes.HISTORY) {\r\n\t\t// \t\taddItemToMap(trkData.history.markerClustersByAssetId[asset.Id]);\r\n\t\t// \t}\r\n\t\t// }\r\n\t\tif (preferences.PREFERENCE_GROUP_POSITIONS && trkData.history.markerClustersByAssetId[asset.Id] !== undefined) {\r\n\t\t\ttrkData.history.markerClustersByAssetId[asset.Id].empty();\r\n\t\t\ttrkData.history.markerClustersByAssetId[asset.Id].multiAdd(clusterMarkers);\r\n\t\t\tif (state.activeMapMode === mapModes.HISTORY) {\r\n\t\t\t\taddItemToMap(trkData.history.markerClustersByAssetId[asset.Id]);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tcreatePositionLinesForAsset(asset, assetVisiblePositions, viewModes.NORMAL);\r\n\t}\r\n}\r\n\r\nfunction updateLoadingMarkerProgress(current, total) {\r\n\tvar percentage = Math.ceil((current / total) * 100);\r\n}\r\n\r\nexport function createPositionHistorySummary(asset, trip, fromId, toId, e) {\r\n\tconsole.log(asset.Id + \" - \" + fromId + \" - \" + toId);\r\n\tvar dialog = $(domNodes.infoDialogs.positionHistory);\r\n\tdialog.data(\"assetId\", asset.Id);\r\n\tvar allPositions = [];\r\n\r\n\tif (trip !== undefined && trip !== null) {\r\n\t\tdialog.data(\"tripId\", trip.Id);\r\n\t\tallPositions = trkData.trips.positionsByTripId[trip.Id].Positions;\r\n\t} else if (state.activeViewMode === viewModes.NORMAL) {\r\n\t\tdialog.removeData(\"tripId\");\r\n\t\tallPositions = trkData.history.positionsByAssetId[asset.Id].Positions;\r\n\t} else if (state.activeViewMode === viewModes.SHARED_VIEW) {\r\n\t\tdialog.removeData(\"tripId\");\r\n\t\tallPositions = trkData.sharedView.positionsByAssetId[asset.Id].Positions;\r\n\t}\r\n\r\n\tvar endIndex = 0;\r\n\tvar fromIndex = allPositions.length - 1;\r\n\tif (fromId !== undefined && fromId !== null) {\r\n\t\tvar from = _.find(allPositions, { Id: fromId });\r\n\t\tif (from !== undefined) {\r\n\t\t\tfromIndex = _.indexOf(allPositions, from);\r\n\t\t}\r\n\t}\r\n\tif (toId !== undefined && toId !== null) {\r\n\t\tvar to = _.find(allPositions, { Id: toId });\r\n\t\tif (to !== undefined) {\r\n\t\t\tendIndex = _.indexOf(allPositions, to);\r\n\t\t}\r\n\t}\r\n\r\n\t// swap indexes if user chose a from after the to\r\n\tif (endIndex > fromIndex) {\r\n\t\tvar fromIndexBefore = fromIndex;\r\n\t\tfromIndex = endIndex;\r\n\t\tendIndex = fromIndexBefore;\r\n\t}\r\n\r\n\tvar historyPositions = allPositions.slice(endIndex, fromIndex + 1);\r\n\r\n\tvar distance = 0;\r\n\tfor (var i = endIndex; i <= fromIndex; i++) {\r\n\t\tif (i + 1 <= fromIndex) {\r\n\t\t\tdistance += L.latLng(allPositions[i].Lat, allPositions[i].Lng).distanceTo(\r\n\t\t\t\tL.latLng(allPositions[i + 1].Lat, allPositions[i + 1].Lng)\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n\r\n\tvar begin = allPositions[fromIndex];\r\n\tvar end = allPositions[endIndex];\r\n\tvar beginTime = moment.unix(begin.Epoch);\r\n\tvar endTime = moment.unix(end.Epoch);\r\n\tvar duration = moment.duration(endTime.diff(beginTime));\r\n\tvar durationDays = Math.floor(duration.asDays());\r\n\tvar durationHours = (\"0\" + Math.floor(duration.asHours() % 24)).slice(-2);\r\n\tvar durationDisplay =\r\n\t\t(durationDays > 0 ? durationDays + \".\" : \"\") +\r\n\t\tdurationHours +\r\n\t\tmoment.utc(duration.asMilliseconds()).format(\":mm:ss\");\r\n\tvar averageSpeed = duration.asSeconds() > 0 ? convertSpeedToPreference(distance / duration.asSeconds()) : 0;\r\n\tvar maxSpeed = convertSpeedToPreference(_.max(_.map(historyPositions, \"Speed\")));\r\n\r\n\tvar beginTimeElements = [text(begin.Time)];\r\n\tif (begin.IsAcc === false) {\r\n\t\tbeginTimeElements = [el(\"span.inaccurate\", begin.Time), el(\"sup\", { title: strings.TIME_INACCURATE }, \"*\")];\r\n\t}\r\n\r\n\tvar beginAddress = includeRowIfNotNull(strings.ADDRESS, begin.Address, { style: { verticalAlign: \"top\" } });\r\n\tvar beginLatLng = includeRowIfNotNull(\r\n\t\tstrings.LAT_LNG,\r\n\t\tel(\r\n\t\t\t\"a.location\",\r\n\t\t\t{ href: \"#\", dataset: { marker: begin.Id } },\r\n\t\t\tconvertToLatLngPreference(begin.DisplayLat, begin.DisplayLng, begin.Grid)\r\n\t\t)\r\n\t);\r\n\r\n\tvar endTimeElements = [text(end.Time)];\r\n\tif (end.IsAcc === false) {\r\n\t\tbeginTimeElements = [el(\"span.inaccurate\", end.Time), el(\"sup\", { title: strings.TIME_INACCURATE }, \"*\")];\r\n\t}\r\n\r\n\tvar endAddress = includeRowIfNotNull(strings.ADDRESS, end.Address, { style: { verticalAlign: \"top\" } });\r\n\tvar endLatLng = includeRowIfNotNull(\r\n\t\tstrings.LAT_LNG,\r\n\t\tel(\r\n\t\t\t\"a.location\",\r\n\t\t\t{ href: \"#\", dataset: { marker: end.Id } },\r\n\t\t\tconvertToLatLngPreference(end.DisplayLat, end.DisplayLng, end.Grid)\r\n\t\t)\r\n\t);\r\n\r\n\tconst options = [];\r\n\tallPositions.forEach(function (position) {\r\n\t\toptions.push(el(\"option\", { value: position.Id }, position.Time));\r\n\t});\r\n\tvar positionSelect = el(\"select.form-control.time-picker\", options);\r\n\r\n\tvar positionEndSelect = positionSelect.cloneNode(true);\r\n\tvar beginOption = positionSelect.querySelector('option[value=\"' + begin.Id + '\"]');\r\n\tif (beginOption != null) {\r\n\t\tbeginOption.selected = true;\r\n\t}\r\n\tvar endOption = positionEndSelect.querySelector('option[value=\"' + end.Id + '\"]');\r\n\tif (endOption != null) {\r\n\t\tendOption.selected = true;\r\n\t}\r\n\r\n\tvar beginHeader = el(\r\n\t\t\"div#positions-begin-header.input-group\",\r\n\t\tel(\"div.input-group-prepend\", el(\"span.input-group-text\", strings.BEGIN))\r\n\t);\r\n\tvar endHeader = el(\r\n\t\t\"div#positions-end-header.input-group\",\r\n\t\tel(\"div.input-group-prepend\", el(\"span.input-group-text\", strings.END))\r\n\t);\r\n\r\n\tvar beginCard = el(\"div.card.mb-2\", [\r\n\t\tel(\"div.card-header.p-0.d-flex.align-items-center\", beginHeader),\r\n\t\tel(\"div.card-body.p-2\", el(\"table\", el(\"tbody\", [beginAddress, beginLatLng]))),\r\n\t]);\r\n\tvar endCard = el(\"div.card.mb-2\", [\r\n\t\tel(\"div.card-header.p-0.d-flex.align-items-center\", endHeader),\r\n\t\tel(\"div.card-body.p-2\", el(\"table\", el(\"tbody\", [endAddress, endLatLng]))),\r\n\t]);\r\n\tvar detailsCard = el(\"div.card.mb-2\", [\r\n\t\tel(\"div.card-header.p-1.pl-2.pr-2.d-flex.align-items-center\", strings.DETAILS),\r\n\t\tel(\r\n\t\t\t\"div.card-body.p-2\",\r\n\t\t\tel(\r\n\t\t\t\t\"table\",\r\n\t\t\t\tel(\"tbody\", [\r\n\t\t\t\t\tincludeRowIfNotNull(strings.POSITIONS, historyPositions.length),\r\n\t\t\t\t\tincludeRowIfNotNull(strings.DISTANCE, convertFromMetresToUserDistancePreference(distance)),\r\n\t\t\t\t\tincludeRowIfNotNull(strings.AVERAGE_SPEED, averageSpeed),\r\n\t\t\t\t\tincludeRowIfNotNull(strings.MAX_SPEED, maxSpeed),\r\n\t\t\t\t\tincludeRowIfNotNull(strings.DURATION, durationDisplay),\r\n\t\t\t\t])\r\n\t\t\t)\r\n\t\t),\r\n\t]);\r\n\tvar content = el(\"div.markercontent\", [beginCard, endCard, detailsCard]);\r\n\r\n\tsetChildren(dialog[0], content);\r\n\r\n\tdocument.getElementById(\"positions-begin-header\").appendChild(positionSelect);\r\n\tdocument.getElementById(\"positions-end-header\").appendChild(positionEndSelect);\r\n\r\n\tif (trip !== undefined && trip !== null) {\r\n\t\tdialog.dialog(\"option\", \"title\", createDialogTitleFragment(trip.Name, asset.Name));\r\n\t} else {\r\n\t\tdialog.dialog(\r\n\t\t\t\"option\",\r\n\t\t\t\"title\",\r\n\t\t\tcreateDialogTitleFragment(asset.Name, options.hideDeviceName ? null : asset.DeviceName)\r\n\t\t);\r\n\t}\r\n\tvar dialogTitleBar = $(\"div.ui-dialog-titlebar\", dialog.parent());\r\n\tvar titleIcon =\r\n\t\t\"url(\" + createMarkerPath(asset.Class, asset.Color, null, null, asset.Id, false, null, false, false) + \")\";\r\n\tdialogTitleBar[0].style.backgroundImage = titleIcon;\r\n\r\n\t// quick actions\r\n\tvar historyQuickActions = document.getElementById(\"position-history-dialog-actions-list\");\r\n\tvar actionAssetOptions = historyQuickActions.querySelector('a[data-action=\"path-asset-options\"]');\r\n\tvar actionHideAsset = historyQuickActions.querySelector('a[data-action=\"path-asset-hide\"]');\r\n\tvar actionHideTrip = historyQuickActions.querySelector('a[data-action=\"path-trip-hide\"]');\r\n\tactionAssetOptions.setAttribute(\"data-asset-id\", asset.Id);\r\n\tactionHideAsset.setAttribute(\"data-asset-id\", asset.Id);\r\n\tvar actionTripOptions = historyQuickActions.querySelector('a[data-action=\"path-trip-options\"]');\r\n\tif (trip !== undefined && trip !== null) {\r\n\t\tactionTripOptions.classList.remove(\"disabled\");\r\n\t\tactionHideTrip.classList.remove(\"disabled\");\r\n\t\tactionTripOptions.setAttribute(\"data-trip-id\", trip.Id);\r\n\t\tactionHideTrip.setAttribute(\"data-trip-id\", trip.Id);\r\n\t\tactionHideTrip.setAttribute(\"data-journey-id\", trip.JourneyId);\r\n\t\tactionHideAsset.classList.remove(\"disabled\");\r\n\t} else {\r\n\t\tactionHideAsset.classList.add(\"disabled\");\r\n\t\tactionHideTrip.classList.add(\"disabled\");\r\n\t\tactionTripOptions.classList.add(\"disabled\");\r\n\t}\r\n\tvar actionHistory = historyQuickActions.querySelector('a[data-action=\"path-history\"]');\r\n\tactionHistory.setAttribute(\"data-from\", begin.Time);\r\n\tactionHistory.setAttribute(\"data-to\", end.Time);\r\n\tactionHistory.setAttribute(\"data-asset-id\", asset.Id);\r\n\r\n\tif (e !== undefined) {\r\n\t\tconst mouseEvent = e.originalEvent;\r\n\t\tmouseEvent.preventDefault = true;\r\n\t\t// position dialog if not already opened\r\n\t\tif (!dialog.dialog(\"isOpen\")) {\r\n\t\t\tdialog.dialog(\"option\", \"position\", {\r\n\t\t\t\tof: mouseEvent,\r\n\t\t\t\tmy: \"left+15 center\",\r\n\t\t\t\tat: \"right center\",\r\n\t\t\t\tcollision: \"flipfit\",\r\n\t\t\t\twithin: domNodes.map,\r\n\t\t\t\tusing: function (param1, param2) {\r\n\t\t\t\t\t$(this).css(param1);\r\n\t\t\t\t},\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\t//$('#position-history-dialog').closest('.ui-dialog').position({ of: mouseEvent, my: 'left+15 center', at: 'right center' });\r\n\t\t}\r\n\t}\r\n\tdialog.dialog(\"open\");\r\n}\r\n\r\nexport function getPositionInHistoryRelative(assetId, positionId, tripId, shift) {\r\n\tlet assetPositions;\r\n\tif (tripId !== null) {\r\n\t\tassetPositions = trkData.trips.positionsByTripId[tripId].Positions;\r\n\t} else if (state.activeViewMode === viewModes.NORMAL) {\r\n\t\tassetPositions = trkData.history.positionsByAssetId[assetId].Positions;\r\n\t} else if (state.activeViewMode === viewModes.SHARED_VIEW) {\r\n\t\tassetPositions = trkData.sharedView.positionsByAssetId[assetId].Positions;\r\n\t}\r\n\r\n\tfor (var j = 0; j < assetPositions.length; j++) {\r\n\t\tif (assetPositions[j].Id == positionId) {\r\n\t\t\t// current position is assetPositions[j], get previous one and show it\r\n\t\t\tvar indexShift = j + shift;\r\n\t\t\tif (indexShift < 0 || indexShift > assetPositions.length) {\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\r\n\t\t\tvar shiftedPosition = assetPositions[indexShift];\r\n\t\t\treturn shiftedPosition;\r\n\t\t}\r\n\t}\r\n\r\n\treturn null;\r\n}\r\n\r\nexport function loadHistory(assetIds, replay) {\r\n\tvar deferreds = [];\r\n\tfor (var i = 0; i < trkData.assets.length; i++) {\r\n\t\tvar makeActive = $j.inArray(trkData.assets[i].Id, assetIds) !== -1;\r\n\t\tdeferreds.push(toggleAssetActive(trkData.assets[i].Id, makeActive, false));\r\n\t}\r\n\r\n\t// todo: this should be a helper function\r\n\tif (state.activeMapMode === mapModes.LIVE) {\r\n\t\t// always show the control area if it is hidden\r\n\t\tif ($(\"#controlarea\").is(\":hidden\")) {\r\n\t\t\t$(\"#controlarea\").slideDown();\r\n\t\t}\r\n\t\tdeferreds.push(switchMapMode(mapModes.HISTORY, null, true));\r\n\t}\r\n\r\n\tPromise.all(deferreds).then(function () {\r\n\t\tsetMapBounds();\r\n\r\n\t\tif (replay) {\r\n\t\t\tvar assetId = assetIds[0];\r\n\t\t\tif (trkData.history.positionsByAssetId[assetId] !== undefined) {\r\n\t\t\t\tvar positionsForAsset = trkData.history.positionsByAssetId[assetId].Positions;\r\n\t\t\t\tif (positionsForAsset !== undefined && positionsForAsset.length > 0) {\r\n\t\t\t\t\tvar latestPositionForAsset = positionsForAsset[positionsForAsset.length - 1];\r\n\t\t\t\t\topenMarkerForPosition(latestPositionForAsset.Id);\r\n\t\t\t\t\tplaybackStart(assetId, latestPositionForAsset.Id, null);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n}\r\n","import trkData from \"./data.js\";\r\nimport state from \"./state.js\";\r\nimport strings from \"./strings.js\";\r\nimport { mapModes } from \"./const.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport { switchMapMode } from \"./map-base.js\";\r\nimport { findAssetById } from \"./assets.js\";\r\nimport { openAssetLatestPosition } from \"./asset-live.js\";\r\nimport user from \"./user.js\";\r\nimport { map } from \"./map-base.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport $j from \"jquery\";\r\nimport _ from \"lodash\";\r\nimport L from \"leaflet\";\r\nimport moment from \"moment\"; // https://www.npmjs.com/package/moment\r\n\r\nexport function initLiveAssetsFollow() {\r\n\t$j(\"#live-follow-status\").on(\"click\", \"#live-follow-close\", function (e) {\r\n\t\te.preventDefault();\r\n\t\tstopLiveFollowing();\r\n\t});\r\n\r\n\t$j(\"#live-follow-status\").on(\"click\", \"#live-follow-assets\", function (e) {\r\n\t\te.preventDefault();\r\n\t\tif (state.liveFollow.asset == null) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif (state.liveFollow.assets.length == 1) {\r\n\t\t\topenAssetLatestPosition(state.liveFollow.assets[0]);\r\n\t\t} else {\r\n\t\t\t// pan/zoom to include all assets latest positions\r\n\t\t\tvar bounds = L.latLngBounds();\r\n\t\t\tfor (var i = 0; i < state.liveFollow.assets.length; i++) {\r\n\t\t\t\tvar assetId = state.liveFollow.assets[i].Id;\r\n\t\t\t\tif (trkData.live.latestPositionsByAssetId[assetId] !== undefined) {\r\n\t\t\t\t\tbounds.extend(\r\n\t\t\t\t\t\tL.latLng(\r\n\t\t\t\t\t\t\ttrkData.live.latestPositionsByAssetId[assetId].Position.Lat,\r\n\t\t\t\t\t\t\ttrkData.live.latestPositionsByAssetId[assetId].Position.Lng\r\n\t\t\t\t\t\t)\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (bounds.isValid()) {\r\n\t\t\t\tmap.fitBounds(bounds, { padding: [10, 10] });\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n}\r\n\r\nexport function updateLiveFollowStatus() {\r\n\tvar assetsFollowed = state.liveFollow.assets.length;\r\n\tif (assetsFollowed === 0) {\r\n\t\tdomNodes.followLiveStatus.classList.remove(\"is-visible\");\r\n\t\treturn;\r\n\t}\r\n\r\n\tif (assetsFollowed === 1) {\r\n\t\t$(\"#live-follow-assets\").text(state.liveFollow.assets[0].Name).prop(\"title\", state.liveFollow.assets[0].Name);\r\n\t} else {\r\n\t\tvar title = \"\";\r\n\t\tfor (var i = 0; i < assetsFollowed; i++) {\r\n\t\t\tif (i > 0) {\r\n\t\t\t\ttitle += \", \";\r\n\t\t\t}\r\n\t\t\ttitle += state.liveFollow.assets[i].Name;\r\n\t\t}\r\n\t\t$(\"#live-follow-assets\")\r\n\t\t\t.text(strings.MULTIPLE_ASSETS.replace(\"{0}\", state.liveFollow.assets.length))\r\n\t\t\t.prop(\"title\", title);\r\n\t}\r\n\tdomNodes.followLiveStatus.classList.add(\"is-visible\");\r\n\r\n\tif (state.activeMapMode !== mapModes.LIVE) {\r\n\t\t// can only follow in live mode\r\n\t\tswitchMapMode(mapModes.LIVE, null, false);\r\n\t} else {\r\n\t\topenMostRecentLiveFollowPosition();\r\n\t}\r\n}\r\n\r\nexport function toggleFollowAsset(asset) {\r\n\tvar index = $.inArray(asset, state.liveFollow.assets);\r\n\tif (index == -1) {\r\n\t\tliveFollowAsset(asset);\r\n\t} else {\r\n\t\tliveUnfollowAsset(asset);\r\n\t}\r\n\r\n\tupdateLiveFollowStatus();\r\n}\r\n\r\nexport function toggleFollowGroup(group) {\r\n\tvar groupAssets = [];\r\n\t// should this get all assets under the group and its subgroup or just the immediately assigned assets?\r\n\tif (group.Id === \"all-assets\") {\r\n\t\tgroupAssets = trkData.assets;\r\n\t}\r\n\tfor (var i = 0; i < trkData.assets.length; i++) {\r\n\t\tvar asset = trkData.assets[i];\r\n\t\tif ($.inArray(group.Id, asset.GroupIds) != -1) {\r\n\t\t\tgroupAssets.push(asset);\r\n\t\t}\r\n\t}\r\n\r\n\tif (isFollowingGroup(group)) {\r\n\t\t// all assets being followed, so un follow them\r\n\t\tfor (var i = 0; i < groupAssets.length; i++) {\r\n\t\t\tliveUnfollowAsset(groupAssets[i]);\r\n\t\t}\r\n\t} else {\r\n\t\t// not all assets being followed, follow ones that are not\r\n\t\tfor (var i = 0; i < groupAssets.length; i++) {\r\n\t\t\tliveFollowAsset(groupAssets[i]);\r\n\t\t}\r\n\t}\r\n\r\n\tupdateLiveFollowStatus();\r\n}\r\n\r\nexport function liveFollowAsset(asset) {\r\n\tstate.liveFollow.isActive = true;\r\n\tstate.liveFollow.asset = asset;\r\n\tvar index = $.inArray(asset, state.liveFollow.assets);\r\n\tif (index == -1) {\r\n\t\tstate.liveFollow.assets.push(asset);\r\n\t}\r\n}\r\n\r\nfunction liveUnfollowAsset(asset) {\r\n\tvar index = $.inArray(asset, state.liveFollow.assets);\r\n\tif (index !== -1) {\r\n\t\tstate.liveFollow.assets.splice(index, 1);\r\n\t}\r\n\tif (state.liveFollow.assets.length === 0) {\r\n\t\tstopLiveFollowing();\r\n\t}\r\n}\r\n\r\nexport function stopLiveFollowing() {\r\n\tdomNodes.followLiveStatus.classList.remove(\"is-visible\");\r\n\t$j(\"#live-follow-updated-time\").data(\"time\", null);\r\n\tstate.liveFollow.isActive = false;\r\n\tstate.liveFollow.asset = null;\r\n\tstate.liveFollow.assets = [];\r\n\tstate.liveFollow.groups = [];\r\n}\r\n\r\nexport function isFollowingGroup(group) {\r\n\tvar groupAssets = [];\r\n\tfor (var i = 0; i < trkData.assets.length; i++) {\r\n\t\tvar asset = trkData.assets[i];\r\n\t\tif ($j.inArray(group.Id, asset.GroupIds) != -1) {\r\n\t\t\tgroupAssets.push(asset);\r\n\t\t}\r\n\t}\r\n\tif (groupAssets.length == 0) {\r\n\t\treturn false;\r\n\t}\r\n\tfor (var i = 0; i < groupAssets.length; i++) {\r\n\t\tvar asset = groupAssets[i];\r\n\t\tif ($j.inArray(asset, state.liveFollow.assets) == -1) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\treturn true;\r\n}\r\n\r\nexport function openMostRecentLiveFollowPosition() {\r\n\tif (!state.liveFollow.isActive) {\r\n\t\treturn;\r\n\t}\r\n\r\n\t// todo: pan/zoom to include all followed assets instead?\r\n\tvar latest = null;\r\n\t_.each(state.liveFollow.assets, function (asset) {\r\n\t\tvar assetLatest = trkData.live.latestPositionsByAssetId[asset.Id];\r\n\t\tif (assetLatest === undefined) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tif (latest === null || latest.Position.Epoch < assetLatest.Position.Epoch) {\r\n\t\t\tlatest = assetLatest;\r\n\t\t}\r\n\t});\r\n\r\n\tif (latest !== null) {\r\n\t\topenAssetLatestPosition(findAssetById(latest.AssetId));\r\n\r\n\t\tvar loc = latest.Position;\r\n\t\tvar locationTime = moment(loc.Time, user.dateFormat);\r\n\t\tvar priorTime = $(\"#live-follow-updated-time\").data(\"time\");\r\n\t\tif (priorTime == null) {\r\n\t\t\t$(\"#live-follow-updated-time\").text(loc.Time);\r\n\t\t\t$(\"#live-follow-updated-time\").data(\"time\", locationTime);\r\n\t\t} else {\r\n\t\t\tif (locationTime.isAfter(priorTime)) {\r\n\t\t\t\t$(\"#live-follow-updated-time\").data(\"time\", locationTime);\r\n\t\t\t\t$(\"#live-follow-updated-time\").text(loc.Time);\r\n\t\t\t}\r\n\t\t}\r\n\t} else {\r\n\t\t$(\"#live-follow-updated-time\").text(strings.NEVER);\r\n\t}\r\n}\r\n","// See also notifications.js\r\n\r\nimport trkData from \"./data.js\";\r\nimport { NOTIFICATION_TYPES, NOTIFICATION_PREFIXES } from \"./notifications.js\";\r\nimport domNodes from \"./domNodes\";\r\nimport state from \"./state.js\";\r\nimport { mapModes } from \"./const.js\";\r\nimport {\r\n\tgetNotificationOpacityForTime,\r\n\tupdateTimeBasedNotificationIndicatorsForNode,\r\n\tupdateTimeBasedNotificationIndicatorsForGroup,\r\n} from \"./notifications.js\";\r\n\r\nimport _ from \"lodash\";\r\n\r\nexport function updateAssetNotificationTime(assetId, type, value) {\r\n\tif (trkData.live.notificationTimesByAssetId[assetId] === undefined) {\r\n\t\ttrkData.live.notificationTimesByAssetId[assetId] = {\r\n\t\t\tpositions: null,\r\n\t\t\tevents: null,\r\n\t\t\talerts: null,\r\n\t\t\tstatus: null,\r\n\t\t\tmessages: null,\r\n\t\t};\r\n\t}\r\n\r\n\tif (type !== \"messages\") {\r\n\t\tvalue *= 1000; // epoch seconds to epoch millis for consistency\r\n\t}\r\n\r\n\tvar currentValue = trkData.live.notificationTimesByAssetId[assetId][type];\r\n\tif (currentValue === null || value > currentValue) {\r\n\t\ttrkData.live.notificationTimesByAssetId[assetId][type] = value;\r\n\t\t// todo: update groups here as well, but make sure the above is not in a loop\r\n\t}\r\n}\r\n\r\nexport function updateTimeBasedNotificationIndicatorsForAsset(asset) {\r\n\tif (!domNodes.assets[asset.Id]) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar notificationTypes = NOTIFICATION_TYPES;\r\n\tvar indicators = [];\r\n\tdomNodes.assets[asset.Id].forEach(function (assetNode) {\r\n\t\tindicators.push(assetNode.querySelector(\".notifications\"));\r\n\t});\r\n\r\n\tif (indicators.length === 0) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tnotificationTypes.forEach(function (itemType) {\r\n\t\tvar recentType = \"recent-\" + itemType;\r\n\r\n\t\tif (state.activeMapMode !== mapModes.LIVE) {\r\n\t\t\t// history mode does not support fading icons\r\n\t\t\tindicators.forEach(function (item) {\r\n\t\t\t\t// remove any opacity classes from history mode\r\n\t\t\t\tif (!item.classList.contains(recentType + \"-no\")) {\r\n\t\t\t\t\tNOTIFICATION_PREFIXES[itemType].classes.forEach(function (cls) {\r\n\t\t\t\t\t\tif (item.classList.contains(cls)) {\r\n\t\t\t\t\t\t\titem.classList.remove(cls);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t});\r\n\t\t\t\t\titem.classList.add(recentType + \"-no\");\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// add/upate indicators for LIVE mode\r\n\t\tvar opacity = null;\r\n\t\tif (trkData.live.notificationTimesByAssetId[asset.Id] !== undefined) {\r\n\t\t\topacity = getNotificationOpacityForTime(trkData.live.notificationTimesByAssetId[asset.Id][itemType]);\r\n\t\t}\r\n\t\t//var classNames = [recentType];\r\n\r\n\t\t//if (opacity !== null) {\r\n\t\t// classNames.push(NOTIFICATION_PREFIXES[type].prefix + '-' + opacity);\r\n\t\t//}\r\n\r\n\t\tindicators.forEach(function (item) {\r\n\t\t\tupdateTimeBasedNotificationIndicatorsForNode(item, opacity, itemType);\r\n\t\t});\r\n\t});\r\n}\r\n\r\nexport function updateTimeBasedNotificationIndicators() {\r\n\tif (state.activeMapMode !== mapModes.LIVE) {\r\n\t\treturn;\r\n\t}\r\n\r\n\t// todo: profile having individual setIntervals on active\r\n\t// assets instead of this global query/update\r\n\t// each interval would be responsible for caching its dom element\r\n\t// and switching views should clear all intervals\r\n\t// the intervals should be attached to assets\r\n\t// the interval would be cleared after the indicator is no longer relevant\r\n\r\n\ttrkData.assets.forEach(function (asset) {\r\n\t\t//updatePositionTimeIndicatorForAsset(asset);\r\n\t\tupdateTimeBasedNotificationIndicatorsForAsset(asset);\r\n\t});\r\n\tvar groupIds = _.map(trkData.groups, \"Id\");\r\n\tgroupIds.push(\"all-assets\");\r\n\tgroupIds.forEach(function (groupId) {\r\n\t\tupdateTimeBasedNotificationIndicatorsForGroup(groupId);\r\n\t});\r\n}\r\n","import trkData from \"./data.js\";\r\nimport { addItemToMap, removeItemFromMap } from \"./map-items.js\";\r\nimport { createPositionLinesForAsset, createPositionLinesForTrip } from \"./geometry-create.js\";\r\nimport strings from \"./strings.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\nimport { toggleLoadingMessage } from \"./ajax.js\";\r\nimport { handleWebServiceError } from \"./ajax.js\";\r\nimport state from \"./state.js\";\r\nimport { viewModes, mapModes, trkDataGroups } from \"./const.js\";\r\nimport { findAssetById } from \"./assets.js\";\r\nimport { highlightPosition } from \"./positions.js\";\r\nimport { addPositionMarkerToPoint } from \"./marker.js\";\r\nimport { updateActiveAssetInformation } from \"./assets-active.js\";\r\nimport log from \"./log.js\";\r\nimport { updateTimeBasedNotificationIndicators } from \"./asset-notification.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport $j from \"jquery\";\r\nimport _ from \"lodash\";\r\n\r\nexport function updateAssetPositionLines(asset, clearExisting, viewMode) {\r\n\tif (viewMode === undefined || viewMode === null) {\r\n\t\tviewMode = viewModes.NORMAL;\r\n\t}\r\n\r\n\tvar assetJourneys = _.filter(trkData.journeys, function (item) {\r\n\t\treturn item.AssetId === asset.Id;\r\n\t});\r\n\r\n\t// asset preference for drawn lines may have been updated\r\n\tif (!asset.DrawLinesBetweenPositions || clearExisting) {\r\n\t\t// clear any lines drawn for the asset\r\n\t\tif (viewMode === viewModes.NORMAL) {\r\n\t\t\tif (trkData.live.mapLinesByAssetId[asset.Id] !== undefined) {\r\n\t\t\t\tremoveItemFromMap(trkData.live.mapLinesByAssetId[asset.Id]);\r\n\t\t\t\tdelete trkData.live.mapLinesByAssetId[asset.Id];\r\n\t\t\t}\r\n\t\t\tif (trkData.history.mapLinesByAssetId[asset.Id] !== undefined) {\r\n\t\t\t\tremoveItemFromMap(trkData.history.mapLinesByAssetId[asset.Id]);\r\n\t\t\t\tdelete trkData.history.mapLinesByAssetId[asset.Id];\r\n\t\t\t}\r\n\t\t\t_.each(assetJourneys, function (journey) {\r\n\t\t\t\t_.each(journey.Trips, function (trip) {\r\n\t\t\t\t\tif (trkData.trips.mapLinesByTripId[trip.Id] !== undefined) {\r\n\t\t\t\t\t\tremoveItemFromMap(trkData.trips.mapLinesByTripId[trip.Id]);\r\n\t\t\t\t\t\tdelete trkData.trips.mapLinesByTripId[trip.Id];\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t\t} else if (viewMode === viewModes.SHARED_VIEW && trkData.sharedView.mapLinesByAssetId[asset.Id] !== undefined) {\r\n\t\t\tremoveItemFromMap(trkData.sharedView.mapLinesByAssetId[asset.Id], null, viewModes.SHARED_VIEW);\r\n\t\t\tdelete trkData.sharedView.mapLinesByAssetId[asset.Id];\r\n\t\t}\r\n\t}\r\n\r\n\tif (asset.DrawLinesBetweenPositions) {\r\n\t\t// draw new lines if preference has been enabled\r\n\t\tif (viewMode === viewModes.NORMAL) {\r\n\t\t\tif (trkData.history.mapLinesByAssetId[asset.Id] === undefined && trkData.history.positionsByAssetId[asset.Id] !== undefined) {\r\n\t\t\t\tvar assetVisiblePositions = _.filter(trkData.history.positionsByAssetId[asset.Id].Positions, function (item) {\r\n\t\t\t\t\treturn !item.IsHidden;\r\n\t\t\t\t});\r\n\t\t\t\tcreatePositionLinesForAsset(asset, assetVisiblePositions, viewModes.NORMAL);\r\n\t\t\t}\r\n\r\n\t\t\t_.each(assetJourneys, function (journey) {\r\n\t\t\t\t_.each(journey.Trips, function (trip) {\r\n\t\t\t\t\tif (\r\n\t\t\t\t\t\ttrkData.trips.mapLinesByTripId[trip.Id] === undefined &&\r\n\t\t\t\t\t\ttrkData.trips.positionsByTripId[trip.Id] !== undefined\r\n\t\t\t\t\t) {\r\n\t\t\t\t\t\tvar tripVisiblePositions = _.filter(trkData.trips.positionsByTripId[trip.Id].Positions, function (item) {\r\n\t\t\t\t\t\t\treturn !item.IsHidden;\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tcreatePositionLinesForTrip(trip, asset, tripVisiblePositions);\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t\t} else if (viewMode === viewModes.SHARED_VIEW && trkData.sharedView.positionsByAssetId[asset.Id] !== undefined) {\r\n\t\t\tvar assetVisiblePositions = _.filter(trkData.sharedView.positionsByAssetId[asset.Id].Positions, function (item) {\r\n\t\t\t\treturn !item.IsHidden;\r\n\t\t\t});\r\n\t\t\tcreatePositionLinesForAsset(asset, assetVisiblePositions, viewModes.SHARED_VIEW);\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport function updateLatestPositionTimeForSharedView() {\r\n\tvar latest = _.maxBy([trkData.live.latestPosition, trkData.history.latestPosition], function (item) {\r\n\t\treturn item == null ? 0 : item.Epoch;\r\n\t});\r\n\tif (latest != null) {\r\n\t\t$(\"#shared-view-last-event\").text(latest.Time);\r\n\t} else {\r\n\t\t$(\"#shared-view-last-event\").text(strings.NEVER);\r\n\t}\r\n}\r\n\r\nexport function showOrLoadAssetPosition(positionId, assetId, message) {\r\n\tif (state.activeMapMode === mapModes.LIVE) {\r\n\t\t// check if the position is within our stored history, or is the current live position\r\n\t\tif (highlightPosition(positionId, message)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar asset = findAssetById(assetId);\r\n\r\n\t\t// position not in live markers, check asset live history TODO replace asset.Positions with trkData.live.positionsByAssetId[asset.Id]\r\n\t\tvar livePosition = _.find(asset.Positions, function (item) {\r\n\t\t\treturn item.Id === positionId;\r\n\t\t});\r\n\t\tif (livePosition !== undefined) {\r\n\t\t\taddTemporaryPositionMarker(livePosition, asset, message);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// position still not found, query it\r\n\t\tconsole.log(\"querying position\");\r\n\t\ttoggleLoadingMessage(true, \"event-position\");\r\n\t\tvar data = { assetId: assetId, positionId: positionId };\r\n\t\treturn $.ajax({\r\n\t\t\ttype: \"POST\",\r\n\t\t\turl: wrapUrl(\"/services/GPSService.asmx/GetPositionById\"),\r\n\t\t\tdata: JSON.stringify(data),\r\n\t\t\tcontentType: \"application/json; charset=utf-8\",\r\n\t\t\tdataType: \"json\",\r\n\t\t\tsuccess: function (msg) {\r\n\t\t\t\tif (msg.d) {\r\n\t\t\t\t\tvar position = msg.d;\r\n\t\t\t\t\tif (position == null) {\r\n\t\t\t\t\t\treturn;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// TODO is asset.Positions a duplicate of trkData.live.positionsByAssetId[asset.Id]?\r\n\t\t\t\t\tif (asset.Positions != null) {\r\n\t\t\t\t\t\tasset.Positions.push(position);\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tasset.Positions = [position];\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\taddTemporaryPositionMarker(position, asset, message);\r\n\t\t\t\t}\r\n\t\t\t\ttoggleLoadingMessage(false, \"event-position\");\r\n\t\t\t},\r\n\t\t\terror: function (xhr, status, error) {\r\n\t\t\t\thandleWebServiceError(strings.MSG_LOAD_POSITION_ERROR);\r\n\t\t\t\ttoggleLoadingMessage(false, \"event-position\");\r\n\t\t\t},\r\n\t\t});\r\n\t} else {\r\n\t\t// should currently be on the map\r\n\t\thighlightPosition(positionId, message);\r\n\t}\r\n}\r\n\r\nfunction addTemporaryPositionMarker(position, asset, message) {\r\n\tvar positionMarker = trkData.live.markersByPositionId[position.Id];\r\n\tvar isMarkerAdded = false;\r\n\tif (positionMarker === undefined) {\r\n\t\t// create the marker\r\n\t\tpositionMarker = addPositionMarkerToPoint(\r\n\t\t\t[position.Lat, position.Lng],\r\n\t\t\tfalse,\r\n\t\t\tposition,\r\n\t\t\tasset,\r\n\t\t\t255,\r\n\t\t\tnull,\r\n\t\t\tnull,\r\n\t\t\tnull,\r\n\t\t\ttrkDataGroups.NORMAL_LIVE\r\n\t\t);\r\n\t\tisMarkerAdded = true;\r\n\t}\r\n\tpositionMarker.data.hide = true;\r\n\tpositionMarker.data.message = message;\r\n\tif (!positionMarker.data.location.IsHidden) {\r\n\t\taddItemToMap(positionMarker, null, viewModes.NORMAL);\r\n\t}\r\n\tpositionMarker.fire(\"click\");\r\n\tif (isMarkerAdded) {\r\n\t\ttrkData.live.markersByAssetId = _.groupBy(trkData.live.markers, function (marker) {\r\n\t\t\treturn marker.data.assetId;\r\n\t\t});\r\n\t\ttrkData.live.markersByPositionId = _.keyBy(trkData.live.markers, function (marker) {\r\n\t\t\treturn marker.data.location.Id;\r\n\t\t});\r\n\t}\r\n}\r\n\r\nexport function clearAssetPositions(asset) {\r\n\t// remove ui elements\r\n\tvar li = $j(\"#assets-all\").find(\"li.asset-\" + asset.Id);\r\n\t$j(\"div.locations\", li).remove();\r\n\r\n\t//remove where trkData.history.positions[i].Id == id\r\n\tif (trkData.history.positions != null) {\r\n\t\tfor (var i = trkData.history.positions.length - 1; i >= 0; i -= 1) {\r\n\t\t\tif (trkData.history.positions[i].Id == asset.Id) trkData.history.positions.splice(i, 1);\r\n\t\t}\r\n\t}\r\n\r\n\ttrkData.history.normalizedPositions = _.reject(trkData.history.normalizedPositions, function (item) {\r\n\t\treturn item.AssetId === asset.Id;\r\n\t});\r\n\tdelete trkData.history.assetIdsWithResults[asset.Id];\r\n\r\n\t//remove where trkData.live.positions[i].AssetId == id\r\n\tif (trkData.live.positions != null) {\r\n\t\tfor (var i = trkData.live.positions.length - 1; i >= 0; i -= 1) {\r\n\t\t\tif (trkData.live.positions[i].AssetId == asset.Id) trkData.live.positions.splice(i, 1);\r\n\t\t}\r\n\t}\r\n\r\n\ttrkData.live.normalizedPositions = _.reject(trkData.live.normalizedPositions, function (item) {\r\n\t\treturn item.AssetId === asset.Id;\r\n\t});\r\n\r\n\tif (trkData.live.latestPositions != null) {\r\n\t\tfor (var i = trkData.live.latestPositions.length - 1; i >= 0; i -= 1) {\r\n\t\t\tif (trkData.live.latestPositions[i].AssetId == asset.Id) trkData.live.latestPositions.splice(i, 1);\r\n\t\t}\r\n\t}\r\n\r\n\t//remove where trkData.markers.assetId == id ... setMap(null)\r\n\tif (trkData.live.markers !== null) {\r\n\t\tfor (var i = trkData.live.markers.length - 1; i >= 0; i -= 1) {\r\n\t\t\tvar assetId = trkData.live.markers[i].data.assetId;\r\n\t\t\tif (assetId == asset.Id) {\r\n\t\t\t\tremoveItemFromMap(trkData.live.markers[i]);\r\n\t\t\t\ttrkData.live.markers.splice(i, 1);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tif (trkData.history.markers !== null) {\r\n\t\tfor (var i = trkData.history.markers.length - 1; i >= 0; i -= 1) {\r\n\t\t\tvar assetId = trkData.history.markers[i].data.assetId;\r\n\t\t\tif (assetId == asset.Id) {\r\n\t\t\t\tremoveItemFromMap(trkData.history.markers[i]);\r\n\t\t\t\ttrkData.history.markers.splice(i, 1);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tif (trkData.live.latestPositionsByAssetId[asset.Id] !== undefined) {\r\n\t\tdelete trkData.live.latestPositionsByAssetId[asset.Id];\r\n\t}\r\n\tif (trkData.live.notificationTimesByAssetId[asset.Id] !== undefined) {\r\n\t\tdelete trkData.live.notificationTimesByAssetId[asset.Id];\r\n\t}\r\n\ttrkData.live.positionsByAssetId = _.groupBy(trkData.live.positions, \"AssetId\");\r\n\ttrkData.live.normalizedPositionsByAssetId = _.groupBy(trkData.live.normalizedPositions, \"AssetId\");\r\n\ttrkData.live.markersByAssetId = _.groupBy(trkData.live.markers, function (marker) {\r\n\t\treturn marker.data.assetId;\r\n\t});\r\n\ttrkData.live.markersByPositionId = _.keyBy(trkData.live.markers, function (marker) {\r\n\t\treturn marker.data.location.Id;\r\n\t});\r\n\ttrkData.history.positionsByAssetId = _.groupBy(trkData.history.positions, \"Id\");\r\n\ttrkData.history.normalizedPositionsByAssetId = _.groupBy(trkData.history.normalizedPositions, \"AssetId\");\r\n\ttrkData.history.markersByAssetId = _.groupBy(trkData.history.markers, function (marker) {\r\n\t\treturn marker.data.assetId;\r\n\t});\r\n\ttrkData.history.markersByPositionId = _.keyBy(trkData.history.markers, function (marker) {\r\n\t\treturn marker.data.location.Id;\r\n\t});\r\n\t// TODO trips for asset should be deleted here as well\r\n\r\n\tupdateActiveAssetInformation(viewModes.NORMAL);\r\n}\r\n\r\nexport function updatePositionStatus() {\r\n\tlog(\"Update position indicators.\");\r\n\r\n\t//// update message status\r\n\t//_.each(domNodes.assets, function (assetNodes, key) {\r\n\t// var asset = findAssetById(key);\r\n\t// updateMessageStatusForAsset(asset);\r\n\t//});\r\n\r\n\tupdateTimeBasedNotificationIndicators();\r\n\tupdateLatestPositionTimeForSharedView();\r\n}\r\n","import strings from \"./strings.js\";\r\nimport trkData from \"./data.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport state from \"./state.js\";\r\nimport { createListing, defaultListItemSort, notificationLists } from \"./item-listing.js\";\r\nimport { openDialogPanel } from \"./panel-nav.js\";\r\nimport { checkForShareViewChange } from \"./shared-view.js\";\r\nimport { getGroupAssetStatus } from \"./asset-group.js\";\r\nimport { convertToLatLngPreference } from \"./preferences.js\";\r\nimport { findAssetById } from \"./assets.js\";\r\nimport { markerClick } from \"./marker-click.js\";\r\nimport { findTripById } from \"./trips.js\";\r\nimport { getAssetDataGroupForCurrentViewMode } from \"./map-viewmode.js\";\r\nimport { getMapMarkersForDataGroup } from \"./marker.js\";\r\nimport { getJourneyDataGroupForCurrentViewMode } from \"./map-viewmode.js\";\r\nimport { toggleAssetActive } from \"./asset-select.js\";\r\nimport { toggleTripActive } from \"./trips.js\";\r\nimport { viewModes, mapModes } from \"./const.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\nimport { isItemIncluded } from \"./polyfills.js\";\r\nimport { addItemToMap, removeItemFromMap } from \"./map-items.js\";\r\nimport { toggleLoadingMessage } from \"./ajax.js\";\r\nimport { handleWebServiceError } from \"./ajax.js\";\r\nimport preferences from \"./preferences.js\";\r\nimport user from \"./user.js\";\r\nimport { updateAssetPositionLines } from \"./asset-positions.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport _ from \"lodash\";\r\nimport { el } from \"redom\"; // https://redom.js.org/\r\n\r\nexport function openPositionsForAsset(asset) {\r\n\tvar dataSource =\r\n\t\tstate.activeMapMode === mapModes.LIVE\r\n\t\t\t? trkData.live.normalizedPositionsByAssetId\r\n\t\t\t: trkData.history.normalizedPositionsByAssetId;\r\n\tvar positions = [];\r\n\tif (dataSource[asset.Id] !== undefined) {\r\n\t\tpositions = _.sortBy(dataSource[asset.Id], defaultListItemSort).reverse();\r\n\t}\r\n\tcreateListing(positions, \"positions\");\r\n\topenDialogPanel(\r\n\t\tdomNodes.dialogs.assetPositions,\r\n\t\tstrings.POSITIONS,\r\n\t\tasset,\r\n\t\tfalse,\r\n\t\tnull,\r\n\t\t\"asset\",\r\n\t\t\"asset-positions\",\r\n\t\topenPositionsForAsset\r\n\t);\r\n\r\n\tif (state.activeMapMode === mapModes.LIVE) {\r\n\t\t// TODO clear unread badges, but probably easier on render with storing unreadPosition/Message/Event IDs\r\n\t}\r\n}\r\n\r\nexport function openPositionsForGroup(group) {\r\n\tvar status = getGroupAssetStatus(group.Id);\r\n\tvar dataSource =\r\n\t\tstate.activeMapMode === mapModes.HISTORY\r\n\t\t\t? trkData.history.normalizedPositionsByAssetId\r\n\t\t\t: trkData.live.normalizedPositionsByAssetId;\r\n\tvar positions = [];\r\n\t_.each(status.assetIds, function (assetId) {\r\n\t\tif (dataSource[assetId] !== undefined) {\r\n\t\t\tpositions = positions.concat(dataSource[assetId]);\r\n\t\t}\r\n\t});\r\n\tpositions = _.sortBy(positions, defaultListItemSort).reverse();\r\n\tcreateListing(positions, \"positions\");\r\n\topenDialogPanel(\r\n\t\tdomNodes.dialogs.assetPositions,\r\n\t\tstrings.POSITIONS,\r\n\t\tgroup,\r\n\t\tfalse,\r\n\t\tundefined,\r\n\t\t\"group\",\r\n\t\t\"group-positions\",\r\n\t\topenPositionsForGroup\r\n\t);\r\n}\r\n\r\nexport function openPositionsForSharedView(sharedView) {\r\n\tvar positions = _.sortBy(trkData.sharedView.normalizedPositions, defaultListItemSort).reverse();\r\n\tcreateListing(positions, \"positions\");\r\n\topenDialogPanel(\r\n\t\tdomNodes.dialogs.assetPositions,\r\n\t\tstrings.POSITIONS,\r\n\t\tsharedView,\r\n\t\tfalse,\r\n\t\tcheckForShareViewChange(sharedView),\r\n\t\t\"shared-view\",\r\n\t\t\"shared-view-positions\",\r\n\t\topenPositionsForSharedView\r\n\t);\r\n}\r\n\r\nexport function openPositionsForJourney(journey) {\r\n\tvar positions = [];\r\n\t_.each(journey.Trips, function (trip) {\r\n\t\tif (trkData.trips.normalizedPositionsByTripId[trip.Id] !== undefined) {\r\n\t\t\tpositions = positions.concat(trkData.trips.normalizedPositionsByTripId[trip.Id]);\r\n\t\t}\r\n\t});\r\n\tpositions = _.sortBy(positions, defaultListItemSort).reverse();\r\n\tcreateListing(positions, \"positions\");\r\n\topenDialogPanel(\r\n\t\tdomNodes.dialogs.assetPositions,\r\n\t\tstrings.POSITIONS,\r\n\t\tjourney,\r\n\t\tfalse,\r\n\t\tundefined,\r\n\t\t\"journey\",\r\n\t\t\"journey-positions\",\r\n\t\topenPositionsForJourney\r\n\t);\r\n}\r\n\r\nexport function openPositionsForTrip(trip) {\r\n\tvar positions = [];\r\n\tif (trkData.trips.normalizedPositionsByTripId[trip.Id] !== undefined) {\r\n\t\tpositions = trkData.trips.normalizedPositionsByTripId[trip.Id];\r\n\t}\r\n\tpositions = _.sortBy(positions, defaultListItemSort).reverse();\r\n\tcreateListing(positions, \"positions\");\r\n\topenDialogPanel(\r\n\t\tdomNodes.dialogs.assetPositions,\r\n\t\tstrings.POSITIONS,\r\n\t\ttrip,\r\n\t\tfalse,\r\n\t\tundefined,\r\n\t\t\"trip\",\r\n\t\t\"trip-positions\",\r\n\t\topenPositionsForTrip\r\n\t);\r\n}\r\n\r\nexport function getPositionLinkForEvent(item) {\r\n\tvar asset = findAssetById(item.AssetId);\r\n\tvar eventposition = null;\r\n\tvar position = getPositionForEvent(item);\r\n\tif (position !== undefined) {\r\n\t\teventposition = position.Address;\r\n\t\tif (eventposition == null || eventposition.trim() == \"\" || asset.HideAddress) {\r\n\t\t\teventposition = convertToLatLngPreference(position.DisplayLat, position.DisplayLng, position.Grid);\r\n\t\t}\r\n\t\teventposition = el(\r\n\t\t\t\"a.location\",\r\n\t\t\t{ href: \"#\", dataset: { marker: position.Id, time: item.Time, asset: item.AssetId } },\r\n\t\t\teventposition\r\n\t\t);\r\n\t}\r\n\treturn eventposition;\r\n}\r\n\r\nexport function mapPositionForEvent(item, asset) {\r\n\tvar eventPosition = {\r\n\t\tid: null,\r\n\t\tlatLng: null,\r\n\t\taddress: null,\r\n\t};\r\n\r\n\tif (asset === undefined || asset === null) {\r\n\t\tasset = findAssetById(item.AssetId);\r\n\t}\r\n\tvar position = getPositionForEvent(item);\r\n\tif (position !== undefined && position !== null) {\r\n\t\teventPosition.id = position.Id;\r\n\t\tif (!asset.HideAddress) {\r\n\t\t\teventPosition.address = position.Address;\r\n\t\t}\r\n\t\teventPosition.latLng = convertToLatLngPreference(position.DisplayLat, position.DisplayLng, position.Grid);\r\n\t}\r\n\treturn eventPosition;\r\n}\r\n\r\nfunction getPositionForEvent(item) {\r\n\tif (item.Position !== undefined && item.Position !== null) {\r\n\t\t// live events have .Position defined, why don't historic? TODO remove .Position from live event\r\n\t\treturn item.Position;\r\n\t}\r\n\tif (trkData.positionsById[item.PositionId] !== undefined) {\r\n\t\treturn trkData.positionsById[item.PositionId].Position;\r\n\t}\r\n\treturn undefined;\r\n}\r\n\r\nexport function highlightPosition(positionId, message) {\r\n\tconsole.log(\"show position: \" + positionId);\r\n\tvar markers = getMapMarkersForDataGroup(getAssetDataGroupForCurrentViewMode());\r\n\tif (markers === undefined) {\r\n\t\treturn false;\r\n\t}\r\n\tvar marker = _.find(markers, function (marker) {\r\n\t\treturn marker.data.location.Id === positionId;\r\n\t});\r\n\r\n\t// prioritize live/history markers over trip marker for same position\r\n\t// TODO doesn't take into account normal/shared view mode\r\n\tvar isTripMarker = false;\r\n\tif (marker === undefined) {\r\n\t\tisTripMarker = true;\r\n\t\tvar tripMarkers = getMapMarkersForDataGroup(getJourneyDataGroupForCurrentViewMode());\r\n\t\tif (tripMarkers === undefined) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tvar marker = _.find(tripMarkers, function (marker) {\r\n\t\t\treturn marker.data.location.Id === positionId;\r\n\t\t});\r\n\t}\r\n\r\n\tif (marker !== undefined) {\r\n\t\tmarker.data.message = message; // TODO what is this\r\n\t\tif (!isTripMarker) {\r\n\t\t\tvar assetId = marker.data.assetId;\r\n\t\t\ttoggleAssetActive(assetId, true, true);\r\n\t\t} else {\r\n\t\t\tvar tripId = marker.data.tripId;\r\n\t\t\tvar trip = findTripById(tripId);\r\n\t\t\ttoggleTripActive(trip.JourneyId, tripId, true, true);\r\n\t\t}\r\n\t\tif (!marker.data.location.IsHidden) {\r\n\t\t\taddItemToMap(marker);\r\n\t\t}\r\n\t\tmarkerClick(marker, \"position\", null, false);\r\n\t\t//marker.fire('click');\r\n\t\treturn true;\r\n\t}\r\n\r\n\treturn false;\r\n}\r\n\r\nexport function togglePositionVisibility(assetId, positionId, isNowHidden, sharedViewId) {\r\n\tvar key = \"position-visibility-\" + positionId;\r\n\ttoggleLoadingMessage(true, key);\r\n\tvar data = {\r\n\t\tAssetId: assetId,\r\n\t\tPositionId: positionId,\r\n\t\tIsVisible: !isNowHidden,\r\n\t\tSharedViewId: sharedViewId,\r\n\t};\r\n\treturn $.ajax({\r\n\t\ttype: \"POST\",\r\n\t\turl: wrapUrl(\"/api/ui/updatepositionvisibility\"),\r\n\t\tcontentType: \"application/json; charset=utf-8\",\r\n\t\tdata: JSON.stringify(data),\r\n\t\tdataType: \"json\",\r\n\t\tsuccess: function (msg) {\r\n\t\t\tif (msg.success) {\r\n\t\t\t\tvar asset = findAssetById(assetId);\r\n\t\t\t\tif (sharedViewId === null) {\r\n\t\t\t\t\t// if it was hidden, then will it have a marker?\r\n\t\t\t\t\tvar liveMarker = trkData.live.markersByPositionId[positionId];\r\n\t\t\t\t\tvar historyMarker = trkData.history.markersByPositionId[positionId];\r\n\t\t\t\t\tvar tripMarkers = _.filter(trkData.trips.markers, function (item) {\r\n\t\t\t\t\t\treturn item.data.location.Id === positionId;\r\n\t\t\t\t\t});\r\n\r\n\t\t\t\t\tvar isAssetCurrentlyActive = !isItemIncluded(user.displayPreferences.hiddenAssets, assetId);\r\n\t\t\t\t\tif (liveMarker !== undefined) {\r\n\t\t\t\t\t\tliveMarker.data.location.IsHidden = isNowHidden;\r\n\t\t\t\t\t\tif (isAssetCurrentlyActive && state.activeMapMode === mapModes.LIVE) {\r\n\t\t\t\t\t\t\tif (isNowHidden) {\r\n\t\t\t\t\t\t\t\tremoveItemFromMap(liveMarker);\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\taddItemToMap(liveMarker);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (historyMarker !== undefined) {\r\n\t\t\t\t\t\thistoryMarker.data.location.IsHidden = isNowHidden;\r\n\t\t\t\t\t\tif (\r\n\t\t\t\t\t\t\tpreferences.PREFERENCE_GROUP_POSITIONS &&\r\n\t\t\t\t\t\t\ttrkData.history.markerClustersByAssetId[asset.Id] !== undefined\r\n\t\t\t\t\t\t) {\r\n\t\t\t\t\t\t\t// add/remove marker from asset's marker cluster\r\n\t\t\t\t\t\t\tif (isNowHidden) {\r\n\t\t\t\t\t\t\t\ttrkData.history.markerClustersByAssetId[asset.Id].remove(historyMarker);\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\ttrkData.history.markerClustersByAssetId[asset.Id].add(historyMarker);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} else if (isAssetCurrentlyActive && state.activeMapMode !== mapModes.LIVE) {\r\n\t\t\t\t\t\t\t// markers added individually to map\r\n\t\t\t\t\t\t\tif (isNowHidden) {\r\n\t\t\t\t\t\t\t\tremoveItemFromMap(historyMarker);\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\taddItemToMap(historyMarker);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// position could be in multiple trips\r\n\t\t\t\t\t_.each(tripMarkers, function (tripMarker) {\r\n\t\t\t\t\t\ttripMarker.data.location.IsHidden = isNowHidden;\r\n\t\t\t\t\t\t// add/remove from map - trip may be hidden\r\n\t\t\t\t\t\tvar tripId = tripMarker.data.tripId;\r\n\t\t\t\t\t\tvar isTripCurrentlyActive = isItemIncluded(user.displayPreferences.visibleTrips, tripId);\r\n\t\t\t\t\t\tvar trip = findTripById(tripId);\r\n\t\t\t\t\t\tif (preferences.PREFERENCE_GROUP_POSITIONS && trkData.trips.markerClustersByTripId[trip.Id] !== undefined) {\r\n\t\t\t\t\t\t\tif (isNowHidden) {\r\n\t\t\t\t\t\t\t\ttrkData.trips.markerClustersByTripId[trip.Id].remove(tripMarker);\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\ttrkData.trips.markerClustersByTripId[trip.Id].add(tripMarker);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} else if (isTripCurrentlyActive) {\r\n\t\t\t\t\t\t\t// trip data active in both live and history mode\r\n\t\t\t\t\t\t\tif (isNowHidden) {\r\n\t\t\t\t\t\t\t\tremoveItemFromMap(tripMarker);\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\taddItemToMap(tripMarker);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t});\r\n\r\n\t\t\t\t\tupdateAssetPositionLines(asset, true);\r\n\t\t\t\t\tclearCachedListingsRelatedToPositionId(positionId, isNowHidden, viewModes.NORMAL);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tvar positionMarkers = _.filter(trkData.sharedView.markers, function (item) {\r\n\t\t\t\t\t\treturn item.data.location.Id === positionId;\r\n\t\t\t\t\t});\r\n\t\t\t\t\t_.each(positionMarkers, function (sharedViewMarker) {\r\n\t\t\t\t\t\tsharedViewMarker.data.location.IsHidden = isNowHidden;\r\n\r\n\t\t\t\t\t\tif (\r\n\t\t\t\t\t\t\tpreferences.PREFERENCE_GROUP_POSITIONS &&\r\n\t\t\t\t\t\t\ttrkData.sharedView.markerClustersByAssetId[assetId] !== undefined\r\n\t\t\t\t\t\t) {\r\n\t\t\t\t\t\t\tif (isNowHidden) {\r\n\t\t\t\t\t\t\t\ttrkData.sharedView.markerClustersByAssetId[assetId].remove(sharedViewMarker);\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\ttrkData.sharedView.markerClustersByAssetId[assetId].add(sharedViewMarker);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tif (isNowHidden) {\r\n\t\t\t\t\t\t\t\tremoveItemFromMap(sharedViewMarker, null, viewModes.SHARED_VIEW);\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\taddItemToMap(sharedViewMarker, null, viewModes.SHARED_VIEW);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t});\r\n\t\t\t\t\tupdateAssetPositionLines(asset, true, viewModes.SHARED_VIEW);\r\n\t\t\t\t\tclearCachedListingsRelatedToPositionId(positionId, isNowHidden, viewModes.SHARED_VIEW);\r\n\t\t\t\t}\r\n\t\t\t\t// any map-toggles with this positionId need updated for this view mode\r\n\t\t\t\tvar viewSelector = \"\";\r\n\t\t\t\tif (sharedViewId !== null) {\r\n\t\t\t\t\tviewSelector = '#panel-secondary[data-item-type=\"shared-views\"] ';\r\n\t\t\t\t}\r\n\t\t\t\tvar icons = document.querySelectorAll(viewSelector + '.map-toggle[data-marker=\"' + positionId + '\"]');\r\n\t\t\t\t_.each(icons, function (icon) {\r\n\t\t\t\t\t// update parent container as well, up two levels\r\n\t\t\t\t\tvar container = icon.parentNode.parentNode;\r\n\t\t\t\t\tif (isNowHidden) {\r\n\t\t\t\t\t\tcontainer.classList.add(\"is-hidden\");\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tcontainer.classList.remove(\"is-hidden\");\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\ticon.setAttribute(\"data-hidden\", isNowHidden);\r\n\t\t\t\t\ticon\r\n\t\t\t\t\t\t.querySelector(\"use\")\r\n\t\t\t\t\t\t.setAttributeNS(\r\n\t\t\t\t\t\t\t\"http://www.w3.org/1999/xlink\",\r\n\t\t\t\t\t\t\t\"href\",\r\n\t\t\t\t\t\t\t\"/content/svg/tracking.svg?v=15#\" + (isNowHidden ? \"invisible\" : \"visible\")\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\ticon.title = isNowHidden ? strings.SHOW_ON_MAP : strings.HIDE_ON_MAP;\r\n\t\t\t\t});\r\n\r\n\t\t\t\t// any quick actions for this position need updated (position information dialog)\r\n\t\t\t\tvar togglePositionActions = document.querySelectorAll(\r\n\t\t\t\t\t'[data-action=\"position-toggle-map\"][data-position-id=\"' + positionId + '\"]'\r\n\t\t\t\t);\r\n\t\t\t\t_.each(togglePositionActions, function (actionPositionVisibility) {\r\n\t\t\t\t\tactionPositionVisibility.setAttribute(\"data-hidden\", isNowHidden);\r\n\t\t\t\t\tactionPositionVisibility\r\n\t\t\t\t\t\t.querySelector(\"use\")\r\n\t\t\t\t\t\t.setAttributeNS(\r\n\t\t\t\t\t\t\t\"http://www.w3.org/1999/xlink\",\r\n\t\t\t\t\t\t\t\"href\",\r\n\t\t\t\t\t\t\t\"/content/svg/tracking.svg?v=15#\" + (isNowHidden ? \"visible\" : \"invisible\")\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\tactionPositionVisibility.querySelector(\"span\").innerText = isNowHidden\r\n\t\t\t\t\t\t? strings.SHOW_POSITION\r\n\t\t\t\t\t\t: strings.HIDE_POSITION;\r\n\t\t\t\t\tactionPositionVisibility.title = isNowHidden ? strings.SHOW_POSITION : strings.HIDE_POSITION;\r\n\t\t\t\t});\r\n\t\t\t} else {\r\n\t\t\t\thandleWebServiceError(msg.ErrorMessage);\r\n\t\t\t}\r\n\t\t\ttoggleLoadingMessage(false, key);\r\n\t\t},\r\n\t\terror: function (xhr, status, error) {\r\n\t\t\thandleWebServiceError(strings.ERROR_POSITION_VISIBILITY);\r\n\t\t\ttoggleLoadingMessage(false, key);\r\n\t\t},\r\n\t});\r\n}\r\n\r\nfunction clearCachedListingsRelatedToPositionId(positionId, isNowHidden, viewMode) {\r\n\t// any listing caches need to be cleared, but only for this view mode\r\n\tvar dataSource = viewMode === viewModes.SHARED_VIEW ? trkData.sharedView : trkData;\r\n\tvar nodeSource = viewMode === viewModes.SHARED_VIEW ? domNodes.sharedView : domNodes;\r\n\tvar corePosition = dataSource.positionsById[positionId];\r\n\tif (corePosition !== undefined) {\r\n\t\tdelete domNodes.positionListingById[positionId];\r\n\r\n\t\t_.each(corePosition.Position.Events, function (positionEvent) {\r\n\t\t\tdelete nodeSource.eventListingById[positionEvent.Id];\r\n\t\t\tdelete nodeSource.eventListingById[positionEvent.Id];\r\n\t\t});\r\n\r\n\t\tvar positionMessages = _.filter(trkData.messagesById, function (item) {\r\n\t\t\treturn item.Position !== undefined && item.Position !== null && item.Position.Id === positionId;\r\n\t\t});\r\n\t\t_.each(positionMessages, function (item) {\r\n\t\t\tdelete nodeSource.messageListingById[item.Id];\r\n\t\t});\r\n\r\n\t\tdelete nodeSource.activityListingById[corePosition.Epoch + \"-\" + corePosition.AssetId];\r\n\r\n\t\tvar lists = [\r\n\t\t\tnotificationLists.activityList,\r\n\t\t\tnotificationLists.alertsList,\r\n\t\t\tnotificationLists.chatList,\r\n\t\t\tnotificationLists.eventsList,\r\n\t\t\tnotificationLists.messagesList,\r\n\t\t\tnotificationLists.positionsList,\r\n\t\t];\r\n\t\t_.each(lists, function (list) {\r\n\t\t\tif (list === null) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\t_.each(list.data.items, function (item) {\r\n\t\t\t\tif (item.Position !== undefined && item.Position !== null && item.PositionId === positionId) {\r\n\t\t\t\t\titem.Position.IsHidden = isNowHidden;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t});\r\n\t}\r\n}\r\n","import trkData from \"./data.js\";\r\nimport state from \"./state.js\";\r\nimport strings from \"./strings.js\";\r\nimport options from \"./options.js\";\r\nimport log from \"./log.js\";\r\nimport { mapModes, trkDataGroups } from \"./const.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\nimport { handleWebServiceError } from \"./ajax.js\";\r\nimport { convertNamedColorToHex } from \"./color.js\";\r\nimport { addItemToMap, removeItemFromMap } from \"./map-items.js\";\r\nimport { markerClick } from \"./marker-click.js\";\r\nimport { isItemIncluded } from \"./polyfills.js\";\r\nimport user from \"./user.js\";\r\nimport { throttles } from \"./timers.js\";\r\nimport { addPositionMarkerToPoint } from \"./marker.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport { setMapBounds } from \"./map-bounds.js\";\r\nimport { findGroupById } from \"./asset-group.js\";\r\nimport { getDbg } from \"./log.js\";\r\nimport { updateAssetState } from \"./asset-state.js\";\r\nimport { map } from \"./map-base.js\";\r\nimport { getCurrentMarkerForAsset, findAssetByUniqueId, findAssetById, normalizeAssetData } from \"./assets.js\";\r\nimport { initLiveAssetsFollow, openMostRecentLiveFollowPosition, liveFollowAsset } from \"./asset-live-follow.js\";\r\nimport { toggleAssetActive } from \"./asset-select.js\";\r\nimport { openPositionsForAsset, openPositionsForGroup } from \"./positions.js\";\r\nimport { updateGroupFunctionBadges, updateAssetFunctionBadges } from \"./badges.js\";\r\nimport { getAssetDataGroupForCurrentViewMode } from \"./map-viewmode.js\";\r\nimport { updateAssetNotificationTime, updateTimeBasedNotificationIndicatorsForAsset } from \"./asset-notification.js\";\r\nimport { updateTimeBasedNotificationIndicatorsForGroup } from \"./notifications.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport _ from \"lodash\";\r\nimport L from \"leaflet\";\r\nimport moment from \"moment\"; // https://www.npmjs.com/package/moment\r\n\r\nexport function initLiveAssets() {\r\n\tinitLiveAssetsFollow();\r\n}\r\n\r\nexport async function updateLiveAssets() {\r\n\tif (trkData.pending.live) {\r\n\t\treturn;\r\n\t}\r\n\r\n\ttrkData.pending.live = true;\r\n\t// if previous query hasn't returned, don't send another\r\n\tlog(\"Updating Live Assets\");\r\n\tvar data = {\r\n\t\tlang: user.dateCulture,\r\n\t\tdbg: getDbg(),\r\n\t};\r\n\r\n\ttry {\r\n\t\tconst req = await fetch(wrapUrl(\"/services/GPSService.asmx/GetLatestPositionsForAssetsReq\"), {\r\n\t\t\tmethod: \"POST\",\r\n\t\t\tbody: JSON.stringify(data),\r\n\t\t\theaders: new Headers({\r\n\t\t\t\t\"Content-Type\": \"application/json; charset=utf-8\",\r\n\t\t\t}),\r\n\t\t});\r\n\t\tconst msg = await req.json();\r\n\t\ttrkData.pending.live = false;\r\n\r\n\t\tif (!msg.d) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar newPositions = msg.d;\r\n\t\tvar updatePositions = false;\r\n\t\tvar isMarkerActive = false;\r\n\t\tvar isNewFollowPosition = false;\r\n\t\tfor (var i = 0; i < newPositions.length; i++) {\r\n\t\t\t// asset/position\r\n\t\t\tvar latestPosition = newPositions[i];\r\n\t\t\tvar assetId = latestPosition.AssetId;\r\n\t\t\tvar asset = findAssetById(assetId);\r\n\t\t\tif (asset == null) {\r\n\t\t\t\t// unknown asset update\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tvar newPosition = latestPosition.Position;\r\n\t\t\tvar isActive =\r\n\t\t\t\tstate.activeMapMode === mapModes.LIVE && !isItemIncluded(user.displayPreferences.hiddenAssets, assetId);\r\n\r\n\t\t\tif (trkData.live.latestPosition == null || trkData.live.latestPosition.Epoch < newPosition.Epoch) {\r\n\t\t\t\ttrkData.live.latestPosition = newPosition;\r\n\t\t\t}\r\n\r\n\t\t\t// if the position has changed (newer), update it\r\n\t\t\tvar isNewPosition = false;\r\n\t\t\tvar priorPosition = trkData.live.latestPositionsByAssetId[assetId];\r\n\t\t\tif (priorPosition !== undefined) {\r\n\t\t\t\tpriorPosition = priorPosition.Position;\r\n\t\t\t\tif (priorPosition.Id !== newPosition.Id) {\r\n\t\t\t\t\t// replace old position with new\r\n\t\t\t\t\tlog(\"New position for \" + asset.Name + \" at \" + newPosition.Time + \". Updating.\");\r\n\r\n\t\t\t\t\tvar priorPositionMarker = trkData.live.markersByPositionId[priorPosition.Id];\r\n\r\n\t\t\t\t\tisMarkerActive = priorPositionMarker !== undefined && priorPositionMarker.data.selected;\r\n\r\n\t\t\t\t\t// push old position into a history list to support persisted event position information\r\n\t\t\t\t\tif (asset.Positions == null) {\r\n\t\t\t\t\t\tasset.Positions = [priorPosition];\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tasset.Positions.push(priorPosition);\r\n\t\t\t\t\t\t// this needs to be limited otherwise we will have issues with memory\r\n\t\t\t\t\t\t// for long-running portal users\r\n\t\t\t\t\t\tif (asset.Positions.length > 50) {\r\n\t\t\t\t\t\t\tasset.Positions.splice(0, 1);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// push old marker into live history list for asset (max 10 spots)\r\n\t\t\t\t\t// and update the history line -- same color as asset\r\n\t\t\t\t\tif (!newPosition.IsHidden) {\r\n\t\t\t\t\t\tif (asset.DrawLinesBetweenPositions) {\r\n\t\t\t\t\t\t\tif (trkData.live.mapLinesByAssetId[asset.Id] === undefined) {\r\n\t\t\t\t\t\t\t\t// init line\r\n\t\t\t\t\t\t\t\tvar livePath = [];\r\n\t\t\t\t\t\t\t\tlivePath.push(L.latLng(priorPosition.Lat, priorPosition.Lng));\r\n\t\t\t\t\t\t\t\tlivePath.push(L.latLng(newPosition.Lat, newPosition.Lng));\r\n\t\t\t\t\t\t\t\tvar color = convertNamedColorToHex(asset.Color);\r\n\r\n\t\t\t\t\t\t\t\ttrkData.live.mapLinesByAssetId[asset.Id] = L.polyline(livePath, {\r\n\t\t\t\t\t\t\t\t\tweight: 4,\r\n\t\t\t\t\t\t\t\t\tcolor: color,\r\n\t\t\t\t\t\t\t\t\topacity: 0.75,\r\n\t\t\t\t\t\t\t\t\tpane: \"back-lines\",\r\n\t\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\t\tif (isActive) {\r\n\t\t\t\t\t\t\t\t\taddItemToMap(trkData.live.mapLinesByAssetId[asset.Id]);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t// add *current* position to live path\r\n\t\t\t\t\t\t\t\t// pop oldest position off if more than 10\r\n\t\t\t\t\t\t\t\tvar currentLivePath = trkData.live.mapLinesByAssetId[asset.Id].getLatLngs();\r\n\t\t\t\t\t\t\t\tif (currentLivePath.length >= options.maxLiveTrailPositions) {\r\n\t\t\t\t\t\t\t\t\tcurrentLivePath.splice(0, 1);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\tcurrentLivePath.push(L.latLng(newPosition.Lat, newPosition.Lng));\r\n\t\t\t\t\t\t\t\ttrkData.live.mapLinesByAssetId[asset.Id].setLatLngs(currentLivePath);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// remove old marker\r\n\t\t\t\t\tif (priorPositionMarker !== undefined) {\r\n\t\t\t\t\t\t$(priorPositionMarker.getElement()).bsTooltip(\"dispose\");\r\n\t\t\t\t\t\tremoveItemFromMap(priorPositionMarker);\r\n\t\t\t\t\t\tpriorPositionMarker.data.hide = true;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tisNewPosition = true;\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\t// new position for asset which doens't have a previous position\r\n\t\t\t\tlog(\"New position for asset #\" + assetId + \"... No previous.\");\r\n\t\t\t\tisNewPosition = true;\r\n\t\t\t}\r\n\r\n\t\t\tif (isNewPosition) {\r\n\t\t\t\tupdatePositions = true;\r\n\t\t\t\t// add new marker\r\n\t\t\t\t// var point = L.latLng(newPosition.Lat, newPosition.Lng);\r\n\t\t\t\tvar geom = [newPosition.Lat, newPosition.Lng];\r\n\t\t\t\tvar newPositionMarker = addPositionMarkerToPoint(\r\n\t\t\t\t\tgeom,\r\n\t\t\t\t\tfalse,\r\n\t\t\t\t\tnewPosition,\r\n\t\t\t\t\tasset,\r\n\t\t\t\t\t255,\r\n\t\t\t\t\tnull,\r\n\t\t\t\t\tnull,\r\n\t\t\t\t\tnull,\r\n\t\t\t\t\ttrkDataGroups.NORMAL_LIVE\r\n\t\t\t\t);\r\n\r\n\t\t\t\t// toggle visibility\r\n\t\t\t\tif (isActive && !newPosition.IsHidden) {\r\n\t\t\t\t\taddItemToMap(newPositionMarker);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tremoveItemFromMap(newPositionMarker);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (priorPosition !== undefined) {\r\n\t\t\t\t\tvar existingIndex = _.findIndex(trkData.live.latestPositions, function (item) {\r\n\t\t\t\t\t\treturn item.Position.Id === priorPosition.Id;\r\n\t\t\t\t\t});\r\n\t\t\t\t\tif (existingIndex !== -1) {\r\n\t\t\t\t\t\ttrkData.live.latestPositions.splice(existingIndex, 1);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t// normalized position\r\n\t\t\t\tif (trkData.positionsById[newPosition.Id] === undefined) {\r\n\t\t\t\t\ttrkData.positionsById[newPosition.Id] = normalizeAssetData(asset.Id, \"position\", latestPosition);\r\n\t\t\t\t}\r\n\r\n\t\t\t\ttrkData.live.latestPositions.push(trkData.positionsById[newPosition.Id]);\r\n\t\t\t\ttrkData.live.latestPositionsByAssetId[assetId] = trkData.positionsById[newPosition.Id];\r\n\t\t\t\tupdateAssetNotificationTime(assetId, \"positions\", latestPosition.Position.Epoch);\r\n\t\t\t\ttrkData.live.positions.push(latestPosition);\r\n\r\n\t\t\t\ttrkData.live.normalizedPositions.push(trkData.positionsById[newPosition.Id]);\r\n\t\t\t\tif (trkData.live.normalizedPositionsByAssetId[assetId] === undefined) {\r\n\t\t\t\t\ttrkData.live.normalizedPositionsByAssetId[assetId] = [];\r\n\t\t\t\t}\r\n\t\t\t\ttrkData.live.normalizedPositionsByAssetId[assetId].push(trkData.positionsById[newPosition.Id]);\r\n\r\n\t\t\t\tif (trkData.live.positionsByAssetId[assetId] === undefined) {\r\n\t\t\t\t\ttrkData.live.positionsByAssetId[assetId] = [];\r\n\t\t\t\t}\r\n\t\t\t\ttrkData.live.positionsByAssetId[assetId].push(latestPosition);\r\n\t\t\t\tif (trkData.live.markersByAssetId[assetId] === undefined) {\r\n\t\t\t\t\ttrkData.live.markersByAssetId[assetId] = [];\r\n\t\t\t\t}\r\n\t\t\t\ttrkData.live.markersByAssetId[assetId].push(newPositionMarker);\r\n\t\t\t\ttrkData.live.markersByPositionId[newPosition.Id] = newPositionMarker;\r\n\r\n\t\t\t\tif (isMarkerActive) {\r\n\t\t\t\t\t// this position was open with live already\r\n\t\t\t\t\t// act like it was just clicked\r\n\t\t\t\t\tmarkerClick(newPositionMarker, \"position\", null, false);\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// update position history for this asset\r\n\t\t\t\tupdateLiveDataForAsset(asset, false, newPosition);\r\n\r\n\t\t\t\t// if tracking this asset, automatically highlight this new position\r\n\t\t\t\tif (!isNewFollowPosition && state.liveFollow.isActive && $.inArray(asset, state.liveFollow.assets) !== -1) {\r\n\t\t\t\t\tisNewFollowPosition = true;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// check for bouncy event\r\n\t\t\t\tcheckMarkerBounce(newPositionMarker);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (isNewFollowPosition) {\r\n\t\t\topenMostRecentLiveFollowPosition();\r\n\t\t}\r\n\r\n\t\tif (updatePositions) {\r\n\t\t\tthrottles.updatePositionStatus();\r\n\t\t}\r\n\t} catch (ex) {\r\n\t\ttrkData.pending.live = false;\r\n\t\thandleWebServiceError(\"Error querying live assets.\");\r\n\t}\r\n}\r\n\r\nexport function checkMarkerBounce(marker) {\r\n\tif (marker == null) {\r\n\t\treturn;\r\n\t}\r\n\tvar location = marker.data.location;\r\n\tif (location == null) {\r\n\t\treturn;\r\n\t}\r\n\tif (location.Events == null) {\r\n\t\treturn;\r\n\t}\r\n\tvar bouncyEvents = options.bounceOnEvents;\r\n\tfor (var i = 0; i < location.Events.length; i++) {\r\n\t\tvar evt = location.Events[i];\r\n\t\tif (bouncyEvents.indexOf(evt.Type) !== -1) {\r\n\t\t\tif (!marker.isBouncing()) {\r\n\t\t\t\tmarker\r\n\t\t\t\t\t.setBouncingOptions({\r\n\t\t\t\t\t\tbounceHeight: 15,\r\n\t\t\t\t\t\tbounceSpeed: 150,\r\n\t\t\t\t\t\texclusive: false,\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.toggleBouncing();\r\n\t\t\t}\r\n\t\t\tif (marker.bounceTimeout != null) {\r\n\t\t\t\tclearTimeout(marker.bounceTimeout);\r\n\t\t\t}\r\n\t\t\tmarker.bounceTimeout = setTimeout(function () {\r\n\t\t\t\tif (marker.isBouncing()) {\r\n\t\t\t\t\tmarker.stopBouncing();\r\n\t\t\t\t}\r\n\t\t\t}, options.bounceForDuration);\r\n\t\t\treturn;\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport async function queryLiveAssets() {\r\n\tlog(\"Querying live assets.\");\r\n\tvar data = {\r\n\t\tlang: user.dateCulture,\r\n\t\tdbg: getDbg(),\r\n\t};\r\n\r\n\ttry {\r\n\t\tconst req = await fetch(wrapUrl(\"/services/GPSService.asmx/GetLatestPositionsForAssetsReq\"), {\r\n\t\t\tmethod: \"POST\",\r\n\t\t\tbody: JSON.stringify(data),\r\n\t\t\theaders: new Headers({\r\n\t\t\t\t\"Content-Type\": \"application/json; charset=utf-8\",\r\n\t\t\t}),\r\n\t\t});\r\n\t\tconst msg = await req.json();\r\n\r\n\t\tif (!msg.d) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tstate.hasQueriedLive = true;\r\n\t\tlog(\"Live assets results received.\");\r\n\t\ttrkData.live.positions = msg.d;\r\n\t\ttrkData.live.normalizedPositions = [];\r\n\t\ttrkData.live.positionsByAssetId = _.groupBy(trkData.live.positions, \"AssetId\");\r\n\r\n\t\ttrkData.live.messageCounts = [];\r\n\t\ttrkData.live.messageCountsByAssetId = {};\r\n\r\n\t\tconst markers = [];\r\n\r\n\t\t// only returns assets with at least one position -- if an asset has no positions it will not be included\r\n\t\tvar isNewFollowPosition = false;\r\n\t\tfor (var i = 0; i < trkData.live.positions.length; i++) {\r\n\t\t\t// asset/position\r\n\t\t\tvar assetId = trkData.live.positions[i].AssetId;\r\n\t\t\tvar asset = findAssetById(assetId);\r\n\t\t\tif (asset === undefined || asset === null) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tvar position = trkData.live.positions[i].Position;\r\n\r\n\t\t\tif (trkData.positionsById[position.Id] === undefined) {\r\n\t\t\t\ttrkData.positionsById[position.Id] = normalizeAssetData(\r\n\t\t\t\t\tasset.Id,\r\n\t\t\t\t\t\"position\",\r\n\t\t\t\t\ttrkData.live.positions[i].Position\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t\ttrkData.live.normalizedPositions.push(trkData.positionsById[position.Id]);\r\n\r\n\t\t\tvar isActive =\r\n\t\t\t\tstate.activeMapMode === mapModes.LIVE && !isItemIncluded(user.displayPreferences.hiddenAssets, asset.Id);\r\n\r\n\t\t\t// add position to map\r\n\t\t\tvar positionMarker = addPositionMarkerToPoint(\r\n\t\t\t\t[position.Lat, position.Lng],\r\n\t\t\t\tfalse,\r\n\t\t\t\tposition,\r\n\t\t\t\tasset,\r\n\t\t\t\t255,\r\n\t\t\t\tnull,\r\n\t\t\t\tnull,\r\n\t\t\t\tnull,\r\n\t\t\t\ttrkDataGroups.NORMAL_LIVE\r\n\t\t\t);\r\n\r\n\t\t\t// toggle visibility\r\n\t\t\tif (isActive && !position.IsHidden) {\r\n\t\t\t\taddItemToMap(positionMarker);\r\n\t\t\t} else {\r\n\t\t\t\tremoveItemFromMap(positionMarker);\r\n\t\t\t}\r\n\r\n\t\t\tif (!isNewFollowPosition && state.liveFollow.isActive && _.indexOf(state.liveFollow.assets, asset) !== -1) {\r\n\t\t\t\tisNewFollowPosition = true;\r\n\t\t\t}\r\n\r\n\t\t\tmarkers.push(positionMarker);\r\n\t\t}\r\n\r\n\t\ttrkData.live.latestPositions = trkData.live.normalizedPositions.slice(); // copy array\r\n\t\ttrkData.live.latestPositionsByAssetId = _.keyBy(trkData.live.latestPositions, \"AssetId\");\r\n\t\t_.each(trkData.live.latestPositionsByAssetId, function (latestPosition) {\r\n\t\t\tupdateAssetNotificationTime(latestPosition.AssetId, \"positions\", latestPosition.Epoch);\r\n\t\t});\r\n\r\n\t\ttrkData.live.normalizedPositionsByAssetId = _.groupBy(trkData.live.normalizedPositions, \"AssetId\");\r\n\t\ttrkData.live.markersByAssetId = _.groupBy(markers, function (marker) {\r\n\t\t\treturn marker.data.assetId;\r\n\t\t});\r\n\t\ttrkData.live.markersByPositionId = _.keyBy(markers, function (marker) {\r\n\t\t\treturn marker.data.location.Id;\r\n\t\t});\r\n\r\n\t\t// show active locations in history area\r\n\t\t_.each(trkData.assets, function (asset) {\r\n\t\t\tupdateLiveDataForAsset(asset, true, trkData.live.latestPositionsByAssetId[asset.Id]);\r\n\t\t});\r\n\t\t//createLivePositionResults();\r\n\r\n\t\tgetLiveAssetsShown();\r\n\r\n\t\tif (isNewFollowPosition) {\r\n\t\t\topenMostRecentLiveFollowPosition();\r\n\t\t}\r\n\r\n\t\t// check for default asset highlight\r\n\t\tif (!showDefaultAsset()) {\r\n\t\t\t// set bounds\r\n\t\t\tsetMapBounds();\r\n\t\t}\r\n\r\n\t\tthrottles.updatePositionStatus();\r\n\t\ttrkData.live.isInitialized = true;\r\n\t\tdomNodes.mapMode.liveLoaded.textContent = moment()\r\n\t\t\t.subtract(user.tickOffset, \"ms\")\r\n\t\t\t.format(user.dateFormat.substring(0, user.dateFormat.indexOf(\" \")) + \" HH:mm\");\r\n\t} catch (ex) {\r\n\t\thandleWebServiceError(\"Error querying live assets.\");\r\n\t}\r\n}\r\n\r\nfunction updateLiveDataForAsset(asset, ignoreState, position) {\r\n\tif (asset === undefined || asset === null) {\r\n\t\treturn;\r\n\t}\r\n\tvar latestPosition = trkData.live.latestPositionsByAssetId[asset.Id];\r\n\tif (latestPosition === undefined) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar nodes = domNodes.assets[asset.Id];\r\n\tif (nodes === undefined) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar position = latestPosition.Position;\r\n\tif (ignoreState === undefined || ignoreState === false) {\r\n\t\tupdateAssetState(asset, position.State);\r\n\t} else {\r\n\t\tupdateAssetState(asset, asset.State);\r\n\t}\r\n\r\n\tupdateAssetFunctionBadges(getAssetDataGroupForCurrentViewMode(), asset.Id);\r\n\tupdateGroupFunctionBadges(getAssetDataGroupForCurrentViewMode(), [asset.Id], \"asset\");\r\n\tupdateTimeBasedNotificationIndicatorsForAsset(asset);\r\n\t_.each(asset.ParentGroupIds, function (groupId) {\r\n\t\tupdateTimeBasedNotificationIndicatorsForGroup(groupId);\r\n\t});\r\n\r\n\t// refresh the positions dialog if it is currently opened for a related asset or group\r\n\tif (state.activeMapMode === mapModes.LIVE) {\r\n\t\tif (position !== undefined && position !== null) {\r\n\t\t\tif (\r\n\t\t\t\tdomNodes.panels.secondary.getAttribute(\"data-group-for\") === \"dialog\" &&\r\n\t\t\t\tdomNodes.panels.secondary.getAttribute(\"data-item-type\") === \"assets\" &&\r\n\t\t\t\tdocument.getElementById(\"dialog-functions\").querySelector(\".dialog\") === domNodes.dialogs.assetPositions\r\n\t\t\t) {\r\n\t\t\t\t// todo: only append to the existing listing, don't recreate\r\n\t\t\t\tvar assetId = parseInt(domNodes.panels.secondary.getAttribute(\"data-item-id\"));\r\n\t\t\t\tif (asset.Id === assetId) {\r\n\t\t\t\t\topenPositionsForAsset(asset);\r\n\t\t\t\t}\r\n\t\t\t} else if (\r\n\t\t\t\tdomNodes.panels.secondary.getAttribute(\"data-group-for\") === \"dialog\" &&\r\n\t\t\t\tdomNodes.panels.secondary.getAttribute(\"data-item-type\") === \"groups\" &&\r\n\t\t\t\tdocument.getElementById(\"dialog-functions\").querySelector(\".dialog\") === domNodes.dialogs.assetPositions\r\n\t\t\t) {\r\n\t\t\t\t// todo: only append to the existing listing, don't recreate\r\n\t\t\t\tvar groupId = domNodes.panels.secondary.getAttribute(\"data-item-id\");\r\n\t\t\t\tif (asset.ParentGroupIds.indexOf(groupId) !== -1) {\r\n\t\t\t\t\topenPositionsForGroup(findGroupById(groupId));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction showDefaultAsset() {\r\n\tif (state.activeMapMode !== mapModes.LIVE) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tif (!state.isFirstLoad) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\tif (options.showAssetId == \"\" && options.showAssetUniqueId == \"\") {\r\n\t\treturn false;\r\n\t}\r\n\r\n\tvar asset = null;\r\n\tif (options.showAssetUniqueId != \"\") {\r\n\t\tasset = findAssetByUniqueId(options.showAssetUniqueId);\r\n\t}\r\n\tif (options.showAssetId != \"\") {\r\n\t\tasset = findAssetById(options.showAssetId);\r\n\t}\r\n\r\n\tif (asset == null) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\topenAssetLatestPosition(asset);\r\n\r\n\tif (user.isAnonymous) {\r\n\t\tliveFollowAsset(asset);\r\n\t}\r\n\treturn true;\r\n}\r\n\r\nexport function openAssetLatestPosition(asset) {\r\n\tvar matchedMarker = getCurrentMarkerForAsset(asset);\r\n\tif (matchedMarker === undefined || matchedMarker === null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\ttoggleAssetActive(asset.Id, true, true);\r\n\r\n\tif (!options.disablePositionPopup) {\r\n\t\t//matchedMarker.fire('click');\r\n\t\tmarkerClick(matchedMarker, \"position\", null, false);\r\n\t} else {\r\n\t\tmap.setView(matchedMarker.getLatLng(), options.defaultZoom);\r\n\t}\r\n}\r\n\r\nexport function getLiveAssetsShown() {\r\n\tvar visibleAssetsWithPositions = _.filter(trkData.live.latestPositionsByAssetId, function (item) {\r\n\t\treturn _.includes(trkData.visible.assets, item.AssetId);\r\n\t});\r\n\r\n\tdocument.getElementById(\"history-noresults\").classList.remove(\"is-visible\");\r\n\tvar noResults = document.getElementById(\"live-noresults\");\r\n\tif (visibleAssetsWithPositions.length > 0) {\r\n\t\tvar msg_liveAssets = strings.MSG_ASSETS_SHOWN_LIVE;\r\n\t\tmsg_liveAssets = msg_liveAssets.replace(\"{0}\", visibleAssetsWithPositions.length); // num assets\r\n\t\tmsg_liveAssets = msg_liveAssets.replace(\"{1}\", visibleAssetsWithPositions.length > 1 ? \"s\" : \"\"); // assets plural\r\n\t\tdomNodes.mapTools.visibleSummary.textContent = msg_liveAssets;\r\n\t\tdomNodes.mapMode.visibleAssetsLive.textContent = visibleAssetsWithPositions.length;\r\n\t\tnoResults.classList.remove(\"is-visible\");\r\n\t} else {\r\n\t\tdomNodes.mapTools.visibleSummary.textContent = \"\";\r\n\t\tdomNodes.mapMode.visibleAssetsLive.textContent = strings.GROUP_NONE;\r\n\t\tif (state.hasQueriedLive && trkData.visible.assets.length > 0) {\r\n\t\t\tnoResults.classList.add(\"is-visible\");\r\n\t\t}\r\n\t}\r\n}","import trkData from \"./data.js\";\r\nimport { viewModes } from \"./const.js\";\r\nimport user from \"./user.js\";\r\n\r\nimport _ from \"lodash\";\r\nimport moment from \"moment\"; // https://www.npmjs.com/package/moment\r\n\r\nexport function updateLimitedDataResults(viewMode) {\r\n\tvar source = null;\r\n\tif (viewMode === viewModes.NORMAL) {\r\n\t\tsource = trkData.history;\r\n\t} else if (viewMode === viewModes.SHARED_VIEW) {\r\n\t\tsource = trkData.sharedView;\r\n\t}\r\n\tif (source === null || source.limitedData === undefined || source.limitedData === null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\t// update UI\r\n\t// reverse paging system\r\n\tvar lastVisibleNumber =\r\n\t\tsource.limitedData.counts.Positions - source.limitedData.limit * (source.limitedData.currentPage - 1);\r\n\tvar firstVisibleNumber = lastVisibleNumber - source.limitedData.limit + 1;\r\n\tif (firstVisibleNumber < 1) {\r\n\t\tfirstVisibleNumber = 1;\r\n\t}\r\n\r\n\tdocument.getElementById(\"limited-data-range\").textContent = \"#\" + firstVisibleNumber + \" - #\" + lastVisibleNumber;\r\n\tdocument.getElementById(\"limited-data-from\").textContent =\r\n\t\tsource.limitedData.pageDates[source.limitedData.currentPage].visibleFromLocal;\r\n\tdocument.getElementById(\"limited-data-to\").textContent =\r\n\t\tsource.limitedData.pageDates[source.limitedData.currentPage].visibleToLocal;\r\n\r\n\t// enable/disable paging buttons\r\n\tif (source.limitedData.currentPage === source.limitedData.pages) {\r\n\t\tdocument.getElementById(\"limited-data-prev\").disabled = true;\r\n\t\tdocument.getElementById(\"limited-data-prev\").classList.add(\"disabled\");\r\n\t} else {\r\n\t\tdocument.getElementById(\"limited-data-prev\").disabled = false;\r\n\t\tdocument.getElementById(\"limited-data-prev\").classList.remove(\"disabled\");\r\n\t}\r\n\tif (source.limitedData.currentPage === 1) {\r\n\t\tdocument.getElementById(\"limited-data-next\").disabled = true;\r\n\t\tdocument.getElementById(\"limited-data-next\").classList.add(\"disabled\");\r\n\t} else {\r\n\t\tdocument.getElementById(\"limited-data-next\").disabled = false;\r\n\t\tdocument.getElementById(\"limited-data-next\").classList.remove(\"disabled\");\r\n\t}\r\n}\r\n\r\nexport function handleLimitedDataResult(viewMode, fromDateUtc, toDateUtc, counts, limit) {\r\n\tvar source = null;\r\n\tif (viewMode === viewModes.NORMAL) {\r\n\t\tsource = trkData.history;\r\n\t} else if (viewMode === viewModes.SHARED_VIEW) {\r\n\t\tsource = trkData.sharedView;\r\n\t}\r\n\tif (source === null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar orderedPositions = _.sortBy(source.normalizedPositions, \"Epoch\");\r\n\tvar firstVisible = _.find(orderedPositions, function (item) {\r\n\t\treturn !item.Position.IsHidden;\r\n\t});\r\n\tvar lastVisible = _.find(orderedPositions.reverse(), function (item) {\r\n\t\treturn !item.Position.IsHidden;\r\n\t});\r\n\tvar firstVisibleTime = null;\r\n\tvar firstVisibleTimeLocal = null;\r\n\tvar lastVisibleTime = null;\r\n\tvar lastVisibleTimeLocal = null;\r\n\tif (firstVisible !== undefined) {\r\n\t\tfirstVisibleTime = moment(firstVisible.Epoch * 1000)\r\n\t\t\t.subtract(user.tickOffset, \"ms\")\r\n\t\t\t.format(user.dateWithStandardTimeFormat);\r\n\t\tfirstVisibleTimeLocal = firstVisible.Position.Time;\r\n\t}\r\n\tif (lastVisible !== undefined) {\r\n\t\tlastVisibleTime = moment(lastVisible.Epoch * 1000)\r\n\t\t\t.subtract(user.tickOffset, \"ms\")\r\n\t\t\t.format(user.dateWithStandardTimeFormat);\r\n\t\tlastVisibleTimeLocal = lastVisible.Position.Time;\r\n\t}\r\n\tif (source.limitedData === null) {\r\n\t\t// TODO set visible-positions-history.textContent to counts.Positions\r\n\t\tvar pages = Math.ceil(counts.Positions / limit);\r\n\r\n\t\tsource.limitedData = {\r\n\t\t\tcounts: counts,\r\n\t\t\tcurrentPage: 1,\r\n\t\t\tpages: pages,\r\n\t\t\tfromDateUtc: fromDateUtc,\r\n\t\t\tfromDateEpoch:\r\n\t\t\t\tfromDateUtc !== null && fromDateUtc !== \"\"\r\n\t\t\t\t\t? moment.utc(fromDateUtc, user.dateWithStandardTimeFormat).valueOf()\r\n\t\t\t\t\t: null,\r\n\t\t\ttoDateUtc: toDateUtc,\r\n\t\t\ttoDateEpoch:\r\n\t\t\t\ttoDateUtc !== null && toDateUtc !== \"\"\r\n\t\t\t\t\t? moment.utc(toDateUtc, user.dateWithStandardTimeFormat).valueOf()\r\n\t\t\t\t\t: null,\r\n\t\t\tpageDates: {},\r\n\t\t\tlimit: limit,\r\n\t\t};\r\n\t\t// TODO proper timezone support\r\n\t\tsource.limitedData.fromDate =\r\n\t\t\tsource.limitedData.fromDateEpoch !== null\r\n\t\t\t\t? moment(source.limitedData.fromDateEpoch).subtract(user.tickOffset).format(user.dateFormat)\r\n\t\t\t\t: null;\r\n\t\tsource.limitedData.toDate =\r\n\t\t\tsource.limitedData.toDateEpoch !== null\r\n\t\t\t\t? moment(source.limitedData.toDateEpoch).subtract(user.tickOffset).format(user.dateFormat)\r\n\t\t\t\t: null;\r\n\t}\r\n\r\n\tsource.limitedData.visibleFromDateEpoch = firstVisible !== undefined ? firstVisible.Epoch * 1000 : null; // TODO use an incrementing counter instead of time\r\n\tsource.limitedData.visibleToDateEpoch = lastVisible !== undefined ? lastVisible.Epoch * 1000 : null; // TODO use an incrementing counter instead of time\r\n\tsource.limitedData.visibleFrom = firstVisibleTime;\r\n\tsource.limitedData.visibleFromLocal = firstVisibleTimeLocal;\r\n\tsource.limitedData.visibleTo = lastVisibleTime;\r\n\tsource.limitedData.visibleToLocal = lastVisibleTimeLocal;\r\n\tsource.limitedData.pageDates[source.limitedData.currentPage] = {\r\n\t\tfromUtc: fromDateUtc,\r\n\t\ttoUtc: toDateUtc,\r\n\t\tvisibleFrom: firstVisibleTime,\r\n\t\tvisibleFromLocal: firstVisibleTimeLocal,\r\n\t\t//visibleFrom: (source.limitedData.currentPage !== source.limitedData.pages ? firstVisibleTime : null),\r\n\t\tvisibleFromUtc: moment.utc(firstVisible.Epoch * 1000).format(user.dateWithStandardTimeFormat),\r\n\t\t//visibleFromUtc: (source.limitedData.currentPage !== source.limitedData.pages ? moment.utc(firstVisible.Epoch * 1000).format(user.dateWithStandardTimeFormat) : null),\r\n\t\tvisibleFromEpoch: firstVisible.Epoch * 1000,\r\n\t\t//visibleFromEpoch: (source.limitedData.currentPage !== source.limitedData.pages ? (firstVisible.Epoch * 1000) : null),\r\n\t\tvisibleTo: lastVisibleTime,\r\n\t\tvisibleToLocal: lastVisibleTimeLocal,\r\n\t\t//visibleTo: (source.limitedData.currentPage !== 1 ? lastVisibleTime : null),\r\n\t\tvisibleToUtc: moment.utc(lastVisible.Epoch * 1000).format(user.dateWithStandardTimeFormat),\r\n\t\t//visibleToUtc: (source.limitedData.currentPage !== 1 ? moment.utc(lastVisible.Epoch * 1000).format(user.dateWithStandardTimeFormat) : null),\r\n\t\tvisibleToEpoch: lastVisible.Epoch * 1000,\r\n\t\t//visibleToEpoch: (source.limitedData.currentPage !== 1 ? (lastVisible.Epoch * 1000) : null)\r\n\t};\r\n\tupdateLimitedDataResults(viewMode);\r\n}\r\n","import trkData from \"./data.js\";\r\nimport { viewModes, mapModes } from \"./const.js\";\r\nimport state from \"./state.js\";\r\nimport strings from \"./strings.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport { updateLimitedDataResults } from \"./limited-data.js\";\r\nimport user from \"./user.js\";\r\n\r\nimport _ from \"lodash\";\r\nimport moment from \"moment\"; // https://www.npmjs.com/package/moment\r\n\r\nexport function updateMapModePanel() {\r\n\tvar historyCustom = document.getElementById(\"history-custom\");\r\n\tif (state.activeViewMode === viewModes.NORMAL && state.activeMapMode === mapModes.LIVE) {\r\n\t\tdomNodes.moduleName.textContent = strings.MODE_LIVE;\r\n\t\tdomNodes.assetsModeLive.classList.add(\"is-visible\");\r\n\t\tdomNodes.assetsModeHistory.classList.remove(\"is-visible\");\r\n\t\tdomNodes.mapMode.container.classList.add(\"mode-live\");\r\n\t\tdomNodes.mapMode.container.classList.remove(\"mode-history\");\r\n\t\tvar historyButtons = document.getElementById(\"filter-history-range\").querySelectorAll(\"button\");\r\n\t\t_.each(historyButtons, function (button) {\r\n\t\t\tbutton.classList.remove(\"active\");\r\n\t\t});\r\n\t\tdomNodes.mapMode.dateRange.classList.remove(\"is-visible\");\r\n\t\tdomNodes.mapMode.modeLiveButton.classList.add(\"active\");\r\n\t\thistoryCustom.classList.remove(\"btn-primary\");\r\n\t\tdomNodes.mapMode.container.classList.remove(\"is-limited\");\r\n\t\tdomNodes.mapMode.container.classList.remove(\"has-excess\");\r\n\t\tdomNodes.mapMode.container.classList.remove(\"is-filtered\");\r\n\r\n\t\t// hide calendar\r\n\t} else {\r\n\t\tdomNodes.moduleName.textContent = strings.MODE_HISTORY;\r\n\t\tdomNodes.assetsModeLive.classList.remove(\"is-visible\");\r\n\t\tdomNodes.assetsModeHistory.classList.add(\"is-visible\");\r\n\t\tdomNodes.mapMode.container.classList.add(\"mode-history\");\r\n\t\tdomNodes.mapMode.container.classList.remove(\"mode-live\");\r\n\t\tdomNodes.mapMode.modeLiveButton.classList.remove(\"active\");\r\n\r\n\t\tdomNodes.mapMode.container.classList.remove(\"is-filtered\");\r\n\t\tdomNodes.mapMode.container.classList.remove(\"has-excess\");\r\n\t\tdomNodes.mapMode.container.classList.remove(\"is-limited\");\r\n\r\n\t\tif (state.activeViewMode === viewModes.NORMAL) {\r\n\t\t\t// if any pre-defined date range buttons are active then hide inactivate custom and hide datepickers\r\n\r\n\t\t\tvar historyButtons = document.getElementById(\"filter-history-range\").querySelectorAll(\"button\");\r\n\t\t\tvar isPredefinedRange = false;\r\n\t\t\t_.each(historyButtons, function (button) {\r\n\t\t\t\tif (button.classList.contains(\"active\") && button.id !== \"history-live\" && button.id !== \"history-custom\") {\r\n\t\t\t\t\tisPredefinedRange = true;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\tif (isPredefinedRange) {\r\n\t\t\t\tdomNodes.mapMode.dateRange.classList.remove(\"is-visible\");\r\n\t\t\t\thistoryCustom.classList.remove(\"btn-primary\");\r\n\t\t\t\thistoryCustom.classList.remove(\"active\");\r\n\t\t\t}\r\n\r\n\t\t\tif (trkData.history.isLimited || trkData.history.limitedData !== null) {\r\n\t\t\t\tdomNodes.mapMode.container.classList.add(\"has-excess\");\r\n\t\t\t\tif (trkData.history.isLoadedLimitedData || trkData.history.limitedData !== null) {\r\n\t\t\t\t\tdomNodes.mapMode.container.classList.add(\"is-limited\");\r\n\t\t\t\t\tif (trkData.history.limitedData !== null) {\r\n\t\t\t\t\t\tdomNodes.mapMode.container.classList.add(\"is-filtered\");\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (state.activeViewMode === viewModes.SHARED_VIEW) {\r\n\t\t\tif (trkData.sharedView.isLimited || trkData.sharedView.limitedData !== null) {\r\n\t\t\t\tdomNodes.mapMode.container.classList.add(\"has-excess\");\r\n\t\t\t\tif (trkData.sharedView.isLoadedLimitedData || trkData.sharedView.limitedData !== null) {\r\n\t\t\t\t\tdomNodes.mapMode.container.classList.add(\"is-limited\");\r\n\t\t\t\t\tif (trkData.sharedView.limitedData !== null) {\r\n\t\t\t\t\t\tdomNodes.mapMode.container.classList.add(\"is-filtered\");\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tupdateLimitedDataResults(state.activeViewMode);\r\n\t}\r\n\tupdateMapModeDateRange();\r\n}\r\n\r\nexport function updateMapModeDateRange() {\r\n\tvar from = null;\r\n\tvar to = null;\r\n\tif (state.activeViewMode === viewModes.NORMAL) {\r\n\t\tif (trkData.history.limitedData !== null) {\r\n\t\t\tfrom = trkData.history.limitedData.fromDate;\r\n\t\t\tto = trkData.history.limitedData.toDate;\r\n\t\t} else {\r\n\t\t\tfrom = trkData.history.fromDateFull;\r\n\t\t\tto = trkData.history.toDate;\r\n\t\t}\r\n\t} else if (state.activeViewMode === viewModes.SHARED_VIEW) {\r\n\t\tif (trkData.sharedView.limitedData !== null) {\r\n\t\t\tfrom = trkData.sharedView.limitedData.fromDate;\r\n\t\t\tto = trkData.sharedView.limitedData.toDate;\r\n\t\t} else {\r\n\t\t\tfrom = trkData.sharedView.fromDate;\r\n\t\t\tto = trkData.sharedView.toDate;\r\n\t\t}\r\n\t}\r\n\r\n\tif (to === \"\" || to === undefined) {\r\n\t\tto = null;\r\n\t}\r\n\tif (from === \"\" || from === undefined) {\r\n\t\tfrom = null;\r\n\t}\r\n\r\n\tif (from === null && to === null) {\r\n\t\tdomNodes.mapMode.from.textContent = strings.ALL_TIME;\r\n\t\tdomNodes.mapMode.to.textContent = \"\";\r\n\t} else {\r\n\t\tif (from !== null) {\r\n\t\t\tdomNodes.mapMode.from.textContent = from;\r\n\t\t} else {\r\n\t\t\tdomNodes.mapMode.from.textContent = \"\";\r\n\t\t}\r\n\t\tif (to === null) {\r\n\t\t\tdomNodes.mapMode.to.textContent = moment().subtract(user.tickOffset, \"ms\").format(user.dateFormat); //.substring(0, user.dateFormat.indexOf(' ')) + ' HH:mm');\r\n\t\t} else {\r\n\t\t\tdomNodes.mapMode.to.textContent = to;\r\n\t\t}\r\n\t}\r\n}\r\n","import trkData from \"./data.js\";\r\nimport state from \"./state.js\";\r\nimport strings from \"./strings.js\";\r\nimport { viewModes, mapModes } from \"./const.js\";\r\nimport user from \"./user.js\";\r\nimport { clearBounds, setMapBounds } from \"./map-bounds.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\nimport { handleWebServiceError } from \"./ajax.js\";\r\nimport { resizeApp } from \"./window-layout.js\";\r\nimport { updateSecondaryPanelNotificationContentForNewMode } from \"./panel.js\";\r\nimport { toggleLoadingMessage } from \"./ajax.js\";\r\nimport { createHistoryPositionResults, processAssetHistoryPositionsResult } from \"./position-history.js\";\r\nimport { addAssetEvents } from \"./asset-events.js\";\r\nimport { showResultLimitsIfApplicable } from \"./item-listing.js\";\r\nimport { getLiveAssetsShown } from \"./asset-live.js\";\r\nimport { updateMapModeDateRange } from \"./map-ui.js\";\r\nimport { addItemToMap, removeItemFromMap } from \"./map-items.js\";\r\nimport { findAssetById } from \"./assets.js\";\r\nimport options from \"./options.js\";\r\nimport { highlightPosition } from \"./positions.js\";\r\nimport { handleLimitedDataResult } from \"./limited-data.js\";\r\nimport { updateLatestPositionTimeForSharedView } from \"./asset-positions.js\";\r\n\r\nimport _ from \"lodash\";\r\nimport moment from \"moment\"; // https://www.npmjs.com/package/moment\r\n\r\n// Returns a the post data to be sent to the GetAssetPositionsForDateRange\r\n// endpoint. Parses the date range and formats it into the structure expected\r\n// by the endpoint.\r\nexport function getPostDataForAssetPositions(sensitivity, isPredefinedDateRange, loadLimitedData, dateFilter) {\r\n\tsensitivity = typeof sensitivity !== \"undefined\" ? sensitivity : null;\r\n\tisPredefinedDateRange = typeof isPredefinedDateRange !== \"undefined\" ? isPredefinedDateRange : false;\r\n\r\n\t// reset bounds\r\n\tclearBounds();\r\n\r\n\tvar fromDate = null;\r\n\tvar toDate = null;\r\n\tvar fromDateUtc = null;\r\n\tvar toDateUtc = null;\r\n\tvar fromDateEpoch = null;\r\n\tvar toDateEpoch = null;\r\n\tvar fromDateRaw = document.getElementById(\"txtDateFrom\").value;\r\n\tvar toDateRaw = document.getElementById(\"txtDateTo\").value;\r\n\r\n\t// override shared view's date ranges to show data that otherwise would exceed display limits\r\n\tif (dateFilter !== undefined && dateFilter !== null) {\r\n\t\tfromDateEpoch = dateFilter.fromEpoch;\r\n\t\ttoDateEpoch = dateFilter.toEpoch;\r\n\t} else {\r\n\t\ttrkData.history.limitedData = null;\r\n\t\tfromDateEpoch = moment(fromDateRaw, user.dateWithStandardTimeFormat);\r\n\t\tif (fromDateEpoch.isValid()) {\r\n\t\t\tfromDateEpoch = moment.utc(fromDateEpoch.valueOf() + user.tickOffset).valueOf(); // TODO proper timezone conversion support\r\n\t\t} else {\r\n\t\t\tfromDateEpoch = null;\r\n\t\t}\r\n\t\ttoDateEpoch = moment(toDateRaw, user.dateWithStandardTimeFormat);\r\n\t\tif (toDateEpoch.isValid()) {\r\n\t\t\ttoDateEpoch = moment.utc(toDateEpoch.valueOf() + user.tickOffset).valueOf(); // TODO proper timezone conversion support\r\n\t\t} else {\r\n\t\t\ttoDateEpoch = null;\r\n\t\t}\r\n\t}\r\n\r\n\tvar fromDateFull = null;\r\n\tif (fromDateEpoch !== undefined && fromDateEpoch !== null) {\r\n\t\tfromDate = moment(fromDateEpoch - user.tickOffset).format(user.dateWithStandardTimeFormat);\r\n\t\tfromDateUtc = moment.utc(fromDateEpoch).format(user.dateWithStandardTimeFormat);\r\n\t\tfromDateFull = moment(fromDateEpoch - user.tickOffset).format(user.dateFormat);\r\n\t}\r\n\tif (toDateEpoch !== undefined && toDateEpoch !== null) {\r\n\t\ttoDate = moment(toDateEpoch - user.tickOffset).format(user.dateWithStandardTimeFormat);\r\n\t\ttoDateUtc = moment.utc(toDateEpoch).format(user.dateWithStandardTimeFormat);\r\n\t}\r\n\r\n\ttrkData.history.fromDate = fromDate;\r\n\ttrkData.history.fromDateFull = fromDateFull;\r\n\ttrkData.history.toDate = toDate;\r\n\ttrkData.history.fromDateUtc = fromDateUtc;\r\n\ttrkData.history.toDateUtc = toDateUtc;\r\n\ttrkData.history.fromDateEpoch = fromDateEpoch;\r\n\ttrkData.history.toDateEpoch = toDateEpoch;\r\n\r\n\tvar assetIds = getVisibleAssetIds().join(\",\");\r\n\tif (assetIds == \"\") {\r\n\t\tconsole.log(\"No assets selected!\");\r\n\t\t//populateEvents([]);\r\n\t\tcreateHistoryPositionResults();\r\n\t\treturn;\r\n\t}\r\n\r\n\tif (loadLimitedData === undefined || loadLimitedData === null) {\r\n\t\tloadLimitedData = false;\r\n\t}\r\n\r\n\treturn {\r\n\t\tfromDate: fromDate,\r\n\t\ttoDate: toDate,\r\n\t\tassetIds: assetIds,\r\n\t\tzoomLevel: null,\r\n\t\tsensitivity: sensitivity,\r\n\t\tformat: user.dateFormat,\r\n\t\tlang: user.dateCulture,\r\n\t\tloadLimitedData: loadLimitedData,\r\n\t\tisMobile: state.isMobile,\r\n\t\tfromDateRaw: fromDateRaw,\r\n\t\ttoDateRaw: toDateRaw,\r\n\t\ttickOffset: user.tickOffset,\r\n\t\tformatDateTime: user.dateWithStandardTimeFormat,\r\n\t};\r\n}\r\n\r\n// Queries historic positions for active assets (not live positions)\r\nexport async function queryActiveAssets(\r\n\tsensitivity,\r\n\tisPredefinedDateRange,\r\n\tloadLimitedData, // Whether to ignore the \"more than 3000 positions\" warning\r\n\tdateFilter, // Plaing object containing `from` and `to` values from the form\r\n) {\r\n\r\n\t// disable show button and show loading message\r\n\tdomNodes.mapMode.show.disabled = true;\r\n\ttoggleLoadingMessage(true, \"assetPositions\");\r\n\t// todo: start data loading message here\r\n\r\n\ttry {\r\n\t\tconst postData = getPostDataForAssetPositions(sensitivity, isPredefinedDateRange, loadLimitedData, dateFilter);\r\n\t\tconst req = await fetch(\r\n\t\t\twrapUrl(\"/services/GPSService.asmx/GetAssetPositionsForDateRange\"),\r\n\t\t\t{\r\n\t\t\t\tmethod: \"POST\",\r\n\t\t\t\tbody: JSON.stringify(postData),\r\n\t\t\t\theaders: new Headers({\r\n\t\t\t\t\t\"Content-Type\": \"application/json; charset=utf-8\",\r\n\t\t\t\t}),\r\n\t\t\t}\r\n\t\t);\r\n\r\n\t\tconst msg = await req.json();\r\n\r\n\t\tstate.hasQueriedHistory = true;\r\n\t\tdomNodes.mapMode.show.disabled = false;\r\n\t\ttoggleLoadingMessage(false, \"assetPositions\");\r\n\r\n\t\t// if we've switched out of history mode before the query completed, discard the results\r\n\t\tif (state.activeMapMode === mapModes.LIVE) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tif (msg.d) {\r\n\t\t\tvar result = msg.d;\r\n\r\n\t\t\t// if it's a limited result that has errored out, don't clear the current view, discard the results\r\n\t\t\tif (result.IsLimited === true && result.IncludesLimitedData === false) {\r\n\t\t\t\tshowResultLimitsIfApplicable(result, false);\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tclearHistoryFromMap();\r\n\t\t\ttrkData.history.isLimited = result.IsLimited;\r\n\t\t\ttrkData.history.isLoadedLimitedData = result.IncludesLimitedData;\r\n\t\t\ttrkData.history.isDateFiltered = !!dateFilter;\r\n\r\n\t\t\ttrkData.history.positions = [];\r\n\t\t\ttrkData.history.normalizedPositions = [];\r\n\t\t\ttrkData.history.events = [];\r\n\t\t\ttrkData.history.normalizedEvents = [];\r\n\t\t\ttrkData.history.normalizedEventIds = {};\r\n\t\t\ttrkData.history.normalizedEventsByAssetId = {};\r\n\t\t\ttrkData.history.positionsByAssetId = {};\r\n\t\t\ttrkData.history.normalizedPositionsByAssetId = {};\r\n\t\t\ttrkData.history.messageCounts = [];\r\n\t\t\ttrkData.history.messageCountsByAssetId = {};\r\n\t\t\ttrkData.history.messages = [];\r\n\t\t\ttrkData.history.messagesByAssetId = {};\r\n\t\t\ttrkData.history.normalizedMessages = [];\r\n\t\t\ttrkData.history.normalizedMessagesByAssetId = {};\r\n\t\t\ttrkData.history.assetIdsWithResults = {};\r\n\t\t\tresult.Assets.forEach(function (assetResult) {\r\n\t\t\t\t// also handles message counts for some reason\r\n\t\t\t\tprocessAssetHistoryPositionsResult(assetResult);\r\n\t\t\t});\r\n\r\n\t\t\tif (result.Events !== null) {\r\n\t\t\t\taddAssetEvents(result.Events, mapModes.HISTORY);\r\n\t\t\t}\r\n\r\n\t\t\tcreateHistoryPositionResults();\r\n\t\t\t// handle result.Events - populate events data table #event-panel, .panel-content\r\n\t\t\t//populateEvents(trkData.history.events); // todo: this will be dupe called\r\n\t\t\t//populateEvents(result.Events);\r\n\r\n\t\t\t// are we loading a specific position\r\n\t\t\t// hack: using setTimeout always feels like a hack\r\n\t\t\t// as we are waiting for the map to render or resize?\r\n\t\t\t// it should be determined what specifically prevents the highlight position\r\n\t\t\t// so we can add an event trigger on that specific event instead\r\n\t\t\t// of having a general timeout which may be too short or too long\r\n\t\t\tsetTimeout(highlightInitialPosition, 500);\r\n\t\t\tresizeApp(true);\r\n\t\t\tsetMapBounds();\r\n\t\t\tupdateLatestPositionTimeForSharedView();\r\n\t\t\tupdateSecondaryPanelNotificationContentForNewMode();\r\n\r\n\t\t\tshowResultLimitsIfApplicable(result, !!dateFilter);\r\n\t\t\t//domNodes.mapMode.from.textContent = fromDate;\r\n\t\t\t//if (toDate === '') {\r\n\t\t\t// domNodes.mapMode.to.textContent = moment().subtract(user.tickOffset, 'ms').format(user.dateFormat.substring(0, user.dateFormat.indexOf(' ')) + ' HH:mm');\r\n\t\t\t//} else {\r\n\t\t\t// domNodes.mapMode.to.textContent = toDate;\r\n\t\t\t//}\r\n\r\n\t\t\tif (!isPredefinedDateRange) {\r\n\t\t\t\t// TODO this is a mess - move to proper spot\r\n\t\t\t\tvar historyCustom = document.getElementById(\"history-custom\");\r\n\t\t\t\tvar historyButtons = document.getElementById(\"filter-history-range\").querySelectorAll(\"button\");\r\n\t\t\t\t_.each(historyButtons, function (button) {\r\n\t\t\t\t\tbutton.classList.remove(\"active\");\r\n\t\t\t\t});\r\n\t\t\t\thistoryCustom.classList.add(\"active\");\r\n\t\t\t\thistoryCustom.classList.add(\"btn-primary\");\r\n\t\t\t}\r\n\r\n\t\t\tif (\r\n\t\t\t\t(trkData.history.isLimited && trkData.history.isLoadedLimitedData) ||\r\n\t\t\t\ttrkData.history.limitedData !== null\r\n\t\t\t) {\r\n\t\t\t\thandleLimitedDataResult(viewModes.NORMAL, trkData.history.fromDateUtc, trkData.history.toDateUtc, result.Counts, result.Limit);\r\n\t\t\t} else {\r\n\t\t\t\ttrkData.history.limitedData = null;\r\n\t\t\t}\r\n\t\t\tupdateActiveAssetInformation(viewModes.NORMAL);\r\n\t\t\tupdateMapModeDateRange();\r\n\t\t}\r\n\t} catch (ex) {\r\n\t\thandleWebServiceError(strings.MSG_ASSET_QUERY_ERROR);\r\n\t\t// re-enable show button and clear loading message\r\n\t\tdomNodes.mapMode.show.disabled = false;\r\n\t\ttoggleLoadingMessage(false, \"assetPositions\");\r\n\t};\r\n}\r\n\r\nexport function updateActiveAssetInformation(viewMode) {\r\n\tviewMode = viewMode !== undefined ? viewMode : viewModes.NORMAL;\r\n\t// todo: if trips are visible, how do they interact with the asset information?\r\n\tif (viewMode !== state.activeViewMode) {\r\n\t\treturn;\r\n\t}\r\n\tif (viewMode === viewModes.NORMAL) {\r\n\t\tif (state.activeMapMode === mapModes.LIVE) {\r\n\t\t\tgetLiveAssetsShown();\r\n\t\t} else {\r\n\t\t\tgetHistoryAssetsAndPositionsShown(viewMode);\r\n\t\t}\r\n\t} else if (viewMode === viewModes.SHARED_VIEW) {\r\n\t\tgetHistoryAssetsAndPositionsShown(viewMode);\r\n\t}\r\n}\r\n\r\nfunction getVisibleAssetIds(viewMode) {\r\n\tviewMode = viewMode !== undefined ? viewMode : viewModes.NORMAL;\r\n\tif (viewMode === viewModes.NORMAL) {\r\n\t\treturn trkData.visible.assets;\r\n\t} else if (viewMode === viewModes.SHARED_VIEW) {\r\n\t\tif (trkData.sharedView.temp !== null) {\r\n\t\t\treturn trkData.sharedView.temp.AssetIds;\r\n\t\t} else if (trkData.sharedView.current !== null) {\r\n\t\t\treturn trkData.sharedView.current.AssetIds;\r\n\t\t} else {\r\n\t\t\treturn [];\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction getHistoryAssetsAndPositionsShown(viewMode) {\r\n\tviewMode = viewMode !== undefined ? viewMode : viewModes.NORMAL;\r\n\tvar visibleAssetIds = getVisibleAssetIds(viewMode);\r\n\tvar visiblePositions = 0;\r\n\tvar dataSource = viewMode === viewModes.SHARED_VIEW ? trkData.sharedView : trkData.history;\r\n\tvar visibleAssetHistory = _.filter(dataSource.normalizedPositionsByAssetId, function (item, id) {\r\n\t\treturn _.includes(visibleAssetIds, parseInt(id));\r\n\t});\r\n\t//var visibleAssetHistory = _.filter(dataSource.normalizedPositions, function (assetPositions, index, list) {\r\n\t// return _.includes(visibleAssetIds, assetPositions.Id);\r\n\t//});\r\n\r\n\tvar visibleAssetsWithPositions = visibleAssetHistory.length;\r\n\t_.each(visibleAssetHistory, function (assetHistory) {\r\n\t\t//visiblePositions += assetHistory.Positions.length;\r\n\t\tvisiblePositions += assetHistory.length;\r\n\t});\r\n\r\n\tdocument.getElementById(\"live-noresults\").classList.remove(\"is-visible\");\r\n\tvar noResults = document.getElementById(\"history-noresults\");\r\n\tif (visibleAssetsWithPositions > 0 && visiblePositions > 0) {\r\n\t\tvar msg_assetsPositions = strings.MSG_ASSETS_AND_POSITIONS;\r\n\t\tmsg_assetsPositions = msg_assetsPositions.replace(\"{0}\", visibleAssetsWithPositions); // num assets\r\n\t\tmsg_assetsPositions = msg_assetsPositions.replace(\"{2}\", visibleAssetsWithPositions > 1 ? \"s\" : \"\"); // assets plural\r\n\t\tmsg_assetsPositions = msg_assetsPositions.replace(\"{1}\", visiblePositions); // num positions\r\n\t\tmsg_assetsPositions = msg_assetsPositions.replace(\"{3}\", visiblePositions > 1 ? \"s\" : \"\"); // positions plural\r\n\t\tdomNodes.mapTools.visibleSummary.textContent = msg_assetsPositions;\r\n\r\n\t\tdomNodes.mapMode.visibleAssetsHistory.textContent = visibleAssetsWithPositions;\r\n\t\tvar visibleText = visiblePositions;\r\n\t\tif (dataSource.limitedData !== null) {\r\n\t\t\tvisibleText += \" / \" + dataSource.limitedData.counts.Positions;\r\n\t\t}\r\n\t\tdomNodes.mapMode.visiblePositionsHistory.textContent = visibleText;\r\n\r\n\t\tnoResults.classList.remove(\"is-visible\");\r\n\t} else {\r\n\t\tdomNodes.mapTools.visibleSummary.textContent = \"\";\r\n\t\tdomNodes.mapMode.visibleAssetsHistory.textContent = strings.GROUP_NONE;\r\n\t\tdomNodes.mapMode.visiblePositionsHistory.textContent = strings.GROUP_NONE;\r\n\t\tif (state.hasQueriedHistory) {\r\n\t\t\tnoResults.classList.add(\"is-visible\");\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction clearHistoryFromMap() {\r\n\t// clear the prior history before running a new one\r\n\t//var markers = _.union(trkData.live.markers, trkData.history.markers);\r\n\t//var markers = trkData.live.markers.concat(trkData.history.markers);\r\n\tvar markers = [];\r\n\tArray.prototype.push.apply(markers, trkData.live.markers);\r\n\tArray.prototype.push.apply(markers, trkData.history.markers);\r\n\r\n\t_.each(markers, function (marker) {\r\n\t\tremoveItemFromMap(marker);\r\n\t});\r\n\r\n\t_.each(trkData.history.markers, function (marker) {\r\n\t\tremoveItemFromMap(marker);\r\n\t});\r\n\ttrkData.history.markers = [];\r\n\ttrkData.history.markersByAssetId = {};\r\n\ttrkData.history.markersByPositionId = {};\r\n\r\n\t// clear lines connecting positions\r\n\t_.each(trkData.history.positions, function (position) {\r\n\t\tvar prAsset = findAssetById(position.Id);\r\n\t\tif (trkData.history.mapLinesByAssetId[prAsset.Id] !== undefined) {\r\n\t\t\t// remove polyline\r\n\t\t\tremoveItemFromMap(trkData.history.mapLinesByAssetId[prAsset.Id]);\r\n\t\t\tdelete trkData.history.mapLinesByAssetId[prAsset.Id];\r\n\t\t}\r\n\t});\r\n\ttrkData.history.mapLinesByAssetId = {};\r\n\r\n\t// clear marker clusters\r\n\t// _.each(trkData.assets, function (asset) {\r\n\t// \tif (trkData.history.markerClustersByAssetId[asset.Id] !== undefined) {\r\n\t// \t\ttrkData.history.markerClustersByAssetId[asset.Id].clearLayers();\r\n\t// \t\t// TODO delete them as well?\r\n\t// \t}\r\n\t// });\r\n\t//trkData.history.markerClustersByAssetId = {};\r\n\r\n\tif (state.openWindow !== null) {\r\n\t\tstate.openWindow.remove();\r\n\t}\r\n\r\n\tclearPositionResults();\r\n}\r\n\r\nfunction highlightInitialPosition() {\r\n\tif (options.showPositionId != \"\") {\r\n\t\thighlightPosition(options.showPositionId, null);\r\n\t\toptions.showPositionId = \"\";\r\n\t}\r\n}\r\n\r\nfunction clearPositionResults() {\r\n\t_.each(domNodes.assets, function (assetNodes) {\r\n\t\t_.each(assetNodes, function (assetNode) {\r\n\t\t\t//var toggle = assetNode.querySelector('.item-toggle');\r\n\t\t\tvar details = assetNode.querySelector(\".item-details\");\r\n\t\t\tvar location = assetNode.querySelector(\".locations\");\r\n\t\t\tvar status = assetNode.querySelectorAll(\".status\");\r\n\t\t\t//toggle.classList.remove('active');\r\n\t\t\t//toggle.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#expand');\r\n\t\t\tdetails.classList.remove(\"is-visible\");\r\n\t\t\tif (location !== null) {\r\n\t\t\t\tlocation.parentNode.removeChild(location);\r\n\t\t\t}\r\n\t\t\t_.each(status, function (item) {\r\n\t\t\t\titem.parentNode.removeChild(item);\r\n\t\t\t});\r\n\t\t});\r\n\t});\r\n}\r\n","import trkData from \"./data.js\";\r\nimport options from \"./options.js\";\r\nimport preferences from \"./preferences.js\";\r\nimport state from \"./state.js\";\r\nimport { map, onTileLoaded } from \"./map-base.js\";\r\nimport { intervals } from \"./timers.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport $j from \"jquery\";\r\nimport L from \"leaflet\";\r\n\r\nexport function initMapTypes() {\r\n\themaOverlay.on(\"tileload\", onTileLoaded(\"hema\"));\r\n\tmoroccoOverlay.on(\"tileload\", onTileLoaded(\"morocco\"));\r\n\tsentinel2SatelliteOverlay.on(\"tileload\", onTileLoaded(\"satellitealt\"));\r\n\r\n\t$j(\"#root\").on(\"mouseleave\", \"#map_type\", function (e) {\r\n\t\t// clear previous timeout, settimeout to half a second\r\n\t\tintervals.mapType = setTimeout(collapseMapType, 1000);\r\n\t});\r\n\r\n\t$j(\"#root\").on(\"mouseenter\", \"#map_type\", function (e) {\r\n\t\tclearTimeout(intervals.mapType);\r\n\t});\r\n\r\n\t$(\"#map-type\").on(\"click\", \"#map-type-labels-show\", function (e) {\r\n\t\tif (this.checked) {\r\n\t\t\ttrkData.isSatelliteLabelOverlayEnabled = true;\r\n\t\t\tchangeMapType(state.currentMapType);\r\n\t\t} else {\r\n\t\t\ttrkData.isSatelliteLabelOverlayEnabled = false;\r\n\t\t\tchangeMapType(state.currentMapType);\r\n\t\t}\r\n\t});\r\n\r\n\t$j(\"#map-type\").on(\"click\", \".road\", function (e) {\r\n\t\te.preventDefault();\r\n\t\tchangeMapType(\"road\");\r\n\t});\r\n\r\n\t$j(\"#map-type\").on(\"click\", \".roadlight\", function (e) {\r\n\t\te.preventDefault();\r\n\t\tchangeMapType(\"roadlight\");\r\n\t});\r\n\r\n\t$j(\"#map-type\").on(\"click\", \".roaddark\", function (e) {\r\n\t\te.preventDefault();\r\n\t\tchangeMapType(\"roaddark\");\r\n\t});\r\n\r\n\t$j(\"#map-type\").on(\"click\", \".satellite\", function (e) {\r\n\t\te.preventDefault();\r\n\t\tchangeMapType(\"satellite\");\r\n\t});\r\n\r\n\t$j(\"#map-type\").on(\"click\", \".satellitealt\", function (e) {\r\n\t\te.preventDefault();\r\n\t\tchangeMapType(\"satellitealt\");\r\n\t});\r\n\r\n\t$j(\"#map-type\").on(\"click\", \".openstreetmap\", function (e) {\r\n\t\te.preventDefault();\r\n\t\tchangeMapType(\"openstreetmap\");\r\n\t});\r\n\r\n\t$j(\"#map-type\").on(\"click\", \".terrain\", function (e) {\r\n\t\te.preventDefault();\r\n\t\tchangeMapType(\"terrain\");\r\n\t});\r\n\r\n\t$j(\"#map-type\").on(\"click\", \".bing\", function (e) {\r\n\t\te.preventDefault();\r\n\t\tchangeMapType(\"bing\");\r\n\t});\r\n\r\n\t$j(\"#map_type\").on(\"click\", \"#map_type_select\", function (e) {\r\n\t\te.preventDefault();\r\n\t\tif ($j(\"#map_type_list:visible\").length > 0) {\r\n\t\t\tcollapseMapType();\r\n\t\t} else {\r\n\t\t\texpandMapType();\r\n\t\t}\r\n\t});\r\n}\r\n\r\n// todo: remove as it does not exist anymore\r\nconst moroccoOverlay = L.tileLayer(\r\n\t\"http://api.navcities.com/tilecache/tilecache.cgi/1.0.0/navcities31v2/{z}/{x}/{y}.png?type=google\",\r\n\t{ zIndex: 2 }\r\n);\r\n\r\nconst hemaOverlay = L.tileLayer(\"http://skippy.hema-labs.com/AUS/ExplorerMap_v1_2/{z}/{y}/{x}.png\", {\r\n\tminZoom: 5,\r\n\tmaxZoom: 14,\r\n\tbounds: L.latLngBounds([10, 95], [-50, 180]),\r\n\tzIndex: 2,\r\n});\r\n\r\nconst sentinel2SatelliteOverlay = L.tileLayer.wms(\"https://{s}.tiles.maps.eox.at/wms?\", {\r\n\tsubdomains: [\"a\", \"b\", \"c\", \"d\", \"e\"],\r\n\tlayers: \"s2cloudless\",\r\n\tformat: \"image/jpeg\",\r\n\tcrs: L.CRS.EPSG4326,\r\n\tattribution:\r\n\t\t\"Sentinel-2 cloudless © https://s2maps.eu by EOX IT Services GmbH (Contains modified Copernicus Sentinel data 2016 & 2017)\",\r\n\tzIndex: 2,\r\n});\r\n\r\nexport const baseTileLayers = [hemaOverlay, moroccoOverlay, sentinel2SatelliteOverlay];\r\n\r\nexport function changeMapType(type) {\r\n\tif (map == null) return;\r\n\tif (type == \"road\" && options.enableHemaMap) {\r\n\t\ttype = \"hema\";\r\n\t} else if (preferences.PREFERENCE_MOROCCO_OVERLAY) {\r\n\t\tif (type != \"satellite\" && type != \"satellitealt\") {\r\n\t\t\ttype = \"morocco\";\r\n\t\t}\r\n\t}\r\n\tstate.currentMapType = type;\r\n\t$(\"#map-type-list a\").removeClass(\"active\");\r\n\r\n\t// bounds, minZoom, maxZoom\r\n\tswitch (type) {\r\n\t\tcase \"satellitealt\":\r\n\t\t\tvar thisBaseLayer = sentinel2SatelliteOverlay;\r\n\r\n\t\t\tmap.setMaxBounds(options.maxBounds);\r\n\t\t\tmap.setMaxZoom(22);\r\n\t\t\tmap.setMinZoom(options.minimumZoom);\r\n\t\t\tif (!map.hasLayer(thisBaseLayer)) {\r\n\t\t\t\tmap.addLayer(thisBaseLayer);\r\n\t\t\t}\r\n\t\t\tfor (var i = 0; i < baseTileLayers.length; i++) {\r\n\t\t\t\tvar baseLayer = baseTileLayers[i];\r\n\t\t\t\tif (map.hasLayer(baseLayer) && baseLayer != thisBaseLayer) {\r\n\t\t\t\t\tmap.removeLayer(baseLayer);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tvar satelliteAlt = $(\"#map-type-list a.satellitealt\").addClass(\"active\");\r\n\t\t\t$(\"#map-type-current\").text(satelliteAlt.text());\r\n\t\t\tbreak;\r\n\r\n\t\tcase \"satellite\":\r\n\t\t\tmap.setMaxBounds(options.maxBounds);\r\n\t\t\tmap.setMaxZoom(20);\r\n\t\t\tmap.setMinZoom(options.minimumZoom);\r\n\r\n\t\t\tvar thisBaseLayer = options.baseLayers.satellitenolabels;\r\n\t\t\tif (trkData.isSatelliteLabelOverlayEnabled && !options.isSatelliteLabelsOverlay) {\r\n\t\t\t\tthisBaseLayer = options.baseLayers.satellitelabels;\r\n\t\t\t}\r\n\r\n\t\t\tif (!map.hasLayer(thisBaseLayer.layer)) {\r\n\t\t\t\tmap.addLayer(thisBaseLayer.layer);\r\n\t\t\t}\r\n\t\t\tif (options.isSatelliteLabelsOverlay && trkData.isSatelliteLabelOverlayEnabled) {\r\n\t\t\t\t// add any label overlay layers\r\n\t\t\t\tif (!map.hasLayer(options.baseLayers.satellitelabels.layer)) {\r\n\t\t\t\t\tmap.addLayer(options.baseLayers.satellitelabels.layer);\r\n\t\t\t\t}\r\n\t\t\t\tif (options.baseLayers.satellitelabelsadditional !== undefined) {\r\n\t\t\t\t\tif (!map.hasLayer(options.baseLayers.satellitelabelsadditional.layer)) {\r\n\t\t\t\t\t\tmap.addLayer(options.baseLayers.satellitelabelsadditional.layer);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tfor (var i = 0; i < baseTileLayers.length; i++) {\r\n\t\t\t\tvar baseLayer = baseTileLayers[i];\r\n\t\t\t\tif (options.isSatelliteLabelsOverlay && trkData.isSatelliteLabelOverlayEnabled) {\r\n\t\t\t\t\tif (\r\n\t\t\t\t\t\tmap.hasLayer(baseLayer) &&\r\n\t\t\t\t\t\tbaseLayer != options.baseLayers.satellitelabels.layer &&\r\n\t\t\t\t\t\toptions.baseLayers.satellitelabelsadditional !== undefined &&\r\n\t\t\t\t\t\tbaseLayer != options.baseLayers.satellitelabelsadditional.layer &&\r\n\t\t\t\t\t\tbaseLayer != options.baseLayers.satellitenolabels.layer\r\n\t\t\t\t\t) {\r\n\t\t\t\t\t\tmap.removeLayer(baseLayer);\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (map.hasLayer(baseLayer) && baseLayer != thisBaseLayer.layer) {\r\n\t\t\t\t\t\tmap.removeLayer(baseLayer);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tvar satellite = $(\"#map-type-list a.satellite\").addClass(\"active\");\r\n\t\t\t$(\"#map-type-current\").text(satellite.text());\r\n\t\t\tif (!preferences.PREFERENCE_REMOVE_ROADS && !preferences.PREFERENCE_MOROCCO_OVERLAY) {\r\n\t\t\t\t$(\"#map-type-labels\").show();\r\n\t\t\t}\r\n\t\t\tdocument.getElementById(\"map-type-labels-show\").checked = trkData.isSatelliteLabelOverlayEnabled;\r\n\t\t\tbreak;\r\n\t\tcase \"morocco\":\r\n\t\t\tvar thisBaseLayer = moroccoOverlay;\r\n\r\n\t\t\tmap.setMaxBounds(options.maxBounds);\r\n\t\t\tmap.setMaxZoom(18);\r\n\t\t\tmap.setMinZoom(options.minimumZoom);\r\n\t\t\tif (!map.hasLayer(thisBaseLayer)) {\r\n\t\t\t\tmap.addLayer(thisBaseLayer);\r\n\t\t\t}\r\n\r\n\t\t\tvar road = $(\"#map-type-list a.road\").addClass(\"active\");\r\n\t\t\t$(\"#map-type-current\").text(road.text());\r\n\t\t\t$(\"#map-type-labels\").hide();\r\n\t\t\tbreak;\r\n\t\tcase \"hema\":\r\n\t\t\tvar thisBaseLayer = hemaOverlay;\r\n\t\t\tmap.setMaxBounds(L.latLngBounds([10, 95], [-50, 180]));\r\n\t\t\tmap.setMaxZoom(14);\r\n\t\t\tmap.setMinZoom(options.minimumZoom > 5 ? options.minimumZoom : 5);\r\n\t\t\tif (!map.hasLayer(thisBaseLayer)) {\r\n\t\t\t\tmap.addLayer(thisBaseLayer);\r\n\t\t\t}\r\n\t\t\t// keep osm overlay?\r\n\t\t\tvar hema = $(\"#map-type-list a.road\").addClass(\"active\");\r\n\t\t\t$(\"#map-type-current\").text(hema.text());\r\n\t\t\t$(\"#map-type-labels\").hide();\r\n\t\t\tbreak;\r\n\t\tcase \"road\":\r\n\t\tdefault:\r\n\t\t\tvar thisBaseLayer = options.baseLayers[type];\r\n\t\t\tmap.setMaxBounds(options.maxBounds);\r\n\t\t\tmap.setMaxZoom(thisBaseLayer.maxZoom);\r\n\t\t\tmap.setMinZoom(options.minimumZoom > thisBaseLayer.minZoom ? options.minimumZoom : thisBaseLayer.minZoom);\r\n\t\t\tif (!map.hasLayer(thisBaseLayer.layer)) {\r\n\t\t\t\tmap.addLayer(thisBaseLayer.layer);\r\n\t\t\t}\r\n\t\t\tfor (var i = 0; i < baseTileLayers.length; i++) {\r\n\t\t\t\tvar baseLayer = baseTileLayers[i];\r\n\t\t\t\tif (map.hasLayer(baseLayer) && baseLayer != thisBaseLayer.layer) {\r\n\t\t\t\t\tmap.removeLayer(baseLayer);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tvar custom = $(\"#map-type-list a.\" + type).addClass(\"active\");\r\n\t\t\t$(\"#map-type-current\").text(custom.text());\r\n\t\t\t$(\"#map-type-labels\").hide();\r\n\t\t\tbreak;\r\n\t}\r\n\t// workaround for bug with GL layers that initialize with offset base map\r\n\tmap.flyToBounds(map.getBounds());\r\n}\r\n\r\nfunction collapseMapType() {\r\n\t$j(\"#map_type_list\").hide();\r\n\t$j(\"body\").off(\"click.maptype\");\r\n}\r\n\r\nfunction expandMapType() {\r\n\t$j(\"#map_type_list\").show();\r\n\r\n\t$j(\"body\").on(\"click.maptype\", function (e) {\r\n\t\tif ($(e.target).parents(\"#map_type\").length == 0) {\r\n\t\t\tcollapseMapType();\r\n\t\t}\r\n\t});\r\n}\r\n","import trkData from \"./data.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport { viewModes, mapModes, trkDataGroups } from \"./const.js\";\r\nimport state from \"./state.js\";\r\nimport { updateActiveAssetInformation } from \"./assets-active.js\";\r\nimport { map } from \"./map-base.js\";\r\nimport { closeSecondaryPanel } from \"./panel.js\";\r\nimport { updateMapModePanel } from \"./map-ui.js\";\r\nimport { deselectSharedView } from \"./shared-view.js\";\r\nimport { moveOrOpenDialogRelativeTo } from \"./modal-dialog.js\";\r\nimport { changeMapType } from \"./map-maptype.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport _ from \"lodash\";\r\n\r\nimport { normalGleoSymbols, sharedViewGleoSymbols } from \"./map-gleo.js\";\r\n\r\nexport async function switchViewMode(toViewMode) {\r\n\tif (state.activeViewMode === toViewMode) {\r\n\t\treturn;\r\n\t}\r\n\tconsole.log(\"switchViewMode\", toViewMode);\r\n\r\n\tvar switchModePromises = [];\r\n\r\n\tcloseSecondaryPanel();\r\n\r\n\t// change activeViewMode\r\n\thideMapMarkersForViewMode(state.activeViewMode);\r\n\r\n\t// set attribute on root node which will handle hiding/showing map control elements\r\n\t// i.e. normal means
      ,\r\n\t// shared view means
      \r\n\tdocument.getElementById(\"track-root\").setAttribute(\"data-view-mode\",\r\n\t\ttoViewMode === viewModes.NORMAL ? 0 : 1\r\n\t);\r\n\tstate.activeViewMode = toViewMode;\r\n\tshowMapMarkersForViewMode(state.activeViewMode);\r\n\r\n\t// TODO disable/enable Live button on map mode...\r\n\r\n\tif (state.activeViewMode === viewModes.SHARED_VIEW) {\r\n\t\ttrkData.sharedView.priorMapMode = state.activeMapMode;\r\n\t\ttrkData.sharedView.priorMapType = state.currentMapType;\r\n\t\ttrkData.sharedView.priorIsSatelliteLabelOverlayEnabled = trkData.isSatelliteLabelOverlayEnabled;\r\n\t\tvar $dialog = $(domNodes.infoDialogs.sharedViewInformation);\r\n\t\tmoveOrOpenDialogRelativeTo($dialog, map._container, \"center center\");\r\n\t\t$dialog.off(\"dialogclose\");\r\n\t} else {\r\n\t\tdocument.getElementById(\"txtDateFrom\").value = trkData.history.fromDate;\r\n\t\tdocument.getElementById(\"txtDateTo\").value = trkData.history.toDate;\r\n\t\tif (trkData.sharedView.priorMapType !== state.currentMapType) {\r\n\t\t\ttrkData.isSatelliteLabelOverlayEnabled = trkData.sharedView.priorIsSatelliteLabelOverlayEnabled;\r\n\t\t\tchangeMapType(trkData.sharedView.priorMapType);\r\n\t\t}\r\n\t}\r\n\r\n\tupdateMapModePanel();\r\n\tupdateActiveAssetInformation(state.activeViewMode);\r\n\treturn Promise.all(switchModePromises);\r\n}\r\n\r\nfunction hideMapMarkersForViewMode(viewMode) {\r\n\tif (viewMode === viewModes.NORMAL) {\r\n\t\tmap.removeLayer(trkData.mapLayers.normal);\r\n\t\tmap.platina.remove(normalGleoSymbols);\r\n\t\t//normalGleoSymbols.remove();\r\n\r\n\t\t$(domNodes.infoDialogs.mapItemInformation).dialog(\"close\");\r\n\t\t$(domNodes.infoDialogs.positionHistory).dialog(\"close\");\r\n\t} else {\r\n\t\tdeselectSharedView();\r\n\t\tmap.removeLayer(trkData.mapLayers.sharedView);\r\n\t\tmap.platina.remove(sharedViewGleoSymbols);\r\n\t\t//sharedViewGleoSymbols.remove();\r\n\r\n\t\t$(domNodes.infoDialogs.sharedViewInformation).dialog(\"close\");\r\n\t\t$(domNodes.infoDialogs.mapItemInformation).dialog(\"close\");\r\n\t\t$(domNodes.infoDialogs.positionHistory).dialog(\"close\");\r\n\t}\r\n}\r\n\r\nfunction showMapMarkersForViewMode(viewMode) {\r\n\tif (viewMode === viewModes.NORMAL) {\r\n\t\tmap.addLayer(trkData.mapLayers.normal);\r\n\t\tnormalGleoSymbols.addTo(map.platina);\r\n\t} else {\r\n\t\tmap.addLayer(trkData.mapLayers.sharedView);\r\n\t\tsharedViewGleoSymbols.addTo(map.platina);\r\n\t}\r\n}\r\n\r\nexport function getAssetDataGroupForViewAndMapModes(viewMode, mapMode) {\r\n\tif (viewMode === viewModes.NORMAL) {\r\n\t\tif (mapMode === mapModes.LIVE) {\r\n\t\t\treturn trkDataGroups.NORMAL_LIVE;\r\n\t\t} else if (mapMode === mapModes.HISTORY) {\r\n\t\t\treturn trkDataGroups.NORMAL_HISTORY;\r\n\t\t} else {\r\n\t\t\t// time slider doesn't have a asset data group\r\n\t\t\treturn undefined;\r\n\t\t}\r\n\t} else if (viewMode === viewModes.SHARED_VIEW) {\r\n\t\treturn trkDataGroups.SHARED_VIEW_HISTORY;\r\n\t}\r\n}\r\n\r\nexport function getAssetDataGroupForCurrentViewMode() {\r\n\t// TODO store this in state instead whenever it would change?\r\n\treturn getAssetDataGroupForViewAndMapModes(state.activeViewMode, state.activeMapMode);\r\n}\r\n\r\nexport function getJourneyDataGroupForCurrentViewMode() {\r\n\tif (state.activeViewMode === viewModes.NORMAL) {\r\n\t\treturn trkDataGroups.JOURNEY_HISTORY;\r\n\t} else {\r\n\t\treturn trkDataGroups.SHARED_VIEW_JOURNEY;\r\n\t}\r\n}\r\n","import { viewModes, trkDataGroups } from \"./const.js\";\r\nimport { extendBounds } from \"./map-bounds.js\";\r\nimport { addItemToMap } from \"./map-items.js\";\r\nimport { markerClick, markerUnhover, markerHover } from \"./marker-click.js\";\r\nimport trkData from \"./data.js\";\r\nimport { getAssetDataGroupForCurrentViewMode } from \"./map-viewmode.js\";\r\nimport options from \"./options.js\";\r\nimport user from \"./user.js\";\r\nimport AssetSprite from \"./gleo-asset-sprite.js\";\r\n\r\nimport L from \"leaflet\";\r\nimport _ from \"lodash\";\r\n\r\nexport function addPositionMarkerToPoint(\r\n\tlatlng,\r\n\tuserAdded,\r\n\tlocation,\r\n\tasset,\r\n\talpha,\r\n\tisCluster,\r\n\tisFirst,\r\n\tisLast,\r\n\tdataGroup,\r\n\ttrip,\r\n\tsharedViewId\r\n) {\r\n\tif (isCluster === undefined || isCluster === null) {\r\n\t\tisCluster = false;\r\n\t}\r\n\tif (latlng === undefined || latlng === null) {\r\n\t\treturn null;\r\n\t}\r\n\textendBounds(L.latLng(latlng));\r\n\r\n\tvar color = asset.Color;\r\n\tif (color === undefined || color === null) {\r\n\t\tcolor = \"\";\r\n\t}\r\n\tvar course = location.Course;\r\n\tif (course === undefined || course === null) {\r\n\t\tcourse = \"\";\r\n\t}\r\n\tvar evtType = getHighestPriorityEventType(location.Events);\r\n\t// var imagePath = createMarkerPath(asset.Class, color, course, alpha, asset.Id, false, evtType, isFirst, isLast);\r\n\t// var icon = L.icon({\r\n\t// \ticonUrl: imagePath,\r\n\t// \ticonSize: [36, 36],\r\n\t// \ticonAnchor: [18, 18],\r\n\t// });\r\n\t// var marker = L.marker(latlng, { icon: icon });\r\n\r\n\tconst marker = new AssetSprite(latlng, asset.Class, color, course, alpha, asset.Id, evtType, isFirst, isLast);\r\n\r\n\tmarker.data = {\r\n\t\tlocation: null,\r\n\t\tassetId: null,\r\n\t\talpha: null,\r\n\t\tisFirst: null,\r\n\t\tisLast: null,\r\n\t\tselected: null,\r\n\t\tmessage: null,\r\n\t\ttripId: null,\r\n\t\tsharedViewId: null,\r\n\t\tisHidden: false,\r\n\t};\r\n\r\n\tif (!isCluster && !location.IsHidden) {\r\n\t\tvar viewMode = null;\r\n\t\tif (dataGroup === trkDataGroups.SHARED_VIEW_HISTORY || (sharedViewId !== undefined && sharedViewId !== null)) {\r\n\t\t\tviewMode = viewModes.SHARED_VIEW;\r\n\t\t}\r\n\t\tif (dataGroup === getAssetDataGroupForCurrentViewMode() || dataGroup === trkDataGroups.JOURNEY_HISTORY) {\r\n\t\t\taddItemToMap(marker, null, viewMode);\r\n\t\t}\r\n\t}\r\n\t//location.marker = marker; // associate marker with location so that it can be hidden/shown\r\n\r\n\tif (location !== null) {\r\n\t\tmarker.data.location = location;\r\n\t}\r\n\tif (asset !== null) {\r\n\t\tmarker.data.assetId = asset.Id;\r\n\t}\r\n\tif (trip !== undefined && trip !== null) {\r\n\t\tmarker.data.tripId = trip.Id;\r\n\t}\r\n\tif (sharedViewId !== undefined && sharedViewId !== null) {\r\n\t\tmarker.data.sharedViewId = sharedViewId;\r\n\t}\r\n\tif (alpha !== undefined && alpha !== null) {\r\n\t\tmarker.data.alpha = alpha;\r\n\t}\r\n\tif (isFirst !== undefined && isFirst !== null && isFirst) {\r\n\t\tmarker.data.isFirst = true;\r\n\t}\r\n\tif (isLast !== undefined && isLast !== null && isLast) {\r\n\t\tmarker.data.isLast = true;\r\n\t}\r\n\tmarker.on(\"pointerover\", function (e) {\r\n\t\tmarkerHover(marker);\r\n\t});\r\n\tmarker.on(\"pointerout\", function (e) {\r\n\t\tmarkerUnhover(marker);\r\n\t});\r\n\tmarker.on(\"click\", function (ev) {\r\n\t\tmarkerClick(marker, \"position\", null, true);\r\n\r\n\t\t// Prevent propagation to the platina in order to prevent closing a cluster\r\n\t\t// spider when clicking on one of its members\r\n\t\tev.stopPropagation();\r\n\t});\r\n\t// we don't simply check the current mode as results may come back asynchronously\r\n\t// so the current mode might not be the intended one\r\n\tvar markers = getMapMarkersForDataGroup(dataGroup);\r\n\tif (markers !== undefined) {\r\n\t\tmarkers.push(marker);\r\n\t}\r\n\treturn marker;\r\n}\r\n\r\nexport function getMapMarkersForDataGroup(dataGroup) {\r\n\tswitch (dataGroup) {\r\n\t\t// normal mode\r\n\t\tcase trkDataGroups.NORMAL_LIVE:\r\n\t\t\treturn trkData.live.markers;\r\n\t\tcase trkDataGroups.NORMAL_HISTORY:\r\n\t\t\treturn trkData.history.markers;\r\n\t\tcase trkDataGroups.JOURNEY_HISTORY:\r\n\t\t\treturn trkData.trips.markers;\r\n\t\t// shared view mode\r\n\t\tcase trkDataGroups.SHARED_VIEW_HISTORY:\r\n\t\t\treturn trkData.sharedView.markers;\r\n\t\tcase trkDataGroups.SHARED_VIEW_JOURNEY:\r\n\t\t\treturn trkData.sharedView.trips.markers;\r\n\t\t//case trkDataGroups.BEHAVIOR_ANALYSIS:\r\n\t\t// return trkData.behaviorAnalysis.markers;\r\n\t\t//case trkDataGroups.FOLLOW_ASSET:\r\n\t\t// return trkData.followAsset.markers;\r\n\t\tdefault:\r\n\t\t\tconsole.warn(\"Unknown map marker group: \" + dataGroup);\r\n\t\t\treturn undefined;\r\n\t}\r\n}\r\n\r\n// List of event numbers ordered by priority, for use in getHighestPriorityEventType\r\n// This list differs from the EVENTS_* constants in asset-events.js\r\nconst eventPriority = [\r\n\t9,\t// emergency\r\n\t14,\t// alert\r\n\t15, // check-in\r\n\t10,\t// reset\r\n\t7,\t// enter fence\r\n\t8,\t// exit fence\r\n\t1, // start\r\n\t2, // stop\r\n\t375, // encrypted track start\r\n\t376\t// encrypted track end\r\n]\r\n\r\n// An asset position can have several events, but typically only one of those can\r\n// be displayed on the map (as an icon next to the asset). This selects one\r\n// of those events.\r\nexport function getHighestPriorityEventType(events) {\r\n\tif (events == null) return null;\r\n\r\n\tvar evtType = null;\r\n\t_.each(events, function (evt) {\r\n\t\tif (evt.Hide) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tif (user.isAnonymous || options.hideEmergencyEvents) {\r\n\t\t\tif (evt.Type === 9 || evt.Type === 124 || evt.Type === 126 || evt.Type === 29) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (user.isAnonymous || options.hideAlertTriggeredEvents) {\r\n\t\t\tif (evt.Type === 14 || evt.Type === 16) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (evtType === null) {\r\n\t\t\tevtType = evt.Type;\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tvar priority = _.indexOf(eventPriority, evt.Type);\r\n\t\tif (priority !== -1) {\r\n\t\t\tvar currentPriority = _.indexOf(eventPriority, evtType);\r\n\t\t\tif (priority < currentPriority || currentPriority === -1) {\r\n\t\t\t\tevtType = evt.Type;\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n\treturn evtType;\r\n}\r\n\r\n","import strings from \"./strings.js\";\r\n\r\nexport function getStatusTextForLocation(location) {\r\n\tvar status = \"\";\r\n\tif (location.State.IsSpeeding === true) {\r\n\t\tstatus = strings.SPEEDING;\r\n\t} else if (location.State.IsTowing === true) {\r\n\t\tstatus = strings.TOWING;\r\n\t} else if (location.State.IsMoving === true) {\r\n\t\tstatus = strings.MOVING;\r\n\t} else if (\r\n\t\tlocation.State.IsMoving === false ||\r\n\t\t(location.State.IsMoving == null && location.State.IsTowing == false)\r\n\t) {\r\n\t\tstatus = strings.STATIONARY;\r\n\t}\r\n\r\n\tvar ignition = \"\";\r\n\tif (location.State.IsIdling === true) {\r\n\t\tignition = strings.IDLING;\r\n\t} else if (location.State.IsIgnitionOn === true) {\r\n\t\tignition = strings.IGNITION_ON;\r\n\t} else if (location.State.IsIgnitionOn === false) {\r\n\t\tignition = strings.IGNITION_OFF;\r\n\t}\r\n\r\n\tif (status !== \"\" && ignition !== \"\") {\r\n\t\tstatus += \", \" + ignition;\r\n\t}\r\n\r\n\tif (location.State.IsInLowPowerMode === true) {\r\n\t\tif (status !== \"\") {\r\n\t\t\tstatus += \", \";\r\n\t\t}\r\n\t\tstatus += strings.IN_LOW_POWER_MODE;\r\n\t}\r\n\r\n\tif (location.State.IsAntennaCut === true) {\r\n\t\tif (status !== \"\") {\r\n\t\t\tstatus += \", \";\r\n\t\t}\r\n\t\tstatus += strings.ANTENNA_CUT;\r\n\t}\r\n\r\n\tif (location.State.IsCellJammed === true) {\r\n\t\tif (status !== \"\") {\r\n\t\t\tstatus += \", \";\r\n\t\t}\r\n\t\tstatus += strings.CELL_JAMMED;\r\n\t}\r\n\r\n\tif (location.State.IsGpsJammed === true) {\r\n\t\tif (status !== \"\") {\r\n\t\t\tstatus += \", \";\r\n\t\t}\r\n\t\tstatus += strings.GPS_JAMMED;\r\n\t}\r\n\r\n\tif (location.State.IsOnBackupPower === true) {\r\n\t\tif (status !== \"\") {\r\n\t\t\tstatus += \", \";\r\n\t\t}\r\n\t\tstatus += strings.ON_BACKUP_POWER;\r\n\t}\r\n\r\n\tif (location.State.IsImmobilized === true) {\r\n\t\tif (status !== \"\") {\r\n\t\t\tstatus += \", \";\r\n\t\t}\r\n\t\tstatus += strings.IMMOBILIZED;\r\n\t}\r\n\treturn status;\r\n}\r\n","import strings from \"./strings.js\";\r\nimport options from \"./options.js\";\r\nimport { findFenceById } from \"./fence.js\";\r\nimport {\r\n\tconvertToLatLngPreference,\r\n\tconvertAltitudeToPreference,\r\n\tconvertSpeedToPreference,\r\n\tconvertFromMetresToUserDistancePreference,\r\n\tfuelText,\r\n} from \"./preferences.js\";\r\nimport user from \"./user.js\";\r\nimport { createMarkerPath } from \"./marker-path.js\";\r\nimport { includeRowIfNotNull, createAccordionCard, formattedTextToDiv } from \"./dom-util.js\";\r\nimport { findWaypointByAsset } from \"./waypoint.js\";\r\nimport { getStatusTextForLocation } from \"./location.js\";\r\nimport { findPlaceByUniqueKey } from \"./place.js\";\r\nimport { createPositionPlaybackForAsset } from \"./playback.js\";\r\nimport { populateCustomAttributes } from \"./attributes.js\";\r\n\r\nimport $j from \"jquery\";\r\nimport L from \"leaflet\";\r\nimport _ from \"lodash\";\r\nimport { el, text } from \"redom\"; // https://redom.js.org/\r\n\r\nexport function populatePositionInformation(asset, marker) {\r\n\t// no strings for html! WHY ME SO DUMB?!\r\n\tvar location = marker.data.location;\r\n\tvar message = marker.data.message;\r\n\tif (asset === undefined || asset === null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar isHover = false;\r\n\tvar isMarkerSelected = true;\r\n\r\n\tvar headingRow = null;\r\n\tif (location.Course != null && !asset.HideCourse) {\r\n\t\theadingRow = includeRowIfNotNull(strings.HEADING, location.Course + \"°\");\r\n\t}\r\n\r\n\tvar elevationRow = null;\r\n\tif (location.Altitude != null && location.Altitude != \"\" && !asset.HideAltitude) {\r\n\t\televationRow = includeRowIfNotNull(strings.ALTITUDE, convertAltitudeToPreference(location.Altitude));\r\n\t}\r\n\r\n\tvar playbackContent = createPositionPlaybackForAsset(asset, marker);\r\n\r\n\tvar waypointRow = null;\r\n\tvar waypoint = findWaypointByAsset(asset);\r\n\tif (waypoint != null) {\r\n\t\tvar distance = waypoint.DistanceTo;\r\n\t\tif (distance == null) {\r\n\t\t\tvar destination = L.latLng(waypoint.Lat, waypoint.Lng);\r\n\t\t\tdistance = convertFromMetresToUserDistancePreference(\r\n\t\t\t\tdestination.distanceTo(L.latLng(location.Lat, location.Lng))\r\n\t\t\t);\r\n\t\t}\r\n\t\twaypointRow = includeRowIfNotNull(strings.WAYPOINT, [text(waypoint.Name), el(\"br\"), text(distance)]);\r\n\t}\r\n\r\n\tvar messageRow = null;\r\n\tif (message != null && message != \"\") {\r\n\t\tmessageRow = includeRowIfNotNull(strings.MESSAGE, messageRow);\r\n\t}\r\n\r\n\tvar garminFormElement = null; // can't have more than one form submission per position\r\n\tvar eventInformationElements = [];\r\n\tvar shockLogRow = null;\r\n\tif (location.Events != null) {\r\n\t\tfor (var i = 0; i < location.Events.length; i++) {\r\n\t\t\tvar evtclass = i % 2 === 0 ? \"odd\" : \"even\";\r\n\t\t\tvar evt = location.Events[i];\r\n\t\t\t// todo: actually filter this out in the data feed -- maybe not, would it break alerts tab?\r\n\t\t\tif (\r\n\t\t\t\tuser.isAnonymous &&\r\n\t\t\t\t(evt.Type === 14 ||\r\n\t\t\t\t\tevt.Type === 16 ||\r\n\t\t\t\t\tevt.Type === 9 ||\r\n\t\t\t\t\tevt.Type === 124 ||\r\n\t\t\t\t\tevt.Type === 126 ||\r\n\t\t\t\t\tevt.Type === 29)\r\n\t\t\t) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif (\r\n\t\t\t\t(options.hideEmergencyEvents || evt.Hide) &&\r\n\t\t\t\t(evt.Type === 9 || evt.Type === 124 || evt.Type === 126 || evt.Type === 29)\r\n\t\t\t) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif ((options.hideAlertTriggeredEvents || evt.Hide) && (evt.Type === 14 || evt.Type === 16)) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tif (evt.Type == 268) {\r\n\t\t\t\t// SL Summary\r\n\t\t\t\tshockLogRow = includeRowIfNotNull(\"ShockLog\", evt.Details);\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tvar eventicon = createMarkerPath(asset.Class, asset.Color, null, null, asset.Id, false, evt.Type);\r\n\t\t\tvar eventInformationDetails = [];\r\n\t\t\tswitch (evt.Type) {\r\n\t\t\t\tcase 14: // alert triggered\r\n\t\t\t\tcase 16: // alert no longer triggered\r\n\t\t\t\t\tif (evt.Details != null) {\r\n\t\t\t\t\t\teventInformationDetails.push(\r\n\t\t\t\t\t\t\tel(\r\n\t\t\t\t\t\t\t\t\"button.event-information.btn.btn-primary\",\r\n\t\t\t\t\t\t\t\t{ dataset: { position: location.Id, event: evt.Id } },\r\n\t\t\t\t\t\t\t\tstrings.EVENT_VIEW_DETAILS\r\n\t\t\t\t\t\t\t)\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\teventInformationDetails.push(text(strings.DELETED));\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase 127: // garmin form submission\r\n\t\t\t\t\tif (evt.Details != null) {\r\n\t\t\t\t\t\tvar formDetails = evt.Details.split(\"|\");\r\n\t\t\t\t\t\tvar formId = formDetails[0];\r\n\t\t\t\t\t\tvar formName = strings.VIEW;\r\n\t\t\t\t\t\tif (formDetails.length > 1) {\r\n\t\t\t\t\t\t\tformName = formDetails[1];\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\teventInformationDetails.push(text(formName));\r\n\t\t\t\t\t\tgarminFormElement = createAccordionCard(\r\n\t\t\t\t\t\t\t\"garmin\",\r\n\t\t\t\t\t\t\t\"asset-information-accordion\",\r\n\t\t\t\t\t\t\tstrings.GARMIN_FORM + \" - \" + formName,\r\n\t\t\t\t\t\t\tel(\"ul.GarminSubmissionItems\", { \"data-form-id\": formId })\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\teventInformationDetails.push(text(strings.DELETED));\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase 270: // beacon read\r\n\t\t\t\t\tif (evt.Details != null) {\r\n\t\t\t\t\t\tvar beacons = evt.Details.split(\"\\n\");\r\n\t\t\t\t\t\tfor (var k = 0; k < beacons.length; k++) {\r\n\t\t\t\t\t\t\tvar beacon = beacons[k].split(\"|\");\r\n\t\t\t\t\t\t\tif (beacon.length > 1) {\r\n\t\t\t\t\t\t\t\tvar beaconUniqueKey = beacon[0];\r\n\t\t\t\t\t\t\t\tvar beaconRSSI = beacon[1];\r\n\t\t\t\t\t\t\t\tif (beaconUniqueKey == null) {\r\n\t\t\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\tvar beaconPlace = findPlaceByUniqueKey(beaconUniqueKey);\r\n\t\t\t\t\t\t\t\tif (beaconPlace != null) {\r\n\t\t\t\t\t\t\t\t\teventInformationDetails.push(\r\n\t\t\t\t\t\t\t\t\t\tel(\"a.beacon-place\", { href: \"#\", dataset: { placeId: beaconPlace.Id } }, beaconPlace.Name)\r\n\t\t\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\t\t\teventInformationDetails.push(text(\" @ \" + beaconRSSI + \" RSSI\"));\r\n\t\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t\teventInformationDetails.push(text(beaconUniqueKey + \" @ \" + beaconRSSI + \" RSSI\"));\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\teventInformationDetails.push(el(\"br\"));\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase 272:\r\n\t\t\t\tcase 273:\r\n\t\t\t\tcase 274:\r\n\t\t\t\t\t// driver fatigue\r\n\t\t\t\t\tif (evt.Details != null) {\r\n\t\t\t\t\t\teventInformationDetails.push(el(\"img.fatigue-image\", { src: \"data:image/jpeg;base64, \" + evt.Details }));\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase 275:\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tdefault:\r\n\t\t\t\t\teventInformationDetails.push(formattedTextToDiv(evt.Details));\r\n\t\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\t// add row for event information\r\n\t\t\t// TODO: remove inline styles except background image\r\n\t\t\teventInformationElements.push(\r\n\t\t\t\tel(\"tr.\" + evtclass, [\r\n\t\t\t\t\tel(\r\n\t\t\t\t\t\t\"td\",\r\n\t\t\t\t\t\tel(\"div\", {\r\n\t\t\t\t\t\t\tstyle: {\r\n\t\t\t\t\t\t\t\tbackgroundImage: \"url(\" + eventicon + \")\",\r\n\t\t\t\t\t\t\t\twidth: \"36px\",\r\n\t\t\t\t\t\t\t\theight: \"27px\",\r\n\t\t\t\t\t\t\t\tdisplay: \"block\",\r\n\t\t\t\t\t\t\t\tbackgroundPosition: \"0 0\",\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t),\r\n\t\t\t\t\tel(\"td\", { style: { verticalAlign: \"middle\" } }, evt.TypeName),\r\n\t\t\t\t\tel(\"td.break-text\", { style: { verticalAlign: \"middle\" } }, _.compact(eventInformationDetails)),\r\n\t\t\t\t])\r\n\t\t\t);\r\n\r\n\t\t\tif (evt.Alert != null && isMarkerSelected && evt.Type === 14) {\r\n\t\t\t\t// todo: use DOM editing here to prevent script/html injection\r\n\t\t\t\tvar includeFields = evt.Alert.Acknowledged === 0;\r\n\r\n\t\t\t\tvar alertHeaderElements = [];\r\n\t\t\t\tif (evt.Alert.Color != null) {\r\n\t\t\t\t\talertHeaderElements.push(el(\"span.alert-color\", { style: { backgroundColor: evt.Alert.Color } }));\r\n\t\t\t\t}\r\n\t\t\t\tif (evt.Alert.Name != null) {\r\n\t\t\t\t\talertHeaderElements.push(text(evt.Alert.Name + \": \"));\r\n\t\t\t\t}\r\n\t\t\t\talertHeaderElements.push(text(evt.Alert.Type));\r\n\r\n\t\t\t\tvar alertFormElements = [];\r\n\t\t\t\tif (evt.Alert.Description != null) {\r\n\t\t\t\t\talertFormElements.push(\r\n\t\t\t\t\t\tel(\"div.form-group\", [\r\n\t\t\t\t\t\t\tel(\"label\", { for: \"PositionAlertDescription\" + evt.Id }, strings.DESCRIPTION),\r\n\t\t\t\t\t\t\tel(\"input#PositionAlertDescription\" + evt.Id + \".form-control-plaintext\", {\r\n\t\t\t\t\t\t\t\ttype: \"text\",\r\n\t\t\t\t\t\t\t\tvalue: evt.Alert.Description,\r\n\t\t\t\t\t\t\t\treadOnly: true,\r\n\t\t\t\t\t\t\t}),\r\n\t\t\t\t\t\t])\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\t\t\t\tif (evt.Alert.ResolutionProcedure != null) {\r\n\t\t\t\t\talertFormElements.push(\r\n\t\t\t\t\t\tel(\"div.form-group\", [\r\n\t\t\t\t\t\t\tel(\"label\", { for: \"PositionAlertResolutionProcedure\" + evt.Id }, strings.RESOLUTION_PROCEDURE),\r\n\t\t\t\t\t\t\tel(\"input#PositionAlertResolutionProcedure\" + evt.Id + \".form-control-plaintext\", {\r\n\t\t\t\t\t\t\t\ttype: \"text\",\r\n\t\t\t\t\t\t\t\tvalue: evt.Alert.ResolutionProcedure,\r\n\t\t\t\t\t\t\t\treadOnly: true,\r\n\t\t\t\t\t\t\t}),\r\n\t\t\t\t\t\t])\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\t\t\t\tvar alertResolutionElements = [];\r\n\t\t\t\tif (includeFields) {\r\n\t\t\t\t\talertResolutionElements.push(\r\n\t\t\t\t\t\tel(\"div.form-group\", [\r\n\t\t\t\t\t\t\tel(\"label\", { for: \"AlertResolution\" + evt.Id }, strings.RESOLUTION),\r\n\t\t\t\t\t\t\t// TODO: remove inline style\r\n\t\t\t\t\t\t\tel(\r\n\t\t\t\t\t\t\t\t\"textarea#AlertResolution\" + evt.Id + \".required.form-control\",\r\n\t\t\t\t\t\t\t\t{ style: { height: \"4em\", width: \"100%\" }, name: \"AlertResolution\" },\r\n\t\t\t\t\t\t\t\tevt.Alert.AcknowledgedText\r\n\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t])\r\n\t\t\t\t\t);\r\n\t\t\t\t} else if (evt.Alert.AcknowledgedText != null) {\r\n\t\t\t\t\talertResolutionElements.push(\r\n\t\t\t\t\t\tel(\"div.form-group\", [\r\n\t\t\t\t\t\t\tel(\"label\", { for: \"PositionAlertResolution\" + evt.Id }, strings.RESOLUTION),\r\n\t\t\t\t\t\t\tel(\"input#PositionAlertResolution\" + evt.Id + \".form-control-plaintext\", {\r\n\t\t\t\t\t\t\t\ttype: \"text\",\r\n\t\t\t\t\t\t\t\tvalue: evt.Alert.AcknowledgedText,\r\n\t\t\t\t\t\t\t\treadOnly: true,\r\n\t\t\t\t\t\t\t}),\r\n\t\t\t\t\t\t])\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\t\t\t\talertFormElements.push(el(\"div.alert-resolution\", alertResolutionElements));\r\n\r\n\t\t\t\tvar ack = strings.ACKNOWLEDGE;\r\n\t\t\t\tvar ackalt = strings.ACKNOWLEDGE_ALT;\r\n\t\t\t\tif (evt.Alert.LabelAcknowledge != null) {\r\n\t\t\t\t\tack = evt.Alert.LabelAcknowledge;\r\n\t\t\t\t}\r\n\t\t\t\tif (evt.Alert.LabelAcknowledgeAlt != null) {\r\n\t\t\t\t\tackalt = evt.Alert.LabelAcknowledgeAlt;\r\n\t\t\t\t}\r\n\t\t\t\tif (includeFields) {\r\n\t\t\t\t\talertFormElements.push(\r\n\t\t\t\t\t\tel(\"div.alert-acknowledge\", [\r\n\t\t\t\t\t\t\tel(\"button.AlertAcknowledge.btn.btn-primary.alert-acknowledge\", ack),\r\n\t\t\t\t\t\t\ttext(\" \" + strings.OR + \" \"),\r\n\t\t\t\t\t\t\tel(\"button.AlertAcknowledgeAlt.btn.btn-secondary.alert-acknowledge-alt\", ackalt),\r\n\t\t\t\t\t\t])\r\n\t\t\t\t\t);\r\n\t\t\t\t} else {\r\n\t\t\t\t\talertFormElements.push(\r\n\t\t\t\t\t\tel(\"div.form-group\", [\r\n\t\t\t\t\t\t\tel(\"label\", { for: \"PositionAlertStatus\" + evt.Id }, strings.STATUS),\r\n\t\t\t\t\t\t\tel(\"input#PositionAlertStatus\" + evt.Id + \".form-control-plaintext\", {\r\n\t\t\t\t\t\t\t\ttype: \"text\",\r\n\t\t\t\t\t\t\t\tvalue: strings.ACKNOWLEDGE_STATUS.replace(\"{Status}\", evt.Alert.AcknowledgedStatus)\r\n\t\t\t\t\t\t\t\t\t.replace(\"{Time}\", evt.Alert.AcknowledgedOn)\r\n\t\t\t\t\t\t\t\t\t.replace(\"{User}\", evt.Alert.AcknowledgedBy),\r\n\t\t\t\t\t\t\t\treadOnly: true,\r\n\t\t\t\t\t\t\t}),\r\n\t\t\t\t\t\t])\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\t\t\t\talertFormElements.push(el(\"input\", { type: \"hidden\", value: evt.Id }));\r\n\t\t\t\tvar alertForm = el(\r\n\t\t\t\t\t\"form#AcknowledgeAlert\" + evt.Id,\r\n\t\t\t\t\t{ dataset: { assetId: asset.Id, eventId: evt.Id } },\r\n\t\t\t\t\talertFormElements\r\n\t\t\t\t);\r\n\r\n\t\t\t\t// add row for alert information\r\n\t\t\t\teventInformationElements.push(\r\n\t\t\t\t\tel(\"tr.\" + evtclass, [\r\n\t\t\t\t\t\tel(\r\n\t\t\t\t\t\t\t\"td\",\r\n\t\t\t\t\t\t\t{ colspan: 3 },\r\n\t\t\t\t\t\t\tel(\r\n\t\t\t\t\t\t\t\t\"div#alert-information-accordion\" + evt.Id + \".alert-information-accordion.position-accordion\",\r\n\t\t\t\t\t\t\t\tcreateAccordionCard(\r\n\t\t\t\t\t\t\t\t\t\"alert-information\" + evt.Id,\r\n\t\t\t\t\t\t\t\t\t\"alert-information-accordion\" + evt.Id,\r\n\t\t\t\t\t\t\t\t\talertHeaderElements,\r\n\t\t\t\t\t\t\t\t\talertForm\r\n\t\t\t\t\t\t\t\t)\r\n\t\t\t\t\t\t\t)\r\n\t\t\t\t\t\t),\r\n\t\t\t\t\t])\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tvar eventInformationTable = null;\r\n\tif (eventInformationElements.length > 0) {\r\n\t\teventInformationTable = el(\"table.table.table-striped\", [\r\n\t\t\tel(\"thead\", el(\"tr\", [el(\"th\", \" \"), el(\"th\", strings.EVENT_EVENT), el(\"th\", strings.EVENT_DETAILS)])),\r\n\t\t\tel(\"tbody\", eventInformationElements),\r\n\t\t]);\r\n\t}\r\n\r\n\tvar ioInformationElement = null;\r\n\tif (location.GPIO != null) {\r\n\t\tvar ioRows = createIOOutput(asset, location.GPIO, true);\r\n\t\tioRows.push(includeRowIfNotNull(strings.CHANGED, location.GPIO.CreatedOn));\r\n\t\tvar ioInformationTable = el(\"table\", el(\"tbody\", _.compact(ioRows)));\r\n\t\tioInformationElement = createAccordionCard(\r\n\t\t\t\"io\",\r\n\t\t\t\"asset-information-accordion\",\r\n\t\t\tstrings.IO_FIELDS,\r\n\t\t\tioInformationTable\r\n\t\t);\r\n\t}\r\n\r\n\tvar obdInformationElement = null;\r\n\tif (location.OBD != null) {\r\n\t\tvar obdInformationTable = el(\"table\", el(\"tbody\", _.compact(createOBDOutput(location.OBD))));\r\n\t\tobdInformationElement = createAccordionCard(\r\n\t\t\t\"obd\",\r\n\t\t\t\"asset-information-accordion\",\r\n\t\t\tstrings.OBD_FIELDS,\r\n\t\t\tobdInformationTable\r\n\t\t);\r\n\t}\r\n\r\n\t// asset photo should be shown in either the general information accordion or alongside the position information\r\n\t// depending on the user's preferences\r\n\tvar assetPhotoElement = null;\r\n\tif (asset.PhotoType !== null && asset.PhotoType !== \"\") {\r\n\t\tassetPhotoElement = el(\r\n\t\t\t\"a\",\r\n\t\t\t{ href: \"/uploads/images/assets/\" + asset.Id + \".\" + asset.PhotoType, target: \"_blank\" },\r\n\t\t\tel(\"img.asset-photo\", { src: \"/uploads/images/assets/\" + asset.Id + \"_thumb.\" + asset.PhotoType })\r\n\t\t);\r\n\t}\r\n\r\n\tvar generalInformationElements = [\r\n\t\tincludeRowIfNotNull(strings.IMEI, asset.IMEI),\r\n\t\tincludeRowIfNotNull(strings.SERIAL_NUMBER, asset.SerialNumber),\r\n\t\tincludeRowIfNotNull(strings.SOFTWARE_VERSION, asset.SoftwareVersion),\r\n\t\tincludeRowIfNotNull(strings.NOTES, asset.Notes),\r\n\t];\r\n\tif (!options.placeAssetPhotosInPositionDialog) {\r\n\t\tgeneralInformationElements.push(includeRowIfNotNull(strings.PHOTO, assetPhotoElement));\r\n\t}\r\n\tgeneralInformationElements = _.compact(generalInformationElements);\r\n\r\n\tvar generalInformationElement = null;\r\n\tif (generalInformationElements.length > 0) {\r\n\t\tvar generalInformationTable = el(\"table\", el(\"tbody\", generalInformationElements));\r\n\t\tgeneralInformationElement = createAccordionCard(\r\n\t\t\t\"general\",\r\n\t\t\t\"asset-information-accordion\",\r\n\t\t\tstrings.GENERAL_FIELDS,\r\n\t\t\tgeneralInformationTable\r\n\t\t);\r\n\t}\r\n\r\n\tvar simInformationElements = _.compact([\r\n\t\tincludeRowIfNotNull(strings.SIM_ICCID, asset.SIMICCID),\r\n\t\tincludeRowIfNotNull(strings.SIM_PHONE_NUMBER, asset.SIMPhoneNumber),\r\n\t\tincludeRowIfNotNull(strings.SIM_PIN, asset.SIMPIN),\r\n\t\tincludeRowIfNotNull(strings.SIM_PIN_UNLOCK, asset.SIMPINUnlock),\r\n\t\tincludeRowIfNotNull(strings.SIM_PROVIDER, asset.SIMProvider),\r\n\t]);\r\n\tvar simInformationElement = null;\r\n\tif (simInformationElements.length > 0) {\r\n\t\tvar simInformationTable = el(\"table\", el(\"tbody\", simInformationElements));\r\n\t\tsimInformationElement = createAccordionCard(\r\n\t\t\t\"sim\",\r\n\t\t\t\"asset-information-accordion\",\r\n\t\t\tstrings.SIM_FIELDS,\r\n\t\t\tsimInformationTable\r\n\t\t);\r\n\t}\r\n\r\n\t// custom attributes\r\n\tvar attributeElements = populateCustomAttributes(asset.Attributes, 0, \"asset-information-accordion\");\r\n\r\n\tvar driverInformationElements = [];\r\n\tif (location.Drivers != null && location.Drivers.length > 0) {\r\n\t\t// show driver for position instead of assigned driver\r\n\t\t// todo: cross-check DriverId to trkData.drivers for this information so the position information can be much less? (findDriverById)\r\n\t\tfor (var i = 0; i < location.Drivers.length; i++) {\r\n\t\t\tvar driver = location.Drivers[i].Driver;\r\n\t\t\tvar driverStatus = location.Drivers[i].DriverStatus;\r\n\t\t\tvar driverSummary = driver.DriverId;\r\n\t\t\tif (driverStatus != null) {\r\n\t\t\t\tdriverSummary += \" - \" + driverStatus.Status;\r\n\t\t\t}\r\n\t\t\tvar assetDriverFields = [\r\n\t\t\t\tincludeRowIfNotNull(strings.DRIVER_ID, driver.DriverId),\r\n\t\t\t\tincludeRowIfNotNull(strings.IBUTTON_ID, driver.IButtonId),\r\n\t\t\t\tincludeRowIfNotNull(strings.GARMIN_ID, driver.GarminId),\r\n\t\t\t\tincludeRowIfNotNull(strings.REGION, driver.Region),\r\n\t\t\t\tincludeRowIfNotNull(strings.PHONE_NUMBER, driver.Phone),\r\n\t\t\t\tincludeRowIfNotNull(strings.BLOOD_TYPE, driver.BloodType),\r\n\t\t\t\tincludeRowIfNotNull(strings.LICENSE_NUMBER, driver.LicenseNumber),\r\n\t\t\t\tincludeRowIfNotNull(strings.LICENSE_EXPIRATION, driver.LicenseExpiration),\r\n\t\t\t\tincludeRowIfNotNull(strings.LICENSE_RESTRICTION, driver.LicenseRestriction),\r\n\t\t\t\tincludeRowIfNotNull(strings.MANAGER, driver.Manager),\r\n\t\t\t\tincludeRowIfNotNull(strings.EMERGENCY_CONTACT, driver.EmergencyContact),\r\n\t\t\t\tincludeRowIfNotNull(strings.EMERGENCY_CONTACT_NUMBER, driver.EmergencyContactNumber),\r\n\t\t\t];\r\n\t\t\tvar assetDriverInfo = [];\r\n\t\t\tif (driver.PhotoType !== null && driver.PhotoType !== \"\") {\r\n\t\t\t\tassetDriverInfo.push(\r\n\t\t\t\t\tel(\r\n\t\t\t\t\t\t\"a\",\r\n\t\t\t\t\t\t{ href: \"/uploads/images/drivers/\" + driver.Id + \".\" + driver.PhotoType, target: \"_blank\" },\r\n\t\t\t\t\t\tel(\"img\", {\r\n\t\t\t\t\t\t\talign: \"right\",\r\n\t\t\t\t\t\t\tsrc: \"/uploads/images/drivers/\" + driver.Id + \"_thumb.\" + driver.PhotoType,\r\n\t\t\t\t\t\t\talt: driver.DriverId,\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t)\r\n\t\t\t\t);\r\n\t\t\t}\r\n\r\n\t\t\tif (driverStatus != null) {\r\n\t\t\t\tassetDriverFields.push(includeRowIfNotNull(strings.STATUS, driverStatus.Status));\r\n\t\t\t}\r\n\t\t\tassetDriverFields = _.compact(assetDriverFields);\r\n\t\t\tif (assetDriverFields.length > 0) {\r\n\t\t\t\tassetDriverInfo.push(el(\"table\", el(\"tbody\", assetDriverFields)));\r\n\t\t\t}\r\n\r\n\t\t\tassetDriverInfo = _.compact(assetDriverInfo);\r\n\t\t\tif (assetDriverInfo.length > 0) {\r\n\t\t\t\tdriverInformationElements.push(\r\n\t\t\t\t\tcreateAccordionCard(\r\n\t\t\t\t\t\t\"driver-\" + i,\r\n\t\t\t\t\t\t\"asset-information-accordion\",\r\n\t\t\t\t\t\tstrings.DRIVER_FIELDS + \": \" + driverSummary,\r\n\t\t\t\t\t\tassetDriverInfo\r\n\t\t\t\t\t)\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t}\r\n\t} else {\r\n\t\t// asset's driver attributes\r\n\t\tvar driverFields = _.compact([\r\n\t\t\tincludeRowIfNotNull(strings.MISSION, asset.Mission),\r\n\t\t\tincludeRowIfNotNull(strings.DRIVER, asset.Driver),\r\n\t\t\tincludeRowIfNotNull(strings.PHONE_NUMBER, asset.PhoneNumber),\r\n\t\t\tincludeRowIfNotNull(strings.LICENSE_NUMBER, asset.LicenseNumber),\r\n\t\t\tincludeRowIfNotNull(strings.NATIONAL_IDENTITY_CARD_NUMBER, asset.NationalIdentityCardNumber),\r\n\t\t]);\r\n\t\tif (driverFields.length > 0) {\r\n\t\t\tdriverInformationElements.push(\r\n\t\t\t\tcreateAccordionCard(\r\n\t\t\t\t\t\"driver\",\r\n\t\t\t\t\t\"asset-information-accordion\",\r\n\t\t\t\t\tstrings.DRIVER_FIELDS,\r\n\t\t\t\t\tel(\"table\", el(\"tbody\", driverFields))\r\n\t\t\t\t)\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n\r\n\tvar vehicleInformationElement = null;\r\n\tvar vehicleFields = _.compact([\r\n\t\tincludeRowIfNotNull(strings.VEHICLE_MAKE_AND_MODEL, asset.VehicleMakeAndModel),\r\n\t\tincludeRowIfNotNull(strings.VEHICLE_PURCHASE_DATE, asset.VehiclePurchaseDate),\r\n\t\tincludeRowIfNotNull(strings.VEHICLE_VIN, asset.VehicleVIN),\r\n\t\tincludeRowIfNotNull(strings.PLATE_NUMBER, asset.PlateNumber),\r\n\t\tincludeRowIfNotNull(strings.FUEL_EFFICIENCY, asset.FuelEfficiency),\r\n\t]);\r\n\tif (vehicleFields.length > 0) {\r\n\t\tvehicleInformationElement = createAccordionCard(\r\n\t\t\t\"vehicle\",\r\n\t\t\t\"asset-information-accordion\",\r\n\t\t\tstrings.VEHICLE_FIELDS,\r\n\t\t\tel(\"table\", el(\"tbody\", vehicleFields))\r\n\t\t);\r\n\t}\r\n\tvar vesselInformationElement = null;\r\n\tvar vesselFields = _.compact([\r\n\t\tincludeRowIfNotNull(strings.VESSEL_NAME, asset.VesselName),\r\n\t\tincludeRowIfNotNull(strings.VESSEL_CALL_SIGN, asset.VesselCallSign),\r\n\t\tincludeRowIfNotNull(strings.VESSEL_IMO_NUMBER, asset.VesselIMONumber),\r\n\t\tincludeRowIfNotNull(strings.VESSEL_FLAG_REGISTRY, asset.VesselFlagRegistry),\r\n\t\tincludeRowIfNotNull(strings.VESSEL_TONNAGE, asset.VesselTonnage),\r\n\t\tincludeRowIfNotNull(strings.VESSEL_CLASS, asset.VesselClass),\r\n\t\tincludeRowIfNotNull(strings.VESSEL_SKIPPER, asset.VesselSkipper),\r\n\t\tincludeRowIfNotNull(strings.VESSEL_MMSI, asset.VesselMMSI),\r\n\t]);\r\n\tif (vesselFields.length > 0) {\r\n\t\tvesselInformationElement = createAccordionCard(\r\n\t\t\t\"vessel\",\r\n\t\t\t\"asset-information-accordion\",\r\n\t\t\tstrings.VESSEL_FIELDS,\r\n\t\t\tel(\"table\", el(\"tbody\", vesselFields))\r\n\t\t);\r\n\t}\r\n\r\n\tvar assetInformationElement = null;\r\n\tvar assetFields = [obdInformationElement, ioInformationElement, generalInformationElement, simInformationElement];\r\n\tassetFields = assetFields.concat(driverInformationElements);\r\n\tassetFields = assetFields.concat([vehicleInformationElement, vesselInformationElement, garminFormElement]);\r\n\tassetFields = assetFields.concat(attributeElements);\r\n\tassetFields = _.compact(assetFields);\r\n\r\n\tif (assetFields.length > 0) {\r\n\t\tassetInformationElement = el(\r\n\t\t\t\"div#asset-information-accordion.asset-information-accordion.position-accordion\",\r\n\t\t\tassetFields\r\n\t\t);\r\n\t}\r\n\r\n\tvar extraRow = null;\r\n\tif (location.Extra != null && location.Extra != \"\") {\r\n\t\textraRow = includeRowIfNotNull(strings.EXTRA, formattedTextToDiv(location.Extra), {\r\n\t\t\tstyle: { verticalAlign: \"top\" },\r\n\t\t}); // TODO: remove inline style\r\n\t}\r\n\tvar addressRow = null;\r\n\tif (location.Address != null && location.Address != \"\" && !asset.HideAddress) {\r\n\t\taddressRow = includeRowIfNotNull(strings.ADDRESS, location.Address, { style: { verticalAlign: \"top\" } }); // TODO: remove inline style\r\n\t}\r\n\tvar odometerRow = null;\r\n\tif (location.Odometer != null) {\r\n\t\todometerRow = includeRowIfNotNull(strings.ODOMETER, convertFromMetresToUserDistancePreference(location.Odometer));\r\n\t}\r\n\tvar sourceRow = null;\r\n\tif (location.Source != null) {\r\n\t\tsourceRow = includeRowIfNotNull(strings.DATA_SOURCE, location.Source);\r\n\t}\r\n\r\n\tvar statusRow = includeRowIfNotNull(strings.STATUS, getStatusTextForLocation(location));\r\n\r\n\tvar fencesRow = null;\r\n\tif (location.InsideFences != null) {\r\n\t\tvar fences = \"\";\r\n\t\tfor (var i = 0; i < location.InsideFences.length; i++) {\r\n\t\t\tvar fenceId = location.InsideFences[i];\r\n\t\t\tvar fence = findFenceById(fenceId);\r\n\t\t\tif (fence != null) {\r\n\t\t\t\tif (fences !== \"\") {\r\n\t\t\t\t\tfences += \", \";\r\n\t\t\t\t}\r\n\t\t\t\tfences += fence.Name;\r\n\t\t\t}\r\n\t\t}\r\n\t\tfencesRow = includeRowIfNotNull(strings.GEOFENCES, fences);\r\n\t}\r\n\r\n\tvar speedRow = null;\r\n\tif (!asset.HideSpeed) {\r\n\t\tvar speed = convertSpeedToPreference(location.Speed);\r\n\t\tif (location.IsEst === true) {\r\n\t\t\tspeed = el(\"span.estimated\", { title: strings.SPEED_ESTIMATED }, [text(speed), el(\"span.tooltip-circle\", \"?\")]);\r\n\t\t}\r\n\t\tspeedRow = includeRowIfNotNull(strings.SPEED, speed);\r\n\t}\r\n\r\n\tvar accuracyRows = [];\r\n\tif (!asset.HideAccuracy) {\r\n\t\tif (location.Accuracy != null && location.Accuracy !== \"\") {\r\n\t\t\taccuracyRows.push(includeRowIfNotNull(strings.ACCURACY, location.Accuracy));\r\n\t\t}\r\n\t\tif (location.HDOP != null) {\r\n\t\t\taccuracyRows.push(includeRowIfNotNull(strings.HDOP, location.HDOP));\r\n\t\t}\r\n\t\tif (location.NumSats != null) {\r\n\t\t\taccuracyRows.push(includeRowIfNotNull(strings.SATELLITES, location.NumSats));\r\n\t\t}\r\n\t}\r\n\t\r\n\tvar timeElement = el(\r\n\t\t\"span.mr-auto\",\r\n\t\tlocation.IsAcc === false\r\n\t\t\t? el(\"span.inaccurate\", [text(location.Time), el(\"sup\", { title: strings.TIME_INACCURATE }, \"*\")])\r\n\t\t\t: text(location.Time)\r\n\t);\r\n\tvar timeContainerElement = el(\"div.d-flex.align-items-center\", timeElement);\r\n\tvar latLngRow = includeRowIfNotNull(\r\n\t\tstrings.LAT_LNG,\r\n\t\tconvertToLatLngPreference(location.DisplayLat, location.DisplayLng, location.Grid)\r\n\t);\r\n\r\n\tvar mapItemDetailsRows = _.compact([\r\n\t\tmessageRow,\r\n\t\taddressRow,\r\n\t\tlatLngRow,\r\n\t\tspeedRow,\r\n\t\televationRow,\r\n\t\theadingRow,\r\n\t\t...accuracyRows,\r\n\t\todometerRow,\r\n\t\tsourceRow,\r\n\t\textraRow,\r\n\t\tstatusRow,\r\n\t\tfencesRow,\r\n\t\tshockLogRow,\r\n\t\twaypointRow,\r\n\t]);\r\n\tvar mapItemsDetailsElement = null;\r\n\tif (mapItemDetailsRows.length > 0) {\r\n\t\tmapItemsDetailsElement = el(\"table.map-item-details\", el(\"tbody\", mapItemDetailsRows));\r\n\t}\r\n\r\n\tvar infoElements = [\r\n\t\toptions.placeAssetPhotosInPositionDialog ? assetPhotoElement : null,\r\n\t\ttimeContainerElement,\r\n\t\tmapItemsDetailsElement,\r\n\t];\r\n\tif (isMarkerSelected) {\r\n\t\tinfoElements = infoElements.concat(playbackContent);\r\n\t}\r\n\tinfoElements.push(eventInformationTable);\r\n\tif (isMarkerSelected) {\r\n\t\tinfoElements.push(assetInformationElement);\r\n\t}\r\n\tinfoElements = _.compact(infoElements);\r\n\treturn el(\"div.markercontent\", infoElements);\r\n}\r\n\r\nfunction createOBDOutput(item) {\r\n\tvar info = [];\r\n\tif (item.PowerVoltage != null) {\r\n\t\tinfo = addToOBDOutput(\"Power Voltage\", item.PowerVoltage, info, \"V\");\r\n\t}\r\n\tif (item.VehicleSpeed != null) {\r\n\t\tinfo = addToOBDOutput(\"Vehicle Speed\", item.VehicleSpeed, info, \"mps\");\r\n\t}\r\n\tif (item.ThrottlePosition != null) {\r\n\t\tinfo = addToOBDOutput(\"Throttle Position\", item.ThrottlePosition, info, \"%\");\r\n\t}\r\n\tif (item.EngineRPM != null) {\r\n\t\tinfo = addToOBDOutput(\"Engine RPM\", item.EngineRPM, info);\r\n\t}\r\n\tif (item.EngineLoad != null) {\r\n\t\tinfo = addToOBDOutput(\"Engine Load\", item.EngineLoad, info, \"%\");\r\n\t}\r\n\tif (item.EngineCoolantTemperature != null) {\r\n\t\tinfo = addToOBDOutput(\"Engine Coolant Temp.\", item.EngineCoolantTemperature, info, \"°C\");\r\n\t}\r\n\tif (item.FuelLevelPercentage != null) {\r\n\t\tinfo = addToOBDOutput(\"Fuel Level\", item.FuelLevelPercentage, info, \"%\");\r\n\t}\r\n\tif (item.FuelLevelRemaining != null) {\r\n\t\tinfo = addToOBDOutput(\"Fuel Level Remaining\", item.FuelLevelRemaining, info);\r\n\t}\r\n\tif (item.FuelConsumption != null) {\r\n\t\tinfo = addToOBDOutput(\"Fuel Consumption\", item.FuelConsumption, info, \"L/100km\");\r\n\t}\r\n\tif (item.FuelRate != null) {\r\n\t\tinfo = addToOBDOutput(\"Fuel Rate\", item.FuelRate, info);\r\n\t}\r\n\tif (item.IsMILActive != null) {\r\n\t\tinfo = addToOBDOutput(\"MIL Active\", item.IsMILActive, info);\r\n\t}\r\n\tif (item.DTCCount != null) {\r\n\t\tinfo = addToOBDOutput(\"DTC Count\", item.DTCCount, info);\r\n\t}\r\n\tif (item.DTCs != null) {\r\n\t\tinfo = addToOBDOutput(\"DTCs\", item.DTCs.split(\",\").join(\", \"), info);\r\n\t}\r\n\tif (item.DTCsClearedDistance != null) {\r\n\t\tinfo = addToOBDOutput(\"DTCs Cleared Distance\", item.DTCsClearedDistance, info, \"km\");\r\n\t}\r\n\tif (item.MILActivatedDistance != null) {\r\n\t\tinfo = addToOBDOutput(\"MIL Activated Distance\", item.MILActivatedDistance, info, \"km\");\r\n\t}\r\n\tif (item.Odometer != null) {\r\n\t\tinfo = addToOBDOutput(\"Odometer\", item.Odometer, info, \"km\");\r\n\t}\r\n\tif (item.MaintenanceRequired != null) {\r\n\t\tinfo = addToOBDOutput(\"Maintenance Required\", item.MaintenanceRequired, info);\r\n\t}\r\n\treturn info;\r\n}\r\n\r\nfunction addToOBDOutput(label, value, output, unit) {\r\n\tif (unit == null) {\r\n\t\tunit = \"\";\r\n\t}\r\n\treturn includeRowIfNotNull(label, value + unit);\r\n}\r\n\r\nexport function createIOOutput(asset, item, isTable) {\r\n\tvar settings = [];\r\n\tsettings = addToIOOutput(asset, item.AnalogPin1, \"a1\", strings.ANALOG_PIN + \" 1\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.AnalogPin2, \"a2\", strings.ANALOG_PIN + \" 2\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.AnalogPin3, \"a3\", strings.ANALOG_PIN + \" 3\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.AnalogPin4, \"a4\", strings.ANALOG_PIN + \" 4\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.AnalogPin5, \"a5\", strings.ANALOG_PIN + \" 5\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.AnalogPin6, \"a6\", strings.ANALOG_PIN + \" 6\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.AnalogPin7, \"a7\", strings.ANALOG_PIN + \" 7\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.AnalogPin8, \"a8\", strings.ANALOG_PIN + \" 8\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.AnalogPin9, \"a9\", strings.ANALOG_PIN + \" 9\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.AnalogPin10, \"a10\", strings.ANALOG_PIN + \" 10\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.DigitalPin1, \"d1\", strings.DIGITAL_PIN + \" 1\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.DigitalPin2, \"d2\", strings.DIGITAL_PIN + \" 2\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.DigitalPin3, \"d3\", strings.DIGITAL_PIN + \" 3\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.DigitalPin4, \"d4\", strings.DIGITAL_PIN + \" 4\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.DigitalPin5, \"d5\", strings.DIGITAL_PIN + \" 5\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.DigitalPin6, \"d6\", strings.DIGITAL_PIN + \" 6\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.DigitalPin7, \"d7\", strings.DIGITAL_PIN + \" 7\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.DigitalPin8, \"d8\", strings.DIGITAL_PIN + \" 8\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.DigitalPin9, \"d9\", strings.DIGITAL_PIN + \" 9\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.DigitalPin10, \"d10\", strings.DIGITAL_PIN + \" 10\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.DigitalPin11, \"d11\", strings.DIGITAL_PIN + \" 11\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.DigitalPin12, \"d12\", strings.DIGITAL_PIN + \" 12\", settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.DigitalIngition, \"d98\", strings.DIGITAL_IGNITION, settings, isTable);\r\n\tsettings = addToIOOutput(asset, item.DigitalSOS, \"d99\", strings.DIGITAL_SOS, settings, isTable);\r\n\tif (!isTable) {\r\n\t\treturn settings.join(\"\");\r\n\t}\r\n\treturn settings;\r\n}\r\n\r\nfunction addToIOOutput(asset, pin, id, defaultLabel, output, isTable) {\r\n\tif (pin == null) {\r\n\t\treturn output;\r\n\t}\r\n\tvar label = getInputLabelById(asset, id);\r\n\tif (label != null && label != \"\") {\r\n\t\tif (label.IsNormallyOpen) {\r\n\t\t\tpin = !pin;\r\n\t\t}\r\n\t\tif (label.InputPin.indexOf(\"d\") != -1) {\r\n\t\t\t// we may have a label override\r\n\t\t\tvar onLabel = strings.ON;\r\n\t\t\tif (!label.OnIsDisabled && label.OnLabel != null && label.OnLabel != \"\") {\r\n\t\t\t\tonLabel = label.OnLabel;\r\n\t\t\t}\r\n\t\t\tvar offLabel = strings.OFF;\r\n\t\t\tif (!label.OffIsDisabled && label.OffLabel != null && label.OffLabel != \"\") {\r\n\t\t\t\toffLabel = label.OffLabel;\r\n\t\t\t}\r\n\t\t\tpin = pin ? onLabel : offLabel;\r\n\t\t} else {\r\n\t\t\t// analog, we may need to factor pin\r\n\t\t\tif (label.AnalogFactor != null && label.AnalogFactor != \"\") {\r\n\t\t\t\tpin = pin / $j.parseFloat(label.AnalogFactor);\r\n\t\t\t}\r\n\t\t\tif (label.AnalogBehavior == 1) {\r\n\t\t\t\t//IsFuel\r\n\t\t\t\tvar level = pin / $j.parseFloat(label.MaxVoltage);\r\n\t\t\t\tpin =\r\n\t\t\t\t\t(level * 100).toFixed(2) +\r\n\t\t\t\t\t\"% (\" +\r\n\t\t\t\t\t(level * $j.parseFloat(label.FuelCapacity)).toFixed(2) +\r\n\t\t\t\t\t\" \" +\r\n\t\t\t\t\tfuelText() +\r\n\t\t\t\t\t\")\";\r\n\t\t\t} else if (label.AnalogUnit != null) {\r\n\t\t\t\tpin += \" \" + label.AnalogUnit;\r\n\t\t\t}\r\n\t\t}\r\n\t\tvar pinLabel = label.Label;\r\n\t\tif (pinLabel == null || pinLabel == \"\") {\r\n\t\t\tpinLabel = defaultLabel;\r\n\t\t}\r\n\t\tif (isTable) {\r\n\t\t\toutput.push(includeRowIfNotNull(pinLabel, pin));\r\n\t\t} else {\r\n\t\t\toutput.push(padRight(pinLabel, 15) + \": \" + pin + \"\\n\");\r\n\t\t}\r\n\t} else {\r\n\t\tif (isTable) {\r\n\t\t\toutput.push(includeRowIfNotNull(defaultLabel, pin));\r\n\t\t} else {\r\n\t\t\toutput.push(padRight(defaultLabel, 15) + \": \" + pin + \"\\n\");\r\n\t\t}\r\n\t}\r\n\treturn output;\r\n}\r\n\r\n/// TODO: Replace with String.prototype.padEnd\r\nfunction padRight(input, length) {\r\n\tif (input == null) return input;\r\n\tif (input == \"\") return input;\r\n\tif (input.length >= length) return input;\r\n\tvar pad = length - input.length;\r\n\treturn pad > 0 ? input + new Array(pad).join(\" \") : input;\r\n}\r\n\r\nfunction getInputLabelById(asset, id) {\r\n\tif (asset == null) return \"\";\r\n\tif (asset.InputLabels == null) return \"\";\r\n\tfor (var i = 0; i < asset.InputLabels.length; i++) {\r\n\t\tvar label = asset.InputLabels[i];\r\n\t\tif (label.InputPin == id) return label;\r\n\t}\r\n\treturn \"\";\r\n}\r\n","import strings from \"./strings.js\";\r\nimport user from \"./user.js\";\r\nimport trkData from \"./data.js\";\r\nimport options from \"./options.js\";\r\nimport { includeRowIfNotNull, svgPath } from \"./dom-util.js\";\r\nimport { canAssetBeRouted } from \"./waypoint.js\";\r\nimport { findFenceById, findAssetIdsInGeofence } from \"./fence.js\";\r\n\r\nexport function createQuickActionsForSharedView(sharedView) {\r\n\tdocument.getElementById(\"shared-view-information-actions\").classList.add(\"is-visible\");\r\n\tvar quickActions = document.getElementById(\"shared-view-information-actions-list\");\r\n\tvar actionOptions = quickActions.querySelector('a[data-action=\"shared-view-options\"]');\r\n\tactionOptions.setAttribute(\"data-shared-view-id\", sharedView.Id);\r\n\r\n\tvar actionToggleStatus = quickActions.querySelector('a[data-action=\"shared-view-status\"]');\r\n\tactionToggleStatus.setAttribute(\"data-shared-view-id\", sharedView.Id);\r\n\tif (sharedView.IsEnabled) {\r\n\t\tactionToggleStatus.title = strings.DISABLE;\r\n\t\tactionToggleStatus.querySelector(\"span\").textContent = strings.DISABLE;\r\n\t\tactionToggleStatus.querySelector(\"use\").setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", svgPath(\"ban\"));\r\n\t} else {\r\n\t\tactionToggleStatus.title = strings.ENABLE;\r\n\t\tactionToggleStatus.querySelector(\"span\").textContent = strings.ENABLE;\r\n\t\tactionToggleStatus.querySelector(\"use\").setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", svgPath(\"check\"));\r\n\t}\r\n}\r\n\r\nexport function createQuickActionsForGeofence(fence) {\r\n\tdocument.getElementById(\"map-item-actions-geofence\").classList.add(\"is-visible\");\r\n\tvar quickActions = document.getElementById(\"geofence-information-actions-list\");\r\n\tvar actionHide = quickActions.querySelector('a[data-action=\"fence-hide\"]');\r\n\tactionHide.setAttribute(\"data-fence-id\", fence.Id);\r\n\tvar actionOptions = quickActions.querySelector('a[data-action=\"fence-options\"]');\r\n\tactionOptions.setAttribute(\"data-fence-id\", fence.Id);\r\n\r\n\tvar isSendDisabled = false,\r\n\t\tisReplayDisabled = false,\r\n\t\tisGroupDisabled = false,\r\n\t\tisAlertDisabled = false,\r\n\t\tisReportDisabled = false;\r\n\tif (user.isAnonymous) {\r\n\t\tisSendDisabled = isGroupDisabled = isAlertDisabled = isReportDisabled = true;\r\n\t}\r\n\r\n\tif (findAssetIdsInGeofence(fence).length == 0) {\r\n\t\tisSendDisabled = isGroupDisabled = isReplayDisabled = isReportDisabled = true;\r\n\t}\r\n\r\n\tvar actionSendMessage = quickActions.querySelector('a[data-action=\"fence-send-message\"]');\r\n\tvar actionHistory = quickActions.querySelector('a[data-action=\"fence-history\"]');\r\n\tvar actionCreateAlert = quickActions.querySelector('a[data-action=\"fence-create-alert\"]');\r\n\tvar actionLocationReport = quickActions.querySelector('a[data-action=\"fence-location-report\"]');\r\n\tvar actionGroupAssets = quickActions.querySelector('a[data-action=\"fence-group-assets\"]');\r\n\tif (isSendDisabled) {\r\n\t\tactionSendMessage.classList.add(\"disabled\");\r\n\t} else {\r\n\t\tactionSendMessage.classList.remove(\"disabled\");\r\n\t}\r\n\tif (isReplayDisabled) {\r\n\t\tactionHistory.classList.add(\"disabled\");\r\n\t} else {\r\n\t\tactionHistory.classList.remove(\"disabled\");\r\n\t}\r\n\tif (isGroupDisabled) {\r\n\t\tactionGroupAssets.classList.add(\"disabled\");\r\n\t} else {\r\n\t\tactionGroupAssets.classList.remove(\"disabled\");\r\n\t}\r\n\tif (isAlertDisabled) {\r\n\t\tactionCreateAlert.classList.add(\"disabled\");\r\n\t} else {\r\n\t\tactionCreateAlert.classList.remove(\"disabled\");\r\n\t}\r\n\tif (isReportDisabled) {\r\n\t\tactionLocationReport.classList.add(\"disabled\");\r\n\t} else {\r\n\t\tactionLocationReport.classList.remove(\"disabled\");\r\n\t}\r\n\tactionSendMessage.setAttribute(\"data-fence-id\", fence.Id);\r\n\tactionHistory.setAttribute(\"data-fence-id\", fence.Id);\r\n\tactionCreateAlert.setAttribute(\"data-fence-id\", fence.Id);\r\n\tactionLocationReport.setAttribute(\"data-fence-id\", fence.Id);\r\n\tactionGroupAssets.setAttribute(\"data-fence-id\", fence.Id);\r\n}\r\n\r\nexport function createQuickActionsForWaypoint(waypoint, asset) {\r\n\tdocument.getElementById(\"map-item-actions-waypoint\").classList.add(\"is-visible\");\r\n\tvar quickActions = document.getElementById(\"waypoint-information-actions-list\");\r\n\tvar actionHide = quickActions.querySelector('a[data-action=\"waypoint-hide\"]');\r\n\tactionHide.setAttribute(\"data-asset-id\", asset.Id);\r\n\tvar actionOptions = quickActions.querySelector('a[data-action=\"waypoint-options\"]');\r\n\tactionOptions.setAttribute(\"data-asset-id\", asset.Id);\r\n\tvar actionGetRoute = quickActions.querySelector('a[data-action=\"waypoint-route-asset\"]');\r\n\tactionGetRoute.setAttribute(\"data-waypoint-id\", waypoint.Id);\r\n\tvar actionMarkComplete = quickActions.querySelector('a[data-action=\"waypoint-mark-complete\"]');\r\n\tactionMarkComplete.setAttribute(\"data-waypoint-id\", waypoint.Id);\r\n\tif (!canAssetBeRouted(asset) || waypoint.route != null) {\r\n\t\tactionGetRoute.classList.add(\"disabled\");\r\n\t} else {\r\n\t\tactionGetRoute.classList.remove(\"disabled\");\r\n\t}\r\n\tif (user.isAnonymous) {\r\n\t\tactionMarkComplete.classList.add(\"disabled\");\r\n\t}\r\n}\r\n\r\nexport function createQuickActionsForPlace(place) {\r\n\tdocument.getElementById(\"map-item-actions-place\").classList.add(\"is-visible\");\r\n\tvar quickActions = document.getElementById(\"place-information-actions-list\");\r\n\tvar actionHide = quickActions.querySelector('a[data-action=\"place-hide\"]');\r\n\tactionHide.setAttribute(\"data-place-id\", place.Id);\r\n\tvar actionOptions = quickActions.querySelector('a[data-action=\"place-options\"]');\r\n\tactionOptions.setAttribute(\"data-place-id\", place.Id);\r\n\tvar isAssetRoutingDisabled =\r\n\t\ttrkData.assets === null || trkData.assets.length === 0 || options.enabledFeatures.indexOf(\"ASSET_ROUTING\") === -1;\r\n\tvar isRoutingDisabled = options.enabledFeatures.indexOf(\"GET_ROUTE\") === -1;\r\n\tvar actionRouteAsset = quickActions.querySelector('a[data-action=\"place-route-asset\"]');\r\n\tactionRouteAsset.setAttribute(\"data-place-id\", place.Id);\r\n\tif (isAssetRoutingDisabled) {\r\n\t\tactionRouteAsset.classList.add(\"disabled\");\r\n\t} else {\r\n\t\tactionRouteAsset.classList.remove(\"disabled\");\r\n\t}\r\n\tvar actionMeasureDistanceTo = quickActions.querySelector('a[data-action=\"place-measure-distance\"]');\r\n\tactionMeasureDistanceTo.setAttribute(\"data-lat\", place.Location.Lat);\r\n\tactionMeasureDistanceTo.setAttribute(\"data-lng\", place.Location.Lng);\r\n\tvar actionRouteFrom = quickActions.querySelector('a[data-action=\"place-route-from\"]');\r\n\tactionRouteFrom.setAttribute(\"data-lat\", place.Location.Lat);\r\n\tactionRouteFrom.setAttribute(\"data-lng\", place.Location.Lng);\r\n\tvar actionRouteTo = quickActions.querySelector('a[data-action=\"place-route-to\"]');\r\n\tactionRouteTo.setAttribute(\"data-lat\", place.Location.Lat);\r\n\tactionRouteTo.setAttribute(\"data-lng\", place.Location.Lng);\r\n\tif (isRoutingDisabled) {\r\n\t\tactionRouteFrom.classList.add(\"disabled\");\r\n\t\tactionRouteTo.classList.add(\"disabled\");\r\n\t} else {\r\n\t\tactionRouteFrom.classList.remove(\"disabled\");\r\n\t\tactionRouteTo.classList.remove(\"disabled\");\r\n\t}\r\n}\r\n\r\nexport function createQuickActionsForPosition(asset, location) {\r\n\tdocument.getElementById(\"map-item-actions-position\").classList.add(\"is-visible\");\r\n\tvar quickActions = document.getElementById(\"position-information-actions-list\");\r\n\tvar actionPositionToPlace = quickActions.querySelector('a[data-action=\"position-to-place\"]');\r\n\tactionPositionToPlace.classList.remove(\"disabled\");\r\n\tactionPositionToPlace.setAttribute(\"data-asset-id\", asset.Id);\r\n\tactionPositionToPlace.setAttribute(\"data-lat\", location.Lat);\r\n\tactionPositionToPlace.setAttribute(\"data-lng\", location.Lng);\r\n\tactionPositionToPlace.setAttribute(\"data-position-id\", location.Id);\r\n\tif (!user.canEditPlaces) {\r\n\t\tactionPositionToPlace.classList.add(\"disabled\");\r\n\t}\r\n\tvar actionHide = quickActions.querySelector('a[data-action=\"position-hide\"]');\r\n\tactionHide.setAttribute(\"data-asset-id\", asset.Id);\r\n\r\n\tvar actionActivity = quickActions.querySelector('a[data-action=\"position-activity\"]');\r\n\tactionActivity.setAttribute(\"data-asset-id\", asset.Id);\r\n\r\n\tvar actionPositionVisibility = quickActions.querySelector('a[data-action=\"position-toggle-map\"]');\r\n\tactionPositionVisibility.setAttribute(\"data-position-id\", location.Id);\r\n\tactionPositionVisibility.setAttribute(\"data-asset-id\", asset.Id);\r\n\tactionPositionVisibility.setAttribute(\"data-hidden\", location.IsHidden === true);\r\n\tactionPositionVisibility\r\n\t\t.querySelector(\"use\")\r\n\t\t.setAttributeNS(\r\n\t\t\t\"http://www.w3.org/1999/xlink\",\r\n\t\t\t\"href\",\r\n\t\t\t\"/content/svg/tracking.svg?v=15#\" + (location.IsHidden ? \"visible\" : \"invisible\")\r\n\t\t);\r\n\tactionPositionVisibility.querySelector(\"span\").innerText = location.IsHidden\r\n\t\t? strings.SHOW_POSITION\r\n\t\t: strings.HIDE_POSITION;\r\n\tactionPositionVisibility.title = location.IsHidden ? strings.SHOW_POSITION : strings.HIDE_POSITION;\r\n\r\n\tvar actionOptions = quickActions.querySelector('a[data-action=\"position-options\"]');\r\n\tactionOptions.setAttribute(\"data-asset-id\", asset.Id);\r\n\tvar actionMeasureDistanceTo = quickActions.querySelector('a[data-action=\"position-measure-distance\"]');\r\n\tactionMeasureDistanceTo.setAttribute(\"data-lat\", location.Lat);\r\n\tactionMeasureDistanceTo.setAttribute(\"data-lng\", location.Lng);\r\n\tvar isRoutingDisabled = options.enabledFeatures.indexOf(\"GET_ROUTE\") === -1;\r\n\tvar actionRouteFrom = quickActions.querySelector('a[data-action=\"position-route-from\"]');\r\n\tactionRouteFrom.setAttribute(\"data-lat\", location.Lat);\r\n\tactionRouteFrom.setAttribute(\"data-lng\", location.Lng);\r\n\tvar actionRouteTo = quickActions.querySelector('a[data-action=\"position-route-to\"]');\r\n\tactionRouteTo.setAttribute(\"data-lat\", location.Lat);\r\n\tactionRouteTo.setAttribute(\"data-lng\", location.Lng);\r\n\tif (isRoutingDisabled) {\r\n\t\tactionRouteFrom.classList.add(\"disabled\");\r\n\t\tactionRouteTo.classList.add(\"disabled\");\r\n\t} else {\r\n\t\tactionRouteFrom.classList.remove(\"disabled\");\r\n\t\tactionRouteTo.classList.remove(\"disabled\");\r\n\t}\r\n}\r\n","import user from \"./user.js\";\r\nimport strings from \"./strings.js\";\r\nimport { disabledCheckboxEl } from \"./dom-util.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport { el } from \"redom\"; // https://redom.js.org/\r\n\r\nexport function initDatatables() {\r\n\t$(\"#AssetAlerts\").dataTable({\r\n\t\tdestroy: true,\r\n\t\tfilter: false,\r\n\t\tinfo: false,\r\n\t\tjQueryUI: false,\r\n\t\tautoWidth: false,\r\n\t\tlengthChange: false,\r\n\t\tpaging: false,\r\n\t\tpageLength: 3,\r\n\t\tdeferRender: true,\r\n\t\torder: [[2, \"asc\"]],\r\n\t\tcolumnDefs: [\r\n\t\t\t{\r\n\t\t\t\ttargets: \"_all\",\r\n\t\t\t\trender: $.fn.dataTable.render.text(),\r\n\t\t\t},\r\n\t\t],\r\n\t\tcolumns: [\r\n\t\t\t{ visible: false }, // id\r\n\t\t\t{ visible: user.canEditAlerts, sortable: false, class: \"center\", render: renderDomElement }, // edit\r\n\t\t\t{}, // name\r\n\t\t\t{}, // description\r\n\t\t\t{}, // condition\r\n\t\t\t{ render: renderDomElement }, // filters\r\n\t\t\t{ visible: user.canEditAlerts, sortable: false, class: \"center\", render: renderDomElement }, // remove\r\n\t\t],\r\n\t\tlanguage: strings.DATATABLE,\r\n\t\tdrawCallback: function (oSettings) {},\r\n\t\tinitComplete: function (oSettings, json) {\r\n\t\t\t$(\"#AssetAlerts\").DataTable().clear();\r\n\t\t},\r\n\t});\r\n}\r\n\r\nexport function renderDomElement(data, type, row) {\r\n\tif (data === undefined || data === null) {\r\n\t\treturn \"\";\r\n\t}\r\n\tif (data instanceof Element || data instanceof HTMLDocument) {\r\n\t\treturn data.outerHTML;\r\n\t}\r\n\treturn \"\";\r\n}\r\n\r\nexport function renderIDPCommandLog(commandLog) {\r\n\tvar commandLogData = [];\r\n\tfor (var i = 0; i < commandLog.length; i++) {\r\n\t\tvar item = commandLog[i];\r\n\t\tcommandLogData.push([\r\n\t\t\titem.CreatedOn,\r\n\t\t\tel(\"pre\", item.Command),\r\n\t\t\tdisabledCheckboxEl(item.IsSent),\r\n\t\t\tdisabledCheckboxEl(item.IsResponse),\r\n\t\t\tdisabledCheckboxEl(item.Success),\r\n\t\t]);\r\n\t}\r\n\r\n\t$(\"#IDPCommandLog\").dataTable({\r\n\t\tdata: commandLogData,\r\n\t\tdestroy: true,\r\n\t\tfilter: false,\r\n\t\tinfo: false,\r\n\t\tjQueryUI: true,\r\n\t\tautoWidth: false,\r\n\t\tlengthChange: false,\r\n\t\tpaging: false,\r\n\t\tpageLength: 5,\r\n\t\tdeferRender: true,\r\n\t\torder: [[0, \"asc\"]],\r\n\t\tcolumnDefs: [\r\n\t\t\t{\r\n\t\t\t\ttargets: \"_all\",\r\n\t\t\t\trender: $.fn.dataTable.render.text(),\r\n\t\t\t},\r\n\t\t],\r\n\t\tcolumns: [\r\n\t\t\t{},\r\n\t\t\t{ render: renderDomElement },\r\n\t\t\t{ render: renderDomElement },\r\n\t\t\t{ render: renderDomElement },\r\n\t\t\t{ render: renderDomElement },\r\n\t\t],\r\n\t\tlanguage: strings.DATATABLE,\r\n\t});\r\n}\r\n","import domNodes from \"./domNodes.js\";\r\nimport { findAssetById } from \"./assets.js\";\r\nimport strings from \"./strings.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\nimport { handleWebServiceError } from \"./ajax.js\";\r\nimport { openDialogPanel } from \"./panel-nav.js\";\r\nimport { handleAjaxFormSubmission, toggleLoadingMessage, formShowErrorMessage } from \"./ajax.js\";\r\nimport { renderDomElement } from \"./datatables.js\";\r\nimport { showOrLoadAssetPosition } from \"./asset-positions.js\";\r\nimport { convertToLatLngPreference } from \"./preferences.js\";\r\nimport { formattedTextToDiv } from \"./dom-util.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport $j from \"jquery\";\r\nimport { el } from \"redom\"; // https://redom.js.org/\r\n\r\nexport function initGarmin() {\r\n\t$(\"#asset-events-dialog\").on(\"click\", \"button.ViewGarminSubmission\", function (e) {\r\n\t\te.preventDefault();\r\n\t\tvar submissionId = $j(this).data(\"id\");\r\n\t\tvar assetId = $j(this).data(\"assetId\");\r\n\t\tloadGarminFormSubmission(assetId, submissionId, null);\r\n\t});\r\n\t$j(\"#event-data\").on(\"click\", \"button.ViewGarminSubmission\", function (e) {\r\n\t\te.preventDefault();\r\n\r\n\t\tvar submissionId = $j(this).data(\"id\");\r\n\t\tvar assetId = $j(this).data(\"assetId\");\r\n\t\tvar positionId = $j(this).parent().parent().find(\"a.location\").attr(\"data-marker\");\r\n\t\tif (positionId != null) {\r\n\t\t\t$j.when(showOrLoadAssetPosition(positionId, assetId)).done(function () {\r\n\t\t\t\t// activate garmin form panel\r\n\t\t\t\t$(\"#accordion-garmin-content\").collapse(\"show\");\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\tloadGarminFormSubmission(assetId, submissionId, null);\r\n\t\t}\r\n\t});\r\n\r\n\t$(domNodes.dialogs.garminFormsHistory).on(\"click\", \"#RefreshGarminFormLogs\", function (e) {\r\n\t\te.preventDefault();\r\n\t\tvar assetId = $(domNodes.dialogs.garminFormsHistory).data(\"assetId\");\r\n\t\tvar asset = findAssetById(assetId);\r\n\t\tif (asset != null) {\r\n\t\t\tloadGarminFormsHistory(asset);\r\n\t\t}\r\n\t});\r\n\t$(domNodes.dialogs.garminFormsHistory).on(\"click\", \"button.ViewGarminSubmission\", function (e) {\r\n\t\te.preventDefault();\r\n\r\n\t\tvar submissionId = $j(this).data(\"id\");\r\n\t\tvar assetId = $(domNodes.dialogs.garminFormsHistory).data(\"assetId\");\r\n\r\n\t\tvar positionId = $j(this).parent().parent().find(\"a.location\").attr(\"data-marker\");\r\n\t\tif (positionId != null) {\r\n\t\t\t$j.when(showOrLoadAssetPosition(positionId, assetId)).done(function () {\r\n\t\t\t\t// activate garmin form panel for position\r\n\t\t\t\t$(\"#accordion-garmin-content\").collapse(\"show\");\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\tloadGarminFormSubmission(assetId, submissionId, null);\r\n\t\t}\r\n\t});\r\n}\r\n\r\nexport function loadGarminFormSubmission(assetId, submissionId, panel) {\r\n\ttoggleLoadingMessage(true, \"form-submission\");\r\n\tvar data = { assetId: assetId, submissionId: submissionId };\r\n\t$j.ajax({\r\n\t\ttype: \"POST\",\r\n\t\turl: wrapUrl(\"/services/GPSService.asmx/GetGarminFormSubmissionForAsset\"),\r\n\t\tdata: JSON.stringify(data),\r\n\t\tcontentType: \"application/json; charset=utf-8\",\r\n\t\tdataType: \"json\",\r\n\t\tsuccess: function (msg) {\r\n\t\t\tif (msg.d) {\r\n\t\t\t\tvar result = msg.d;\r\n\t\t\t\tif (result.Success == true) {\r\n\t\t\t\t\tshowGarminFormSubmission(result.Item, panel);\r\n\t\t\t\t} else {\r\n\t\t\t\t\thandleWebServiceError(strings.MSG_GARMIN_FORM_SUBMISSION_ERROR);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\ttoggleLoadingMessage(false, \"form-submission\");\r\n\t\t},\r\n\t\terror: function (xhr, status, error) {\r\n\t\t\thandleWebServiceError(strings.MSG_GARMIN_FORM_SUBMISSION_ERROR);\r\n\t\t\ttoggleLoadingMessage(false, \"form-submission\");\r\n\t\t},\r\n\t});\r\n}\r\n\r\nexport function loadGarminFormsHistoryDialog(asset) {\r\n\t$(domNodes.dialogs.garminFormsHistory).data(\"assetId\", asset.Id);\r\n\topenDialogPanel(\r\n\t\tdomNodes.dialogs.garminFormsHistory,\r\n\t\tstrings.GARMIN_FORMS,\r\n\t\tasset,\r\n\t\ttrue,\r\n\t\tnull,\r\n\t\t\"asset\",\r\n\t\t\"view-logs-garmin-forms\",\r\n\t\tloadGarminFormsHistoryDialog\r\n\t);\r\n\tloadGarminFormsHistory(asset);\r\n}\r\n\r\nfunction loadGarminFormsHistory(asset) {\r\n\tvar btn = document.getElementById(\"RefreshGarminFormLogs\");\r\n\tvar status = document.getElementById(\"garmin-forms-status\");\r\n\tvar data = { assetId: asset.Id };\r\n\r\n\thandleAjaxFormSubmission(\r\n\t\t\"GetGarminFormSubmissionHistoryForAsset\",\r\n\t\tdata,\r\n\t\tbtn,\r\n\t\tstatus,\r\n\t\tnull,\r\n\t\tstrings.MSG_GARMIN_FORMS_HISTORY_ERROR,\r\n\t\tfunction (result) {\r\n\t\t\tif (result.Success == true) {\r\n\t\t\t\tvar itemData = [];\r\n\t\t\t\tif (result.Items != null) {\r\n\t\t\t\t\tfor (var i = 0; i < result.Items.length; i++) {\r\n\t\t\t\t\t\tvar item = result.Items[i];\r\n\t\t\t\t\t\tvar address = \"\";\r\n\t\t\t\t\t\tvar address = null;\r\n\t\t\t\t\t\tif (item.Position != null) {\r\n\t\t\t\t\t\t\taddress = item.Position.Address;\r\n\t\t\t\t\t\t\tif (address == null || address.trim() == \"\" || asset.HideAddress) {\r\n\t\t\t\t\t\t\t\taddress = convertToLatLngPreference(\r\n\t\t\t\t\t\t\t\t\titem.Position.DisplayLat,\r\n\t\t\t\t\t\t\t\t\titem.Position.DisplayLng,\r\n\t\t\t\t\t\t\t\t\titem.Position.Grid\r\n\t\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\taddress = el(\"a.location\", { href: \"#\", dataset: { marker: item.Position.Id } }, address);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\titemData.push([\r\n\t\t\t\t\t\t\titem.Id,\r\n\t\t\t\t\t\t\titem.Title,\r\n\t\t\t\t\t\t\taddress,\r\n\t\t\t\t\t\t\tel(\r\n\t\t\t\t\t\t\t\t\"button.ViewGarminSubmission.command.details.btn.btn-secondary\",\r\n\t\t\t\t\t\t\t\t{ dataset: { assetId: asset.Id, id: item.Id } },\r\n\t\t\t\t\t\t\t\tstrings.VIEW\r\n\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t\titem.CreatedOn,\r\n\t\t\t\t\t\t]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t$(\"#GarminFormLogs\").dataTable({\r\n\t\t\t\t\tdata: itemData,\r\n\t\t\t\t\tdestroy: true,\r\n\t\t\t\t\tfilter: false,\r\n\t\t\t\t\tinfo: false,\r\n\t\t\t\t\tjQueryUI: true,\r\n\t\t\t\t\tautoWidth: false,\r\n\t\t\t\t\tlengthChange: false,\r\n\t\t\t\t\tpaging: true,\r\n\t\t\t\t\tpageLength: 10,\r\n\t\t\t\t\tdeferRender: true,\r\n\t\t\t\t\torder: [[4, \"desc\"]],\r\n\t\t\t\t\tcolumnDefs: [\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\ttargets: \"_all\",\r\n\t\t\t\t\t\t\trender: $.fn.dataTable.render.text(),\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t],\r\n\t\t\t\t\tcolumns: [\r\n\t\t\t\t\t\t{ visible: false }, // id\r\n\t\t\t\t\t\t{}, // title\r\n\t\t\t\t\t\t{ render: renderDomElement }, // address\r\n\t\t\t\t\t\t{ sortable: false, render: renderDomElement }, // view submission\r\n\t\t\t\t\t\t{ width: \"75px\" }, // createdon\r\n\t\t\t\t\t],\r\n\t\t\t\t\tlanguage: strings.DATATABLE,\r\n\t\t\t\t});\r\n\r\n\t\t\t\t$j(\"#GarminFormLogs\").removeAttr(\"style\");\r\n\t\t\t} else {\r\n\t\t\t\tformShowErrorMessage(status, strings.MSG_GARMIN_FORMS_HISTORY_ERROR);\r\n\t\t\t}\r\n\t\t}\r\n\t);\r\n}\r\n\r\nfunction showGarminFormSubmission(submission, panel) {\r\n\tvar dialog = $j(domNodes.infoDialogs.garminSubmission);\r\n\tvar openDialog = false;\r\n\tif (panel == null) {\r\n\t\tpanel = dialog;\r\n\t\topenDialog = true;\r\n\t\tsetDialogTitle(dialog, submission.Title);\r\n\t}\r\n\tvar title = $j(\".GarminSubmissionTitle\", panel).text(submission.Title);\r\n\tvar date = $j(\".GarminSubmissionDate\", panel).text(submission.CreatedOn);\r\n\tvar items = $j(\".GarminSubmissionItems\", panel).empty();\r\n\tfor (var i = 0; i < submission.Items.length; i++) {\r\n\t\tvar item = submission.Items[i];\r\n\t\tvar itemText = item.Title;\r\n\t\tif (item.Subtitle != null && item.Subtitle != \"\" && item.Subtitle != item.Title) {\r\n\t\t\titemText += \" - \" + item.Subtitle;\r\n\t\t}\r\n\t\tvar itemLI = $j(\"
    • \").text(itemText);\r\n\t\tvar itemUL = $j(\"
    • ')\r\n\t\t//action.appendTo(bar);\r\n\t\t$quickActions.appendTo(bar);\r\n\t}\r\n\tif (dialog.getAttribute(\"data-has-collapse\") !== null) {\r\n\t\tvar collapse = document.createElement(\"button\");\r\n\t\tcollapse.setAttribute(\"data-for-dialog\", dialog.id);\r\n\t\tcollapse.className = \"btn item-collapse panel-options\";\r\n\t\tvar collapseSvg = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\r\n\t\tvar collapseUse = document.createElementNS(\"http://www.w3.org/2000/svg\", \"use\");\r\n\t\tcollapseSvg.appendChild(collapseUse);\r\n\t\tcollapseUse.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", svgPath(\"angle-up\"));\r\n\t\tcollapse.appendChild(collapseSvg);\r\n\t\tbar[0].appendChild(collapse);\r\n\t}\r\n\r\n\tvar close = document.createElement(\"button\");\r\n\tclose.setAttribute(\"data-for-dialog\", dialog.id);\r\n\tclose.className = \"btn item-close\";\r\n\tvar closeSvg = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\r\n\tvar closeUse = document.createElementNS(\"http://www.w3.org/2000/svg\", \"use\");\r\n\tcloseSvg.appendChild(closeUse);\r\n\tcloseUse.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", svgPath(\"times\"));\r\n\tclose.appendChild(closeSvg);\r\n\tbar[0].appendChild(close);\r\n\r\n\t//var close = $('');\r\n\t//close.appendTo(bar);\r\n\tbar.prependTo(panel);\r\n}\r\n","import trkData from \"./data.js\";\r\nimport strings from \"./strings.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\nimport { toggleLoadingMessage } from \"./ajax.js\";\r\nimport { handleWebServiceError } from \"./ajax.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport { openDialogPanel } from \"./panel-nav.js\";\r\nimport { renderDomElement } from \"./datatables.js\";\r\nimport { handleAjaxFormSubmission, formShowErrorMessage, formShowSuccessMessage } from \"./ajax.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport $j from \"jquery\";\r\nimport _ from \"lodash\";\r\nimport { el } from \"redom\"; // https://redom.js.org/\r\n\r\nexport function getDescriptionForDriver(driver) {\r\n\tvar description = \"\";\r\n\tif (driver == null) {\r\n\t\treturn description;\r\n\t}\r\n\tif (driver.IButtonId !== undefined && driver.IButtonId !== null && driver.IButtonId !== '') {\r\n\t\tdescription = driver.IButtonId;\r\n\t}\r\n\tif (driver.GarminId !== null && driver.GarminId !== undefined) {\r\n\t\tif (description !== \"\") {\r\n\t\t\tdescription += \" / \";\r\n\t\t}\r\n\t\tdescription += driver.GarminId;\r\n\t}\r\n\treturn description;\r\n}\r\n\r\nexport function addDriverToAsset(driverId, assetId) {\r\n\tif (trkData.assetDrivers == null) return;\r\n\tfor (var i = 0; i < trkData.assetDrivers.length; i++) {\r\n\t\tif (trkData.assetDrivers[i].AssetId == assetId) {\r\n\t\t\ttrkData.assetDrivers[i].DriverIds.push(driverId);\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport function findAssetDriverGroupIdsByAssetId(id) {\r\n\tvar item = _.find(trkData.assetDriverGroups, { AssetId: parseInt(id) });\r\n\treturn item === undefined ? null : item.DriverGroupIds;\r\n}\r\n\r\nexport function findAssetDriversByAssetId(id) {\r\n\tvar item = _.find(trkData.assetDrivers, { AssetId: parseInt(id) });\r\n\treturn item === undefined ? null : item.DriverIds;\r\n}\r\n\r\nfunction calculateGroupDriverIdsForAsset(assetId) {\r\n\tvar assetDriverGroup = _.find(trkData.assetDriverGroups, { AssetId: parseInt(assetId) });\r\n\tif (assetDriverGroup === undefined) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar groupDriverIds = [];\r\n\t_.each(assetDriverGroup.DriverGroupIds, function (groupId) {\r\n\t\tvar driverGroup = _.find(trkData.driverGroups, { Id: groupId });\r\n\t\tif (driverGroup === undefined) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tgroupDriverIds = groupDriverIds.concat(driverGroup.ItemIds);\r\n\t});\r\n\r\n\tvar assetDrivers = _.find(trkData.assetDrivers, { AssetId: parseInt(assetId) });\r\n\tif (assetDrivers === undefined) {\r\n\t\ttrkData.assetDrivers.push({ AssetId: parseInt(assetId), DriverIds: [], GroupDriverIds: groupDriverIds });\r\n\t} else {\r\n\t\tassetDrivers.GroupDriverIds = groupDriverIds;\r\n\t}\r\n}\r\n\r\nexport function addDriverGroupToAsset(driverGroupId, assetId) {\r\n\tvar assetDriverGroup = _.find(trkData.assetDriverGroups, { AssetId: parseInt(assetId) });\r\n\tif (assetDriverGroup === undefined) {\r\n\t\ttrkData.assetDriverGroups.push({ AssetId: parseInt(assetId), DriverGroupIds: [driverGroupId] });\r\n\t} else {\r\n\t\tif (_.indexOf(assetDriverGroup.DriverGroupIds, driverGroupId) === -1) {\r\n\t\t\tassetDriverGroup.DriverGroupIds.push(driverGroupId);\r\n\t\t}\r\n\t}\r\n\r\n\t// recalculate GroupDriverIds for the asset\r\n\tcalculateGroupDriverIdsForAsset(assetId);\r\n}\r\n\r\nexport function removeDriverGroupFromAsset(driverGroupId, assetId) {\r\n\tvar assetDriverGroup = _.find(trkData.assetDriverGroups, { AssetId: parseInt(assetId) });\r\n\tif (assetDriverGroup === undefined) {\r\n\t\treturn;\r\n\t}\r\n\tvar groupIndex = _.indexOf(assetDriverGroup.DriverGroupIds, driverGroupId);\r\n\tif (groupIndex !== -1) {\r\n\t\t// remove group\r\n\t\tassetDriverGroup.DriverGroupIds.splice(groupIndex, 1);\r\n\t}\r\n\r\n\t// recalculate GroupDriverIds for the asset based on remaining group(s) assigned\r\n\tcalculateGroupDriverIdsForAsset(assetId);\r\n}\r\n\r\nexport function removeDriverFromAsset(driverId, assetId) {\r\n\tif (trkData.assetDrivers == null) return;\r\n\tfor (var i = 0; i < trkData.assetDrivers.length; i++) {\r\n\t\tif (trkData.assetDrivers[i].AssetId == assetId) {\r\n\t\t\tfor (var j = 0; j < trkData.assetDrivers[i].DriverIds.length; j++) {\r\n\t\t\t\tif (driverId == trkData.assetDrivers[i].DriverIds[j]) {\r\n\t\t\t\t\ttrkData.assetDrivers[i].DriverIds.splice(j, 1);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport function findDriverById(id) {\r\n\tvar item = _.find(trkData.drivers, { Id: parseInt(id) });\r\n\treturn item === undefined ? null : item;\r\n}\r\n\r\nexport function findAssetDriverIdsByAssetIdSorted(id) {\r\n\t// trkData.drivers is sorted\r\n\tif (trkData.assetDrivers == null) {\r\n\t\treturn null;\r\n\t}\r\n\tvar ids = null;\r\n\tvar assetDrivers = _.find(trkData.assetDrivers, { AssetId: parseInt(id) });\r\n\tif (assetDrivers !== undefined) {\r\n\t\tids = assetDrivers.DriverIds.concat(assetDrivers.GroupDriverIds);\r\n\t}\r\n\tif (ids != null) {\r\n\t\tvar sortedIds = [];\r\n\t\tfor (var i = 0, len = trkData.drivers.length; i < len; i++) {\r\n\t\t\tvar driverId = trkData.drivers[i].Id;\r\n\t\t\tfor (var j = 0; j < ids.length; j++) {\r\n\t\t\t\tvar includedId = ids[j];\r\n\t\t\t\tif (driverId == includedId) {\r\n\t\t\t\t\tsortedIds.push(includedId);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn sortedIds;\r\n\t}\r\n\r\n\treturn null;\r\n}\r\n\r\nexport function updateAssetDriverStatus(asset, driverId, statusId, btn, notes) {\r\n\tvar status = document.getElementById(\"current-drivers-status\");\r\n\tvar data = { assetId: asset.Id, driverId: driverId, statusId: statusId, notes: notes };\r\n\thandleAjaxFormSubmission(\r\n\t\t\"AssetDriverUpdateStatus\",\r\n\t\tdata,\r\n\t\tbtn,\r\n\t\tstatus,\r\n\t\tstrings.MSG_UPDATE_DRIVER_SUCCESS,\r\n\t\tstrings.MSG_UPDATE_DRIVER_ERROR,\r\n\t\tfunction () {\r\n\t\t\tloadAssetDrivers(asset);\r\n\t\t}\r\n\t);\r\n\r\n\t// if(btn != null)\r\n\t// btn.prop('disabled', true);\r\n\t//toggleLoadingMessage(true, 'asset-driver-update');\r\n\t//$j.ajax({\r\n\t// type: 'POST',\r\n\t// url: wrapUrl('/services/GPSService.asmx/AssetDriverUpdateStatus'),\r\n\t// data: JSON.stringify(data),\r\n\t// contentType: 'application/json; charset=utf-8',\r\n\t// dataType: 'json',\r\n\t// success: function (msg) {\r\n\t// if (btn != null)\r\n\t// btn.prop('disabled', false);\r\n\t// if (msg.d) {\r\n\t// var result = msg.d;\r\n\t// if (result.Success == true) {\r\n\t// loadAssetDrivers(asset);\r\n\t// trkData.validation.currentDriver.resetForm();\r\n\t// trkData.validation.currentDriver.currentForm.reset();\r\n\t// } else {\r\n\t// handleWebServiceError(strings.MSG_UPDATE_DRIVER_ERROR);\r\n\t// }\r\n\t// }\r\n\t// toggleLoadingMessage(false, 'asset-driver-update');\r\n\t// },\r\n\t// error: function (xhr, status, error) {\r\n\t// handleWebServiceError(strings.MSG_UPDATE_DRIVER_ERROR);\r\n\t// toggleLoadingMessage(false, 'asset-driver-update');\r\n\t// if (btn != null)\r\n\t// btn.prop('disabled', false);\r\n\t// }\r\n\t//});\r\n}\r\n\r\nexport function loginAssetDriver(asset, driverId, notes, btn, statusId) {\r\n\tvar status = document.getElementById(\"current-drivers-status\");\r\n\tvar data = { assetId: asset.Id, driverId: driverId, notes: notes, statusId: statusId };\r\n\thandleAjaxFormSubmission(\r\n\t\t\"AssetDriverLogin\",\r\n\t\tdata,\r\n\t\tbtn,\r\n\t\tstatus,\r\n\t\tstrings.MSG_LOGIN_DRIVER_SUCCESS,\r\n\t\tstrings.MSG_LOGIN_DRIVER_ERROR,\r\n\t\tfunction () {\r\n\t\t\ttrkData.validation.currentDriver.resetForm();\r\n\t\t\ttrkData.validation.currentDriver.currentForm.reset();\r\n\r\n\t\t\tloadAssetDrivers(asset);\r\n\t\t}\r\n\t);\r\n\r\n\t//var btn = $j('#LoginDriver')[0];\r\n\r\n\t// btn.disabled = true;\r\n\t//toggleLoadingMessage(true, 'asset-driver-login');\r\n\t//$j.ajax({\r\n\t// type: 'POST',\r\n\t// url: wrapUrl('/services/GPSService.asmx/AssetDriverLogin'),\r\n\t// data: JSON.stringify(data),\r\n\t// contentType: 'application/json; charset=utf-8',\r\n\t// dataType: 'json',\r\n\t// success: function (msg) {\r\n\t// btn.disabled = false;\r\n\t// if (msg.d) {\r\n\t// var result = msg.d;\r\n\t// if (result.Success == true) {\r\n\t// loadAssetDrivers(asset);\r\n\t// } else {\r\n\t// handleWebServiceError(strings.MSG_LOGIN_DRIVER_ERROR);\r\n\t// }\r\n\t// }\r\n\t// toggleLoadingMessage(false, 'asset-driver-login');\r\n\t// },\r\n\t// error: function (xhr, status, error) {\r\n\t// handleWebServiceError(strings.MSG_LOGIN_DRIVER_ERROR);\r\n\t// toggleLoadingMessage(false, 'asset-driver-login');\r\n\t// btn.disabled = false;\r\n\t// }\r\n\t//});\r\n}\r\n\r\nexport function loadAssetDrivers(asset) {\r\n\tvar btn = $j(\"#RefreshAssetDrivers\");\r\n\tvar data = { assetId: asset.Id };\r\n\tbtn.prop(\"disabled\", true);\r\n\ttoggleLoadingMessage(true, \"asset-drivers\");\r\n\t$j.ajax({\r\n\t\ttype: \"POST\",\r\n\t\turl: wrapUrl(\"/services/GPSService.asmx/GetAssetDriverInformation\"),\r\n\t\tdata: JSON.stringify(data),\r\n\t\tcontentType: \"application/json; charset=utf-8\",\r\n\t\tdataType: \"json\",\r\n\t\tsuccess: function (msg) {\r\n\t\t\tbtn.prop(\"disabled\", false);\r\n\t\t\tif (msg.d) {\r\n\t\t\t\tvar result = msg.d;\r\n\t\t\t\tif (result.Success == true) {\r\n\t\t\t\t\tvar statuses = $j(\"#AssetStatuses\").empty();\r\n\t\t\t\t\tstatuses.append($j(\"\", // ack button\r\n\t\t\tasset.Id,\r\n\t\t\titem.Type,\r\n\t\t\titem.Alert.Color,\r\n\t\t];\r\n\t} else {\r\n\t\tvar item = mapAssetEventToListing(item);\r\n\t\tif (item === null) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\titemData = [\r\n\t\t\tasset != null ? asset.Name : \"\",\r\n\t\t\titem.icon + ' ' + item.name + \"\",\r\n\t\t\t\"\", //item.position,\r\n\t\t\titem.details,\r\n\t\t\titem.time,\r\n\t\t\tasset.Id,\r\n\t\t\titem.type,\r\n\t\t\titem.isAccurate,\r\n\t\t\titem.id,\r\n\t\t];\r\n\t}\r\n\treturn itemData;\r\n}\r\n\r\nfunction updateAssetEventNotificationTime(event) {\r\n\tif (EVENTS_ALERT.indexOf(event.Type) !== -1 || EVENTS_EMERGENCY.indexOf(event.Type) !== -1) {\r\n\t\tupdateAssetNotificationTime(event.AssetId, \"alerts\", event.Epoch);\r\n\t} else if (EVENTS_STATUS.indexOf(event.Type) !== -1) {\r\n\t\tupdateAssetNotificationTime(event.AssetId, \"status\", event.Epoch);\r\n\t} else if (EVENTS_TEXT_MESSAGE.indexOf(event.Type) !== -1) {\r\n\t\t// messages handled by .messagesByAssetId\r\n\t} else {\r\n\t\tupdateAssetNotificationTime(event.AssetId, \"events\", event.Epoch);\r\n\t}\r\n}\r\n\r\nfunction mapAssetEventToListing(item) {\r\n\t// unused\r\n\t// TODO move this out of rendering\r\n\tif (options.hideAlertTriggeredEvents || item.Hide || user.isAnonymous) {\r\n\t\tif (EVENTS_ALERT.indexOf(item.Type) !== -1) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\tif (options.hideEmergencyEvents || user.isAnonymous) {\r\n\t\tif (EVENTS_EMERGENCY.indexOf(item.Type) !== -1) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\r\n\tif (item.Type === 268) {\r\n\t\t// SL Summary\r\n\t\treturn null;\r\n\t}\r\n\r\n\tvar asset = findAssetById(item.AssetId);\r\n\tvar svgIcon = createIconForAssetEvent(asset, item.Type);\r\n\tvar eventIcon =\r\n\t\t'
      ';\r\n\tvar details = item.Details != null ? htmlEscape(item.Details).replace(/\\r?\\n/g, \"
      \") : \"\";\r\n\tswitch (item.Type) {\r\n\t\tcase 127: // garmin form submitted\r\n\t\t\tif (item.Details != null) {\r\n\t\t\t\tvar formDetails = item.Details.split(\"|\");\r\n\t\t\t\tvar formId = formDetails[0];\r\n\t\t\t\tvar formName = strings.VIEW;\r\n\t\t\t\tif (formDetails.length > 1) {\r\n\t\t\t\t\tformName = formDetails[1];\r\n\t\t\t\t}\r\n\t\t\t\tdetails =\r\n\t\t\t\t\t'\";\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\tcase 270: // beacon read\r\n\t\t\tif (item.Details != null) {\r\n\t\t\t\tdetails = \"\";\r\n\t\t\t\tvar beacons = item.Details.split(\"\\n\");\r\n\t\t\t\tfor (var k = 0; k < beacons.length; k++) {\r\n\t\t\t\t\tvar beacon = beacons[k].split(\"|\");\r\n\t\t\t\t\tif (beacon.length > 1) {\r\n\t\t\t\t\t\tvar beaconUniqueKey = beacon[0];\r\n\t\t\t\t\t\tvar beaconRSSI = beacon[1];\r\n\t\t\t\t\t\tvar beaconTxPower = beacon[2];\r\n\t\t\t\t\t\tif (beaconUniqueKey == null) {\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tvar beaconPlace = findPlaceByUniqueKey(beaconUniqueKey);\r\n\t\t\t\t\t\tif (beaconPlace != null) {\r\n\t\t\t\t\t\t\tdetails +=\r\n\t\t\t\t\t\t\t\t'
      ' +\r\n\t\t\t\t\t\t\t\tbeaconPlace.Name +\r\n\t\t\t\t\t\t\t\t\" @ \" +\r\n\t\t\t\t\t\t\t\tbeaconRSSI +\r\n\t\t\t\t\t\t\t\t\" RSSI\";\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tdetails += beaconUniqueKey + \" @ \" + beaconRSSI + \" RSSI\";\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tif (beaconTxPower != \"\") {\r\n\t\t\t\t\t\t\tdetails += \", \" + beaconTxPower + \" Tx\";\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tdetails += \"
      \";\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\tcase 272:\r\n\t\tcase 273:\r\n\t\tcase 274:\r\n\t\t\t// driver fatigue\r\n\t\t\tif (item.Details != null) {\r\n\t\t\t\tdetails =\r\n\t\t\t\t\t'\";\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\tcase 275:\r\n\t\t\tif (item.Details != null) {\r\n\t\t\t\tvar parts = item.Details.split(\"|||\");\r\n\t\t\t\tdetails =\r\n\t\t\t\t\t'\";\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t}\r\n\r\n\tvar fullName = item.TypeName;\r\n\tif (item.Alert !== undefined && item.Alert !== null) {\r\n\t\tvar alertName = item.Alert.Type;\r\n\t\tif (item.Alert.Name !== null) {\r\n\t\t\talertName = item.Alert.Name + \" [ \" + item.Alert.Type + \" ]\";\r\n\t\t}\r\n\t\tfullName += \", \" + alertName;\r\n\t}\r\n\r\n\treturn {\r\n\t\tid: item.Id,\r\n\t\ticon: eventIcon,\r\n\t\tsvgIcon: svgIcon,\r\n\t\tname: item.TypeName,\r\n\t\tfullName: fullName,\r\n\t\tposition: mapPositionForEvent(item, asset),\r\n\t\tdetails: details,\r\n\t\ttime: item.Time,\r\n\t\tepoch: item.Epoch,\r\n\t\ttype: item.Type,\r\n\t\tisAccurate: item.IsAcc,\r\n\t\talert: item.Alert,\r\n\t\tassetId: item.AssetId,\r\n\t};\r\n}\r\n","import trkData from \"./data.js\";\r\nimport options from \"./options.js\";\r\nimport user from \"./user.js\";\r\n\r\nimport { EVENTS_STATUS, EVENTS_TEXT_MESSAGE, EVENTS_EMERGENCY, EVENTS_ALERT } from \"./asset-events.js\";\r\n\r\nexport function getDisplayFilterForEventType(type) {\r\n\tswitch (type) {\r\n\t\tcase \"activity\":\r\n\t\t\treturn function (item) {\r\n\t\t\t\t// basically a duplicate of the below filters, but I don't see a way around it\r\n\t\t\t\tif (item.Chat !== undefined) {\r\n\t\t\t\t\tvar isChatDisabled = user.isAnonymous && !options.allowAnonymousMessaging;\r\n\t\t\t\t\treturn !isChatDisabled && item.Chat !== undefined;\r\n\t\t\t\t} else if (item.Event !== undefined) {\r\n\t\t\t\t\tif (item.Event.Hide) {\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tvar hideAlertTriggeredEvents = options.hideAlertTriggeredEvents || user.isAnonymous;\r\n\t\t\t\t\tvar hideEmergencyEvents = options.hideEmergencyEvents || user.isAnonymous;\r\n\t\t\t\t\tvar isAlertEvent = EVENTS_ALERT.indexOf(item.Event.Type) !== -1;\r\n\t\t\t\t\tif (isAlertEvent && hideAlertTriggeredEvents) {\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tvar isEmergencyEvent = EVENTS_EMERGENCY.indexOf(item.Event.Type) !== -1;\r\n\t\t\t\t\tif (isEmergencyEvent && hideEmergencyEvents) {\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (item.Event.Type === 268) {\r\n\t\t\t\t\t\t// SL Summary\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (EVENTS_TEXT_MESSAGE.indexOf(item.Event.Type) !== -1) {\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn true;\r\n\t\t\t};\r\n\t\tcase \"chat\":\r\n\t\t\treturn function (item) {\r\n\t\t\t\tvar isChatDisabled = user.isAnonymous && !options.allowAnonymousMessaging;\r\n\t\t\t\treturn !isChatDisabled && item.Chat !== undefined;\r\n\t\t\t};\r\n\t\tcase \"messages\":\r\n\t\t\treturn function (item) {\r\n\t\t\t\treturn item.Chat === undefined && item.Message !== undefined;\r\n\t\t\t};\r\n\t\tcase \"positions\":\r\n\t\t\treturn function (item) {\r\n\t\t\t\treturn item.Position !== undefined;\r\n\t\t\t};\r\n\t\tcase \"status\":\r\n\t\t\treturn function (item) {\r\n\t\t\t\treturn !item.Event.Hide && EVENTS_STATUS.indexOf(item.Event.Type) !== -1;\r\n\t\t\t};\r\n\t\tcase \"alerts\":\r\n\t\t\treturn function (item) {\r\n\t\t\t\tif (item.Event.Hide) {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t\tvar hideAlertTriggeredEvents = options.hideAlertTriggeredEvents || user.isAnonymous;\r\n\t\t\t\tvar hideEmergencyEvents = options.hideEmergencyEvents || user.isAnonymous;\r\n\t\t\t\tvar isAlertEvent = EVENTS_ALERT.indexOf(item.Event.Type) !== -1;\r\n\t\t\t\tif (isAlertEvent && hideAlertTriggeredEvents) {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t\tvar isEmergencyEvent = EVENTS_EMERGENCY.indexOf(item.Event.Type) !== -1;\r\n\t\t\t\tif (isEmergencyEvent && hideEmergencyEvents) {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t\treturn isAlertEvent || isEmergencyEvent;\r\n\t\t\t};\r\n\t\tcase \"events\":\r\n\t\tdefault:\r\n\t\t\treturn function (item) {\r\n\t\t\t\treturn (\r\n\t\t\t\t\t!item.Event.Hide &&\r\n\t\t\t\t\titem.Event.Type !== 268 && // SL Summary\r\n\t\t\t\t\tEVENTS_STATUS.indexOf(item.Event.Type) === -1 &&\r\n\t\t\t\t\tEVENTS_ALERT.indexOf(item.Event.Type) === -1 &&\r\n\t\t\t\t\tEVENTS_EMERGENCY.indexOf(item.Event.Type) === -1 &&\r\n\t\t\t\t\tEVENTS_TEXT_MESSAGE.indexOf(item.Event.Type) === -1\r\n\t\t\t\t); // because it will be a chat item?\r\n\t\t\t};\r\n\t}\r\n}\r\n","import strings from \"./strings.js\";\r\nimport trkData from \"./data.js\";\r\nimport user from \"./user.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport { findGroupById, createAssetNode } from \"./asset-group.js\";\r\nimport { isItemIncluded } from \"./polyfills.js\";\r\nimport { toggleGroupExpandedIcon } from \"./asset-group.js\";\r\nimport { sortItemsByMode } from \"./item-sorting.js\";\r\nimport { updateGroupVisibilityStatus, createGroupNode } from \"./asset-group.js\";\r\nimport { findAssetById } from \"./assets.js\";\r\nimport { createFenceNode } from \"./fence.js\";\r\nimport { saveItemCustomSortOrder } from \"./item-sorting.js\";\r\nimport { createSharedViewNode } from \"./shared-view.js\";\r\nimport { createPlaceNode } from \"./place.js\";\r\nimport { createTripNode } from \"./trips.js\";\r\nimport { resizeApp } from \"./window-layout.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport $j from \"jquery\";\r\nimport _ from \"lodash\";\r\n\r\nfunction populateSidePanelItems(data) {\r\n\tvar itemNodes = {};\r\n\tvar groupNodes = {};\r\n\r\n\tvar groupItems = {}; // for all groups: groupId -> { groups: {*full child group object*}[], assets: {*full asset object*}[] }\r\n\r\n\t// initialize groups\r\n\t_.each(data.groups, function (group) {\r\n\t\tgroupItems[group.Id] = { groups: [], items: [] };\r\n\t});\r\n\r\n\t// assign subgroups to groups\r\n\t_.each(data.groups, function (group) {\r\n\t\tif (group.ParentGroupId !== null && groupItems[group.ParentGroupId] !== undefined) {\r\n\t\t\tgroupItems[group.ParentGroupId].groups.push(group);\r\n\t\t\tvar parentGroup = data.groups.find(g => g.Id === group.ParentGroupId);\r\n\t\t\tif (parentGroup !== null) {\r\n\t\t\t\tparentGroup.GroupIds.push(group.Id);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// render baseline group for dom\r\n\t\tgroupNodes[group.Id] = createGroupNode(group);\r\n\t});\r\n\r\n\t// initialize group nodes cache and set colors for stylesheet\r\n\t_.each(groupNodes, function (group, key, list) {\r\n\t\tdomNodes.groups[key] = groupNodes[key];\r\n\t\tvar actualGroup = data.groups.find(g => g.Id === key);\r\n\t\tif (actualGroup !== null) {\r\n\t\t\tdomNodes.groupColors[key] = actualGroup.Color;\r\n\t\t}\r\n\t});\r\n\r\n\tgroupItems[\"all-\" + data.itemType].items = sortItemsByMode(data.itemType, data.items, \"all-\" + data.itemType, data.itemType);\r\n\tvar primaryGroups = sortItemsByMode(\r\n\t\tdata.itemType,\r\n\t\t_.filter(data.groups, function (group) {\r\n\t\t\treturn group.ParentGroupId === null && !group.IsDefault;\r\n\t\t}),\r\n\t\tdata.itemType + \"-root\",\r\n\t\t\"groups\"\r\n\t);\r\n\tvar primaryGroups = _.concat(\r\n\t\t_.filter(data.groups, function (group) {\r\n\t\t\treturn group.IsDefault;\r\n\t\t}),\r\n\t\tprimaryGroups\r\n\t)\r\n\r\n\t// assign items to groups\r\n\t_.each(data.items, function (item, index, list) {\r\n\t\t_.each(item.GroupIds, function (groupId, ind, li) {\r\n\t\t\tif (groupItems[groupId] !== undefined) {\r\n\t\t\t\tgroupItems[groupId].items.push(item);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// render baseline item for dom\r\n\t\titemNodes[item.Id] = data.createItemNode(item);\r\n\t});\r\n\r\n\t// sort items and subgroups within groups\r\n\t_.each(groupItems, function (value, key, list) {\r\n\t\tlist[key].items = sortItemsByMode(data.itemType, value.items, key, data.itemType);\r\n\t\tlist[key].groups = sortItemsByMode(data.itemType, value.groups, key, \"groups\");\r\n\t});\r\n\r\n\t// console.log(groupItems);\r\n\r\n\t_.each(groupItems, function (group, key, list) {\r\n\t\tvar groupContentsNode = groupNodes[key].querySelector(\".group-contents\");\r\n\t\tvar groupContentsItems = groupContentsNode.querySelector(\"ul.group-list-list\");\r\n\t\tvar groupData = groupItems[key];\r\n\r\n\t\t// add subgroup nodes to group dom\r\n\t\t_.each(groupData.groups, function (subGroup, index, list) {\r\n\t\t\t// groups can only be assigned to one parent group, so do not clone their nodes\r\n\t\t\tgroupContentsItems.appendChild(groupNodes[subGroup.Id]);\r\n\t\t});\r\n\r\n\t\t// add items nodes to group dom\r\n\t\t_.each(groupData.items, function (item, index, list) {\r\n\t\t\t// items can be added to multiple groups, so they must be cloned\r\n\t\t\tvar groupItem = itemNodes[item.Id].cloneNode(true);\r\n\r\n\t\t\tgroupContentsItems.appendChild(groupItem);\r\n\t\t\tif (data.domNodesItems[item.Id] === undefined) {\r\n\t\t\t\tdata.domNodesItems[item.Id] = [];\r\n\t\t\t}\r\n\t\t\tdata.domNodesItems[item.Id].push(groupItem);\r\n\t\t});\r\n\r\n\t\t// cache group content nodes so they can be detached/reattached to the dom\r\n\t\t// based on whether the group is expanded/visible\r\n\t\tdomNodes.groupContents[key] = groupContentsNode;\r\n\r\n\t\tif (!isItemIncluded(user.displayPreferences.expandedGroups, key)) {\r\n\t\t\t// only render group contents when the group is visible (expanded)\r\n\t\t\tgroupContentsNode.parentNode.removeChild(groupContentsNode);\r\n\t\t\ttoggleGroupExpandedIcon(key, false);\r\n\t\t} else {\r\n\t\t\ttoggleGroupExpandedIcon(key, true);\r\n\t\t}\r\n\t});\r\n\r\n\t// toggle active groups within fragment\r\n\t// by default groups are inactive unless they contain at least one active asset\r\n\t// this isn't done on creation as it would be pretty tricky and not worth the effort\r\n\t_.each(groupItems, function (group, key, list) {\r\n\t\tupdateGroupVisibilityStatus(key, data.itemType);\r\n\t});\r\n\r\n\tvar assetsGroupsFragment = document.createDocumentFragment();\r\n\tassetsGroupsFragment.appendChild(groupNodes[\"all-\" + data.itemType]);\r\n\t_.each(primaryGroups, function (value, key, list) {\r\n\t\tassetsGroupsFragment.appendChild(groupNodes[value.Id]);\r\n\t});\r\n\r\n\tif (data.items.length === 0) {\r\n\t\tdocument.getElementById(\"no-\" + data.itemType).classList.add(\"is-visible\");\r\n\t\tdocument.getElementById(data.itemType + \"-all\").classList.remove(\"is-visible\");\r\n\t\tdocument.getElementById(\"filter-\" + data.itemType).querySelector(\".filter-box\").classList.remove(\"is-visible\");\r\n\t} else {\r\n\t\tdocument.getElementById(\"no-\" + data.itemType).classList.remove(\"is-visible\");\r\n\t\tdocument.getElementById(data.itemType + \"-all\").classList.add(\"is-visible\");\r\n\t\tdocument.getElementById(\"filter-\" + data.itemType).querySelector(\".filter-box\").classList.add(\"is-visible\");\r\n\t}\r\n\r\n\tvar itemGroupsContainer = document.getElementById(data.itemType + \"-all\");\r\n\titemGroupsContainer.appendChild(assetsGroupsFragment);\r\n\t// root item group sorting\r\n\t$(itemGroupsContainer).sortable({\r\n\t\taxis: \"y\",\r\n\t\titems: \"> li.group\",\r\n\t\thandle: \"> .group-header .group-drag\",\r\n\t\tupdate: function (event, ui) {\r\n\t\t\tsaveItemCustomSortOrder();\r\n\t\t},\r\n\t});\r\n\r\n\t// only initialize the sorting when the group contents are rendered\r\n\t_.each(domNodes.groupContents, function (groupContentsNode, key, list) {\r\n\t\tif (groupContentsNode.parentNode != null)\r\n\t\t{\r\n\t\t\tinitializeOrRefreshGroupSortable(key);\r\n\t\t}\r\n\t});\r\n}\r\n\r\nexport function initializeOrRefreshGroupSortable(groupId) {\r\n\t// refresh the sortable so it can be aware of new items being added to the container\r\n\tif (domNodes.groupContents[groupId] == null) {\r\n\t\tconsole.log(\"unknown group contents for id\", groupId);\r\n\t\treturn;\r\n\t}\r\n\tvar groupContentsNode = domNodes.groupContents[groupId];\r\n\tvar groupContentsItems = groupContentsNode.querySelector(\"ul.group-list-list\");\r\n\tif (groupContentsNode.classList.contains('ui-sortable')) {\r\n\t\t// already initialized, just refresh for newly added item(s)\r\n\t\t// subgroup ordering\r\n\t\t$(groupContentsItems).sortable(\"refresh\");\r\n\t\t// group items ordering\r\n\t\t$(groupContentsNode).sortable(\"refresh\");\r\n\t} else {\r\n\t\t// subgroup ordering\r\n\t\t$(groupContentsItems).sortable({\r\n\t\t\taxis: 'y',\r\n\t\t\titems: '> li.group',\r\n\t\t\thandle: '> .group-header .group-drag',\r\n\t\t\tupdate: function (event, ui) {\r\n\t\t\t\tsaveItemCustomSortOrder();\r\n\t\t\t},\r\n\t\t});\r\n\r\n\t\t// group items ordering\r\n\t\t$(groupContentsNode).sortable({\r\n\t\t\taxis: 'y',\r\n\t\t\titems: '> .group-list-list > li.group-item',\r\n\t\t\thandle: '.item-drag',\r\n\t\t\tupdate: function (event, ui) {\r\n\t\t\t\tsaveItemCustomSortOrder();\r\n\t\t\t},\r\n\t\t});\r\n\r\n\t\tdomNodes.groups[groupId].setAttribute(\"data-sort-initialized\", true);\r\n\t}\r\n}\r\n\r\nfunction populateSidePanelJourneys() {\r\n\t// journeys - just like asset groups\r\n\tif (trkData.journeys.length === 0) {\r\n\t\t// no journeys, add to journeys-all?\r\n\t\tvar noExtrasNode = document.createElement(\"li\");\r\n\t\tnoExtrasNode.className = \"toggle-content no-items is-visible\";\r\n\t\tnoExtrasNode.setAttribute(\"id\", \"no-journeys\");\r\n\t\tvar noExtrasText = document.createElement(\"span\");\r\n\t\tnoExtrasText.textContent = strings.NO_JOURNEYS;\r\n\t\tnoExtrasNode.appendChild(noExtrasText);\r\n\t\tdocument.getElementById(\"journeys-all\").appendChild(noExtrasNode);\r\n\t\t//document.getElementById('filter-journeys').querySelector('.filter-box').classList.remove('is-visible');\r\n\t}\r\n\t_.each(trkData.journeys, function (item) {\r\n\t\tvar journeyAsset = findAssetById(item.AssetId);\r\n\t\tif (journeyAsset === null) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tvar groupId = \"journey-\" + item.Id;\r\n\t\tvar extraGroupNode = createGroupNode({ Id: groupId, Name: item.Name, Color: item.Color, Type: \"journeys\" });\r\n\t\tdomNodes.groups[groupId] = extraGroupNode;\r\n\r\n\t\tvar extraGroupContentsNode = extraGroupNode.querySelector(\".group-contents\");\r\n\t\tvar extraGroupContentsItems = extraGroupContentsNode.querySelector(\"ul.group-list-list\");\r\n\t\tvar noExtrasNode = document.createElement(\"li\");\r\n\t\tnoExtrasNode.className = \"toggle-content no-items\";\r\n\t\tif (item.Trips.length === 0) {\r\n\t\t\tnoExtrasNode.className += \" is-visible\";\r\n\t\t\t//document.getElementById('filter-journeys').querySelector('.filter-box').classList.remove('is-visible');\r\n\t\t}\r\n\t\tnoExtrasNode.setAttribute(\"id\", \"no-\" + groupId);\r\n\t\tvar noExtrasText = document.createElement(\"span\");\r\n\t\tnoExtrasText.textContent = strings.NO_TRIPS;\r\n\t\tnoExtrasNode.appendChild(noExtrasText);\r\n\t\textraGroupContentsItems.appendChild(noExtrasNode);\r\n\r\n\t\tvar journey = item;\r\n\t\t_.each(item.Trips, function (item, index, list) {\r\n\t\t\tvar extraNode = createTripNode(journey, item, journeyAsset);\r\n\t\t\tdomNodes.trips[item.Id] = extraNode;\r\n\t\t\textraGroupContentsItems.appendChild(extraNode);\r\n\t\t});\r\n\r\n\t\tvar extraAllItems = extraGroupNode.getElementsByClassName(\"journeys-item\").length;\r\n\t\tvar extraActiveItems = extraGroupNode.getElementsByClassName(\"journeys-item active\").length;\r\n\t\tif (extraActiveItems > 0) {\r\n\t\t\tvar extraVisibility = extraGroupNode.querySelector(\".showhide\");\r\n\r\n\t\t\tif (extraAllItems !== extraActiveItems) {\r\n\t\t\t\textraVisibility\r\n\t\t\t\t\t.querySelector(\"use\")\r\n\t\t\t\t\t.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", \"/content/svg/tracking.svg?v=15#indeterminate\");\r\n\t\t\t\textraVisibility.classList.add(\"indeterminate\");\r\n\t\t\t} else {\r\n\t\t\t\textraVisibility\r\n\t\t\t\t\t.querySelector(\"use\")\r\n\t\t\t\t\t.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", \"/content/svg/tracking.svg?v=15#visible\");\r\n\t\t\t\textraVisibility.classList.add(\"active\");\r\n\t\t\t}\r\n\t\t\textraGroupNode.classList.add(\"active\");\r\n\t\t}\r\n\t\tdomNodes.groupContents[groupId] = extraGroupContentsNode;\r\n\t\tif (item.Color !== undefined && item.Color !== null) {\r\n\t\t\tdomNodes.groupColors[groupId] = item.Color;\r\n\t\t}\r\n\t\tif (!isItemIncluded(user.displayPreferences.expandedGroups, groupId)) {\r\n\t\t\t// only render group contents when the group is visible (expanded)\r\n\t\t\textraGroupContentsNode.parentNode.removeChild(extraGroupContentsNode);\r\n\t\t\ttoggleGroupExpandedIcon(groupId, false);\r\n\t\t} else {\r\n\t\t\ttoggleGroupExpandedIcon(groupId, true);\r\n\t\t}\r\n\r\n\t\tvar allJourneys = document.getElementById(\"journeys-all\");\r\n\t\tallJourneys.appendChild(extraGroupNode);\r\n\r\n\t\tupdateGroupVisibilityStatus(groupId);\r\n\t});\r\n}\r\n\r\nfunction populateSidePanelExtraGroups() {\r\n\tvar extraGroups = [{\r\n\t\ttype: \"shared-views\",\r\n\t\tcreateNodeFunction: createSharedViewNode,\r\n\t\tname: strings.ALL_SHARED_VIEWS,\r\n\t\tcolor: null,\r\n\t\temptyText: strings.NO_SHARED_VIEWS,\r\n\t\tnoItemsText: strings.NO_SHARED_VIEWS_HINT,\r\n\t\titems: sortItemsByMode(\"shared-views\", trkData.sharedViews, \"all-shared-views\", \"shared-views\"),\r\n\t\tnodeList: domNodes.sharedViews,\r\n\t}];\r\n\r\n\t_.each(extraGroups, function (group, index, list) {\r\n\t\tvar groupId = \"all-\" + group.type;\r\n\t\tvar extraGroupNode = createGroupNode({ Id: groupId, Name: group.name, Color: group.color, Type: group.type });\r\n\t\tdomNodes.groups[groupId] = extraGroupNode;\r\n\t\tvar extraGroupContentsNode = extraGroupNode.querySelector(\".group-contents\");\r\n\t\tvar extraGroupContentsItems = extraGroupContentsNode.querySelector(\"ul.group-list-list\");\r\n\t\tvar noExtrasNode = document.createElement(\"li\");\r\n\t\tnoExtrasNode.className = \"toggle-content no-items\";\r\n\t\tif (group.items.length === 0) {\r\n\t\t\tnoExtrasNode.className += \" is-visible\";\r\n\t\t\tdocument\r\n\t\t\t\t.getElementById(\"filter-\" + group.type)\r\n\t\t\t\t.querySelector(\".filter-box\")\r\n\t\t\t\t.classList.remove(\"is-visible\");\r\n\t\t} else {\r\n\t\t\tdocument\r\n\t\t\t\t.getElementById(\"filter-\" + group.type)\r\n\t\t\t\t.querySelector(\".filter-box\")\r\n\t\t\t\t.classList.add(\"is-visible\");\r\n\t\t}\r\n\t\tnoExtrasNode.setAttribute(\"id\", \"no-\" + groupId);\r\n\r\n\t\tif (group.type == \"shared-views\" && group.items.length === 0) {\r\n\t\t\tdocument.getElementById(\"no-shared-views\").classList.add(\"is-visible\");\r\n\t\t\tdocument.getElementById(\"shared-views-all\").classList.remove(\"is-visible\");\r\n\t\t\t//document.getElementById('filter-shared-views').classList.remove('is-visible');\r\n\t\t}\r\n\r\n\t\tvar noExtrasText = document.createElement(\"span\");\r\n\t\tnoExtrasText.textContent = group.emptyText;\r\n\t\tnoExtrasNode.appendChild(noExtrasText);\r\n\t\textraGroupContentsItems.appendChild(noExtrasNode);\r\n\r\n\t\t_.each(group.items, function (item, index, list) {\r\n\t\t\tvar extraNode = group.createNodeFunction(item);\r\n\t\t\tgroup.nodeList[item.Id] = extraNode;\r\n\t\t\textraGroupContentsItems.appendChild(extraNode);\r\n\t\t});\r\n\r\n\t\tvar extraAllItems = extraGroupNode.getElementsByClassName(group.type + \"-item\").length;\r\n\t\tvar extraActiveItems = extraGroupNode.getElementsByClassName(group.type + \"-item active\").length;\r\n\t\tif (extraActiveItems > 0) {\r\n\t\t\tvar extraVisibility = extraGroupNode.querySelector(\".showhide\");\r\n\r\n\t\t\tif (extraAllItems !== extraActiveItems) {\r\n\t\t\t\textraVisibility\r\n\t\t\t\t\t.querySelector(\"use\")\r\n\t\t\t\t\t.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", \"/content/svg/tracking.svg?v=15#indeterminate\");\r\n\t\t\t\textraVisibility.classList.add(\"indeterminate\");\r\n\t\t\t} else {\r\n\t\t\t\textraVisibility\r\n\t\t\t\t\t.querySelector(\"use\")\r\n\t\t\t\t\t.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", \"/content/svg/tracking.svg?v=15#visible\");\r\n\t\t\t\textraVisibility.classList.add(\"active\");\r\n\t\t\t}\r\n\t\t\textraGroupNode.classList.add(\"active\");\r\n\t\t}\r\n\t\tdomNodes.groupContents[groupId] = extraGroupContentsNode;\r\n\t\tif (group.color !== undefined && group.color !== null) {\r\n\t\t\tdomNodes.groupColors[groupId] = group.color;\r\n\t\t}\r\n\t\tif (!isItemIncluded(user.displayPreferences.expandedGroups, \"all-\" + group.type)) {\r\n\t\t\t// only render group contents when the group is visible (expanded)\r\n\t\t\textraGroupContentsNode.parentNode.removeChild(extraGroupContentsNode);\r\n\t\t\ttoggleGroupExpandedIcon(groupId, false);\r\n\t\t} else {\r\n\t\t\ttoggleGroupExpandedIcon(groupId, true);\r\n\t\t}\r\n\r\n\t\t// insert into separate panel/container\r\n\t\tvar container = document.getElementById(group.type + \"-all\");\r\n\t\tcontainer.appendChild(extraGroupNode);\r\n\r\n\t\tupdateGroupVisibilityStatus(groupId);\r\n\r\n\t\t$(extraGroupContentsNode).sortable({\r\n\t\t\taxis: \"y\",\r\n\t\t\titems: \"> .group-list-list > li.group-item\",\r\n\t\t\thandle: \".item-drag\",\r\n\t\t\tupdate: function (event, ui) {\r\n\t\t\t\tsaveItemCustomSortOrder();\r\n\t\t\t},\r\n\t\t});\r\n\t});\r\n}\r\n\r\nexport function populateSidePanel() {\r\n\tpopulateSidePanelItems({\r\n\t\titemType: \"assets\",\r\n\t\tgroups: trkData.groups,\r\n\t\titems: trkData.assets,\r\n\t\tcreateItemNode: createAssetNode,\r\n\t\tdomNodesItems: domNodes.assets\r\n\t});\r\n\tpopulateSidePanelItems({\r\n\t\titemType: \"places\",\r\n\t\tgroups: trkData.placeGroups,\r\n\t\titems: trkData.places,\r\n\t\tcreateItemNode: createPlaceNode,\r\n\t\tdomNodesItems: domNodes.places\r\n\t});\r\n\tpopulateSidePanelItems({\r\n\t\titemType: \"fences\",\r\n\t\tgroups: trkData.fenceGroups,\r\n\t\titems: trkData.fences,\r\n\t\tcreateItemNode: createFenceNode,\r\n\t\tdomNodesItems: domNodes.fences\r\n\t});\r\n\tpopulateSidePanelJourneys();\r\n\tpopulateSidePanelExtraGroups();\r\n}\r\n\r\nexport function hideSidePanel() {\r\n\t$j(\"#topbar-left\").css(\"left\", \"-25em\");\r\n\t$j(\"#topbar-right\").css(\"margin-left\", \"0\");\r\n\t$j(\"#panel\").hide();\r\n\t$j(\"#map\").css(\"left\", 0).css(\"margin-right\", 0);\r\n\t$j(\"#map_tools,#map_tools_bottom,#map_panels\").css(\"margin-left\", 0);\r\n\t$j(\"#panel-expand\").show();\r\n\t$j(\"#ds-v .shade, #event-panel,#event-panel-container\").css(\"left\", \"0\");\r\n\t$j(\"#panel-contract\").hide();\r\n\tresizeApp(true);\r\n}\r\n\r\nexport function showSidePanel() {\r\n\t$j(\"#topbar-left\").css(\"left\", \"0\");\r\n\t$j(\"#topbar-right\").css(\"margin-left\", \"25em\");\r\n\t$j(\"#panel\").show();\r\n\t$j(\"#map\").css(\"left\", \"25em\").css(\"margin-right\", \"25em\");\r\n\t$j(\"#map_tools,#map_tools_bottom,#map_panels\").css(\"margin-left\", \"25em\");\r\n\t$j(\"#panel-contract\").show();\r\n\t$j(\"#ds-v .shade, #event-panel,#event-panel-container\").css(\"left\", \"25em\");\r\n\t$j(\"#panel-expand\").hide();\r\n\tresizeApp(true);\r\n}\r\n","import strings from \"./strings.js\";\r\nimport trkData from \"./data.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport state from \"./state.js\";\r\nimport { findAssetById } from \"./assets.js\";\r\nimport { getDisplayFilterForEventType } from \"./display-filter.js\";\r\nimport user, { displayPreferencesAdd, displayPreferencesRemove, loadDisplayPreferences } from \"./user.js\";\r\nimport log from \"./log.js\";\r\nimport { isItemIncluded } from \"./polyfills.js\";\r\nimport { openDialogPanel } from \"./panel-nav.js\";\r\nimport { mapModes } from \"./const.js\";\r\nimport { createListing, defaultListItemSort } from \"./item-listing.js\";\r\nimport { createMarkerPath } from \"./marker-path.js\";\r\nimport options from \"./options.js\";\r\nimport templates from \"./templates.js\";\r\nimport { populateCheckboxList } from \"./dom-util.js\";\r\nimport { changePrimaryButtonLabel } from \"./modal.js\";\r\nimport { populateGroupList } from \"./group-list.js\";\r\nimport { sortItemsByMode, sortByName } from \"./item-sorting.js\";\r\nimport { openAssetGroupSettingsPanel } from \"./panel-settings.js\";\r\nimport { initializeOrRefreshGroupSortable } from \"./panel-side.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\nimport { toggleLoadingMessage } from \"./ajax.js\";\r\nimport { handleWebServiceError } from \"./ajax.js\";\r\nimport { getDbg } from \"./log.js\";\r\nimport { getBackgroundColorAsHex, convertHexToSortable, convertNamedColorToHex } from \"./color.js\";\r\nimport { getSharedViewLink } from \"./shared-view.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport $j from \"jquery\";\r\nimport _ from \"lodash\";\r\n\r\n/*global JsSearch */\r\n// import JsSearch from '../js-search.js';\r\n\r\nexport function findGroupById(id, groupFor = \"assets\") {\r\n\tvar item = _.find(\r\n\t\tgroupFor === \"assets\" ? trkData.groups :\r\n\t\tgroupFor === \"places\" ? trkData.placeGroups :\r\n\t\tgroupFor === \"fences\" ? trkData.fenceGroups : {}\r\n\t, { Id: id });\r\n\treturn item === undefined ? null : item;\r\n}\r\n\r\nfunction getGroupStatus(groupId, groupFor) {\r\n\tvar result = {\r\n\t\titems: 0,\r\n\t\tactive: 0,\r\n\t\tassets: 0,\r\n\t\tassetIds: [],\r\n\t\tfences: 0,\r\n\t\tfenceIds: [],\r\n\t\tplaces: 0,\r\n\t\tplaceIds: [],\r\n\t\ttrips: 0,\r\n\t\ttripIds: [],\r\n\t\tsharedViews: 0,\r\n\t\tsharedViewIds: [],\r\n\t\tcountNumber: 0,\r\n\t};\r\n\r\n\tvar groupContents = domNodes.groupContents[groupId];\r\n\tif (groupContents === undefined) {\r\n\t\tconsole.trace(`Group ${groupId} not found`);\r\n\t\treturn result;\r\n\t}\r\n\r\n\tresult.items = groupContents.getElementsByClassName(\"group-item\").length;\r\n\tresult.active = groupContents.getElementsByClassName(\"group-item active\").length;\r\n\tvar assets = groupContents.getElementsByClassName(\"assets-item\");\r\n\t_.each(assets, function (elem) {\r\n\t\tresult.assetIds.push(parseInt(elem.getAttribute(\"data-asset-id\")));\r\n\t});\r\n\tresult.assets = assets.length;\r\n\tvar places = groupContents.getElementsByClassName(\"places-item\");\r\n\t_.each(places, function (elem) {\r\n\t\tresult.placeIds.push(parseInt(elem.getAttribute(\"data-place-id\")));\r\n\t});\r\n\tresult.places = places.length;\r\n\tvar fences = groupContents.getElementsByClassName(\"fences-item\");\r\n\t_.each(fences, function (elem) {\r\n\t\tresult.fenceIds.push(elem.getAttribute(\"data-fence-id\"));\r\n\t});\r\n\tresult.fences = fences.length;\r\n\tvar trips = groupContents.getElementsByClassName(\"journeys-item\");\r\n\t_.each(trips, function (elem) {\r\n\t\tresult.tripIds.push(parseInt(elem.getAttribute(\"data-trip-id\")));\r\n\t});\r\n\tresult.trips = trips.length;\r\n\tvar sharedViews = groupContents.getElementsByClassName(\"shared-views-item\");\r\n\t_.each(sharedViews, function (elem) {\r\n\t\tresult.sharedViewIds.push(parseInt(elem.getAttribute(\"data-shared-view-id\")));\r\n\t});\r\n\tresult.sharedViews = sharedViews.length;\r\n\r\n\tswitch (groupId) {\r\n\t\tcase \"all-shared-views\":\r\n\t\t\treturn {\r\n\t\t\t\titems: result.sharedViewIds.length,\r\n\t\t\t\tactive: result.sharedViewIds.length,\r\n\t\t\t\tcountNumber: result.sharedViewIds.length,\r\n\t\t\t};\r\n\t\tdefault:\r\n\t\t\tif (groupId.indexOf(\"journey-\") !== -1) {\r\n\t\t\t\tresult.countNumber = result.tripIds.length;\r\n\t\t\t\treturn result;\r\n\t\t\t}\r\n\t}\r\n\r\n\tvar group = findGroupById(groupId, groupFor);\r\n\r\n\tif (group !== null) {\r\n\t\tif (group.GroupIds !== undefined) {\r\n\t\t\t_.each(group.GroupIds, function (subGroupId) {\r\n\t\t\t\tvar groupCounts = getGroupStatus(subGroupId, groupFor);\r\n\t\t\t\tresult.items += groupCounts.items;\r\n\t\t\t\tresult.active += groupCounts.active;\r\n\t\t\t\tresult.assets += groupCounts.assets;\r\n\t\t\t\tresult.assetIds = _.union(result.assetIds, groupCounts.assetIds);\r\n\t\t\t\tresult.places += groupCounts.places;\r\n\t\t\t\tresult.placeIds = _.union(result.placeIds, groupCounts.placeIds);\r\n\t\t\t\tresult.fences += groupCounts.fences;\r\n\t\t\t\tresult.fenceIds = _.union(result.fenceIds, groupCounts.fenceIds);\r\n\t\t\t\tresult.trips += groupCounts.trips;\r\n\t\t\t\tresult.tripIds = _.union(result.tripIds, groupCounts.tripIds);\r\n\t\t\t\tresult.sharedViews += groupCounts.sharedViews;\r\n\t\t\t\tresult.sharedViewIds = _.union(result.sharedViewIds, groupCounts.sharedViewIds);\r\n\t\t\t});\r\n\t\t}\r\n\t\tif(groupFor === \"assets\") {\r\n\t\t\tgroup.AssetIds = _.map(result.assetIds, function (assetId) {\r\n\t\t\t\treturn parseInt(assetId);\r\n\t\t\t});\r\n\t\t}\r\n\t} else {\r\n\t\tconsole.trace(`Group ${groupId} for ${groupFor} not found`);\r\n\t}\r\n\r\n\t_.each(result.assetIds, function (id) {\r\n\t\tvar assetId = parseInt(id);\r\n\t\tvar asset = findAssetById(assetId);\r\n\t\tif (asset.ParentGroupIds == null) {\r\n\t\t\tasset.ParentGroupIds = [];\r\n\t\t}\r\n\t\tif (asset.ParentGroupIds.indexOf(groupId) === -1) {\r\n\t\t\tasset.ParentGroupIds.push(groupId);\r\n\t\t}\r\n\t});\r\n\r\n\tswitch (groupFor) {\r\n\t\tcase \"fences\":\r\n\t\t\tresult.countNumber = result.fenceIds.length;\r\n\t\t\tbreak;\r\n\t\tcase \"places\":\r\n\t\t\tresult.countNumber = result.placeIds.length;\r\n\t\t\tbreak;\r\n\t\tcase \"assets\":\r\n\t\t\tresult.countNumber = result.assetIds.length;\r\n\t\t\tbreak;\r\n\t\tdefault:\r\n\t\t\tthrow new Error(\"Invalid group type: \" + groupFor);\r\n\t}\r\n\r\n\treturn result;\r\n}\r\n\r\nexport function getGroupAssetStatus(groupId) {\r\n\treturn getGroupStatus(groupId, \"assets\");\r\n}\r\n\r\nexport function updateGroupVisibilityStatus(groupId, groupFor) {\r\n\tif (!groupFor) {\r\n\t\tif (groupId === \"all-assets\") {\r\n\t\t\tgroupFor = \"assets\";\r\n\t\t} else if (groupId === \"all-fences\") {\r\n\t\t\tgroupFor = \"fences\";\r\n\t\t} else if (groupId === \"all-places\") {\r\n\t\t\tgroupFor = \"places\";\r\n\t\t}\r\n\t}\r\n\tif (!groupFor) {\r\n\t\tgroupFor = \"assets\";\r\n\t}\r\n\r\n\tvar groupNode = document.getElementById(\"group-\" + groupId);\r\n\tif (groupNode === null) {\r\n\t\tgroupNode = domNodes.groups[groupId];\r\n\t\tif (groupNode === undefined) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t}\r\n\tvar visibilityIcon = groupNode.querySelector(\".showhide\");\r\n\tvar groupStats = getGroupStatus(groupId, groupFor);\r\n\tif (groupStats.active > 0) {\r\n\t\tif (groupStats.items === groupStats.active) {\r\n\t\t\tvisibilityIcon\r\n\t\t\t\t.querySelector(\"use\")\r\n\t\t\t\t.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", \"/content/svg/tracking.svg?v=15#visible\");\r\n\t\t\tvisibilityIcon.classList.add(\"active\");\r\n\t\t\tvisibilityIcon.classList.remove(\"indeterminate\");\r\n\t\t} else {\r\n\t\t\tvisibilityIcon\r\n\t\t\t\t.querySelector(\"use\")\r\n\t\t\t\t.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", \"/content/svg/tracking.svg?v=15#indeterminate\");\r\n\t\t\tvisibilityIcon.classList.add(\"indeterminate\");\r\n\t\t\tvisibilityIcon.classList.remove(\"active\");\r\n\t\t}\r\n\t} else {\r\n\t\tvisibilityIcon\r\n\t\t\t.querySelector(\"use\")\r\n\t\t\t.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", \"/content/svg/tracking.svg?v=15#invisible\");\r\n\t\tvisibilityIcon.classList.remove(\"active\");\r\n\t\tvisibilityIcon.classList.remove(\"indeterminate\");\r\n\t}\r\n\tvar groupCount = groupNode.querySelector(\".item-count\");\r\n\tif (groupCount !== null) {\r\n\t\tgroupCount.textContent = groupStats.countNumber;\r\n\t\tif (groupStats.countNumber >= 1000) {\r\n\t\t\tgroupCount.classList.add(\"large\");\r\n\t\t} else {\r\n\t\t\tgroupCount.classList.remove(\"large\");\r\n\t\t}\r\n\t}\r\n\tvar group = findGroupById(groupId, groupFor);\r\n\tif (group && group.ParentGroupId) {\r\n\t\tupdateGroupVisibilityStatus(group.ParentGroupId, groupFor);\r\n\t}\r\n}\r\n\r\nexport function openEventsForGroup(group) {\r\n\tvar events = getFilteredEventsForGroup(group.Id, getDisplayFilterForEventType(\"events\"));\r\n\tcreateListing(events, \"events\");\r\n\topenDialogPanel(\r\n\t\tdomNodes.dialogs.assetEvents,\r\n\t\tstrings.EVENTS,\r\n\t\tgroup,\r\n\t\tfalse,\r\n\t\tnull,\r\n\t\t\"group\",\r\n\t\t\"group-events\",\r\n\t\topenEventsForGroup\r\n\t);\r\n}\r\n\r\nexport function openStatusForGroup(group) {\r\n\tvar events = getFilteredEventsForGroup(group.Id, getDisplayFilterForEventType(\"status\"));\r\n\tcreateListing(events, \"status\");\r\n\topenDialogPanel(\r\n\t\tdomNodes.dialogs.assetStatus,\r\n\t\tstrings.STATUS,\r\n\t\tgroup,\r\n\t\tfalse,\r\n\t\tnull,\r\n\t\t\"group\",\r\n\t\t\"group-status\",\r\n\t\topenStatusForGroup\r\n\t);\r\n}\r\n\r\nexport function openAlertsForGroup(group) {\r\n\tif (user.isAnonymous) {\r\n\t\treturn;\r\n\t}\r\n\tvar events = getFilteredEventsForGroup(group.Id, getDisplayFilterForEventType(\"alerts\"));\r\n\tcreateListing(events, \"alerts\");\r\n\topenDialogPanel(\r\n\t\tdomNodes.dialogs.assetAlerts,\r\n\t\tstrings.ALERTS,\r\n\t\tgroup,\r\n\t\tfalse,\r\n\t\tnull,\r\n\t\t\"group\",\r\n\t\t\"group-alerts\",\r\n\t\topenAlertsForGroup\r\n\t);\r\n}\r\n\r\nexport function addItemToGroup(item, groupId, groupFor) {\r\n\tvar group = findGroupById(groupId, groupFor);\r\n\tif (group == null) {\r\n\t\tgroupId = \"all-\" + groupFor;\r\n\t}\r\n\r\n\tvar groupContentsNode = domNodes.groupContents[groupId].querySelector(\"ul.group-list-list\");\r\n\r\n\t// itemNode may not be created\r\n\tvar groupItemNode;\r\n\tif (domNodes[groupFor][item.Id] === undefined) {\r\n\t\tgroupItemNode = createAssetNode(item);\r\n\t\tdomNodes[groupFor][item.Id] = [];\r\n\t} else {\r\n\t\tgroupItemNode = domNodes[groupFor][item.Id][0].cloneNode(true);\r\n\t}\r\n\tdomNodes[groupFor][item.Id].push(groupItemNode);\r\n\r\n\t// find the appropriate point in the groups asset list to add the item\r\n\tvar groupItems = trkData[groupFor];\r\n\tif (!groupId.startsWith(\"all-\")) {\r\n\t\tgroupItems = _.filter(groupItems, function (asset) {\r\n\t\t\treturn _.indexOf(asset.GroupIds, groupId) !== -1;\r\n\t\t});\r\n\t}\r\n\tgroupItems = sortItemsByMode(groupFor, groupItems, groupId, groupFor);\r\n\tif (groupItems.length > 1) {\r\n\t\tvar itemIndex = _.indexOf(groupItems, item);\r\n\t\tvar subgroups = group !== null ? group.GroupIds.length : 0;\r\n\t\tgroupContentsNode.insertBefore(groupItemNode, groupContentsNode.children[itemIndex + subgroups]);\r\n\t} else {\r\n\t\tgroupContentsNode.appendChild(groupItemNode);\r\n\t}\r\n\tupdateGroupVisibilityStatus(groupId, groupFor);\r\n\r\n\tdocument.getElementById(\"no-\" + groupFor).classList.remove(\"is-visible\");\r\n\tdocument.getElementById(groupFor + \"-all\").classList.add(\"is-visible\");\r\n\tdocument.getElementById(\"filter-\" + groupFor).querySelector(\".filter-box\").classList.add(\"is-visible\");\r\n}\r\n\r\nexport function addAssetToGroup(asset, groupId) {\r\n\taddItemToGroup(asset, groupId, \"assets\");\r\n}\r\n\r\nexport function removeItemFromGroup(asset, groupId, groupFor) {\r\n\t// remove assetNode from group contents and cache\r\n\tvar groupContents = domNodes.groupContents[groupId];\r\n\tif (groupContents === undefined) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tgroupContents = groupContents.querySelector(\"ul.group-list-list\");\r\n\tif (groupContents === null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar foundIndex = -1;\r\n\t_.each(domNodes[groupFor][asset.Id], function (itemNode, index) {\r\n\t\tfoundIndex = _.indexOf(groupContents.childNodes, itemNode);\r\n\t\tif (foundIndex !== -1) {\r\n\t\t\tgroupContents.removeChild(itemNode);\r\n\t\t\titemNode = null;\r\n\t\t\treturn;\r\n\t\t}\r\n\t});\r\n\r\n\tif (foundIndex !== -1) {\r\n\t\tdomNodes[groupFor][asset.Id].splice(foundIndex, 1);\r\n\t}\r\n\r\n\tupdateGroupVisibilityStatus(groupId, groupFor);\r\n}\r\n\r\nexport function removeAssetFromGroup(asset, groupId) {\r\n\treturn removeItemFromGroup(asset, groupId, \"assets\");\r\n}\r\n\r\nfunction expandGroup(groupId) {\r\n\tvar groupContainer = domNodes.groups[groupId];\r\n\t// render the group contents\r\n\tdisplayPreferencesAdd(\"expandedGroups\", groupId);\r\n\tgroupContainer.appendChild(domNodes.groupContents[groupId]);\r\n\tif (groupId === \"all-fences\" || groupId === \"all-places\") {\r\n\t\tif (domNodes.groupContents[groupId].childNodes.length === 0) {\r\n\t\t\tvar noExtraItems = groupContainer.querySelector(\"#no-\" + groupId);\r\n\t\t\tnoExtraItems.classList.add(\"is-visible\");\r\n\t\t}\r\n\t}\r\n\ttoggleGroupExpandedIcon(groupId, true);\r\n\r\n\tif (!groupContainer.hasAttribute(\"data-sort-initialized\")) {\r\n\t\tinitializeOrRefreshGroupSortable(groupId);\r\n\t\tgroupContainer.setAttribute(\"data-sort-initialized\", true);\r\n\t}\r\n}\r\n\r\nfunction collapseGroup(groupId) {\r\n\tvar groupContainer = domNodes.groups[groupId];\r\n\t// remove the group contents\r\n\tdisplayPreferencesRemove(\"expandedGroups\", groupId);\r\n\tgroupContainer.removeChild(domNodes.groupContents[groupId]);\r\n\tif (groupId === \"all-fences\" || groupId === \"all-places\") {\r\n\t\tvar noItems = groupContainer.querySelector(\"#no-\" + groupId);\r\n\t\tif (noItems !== null) {\r\n\t\t\tnoItems.classList.remove(\"is-visible\");\r\n\t\t}\r\n\t}\r\n\ttoggleGroupExpandedIcon(groupId, false);\r\n}\r\n\r\nexport function toggleGroupExpanded(groupId) {\r\n\tif (isItemIncluded(user.displayPreferences.expandedGroups, groupId)) {\r\n\t\tcollapseGroup(groupId);\r\n\t} else {\r\n\t\texpandGroup(groupId);\r\n\t}\r\n}\r\n\r\nexport function toggleGroupExpandedIcon(groupId, isVisible) {\r\n\tvar group = domNodes.groups[groupId];\r\n\tif (group === undefined) {\r\n\t\treturn;\r\n\t}\r\n\tconst toggleIcon = group.querySelector(\".group-toggle\");\r\n\tif (toggleIcon === null) {\r\n\t\treturn;\r\n\t}\r\n\tif (isVisible) {\r\n\t\tgroup.classList.add(\"is-expanded\");\r\n\t\ttoggleIcon\r\n\t\t\t.querySelector(\"use\")\r\n\t\t\t.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", \"/content/svg/tracking.svg?v=15#folder-open-solid\");\r\n\t\ttoggleIcon.classList.add(\"active\");\r\n\t} else {\r\n\t\tgroup.classList.remove(\"is-expanded\");\r\n\t\ttoggleIcon\r\n\t\t\t.querySelector(\"use\")\r\n\t\t\t.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", \"/content/svg/tracking.svg?v=15#folder-solid\");\r\n\t\ttoggleIcon.classList.remove(\"active\");\r\n\t}\r\n}\r\n\r\nexport function indexAssetGroupsForSearch() {\r\n\ttrkData.search.assetGroups = new JsSearch.Search(\"Id\");\r\n\ttrkData.search.assetGroups.indexStrategy = new JsSearch.AllSubstringsIndexStrategy();\r\n\tvar attributes = [\"Name\", \"DestinationId\"];\r\n\t_.each(attributes, function (attribute) {\r\n\t\ttrkData.search.assetGroups.addIndex(attribute);\r\n\t});\r\n\ttrkData.search.assetGroups.addDocuments(trkData.groups);\r\n\tlog(\"Asset Groups indexed for search filtering.\");\r\n}\r\n\r\nfunction getFilteredEventsForGroup(groupId, filter) {\r\n\tvar status = getGroupAssetStatus(groupId);\r\n\tvar events = [];\r\n\tvar eventsSource =\r\n\t\tstate.activeMapMode === mapModes.HISTORY\r\n\t\t\t? trkData.history.normalizedEventsByAssetId\r\n\t\t\t: trkData.live.normalizedEventsByAssetId;\r\n\t_.each(status.assetIds, function (assetId) {\r\n\t\tevents = events.concat(_.filter(eventsSource[assetId], filter));\r\n\t});\r\n\tevents = _.sortBy(events, defaultListItemSort).reverse();\r\n\treturn events;\r\n}\r\n\r\nexport function createAssetNode(asset) {\r\n\tif (asset == null) return null;\r\n\tconst isActive = !isItemIncluded(user.displayPreferences.hiddenAssets, asset.Id);\r\n\tif (isActive) {\r\n\t\tif (_.indexOf(trkData.visible.assets, asset.Id) === -1) {\r\n\t\t\ttrkData.visible.assets.push(asset.Id);\r\n\t\t}\r\n\t}\r\n\tconst alpha = isActive ? null : 50;\r\n\r\n\tconst assetDto = {\r\n\t\tid: asset.Id,\r\n\t\tname: asset.Name,\r\n\t\tcolor: asset.Color,\r\n\t\ticon: createMarkerPath(asset.Class, asset.Color, null, alpha, asset.Id, true),\r\n\t\tisVisible: !isItemIncluded(user.displayPreferences.hiddenAssets, asset.Id),\r\n\t\tvisibilityClass: !isItemIncluded(user.displayPreferences.hiddenAssets, asset.Id) ? \"active\" : \"disabled\",\r\n\t\tserviceClass: asset.IsOutOfService ? \"noservice\" : \"\",\r\n\t\tlocation: {\r\n\t\t\taddress: null,\r\n\t\t\tlatitude: null,\r\n\t\t\tlongitude: null,\r\n\t\t\ttime: null,\r\n\t\t\tspeed: null,\r\n\t\t},\r\n\t\tnotifications: {\r\n\t\t\tsymbol: user.isAnonymous\r\n\t\t\t\t? options.allowAnonymousMessaging\r\n\t\t\t\t\t? \"notifications-sh\"\r\n\t\t\t\t\t: \"notifications-sh-nc\"\r\n\t\t\t\t: \"notifications\",\r\n\t\t\talerts: 0,\r\n\t\t\tevents: 0,\r\n\t\t\tstatus: 0,\r\n\t\t\tmessages: 0,\r\n\t\t},\r\n\t};\r\n\r\n\tconst fragment = templates.asset(assetDto);\r\n\treturn fragment.childNodes[0];\r\n\r\n\t// var device = trkData.devicesById[asset.DeviceId];\r\n\t//\r\n\t// var assetDto = {\r\n\t// \tId: asset.Id,\r\n\t// \tName: asset.Name,\r\n\t// \tServiceClass: asset.IsOutOfService ? \" noservice\" : \"\",\r\n\t// \tSupportsMessaging: device.SupportsMessaging && !user.isAnonymous,\r\n\t// \tImagePath: createMarkerPath(asset.Class, asset.Color, null, alpha, asset.Id, true),\r\n\t// \tIsActive: isActive,\r\n\t// };\r\n\t//\r\n\t// var mailbox = document.createDocumentFragment();\r\n\t// if (assetDto.SupportsMessaging) {\r\n\t// \t//
      \r\n\t// \t//mailbox = document.createElement('div');\r\n\t// \t//mailbox.className = 'mailbox';\r\n\t// \tvar box = document.createElement(\"a\");\r\n\t// \tbox.setAttribute(\"href\", \"#\");\r\n\t// \tbox.className = \"mailbox t-icon t-icon-mail\";\r\n\t// \t//var icon = document.createElement('span');\r\n\t// \t//icon.className = 't-icon t-icon-mail';\r\n\t// \t//link.appendChild(icon);\r\n\t// \tmailbox.appendChild(box);\r\n\t// }\r\n\t// var serviceClass = \"\";\r\n\t// if (asset.IsOutOfService) {\r\n\t// \tserviceClass = \" noservice\";\r\n\t// }\r\n\t//\r\n\t// var assetLi = document.createElement(\"li\");\r\n\t// assetLi.className =\r\n\t// \t\"assets-item group-item asset-\" +\r\n\t// \tassetDto.Id +\r\n\t// \tassetDto.ServiceClass +\r\n\t// \t(!assetDto.IsActive ? \" disabled\" : \" active\");\r\n\t// assetLi.setAttribute(\"data-asset-id\", assetDto.Id);\r\n\t// assetLi.style.cssText = \"background-image: url(\" + assetDto.ImagePath + \");\";\r\n\t// var assetShowHide = document.createElement(\"input\");\r\n\t// assetShowHide.setAttribute(\"type\", \"checkbox\");\r\n\t// assetShowHide.className = \"showhide\";\r\n\t// assetShowHide.setAttribute(\"data-asset-id\", assetDto.Id);\r\n\t// if (assetDto.IsActive) {\r\n\t// \tassetShowHide.setAttribute(\"checked\", \"checked\");\r\n\t// }\r\n\t// var assetName = document.createElement(\"a\");\r\n\t// assetName.className = \"asset-name\";\r\n\t// assetName.setAttribute(\"for\", \"asset-\" + assetDto.Id);\r\n\t// assetName.setAttribute(\"href\", \"#\");\r\n\t// assetName.textContent = assetDto.Name;\r\n\t// //var assetTreeControl = document.createElement('div');\r\n\t// //assetTreeControl.className = 'tree-control toggle-content is-visible';\r\n\t// var assetTreeControlLink = document.createElement(\"a\");\r\n\t// assetTreeControlLink.setAttribute(\"href\", \"#\");\r\n\t// assetTreeControlLink.className = \"tree-toggle show recent-positions-no toggle-content\";\r\n\t// //var assetEdit = document.createElement('div');\r\n\t// //assetEdit.className = 'edit';\r\n\t// var assetEditLink = document.createElement(\"a\");\r\n\t// assetEditLink.setAttribute(\"href\", \"#\");\r\n\t// assetEditLink.className = \"edit t-icon t-icon-context\";\r\n\t// //var assetEditIcon = document.createElement('span');\r\n\t// //assetEditIcon.className = 't-icon t-icon-context';\r\n\t// var assetIndicators = document.createElement(\"div\");\r\n\t// assetIndicators.className = \"indicators\";\r\n\t//\r\n\t// //assetEditLink.appendChild(assetEditIcon);\r\n\t// //assetEdit.appendChild(assetEditLink);\r\n\t//\r\n\t// //assetTreeControl.appendChild(assetTreeControlLink);\r\n\t// assetIndicators.appendChild(mailbox);\r\n\t//\r\n\t// assetLi.appendChild(assetShowHide);\r\n\t// assetLi.appendChild(assetName);\r\n\t// //assetLi.appendChild(assetTreeControl);\r\n\t// assetLi.appendChild(assetTreeControlLink);\r\n\t// //assetLi.appendChild(assetEdit);\r\n\t// assetLi.appendChild(assetEditLink);\r\n\t// assetLi.appendChild(assetIndicators);\r\n\t//\r\n\t// return assetLi;\r\n}\r\n\r\nexport function populateGroupDialog(group) {\r\n\t// populate fields with assetGroup information\r\n\t$(\"#txtGroupName\").val(group.Name);\r\n\t$(\"#txtGroupDestinationId\").val(group.DestinationId);\r\n\t$(\"#txtColor\").val(group.Color);\r\n\t$(\"#txtColor\").css(\"background-color\", group.Color);\r\n\t$(\"#txtColor\").next(\"span\").css(\"background-color\", group.Color);\r\n\t$(\"#EditAssetGroupAllowChat\").prop(\"checked\", group.IsChatEnabled);\r\n\t$(\"#EditAssetGroupAllowLocationSharing\").prop(\"checked\", group.IsLocationSharingEnabled);\r\n}\r\n\r\nexport function populateAssetGroupDialog(assetGroup) {\r\n\tpopulateGroupDialog(assetGroup);\r\n}\r\n\r\nexport function openAssetGroupDialog(assetGroup) {\r\n\tif (options.enabledFeatures.indexOf(\"UI_GROUPS\") === -1) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar dialog = domNodes.dialogs.editGroup;\r\n\tstate.groupDialog.isEditing = assetGroup !== null;\r\n\tstate.groupDialog.type = \"asset\";\r\n\r\n\tvar buttonText = state.groupDialog.isEditing ? strings.SAVE_CHANGES : strings.CREATE_GROUP;\r\n\tchangePrimaryButtonLabel(domNodes.dialogs.editGroup, buttonText);\r\n\r\n\tif (!state.groupDialog.isEditing) {\r\n\t\t$(\"#accordion-edit-asset-group-main-content\").collapse(\"show\");\r\n\t}\r\n\r\n\t$(\".section-asset-group\").show();\r\n\t$(\"#edit-group-item-name\").text(strings.ASSETS);\r\n\r\n\ttrkData.validation.addGroup.resetForm();\r\n\ttrkData.validation.addGroup.currentForm.reset();\r\n\r\n\tpopulateGroupList();\r\n\tvar dialog = $j(domNodes.dialogs.editGroup);\r\n\t$(\"#edit-asset-group-accordion .primary-card button\").removeClass(\"disabled\");\r\n\tvar dialogTitle = strings.ADD_ASSET_GROUP;\r\n\tif (state.groupDialog.isEditing) {\r\n\t\t$j(\"#asset-group-parent\").hide();\r\n\t\tdialogTitle = strings.EDIT_ASSET_GROUP;\r\n\r\n\t\t$j(dialog).data(\"groupId\", assetGroup.Id);\r\n\t} else {\r\n\t\t$j(\"#asset-group-parent\").show();\r\n\t}\r\n\r\n\tif (!user.isAdmin) {\r\n\t\t$(\"#accordion-edit-asset-group-users-head button\").addClass(\"disabled\");\r\n\t}\r\n\r\n\t// checkbox lists...\r\n\tvar userList = user.isAdmin ? trkData.users : [];\r\n\tpopulateCheckboxList(\r\n\t\t\"edit-asset-group-users-list\",\r\n\t\tuserList,\r\n\t\t\"EditAssetGroupUserIds\",\r\n\t\tfunction (item) {\r\n\t\t\tif (assetGroup === null) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tvar itemUsers = _.find(trkData.assetGroupUsers, { AssetGroupId: assetGroup.Id });\r\n\t\t\tif (itemUsers === undefined) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\treturn _.indexOf(itemUsers.UserIds, item.Id) !== -1;\r\n\t\t},\r\n\t\tfunction (item) {\r\n\t\t\treturn item.Name;\r\n\t\t},\r\n\t\t\"users\",\r\n\t\tfunction (item) {\r\n\t\t\treturn item.Username;\r\n\t\t}\r\n\t);\r\n\tpopulateCheckboxList(\r\n\t\t\"edit-asset-group-assets-list\",\r\n\t\ttrkData.assets,\r\n\t\t\"EditAssetGroupAssetIds\",\r\n\t\tfunction (item) {\r\n\t\t\tif (assetGroup === null) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\treturn _.indexOf(item.GroupIds, assetGroup.Id) !== -1;\r\n\t\t},\r\n\t\tfunction (item) {\r\n\t\t\treturn item.Name;\r\n\t\t},\r\n\t\t\"assets\",\r\n\t\tfunction (item) {\r\n\t\t\treturn item.UniqueId;\r\n\t\t}\r\n\t);\r\n\r\n\tif (assetGroup !== null) {\r\n\t\tpopulateAssetGroupDialog(assetGroup);\r\n\t}\r\n\r\n\topenDialogPanel(\r\n\t\tdomNodes.dialogs.editGroup,\r\n\t\tdialogTitle,\r\n\t\tassetGroup,\r\n\t\tfalse,\r\n\t\tnull,\r\n\t\t\"group\",\r\n\t\tassetGroup !== null ? \"edit-group\" : null,\r\n\t\tassetGroup !== null ? openAssetGroupDialog : null\r\n\t);\r\n\tdocument.getElementById(\"txtGroupName\").focus();\r\n}\r\n\r\nexport function createGroupNode(group) {\r\n\tvar groupDto = {\r\n\t\tid: group.Id,\r\n\t\tisFavorite: false,\r\n\t\tisVisible: isItemIncluded(user.displayPreferences.expandedGroups, group.Id),\r\n\t\tcolor: group.Color,\r\n\t\tname: group.Name,\r\n\t\tdescription: \"\",\r\n\t\ttype: group.Type,\r\n\t\tnotifications: {\r\n\t\t\tsymbol: user.isAnonymous\r\n\t\t\t\t? options.allowAnonymousMessaging\r\n\t\t\t\t\t? \"notifications-sh\"\r\n\t\t\t\t\t: \"notifications-sh-nc\"\r\n\t\t\t\t: \"notifications\",\r\n\t\t},\r\n\t};\r\n\tvar fragment = templates.assetGroup(groupDto);\r\n\tvar item = fragment.childNodes[0];\r\n\tvar noSettingsGroups = [\"all-fences\", \"all-places\", \"all-shared-views\"];\r\n\tif (_.indexOf(noSettingsGroups, group.Id) !== -1) {\r\n\t\tvar groupSettings = item.querySelector(\".item-settings\");\r\n\t\tgroupSettings.parentNode.removeChild(groupSettings);\r\n\t}\r\n\tif (group.Type !== \"assets\" && group.Type !== \"journeys\") {\r\n\t\tvar groupIndicators = item.querySelector(\".asset-indicators\");\r\n\t\tgroupIndicators.parentNode.removeChild(groupIndicators);\r\n\t}\r\n\treturn item;\r\n}\r\n\r\nexport function addGroup(group, groupItemsIds) {\r\n\tvar groupNode = createGroupNode(group);\r\n\tvar groupContentsNode = groupNode.querySelector(\".group-contents\");\r\n\tvar groupContentsItems = groupContentsNode.querySelector(\"ul.group-list-list\");\r\n\r\n\tvar groupItems = _.filter(trkData[group.Type], function (item) {\r\n\t\treturn _.indexOf(groupItemsIds, item.Id) !== -1;\r\n\t}).sort(sortByName);\r\n\t_.each(groupItems, function (item) {\r\n\t\titem.GroupIds.push(group.Id);\r\n\t\tvar groupAssetNode = domNodes[group.Type][item.Id][0].cloneNode(true);\r\n\t\tdomNodes[group.Type][item.Id].push(groupAssetNode);\r\n\t\tgroupContentsItems.appendChild(groupAssetNode);\r\n\t});\r\n\r\n\t// cache group content nodes so they can be detached/reattached to the dom\r\n\t// based on whether the group is expanded/visible\r\n\tdomNodes.groups[group.Id] = groupNode;\r\n\tdomNodes.groupContents[group.Id] = groupContentsNode;\r\n\r\n\tvar allGroups = group.Type === \"assets\" ? trkData.groups :\r\n\t\tgroup.Type === \"places\" ? trkData.placeGroups :\r\n\t\tgroup.Type === \"fences\" ? trkData.fenceGroups : {};\r\n\r\n\t// add group to appropriate place\r\n\tif (group.ParentGroupId != null) {\r\n\t\tvar parentGroup = findGroupById(group.ParentGroupId, group.Type);\r\n\t\tif (parentGroup !== null) {\r\n\t\t\tvar parentGroupContents = domNodes.groupContents[parentGroup.Id].querySelector(\"ul.group-list-list\");\r\n\t\t\t// place in appropriate order within group's subgroups\r\n\t\t\tparentGroup.GroupIds.push(group.Id);\r\n\t\t\tif (parentGroup.GroupIds.length == 1) {\r\n\t\t\t\tparentGroupContents.insertBefore(groupNode, parentGroupContents.firstChild);\r\n\t\t\t} else {\r\n\t\t\t\tvar subGroups = _.filter(allGroups, function (item) {\r\n\t\t\t\t\treturn _.indexOf(parentGroup.GroupIds, item.Id) !== -1;\r\n\t\t\t\t}).sort(sortByName);\r\n\t\t\t\tvar groupIndex = _.indexOf(subGroups, group);\r\n\t\t\t\tparentGroupContents.insertBefore(groupNode, parentGroupContents.children[groupIndex]);\r\n\t\t\t}\r\n\t\t}\r\n\t} else {\r\n\t\t// add to root groups listing\r\n\t\tvar groupsContainer = document.getElementById(group.Type + \"-all\");\r\n\t\tvar subGroups = _.filter(allGroups, function (item) {\r\n\t\t\treturn item.ParentGroupId == null && !item.IsDefault;\r\n\t\t}).sort(sortByName);\r\n\t\tif (subGroups.length > 1) {\r\n\t\t\tvar groupIndex = _.indexOf(subGroups, group);\r\n\t\t\tgroupsContainer.insertBefore(groupNode, groupsContainer.children[groupIndex + 1]);\r\n\t\t} else {\r\n\t\t\tgroupsContainer.insertBefore(groupNode, groupsContainer.children[1]);\r\n\t\t}\r\n\t}\r\n\tdomNodes.groupColors[group.Id] = group.Color;\r\n\tupdateGroupVisibilityStatus(group.Id, group.Type);\r\n\tcreateGroupColorStyles();\r\n}\r\n\r\nexport function deleteAssetGroup(deleted) {\r\n\t// remove group from UI\r\n\tvar li = $j(\"#group-\" + deleted.Id);\r\n\tli.remove();\r\n\r\n\t// remove group from trkData.groups\r\n\tfor (var i = 0; i < trkData.groups.length; i++) {\r\n\t\tif (trkData.groups[i].Id == deleted.Id) {\r\n\t\t\ttrkData.groups.splice(i, 1);\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\ttrkData.groupsById = _.keyBy(trkData.groups, \"Id\");\r\n\r\n\tvar modifiedGroupIds = [];\r\n\t_.each(trkData.groups, function (group) {\r\n\t\tif (_.indexOf(group.GroupIds, deleted.Id)) {\r\n\t\t\tgroup.GroupIds = _.without(group.GroupIds, deleted.Id);\r\n\t\t\tmodifiedGroupIds.push(group.Id);\r\n\t\t}\r\n\t});\r\n\r\n\tif (trkData.assetGroupUsers != null) {\r\n\t\tfor (var i = trkData.assetGroupUsers.length - 1; i >= 0; i -= 1) {\r\n\t\t\tif (trkData.assetGroupUsers[i].AssetGroupId == deleted.Id) {\r\n\t\t\t\ttrkData.assetGroupUsers.splice(i, 1);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// remove group id from all asset's groupIds listings\r\n\tfor (var i = 0; i < trkData.assets.length; i++) {\r\n\t\tvar asset = trkData.assets[i];\r\n\t\tfor (var j = 0; j < asset.GroupIds.length; j++) {\r\n\t\t\tif (asset.GroupIds[j] == deleted.Id) {\r\n\t\t\t\tasset.GroupIds.splice(j, 1);\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// remove group expanded preference\r\n\tdisplayPreferencesRemove(\"expandedGroups\", deleted.Id);\r\n\r\n\t// update group status for parent groups\r\n\t_.each(modifiedGroupIds, function (groupId) {\r\n\t\tupdateGroupVisibilityStatus(groupId);\r\n\t});\r\n}\r\n\r\nexport function updateGroup(updated) {\r\n\t// find li for group and update name/color\r\n\t// group could be hidden so search domNodes\r\n\tdomNodes.groupColors[updated.Id] = updated.Color;\r\n\r\n\t// may also be open in secondaryPanel, so refresh in case name/color was updated\r\n\tif (\r\n\t\tdomNodes.panels.secondary.getAttribute(\"data-group-for\") === \"groups\" &&\r\n\t\tdomNodes.panels.secondary.getAttribute(\"data-item-id\") === updated.Id\r\n\t) {\r\n\t\topenAssetGroupSettingsPanel(updated);\r\n\t}\r\n\r\n\tvar li = domNodes.groups[updated.Id];\r\n\tif (li !== undefined) {\r\n\t\tli.style.borderColor = updated.Color;\r\n\t\tvar name = li.querySelector(\".group-name\");\r\n\t\tname.textContent = updated.Name;\r\n\t}\r\n\r\n\tcreateGroupColorStyles();\r\n\t//var li = $j('#group-' + updated.Id).eq(0);\r\n\t//if (li != null) {\r\n\t// //li.css('background-color', updated.Color);\r\n\t// li.css('border-color', updated.Color);\r\n\t// $j('a.group span', li).text(updated.Name);\r\n\t//}\r\n}\r\n\r\nexport function createGroupColorStyles() {\r\n\tvar existingStyle = document.getElementById(\"group-colors-style\");\r\n\tvar colors = \"\";\r\n\t_.each(domNodes.groupColors, function (elem, index, list) {\r\n\t\t//colors += '#group-' + index + ' .group-color, #group-' + index + ' > .list-item-action.active { color: ' + elem + '; border-color: ' + elem + '; }' + \"\\n\";\r\n\t\t//colors += '#group-' + index + ' > .group-header, #group-' + index + ' > .group-header > .list-item-action.active { color: ' + elem + '; border-color: ' + elem + '; }' + \"\\n\";\r\n\t\t//colors += '#group-' + index + ' .group-color { color: ' + elem + '; border-color: ' + elem + '; }' + \"\\n\";\r\n\t\t//colors += '#group-' + index + ' > .group-header { color: ' + elem + '; border-color: ' + elem + '; }' + \"\\n\";\r\n\t\tcolors += \"#group-\" + index + \" > .group-header { border-color: \" + elem + \"; }\" + \"\\n\";\r\n\t\t//colors += '#group-' + index + ' .list-item-action.showhide.active { color: ' + elem + '; }' + \"\\n\";\r\n\t});\r\n\tif (existingStyle === null) {\r\n\t\tvar groupColors = document.createElement(\"style\");\r\n\t\tgroupColors.id = \"group-colors-style\";\r\n\t\tgroupColors.type = \"text/css\";\r\n\t\tgroupColors.innerHTML = colors;\r\n\t\tdocument.getElementsByTagName(\"head\")[0].appendChild(groupColors);\r\n\t} else {\r\n\t\texistingStyle.innerHTML = colors;\r\n\t}\r\n}\r\n\r\nexport function indexCustomAttributes(attributes) {\r\n\treturn _.map(attributes, function (attribute) {\r\n\t\treturn attribute.Value;\r\n\t});\r\n}\r\n\r\nexport function queryGroupsAndAssets() {\r\n\ttoggleLoadingMessage(true, \"assetGroups\");\r\n\tvar data = {\r\n\t\tdbg: getDbg(),\r\n\t};\r\n\treturn $.ajax({\r\n\t\ttype: \"POST\",\r\n\t\turl: wrapUrl(\"/services/GPSService.asmx/GetGroupsAndAssetsReq\"),\r\n\t\tdata: JSON.stringify(data),\r\n\t\tcontentType: \"application/json\",\r\n\t\tdataType: \"json\",\r\n\t})\r\n\t\t.done(function (msg) {\r\n\t\t\tif (!msg.d) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\ttoggleLoadingMessage(false, \"assetGroups\");\r\n\t\t\tvar result = msg.d;\r\n\r\n\t\t\ttrkData.devices = result.Devices;\r\n\t\t\ttrkData.devicesById = _.keyBy(trkData.devices, \"Id\");\r\n\r\n\t\t\ttrkData.assets = result.Assets;\r\n\t\t\t_.each(trkData.assets, function (asset) {\r\n\t\t\t\tasset.ColorSorted = convertHexToSortable(convertNamedColorToHex(asset.Color));\r\n\t\t\t\tasset.Tags = indexCustomAttributes(asset.Attributes);\r\n\t\t\t});\r\n\t\t\ttrkData.assetsById = _.keyBy(trkData.assets, \"Id\");\r\n\r\n\t\t\ttrkData.fences = result.Fences;\r\n\t\t\t_.each(trkData.fences, function (fence) {\r\n\t\t\t\tfence.ColorSorted = convertHexToSortable(fence.Color);\r\n\t\t\t});\r\n\t\t\t_.each(result.FenceGroups, function (group) {\r\n\t\t\t\tgroup.GroupIds = [];\r\n\t\t\t\tgroup.Type = \"fences\";\r\n\t\t\t});\r\n\t\t\ttrkData.fenceGroups = result.FenceGroups;\r\n\t\t\tvar allFencesGroup = {\r\n\t\t\t\tId: \"all-fences\",\r\n\t\t\t\tIsDefault: true,\r\n\t\t\t\tName: strings.ALL_GEOFENCES,\r\n\t\t\t\tColor: getBackgroundColorAsHex(document.getElementById(\"nav-toggle\")),\r\n\t\t\t\tParentGroupId: null,\r\n\t\t\t\tGroupIds: [],\r\n\t\t\t\tType: \"fences\",\r\n\t\t\t};\r\n\t\t\ttrkData.fenceGroups.splice(0, 0, allFencesGroup);\r\n\t\t\t_.each(trkData.fenceGroups, function (group) {\r\n\t\t\t\tgroup.ColorSorted = convertHexToSortable(group.Color);\r\n\t\t\t});\r\n\t\t\ttrkData.fencesById = _.keyBy(trkData.fences, \"Id\");\r\n\r\n\t\t\ttrkData.sharedViews = result.SharedViews;\r\n\t\t\t_.each(trkData.sharedViews, function (sharedView) {\r\n\t\t\t\tsharedView.ColorSorted = convertHexToSortable(sharedView.Color);\r\n\t\t\t\tsharedView.Link = getSharedViewLink(sharedView); // TODO remove this\r\n\t\t\t});\r\n\t\t\ttrkData.sharedViewsById = _.keyBy(trkData.sharedViews, \"Id\");\r\n\r\n\t\t\ttrkData.places = result.Places;\r\n\t\t\t_.each(trkData.places, function (place) {\r\n\t\t\t\tplace.ColorSorted = convertHexToSortable(convertNamedColorToHex(place.Color));\r\n\t\t\t});\r\n\t\t\ttrkData.placesById = _.keyBy(trkData.places, \"Id\");\r\n\t\t\t_.each(result.PlaceGroups, function (group) {\r\n\t\t\t\tgroup.GroupIds = [];\r\n\t\t\t\tgroup.Type = \"places\";\r\n\t\t\t});\r\n\t\t\ttrkData.placeGroups = result.PlaceGroups;\r\n\t\t\tvar allPlacesGroup = {\r\n\t\t\t\tId: \"all-places\",\r\n\t\t\t\tIsDefault: true,\r\n\t\t\t\tName: strings.ALL_PLACES,\r\n\t\t\t\tColor: getBackgroundColorAsHex(document.getElementById(\"nav-toggle\")),\r\n\t\t\t\tParentGroupId: null,\r\n\t\t\t\tGroupIds: [],\r\n\t\t\t\tType: \"places\",\r\n\t\t\t};\r\n\t\t\ttrkData.placeGroups.splice(0, 0, allPlacesGroup);\r\n\t\t\t_.each(trkData.placeGroups, function (group) {\r\n\t\t\t\tgroup.ColorSorted = convertHexToSortable(convertNamedColorToHex(group.Color));\r\n\t\t\t});\r\n\r\n\t\t\ttrkData.journeys = result.Journeys;\r\n\t\t\t_.each(trkData.journeys, function (journey) {\r\n\t\t\t\t_.each(journey.Trips, function (trip) {\r\n\t\t\t\t\ttrip.JourneyId = journey.Id;\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t\t\ttrkData.journeysById = _.keyBy(trkData.journeys, \"Id\");\r\n\r\n\t\t\ttrkData.waypoints = result.Waypoints;\r\n\r\n\t\t\ttrkData.driverStatuses = result.DriverStatuses;\r\n\r\n\t\t\ttrkData.attributes = result.Attributes;\r\n\r\n\t\t\ttrkData.attributeGroups = result.AttributeGroups;\r\n\r\n\t\t\t_.each(result.Groups, function (group) {\r\n\t\t\t\tgroup.Groups = [];\r\n\t\t\t\tgroup.GroupIds = [];\r\n\t\t\t\tgroup.Type = \"assets\";\r\n\t\t\t});\r\n\t\t\ttrkData.groups = result.Groups;\r\n\t\t\tvar allAssetsGroup = {\r\n\t\t\t\tId: \"all-assets\",\r\n\t\t\t\tName: strings.ALL_ASSETS,\r\n\t\t\t\tColor: getBackgroundColorAsHex(document.getElementById(\"nav-toggle\")), // todo: pass default colors in here, or extract from CSS value? window.getComputedStyle(document.getElementById('group-all-assets)).borderLeftColor // RBG\r\n\t\t\t\tParentGroupId: null,\r\n\t\t\t\tGroupIds: [],\r\n\t\t\t\tGroups: [],\r\n\t\t\t\tAssetIds: [],\r\n\t\t\t\tDestinationId: \"\",\r\n\t\t\t\tIsDefault: true,\r\n\t\t\t\tType: \"assets\",\r\n\t\t\t};\r\n\t\t\ttrkData.groups.splice(0, 0, allAssetsGroup);\r\n\t\t\t_.each(trkData.groups, function (group) {\r\n\t\t\t\tgroup.ColorSorted = convertHexToSortable(group.Color);\r\n\t\t\t});\r\n\t\t\ttrkData.groupsById = _.keyBy(trkData.groups, \"Id\");\r\n\r\n\t\t\t// load preferences for asset/group/fence UI show/hide\r\n\t\t\tloadDisplayPreferences();\r\n\t\t})\r\n\t\t.fail(function (xhr, status, error) {\r\n\t\t\thandleWebServiceError(strings.MSG_QUERY_GROUPS_ERROR);\r\n\t\t\ttoggleLoadingMessage(false, \"assetGroups\");\r\n\t\t});\r\n}\r\n","import trkData from \"./data.js\";\r\nimport { displayPreferencesRemove } from \"./user.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport { updateGroupVisibilityStatus } from \"./asset-group.js\";\r\nimport { closeSecondaryPanel } from \"./panel.js\";\r\nimport { deleteTrip } from \"./trips.js\";\r\n\r\nimport _ from \"lodash\";\r\n\r\nexport function findJourneyById(id) {\r\n\tvar item = _.find(trkData.journeys, { Id: parseInt(id) });\r\n\treturn item === undefined ? null : item;\r\n}\r\n\r\nexport function deleteJourney(journey) {\r\n\tvar groupId = \"journey-\" + journey.Id;\r\n\tdomNodes.groups[groupId].parentNode.removeChild(domNodes.groups[groupId]);\r\n\tvar panel = domNodes.panels.secondary;\r\n\tif (\r\n\t\tpanel.getAttribute(\"data-group-for\") === \"journeys\" &&\r\n\t\tparseInt(panel.getAttribute(\"data-item-id\")) === journey.Id\r\n\t) {\r\n\t\tcloseSecondaryPanel();\r\n\t}\r\n\r\n\tfor (var j = journey.Trips.length - 1; j >= 0; j--) {\r\n\t\tdeleteTrip(journey.Trips[j]);\r\n\t}\r\n\r\n\tfor (var i = 0; i < trkData.journeys.length; i++) {\r\n\t\tif (trkData.journeys[i].Id == journey.Id) {\r\n\t\t\ttrkData.journeys.splice(i, 1);\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\ttrkData.journeysById = _.keyBy(trkData.journeys, \"Id\");\r\n\tdisplayPreferencesRemove(\"expandedGroups\", groupId);\r\n\tupdateGroupVisibilityStatus(groupId);\r\n}\r\n","import preferences from \"./preferences.js\";\r\nimport { findAssetById } from \"./assets.js\";\r\nimport user from \"./user.js\";\r\nimport { findJourneyById } from \"./journey.js\";\r\n\r\nexport function createMarkerPath(icon, color, heading, alpha, assetId, forceAlpha, type, isFirst, isLast, number) {\r\n\tif (color === undefined || color === null) {\r\n\t\tcolor = \"\";\r\n\t}\r\n\tif (heading === undefined || heading === null) {\r\n\t\theading = \"\";\r\n\t}\r\n\tif (alpha === undefined || alpha === null) alpha = \"\";\r\n\r\n\tnumber = typeof number !== \"undefined\" ? number : \"\";\r\n\ttype = typeof type !== \"undefined\" ? type : \"\";\r\n\r\n\tvar headingVal = \"\";\r\n\tif (heading != \"\") {\r\n\t\theading = Math.floor(heading);\r\n\t\theading = Math.floor(heading / 5) * 5; // round to nearest 5 degrees\r\n\t\theadingVal = \"&heading=\" + heading;\r\n\t}\r\n\r\n\tvar alphaVal = \"\";\r\n\tif (alpha !== \"\") {\r\n\t\tif (preferences.PREFERENCE_ALPHA_POSITIONS || forceAlpha) {\r\n\t\t\tif (Math.ceil(alpha) !== 255) {\r\n\t\t\t\talphaVal = \"&alpha=\" + Math.ceil(alpha);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tvar colorVal = \"&color=\" + color;\r\n\t// if custom icon and no heading, no color needed\r\n\tif (icon.toLowerCase() === \"upload\" && heading === \"\") {\r\n\t\tcolorVal = \"\";\r\n\t}\r\n\r\n\tvar aidVal = \"\";\r\n\tvar lmVal = \"\";\r\n\tif (icon.toLowerCase() === \"upload\") {\r\n\t\taidVal = \"&aid=\" + assetId;\r\n\t\tif (assetId != null) {\r\n\t\t\tvar asset = findAssetById(assetId);\r\n\t\t\tif (asset !== null) {\r\n\t\t\t\tlmVal = \"&lm=\" + asset.IconModified;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tvar typeVal = \"\";\r\n\tif (type !== null && type !== \"\") {\r\n\t\ttypeVal = \"&type=\" + type;\r\n\t}\r\n\r\n\tvar numberVal = \"\";\r\n\tif (number !== null && number !== \"\") {\r\n\t\tnumberVal = \"&num=\" + number;\r\n\t}\r\n\r\n\tvar flagVal = \"\";\r\n\tvar hideFlags = false;\r\n\tif ((isFirst === true || isLast === true) && assetId != null) {\r\n\t\tif (assetId != null) {\r\n\t\t\tvar asset = findAssetById(assetId);\r\n\t\t\thideFlags = asset.HideFlags;\r\n\t\t}\r\n\t}\r\n\tif (!hideFlags) {\r\n\t\tif (isFirst === true) {\r\n\t\t\tflagVal = \"&flag=begin\";\r\n\t\t} else if (isLast === true) {\r\n\t\t\tflagVal = \"&flag=end\";\r\n\t\t}\r\n\t}\r\n\r\n\tvar imagePath =\r\n\t\t\"/markers/\" + icon + \"?\" + colorVal + headingVal + alphaVal + aidVal + typeVal + flagVal + numberVal + lmVal;\r\n\tif (user.isImpersonated && aidVal !== \"\") {\r\n\t\timagePath += \"&ishr=\" + encodeURIComponent(user.id);\r\n\t}\r\n\t// if (options.useStaticSubdomains) {\r\n\t// \t// TODO: handle this with a httpmodule instead?\r\n\t// \t// serve static content from other subdomains to improve request handling\r\n\t// \tvar domain = \"static\";\r\n\t// \tswitch (icon.toLowerCase()) {\r\n\t// \t\tcase \"person\":\r\n\t// \t\tcase \"boat\":\r\n\t// \t\tcase \"airplane\":\r\n\t// \t\tcase \"yacht\":\r\n\t// \t\t\tdomain += \"1\";\r\n\t// \t\t\tbreak;\r\n\t// \t\tcase \"truck\":\r\n\t// \t\tcase \"car\":\r\n\t// \t\tcase \"upload\":\r\n\t// \t\tcase \"helicopter\":\r\n\t// \t\t\tdomain += \"2\";\r\n\t// \t\t\tbreak;\r\n\t// \t\tdefault:\r\n\t// \t\t\tbreak;\r\n\t// \t}\r\n\t// \t// return 'https://domain.actualdomain.com{imagePath}'\r\n\t// }\r\n\treturn imagePath;\r\n}\r\n\r\nexport function getGenericIconUrlForItemType(type, item) {\r\n\tvar icon = null;\r\n\tswitch (type) {\r\n\t\tcase \"fences\":\r\n\t\t\ticon =\r\n\t\t\t\t\"url(\" + createMarkerPath(\"Fence\", item.Color.substring(1), null, null, null, false, null, false, false) + \")\";\r\n\t\t\tbreak;\r\n\t\tcase \"places\":\r\n\t\t\ticon = \"url(\" + createMarkerPath(\"Generic\", item.Color, null, null, null, false, null, false, false) + \")\";\r\n\t\t\tbreak;\r\n\t\tcase \"assets\":\r\n\t\t\ticon = \"url(\" + createMarkerPath(item.Class, item.Color, null, null, item.Id, false, null, false, false) + \")\";\r\n\t\t\tbreak;\r\n\t\t//case 'journeys': // fallthrough\r\n\t\tcase \"trips\":\r\n\t\t\tif (type === \"trips\") {\r\n\t\t\t\tvar journey = findJourneyById(item.JourneyId);\r\n\t\t\t\tvar asset = findAssetById(journey.AssetId);\r\n\t\t\t\ticon =\r\n\t\t\t\t\t\"url(\" + createMarkerPath(asset.Class, asset.Color, null, null, asset.Id, false, null, false, false) + \")\";\r\n\t\t\t} else {\r\n\t\t\t\ticon =\r\n\t\t\t\t\t\"url(\" +\r\n\t\t\t\t\tcreateMarkerPath(\"Fence\", item.Color.substring(1), null, null, null, false, null, false, false) +\r\n\t\t\t\t\t\")\";\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\t//case 'groups': // TODO... SVG support here to match listing\r\n\t\t// icon = 'url(' + createMarkerPath('Fence', item.Color.substring(1), null, null, null, false, null, false, false) + ')';\r\n\t\t// break;\r\n\t\t//case 'shared-views': // TODO... SVG support here\r\n\t\t// break;\r\n\t\tdefault: // dialog\r\n\t\t\tbreak;\r\n\t}\r\n\treturn icon;\r\n}\r\n","import $j from \"jquery\";\r\nimport options from \"./options.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\n\r\nexport function reverseGeocode(latlng, callback) {\r\n\tif (options.enabledFeatures.indexOf(\"UI_GEOCODING\") === -1) {\r\n\t\treturn;\r\n\t}\r\n\tvar data = { Lat: latlng.lat, Lng: latlng.lng };\r\n\treturn $j.ajax({\r\n\t\ttype: \"POST\",\r\n\t\turl: wrapUrl(\"/services/GPSService.asmx/ReverseGeocode\"),\r\n\t\tdata: JSON.stringify(data),\r\n\t\tcontentType: \"application/json; charset=utf-8\",\r\n\t\tdataType: \"json\",\r\n\t\tsuccess: function (msg) {\r\n\t\t\tvar result = msg.d;\r\n\t\t\tif (result) {\r\n\t\t\t\tconsole.log(result);\r\n\t\t\t\tif (result.Success == true) {\r\n\t\t\t\t\tcallback(true, result.Address);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tcallback(false);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\terror: function (xhr, status, error) {\r\n\t\t\tcallback(false);\r\n\t\t},\r\n\t});\r\n}\r\n\r\nexport function addressSearch(address, callback) {\r\n\tvar data = { address: address };\r\n\treturn $j.ajax({\r\n\t\ttype: \"POST\",\r\n\t\turl: wrapUrl(\"/services/GPSService.asmx/AddressSearch\"),\r\n\t\tdata: JSON.stringify(data),\r\n\t\tcontentType: \"application/json; charset=utf-8\",\r\n\t\tdataType: \"json\",\r\n\t\tsuccess: function (msg) {\r\n\t\t\tconsole.log(\"address search success\");\r\n\t\t\tvar result = msg.d;\r\n\t\t\tif (result) {\r\n\t\t\t\tconsole.log(result);\r\n\t\t\t\tif (result.Success === true) {\r\n\t\t\t\t\tcallback(true, result.Result);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tcallback(false);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\terror: function (xhr, status, error) {\r\n\t\t\tcallback(false);\r\n\t\t},\r\n\t});\r\n}\r\n","import state from \"./state.js\";\r\nimport { map } from \"./map-base.js\";\r\nimport { addItemToMap, removeItemFromMap } from \"./map-items.js\";\r\nimport { createMarkerPath } from \"./marker-path.js\";\r\nimport { addPointToRuler } from \"./ruler.js\";\r\nimport { addPointToRouting } from \"./routing.js\";\r\nimport { reverseGeocode } from \"./geocode.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport L from \"leaflet\";\r\nimport _ from \"lodash\";\r\n\r\nexport function startChoosingMapLocation(handler) {\r\n\tvar handlerIndex = _.indexOf(state.mapClickQueue, handler);\r\n\tif (handlerIndex === -1) {\r\n\t\tstate.mapClickQueue.push(handler);\r\n\t\tL.DomUtil.addClass(map._container, \"crosshair-cursor-enabled\");\r\n\t}\r\n}\r\n\r\nexport function stopChoosingMapLocation(handler) {\r\n\tvar handlerIndex = _.indexOf(state.mapClickQueue, handler);\r\n\tif (handlerIndex !== -1) {\r\n\t\tstate.mapClickQueue.splice(handlerIndex);\r\n\t}\r\n\tif (state.mapClickQueue.length === 0) {\r\n\t\tif (state.chosenLocations[handler] !== null && state.chosenLocations[handler] !== undefined) {\r\n\t\t\tremoveItemFromMap(state.chosenLocations[handler]);\r\n\t\t}\r\n\t\tL.DomUtil.removeClass(map._container, \"crosshair-cursor-enabled\");\r\n\t}\r\n}\r\n\r\nexport function updateChosenLocation(latlng, handler) {\r\n\tif (!(latlng instanceof L.LatLng)) {\r\n\t\tlatlng = L.latLng(latlng);\r\n\t}\r\n\r\n\tif (state.mapClickQueue.length === 0) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar clickHandler = state.mapClickQueue[state.mapClickQueue.length - 1];\r\n\tif (handler === null || handler === undefined) {\r\n\t\thandler = clickHandler;\r\n\t}\r\n\r\n\t// place/move a marker icon at the spot for handlers adding/selecting some kind of marked location\r\n\t// we need to have a marker per handler so they don't interfere with each other\r\n\t// and the dragend callback needs to be aware of the handler associated with it\r\n\tif (\r\n\t\thandler === state.mapClickHandlers.POSITION ||\r\n\t\thandler === state.mapClickHandlers.POSITION_ADD ||\r\n\t\thandler === state.mapClickHandlers.PLACE\r\n\t) {\r\n\t\tif (state.chosenLocations[handler] === null || state.chosenLocations[handler] === undefined) {\r\n\t\t\tvar locationIcon = L.icon({\r\n\t\t\t\ticonUrl: createMarkerPath(\"Generic\", \"red\", null, null, null, false),\r\n\t\t\t\ticonSize: [36, 36],\r\n\t\t\t\ticonAnchor: [18, 18],\r\n\t\t\t});\r\n\t\t\tstate.chosenLocations[handler] = L.marker(latlng, { icon: locationIcon, draggable: true });\r\n\t\t\taddItemToMap(state.chosenLocations[handler]);\r\n\t\t\tstate.chosenLocations[handler].on(\"dragend\", function (e) {\r\n\t\t\t\tupdateChosenLocation(e.target.getLatLng(), handler);\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\taddItemToMap(state.chosenLocations[handler]);\r\n\t\t\tstate.chosenLocations[handler].setLatLng(latlng);\r\n\t\t}\r\n\t}\r\n\r\n\tswitch (handler) {\r\n\t\tcase state.mapClickHandlers.POSITION:\r\n\t\t\t$(\"#SendPositionLatLng\").text(latlng.lat.toFixed(6) + \", \" + latlng.lng.toFixed(6));\r\n\t\t\t$(\"#hfPositionLat\").val(latlng.lat);\r\n\t\t\t$(\"#hfPositionLng\").val(latlng.lng);\r\n\t\t\t$(\"#send-position-search\").removeClass(\"is-visible\");\r\n\t\t\t$(\"#form-send-position-send\").addClass(\"is-visible\");\r\n\t\t\treverseGeocode(latlng, function (success, address) {\r\n\t\t\t\tif (success && address !== \"\") {\r\n\t\t\t\t\tif ($(\"#SendPositionName\").val() === \"\") {\r\n\t\t\t\t\t\t$(\"#SendPositionName\").val(address);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\tbreak;\r\n\t\tcase state.mapClickHandlers.POSITION_ADD:\r\n\t\t\t$(\"#txtAddPositionLat\").val(latlng.lat.toFixed(6));\r\n\t\t\t$(\"#txtAddPositionLng\").val(latlng.lng.toFixed(6));\r\n\t\t\tbreak;\r\n\t\tcase state.mapClickHandlers.PLACE:\r\n\t\t\t$(\"#add-place-location\").text(latlng.lat.toFixed(6) + \", \" + latlng.lng.toFixed(6));\r\n\t\t\t$(\"#add-place-search\").removeClass(\"is-visible\");\r\n\t\t\t$(\"#form-add-place\").addClass(\"is-visible\");\r\n\t\t\t$(\"#hfPlaceLat\").val(latlng.lat);\r\n\t\t\t$(\"#hfPlaceLng\").val(latlng.lng);\r\n\r\n\t\t\treverseGeocode(latlng, function (success, address) {\r\n\t\t\t\tif (success && address !== \"\") {\r\n\t\t\t\t\tif ($(\"#txtPlaceName\").val() === \"\") {\r\n\t\t\t\t\t\t$(\"#txtPlaceName\").val(address);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\tbreak;\r\n\t\tcase state.mapClickHandlers.RULER:\r\n\t\t\taddPointToRuler(latlng);\r\n\t\t\tbreak;\r\n\t\tcase state.mapClickHandlers.GEOFENCE:\r\n\t\t\tbreak;\r\n\t\tcase state.mapClickHandlers.ROUTING:\r\n\t\t\taddPointToRouting(latlng);\r\n\t\t\tbreak;\r\n\t}\r\n}\r\n","import { handleWebServiceError } from \"./ajax.js\";\r\nimport { addItemToMap, removeItemFromMap } from \"./map-items.js\";\r\nimport strings from \"./strings.js\";\r\nimport trkData from \"./data.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\nimport { intervals } from \"./timers.js\";\r\n\r\nimport $j from \"jquery\";\r\nconst Cookies = window.Cookies; // from 'js.cookie.js', v2.2.0\r\nimport L from \"leaflet\";\r\n\r\nfunction wrapLat(lat) {\r\n\tif (lat > 90) {\r\n\t\tlat = -90 + (lat - 90);\r\n\t}\r\n\tif (lat < -90) {\r\n\t\tlat = 90 - (-90 - lat);\r\n\t}\r\n\treturn lat;\r\n}\r\n\r\nfunction wrapLng(lng) {\r\n\tif (lng > 180) {\r\n\t\tlng = -180 + (lng - 180);\r\n\t}\r\n\tif (lng < -180) {\r\n\t\tlng = 180 - (-180 - lng);\r\n\t}\r\n\treturn lng;\r\n}\r\n\r\nexport function enableLayerIridiumNext() {\r\n\tif (trkData.iridiumnext.isActive) return;\r\n\ttrkData.iridiumnext.isActive = true;\r\n\tstartSatelliteOverlay(\"IridiumNext\");\r\n\t$j(\"#map-layers-list .iridiumnext\").addClass(\"active\");\r\n\tCookies.set(\"layer-iridiumnext\", true, { expires: 365, path: \"/\", secure: true });\r\n}\r\n\r\nexport function enableLayerGlobalstar() {\r\n\tif (trkData.globalstar.isActive) return;\r\n\ttrkData.globalstar.isActive = true;\r\n\tstartSatelliteOverlay(\"Globalstar\");\r\n\t$j(\"#map-layers-list .globalstar\").addClass(\"active\");\r\n\tCookies.set(\"layer-globalstar\", true, { expires: 365, path: \"/\", secure: true });\r\n}\r\n\r\nexport function enableLayerInmarsat() {\r\n\tif (trkData.inmarsat.isActive) return;\r\n\ttrkData.inmarsat.isActive = true;\r\n\tstartSatelliteOverlay(\"Inmarsat\");\r\n\t$j(\"#map-layers-list .inmarsat\").addClass(\"active\");\r\n\tCookies.set(\"layer-inmarsat\", true, { expires: 365, path: \"/\", secure: true });\r\n}\r\n\r\nexport function enableLayerOrbcomm() {\r\n\tif (trkData.orbcomm.isActive) return;\r\n\ttrkData.orbcomm.isActive = true;\r\n\tstartSatelliteOverlay(\"Orbcomm\");\r\n\t$j(\"#map-layers-list .orbcomm\").addClass(\"active\");\r\n\tCookies.set(\"layer-orbcomm\", true, { expires: 365, path: \"/\", secure: true });\r\n}\r\n\r\nexport function enableLayerGeostationary() {\r\n\tif (trkData.geostationary.isActive) return;\r\n\ttrkData.geostationary.isActive = true;\r\n\tstartSatelliteOverlay(\"Geostationary\");\r\n\t$j(\"#map-layers-list .geostationary\").addClass(\"active\");\r\n\tCookies.set(\"layer-geostationary\", true, { expires: 365, path: \"/\", secure: true });\r\n}\r\n\r\nexport function disableLayerIridiumNext() {\r\n\tif (!trkData.iridiumnext.isActive) return;\r\n\ttrkData.iridiumnext.isActive = false;\r\n\tstopSatelliteOverlay(\"IridiumNext\");\r\n\t$j(\"#map-layers-list .iridiumnext\").removeClass(\"active\");\r\n\tCookies.remove(\"layer-iridiumnext\");\r\n}\r\n\r\nexport function disableLayerGlobalstar() {\r\n\tif (!trkData.globalstar.isActive) return;\r\n\ttrkData.globalstar.isActive = false;\r\n\tstopSatelliteOverlay(\"Globalstar\");\r\n\t$j(\"#map-layers-list .globalstar\").removeClass(\"active\");\r\n\tCookies.remove(\"layer-globalstar\");\r\n}\r\n\r\nexport function disableLayerInmarsat() {\r\n\tif (!trkData.inmarsat.isActive) return;\r\n\ttrkData.inmarsat.isActive = false;\r\n\tstopSatelliteOverlay(\"Inmarsat\");\r\n\t$j(\"#map-layers-list .inmarsat\").removeClass(\"active\");\r\n\tCookies.remove(\"layer-inmarsat\");\r\n}\r\n\r\nexport function disableLayerOrbcomm() {\r\n\tif (!trkData.orbcomm.isActive) return;\r\n\ttrkData.orbcomm.isActive = false;\r\n\tstopSatelliteOverlay(\"Orbcomm\");\r\n\t$j(\"#map-layers-list .orbcomm\").removeClass(\"active\");\r\n\tCookies.remove(\"layer-orbcomm\");\r\n}\r\n\r\nexport function disableLayerGeostationary() {\r\n\tif (!trkData.geostationary.isActive) return;\r\n\ttrkData.geostationary.isActive = false;\r\n\tstopSatelliteOverlay(\"Geostationary\");\r\n\t$j(\"#map-layers-list .geostationary\").removeClass(\"active\");\r\n\tCookies.remove(\"layer-geostationary\");\r\n}\r\n\r\nfunction startSatelliteOverlay(constellation) {\r\n\t// map current positions\r\n\tvar dataPost = { constellation: constellation };\r\n\t$j.ajax({\r\n\t\ttype: \"POST\",\r\n\t\turl: wrapUrl(\"/services/GPSService.asmx/GetSatelliteTelemetry\"),\r\n\t\tdata: JSON.stringify(dataPost),\r\n\t\tcontentType: \"application/json; charset=utf-8\",\r\n\t\tdataType: \"json\",\r\n\t\tsuccess: function (msg) {\r\n\t\t\tif (msg.d) {\r\n\t\t\t\tvar result = msg.d;\r\n\t\t\t\tif (result) {\r\n\t\t\t\t\tif (result.Success === false) {\r\n\t\t\t\t\t\thandleWebServiceError(strings.MSG_SATELLITE_ORBIT_ERROR + \" \" + result.ErrorMessage);\r\n\t\t\t\t\t\treturn;\r\n\t\t\t\t\t}\r\n\t\t\t\t\ttrkData.satelliteMarkers[constellation] = [];\r\n\t\t\t\t\ttrkData.satelliteOrbits[constellation] = result.Satellites;\r\n\t\t\t\t\ttrkData.satelliteIndex[constellation] = 1;\r\n\t\t\t\t\t// map the current positions\r\n\t\t\t\t\tfor (var i = 0; i < result.Satellites.length; i++) {\r\n\t\t\t\t\t\tvar sat = result.Satellites[i];\r\n\t\t\t\t\t\tif (sat.Positions == null) {\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tif (sat.Positions.length == 0) {\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tvar pos = sat.Positions[0];\r\n\t\t\t\t\t\t// wrap locations\r\n\t\t\t\t\t\tvar lat = wrapLat(pos.Lat);\r\n\t\t\t\t\t\tvar lng = wrapLng(pos.Lng);\r\n\t\t\t\t\t\tvar latlng = L.latLng(lat, lng);\r\n\r\n\t\t\t\t\t\tvar icon = L.icon({\r\n\t\t\t\t\t\t\ticonUrl: \"/content/images/marker-satellite-small.png\",\r\n\t\t\t\t\t\t\ticonSize: [20, 20],\r\n\t\t\t\t\t\t\ticonAnchor: [10, 10],\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tvar tooltip = sat.Name;\r\n\t\t\t\t\t\tif (sat.Description != null) {\r\n\t\t\t\t\t\t\ttooltip += \" - \" + sat.Description;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tvar satelliteMarker = L.marker(latlng, {\r\n\t\t\t\t\t\t\ticon: icon,\r\n\t\t\t\t\t\t\t//title: sat.Name\r\n\t\t\t\t\t\t}).bindTooltip(tooltip);\r\n\t\t\t\t\t\taddItemToMap(satelliteMarker);\r\n\t\t\t\t\t\tsat.Marker = satelliteMarker;\r\n\t\t\t\t\t\ttrkData.satelliteMarkers[constellation].push(satelliteMarker);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// set timeout to show the next\r\n\t\t\t\t\tintervals.satelliteInterval[constellation] = setInterval(function () {\r\n\t\t\t\t\t\tnextTelemetryPosition(constellation);\r\n\t\t\t\t\t}, 10000);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\terror: function (xhr, status, error) {\r\n\t\t\thandleWebServiceError(strings.MSG_SATELLITE_ORBIT_ERROR);\r\n\t\t},\r\n\t});\r\n}\r\n\r\nfunction stopSatelliteOverlay(constellation) {\r\n\tif (trkData.satelliteMarkers[constellation] != null) {\r\n\t\tfor (var j = 0; j < trkData.satelliteMarkers[constellation].length; j++) {\r\n\t\t\tvar marker = trkData.satelliteMarkers[constellation][j];\r\n\t\t\tremoveItemFromMap(marker);\r\n\t\t}\r\n\t}\r\n\tclearInterval(intervals.satelliteInterval[constellation]);\r\n\ttrkData.satelliteIndex[constellation] = 0;\r\n}\r\n\r\nfunction nextTelemetryPosition(constellation) {\r\n\tif (trkData.satelliteIndex[constellation] >= 60) {\r\n\t\t// it's time to requery\r\n\t\tstopSatelliteOverlay(constellation);\r\n\t\tstartSatelliteOverlay(constellation);\r\n\t\treturn;\r\n\t}\r\n\r\n\tfor (var i = 0; i < trkData.satelliteOrbits[constellation].length; i++) {\r\n\t\tvar sat = trkData.satelliteOrbits[constellation][i];\r\n\t\tvar pos = sat.Positions[trkData.satelliteIndex[constellation]];\r\n\t\tif (pos == null) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tvar lat = wrapLat(pos.Lat);\r\n\t\tvar lng = wrapLng(pos.Lng);\r\n\t\tvar latlng = L.latLng(lat, lng);\r\n\t\tsat.Marker.setLatLng(latlng);\r\n\t}\r\n\ttrkData.satelliteIndex[constellation]++;\r\n}\r\n","/// Map functionality for wheather radar (& IR, oil, maritime etc) basemap overlays.\r\n\r\nimport trkData from \"./data.js\";\r\nimport log from \"./log.js\";\r\nimport {\r\n\tmap,\r\n\tlayers,\r\n\taddOverlay,\r\n\tremoveOverlay,\r\n\taddLayer,\r\n\tremoveLayer,\r\n\tisOverlayActive,\r\n\tonTileLoaded,\r\n} from \"./map-base.js\";\r\nimport options from \"./options.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport $j from \"jquery\";\r\nimport L from \"leaflet\";\r\nconst Cookies = window.Cookies; // from 'js.cookie.js', v2.2.0\r\n\r\nvar Conv = {\r\n\ttoMercator: function (lat, lng) {\r\n\t\tvar x = (lng * 20037508.34) / 180;\r\n\t\tvar y = Math.log(Math.tan(((90 + lat) * Math.PI) / 360)) / (Math.PI / 180);\r\n\t\ty = (y * 20037508.34) / 180;\r\n\t\treturn L.point(x, y);\r\n\t},\r\n\tpointToLatLng: function (x, y) {\r\n\t\tvar lng = (x / 20037508.34) * 180;\r\n\t\tvar lat = (y / 20037508.34) * 180;\r\n\t\tlat = (180 / Math.PI) * (2 * Math.atan(Math.exp((lat * Math.PI) / 180)) - Math.PI / 2);\r\n\t\treturn L.latLng(lat, lng);\r\n\t},\r\n};\r\n\r\nexport function initMapWeather() {\r\n\tradarOverlay.on(\"tileload\", onTileLoaded(\"radar\"));\r\n\tmaritimeOverlay.on(\"tileload\", onTileLoaded(\"maritime\"));\r\n\toilOverlay.on(\"tileload\", onTileLoaded(\"oil\"));\r\n\r\n\tmap.on(\"load\", onMapLoaded);\r\n}\r\n\r\nconst oilOverlay = L.tileLayer(\r\n\t\"https://gis.boem.gov/arcgis/rest/services/BOEM_BSEE/MMC_Layers/MapServer/export?f=image&dpi=96&bboxSR=102100&imageSR=102100&format=png8&transparent=true&bbox={bbox}&size=256,256&layers=show:7,8,15,11,1,0\",\r\n\t{\r\n\t\tbbox: function (tile) {\r\n\t\t\tvar rect = TileToLocationRect(tile);\r\n\t\t\tvar mbb1 = Conv.toMercator(rect.getSouth(), rect.getWest());\r\n\t\t\tvar mbb2 = Conv.toMercator(rect.getNorth(), rect.getEast());\r\n\t\t\treturn mbb1.x + \",\" + mbb1.y + \",\" + mbb2.x + \",\" + mbb2.y;\r\n\t\t},\r\n\t\tzIndex: 4,\r\n\t}\r\n);\r\n\r\nconst maritimeOverlay = L.tileLayer(\r\n\t\"//gis.charttools.noaa.gov/arcgis/rest/services/MCS/ENCOnline/MapServer/exts/MaritimeChartService/MapServer/export?dpi=96&transparent=true&format=png8&bbox={bbox}&bboxSR=3857&size=256%2C256&f=image&layers=show:2,3,4,5,6,7\",\r\n\t{\r\n\t\tbbox: function (tile) {\r\n\t\t\tvar rect = TileToLocationRect(tile);\r\n\t\t\tvar mbb1 = Conv.toMercator(rect.getSouth(), rect.getWest());\r\n\t\t\tvar mbb2 = Conv.toMercator(rect.getNorth(), rect.getEast());\r\n\t\t\treturn mbb1.x + \",\" + mbb1.y + \",\" + mbb2.x + \",\" + mbb2.y;\r\n\t\t},\r\n\t\tzIndex: 4,\r\n\t}\r\n);\r\n\r\nconst radarAustraliaOverlay = L.tileLayer(\"/services/layer.ashx?type=radar-aus&bbox={bbox}&rnd={time}\", {\r\n\ttime: function (tile) {\r\n\t\treturn new Date().getTime();\r\n\t},\r\n\tbase: function (tile) {\r\n\t\treturn trkData.radarAustralia.basetime;\r\n\t},\r\n\tissue: function (tile) {\r\n\t\treturn trkData.radarAustralia.issuetime;\r\n\t},\r\n\tbbox: function (tile) {\r\n\t\tvar rect = TileToLocationRect(tile);\r\n\t\t//var mbb1 = Conv.toMercator(rect.getSouth(), rect.getWest());\r\n\t\t//var mbb2 = Conv.toMercator(rect.getNorth(), rect.getEast());\r\n\t\treturn rect.getWest() + \",\" + rect.getSouth() + \",\" + rect.getEast() + \",\" + rect.getNorth();\r\n\t},\r\n\tbounds: L.latLngBounds([-7.740789, 108.318991], [-44.235258, 154.901023]),\r\n\tzIndex: 5,\r\n\tminZoom: 3,\r\n});\r\n\r\nconst radarOverlay = L.tileLayer(\r\n\t\"https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q-900913/{z}/{x}/{y}.png?{time}\",\r\n\t{\r\n\t\ttime: function (tile) {\r\n\t\t\treturn new Date().getTime();\r\n\t\t},\r\n\t\tbounds: L.latLngBounds([71.48, -174.25], [17.0, -51.42]),\r\n\t\tzIndex: 5,\r\n\t}\r\n);\r\n\r\nconst worldCloudOverlay = L.tileLayer(\r\n\t\"https://earthlive.maptiles.arcgis.com/arcgis/rest/services/GOES/GOES31C/MapServer/tile/{z}/{y}/{x}?{time}\",\r\n\t{\r\n\t\ttime: function (tile) {\r\n\t\t\treturn new Date().getTime();\r\n\t\t},\r\n\t\tzIndex: 4,\r\n\t\topacity: 0.4,\r\n\t}\r\n);\r\n\r\nconst seamapOverlay = L.tileLayer(\"https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png?type=google\", {\r\n\tmaxZoom: 18,\r\n\tattribution: \"OpenSeaMap\",\r\n\tzIndex: 4,\r\n});\r\n\r\nexport function refreshWorldIR() {\r\n\tif (isOverlayActive(worldCloudOverlay)) {\r\n\t\t//$j('#radar .loading').show();\r\n\t\tlog(\"World Cloud IR refreshing.\");\r\n\t\tremoveOverlay(worldCloudOverlay);\r\n\t\taddOverlay(worldCloudOverlay);\r\n\t\ttrkData.worldIR.timer = setTimeout(refreshWorldIR, options.worldIRRefreshMinutes * 60 * 1000);\r\n\t} else {\r\n\t\tlog(\"World cloud IR not active\");\r\n\t}\r\n}\r\n\r\nexport function refreshRadar() {\r\n\t// if the radar is enabled, refresh it\r\n\tif (isOverlayActive(radarOverlay)) {\r\n\t\tlog(\"Radar refreshing.\");\r\n\t\tremoveOverlay(radarOverlay);\r\n\t\taddOverlay(radarOverlay);\r\n\t\ttrkData.radar.timer = setTimeout(refreshRadar, options.radarRefreshMinutes * 60 * 1000);\r\n\t} else {\r\n\t\ttrkData.radar.timer = null;\r\n\t\tlog(\"radar not active\");\r\n\t}\r\n}\r\n\r\nexport function refreshRadarAustralia() {\r\n\t// if the radar is enabled, refresh it\r\n\tif (isOverlayActive(radarAustraliaOverlay)) {\r\n\t\tlog(\"Australia Radar refreshing.\");\r\n\t\tremoveOverlay(radarAustraliaOverlay);\r\n\t\taddOverlay(radarAustraliaOverlay);\r\n\t\ttrkData.radarAustralia.timer = setTimeout(refreshRadarAustralia, options.radarRefreshMinutes * 60 * 1000);\r\n\t\t//enableRadarAustralia(true, function () {\r\n\t\t// removeOverlay(radarAustraliaOverlay);\r\n\t\t// addOverlay(radarAustraliaOverlay);\r\n\t\t// trkData.radarAustralia.timer = setTimeout(refreshRadarAustralia, options.radarRefreshMinutes * 60 * 1000);\r\n\t\t//});\r\n\t} else {\r\n\t\ttrkData.radarAustralia.timer = null;\r\n\t\tlog(\"Australia radar not active.\");\r\n\t}\r\n}\r\n\r\nexport function enableLayerWeather(requestData) {\r\n\tif (trkData.weather.isActive) return;\r\n\ttrkData.weather.isActive = true;\r\n\ttrkData.weather.layer = L.geoJSON(null, {\r\n\t\tstyle: function (feature) {},\r\n\t\tonEachFeature: function (feature, layer) {\r\n\t\t\tif (feature.properties != null) {\r\n\t\t\t\tif (feature.properties.city != null) {\r\n\t\t\t\t\tif (feature.properties.temperature != null) {\r\n\t\t\t\t\t\t//var tooltipIcon = '
      ';\r\n\t\t\t\t\t\tvar tooltip =\r\n\t\t\t\t\t\t\t\"\" +\r\n\t\t\t\t\t\t\tfeature.properties.city +\r\n\t\t\t\t\t\t\t\"\" +\r\n\t\t\t\t\t\t\t\"
      \" +\r\n\t\t\t\t\t\t\tfeature.properties.temperature +\r\n\t\t\t\t\t\t\t\"°C\" +\r\n\t\t\t\t\t\t\t\"
      \" +\r\n\t\t\t\t\t\t\tfeature.properties.weather;\r\n\t\t\t\t\t\tlayer.bindPopup(tooltip);\r\n\t\t\t\t\t\tlayer.bindTooltip(tooltip);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tpointToLayer: function (feature, latlng) {\r\n\t\t\tif (feature.properties != undefined && feature.properties.icon != undefined) {\r\n\t\t\t\tvar icon = L.icon({\r\n\t\t\t\t\ticonUrl: feature.properties.icon,\r\n\t\t\t\t\ticonSize: [50, 50],\r\n\t\t\t\t\ticonAnchor: [25, 25],\r\n\t\t\t\t\ttooltipAnchor: [25, 0],\r\n\t\t\t\t\tpopupAnchor: [0, -25],\r\n\t\t\t\t});\r\n\t\t\t\treturn L.marker(latlng, { icon: icon });\r\n\t\t\t}\r\n\t\t\treturn L.marker(latlng);\r\n\t\t},\r\n\t});\r\n\r\n\tif (requestData) {\r\n\t\tcheckWeatherForecast();\r\n\t}\r\n\taddLayer(trkData.weather.layer);\r\n\t$j(\"#map-layers-list .weather\").addClass(\"active\");\r\n\tCookies.set(\"layer-weather\", true, { expires: 365, path: \"/\", secure: true });\r\n}\r\n\r\nexport function disableLayerWeather() {\r\n\tif (!trkData.weather.isActive) return;\r\n\tremoveLayer(trkData.weather.layer);\r\n\ttrkData.weather.isActive = false;\r\n\t$j(\"#map-layers-list .weather\").removeClass(\"active\");\r\n\tCookies.remove(\"layer-weather\");\r\n}\r\n\r\nexport function disableLayerClouds() {\r\n\tif (!trkData.clouds.isActive) return;\r\n\tremoveLayer(layers.clouds);\r\n\ttrkData.clouds.isActive = false;\r\n\t$j(\"#map-layers-list .clouds\").removeClass(\"active\");\r\n\tCookies.remove(\"layer-clouds\");\r\n}\r\n\r\nexport function enableLayerClouds() {\r\n\tif (trkData.clouds.isActive) return;\r\n\taddLayer(layers.clouds);\r\n\ttrkData.clouds.isActive = true;\r\n\t$j(\"#map-layers-list .clouds\").addClass(\"active\");\r\n\tCookies.set(\"layer-clouds\", true, { expires: 365, path: \"/\", secure: true });\r\n}\r\n\r\nexport function enableLayerOil() {\r\n\tif (trkData.oil.isActive) return;\r\n\r\n\ttrkData.oil.isActive = true;\r\n\taddOverlay(oilOverlay);\r\n\t$j(\"#map-layers-list .oil\").addClass(\"active\");\r\n\tCookies.set(\"layer-oil\", true, { expires: 365, path: \"/\", secure: true });\r\n}\r\n\r\nexport function disableLayerOil() {\r\n\tif (!trkData.oil.isActive) return;\r\n\ttrkData.oil.isActive = false;\r\n\tremoveOverlay(oilOverlay);\r\n\t$j(\"#map-layers-list .oil\").removeClass(\"active\");\r\n\tCookies.remove(\"layer-oil\");\r\n}\r\n\r\nexport function enableLayerMaritime() {\r\n\tif (trkData.maritime.isActive) return;\r\n\r\n\ttrkData.maritime.isActive = true;\r\n\taddOverlay(maritimeOverlay);\r\n\t$j(\"#map-layers-list .maritime\").addClass(\"active\");\r\n\tCookies.set(\"layer-maritime\", true, { expires: 365, path: \"/\", secure: true });\r\n}\r\n\r\nexport function disableLayerMaritime() {\r\n\tif (!trkData.maritime.isActive) return;\r\n\ttrkData.maritime.isActive = false;\r\n\tremoveOverlay(maritimeOverlay);\r\n\t$j(\"#map-layers-list .maritime\").removeClass(\"active\");\r\n\tCookies.remove(\"layer-maritime\");\r\n}\r\n\r\nexport function enableWorldIR() {\r\n\tif (trkData.worldIR.isActive) return;\r\n\r\n\ttrkData.worldIR.isActive = true;\r\n\taddOverlay(worldCloudOverlay);\r\n\ttrkData.worldIR.timer = setTimeout(refreshWorldIR, options.worldIRRefreshMinutes * 60 * 1000);\r\n\t$j(\"#map-layers-list .worldIR\").addClass(\"active\");\r\n\tCookies.set(\"worldIR\", true, { expires: 365, path: \"/\", secure: true });\r\n}\r\n\r\nexport function disableWorldIR() {\r\n\tif (!trkData.worldIR.isActive) return;\r\n\ttrkData.worldIR.isActive = false;\r\n\tclearTimeout(trkData.worldIR.timer);\r\n\tremoveOverlay(worldCloudOverlay);\r\n\r\n\tCookies.remove(\"worldIR\");\r\n\t$j(\"#map-layers-list .worldIR\").removeClass(\"active\");\r\n}\r\n\r\nexport function enableRadar() {\r\n\tif (trkData.radar.isActive) return;\r\n\r\n\ttrkData.radar.isActive = true;\r\n\taddOverlay(radarOverlay);\r\n\t// use setInterval instead\r\n\ttrkData.radar.timer = setTimeout(refreshRadar, options.radarRefreshMinutes * 60 * 1000);\r\n\t//$j('#radar a').addClass('active');\r\n\t$j(\"#map-layers-list .radar\").addClass(\"active\");\r\n\t$j(\"#legend_radar\").show();\r\n\tCookies.set(\"radar\", true, { expires: 365, path: \"/\", secure: true });\r\n}\r\n\r\nexport function disableRadar() {\r\n\tif (!trkData.radar.isActive) return;\r\n\ttrkData.radar.isActive = false;\r\n\tclearTimeout(trkData.radar.timer);\r\n\tremoveOverlay(radarOverlay);\r\n\tCookies.remove(\"radar\");\r\n\t$j(\"#legend_radar\").hide();\r\n\t$j(\"#map-layers-list .radar\").removeClass(\"active\");\r\n}\r\n\r\nexport function enableRadarAustraliaActive() {\r\n\tif (trkData.radarAustralia.isActive) return;\r\n\ttrkData.radarAustralia.isActive = true;\r\n\taddOverlay(radarAustraliaOverlay);\r\n\t// use setInterval instead\r\n\ttrkData.radarAustralia.timer = setTimeout(refreshRadarAustralia, options.radarRefreshMinutes * 60 * 1000);\r\n\t$j(\"#map-layers-list .radar-australia\").addClass(\"active\");\r\n\t$j(\"#legend_radar\").show();\r\n\tCookies.set(\"radar-australia\", true, { expires: 365, path: \"/\", secure: true });\r\n}\r\n\r\nexport function disableRadarAustralia() {\r\n\tif (!trkData.radarAustralia.isActive) {\r\n\t\treturn;\r\n\t}\r\n\ttrkData.radarAustralia.isActive = false;\r\n\tclearTimeout(trkData.radarAustralia.timer);\r\n\tremoveOverlay(radarAustraliaOverlay);\r\n\tCookies.remove(\"radar-australia\");\r\n\t$(\"#legend_radar\").hide();\r\n\t$(\"#map-layers-list .radar-australia\").removeClass(\"active\");\r\n}\r\n\r\nexport function checkWeatherForecast() {\r\n\tif (!trkData.weather.isActive) return;\r\n\tif (trkData.weather.gettingData) {\r\n\t\tif (trkData.weather.request != null) {\r\n\t\t\ttrkData.weather.request.abort();\r\n\t\t}\r\n\t\ttrkData.weather.gettingData = false;\r\n\t}\r\n\tgetWeatherForecast();\r\n}\r\n\r\nexport function getWeatherForecast() {\r\n\tvar bounds = map.getBounds();\r\n\tvar NE = bounds.getNorthEast();\r\n\tvar SW = bounds.getSouthWest();\r\n\tvar northLat = NE.lat;\r\n\tvar eastLng = NE.lng;\r\n\tvar southLat = SW.lat;\r\n\tvar westLng = SW.lng;\r\n\ttrkData.weather.gettingData = true;\r\n\tvar url =\r\n\t\t\"https://api.openweathermap.org/data/2.5/box/city?bbox=\" +\r\n\t\twestLng +\r\n\t\t\",\" +\r\n\t\tnorthLat +\r\n\t\t\",\" + //left top\r\n\t\teastLng +\r\n\t\t\",\" +\r\n\t\tsouthLat +\r\n\t\t\",\" + //right bottom\r\n\t\tmap.getZoom() +\r\n\t\t\"&cluster=yes&format=json\" +\r\n\t\t\"&APPID=\" +\r\n\t\t\"551b97ca557560dfc7d8c49a81b37d89\";\r\n\ttrkData.weather.request = $j.ajax({\r\n\t\ttype: \"GET\",\r\n\t\turl: wrapUrl(url),\r\n\t\tdataType: \"json\",\r\n\t\tcontentType: false,\r\n\t\tsuccess: function (results) {\r\n\t\t\tconsole.log(results);\r\n\t\t\tif (results == null) return;\r\n\t\t\tif (results.list == null) return;\r\n\t\t\tif (results.list.length > 0) {\r\n\t\t\t\t// reset data\r\n\t\t\t\ttrkData.weather.geoJSON = {\r\n\t\t\t\t\ttype: \"FeatureCollection\",\r\n\t\t\t\t\tfeatures: [],\r\n\t\t\t\t};\r\n\r\n\t\t\t\t// clear the prior set of data\r\n\t\t\t\ttrkData.weather.layer.clearLayers();\r\n\r\n\t\t\t\tfor (var i = 0; i < results.list.length; i++) {\r\n\t\t\t\t\ttrkData.weather.geoJSON.features.push(forecastJsonToGeoJson(results.list[i]));\r\n\t\t\t\t}\r\n\t\t\t\ttrkData.weather.layer.addData(trkData.weather.geoJSON);\r\n\t\t\t\ttrkData.weather.gettingData = false;\r\n\t\t\t}\r\n\t\t},\r\n\t\terror: function () {\r\n\t\t\ttrkData.weather.gettingData = false;\r\n\t\t},\r\n\t});\r\n}\r\n\r\nexport function forecastJsonToGeoJson(weatherItem) {\r\n\tvar feature = {\r\n\t\ttype: \"Feature\",\r\n\t\tproperties: {\r\n\t\t\tcity: weatherItem.name,\r\n\t\t\tweather: weatherItem.weather[0].main,\r\n\t\t\ttemperature: weatherItem.main.temp,\r\n\t\t\tmin: weatherItem.main.temp_min,\r\n\t\t\tmax: weatherItem.main.temp_max,\r\n\t\t\thumidity: weatherItem.main.humidity,\r\n\t\t\tpressure: weatherItem.main.pressure,\r\n\t\t\twindSpeed: weatherItem.wind.speed,\r\n\t\t\twindDegrees: weatherItem.wind.deg,\r\n\t\t\twindGust: weatherItem.wind.gust,\r\n\t\t\ticon: \"https://openweathermap.org/img/w/\" + weatherItem.weather[0].icon + \".png\",\r\n\t\t\tcoordinates: [weatherItem.coord.Lon, weatherItem.coord.Lat],\r\n\t\t},\r\n\t\tgeometry: {\r\n\t\t\ttype: \"Point\",\r\n\t\t\tcoordinates: [weatherItem.coord.Lon, weatherItem.coord.Lat],\r\n\t\t},\r\n\t};\r\n\t//// Set the custom marker icon\r\n\t//trkData.weather.layer.setStyle(function(feature) {\r\n\t// return {\r\n\t// icon: {\r\n\t// url: wrapUrl(feature.getProperty('icon')),\r\n\t// anchor: new google.maps.Point(25, 25)\r\n\t// }\r\n\t// };\r\n\t//});\r\n\t// returns object\r\n\treturn feature;\r\n}\r\n\r\nfunction TileToLocationRect(tile) {\r\n\tvar mapSize = Math.pow(2, tile.z);\r\n\tvar west = (tile.x * 360) / mapSize - 180;\r\n\tvar east = ((tile.x + 1) * 360) / mapSize - 180;\r\n\r\n\tvar efactor = Math.exp((0.5 - tile.y / mapSize) * 4 * Math.PI);\r\n\tvar north = Math.asin((efactor - 1) / (efactor + 1)) * (180 / Math.PI);\r\n\r\n\tefactor = Math.exp((0.5 - (tile.y + 1) / mapSize) * 4 * Math.PI);\r\n\tvar south = Math.asin((efactor - 1) / (efactor + 1)) * (180 / Math.PI);\r\n\tvar rect = L.latLngBounds(L.latLng(north, west), L.latLng(south, east));\r\n\treturn rect;\r\n}\r\n\r\nfunction onMapLoaded() {\r\n\tif (Cookies.get(\"radar\") !== undefined || $j.inArray(\"radar\", options.enableLayers) !== -1) {\r\n\t\tenableRadar();\r\n\t}\r\n\r\n\tif (Cookies.get(\"radar-australia\") !== undefined || $j.inArray(\"radar-australia\", options.enableLayers) !== -1) {\r\n\t\t//enableRadarAustralia(false, enableRadarAustraliaActive);\r\n\t\tenableRadarAustraliaActive();\r\n\t}\r\n\r\n\tif (Cookies.get(\"worldIR\") !== undefined || $j.inArray(\"worldIR\", options.enableLayers) !== -1) {\r\n\t\tenableWorldIR();\r\n\t}\r\n\r\n\tif (Cookies.get(\"layer-oil\") !== undefined || $j.inArray(\"oil\", options.enableLayers) !== -1) {\r\n\t\tenableLayerOil();\r\n\t}\r\n\r\n\tif (Cookies.get(\"layer-maritime\") !== undefined || $j.inArray(\"maritime\", options.enableLayers) !== -1) {\r\n\t\tenableLayerMaritime();\r\n\t}\r\n\r\n\tif (Cookies.get(\"layer-weather\") !== undefined || $j.inArray(\"weather\", options.enableLayers) !== -1) {\r\n\t\tenableLayerWeather(false);\r\n\t}\r\n\r\n\tif (Cookies.get(\"layer-clouds\") !== undefined || $j.inArray(\"clouds\", options.enableLayers) !== -1) {\r\n\t\tenableLayerClouds();\r\n\t}\r\n}\r\n","import { mapModes, panels } from \"./const.js\";\r\nimport state from \"./state.js\";\r\nimport options from \"./options.js\";\r\n\r\nexport function setUrlHistoryForActivePanel(panelAction) {\r\n\tvar action = \"\";\r\n\tvar defaultAction = \" \";\r\n\tif (panelAction !== undefined) {\r\n\t\taction = \"/\" + panelAction;\r\n\t\tdefaultAction = \"#\" + panelAction;\r\n\t}\r\n\tvar modeText =\r\n\t\tstate.activeMapMode === null ? options.defaultMode : state.activeMapMode === mapModes.HISTORY ? \"history\" : \"live\";\r\n\tswitch (state.activePanel) {\r\n\t\tcase panels.ASSETS:\r\n\t\t\thistory.replaceState(\"panel-active\", \"\", \"#\" + modeText + \"/assets\" + action);\r\n\t\t\tbreak;\r\n\t\tcase panels.GEOFENCES:\r\n\t\t\thistory.replaceState(\"panel-active\", \"\", \"#\" + modeText + \"/fences\" + action);\r\n\t\t\tbreak;\r\n\t\tcase panels.PLACES:\r\n\t\t\thistory.replaceState(\"panel-active\", \"\", \"#\" + modeText + \"/places\" + action);\r\n\t\t\tbreak;\r\n\t\tcase panels.JOURNEYS:\r\n\t\t\thistory.replaceState(\"panel-active\", \"\", \"#\" + modeText + \"/journeys\" + action);\r\n\t\t\tbreak;\r\n\t\tcase panels.SHARED_VIEWS:\r\n\t\t\thistory.replaceState(\"panel-active\", \"\", \"#\" + modeText + \"/shared-views\" + action);\r\n\t\t\tbreak;\r\n\t\tdefault:\r\n\t\t\thistory.replaceState(\"panel-active\", \"\", defaultAction);\r\n\t\t\tbreak;\r\n\t}\r\n}\r\n","import user from \"./user.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\nimport { throttles } from \"./timers.js\";\r\n\r\nimport $j from \"jquery\";\r\n\r\nexport let version = \"\";\r\n\r\nexport function overrideVersion(v) {\r\n\tversion = v;\r\n}\r\n\r\nexport function checkVersion() {\r\n\t$j.ajax({\r\n\t\ttype: \"POST\",\r\n\t\turl: wrapUrl(\"/services/GPSService.asmx/GetStatus\"),\r\n\t\tcontentType: \"application/json; charset=utf-8\",\r\n\t\tdataType: \"json\",\r\n\t\tsuccess: function (msg) {\r\n\t\t\tvar resp = msg.d;\r\n\t\t\tif (version != resp.Version || user.id != resp.UserId) {\r\n\t\t\t\tvar reloadTime = Math.floor(Math.random() * 300000);\r\n\t\t\t\tconsole.log(\r\n\t\t\t\t\t\"User or version mismatch. Version: \" +\r\n\t\t\t\t\t\tversion +\r\n\t\t\t\t\t\t\" vs \" +\r\n\t\t\t\t\t\tresp.Version +\r\n\t\t\t\t\t\t\". User: \" +\r\n\t\t\t\t\t\tuser.id +\r\n\t\t\t\t\t\t\" vs \" +\r\n\t\t\t\t\t\tresp.UserId +\r\n\t\t\t\t\t\t\". Reloading in \" + (reloadTime / 1000) + \"s.\"\r\n\t\t\t\t);\r\n\t\t\t\t// don't redirect immediately because this browser session could be the one initiating the logout and will naturally redirect\r\n\t\t\t\tsetTimeout(function () {\r\n\t\t\t\t\tlocation.reload(true);\r\n\t\t\t\t}, reloadTime);\r\n\t\t\t} else {\r\n\t\t\t\tconsole.log(\"Portal status check valid.\");\r\n\t\t\t}\r\n\t\t},\r\n\t\terror: function (xhr, status, error) {},\r\n\t});\r\n\tthrottles.updatePositionStatus();\r\n}\r\n","import strings from \"./strings.js\";\r\nimport trkData from \"./data.js\";\r\nimport domNodes from \"./domNodes.js\";\r\nimport log from \"./log.js\";\r\nimport { findAssetById } from \"./assets.js\";\r\nimport { formShowSuccessMessage } from \"./ajax.js\";\r\nimport { handleWebServiceError } from \"./ajax.js\";\r\nimport user from \"./user.js\";\r\nimport { wrapUrl } from \"./wrapurl.js\";\r\nimport { loadDialogButtons, closeButton } from \"./modal-dialog-buttons.js\";\r\nimport { getPositionLinkForEvent } from \"./positions.js\";\r\nimport { openDialogPanel } from \"./panel-nav.js\";\r\nimport { handleAjaxFormSubmission, toggleLoadingMessage } from \"./ajax.js\";\r\nimport { closeSecondaryPanel } from \"./panel.js\";\r\n\r\nimport $ from \"jquery\";\r\nimport $j from \"jquery\";\r\nimport _ from \"lodash\";\r\nimport { el, text } from \"redom\"; // https://redom.js.org/\r\n\r\nexport function initAlerts() {\r\n\t// unused TODO: remove\r\n\t$(\"#alert-data\").DataTable({\r\n\t\tdestroy: true,\r\n\t\tfilter: false,\r\n\t\tinfo: true,\r\n\t\tjQueryUI: true,\r\n\t\tautoWidth: false,\r\n\t\tlengthChange: false,\r\n\t\tpaging: true,\r\n\t\tpagingType: \"full_numbers\",\r\n\t\tdeferRender: true,\r\n\t\tprocessing: false,\r\n\t\torder: [[1, \"desc\"]],\r\n\t\tcolumns: [\r\n\t\t\t{ sortable: false, width: \"40px\" }, // Color Display\r\n\t\t\t{ sortable: true }, // Priority\r\n\t\t\t{ sortable: true }, // Asset\r\n\t\t\t{ sortable: true }, // Type\r\n\t\t\t{ sortable: false }, // Description\r\n\t\t\t{ sortable: false }, // Resolution Procedure\r\n\t\t\t{ sortable: false }, // Position/lat/lng\r\n\t\t\t//{ sortable: false }, // Details/link\r\n\t\t\t{ width: \"75px\" }, // Time\r\n\t\t\t{ sortable: false, class: \"center\" }, // Acknowledge\r\n\t\t\t{ visible: false }, // AssetId (hidden)\r\n\t\t\t{ visible: false }, // Event Type Id\r\n\t\t\t{ visible: false }, // Color\r\n\t\t],\r\n\t\tdom: '<\"H\"lfr>t<\"F\"ip>',\r\n\t\tlanguage: strings.DATATABLE,\r\n\t\tinitComplete: function (oSettings, json) {\r\n\t\t\tvar api = this.api();\r\n\t\t\tlog(\"Alerts table initialized.\");\r\n\t\t\t$j(\"#alert-data\").data(\"init\", true);\r\n\t\t\ttrkData.alerts = [];\r\n\t\t\tqueryAlertsRequiringAcknowledgement(null);\r\n\t\t\tapi.clear();\r\n\t\t},\r\n\t\tdrawCallback: function (oSettings) {\r\n\t\t\tvar api = this.api();\r\n\t\t\tvar rowCount = api.rows({ search: \"applied\" }).eq(0).length;\r\n\t\t\tif (rowCount > 0) {\r\n\t\t\t\t$j(\"#alerts-control-btn a .badge,#event-panel-tab-alerts .badge\")\r\n\t\t\t\t\t.text(rowCount)\r\n\t\t\t\t\t.addClass(\"active\")\r\n\t\t\t\t\t.removeClass(\"inactive\");\r\n\t\t\t} else {\r\n\t\t\t\t$j(\"#alerts-control-btn a .badge,#event-panel-tab-alerts .badge\")\r\n\t\t\t\t\t.text(rowCount)\r\n\t\t\t\t\t.removeClass(\"active\")\r\n\t\t\t\t\t.addClass(\"inactive\");\r\n\t\t\t}\r\n\r\n\t\t\t//$j('#alerts-control-btn a,#event-panel-tab-alerts').text(strings.ALERTS_HEADER.replace('{0}', api.rows({ search: 'applied' }).eq(0).length));\r\n\r\n\t\t\t//$j('.AlertAcknowledge').button({ text: null, icons: { primary: 'ui-icon-check' } });\r\n\t\t},\r\n\t\trowCallback: function (row, data, index) {\r\n\t\t\tif (data[11] != \"\") {\r\n\t\t\t\t$j(\"td:eq(0)\", row).css(\"background-color\", data[11]);\r\n\t\t\t}\r\n\t\t},\r\n\t});\r\n\r\n\tvar acknowledgeAlertButtons = [\r\n\t\t{\r\n\t\t\tid: \"AcknowledgeDialog\",\r\n\t\t\ttext: strings.ACKNOWLEDGE,\r\n\t\t\tclick: function () {\r\n\t\t\t\tacknowledgeAssetAlert(this, true);\r\n\t\t\t},\r\n\t\t},\r\n\t\t{\r\n\t\t\tid: \"AcknowledgeAltDialog\",\r\n\t\t\tbuttonType: \"secondary\",\r\n\t\t\tclass: \"alert-acknowledge-alt\",\r\n\t\t\ttext: strings.ACKNOWLEDGE_ALT,\r\n\t\t\tclick: function () {\r\n\t\t\t\tacknowledgeAssetAlert(this, true);\r\n\t\t\t},\r\n\t\t},\r\n\t\tcloseButton,\r\n\t];\r\n\tloadDialogButtons(domNodes.dialogs.acknowledgeAlert, acknowledgeAlertButtons);\r\n\t$(domNodes.dialogs.acknowledgeAlert).on(\"click\", \"#alert-prev\", function (e) {\r\n\t\te.preventDefault();\r\n\t\topenAlertInDialog(-1);\r\n\t});\r\n\t$(domNodes.dialogs.acknowledgeAlert).on(\"click\", \"#alert-next\", function (e) {\r\n\t\te.preventDefault();\r\n\t\topenAlertInDialog(1);\r\n\t});\r\n\r\n\t$(\"#panel-secondary\").on(\"click\", \"button.alert-acknowledge,button.alert-acknowledge-alt\", function (e) {\r\n\t\te.preventDefault();\r\n\t\tacknowledgeAssetAlert(this, false);\r\n\t});\r\n\r\n\t$j(domNodes.infoDialogs.mapItemInformation).on(\"click\", \"button.AlertAcknowledge\", function (e) {\r\n\t\te.preventDefault();\r\n\r\n\t\tacknowledgeAssetAlert(this, false);\r\n\t});\r\n\r\n\t$j(domNodes.infoDialogs.mapItemInformation).on(\"click\", \"button.AlertAcknowledgeAlt\", function (e) {\r\n\t\te.preventDefault();\r\n\r\n\t\tacknowledgeAssetAlert(this, false);\r\n\t});\r\n\r\n\tlet assetsContainer = $j(\"#panel-content-wrapper\");\r\n\r\n\tassetsContainer.on(\"click\", \"svg.asset-alert\", function (e) {\r\n\t\te.preventDefault();\r\n\r\n\t\tvar asset = findAssetById(parseInt(this.getAttribute(\"data-asset-id\")));\r\n\t\tif (asset == null) return;\r\n\t\tvar alert = null;\r\n\t\tfor (var i = 0; i < trkData.alerts.length; i++) {\r\n\t\t\tif (trkData.alerts[i].AssetId == asset.Id) {\r\n\t\t\t\talert = trkData.alerts[i];\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (alert == null) return;\r\n\t\topenAcknowledgeAlertDialog(asset, alert);\r\n\t\t//openAlertsRequiringAcknowledgementDialog(alt);\r\n\t});\r\n}\r\n\r\nfunction updateAlertDialog(event) {\r\n\tvar form = document.getElementById(\"acknowledge-form\");\r\n\tvar currentEventId = parseInt(form.getAttribute(\"data-event-id\"));\r\n\tvar currentAssetId = parseInt(form.getAttribute(\"data-asset-id\"));\r\n\tvar currentIndex = parseInt(form.getAttribute(\"data-index\"));\r\n\r\n\tvar asset = findAssetById(currentAssetId);\r\n\tif (asset === null) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar assetAlerts = _.filter(trkData.alerts, function (evt) {\r\n\t\treturn evt.AssetId === asset.Id;\r\n\t});\r\n\tif (event.Id === currentEventId) {\r\n\t\t// the currently opened event was acknowledged\r\n\t\t// move to the next one, if available, with an acknowledged status\r\n\t\t// and show a no more if not\r\n\t\tif (assetAlerts.length === 0) {\r\n\t\t\t// no more events requiring acknowledgement for the asset\r\n\t\t\tvar none = document.getElementById(\"asset-acknowledge-alerts-none\");\r\n\t\t\tnone.classList.add(\"is-visible\");\r\n\t\t\tform.classList.remove(\"is-visible\");\r\n\t\t} else {\r\n\t\t\tvar newIndex = currentIndex;\r\n\t\t\tif (currentIndex > assetAlerts.length) {\r\n\t\t\t\tnewIndex = assetAlerts.length;\r\n\t\t\t}\r\n\t\t\tvar event = assetAlerts[newIndex - 1];\r\n\t\t\tif (event !== undefined && event !== null) {\r\n\t\t\t\topenAcknowledgeAlertDialog(asset, event);\r\n\r\n\t\t\t\t// persist acknowledged message\r\n\t\t\t\tformShowSuccessMessage(form.querySelector(\".dialog-status\"), strings.MSG_ALERT_ACKNOWLEDGE_SUCCESS);\r\n\t\t\t}\r\n\t\t}\r\n\t} else {\r\n\t\tif (event.AssetId === currentAssetId) {\r\n\t\t\t// number of events is updated, update the indexes shown\r\n\t\t\tvar currentEvent = _.find(assetAlerts, { Id: currentEventId });\r\n\t\t\tpopulateAssetAlertIndex(asset, currentEvent);\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction confirmAlert(event) {\r\n\tlog(\"Confirming alert.\");\r\n\tvar isInAlertsListing = false;\r\n\tfor (var i = 0; i < trkData.alerts.length; i++) {\r\n\t\tif (event.Id === trkData.alerts[i].Id) {\r\n\t\t\ttrkData.alerts.splice(i, 1);\r\n\t\t\tisInAlertsListing = true;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\t// update dialogs, if they are open\r\n\tupdateAlertDialog(event);\r\n\r\n\t// update position information dialog (if exists)\r\n\tvar form = $(\"#AcknowledgeAlert\" + event.Id + \",#asset-alert-acknowledge-\" + event.Id);\r\n\tif (form.length) {\r\n\t\t$(\"div.alert-resolution\", form)\r\n\t\t\t.empty()\r\n\t\t\t.append(\r\n\t\t\t\t$('
      ')\r\n\t\t\t\t\t.append($(\"