Source: GeoRegion.js

import GeoMath from "./GeoMath";
import GeoPoint from "./GeoPoint";

/**
 * @summary 地理空間的Region
 *
 * @memberof mapray
 */
class GeoRegion {

    /**
     * 初期値は empty である。
     * lon は次の条件とする。
     * <p> 条件 this._min_lon < this._max_lon </p>
     * <p> 条件 this._max_lon - this._min_lon <= 360 </p>
     */
    constructor()
    {
        this._empty = true;
        this._min_lon = 0;
        this._max_lon = 0;
        this._min_lat = 0;
        this._max_lat = 0;
        this._min_alt = 0;
        this._max_alt = 0;
    }


    /**
     * @summary 空確認
     *
     * @return {boolean}        empty値
     */
    empty()
    {
        return this._empty;
    }


    /**
     * @summary Pointを追加
     * <p> 条件 this._min_lon < this._max_lon </p>
     * <p> 条件 this._max_lon - this._min_lon <= 360 </p>
     *
     * @param {number}  lon  地理空間位置 longitude
     * @param {number}  lat  地理空間位置 latitude
     * @param {number}  alt  地理空間位置 altitude
     * @private
     */
    _add( lon, lat, alt )
    {
        if ( this._empty ) {
            this._empty = false;
            lon = lon - 360 * Math.floor( lon/360 + 0.5 );
            this._min_lon = this._max_lon = lon;
            this._min_lat = this._max_lat = lat;
            this._min_alt = this._max_alt = alt;
        }
        else {
            const right_min_lon = this._calcRightPosition( this._min_lon, lon );

            if ( right_min_lon <= this._max_lon ) {
                // lon はmin,maxに内包されている
                // lon の min, max の確認は不要
            }
            else {
                const right_max_lon = this._calcRightPosition( this._max_lon, lon );

                const length1 = right_max_lon - this._max_lon ;
                const length2 = right_min_lon - 360 - this._min_lon;
                if ( Math.abs(length1) <= Math.abs(length2) ) {
                    // expand east
                    this._max_lon = this._max_lon + length1;
                }
                else {
                    // expand west
                    this._min_lon = this._min_lon + length2;
                }
            }

            if      ( lat < this._min_lat ) this._min_lat = lat;
            else if ( lat > this._max_lat ) this._max_lat = lat;
            if      ( alt < this._min_alt ) this._min_alt = alt;
            else if ( alt > this._max_alt ) this._max_alt = alt;
        }
    }

    /**
     * @summary base_lon より右となる最小の longitude を算出
     *
     * @param {number}  base_lon  基準となるlongitude
     * @param {number}  lon       確認したいlongitude
     * @return {number} 最小longitude
     * @private
     */
     _calcRightPosition( base_lon, lon )
     {
         let diff = lon - base_lon;
         diff = diff - 360 * Math.floor(diff / 360);
         return base_lon + diff;
     }


    /**
     * @summary GeoPointを追加
     *
     * @param {mapary.GeoPoint}  point  地理空間位置
     */
    addPoint( point )
    {
        this._add( point.longitude, point.latitude, point.altitude );
    }


    /**
     * @summary GeoPointを追加
     *
     * @param {...mapary.GeoPoint}  points  地理空間位置
     */
    addPoints( ...points )
    {
        points.forEach( point => {
            this._add( point.longitude, point.latitude, point.altitude );
        });
    }


    /**
     * @summary PointArrayを追加
     *
     * @param {number[]}  pointsArray  地理空間位置Array
     */
    addPointsAsArray( pointsArray )
    {
        for ( let i = 0; i < pointsArray.length; i += 3 ) {
            this._add( pointsArray[i], pointsArray[i+1], pointsArray[i+2] );
        };
    }


    /**
     * @summary GeoRegionを結合
     *
     * @param {mapray.GeoRegion}  region  地理空間Region
     */
    merge( region )
    {
        if( region.empty() ) { return; }
        if ( this._empty ) {
            this._min_lon = region._min_lon;
            this._max_lon = region._max_lon;
            this._min_lat = region._min_lat;
            this._max_lat = region._max_lat;
            this._min_alt = region._min_alt;
            this._max_alt = region._max_alt;
            this._empty = false;
        }
        else {
            // 内包判定
            let right_min_lon = this._calcRightPosition( this._min_lon, region._min_lon );
            const right_max_lon = this._calcRightPosition( this._min_lon, region._max_lon );

            if( right_min_lon > right_max_lon ) {
                right_min_lon -= 360;
            }

            let min_include = false;
            if ( ( right_min_lon >= this._min_lon ) && ( right_min_lon <= this._max_lon ) ) {
                min_include = true;
            }
            let max_include = false;
            if ( ( right_max_lon >= this._min_lon ) && ( right_max_lon <= this._max_lon ) ) {
                max_include = true;
            }

            // 1.thisがregionを内包
            if ( min_include && max_include ) {
                // 内包状態 -> 変化無し
            }

            // 2.westのみ内包 -> east拡大
            else if ( min_include ) {
                this._max_lon = right_max_lon;
            }

            // 3.eastのみ内包 -> west拡大
            else if ( max_include ) {
                this._min_lon = right_min_lon;
            }

            // 4.regionがthisを内包
            else if ( ( this._min_lon >= right_min_lon ) && ( this._max_lon <= right_max_lon ) )
            {
                this._min_lon = region._min_lon;
                this._max_lon = region._max_lon;
            }

            // 5.共有無し
            else {
                // east と west 比較
                const length1 = right_max_lon - this._max_lon ;
                const length2 = right_min_lon - 360 - this._min_lon;
                if ( Math.abs(length1) <= Math.abs(length2) ) {
                    // expand east
                    this._max_lon = this._max_lon + length1;
                }
                else {
                    // expand west
                    this._min_lon = this._min_lon + length2;
                }
            }

            // lat と alt
            this._min_lat = Math.min(this._min_lat, region._min_lat);
            this._max_lat = Math.max(this._max_lat, region._max_lat);
            this._min_alt = Math.min(this._min_alt, region._min_alt);
            this._max_alt = Math.max(this._max_alt, region._max_alt);
        }
    }


    /**
     * @summary Region内の任意点の取得
     * center     ( 0.5, 0.5 )
     * east       (   1, 0.5 )
     * west       (   0, 0.5 )
     * north      ( 0.5,   1 )
     * south      ( 0.5,   0 )
     * northEast  (   1,   1 )
     * southWest  (   0,   0 )
     *
     * @param {number}  lon_pos   longitude位置割合
     * @param {number}  lat_pos   latitude位置割合
     * @param {number}  [alt_pos] anlitude位置割合
     * @return {mapary.GeoPoint}   GeoPoint
     */
    getPoint( lon_pos, lat_pos, alt_pos=0 )
    {
        if ( this._empty ) { return null; }
        return new GeoPoint(
            (1-lon_pos) * this._min_lon + lon_pos * this._max_lon,
            (1-lat_pos) * this._min_lat + lat_pos * this._max_lat,
            (1-alt_pos) * this._min_alt + alt_pos * this._max_alt
        );
    }


    /**
     * @summary SouthWestのGeopointを返却
     *
     * @return {mapary.GeoPoint}   南西(min)のGeoPoint
     */
    getSouthWest()
    {
        if ( this._empty ) { return null; }
        return new GeoPoint( this._min_lon, this._min_lat, this._min_alt );
    }


    /**
     * @summary NorthEastのGeopointを返却
     *
     * @return {mapary.GeoPoint}  北東(max)のGeoPoint
     */
    getNorthEast()
    {
        if ( this._empty ) { return null; }
        return new GeoPoint( this._max_lon, this._max_lat, this._max_alt );
    }


    /**
     * @summary 中心位置のGeopointを返却
     *
     * @return {mapary.GeoPoint}  中心(center)のGeoPoint
     */
    getCenter()
    {
        if ( this._empty ) { return null; }
        return this.getPoint( 0.5, 0.5, 0.5 );
    }


    /**
     * @summary RegionのLongitude方向 の地表面距離を算出
     *
     * @return {number}  Longitude方向の地表面距離
     */
    getLongitudeDistance()
    {
        if ( this._empty ) { return null; }
        return this.getPoint( 0, 0.5 ).getGeographicalDistance( this.getPoint( 1, 0.5 ) );
    }


    /**
     * @summary RegionのLatitude方向 の地表面距離を算出
     *
     * @return {number}  Latitude方向の地表面距離
     */
    getLatitudeDistance()
    {
        if ( this._empty ) { return null; }
        return this.getPoint( 0.5, 0 ).getGeographicalDistance( this.getPoint( 0.5, 1 ) );
    }
}


export default GeoRegion;