changed the header and license
[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 {geoAlbersUsa, geoEquirectangular, geoPath} from 'd3-geo';
23 import {feature, mesh} from 'topojson';
24
25 import {
26   BOUNDARY_MESH_KEY,
27   LAND_FEATURE_KEY,
28   MAP_OBJECT_KEYS,
29   PLOT_POINT_KEY_BASE,
30   PLOT_POINT_SHAPES,
31   PROJECTION_TYPES
32 } from './MapConstants.js';
33 import usMapJson from './mapJson/usJson.json';
34 import worldMapJson from  './mapJson/worldJson.json';
35
36 class TopographicMap extends Component {
37   static propTypes = {
38     width: PropTypes.number,
39     height: PropTypes.number,
40     pointArray: PropTypes.array,
41     currentProjection: PropTypes.string
42   };
43
44   static defaultProps = {
45     width: 840,
46     height: 500,
47     pointArray: [],
48     currentProjection: PROJECTION_TYPES.ALBERS_USA
49   };
50
51   constructor(props) {
52     super(props);
53
54     this.state = {
55       landFeatures: undefined,
56       boundaryMesh: undefined,
57       plotPoints: []
58     };
59
60     this.setCurrentProjection = this.setCurrentProjection.bind(this);
61     this.processAndRenderMapData = this.processAndRenderMapData.bind(this);
62     this.generateLandFeatures = this.generateLandFeatures.bind(this);
63     this.generateBoundaryMesh = this.generateBoundaryMesh.bind(this);
64     this.generatePlotPointArray = this.generatePlotPointArray.bind(this);
65     this.extractNestedObjectInJson = this.extractNestedObjectInJson.bind(this);
66     this.areArraysEqual = this.areArraysEqual.bind(this);
67
68     this.setCurrentProjection(props.currentProjection);
69     this.projection.translate([this.props.width / 2, this.props.height / 2]);
70     this.path = geoPath().projection(this.projection);
71     this.didProjectionTypeChange = true;
72     this.isMapMounted = false;
73   }
74
75   componentWillReceiveProps(nextProps) {
76     if (!this.areArraysEqual(this.props.pointArray, nextProps.pointArray)) {
77       if (this.props.currentProjection !== nextProps.currentProjection) {
78         this.didProjectionTypeChange = true;
79         this.setCurrentProjection(nextProps.currentProjection);
80       }
81       if (this.isMapMounted) {
82         this.processAndRenderMapData(nextProps.pointArray);
83       }
84     }
85   }
86
87   componentDidMount() {
88     this.isMapMounted = true;
89     this.processAndRenderMapData(this.props.pointArray);
90   }
91
92   setCurrentProjection(projectionType) {
93     switch (projectionType) {
94       case PROJECTION_TYPES.ALBERS_USA:
95         this.projection = geoAlbersUsa();
96         break;
97
98       case PROJECTION_TYPES.EQUIRECTANGULAR:
99         this.projection = geoEquirectangular();
100         break;
101
102       default:
103         // TODO -> FE logging should be a thing at some point. Maybe a log call
104         // to the BE?
105         break;
106     }
107   }
108
109   processAndRenderMapData(plotPointArray) {
110     let landFeatures = this.state.landFeatures;
111     let boundaryMesh = this.state.boundaryMesh;
112     let plotPoints = this.state.plotPoints;
113
114     switch (this.props.currentProjection) {
115       case PROJECTION_TYPES.ALBERS_USA:
116         if (this.didProjectionTypeChange) {
117           landFeatures =
118             this.generateLandFeatures(usMapJson,
119               MAP_OBJECT_KEYS.ALBERS_USA_LAND_KEYS);
120           boundaryMesh =
121             this.generateBoundaryMesh(usMapJson,
122               MAP_OBJECT_KEYS.ALBERS_USA_BOUNDARY_KEYS);
123           this.didProjectionTypeChange = false;
124         }
125         plotPoints = this.generatePlotPointArray(plotPointArray);
126         break;
127       case PROJECTION_TYPES.EQUIRECTANGULAR:
128         if (this.didProjectionTypeChange) {
129           landFeatures =
130             this.generateLandFeatures(worldMapJson,
131               MAP_OBJECT_KEYS.EQUIRECTANGULAR_LAND_KEYS);
132           boundaryMesh =
133             this.generateBoundaryMesh(worldMapJson,
134               MAP_OBJECT_KEYS.EQUIRECTANGULAR_BOUNDARY_KEYS);
135           this.didProjectionTypeChange = false;
136         }
137         plotPoints = this.generatePlotPointArray(plotPointArray);
138         break;
139       default:
140
141         // TODO -> FE logging should be a thing at some point. Maybe a log call
142         // to the BE?
143         break;
144     }
145
146     this.setState(() => {
147       return {
148         landFeatures: landFeatures,
149         boundaryMesh: boundaryMesh,
150         plotPoints: plotPoints
151       };
152     });
153   }
154
155   generateLandFeatures(jsonData, featureKeys) {
156     let featureType = this.extractNestedObjectInJson(jsonData, featureKeys);
157     let landFeature = undefined;
158     if (featureType !== undefined) {
159       let landFeaturePath = this.path(feature(jsonData, featureType));
160       let landFeatureProps = {
161         className: 'land-features',
162         d: landFeaturePath,
163         key: LAND_FEATURE_KEY
164       };
165       landFeature =
166         React.createElement(PLOT_POINT_SHAPES.PATH, landFeatureProps);
167     }
168     return landFeature;
169   }
170
171   generateBoundaryMesh(jsonData, boundaryKeys) {
172     let boundaryType = this.extractNestedObjectInJson(jsonData, boundaryKeys);
173     let boundary = undefined;
174     if (boundaryType !== undefined) {
175       let boundaryPath = this.path(mesh(jsonData, boundaryType, (a, b) => {
176         return a !== b;
177       }));
178       let boundaryProps = {
179         className: 'boundary-mesh',
180         d: boundaryPath,
181         key: BOUNDARY_MESH_KEY
182       };
183       boundary = React.createElement(PLOT_POINT_SHAPES.PATH, boundaryProps);
184     }
185     return boundary;
186   }
187
188   generatePlotPointArray(pointArray) {
189     let generatedPoints = [];
190     if (pointArray !== undefined && pointArray.length > 0) {
191       generatedPoints = pointArray.map((longLat, index) => {
192         let projectedLongLat = this.projection(
193           [longLat.longitude, longLat.latitude]);
194         let plotPointProps = {
195           className: 'plot-point',
196           r: 4,
197           cx: projectedLongLat[0],
198           cy: projectedLongLat[1],
199           key: PLOT_POINT_KEY_BASE + index,
200         };
201         return React.createElement(PLOT_POINT_SHAPES.CIRCLE, plotPointProps);
202       });
203     }
204     return generatedPoints;
205   }
206
207   render() {
208     let {landFeatures, boundaryMesh, plotPoints} = this.state;
209     let {width, height} = this.props;
210
211     return (
212       <div width={width} height={height}>
213         <svg width={width} height={height}>
214           <g>
215             {landFeatures}
216             {boundaryMesh}
217             {plotPoints}
218           </g>
219         </svg>
220       </div>
221     );
222   }
223
224   extractNestedObjectInJson(jsonData, keysArray) {
225     let subObject = undefined;
226     if (jsonData !== undefined && keysArray !== undefined) {
227       subObject = jsonData[keysArray[0]];
228       if (subObject !== undefined) {
229         for (let i = 1; i < keysArray.length; i++) {
230           subObject = subObject[keysArray[i]];
231         }
232       }
233     }
234     return subObject;
235   }
236
237   areArraysEqual(arrayOne, arrayTwo) {
238     if (arrayOne.length !== arrayTwo.length) {
239       return false;
240     }
241     for (let i = 0; i < arrayOne.length; i++) {
242       if (arrayOne[i] instanceof Array && arrayTwo instanceof Array) {
243         if (!this.areArraysEqual(arrayOne[i], arrayTwo[i])) {
244           return false;
245         }
246       }
247       else if (arrayOne[i] !== arrayTwo[i]) {
248         return false;
249       }
250     }
251     return true;
252   }
253 }
254
255 export default TopographicMap;