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