Initial coomit for AAI-UI(sparky-fe)
[aai/sparky-fe.git] / src / generic-components / map / TopographicMap.jsx
1 /*
2  * ============LICENSE_START===================================================
3  * SPARKY (AAI UI service)
4  * ============================================================================
5  * Copyright © 2017 AT&T Intellectual Property.
6  * Copyright © 2017 Amdocs
7  * All rights reserved.
8  * ============================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * ============LICENSE_END=====================================================
21  *
22  * ECOMP and OpenECOMP are trademarks
23  * and service marks of AT&T Intellectual Property.
24  */
25
26 import React, {Component, PropTypes} from 'react';
27 import {geoAlbersUsa, geoEquirectangular, geoPath} from 'd3-geo';
28 import {feature, mesh} from 'topojson';
29
30 import {
31   BOUNDARY_MESH_KEY,
32   LAND_FEATURE_KEY,
33   MAP_OBJECT_KEYS,
34   PLOT_POINT_KEY_BASE,
35   PLOT_POINT_SHAPES,
36   PROJECTION_TYPES
37 } from './MapConstants.js';
38 import usMapJson from './mapJson/usJson.json';
39 import worldMapJson from  './mapJson/worldJson.json';
40
41 class TopographicMap extends Component {
42   static propTypes = {
43     width: PropTypes.number,
44     height: PropTypes.number,
45     pointArray: PropTypes.array,
46     currentProjection: PropTypes.string
47   };
48
49   static defaultProps = {
50     width: 840,
51     height: 500,
52     pointArray: [],
53     currentProjection: PROJECTION_TYPES.ALBERS_USA
54   };
55
56   constructor(props) {
57     super(props);
58
59     this.state = {
60       landFeatures: undefined,
61       boundaryMesh: undefined,
62       plotPoints: []
63     };
64
65     this.setCurrentProjection = this.setCurrentProjection.bind(this);
66     this.processAndRenderMapData = this.processAndRenderMapData.bind(this);
67     this.generateLandFeatures = this.generateLandFeatures.bind(this);
68     this.generateBoundaryMesh = this.generateBoundaryMesh.bind(this);
69     this.generatePlotPointArray = this.generatePlotPointArray.bind(this);
70     this.extractNestedObjectInJson = this.extractNestedObjectInJson.bind(this);
71     this.areArraysEqual = this.areArraysEqual.bind(this);
72
73     this.setCurrentProjection(props.currentProjection);
74     this.projection.translate([this.props.width / 2, this.props.height / 2]);
75     this.path = geoPath().projection(this.projection);
76     this.didProjectionTypeChange = true;
77     this.isMapMounted = false;
78   }
79
80   componentWillReceiveProps(nextProps) {
81     if (!this.areArraysEqual(this.props.pointArray, nextProps.pointArray)) {
82       if (this.props.currentProjection !== nextProps.currentProjection) {
83         this.didProjectionTypeChange = true;
84         this.setCurrentProjection(nextProps.currentProjection);
85       }
86       if (this.isMapMounted) {
87         this.processAndRenderMapData(nextProps.pointArray);
88       }
89     }
90   }
91
92   componentDidMount() {
93     this.isMapMounted = true;
94     this.processAndRenderMapData(this.props.pointArray);
95   }
96
97   setCurrentProjection(projectionType) {
98     switch (projectionType) {
99       case PROJECTION_TYPES.ALBERS_USA:
100         this.projection = geoAlbersUsa();
101         break;
102
103       case PROJECTION_TYPES.EQUIRECTANGULAR:
104         this.projection = geoEquirectangular();
105         break;
106
107       default:
108         // TODO -> FE logging should be a thing at some point. Maybe a log call
109         // to the BE?
110         break;
111     }
112   }
113
114   processAndRenderMapData(plotPointArray) {
115     let landFeatures = this.state.landFeatures;
116     let boundaryMesh = this.state.boundaryMesh;
117     let plotPoints = this.state.plotPoints;
118
119     switch (this.props.currentProjection) {
120       case PROJECTION_TYPES.ALBERS_USA:
121         if (this.didProjectionTypeChange) {
122           landFeatures =
123             this.generateLandFeatures(usMapJson,
124               MAP_OBJECT_KEYS.ALBERS_USA_LAND_KEYS);
125           boundaryMesh =
126             this.generateBoundaryMesh(usMapJson,
127               MAP_OBJECT_KEYS.ALBERS_USA_BOUNDARY_KEYS);
128           this.didProjectionTypeChange = false;
129         }
130         plotPoints = this.generatePlotPointArray(plotPointArray);
131         break;
132       case PROJECTION_TYPES.EQUIRECTANGULAR:
133         if (this.didProjectionTypeChange) {
134           landFeatures =
135             this.generateLandFeatures(worldMapJson,
136               MAP_OBJECT_KEYS.EQUIRECTANGULAR_LAND_KEYS);
137           boundaryMesh =
138             this.generateBoundaryMesh(worldMapJson,
139               MAP_OBJECT_KEYS.EQUIRECTANGULAR_BOUNDARY_KEYS);
140           this.didProjectionTypeChange = false;
141         }
142         plotPoints = this.generatePlotPointArray(plotPointArray);
143         break;
144       default:
145
146         // TODO -> FE logging should be a thing at some point. Maybe a log call
147         // to the BE?
148         break;
149     }
150
151     this.setState(() => {
152       return {
153         landFeatures: landFeatures,
154         boundaryMesh: boundaryMesh,
155         plotPoints: plotPoints
156       };
157     });
158   }
159
160   generateLandFeatures(jsonData, featureKeys) {
161     let featureType = this.extractNestedObjectInJson(jsonData, featureKeys);
162     let landFeature = undefined;
163     if (featureType !== undefined) {
164       let landFeaturePath = this.path(feature(jsonData, featureType));
165       let landFeatureProps = {
166         className: 'land-features',
167         d: landFeaturePath,
168         key: LAND_FEATURE_KEY
169       };
170       landFeature =
171         React.createElement(PLOT_POINT_SHAPES.PATH, landFeatureProps);
172     }
173     return landFeature;
174   }
175
176   generateBoundaryMesh(jsonData, boundaryKeys) {
177     let boundaryType = this.extractNestedObjectInJson(jsonData, boundaryKeys);
178     let boundary = undefined;
179     if (boundaryType !== undefined) {
180       let boundaryPath = this.path(mesh(jsonData, boundaryType, (a, b) => {
181         return a !== b;
182       }));
183       let boundaryProps = {
184         className: 'boundary-mesh',
185         d: boundaryPath,
186         key: BOUNDARY_MESH_KEY
187       };
188       boundary = React.createElement(PLOT_POINT_SHAPES.PATH, boundaryProps);
189     }
190     return boundary;
191   }
192
193   generatePlotPointArray(pointArray) {
194     let generatedPoints = [];
195     if (pointArray !== undefined && pointArray.length > 0) {
196       generatedPoints = pointArray.map((longLat, index) => {
197         let projectedLongLat = this.projection(
198           [longLat.longitude, longLat.latitude]);
199         let plotPointProps = {
200           className: 'plot-point',
201           r: 4,
202           cx: projectedLongLat[0],
203           cy: projectedLongLat[1],
204           key: PLOT_POINT_KEY_BASE + index,
205         };
206         return React.createElement(PLOT_POINT_SHAPES.CIRCLE, plotPointProps);
207       });
208     }
209     return generatedPoints;
210   }
211
212   render() {
213     let {landFeatures, boundaryMesh, plotPoints} = this.state;
214     let {width, height} = this.props;
215
216     return (
217       <div width={width} height={height}>
218         <svg width={width} height={height}>
219           <g>
220             {landFeatures}
221             {boundaryMesh}
222             {plotPoints}
223           </g>
224         </svg>
225       </div>
226     );
227   }
228
229   extractNestedObjectInJson(jsonData, keysArray) {
230     let subObject = undefined;
231     if (jsonData !== undefined && keysArray !== undefined) {
232       subObject = jsonData[keysArray[0]];
233       if (subObject !== undefined) {
234         for (let i = 1; i < keysArray.length; i++) {
235           subObject = subObject[keysArray[i]];
236         }
237       }
238     }
239     return subObject;
240   }
241
242   areArraysEqual(arrayOne, arrayTwo) {
243     if (arrayOne.length !== arrayTwo.length) {
244       return false;
245     }
246     for (let i = 0; i < arrayOne.length; i++) {
247       if (arrayOne[i] instanceof Array && arrayTwo instanceof Array) {
248         if (!this.areArraysEqual(arrayOne[i], arrayTwo[i])) {
249           return false;
250         }
251       }
252       else if (arrayOne[i] !== arrayTwo[i]) {
253         return false;
254       }
255     }
256     return true;
257   }
258 }
259
260 export default TopographicMap;