From eb3efa7a6928990eee4594376f83a7ed8a8c0d28 Mon Sep 17 00:00:00 2001
From: Gustl22 <user.rebo@gmx.de>
Date: Tue, 12 Feb 2019 10:46:17 +0100
Subject: [PATCH] Sun calculator (#655)

---
 .../tile/buildings/BuildingRenderer.java      |   1 +
 .../org/oscim/renderer/ExtrusionRenderer.java |  31 +-
 vtm/src/org/oscim/renderer/light/Sun.java     | 267 ++++++++++++++++++
 3 files changed, 297 insertions(+), 2 deletions(-)
 create mode 100644 vtm/src/org/oscim/renderer/light/Sun.java

diff --git a/vtm/src/org/oscim/layers/tile/buildings/BuildingRenderer.java b/vtm/src/org/oscim/layers/tile/buildings/BuildingRenderer.java
index 71955f24..314a3ef2 100644
--- a/vtm/src/org/oscim/layers/tile/buildings/BuildingRenderer.java
+++ b/vtm/src/org/oscim/layers/tile/buildings/BuildingRenderer.java
@@ -71,6 +71,7 @@ public class BuildingRenderer extends ExtrusionRenderer {
 
     @Override
     public void update(GLViewport v) {
+        super.update(v);
 
         int diff = (v.pos.zoomLevel - mZoomLimiter.getMinZoom());
 
diff --git a/vtm/src/org/oscim/renderer/ExtrusionRenderer.java b/vtm/src/org/oscim/renderer/ExtrusionRenderer.java
index 4315e275..208acf4f 100644
--- a/vtm/src/org/oscim/renderer/ExtrusionRenderer.java
+++ b/vtm/src/org/oscim/renderer/ExtrusionRenderer.java
@@ -24,6 +24,7 @@ import org.oscim.core.Tile;
 import org.oscim.renderer.bucket.ExtrusionBucket;
 import org.oscim.renderer.bucket.ExtrusionBuckets;
 import org.oscim.renderer.bucket.RenderBuckets;
+import org.oscim.renderer.light.Sun;
 import org.oscim.utils.FastMath;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -45,11 +46,15 @@ public abstract class ExtrusionRenderer extends LayerRenderer {
     protected float mAlpha = 1;
 
     private float mZLimit = Float.MAX_VALUE;
-    private float[] mLightPos = new float[]{0.3f, 0.3f, 1f};
+
+    private Sun mSun;
+    private boolean mEnableCurrentSunPos;
 
     public ExtrusionRenderer(boolean mesh, boolean translucent) {
         mMesh = mesh;
         mTranslucent = translucent;
+
+        mSun = new Sun();
     }
 
     public static class Shader extends GLShader {
@@ -115,6 +120,14 @@ public abstract class ExtrusionRenderer extends LayerRenderer {
         }
     }
 
+    public Sun getSun() {
+        return mSun;
+    }
+
+    public void enableCurrentSunPos(boolean enableSunPos) {
+        mEnableCurrentSunPos = enableSunPos;
+    }
+
     @Override
     public boolean setup() {
         if (!mMesh)
@@ -125,6 +138,20 @@ public abstract class ExtrusionRenderer extends LayerRenderer {
         return true;
     }
 
+    @Override
+    public void update(GLViewport viewport) {
+        if (mEnableCurrentSunPos) {
+            float lat = (float) viewport.pos.getLatitude();
+            float lon = (float) viewport.pos.getLongitude();
+            if (FastMath.abs(mSun.getLatitude() - lat) > 0.2f
+                    || Math.abs(mSun.getLongitude() - lon) > 0.2f) {
+                // location is only updated if necessary (not every frame)
+                mSun.setCoordinates(lat, lon);
+            }
+            mSun.update();
+        }
+    }
+
     private void renderCombined(int vertexPointer, ExtrusionBuckets ebs) {
 
         for (ExtrusionBucket eb = ebs.buckets(); eb != null; eb = eb.next()) {
@@ -171,7 +198,7 @@ public abstract class ExtrusionRenderer extends LayerRenderer {
         gl.depthFunc(GL.LESS);
         gl.uniform1f(s.uAlpha, mAlpha);
         gl.uniform1f(s.uZLimit, mZLimit);
-        GLUtils.glUniform3fv(s.uLight, 1, mLightPos);
+        GLUtils.glUniform3fv(s.uLight, 1, mSun.getPosition());
 
         ExtrusionBuckets[] ebs = mExtrusionBucketSet;
 
diff --git a/vtm/src/org/oscim/renderer/light/Sun.java b/vtm/src/org/oscim/renderer/light/Sun.java
new file mode 100644
index 00000000..80f86406
--- /dev/null
+++ b/vtm/src/org/oscim/renderer/light/Sun.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2019 Gustl22
+ *
+ * This program is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.oscim.renderer.light;
+
+import org.oscim.backend.DateTimeAdapter;
+import org.oscim.backend.canvas.Color;
+import org.oscim.utils.ColorUtil;
+import org.oscim.utils.geom.GeometryUtils;
+import org.oscim.utils.math.MathUtils;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * See https://lexikon.astronomie.info/zeitgleichung
+ */
+public class Sun {
+
+    public static float SHADOW_ALPHA = 0.3f;
+
+    private static final DateTimeAdapter date = DateTimeAdapter.instance;
+    private float mSunrise; // in hours
+    private float mSunset; // in hours
+    private float mLatitude; // in degree
+    private float mLongitude; // in degree
+    private int mDayOfYear; // up to 366
+    private float mProgress; // 0 to 2
+
+    private float[] mSunPos = new float[3];
+    private int mLightColor;
+    private Map<Float, Integer> mColorMap;
+
+    /**
+     * Track sun position (accuracy of ~1 minute).
+     */
+    public Sun() {
+        // Init defaults
+        mDayOfYear = date.getDayOfYear();
+        setCoordinates(0, 0);
+        mLightColor = Color.get(SHADOW_ALPHA, 255, 255, 255);
+        setProgress(0.4f);
+        updatePosition();
+    }
+
+    /**
+     * @return the latitude where the sun is in zenith
+     */
+    private float declination() {
+        return (float) (0.4095 * Math.sin(0.016906 * (mDayOfYear - 80.086)));
+    }
+
+    /**
+     * The discrepancy of mean time and sun time.
+     *
+     * @return discrepancy in hours
+     */
+    private float discrepancyMeanTime() {
+        return (float) (-0.171 * Math.sin(0.0337 * mDayOfYear + 0.465) - 0.1299 * Math.sin(0.01787 * mDayOfYear - 0.168));
+    }
+
+    /**
+     * RGB - the color of sun.
+     * A - the diffuse of shadow.
+     */
+    public int getColor() {
+        return mLightColor;
+    }
+
+    /**
+     * Get the colors of day cycle.
+     *
+     * @return the color map
+     */
+    public Map<Float, Integer> getColorMap() {
+        return mColorMap;
+    }
+
+    public float getLatitude() {
+        return mLatitude;
+    }
+
+    public float getLongitude() {
+        return mLongitude;
+    }
+
+    public float[] getPosition() {
+        return mSunPos;
+    }
+
+    public float getProgress() {
+        return mProgress;
+    }
+
+    /**
+     * @return the local sunrise time in hours.
+     */
+    public float getSunrise() {
+        return mSunrise;
+    }
+
+    /**
+     * @return the local sunset time in hours.
+     */
+    public float getSunset() {
+        return mSunset;
+    }
+
+    private void initDefaultColorMap() {
+        mColorMap = new HashMap<>();
+        mColorMap.put(0.0f, Color.get((int) (255 * SHADOW_ALPHA), 150, 120, 140)); // Sunrise
+        mColorMap.put(0.04f, Color.get((int) (255 * SHADOW_ALPHA), 205, 170, 160));
+        mColorMap.put(0.1f, Color.get((int) (255 * SHADOW_ALPHA), 245, 240, 215));
+        mColorMap.put(0.2f, Color.get((int) (255 * SHADOW_ALPHA), 255, 255, 255)); // Forenoon
+        mColorMap.put(0.8f, Color.get((int) (255 * SHADOW_ALPHA), 255, 255, 255)); // Afternoon
+        mColorMap.put(0.99f, Color.get((int) (255 * SHADOW_ALPHA), 255, 220, 230));
+        mColorMap.put(1.0f, Color.get((int) (255 * SHADOW_ALPHA), 100, 100, 130)); // Sunset
+        mColorMap.put(1.9f, Color.get((int) (255 * SHADOW_ALPHA), 100, 100, 130)); // Night
+    }
+
+    /**
+     * @param color RGB - the color of sun, A - the diffuse of shadow
+     */
+    public void setColor(int color) {
+        mLightColor = color;
+    }
+
+    /**
+     * Set the colors of day cycle.
+     */
+    public void setColorMap(Map<Float, Integer> colorMap) {
+        mColorMap = colorMap;
+    }
+
+    public void setCoordinates(float latitude, float longitude) {
+        mLatitude = latitude;
+        mLongitude = longitude;
+        updateToDay();
+    }
+
+    /**
+     * Customize day of the year
+     */
+    public void setDayOfYear(int day) {
+        mDayOfYear = day;
+    }
+
+    public void setPosition(float x, float y, float z) {
+        mSunPos[0] = x;
+        mSunPos[1] = y;
+        mSunPos[2] = z;
+        mSunPos = GeometryUtils.normalize(mSunPos);
+    }
+
+    /**
+     * Customize progress.
+     */
+    public void setProgress(float progress) {
+        mProgress = progress;
+    }
+
+    /**
+     * @param h the offset of horizon (in radians) e.g. bend of earth / atmosphere etc.
+     * @return the difference of sunrise or sunset to noon
+     */
+    private float timeDiff(float h) {
+        float lat = mLatitude * MathUtils.degreesToRadians;
+        float declination = declination();
+        return (float) (12 * Math.acos((Math.sin(h) - Math.sin(lat) * Math.sin(declination)) / (Math.cos(lat) * Math.cos(declination))) / Math.PI);
+    }
+
+    /**
+     * Update sun progress, position and color to current date time.
+     */
+    public void update() {
+        updateProgress();
+        updatePosition();
+        updateColor();
+    }
+
+    public int updateColor() {
+        if (mColorMap == null)
+            initDefaultColorMap();
+
+        float progressStart, progressEnd;
+        Iterator<Float> prIter = mColorMap.keySet().iterator();
+        progressStart = progressEnd = prIter.next();
+
+        while (prIter.hasNext()) {
+            float progress = prIter.next();
+            if (((mProgress + 2f - progress) % 2f) < ((mProgress + 2f - progressStart) % 2f))
+                progressStart = progress;
+            else if (((progress + 2f - mProgress) % 2f) < ((progressEnd + 2f - mProgress) % 2f))
+                progressEnd = progress;
+        }
+
+        float fraction = ((mProgress + 2f - progressStart) % 2f) / ((progressEnd + 2f - progressStart) % 2f);
+
+        int colorStart = mColorMap.get(progressStart);
+        int colorEnd = mColorMap.get(progressEnd);
+        mLightColor = ColorUtil.blend(colorStart, colorEnd, fraction);
+        return mLightColor;
+    }
+
+    /**
+     * Very simple normalized sun coordinates.
+     */
+    public float[] updatePosition() {
+        mSunPos[0] = (float) Math.cos(mProgress * Math.PI);
+        mSunPos[1] = (float) Math.sin(mProgress * Math.PI);
+        mSunPos[2] = 3 * mSunPos[1];
+        mSunPos = GeometryUtils.normalize(mSunPos);
+        return mSunPos;
+    }
+
+    /**
+     * The progress
+     * of the daylight in range 0 (sunrise) to 1 (sunset) and
+     * of the night in range 1 (sunset) to 2 (sunrise).
+     *
+     * @return the progress in range 0 to 2
+     */
+    public float updateProgress() {
+        // TODO Java8 replace with LocalDateTime
+        // LocalDateTime date = new LocalDateTime();
+        // float time = date.getHour();
+        // time += date.getMinute() / 60f;
+        // time += date.getSecond() / 3600f;
+
+        float time = date.getHour();
+        time += date.getMinute() / 60f;
+        time += date.getSecond() / 3600f;
+
+        float progress = (time - mSunrise) / (mSunset - mSunrise);
+        if (progress > 1f || progress < 0f) {
+            progress = ((time + 24 - mSunset) % 24) / (mSunrise + 24 - mSunset);
+            progress += 1;
+        }
+        mProgress = MathUtils.clamp(progress, 0f, 2f);
+        return mProgress;
+    }
+
+    /**
+     * Calculate the sunrise and sunset of set day (local time).
+     */
+    public void updateToDay() {
+        float h = -0.0145f; // -50 latitude minutes
+
+        float diff = timeDiff(h);
+        float discp = discrepancyMeanTime();
+        float calc = 12 - discp - (mLongitude / 15f) + (date.getTimeZoneOffset() / (60f * 60f * 1000f));
+        mSunrise = calc - diff;
+        mSunset = calc + diff;
+    }
+}