From 43b9a6b39fd393355a9688911714d4583bcf86d5 Mon Sep 17 00:00:00 2001 From: Artem Halushko Date: Mon, 2 Mar 2020 12:15:14 +0200 Subject: [PATCH] added image map support --- ui-ngx/src/app/core/utils.ts | 40 ++++++- .../components/widget/lib/maps/leaflet-map.ts | 8 +- .../components/widget/lib/maps/map-models.ts | 1 + .../components/widget/lib/maps/map-widget2.ts | 15 ++- .../widget/lib/maps/providers/image-map.ts | 112 ++++++++++++++++++ .../widget/lib/maps/providers/index.ts | 2 + 6 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 381aadfdb7..d46b190206 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -15,8 +15,8 @@ /// import _ from 'lodash'; -import { Observable, Subject } from 'rxjs'; -import { finalize, share } from 'rxjs/operators'; +import { Observable, Subject, from, fromEvent, of } from 'rxjs'; +import { finalize, share, map } from 'rxjs/operators'; import base64js from 'base64-js'; export function onParentScrollOrWindowResize(el: Node): Observable { @@ -221,6 +221,18 @@ function scrollParents(node: Node): Node[] { return scrollParentNodes; } +function hashCode(str) { + var hash = 0; + var i, char; + if (str.length == 0) return hash; + for (i = 0; i < str.length; i++) { + char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // Convert to 32bit integer + } + return hash; +} + function easeInOut( currentTime: number, startTime: number, @@ -411,3 +423,27 @@ export function snakeCase(name: string, separator: string): string { export function getDescendantProp(obj: any, path: string): any { return path.split('.').reduce((acc, part) => acc && acc[part], obj); } + +export function imageLoader(imageUrl: string): Observable{ + const image = new Image(); + const imageLoad$ = fromEvent(image, 'load').pipe(map(event=>image)); + image.src = imageUrl; + return imageLoad$; +} + +const imageAspectMap = {}; + +export function aspectCache(imageUrl: string): Observable{ + if(imageUrl?.length){ + const hash = hashCode(imageUrl); + let aspect = imageAspectMap[hash]; + if(aspect){ + return of(aspect); + } + else return imageLoader(imageUrl).pipe(map(image=>{ + aspect = image.width/image.height; + imageAspectMap[hash] = aspect; + return aspect; + })) + } +} \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index 26b1cecd81..6e21d3df87 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -12,11 +12,11 @@ export default class LeafletMap { markers = []; tooltips = []; map: L.Map; - options; + options: MapOptions; isMarketCluster; - constructor($container, options: MapOptions) { + constructor($container: HTMLElement, options: MapOptions) { this.options = options; } @@ -105,6 +105,10 @@ export default class LeafletMap { } } + onResize(){ + + } + getTooltips() { return this.tooltips;//rewrite } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts index 1b86d5ef34..05e3728d18 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts @@ -5,6 +5,7 @@ export interface MapOptions { disableScrollZooming?: boolean, minZoomLevel?: number, mapProvider: MapProviders, + mapUrl?: string; credentials?: any, // declare credentials format defaultCenterPosition?: L.LatLngExpression, markerClusteringSetting? diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts index a307c53d33..5cb11e9262 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts @@ -3,8 +3,7 @@ import LeafletMap from './leaflet-map'; import { deepClone } from '@core/utils'; import { openstreetMapSettingsSchema, googleMapSettingsSchema, imageMapSettingsSchema, tencentMapSettingsSchema, hereMapSettingsSchema, commonMapSettingsSchema, routeMapSettingsSchema, markerClusteringSettingsSchema, markerClusteringSettingsSchemaGoogle, markerClusteringSettingsSchemaLeaflet } from './schemes'; import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface'; -import { OpenStreetMap, TencentMap } from './providers'; -import { GoogleMap } from './providers/google-map'; +import { OpenStreetMap, TencentMap, ImageMap, GoogleMap } from './providers'; const providerSets = { 'openstreet-map': { @@ -18,6 +17,10 @@ const providerSets = { 'google-map': { MapClass: GoogleMap, schema: googleMapSettingsSchema + }, + 'image-map': { + MapClass: ImageMap, + schema: imageMapSettingsSchema } } @@ -28,6 +31,8 @@ TbMapWidgetV2 = class TbMapWidgetV2 implements MapWidgetInterface { schema; constructor(mapProvider: MapProviders, drawRoutes, ctx, useDynamicLocations, $element, isEdit) { + console.log(ctx.settings); + // if(!$element) return if (!$element) { $element = ctx.$container[0]; @@ -40,11 +45,11 @@ TbMapWidgetV2 = class TbMapWidgetV2 implements MapWidgetInterface { disableScrollZooming: false, minZoomLevel: drawRoutes ? 18 : 15, mapProvider: mapProvider, + mapUrl: ctx?.settings?.mapImageUrl, credentials: '', defaultCenterPosition: [0, 0], markerClusteringSetting: null } - console.log("TCL: TbMapWidgetV2 -> constructor -> providerSets[mapProvider]",mapProvider) let MapClass = providerSets[mapProvider]?.MapClass; if(!MapClass){ //delete this; @@ -62,7 +67,8 @@ TbMapWidgetV2 = class TbMapWidgetV2 implements MapWidgetInterface { } onResize() { - } + this.map.onResize();//not work + } getSettingsSchema(): Object { return this.schema; @@ -70,6 +76,7 @@ TbMapWidgetV2 = class TbMapWidgetV2 implements MapWidgetInterface { resize() { this.map?.invalidateSize(); + this.map.onResize(); } public static dataKeySettingsSchema(): Object { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts new file mode 100644 index 0000000000..b533f9ea6d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts @@ -0,0 +1,112 @@ +import L from 'leaflet'; +import LeafletMap from '../leaflet-map'; +import { MapOptions } from '../map-models'; +import { aspectCache } from '@app/core/utils'; + +const maxZoom = 4;//? + +export class ImageMap extends LeafletMap { + + imageOverlay; + aspect = 0; + width = 0; + height = 0; + + constructor(private $container: HTMLElement, options: MapOptions) { + super($container, options); + aspectCache(options.mapUrl).subscribe(aspect => { + this.aspect = aspect; + this.onResize(); + super.setMap(this.map); + super.initSettings(options); + }); + } + + updateBounds(updateImage?, lastCenterPos?) { + const w = this.width; + const h = this.height; + let southWest = this.pointToLatLng(0, h); + let northEast = this.pointToLatLng(w, 0); + const bounds = new L.LatLngBounds(southWest, northEast); + + if (updateImage && this.imageOverlay) { + this.imageOverlay.remove(); + this.imageOverlay = null; + } + + if (this.imageOverlay) { + this.imageOverlay.setBounds(bounds); + } else { + this.imageOverlay = L.imageOverlay(this.options.mapUrl, bounds).addTo(this.map); + + } + const padding = 200 * maxZoom; + southWest = this.pointToLatLng(-padding, h + padding); + northEast = this.pointToLatLng(w + padding, -padding); + const maxBounds = new L.LatLngBounds(southWest, northEast); + this.map.setMaxBounds(maxBounds); + if (lastCenterPos) { + lastCenterPos.x *= w; + lastCenterPos.y *= h; + /* this.ctx.$scope.$injector.get('$mdUtil').nextTick(() => { + this.map.panTo(center, { animate: false }); + });*/ + } + } + + onResize(updateImage?) { + let width = this.$container.clientWidth; + if (width > 0 && this.aspect) { + let height = width / this.aspect; + const imageMapHeight = this.$container.clientHeight; + if (imageMapHeight > 0 && height > imageMapHeight) { + height = imageMapHeight; + width = height * this.aspect; + } + width *= maxZoom; + const prevWidth = this.width; + const prevHeight = this.height; + if (this.width !== width) { + this.width = width; + this.height = width / this.aspect; + if (!this.map) { + this.initMap(updateImage); + } else { + const lastCenterPos = this.latLngToPoint(this.map.getCenter()); + lastCenterPos.x /= prevWidth; + lastCenterPos.y /= prevHeight; + this.updateBounds(updateImage, lastCenterPos); + this.map.invalidateSize(true); + // this.updateMarkers(); + } + + } + } + } + + initMap(updateImage?) { + if (!this.map && this.aspect > 0) { + var center = this.pointToLatLng(this.width / 2, this.height / 2); + this.map = L.map(this.$container, { + minZoom: 1, + maxZoom: maxZoom, + scrollWheelZoom: !this.options.disableScrollZooming, + center: center, + zoom: 1, + crs: L.CRS.Simple, + attributionControl: false + }); + this.updateBounds(updateImage); + // this.updateMarkers(); + } + } + + + pointToLatLng(x, y) { + return L.CRS.Simple.pointToLatLng({ x, y } as L.PointExpression, maxZoom - 1); + } + + latLngToPoint(latLng) { + return L.CRS.Simple.latLngToPoint(latLng, maxZoom - 1); + } +} \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/index.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/index.ts index 46d700b2f1..5f0b6dac84 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/index.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/index.ts @@ -1,2 +1,4 @@ export * from './tencent-map'; +export * from './google-map'; +export * from './image-map'; export * from './openstreet-map';