diff options
Diffstat (limited to 'src/generic-components/map/TopographicMap.jsx')
-rw-r--r-- | src/generic-components/map/TopographicMap.jsx | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/src/generic-components/map/TopographicMap.jsx b/src/generic-components/map/TopographicMap.jsx new file mode 100644 index 0000000..d4e91d6 --- /dev/null +++ b/src/generic-components/map/TopographicMap.jsx @@ -0,0 +1,260 @@ +/* + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +import React, {Component, PropTypes} from 'react'; +import {geoAlbersUsa, geoEquirectangular, geoPath} from 'd3-geo'; +import {feature, mesh} from 'topojson'; + +import { + BOUNDARY_MESH_KEY, + LAND_FEATURE_KEY, + MAP_OBJECT_KEYS, + PLOT_POINT_KEY_BASE, + PLOT_POINT_SHAPES, + PROJECTION_TYPES +} from './MapConstants.js'; +import usMapJson from './mapJson/usJson.json'; +import worldMapJson from './mapJson/worldJson.json'; + +class TopographicMap extends Component { + static propTypes = { + width: PropTypes.number, + height: PropTypes.number, + pointArray: PropTypes.array, + currentProjection: PropTypes.string + }; + + static defaultProps = { + width: 840, + height: 500, + pointArray: [], + currentProjection: PROJECTION_TYPES.ALBERS_USA + }; + + constructor(props) { + super(props); + + this.state = { + landFeatures: undefined, + boundaryMesh: undefined, + plotPoints: [] + }; + + this.setCurrentProjection = this.setCurrentProjection.bind(this); + this.processAndRenderMapData = this.processAndRenderMapData.bind(this); + this.generateLandFeatures = this.generateLandFeatures.bind(this); + this.generateBoundaryMesh = this.generateBoundaryMesh.bind(this); + this.generatePlotPointArray = this.generatePlotPointArray.bind(this); + this.extractNestedObjectInJson = this.extractNestedObjectInJson.bind(this); + this.areArraysEqual = this.areArraysEqual.bind(this); + + this.setCurrentProjection(props.currentProjection); + this.projection.translate([this.props.width / 2, this.props.height / 2]); + this.path = geoPath().projection(this.projection); + this.didProjectionTypeChange = true; + this.isMapMounted = false; + } + + componentWillReceiveProps(nextProps) { + if (!this.areArraysEqual(this.props.pointArray, nextProps.pointArray)) { + if (this.props.currentProjection !== nextProps.currentProjection) { + this.didProjectionTypeChange = true; + this.setCurrentProjection(nextProps.currentProjection); + } + if (this.isMapMounted) { + this.processAndRenderMapData(nextProps.pointArray); + } + } + } + + componentDidMount() { + this.isMapMounted = true; + this.processAndRenderMapData(this.props.pointArray); + } + + setCurrentProjection(projectionType) { + switch (projectionType) { + case PROJECTION_TYPES.ALBERS_USA: + this.projection = geoAlbersUsa(); + break; + + case PROJECTION_TYPES.EQUIRECTANGULAR: + this.projection = geoEquirectangular(); + break; + + default: + // TODO -> FE logging should be a thing at some point. Maybe a log call + // to the BE? + break; + } + } + + processAndRenderMapData(plotPointArray) { + let landFeatures = this.state.landFeatures; + let boundaryMesh = this.state.boundaryMesh; + let plotPoints = this.state.plotPoints; + + switch (this.props.currentProjection) { + case PROJECTION_TYPES.ALBERS_USA: + if (this.didProjectionTypeChange) { + landFeatures = + this.generateLandFeatures(usMapJson, + MAP_OBJECT_KEYS.ALBERS_USA_LAND_KEYS); + boundaryMesh = + this.generateBoundaryMesh(usMapJson, + MAP_OBJECT_KEYS.ALBERS_USA_BOUNDARY_KEYS); + this.didProjectionTypeChange = false; + } + plotPoints = this.generatePlotPointArray(plotPointArray); + break; + case PROJECTION_TYPES.EQUIRECTANGULAR: + if (this.didProjectionTypeChange) { + landFeatures = + this.generateLandFeatures(worldMapJson, + MAP_OBJECT_KEYS.EQUIRECTANGULAR_LAND_KEYS); + boundaryMesh = + this.generateBoundaryMesh(worldMapJson, + MAP_OBJECT_KEYS.EQUIRECTANGULAR_BOUNDARY_KEYS); + this.didProjectionTypeChange = false; + } + plotPoints = this.generatePlotPointArray(plotPointArray); + break; + default: + + // TODO -> FE logging should be a thing at some point. Maybe a log call + // to the BE? + break; + } + + this.setState(() => { + return { + landFeatures: landFeatures, + boundaryMesh: boundaryMesh, + plotPoints: plotPoints + }; + }); + } + + generateLandFeatures(jsonData, featureKeys) { + let featureType = this.extractNestedObjectInJson(jsonData, featureKeys); + let landFeature = undefined; + if (featureType !== undefined) { + let landFeaturePath = this.path(feature(jsonData, featureType)); + let landFeatureProps = { + className: 'land-features', + d: landFeaturePath, + key: LAND_FEATURE_KEY + }; + landFeature = + React.createElement(PLOT_POINT_SHAPES.PATH, landFeatureProps); + } + return landFeature; + } + + generateBoundaryMesh(jsonData, boundaryKeys) { + let boundaryType = this.extractNestedObjectInJson(jsonData, boundaryKeys); + let boundary = undefined; + if (boundaryType !== undefined) { + let boundaryPath = this.path(mesh(jsonData, boundaryType, (a, b) => { + return a !== b; + })); + let boundaryProps = { + className: 'boundary-mesh', + d: boundaryPath, + key: BOUNDARY_MESH_KEY + }; + boundary = React.createElement(PLOT_POINT_SHAPES.PATH, boundaryProps); + } + return boundary; + } + + generatePlotPointArray(pointArray) { + let generatedPoints = []; + if (pointArray !== undefined && pointArray.length > 0) { + generatedPoints = pointArray.map((longLat, index) => { + let projectedLongLat = this.projection( + [longLat.longitude, longLat.latitude]); + let plotPointProps = { + className: 'plot-point', + r: 4, + cx: projectedLongLat[0], + cy: projectedLongLat[1], + key: PLOT_POINT_KEY_BASE + index, + }; + return React.createElement(PLOT_POINT_SHAPES.CIRCLE, plotPointProps); + }); + } + return generatedPoints; + } + + render() { + let {landFeatures, boundaryMesh, plotPoints} = this.state; + let {width, height} = this.props; + + return ( + <div width={width} height={height}> + <svg width={width} height={height}> + <g> + {landFeatures} + {boundaryMesh} + {plotPoints} + </g> + </svg> + </div> + ); + } + + extractNestedObjectInJson(jsonData, keysArray) { + let subObject = undefined; + if (jsonData !== undefined && keysArray !== undefined) { + subObject = jsonData[keysArray[0]]; + if (subObject !== undefined) { + for (let i = 1; i < keysArray.length; i++) { + subObject = subObject[keysArray[i]]; + } + } + } + return subObject; + } + + areArraysEqual(arrayOne, arrayTwo) { + if (arrayOne.length !== arrayTwo.length) { + return false; + } + for (let i = 0; i < arrayOne.length; i++) { + if (arrayOne[i] instanceof Array && arrayTwo instanceof Array) { + if (!this.areArraysEqual(arrayOne[i], arrayTwo[i])) { + return false; + } + } + else if (arrayOne[i] !== arrayTwo[i]) { + return false; + } + } + return true; + } +} + +export default TopographicMap; |