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);