Skip to content

Commit

Permalink
Add Mercator projection and 2D map support.
Browse files Browse the repository at this point in the history
  • Loading branch information
GuzulFromUkraine authored and ComBatVision committed Dec 25, 2023
1 parent b20e0f0 commit 9ae42ec
Show file tree
Hide file tree
Showing 56 changed files with 850 additions and 431 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package earth.worldwind.examples

import android.os.Bundle
import android.widget.CheckBox
import android.widget.FrameLayout
import earth.worldwind.WorldWindow
import earth.worldwind.globe.elevation.coverage.BasicElevationCoverage
import earth.worldwind.globe.projection.MercatorProjection
import earth.worldwind.globe.projection.Wgs84Projection
import earth.worldwind.layer.BackgroundLayer
import earth.worldwind.layer.atmosphere.AtmosphereLayer
import earth.worldwind.layer.mercator.MercatorLayerFactory
Expand Down Expand Up @@ -42,8 +45,13 @@ The globe uses the default navigation gestures:
wwd = WorldWindow(this)

// Add the WorldWindow view object to the layout that was reserved for the globe.
val globeLayout = findViewById<FrameLayout>(R.id.globe)
globeLayout.addView(wwd)
findViewById<FrameLayout>(R.id.globe).addView(wwd)

// Change projection by toolbar checkbox
findViewById<CheckBox>(R.id.is2d).setOnCheckedChangeListener { _, checked ->
wwd.engine.globe.projection = if (checked) MercatorProjection() else Wgs84Projection()
wwd.requestRedraw()
}

// Define cache content manager
val contentManager = GpkgContentManager(File(cacheDir, "content.gpkg").absolutePath)
Expand Down
11 changes: 10 additions & 1 deletion worldwind-examples-android/src/main/res/layout/main_content.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,16 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
app:popupTheme="@style/AppTheme.PopupOverlay">
<CheckBox
android:id="@+id/is2d"
android:text="2D"
android:button="@null"
android:drawableEnd="?android:attr/listChoiceIndicatorMultiple"
android:layout_gravity="end"
android:layout_width="wrap_content"
android:layout_height="40dp"/>
</androidx.appcompat.widget.Toolbar>

</com.google.android.material.appbar.AppBarLayout>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ <h3>CameraControlFragment.kt</h3>
position.altitude = position.altitude.coerceIn(minAltitude, maxAltitude)

// Check if camera altitude is not under the surface
val elevation = wwd.engine.globe.getElevation(
val elevation = if (wwd.engine.globe.is2D) COLLISION_THRESHOLD else wwd.engine.globe.getElevation(
position.latitude, position.longitude
) * wwd.engine.verticalExaggeration + COLLISION_THRESHOLD
if (elevation > position.altitude) position.altitude = elevation
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package earth.worldwind.tutorials

import earth.worldwind.BasicWorldWindowController
import earth.worldwind.WorldWind
import earth.worldwind.WorldWindow
import earth.worldwind.geom.AltitudeMode
import earth.worldwind.geom.Angle.Companion.ZERO
Expand Down Expand Up @@ -185,7 +186,7 @@ class CameraControlFragment: BasicGlobeFragment() {
position.altitude = position.altitude.coerceIn(minAltitude, maxAltitude)

// Check if camera altitude is not under the surface
val elevation = wwd.engine.globe.getElevation(
val elevation = if (wwd.engine.globe.is2D) COLLISION_THRESHOLD else wwd.engine.globe.getElevation(
position.latitude, position.longitude
) * wwd.engine.verticalExaggeration + COLLISION_THRESHOLD
if (elevation > position.altitude) position.altitude = elevation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import android.content.res.Configuration
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.View.VISIBLE
import android.widget.CheckBox
import android.widget.FrameLayout
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
Expand All @@ -21,6 +23,8 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.navigation.NavigationView
import earth.worldwind.WorldWindow
import earth.worldwind.frame.BasicFrameMetrics
import earth.worldwind.globe.projection.MercatorProjection
import earth.worldwind.globe.projection.Wgs84Projection
import earth.worldwind.util.Logger
import earth.worldwind.util.Logger.log
import kotlinx.coroutines.cancelChildren
Expand Down Expand Up @@ -51,17 +55,22 @@ class MainActivity: AppCompatActivity(), NavigationView.OnNavigationItemSelected
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
onCreateDrawer()
if (findViewById<View?>(R.id.code_container) != null) {
if (findViewById<FrameLayout>(R.id.code_container) != null) {
// The code container view will be present only in the
// large-screen layouts (res/values-w900dp).
// If this view is present, then the
// activity should be in two-pane mode.
twoPaneView = true
}
findViewById<CheckBox>(R.id.is2d).setOnCheckedChangeListener { _, checked ->
val currentFragment = supportFragmentManager.fragments[0] as BasicGlobeFragment
currentFragment.wwd.engine.globe.projection = if (checked) MercatorProjection() else Wgs84Projection()
currentFragment.wwd.requestRedraw()
}
if (!twoPaneView) {
val codeViewButton = findViewById<FloatingActionButton>(R.id.fab)
codeViewButton.visibility = View.VISIBLE // is set to GONE in layout
codeViewButton.setOnClickListener { view: View ->
codeViewButton.visibility = VISIBLE // is set to GONE in layout
codeViewButton.setOnClickListener { view ->
val context = view.context
val intent = Intent(context, CodeActivity::class.java)
val bundle = Bundle()
Expand Down Expand Up @@ -315,6 +324,7 @@ class MainActivity: AppCompatActivity(), NavigationView.OnNavigationItemSelected
}
val drawer = findViewById<DrawerLayout>(R.id.drawer_layout)
drawer.closeDrawer(GravityCompat.START)
findViewById<CheckBox>(R.id.is2d).isChecked = false
return true
}

Expand Down
11 changes: 10 additions & 1 deletion worldwind-tutorials/src/androidMain/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,16 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
app:popupTheme="@style/AppTheme.PopupOverlay">
<CheckBox
android:id="@+id/is2d"
android:text="2D"
android:button="@null"
android:drawableEnd="?android:attr/listChoiceIndicatorMultiple"
android:layout_gravity="end"
android:layout_width="wrap_content"
android:layout_height="40dp"/>
</androidx.appcompat.widget.Toolbar>

</com.google.android.material.appbar.AppBarLayout>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package earth.worldwind.tutorials
import earth.worldwind.WorldWindow
import earth.worldwind.gesture.SelectDragCallback
import earth.worldwind.globe.elevation.coverage.BasicElevationCoverage
import earth.worldwind.globe.projection.MercatorProjection
import earth.worldwind.globe.projection.Wgs84Projection
import earth.worldwind.layer.BackgroundLayer
import earth.worldwind.layer.atmosphere.AtmosphereLayer
import earth.worldwind.layer.mercator.MercatorLayerFactory
Expand All @@ -21,7 +23,8 @@ fun main() {
window.onload = {
// Create a WorldWindow for the canvas.
val wwd = WorldWindow(document.getElementById("WorldWindow") as HTMLCanvasElement)
val select = document.getElementById("Tutorials") as HTMLSelectElement
val tutorialSelect = document.getElementById("Tutorials") as HTMLSelectElement
val projectionSelect = document.getElementById("Projections") as HTMLSelectElement
val actionsContainer = document.getElementById("Actions") as HTMLDivElement
val tutorials = mapOf (
"Basic globe" to BasicTutorial(wwd.engine),
Expand All @@ -44,6 +47,10 @@ fun main() {
//"WCS Elevation" to WcsElevationTutorial(wwd.engine),
"Elevation Heatmap" to ElevationHeatmapTutorial(wwd.engine),
)
val projections = mapOf (
"WGS84 Projection" to Wgs84Projection(),
"Mercator Projection" to MercatorProjection()
)
var currentTutorial: String? = null

// Add some image layers to the WorldWindow's globe.
Expand Down Expand Up @@ -90,15 +97,28 @@ fun main() {
wwd.requestRedraw()
}

fun selectProjection(projectionName: String) {
wwd.engine.globe.projection = projections[projectionName]!!
wwd.requestRedraw()
}

tutorials.keys.forEach {
(document.createElement("option") as HTMLOptionElement).apply {
value = it
innerHTML = it
select.append(this)
tutorialSelect.append(this)
}
}
select.onchange = { event -> selectTutorial((event.target as HTMLSelectElement).value) }

projections.keys.forEach {
(document.createElement("option") as HTMLOptionElement).apply {
value = it
innerHTML = it
projectionSelect.append(this)
}
}
tutorialSelect.onchange = { event -> selectTutorial((event.target as HTMLSelectElement).value) }
projectionSelect.onchange = { event -> selectProjection((event.target as HTMLSelectElement).value)}
selectTutorial(tutorials.keys.first())
selectProjection(projections.keys.first())
}
}
8 changes: 8 additions & 0 deletions worldwind-tutorials/src/jsMain/resources/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
width: 200px;
}

#Projections {
position: fixed;
top: 60px;
left: 30px;
width: 200px;
}

#Actions {
position: fixed;
bottom: 30px;
Expand All @@ -39,6 +46,7 @@
Your browser does not support HTML5 Canvas.
</canvas>
<select id="Tutorials"></select>
<select id="Projections"></select>
<div id="Actions"></div>
<script src="worldwind-tutorials.js"></script>
</body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package earth.worldwind

import android.view.MotionEvent
import earth.worldwind.geom.LookAt
import earth.worldwind.geom.Vec3
import earth.worldwind.gesture.*
import earth.worldwind.gesture.GestureState.*
import kotlin.math.cos
Expand All @@ -14,6 +15,7 @@ open class BasicWorldWindowController(protected val wwd: WorldWindow): WorldWind
protected var lastRotation = 0f
protected val lookAt = LookAt()
protected val beginLookAt = LookAt()
protected val beginLookAtPoint = Vec3()
protected var activeGestures = 0
protected val panRecognizer: GestureRecognizer = PanRecognizer().also {
it.addListener(this)
Expand Down Expand Up @@ -68,6 +70,10 @@ open class BasicWorldWindowController(protected val wwd: WorldWindow): WorldWind
}

protected open fun handlePan(recognizer: GestureRecognizer) {
if (wwd.engine.globe.is2D) handlePan2D(recognizer) else handlePan3D(recognizer)
}

protected open fun handlePan3D(recognizer: GestureRecognizer) {
val state = recognizer.state
val dx = recognizer.translationX
val dy = recognizer.translationY
Expand All @@ -81,11 +87,10 @@ open class BasicWorldWindowController(protected val wwd: WorldWindow): WorldWind
// Get observation point position.
var lat = lookAt.position.latitude
var lon = lookAt.position.longitude
val rng = lookAt.range

// Convert the translation from screen coordinates to degrees. Use observation point range as a metric for
// converting screen pixels to meters, and use the globe's radius for converting from meters to arc degrees.
val metersPerPixel = wwd.engine.pixelSizeAtDistance(rng)
val metersPerPixel = wwd.engine.pixelSizeAtDistance(lookAt.range)
val forwardMeters = (dy - lastY) * metersPerPixel
val sideMeters = -(dx - lastX) * metersPerPixel
lastX = dx
Expand Down Expand Up @@ -121,6 +126,40 @@ open class BasicWorldWindowController(protected val wwd: WorldWindow): WorldWind
}
}

protected open fun handlePan2D(recognizer: GestureRecognizer) {
val state = recognizer.state
val tx = recognizer.translationX
val ty = recognizer.translationY

when (state) {
BEGAN -> {
gestureDidBegin()
wwd.engine.globe.geographicToCartesian(
beginLookAt.position.latitude,
beginLookAt.position.longitude,
beginLookAt.position.altitude,
beginLookAtPoint
)
}
CHANGED -> {
val metersPerPixel = wwd.engine.pixelSizeAtDistance(lookAt.range)
val forwardMeters = ty * metersPerPixel
val sideMeters = - tx * metersPerPixel

// Adjust the change in latitude and longitude based on observation point heading.
val heading = lookAt.heading
val sinHeading = sin(heading.inRadians)
val cosHeading = cos(heading.inRadians)
val x = beginLookAtPoint.x + forwardMeters * sinHeading + sideMeters * cosHeading
val y = beginLookAtPoint.y + forwardMeters * cosHeading - sideMeters * sinHeading
wwd.engine.globe.cartesianToGeographic(x, y, beginLookAtPoint.z, lookAt.position)
applyChanges()
}
ENDED, CANCELLED -> gestureDidEnd()
else -> {}
}
}

protected open fun handlePinch(recognizer: GestureRecognizer) {
val state = recognizer.state
val scale = (recognizer as PinchRecognizer).scaleWithOffset
Expand Down
Loading

0 comments on commit 9ae42ec

Please sign in to comment.