Web Components World Utility

    The resource topic provides implementation of utility that helps with generating Web Components geographic data.

    Code Snippet

    export default class WorldUtils {
    
        // calculate geo-paths between two locations using great circle formula
        public static calcPaths(origin: any, dest: any): any[] {
            let interval = 200;
            let paths: any[] = [[]];
            let pathID = 0;
            let distance = this.calcDistance(origin, dest);
            if (distance <= interval) {
                paths[pathID].push({ x: origin.lon, y: origin.lat });
                paths[pathID].push({ x: dest.lon, y: dest.lat });
            } else {
                let current = origin;
                let previous = origin;
    
                for (let dist = interval; dist <= distance; dist += interval)
                {
                    previous = current
                    paths[pathID].push({ x: current.lon, y: current.lat });
    
                    let bearing = this.calcBearing(current, dest);
                    current = this.calcDestination(current, bearing, interval);
                    // ensure geo-path wrap around the world through the new date-line
                    if (previous.lon > 150 && current.lon < -150) {
                        paths[pathID].push({ x: 180, y: current.lat });
                        paths.push([]);
                        pathID++
                        current = { lon: -180, lat: current.lat }
                    } else if (previous.lon < -150 && current.lon > 150) {
                        paths[pathID].push({ x: -180, y: current.lat });
                        paths.push([]);
                        pathID++
                        current = { lon: 180, lat: current.lat }
                    }
                }
                paths[pathID].push({ x: dest.lon, y: dest.lat });
            }
            return paths;
        }
    
        // calculate bearing angle between two locations
        public static calcBearing(origin: any, dest: any) : number
        {
            origin = this.toRadianLocation(origin);
            dest = this.toRadianLocation(dest);
            let range = (dest.lon - origin.lon);
            let y = Math.sin(range) * Math.cos(dest.lat);
            let x = Math.cos(origin.lat) * Math.sin(dest.lat) -
                    Math.sin(origin.lat) * Math.cos(dest.lat) * Math.cos(range);
            let angle = Math.atan2(y, x);
            return this.toDegreesNormalized(angle);
        }
    
        // calculate destination for origin location and travel distance
        public static calcDestination(origin: any, bearing: number, distance: number): any {
            let radius = 6371.0;
            origin = this.toRadianLocation(origin);
            bearing = this.toRadians(bearing);
            distance = distance / radius; // angular distance in radians
    
            let lat = Math.asin(Math.sin(origin.lat) * Math.cos(distance) +
                           Math.cos(origin.lat) * Math.sin(distance) * Math.cos(bearing));
            let x = Math.sin(bearing) * Math.sin(distance) * Math.cos(origin.lat);
            let y = Math.cos(distance) - Math.sin(origin.lat) * Math.sin(origin.lat);
            let lon = origin.lon + Math.atan2(x, y);
            // normalize lon to coordinate between -180º and +180º
            lon = (lon + 3 * Math.PI) % (2 * Math.PI) - Math.PI;
    
            lon = this.toDegrees(lon);
            lat = this.toDegrees(lat);
    
            return { lon: lon, lat: lat };
        }
    
        // calculate distance between two locations
        public static calcDistance(origin: any, dest: any) : number {
            origin = this.toRadianLocation(origin);
            dest = this.toRadianLocation(dest);
            let sinProd = Math.sin(origin.lat) * Math.sin(dest.lat);
            let cosProd = Math.cos(origin.lat) * Math.cos(dest.lat);
            let lonDelta = (dest.lon - origin.lon);
    
            let angle = Math.acos(sinProd + cosProd * Math.cos(lonDelta));
            let distance = angle * 6371.0;
            return distance; // * 6371.0; // in km
        }
    
        public static toRadianLocation(geoPoint: any) : any {
            let x = this.toRadians(geoPoint.lon);
            let y = this.toRadians(geoPoint.lat);
            return { lon: x, lat: y };
        }
    
        public static toRadians(degrees: number) : number
        {
            return degrees * Math.PI / 180;
        }
    
        public static toDegrees(radians: number) : number {
            return (radians * 180.0 / Math.PI);
        }
    
        public static toDegreesNormalized(radians: number) : number
        {
            let degrees = this.toDegrees(radians);
            degrees = (degrees + 360) % 360;
            return degrees;
        }
    
        // converts latitude coordinate to a string
        public static toStringLat(latitude: number) : string {
            let str = Math.abs(latitude).toFixed(1) + "°";
            return latitude > 0 ? str + "N" : str + "S";
        }
    
        // converts longitude coordinate to a string
        public static toStringLon(coordinate: number) : string {
            let val = Math.abs(coordinate);
            let str = val < 100 ? val.toFixed(1) : val.toFixed(0);
            return coordinate > 0 ? str + "°E" : str + "°W";
        }
    
        public static toStringAbbr(value: number) : string {
            if (value > 1000000000000) {
                return (value / 1000000000000).toFixed(1) + " T"
            } else if (value > 1000000000) {
                return (value / 1000000000).toFixed(1) + " B"
            } else if (value > 1000000) {
                return (value / 1000000).toFixed(1) + " M"
            } else if (value > 1000) {
                return (value / 1000).toFixed(1) + " K"
            }
            return value.toFixed(0);
        }
    
        public static getLongitude(location: any) : number {
            if (location.x) return location.x;
            if (location.lon) return location.lon;
            if (location.longitude) return location.longitude;
            return Number.NaN;
        }
    
        public static getLatitude(location: any) : number {
            if (location.y) return location.y;
            if (location.lat) return location.lat;
            if (location.latitude) return location.latitude;
            return Number.NaN;
        }
    
        public static getBounds(locations: any[]) : any {
            let minLat = 90;
            let maxLat = -90;
            let minLon = 180;
            let maxLon = -180;
    
            for (const location of locations) {
                const crrLon = this.getLongitude(location);
                if (!Number.isNaN(crrLon)) {
                    minLon = Math.min(minLon, crrLon);
                    maxLon = Math.max(maxLon, crrLon);
                }
    
                const crrLat = this.getLatitude(location);
                if (!Number.isNaN(crrLat)) {
                    minLat = Math.min(minLat, crrLat);
                    maxLat = Math.max(maxLat, crrLat);
                }
            }
    
            const geoBounds = {
                left: minLon,
                top: minLat,
                width: Math.abs(maxLon - minLon),
                height: Math.abs(maxLat - minLat)
            };
            return geoBounds;
        }
    }
    

    API References