content: /**
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.apache.lucene.spatial.geometry;
  
  
  /**
   * Abstract base lat-lng class which can manipulate fixed point or floating
   * point based coordinates. Instances are immutable.
   * 
   * @see FloatLatLng
   *
   * <p><font color="red"><b>NOTE:</b> This API is still in
   * flux and might change in incompatible ways in the next
   * release.</font>
   */
  public abstract class LatLng {
  
    public abstract boolean isNormalized();
  
    public abstract boolean isFixedPoint();
  
    public abstract LatLng normalize();
  
    public abstract int getFixedLat();
  
    public abstract int getFixedLng();
  
    public abstract double getLat();
  
    public abstract double getLng();
  
    public abstract LatLng copy();
  
    public abstract FixedLatLng toFixed();
  
    public abstract FloatLatLng toFloat();
    
    /**
     * Convert the lat/lng into the cartesian coordinate plane such that all
     * world coordinates are represented in the first quadrant.
     * The x dimension corresponds to latitude and y corresponds to longitude.
     * The translation starts with the normalized latlng and adds 180 to the latitude and 
     * 90 to the longitude (subject to fixed point scaling).
     */
    public CartesianPoint toCartesian() {
      LatLng ll=normalize();
      
      int lat=ll.getFixedLat();
      int lng=ll.getFixedLng();
      
      return new CartesianPoint(
          lng+180*FixedLatLng.SCALE_FACTOR_INT,
          lat+90*FixedLatLng.SCALE_FACTOR_INT
      );
    }
    
    /**
     * The inverse of toCartesian().  Always returns a FixedLatLng.
     * @param pt
     */
    public static LatLng fromCartesian(CartesianPoint pt) {
      int lat=pt.getY() - 90 * FixedLatLng.SCALE_FACTOR_INT;
      int lng=pt.getX() - 180 * FixedLatLng.SCALE_FACTOR_INT;
      
      return new FixedLatLng(lat, lng);
    }
    
    /**
     * Calculates the distance between two lat/lng's in miles.
     * Imported from mq java client.
     * 
     * @param ll2
     *            Second lat,lng position to calculate distance to.
     * 
     * @return Returns the distance in miles.
     */
    public double arcDistance(LatLng ll2) {
      return arcDistance(ll2, DistanceUnits.MILES);
    }
  
    /**
     * Calculates the distance between two lat/lng's in miles or meters.
     * Imported from mq java client.  Variable references changed to match.
     * 
     * @param ll2
     *            Second lat,lng position to calculate distance to.
     * @param lUnits
     *            Units to calculate distance, defaults to miles
     * 
     * @return Returns the distance in meters or miles.
     */
    public double arcDistance(LatLng ll2, DistanceUnits lUnits) {
      LatLng ll1 = normalize();
      ll2 = ll2.normalize();
  
      double lat1 = ll1.getLat(), lng1 = ll1.getLng();
      double lat2 = ll2.getLat(), lng2 = ll2.getLng();
  
      // Check for same position
      if (lat1 == lat2 && lng1 == lng2)
        return 0.0;
  
      // Get the m_dLongitude difference. Don't need to worry about
      // crossing 180 since cos(x) = cos(-x)
      double dLon = lng2 - lng1;
  
      double a = radians(90.0 - lat1);
      double c = radians(90.0 - lat2);
      double cosB = (Math.cos(a) * Math.cos(c))
          + (Math.sin(a) * Math.sin(c) * Math.cos(radians(dLon)));
  
      double radius = (lUnits == DistanceUnits.MILES) ? 3963.205/* MILERADIUSOFEARTH */
      : 6378.160187/* KMRADIUSOFEARTH */;
  
      // Find angle subtended (with some bounds checking) in radians and
      // multiply by earth radius to find the arc distance
      if (cosB < -1.0)
        return 3.14159265358979323846/* PI */* radius;
      else if (cosB >= 1.0)
        return 0;
      else
        return Math.acos(cosB) * radius;
    }
  
    private double radians(double a) {
      return a * 0.01745329251994;
    }
  
    @Override
    public String toString() {
      return "[" + getLat() + "," + getLng() + "]";
    }
  
    /**
     * Calculate the midpoint between this point an another.  Respects fixed vs floating point
     * @param other
     */
    public abstract LatLng calculateMidpoint(LatLng other);
    
    @Override
    public abstract int hashCode();
  
    @Override
    public abstract boolean equals(Object obj);
  }
  