Source: components/userComponents/monitoredArea/statisticsTabs/MapOfTransits.js

import React from "react";
import {SimpleHereMap} from '../../maps/SimpleHereMap';
import {Checkbox} from "react-ui-icheck";
import {withNamespaces} from "react-i18next";
import ReactDOMServer from "react-dom/server";
import PassDetail from "../detail/PassDetail";
import {createColorsConfig, getColor} from '../../../../Utils';
import CountLegend from '../legend/CountLegend';

/**
 * Statistics Map of transits Tab
 * @extends Component
 * @hideconstructor
 */
class MapOfTransits extends React.Component {
  /**
   * @constructs
   * @param props
   */
	constructor(props) {
		super(props);
		/* Initialize React ref to map */
		this.hereMap = React.createRef();
		this.state = {
			checkedShowZones: true,
			checkedShowTrajectories: true,
			checkedShowTransits: true,
			zoneShapes: [],
			passShapes: [],
			infoBubble: undefined,
			passDetail: undefined,
		};
	}

  /**
	 *
   * @param zoneA
   * @param zoneB
   * @param color
   * @returns {Window.H.map.Polyline}
   */
	createPassShape = (zoneA, zoneB, color) => {
		let pts = [];
		const centroidA = this.calculateCentroid(zoneA.worldVertices.points);
		const centroidB = this.calculateCentroid(zoneB.worldVertices.points);
		const centroids = this.recalculateCentroids(centroidA, centroidB);
		pts.push(centroids[0]);
		pts.push(centroids[1]);
		return this.createPolyline(pts, color);
	};
  /**
	 *
   * @param zoneA
   * @param color
   * @returns {Window.H.map.Polygon}
   */
	createZoneShape = (zoneA, color) => {
		const pts = zoneA.worldVertices.points.map(point => {
			return {lat: point.x, lng: point.y}
		});
		return this.createPolygone(pts, color);
	};
  /**
	 *
   * @param points
   * @param color
   * @returns {window.H.map.Polyline}
   */
	createPolyline = (points, color) => {
		//Create polyline points
		let lineString = new window.H.geo.LineString();
		points.map(point => lineString.pushPoint(point));

		let polyLineStyleConfig = {
			strokeColor: color,
			fillColor: color,
			lineWidth: 10
		};
		let arrowStyleConfig = {fillColor: 'white', frequency: 2, width: 1, length: 0.9};
		let polylineOptions = {style: polyLineStyleConfig, arrows: arrowStyleConfig};

		//create polygon from the linestring
		return new window.H.map.Polyline(lineString, polylineOptions);
	};
  /**
	 *
   * @param points
   * @param color
   * @returns {window.H.map.Polygon}
   */
	createPolygone = (points, color) => {
		let lineString = new window.H.geo.LineString();
		points.map(point => {
			lineString.pushPoint(point);
		});

		//create polygon from the linestring
		return new window.H.map.Polygon(lineString, {
			style: {
				fillColor: color,
				strokeColor: color,
				lineWidth: 3
			}
		});
	};
  /**
	 *
   * @param geoShape
   */
	addEventListeners = (geoShape) => {
		geoShape.addEventListener('pointerenter', this.handlePointerEnter);
	};
/**
 */
	handlePointerEnter = (evt) => {
		if (this.state.infoBubble) {
			this.state.infoBubble.close()
		}

		let coord = this.hereMap.current.screenToGeo(evt.currentPointer.viewportX, evt.currentPointer.viewportY);
		const bubble = new window.H.ui.InfoBubble(coord, {
			// read custom data
			content: ReactDOMServer.renderToStaticMarkup(
				<div className="box-body">
					<div className="text-primary">{evt.target.getData().count}</div>
					<div className="text-sm">{evt.target.getData().name}</div>
				</div>)
		});
		this.state.infoBubble = bubble;
		this.hereMap.current.addBubbleToUi(this.state.infoBubble);
		this.setState({
			passDetail: evt.target.getData()
		})
	};

  /**
	 *
   * @param event
   */
	handleCheckboxChanged = (event) => {
		const target = event.target;
		const value = target.checked;
		const name = target.name;

		this.setState({
			[name]: value
		});
	};

  /**
   * @typedef {Object} Centroid
   * @property {number} lat
   * @property {number} lng
	 * @memberOf MapOfTransits
   */

  /**
	 *
   * @param coords
   * @returns {{Centroid}}
   */
	calculateCentroid = (coords) => {

		const averageLat = coords.reduce((total, next) => total + next.x, 0) / coords.length;
		const averageLng = coords.reduce((total, next) => total + next.y, 0) / coords.length;

		return {lat: averageLat, lng: averageLng};
	};

  /**
	 *
   * @param centroidA
   * @param centroidB
   * @returns {(Centroid|Array)}
   */
	recalculateCentroids = (centroidA, centroidB) => {
		const bias = 0.000016; //posun priblizne 1.1132 m
		const deltaLat = Math.abs(Math.abs(centroidA.lat) - Math.abs(centroidB.lat));
		const deltaLng = Math.abs(Math.abs(centroidA.lng) - Math.abs(centroidB.lng));
		if (centroidA.lat < centroidB.lat && centroidA.lng < centroidB.lng) {
			if (deltaLat > bias) {
				centroidA.lat = centroidA.lat - bias;
				centroidB.lat = centroidB.lat - bias;
			}

			if (deltaLng > bias) {
				centroidA.lng = centroidA.lng + bias;
				centroidB.lng = centroidB.lng + bias;
			}
		} else if (centroidA.lat < centroidB.lat && centroidA.lng > centroidB.lng) {
			if (deltaLat > bias) {
				centroidA.lat = centroidA.lat + bias;
				centroidB.lat = centroidB.lat + bias;
			}

			if (deltaLng > bias) {
				centroidA.lng = centroidA.lng + bias;
				centroidB.lng = centroidB.lng + bias;
			}
		} else if (centroidA.lat > centroidB.lat && centroidA.lng > centroidB.lng) {
			if (deltaLat > bias) {
				centroidA.lat = centroidA.lat + bias;
				centroidB.lat = centroidB.lat + bias;
			}
			if (deltaLng > bias) {
				centroidA.lng = centroidA.lng - bias;
				centroidB.lng = centroidB.lng - bias;
			}
		} else if (centroidA.lat > centroidB.lat && centroidA.lng < centroidB.lng) {
			if (deltaLat > bias) {
				centroidA.lat = centroidA.lat - bias;
				centroidB.lat = centroidB.lat - bias;
			}

			if (deltaLng > bias) {
				centroidA.lng = centroidA.lng - bias;
				centroidB.lng = centroidB.lng - bias;
			}
		}
		return [centroidA, centroidB];
	};

  /**
	 *
   * @param isChecked
   */
	showZones = (isChecked) => {

		if (this.state.zoneShapes.length > 0) this.hereMap.current.removeObjectsFromMap(this.state.zoneShapes);
		//remove existing zones
		this.state.zoneShapes = [];

		if (isChecked) {
			//Vykreslenie zón
			if (this.props.areaStatistics
				&& this.props.areaStatistics.zones && this.props.areaStatistics.zones.length > 0) {

				const passes = this.props.areaStatistics.passes;
				const zones = this.props.areaStatistics.zones;

				let agregatedCounts = zones.map(zone => {
					return passes
						.filter(pass => pass.zoneNumbersSequence
							.includes(zone.zoneNumber))
						.reduce((total, pass) => total + pass.count, 0)
				});
				const colorsConfig = createColorsConfig(agregatedCounts);
				this.state.zoneShapes = zones.map(zone => {
					//let pass = passes.find(pass => pass.zoneNumbersSequence.length === 1 && zone.zoneNumber === pass.zoneNumbersSequence[0]);
					let agregatedCount = passes
						.filter(pass => pass.zoneNumbersSequence
							.includes(zone.zoneNumber))
						.reduce((total, pass) => total + pass.count, 0);

					let zoneShape = this.createZoneShape(zone, getColor(agregatedCount, colorsConfig));
					zoneShape.setData(
						{
							name: zone.name,
							count: agregatedCount ? agregatedCount : 0
						});
					this.addEventListeners(zoneShape);
					return zoneShape;
				});

				this.state.zoneShapes
					.filter(zoneShape => zoneShape)
					.map(zoneShape => this.hereMap.current.addObjectToMap(zoneShape));
			}
		}
	};

  /**
	 *
   * @param isChecked
   */
	showPasses = (isChecked) => {
		//remove existing transits as polylines
		if (this.state.passShapes.length > 0) {
			this.hereMap.current.removeObjectsFromMap(this.state.passShapes.filter(polyline => polyline))
		}

		this.state.passShapes = [];

		if (isChecked) {
			//Vykreslenie prejazdov
			if (this.props.areaStatistics
				&& this.props.areaStatistics.passes && this.props.areaStatistics.passes.length > 0
				&& this.props.areaStatistics.zones && this.props.areaStatistics.zones.length > 0) {

				const passes = this.props.areaStatistics.passes;
				const zones = this.props.areaStatistics.zones;
				const colorsConfig = createColorsConfig(passes.map(pass => pass.count));

				this.state.passShapes = passes.filter(pass => pass.zoneNumbersSequence.length === 2).map(pass => {
					const zoneA = this.findZoneByZoneNumber(pass.zoneNumbersSequence[0], zones);
					const zoneB = this.findZoneByZoneNumber(pass.zoneNumbersSequence[1], zones);
					let passShape = this.createPassShape(zoneA, zoneB, getColor(pass.count, colorsConfig));
					passShape.setData({
						name: pass.name,
						count: pass.count,
						tableOfTransits: pass.transits
					});
					this.addEventListeners(passShape);
					return passShape;
				});

				this.state.passShapes.map(geoShape => {
					if (geoShape) this.hereMap.current.addObjectToMap(geoShape);
				});
			}
		}
	};

  /**
	 *
   * @param zoneNumber
   * @param zones
   * @returns {*}
   */
	findZoneByZoneNumber = (zoneNumber, zones) => {
		if (zones && zones.length > 0) {
			const zone = zones.find(z => z.zoneNumber === zoneNumber);
			return zone;
		} else {
			console.log("ERROR findZoneByZoneNumber: " + zoneNumber + ": " + JSON.stringify(zones));
			return undefined;
		}
	};

  /**
	 *
   */
	updateMap = () => {
		this.showZones(this.state.checkedShowZones);
		this.showPasses(this.state.checkedShowTransits);
		if (this.state.infoBubble) {
			this.state.infoBubble.close()
		}
		if (this.props.area !== undefined && this.props.area !== null) {
      this.hereMap.current.setCenter(this.props.area.deviceLatitude, this.props.area.deviceLongitude);
		}
	};

  /**
	 *
   */
	componentDidMount() {
		this.updateMap();
	}

  /**
	 *
   * @param prevProps
   * @param prevState
   * @param snapshot
   */
	componentDidUpdate(prevProps, prevState, snapshot) {
		this.updateMap();
	}

  /**
	 *
   * @returns {*}
   */
	render() {
		const {t} = this.props;
		let map = null;
		if (this.props.area === undefined || this.props.area === null) {
			map = <SimpleHereMap width="100%" height="400px" ref={this.hereMap}
			/>
		} else {
			map = <SimpleHereMap width="100%" height="400px" ref={this.hereMap}
			                     lat={this.props.area.deviceLatitude}
			                     lng={this.props.area.deviceLongitude}
			                     zoom={19}
			/>
		}
		return (

			<div>
				<div className="box">

					<div className="box-header">
						<div className="col-md-6">
							<h3 className="box-title text-right">{t('monitoredArea.mapOfTransits')}</h3>
						</div>
					</div>

					<div className="box-body col-xs-12">
						<div className="input-group col-xs-12">
							<div className="col-xs-12 col-md-6 col-lg-4" data-toggle="tooltip"
									 title={t('realView.showTrajectoriesTooltip')}>
								<Checkbox
									checkboxClass="icheckbox_square-blue"
									name={'checkedShowZones'}
									checked={this.state.checkedShowZones}
									label={t('monitoredArea.showZones')}
									onChange={this.handleCheckboxChanged}
								/>
							</div>

							<div className="col-xs-12 col-md-6 col-lg-4" data-toggle="tooltip"
                   title={t('realView.showTrajectoriesTooltip')}>
								<Checkbox
									checkboxClass="icheckbox_square-blue"
									name={'checkedShowTransits'}
									checked={this.state.checkedShowTransits}
									label={t('monitoredArea.showTransits')}
									onChange={this.handleCheckboxChanged}
								/>
							</div>
						</div>
						<div className="col-xs-12">
							<div>
								<div className="box-body">
									<div className="col-xs-12 col-lg-8">
                    <div className="box-body">
										{map}
										</div>
									</div>
									<div className="col-xs-12 col-lg-4">
                    <div className="box-body">
										<PassDetail
											data={this.state.passDetail? this.state.passDetail : {}}
										/>
										</div>
									</div>
								</div>
								<div className="box-footer no-border">
								</div>
							</div>
						</div>
					</div>
					<div className="box-footer">
					</div>
				</div>


			</div>
		);
	}
}

export default withNamespaces()(MapOfTransits);