jflowmap-master/src/jflowmap/geo/MapProjections.java
/*
 * This file is part of JFlowMap.
 *
 * Copyright 2009 Ilya Boyandin
 *
 * Licensed 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 jflowmap.geo;

import java.awt.geom.Point2D;

import jflowmap.geom.Point;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;

/**
 * @author Ilya Boyandin
 */
public enum MapProjections implements MapProjection {

  NONE {
    @Override
    public Point2D project(double lon, double lat) {
      return new Point2D.Double(lon, lat);
    }
  },

  FLIPY {
    @Override
    public Point2D project(double lon, double lat) {
      return new Point2D.Double(lon, -lat);
    }
  },

  MERCATOR {
    // if the scale is smaller than 100 short edges will not be visible
    private final static double SCALE = 100;
    private final static boolean INVERT_Y = true;

    @Override
    public Point2D project(double lon, double lat) {
      return new Point2D.Double(
          SCALE * lon / 180,
          SCALE * (INVERT_Y ? -1 : 1) * (lat > 85 ?
              1 : (lat < -85 ?
                  -1 : Math.log(Math.tan(Math.PI / 4 + radians(lat) / 2)) / Math.PI)
              ));
    }

  },

  WINKELTRIPEL {
    private final double phi1 = Math.acos(2/Math.PI);
    private final double cos_phi1 = Math.cos(phi1);

    public Point2D project(double lat, double lon) {
      double lplam = radians(lat);
      double lpphi = radians(lon);

      double c = 0.5 * lplam;
      double cos_lpphi = Math.cos(lpphi);
      double alpha = Math.acos(cos_lpphi * Math.cos(c));

      double x, y;

      if (alpha != 0) {
        double sinc_alpha = sinc(alpha);
        x = 2.0 * cos_lpphi * Math.sin(c) / sinc_alpha;
        y = Math.sin(lpphi) / sinc_alpha;
      } else {
        x = y = 0.0;
      }

      x = (x + lplam * cos_phi1) * 0.5;
      y = (y + lpphi) * 0.5;

      y = -y;

      return new Point2D.Double(x, y);
    }

    private double sinc(double x) { return Math.sin(x)/x; }

  }

  ;


  private static double radians(double degrees) {
    return degrees * Math.PI / 180;
  }


  public static Iterable<Point> projectAll(Iterable<Point> points, final MapProjection mapProjection) {
    return Iterables.transform(points, new Function<Point, Point>() {
      @Override
      public Point apply(Point from) {
        return Point.valueOf(mapProjection.project(from.x(), from.y()));
      }
    });
  }

}
