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