diff --git a/app/build.gradle b/app/build.gradle index 987789f0a..8672b9e34 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,7 +6,7 @@ android { defaultConfig { applicationId "com.google.android.stardroid" - minSdkVersion 21 + minSdkVersion 26 targetSdkVersion 31 versionCode 1530 versionName "1.10.0 - RC1" diff --git a/app/src/main/java/com/google/android/stardroid/ApplicationModule.java b/app/src/main/java/com/google/android/stardroid/ApplicationModule.java index 8313c53fd..d607f1b9c 100644 --- a/app/src/main/java/com/google/android/stardroid/ApplicationModule.java +++ b/app/src/main/java/com/google/android/stardroid/ApplicationModule.java @@ -18,6 +18,7 @@ import com.google.android.stardroid.control.MagneticDeclinationCalculator; import com.google.android.stardroid.control.RealMagneticDeclinationCalculator; import com.google.android.stardroid.control.ZeroMagneticDeclinationCalculator; +import com.google.android.stardroid.layers.CometsLayer; import com.google.android.stardroid.layers.EclipticLayer; import com.google.android.stardroid.layers.GridLayer; import com.google.android.stardroid.layers.HorizonLayer; @@ -153,6 +154,7 @@ LayerManager provideLayerManager( layerManager.addLayer(new ConstellationsLayer(assetManager, resources)); layerManager.addLayer(new SolarSystemLayer(model, resources, preferences)); layerManager.addLayer(new MeteorShowerLayer(model, resources)); + layerManager.addLayer(new CometsLayer(model, resources)); layerManager.addLayer(new GridLayer(resources, 24, 9)); layerManager.addLayer(new HorizonLayer(model, resources)); layerManager.addLayer(new EclipticLayer(resources)); diff --git a/app/src/main/java/com/google/android/stardroid/activities/DynamicStarMapActivity.java b/app/src/main/java/com/google/android/stardroid/activities/DynamicStarMapActivity.java index f869c0dc0..cf496d4d6 100644 --- a/app/src/main/java/com/google/android/stardroid/activities/DynamicStarMapActivity.java +++ b/app/src/main/java/com/google/android/stardroid/activities/DynamicStarMapActivity.java @@ -615,7 +615,7 @@ private void doSearchWithIntent(Intent searchIntent) { } else { Log.d(TAG, "One result returned."); final SearchResult result = results.get(0); - activateSearchTarget(result.coords, result.capitalizedName); + activateSearchTarget(result.coords(), result.capitalizedName); } } diff --git a/app/src/main/java/com/google/android/stardroid/activities/dialogs/MultipleSearchResultsDialogFragment.java b/app/src/main/java/com/google/android/stardroid/activities/dialogs/MultipleSearchResultsDialogFragment.java index d2b5e9484..cbc5718fa 100644 --- a/app/src/main/java/com/google/android/stardroid/activities/dialogs/MultipleSearchResultsDialogFragment.java +++ b/app/src/main/java/com/google/android/stardroid/activities/dialogs/MultipleSearchResultsDialogFragment.java @@ -48,7 +48,7 @@ public void onClick(DialogInterface dialog, int whichButton) { Log.d(TAG, "Many search results Dialog closed with cancel"); } else { final SearchResult item = multipleSearchResultsAdaptor.getItem(whichButton); - parentActivity.activateSearchTarget(item.coords, item.capitalizedName); + parentActivity.activateSearchTarget(item.coords(), item.capitalizedName); } dialog.dismiss(); } diff --git a/app/src/main/java/com/google/android/stardroid/activities/dialogs/NoSearchResultsDialogFragment.java b/app/src/main/java/com/google/android/stardroid/activities/dialogs/NoSearchResultsDialogFragment.java index f8fbc184b..5d343eab8 100644 --- a/app/src/main/java/com/google/android/stardroid/activities/dialogs/NoSearchResultsDialogFragment.java +++ b/app/src/main/java/com/google/android/stardroid/activities/dialogs/NoSearchResultsDialogFragment.java @@ -32,7 +32,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { ((HasComponent) getActivity()).getComponent().inject(this); return new AlertDialog.Builder(parentActivity) - .setTitle(R.string.no_search_title).setMessage(R.string.no_search_results_text) + .setTitle(R.string.no_search_title).setMessage(R.string.no_search_results_text2) .setNegativeButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog1, int whichButton) { Log.d(TAG, "No search results Dialog closed"); diff --git a/app/src/main/java/com/google/android/stardroid/layers/AbstractRenderablesLayer.kt b/app/src/main/java/com/google/android/stardroid/layers/AbstractRenderablesLayer.kt index a75539880..823f8c1ba 100644 --- a/app/src/main/java/com/google/android/stardroid/layers/AbstractRenderablesLayer.kt +++ b/app/src/main/java/com/google/android/stardroid/layers/AbstractRenderablesLayer.kt @@ -50,9 +50,8 @@ abstract class AbstractRenderablesLayer(resources: Resources, private val should linePrimitives.addAll(renderables.lines) val names = astroRenderable.names if (!names.isEmpty()) { - val searchLoc = astroRenderable.searchLocation for (name in names) { - searchIndex[name.toLowerCase()] = SearchResult(name, searchLoc) + searchIndex[name.toLowerCase()] = SearchResult(name, astroRenderable) prefixStore.add(name.toLowerCase()) } } @@ -105,9 +104,9 @@ abstract class AbstractRenderablesLayer(resources: Resources, private val should override fun searchByObjectName(name: String): List { Log.d(TAG, "Search planets layer for $name") - val matches: MutableList = ArrayList() + val matches = ArrayList() val searchResult = searchIndex[name.toLowerCase()] - if (searchResult != null) { + if (searchResult != null && searchResult.renderable.isVisible) { matches.add(searchResult) } Log.d(TAG, layerName + " provided " + matches.size + "results for " + name) @@ -122,6 +121,6 @@ abstract class AbstractRenderablesLayer(resources: Resources, private val should } companion object { - private val TAG = MiscUtil.getTag(AbstractRenderablesLayer::class.java) + val TAG = MiscUtil.getTag(AbstractRenderablesLayer::class.java) } } \ No newline at end of file diff --git a/app/src/main/java/com/google/android/stardroid/layers/CometsLayer.kt b/app/src/main/java/com/google/android/stardroid/layers/CometsLayer.kt new file mode 100644 index 000000000..529a6bc20 --- /dev/null +++ b/app/src/main/java/com/google/android/stardroid/layers/CometsLayer.kt @@ -0,0 +1,325 @@ +// Copyright 2011 Google Inc. +// +// 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 com.google.android.stardroid.layers + +import android.content.res.Resources +import android.os.Build +import androidx.annotation.RequiresApi +import com.google.android.stardroid.R +import com.google.android.stardroid.base.TimeConstants +import com.google.android.stardroid.control.AstronomerModel +import com.google.android.stardroid.math.RaDec.Companion.decDegreesFromDMS +import com.google.android.stardroid.math.RaDec.Companion.raDegreesFromHMS +import com.google.android.stardroid.math.Vector3 +import com.google.android.stardroid.math.getGeocentricCoords +import com.google.android.stardroid.renderables.* +import com.google.android.stardroid.renderer.RendererObjectManager.UpdateType +import com.google.android.stardroid.util.MiscUtil +import java.time.LocalDate +import java.time.LocalTime +import java.time.ZoneId +import java.time.ZonedDateTime +import java.util.* +import kotlin.math.abs + +private const val METEOR_SOURCE_PROVIDER = "source_provider.6" + +/** + * A [Layer] to show the occasional transient comet. + * + * @author John Taylor + */ +// Some of this might eventually get generalized for other 'interpolatable' objects. +class CometsLayer(private val model: AstronomerModel, resources: Resources) : + AbstractRenderablesLayer(resources, true) { + private val comets = ArrayList() + + @RequiresApi(Build.VERSION_CODES.O) + data class TimeEntry( + private val localdate: LocalDate, // Assumed UTC + val raDeg: Float, + val decDeg: Float, + val mag: Float + ) { + val date: Date + + init { + val zonedDateTime = ZonedDateTime.of(localdate, LocalTime.MIDNIGHT, ZoneId.of("UTC")) + date = Date.from(zonedDateTime.toInstant()) + } + } + + /** + * Simple linear interpolation. + * Nothing fancy - just linear scan so O(N) in the number of entries. + */ + class Interpolator(private val xs: List, private val ys: List) { + init { + if (xs.size != ys.size) throw IllegalArgumentException("Arrays must be of same length") + if (xs.size < 2) throw IllegalArgumentException("Must have at least two entries") + } + + fun interpolate(x: Long): Float { + if (x < xs.first() || x > xs.last()) throw IllegalArgumentException("Input out of bounds") + for (i in 0..this.xs.size - 2) { + if (x >= xs[i] && x <= xs[i + 1]) { + return ((x - xs[i]) * ys[i + 1] + (xs[i + 1] - x) * ys[i]) / (xs[i + 1] - xs[i]) + } + } + throw IllegalArgumentException("Ran off the end - this shouldn't happen") + } + } + + private class Comet( + val nameId: Int, private val positions: List + ) { + val start: Date + val end: Date + val xInterpolator: Interpolator + val yInterpolator: Interpolator + val zInterpolator: Interpolator + val magInterpolator: Interpolator + + fun pos(time: Date): Vector3 { + if (time.before(start) || time.after(end)) { + // Comet shouldn't be visible, but we'll pin it to the start location. + return getGeocentricCoords(positions.first().raDeg, positions.first().decDeg) + } + return Vector3( + xInterpolator.interpolate(time.time), + yInterpolator.interpolate(time.time), + zInterpolator.interpolate(time.time) + ).normalizedCopy() + } + + init { + if (positions.isEmpty()) throw IllegalStateException("Comet has no positions") + start = positions.first().date + end = positions.last().date + var previous = start + // TODO(johntaylor): quick and dirty for now, but do something more kotlin-ey later. + // Maybe with sequences. I don't have time right now. + val times = mutableListOf() + val xValues = mutableListOf() + val yValues = mutableListOf() + val zValues = mutableListOf() + val mags = mutableListOf() + for (entry in positions) { + if (entry.date.before(previous)) throw java.lang.IllegalStateException( + "Comet dates not " + + "in ascending order" + ) + previous = entry.date + times.add(entry.date.time) + val geoCentricCoords = getGeocentricCoords(entry.raDeg, entry.decDeg) + xValues.add(geoCentricCoords.x) + yValues.add(geoCentricCoords.y) + zValues.add(geoCentricCoords.z) + mags.add(entry.mag) + } + xInterpolator = Interpolator(times, xValues) + yInterpolator = Interpolator(times, yValues) + zInterpolator = Interpolator(times, zValues) + magInterpolator = Interpolator(times, mags) + } + } + + private fun initializeComets() { + comets.add( + Comet( + R.string.comet_leonard, + listOf( + TimeEntry( + LocalDate.of(2021, 12, 11), + raDegreesFromHMS(16f, 18f, 29f), + decDegreesFromDMS(7f, 08f, 17f), + 8f + ), + TimeEntry( + LocalDate.of(2021, 12, 12), + raDegreesFromHMS(16f, 49f, 54f), + decDegreesFromDMS(1f, 32f, 35f), + 8f + ), + TimeEntry( + LocalDate.of(2021, 12, 13), + raDegreesFromHMS(17f, 22f, 18f), + decDegreesFromDMS(-4f, 18f, 41f), + 8f + ), + TimeEntry( + LocalDate.of(2021, 12, 14), + raDegreesFromHMS(17f, 54f, 23f), + decDegreesFromDMS(-9f, 57f, 38f), + 8f + ), + TimeEntry( + LocalDate.of(2021, 12, 15), + raDegreesFromHMS(18f, 24f, 50f), + decDegreesFromDMS(-15f, 00f, 35f), + 8f + ), + TimeEntry( + LocalDate.of(2021, 12, 16), + raDegreesFromHMS(18f, 52f, 44f), + decDegreesFromDMS(-19f, 15f, 14f), + 8f + ), + TimeEntry( + LocalDate.of(2021, 12, 17), + raDegreesFromHMS(19f, 17f, 35f), + decDegreesFromDMS(-22f, 40f, 24f), + 8f + ), + TimeEntry( + LocalDate.of(2021, 12, 18), + raDegreesFromHMS(19f, 39f, 19f), + decDegreesFromDMS(-25f, 21f, 51f), + 8f + ), + TimeEntry( + LocalDate.of(2021, 12, 19), + raDegreesFromHMS(19f, 58f, 04f), + decDegreesFromDMS(-27f, 27f, 36f), + 9f + ), + TimeEntry( + LocalDate.of(2021, 12, 20), + raDegreesFromHMS(20f, 14f, 07f), + decDegreesFromDMS(-29f, 05f, 35f), + 9f + ), + TimeEntry( + LocalDate.of(2021, 12, 21), + raDegreesFromHMS(20f, 27f, 50f), + decDegreesFromDMS(-30f, 22f, 22f), + 9f + ), + TimeEntry( + LocalDate.of(2021, 12, 22), + raDegreesFromHMS(20f, 39f, 30f), + decDegreesFromDMS(-31f, 23f, 09f), + 9f + ), + TimeEntry( + LocalDate.of(2021, 12, 23), + raDegreesFromHMS(20f, 49f, 29f), + decDegreesFromDMS(-32f, 11f, 50f), + 9.23f + ), + TimeEntry( + LocalDate.of(2021, 12, 24), + raDegreesFromHMS(20f, 58f, 00f), + decDegreesFromDMS(-32f, 51f, 30f), + 9.36f + ), + ) + ) + ) + } + + override fun initializeAstroSources(sources: ArrayList) { + for (comet in comets) { + sources.add(CometRenderable(model, comet, resources)) + } + } + + override val layerDepthOrder = 80 + + // This is the same as the meteor layer. + override val preferenceId = METEOR_SOURCE_PROVIDER + override val layerName = "Comets" + override val layerNameId = R.string.show_comet_layer_pref + + private class CometRenderable( + private val model: AstronomerModel, + private val comet: Comet, + resources: Resources + ) : AbstractAstronomicalRenderable() { + // TODO(johntaylor): why are we overriding these properties? + override val labels = ArrayList() + override val images = ArrayList() + private var lastUpdateTimeMs = 0L + private val theImage: ImagePrimitive + private val label: TextPrimitive + private val name = resources.getString(comet.nameId) + override val names = ArrayList() + private var coords: Vector3 + override var searchLocation = Vector3.zero() + private set + + private fun updateComets() { + lastUpdateTimeMs = model.time.time + // We will only show the comet between certain times. + val now = model.time + theImage.setUpVector(UP) + if (now.after(comet.start) && now.before(comet.end)) { + label.text = name + theImage.setImageId(R.drawable.comet) + isVisible = true + // TODO(johntaylor): wait...are we really updating the image positions by dipping into + // the coordinate object we pass in? That's terrible. + // Must assign here as a result. + coords.assign(comet.pos(now)) + searchLocation = comet.pos(now) + } else { + // TODO(johntaylor): we need a better solution than just blanking out the object! + label.text = " " + theImage.setImageId(R.drawable.blank) + isVisible = false + } + } + + override fun initialize(): Renderable { + updateComets() + return this + } + + override fun update(): EnumSet { + val updateTypes = EnumSet.noneOf(UpdateType::class.java) + if (abs(model.time.time - lastUpdateTimeMs) > UPDATE_FREQ_MS) { + updateComets() + updateTypes.add(UpdateType.UpdateImages) + updateTypes.add(UpdateType.Reset) + } + return updateTypes + } + + companion object { + private const val LABEL_COLOR = 0xf67e81 + private val UP = Vector3(0.0f, 1.0f, 0.0f) + private const val UPDATE_FREQ_MS = 1L * TimeConstants.MILLISECONDS_PER_HOUR + private const val SCALE_FACTOR = 0.03f + val TAG = MiscUtil.getTag(CometsLayer::class.java) + } + + init { + names.add(name) + // blank is a 1pxX1px image that should be invisible. + // We'd prefer not to show any image except on the shower dates, but there + // appears to be a bug in the renderer/layer interface in that Update values are not + // respected. Ditto the label. + // TODO(johntaylor): fix the bug and remove this blank image + coords = comet.pos(model.time) + theImage = ImagePrimitive(coords, resources, R.drawable.blank, UP, SCALE_FACTOR) + images.add(theImage) + label = TextPrimitive(coords, name, LABEL_COLOR) + labels.add(label) + } + } + + init { + initializeComets() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/google/android/stardroid/layers/MeteorShowerLayer.kt b/app/src/main/java/com/google/android/stardroid/layers/MeteorShowerLayer.kt index 2676fda40..6702b026e 100644 --- a/app/src/main/java/com/google/android/stardroid/layers/MeteorShowerLayer.kt +++ b/app/src/main/java/com/google/android/stardroid/layers/MeteorShowerLayer.kt @@ -37,7 +37,7 @@ class MeteorShowerLayer(private val model: AstronomerModel, resources: Resources /** * Represents a meteor shower. */ - private class Shower( + private data class Shower( val nameId: Int, val radiant: Vector3, val start: Date, val peak: Date, val end: Date, val peakMeteorsPerHour: Int ) @@ -174,6 +174,7 @@ class MeteorShowerLayer(private val model: AstronomerModel, resources: Resources theImage.setUpVector(UP) // TODO(johntaylor): consider varying the sizes by scaling factor as time progresses. if (now.after(shower.start) && now.before(shower.end)) { + isVisible = true label.text = name val percentToPeak = if (now.before(shower.peak)) { (now.time - shower.start.time).toDouble() / @@ -190,6 +191,7 @@ class MeteorShowerLayer(private val model: AstronomerModel, resources: Resources theImage.setImageId(R.drawable.meteor1_screen) } } else { + isVisible = false label.text = " " theImage.setImageId(R.drawable.blank) } diff --git a/app/src/main/java/com/google/android/stardroid/math/RaDec.kt b/app/src/main/java/com/google/android/stardroid/math/RaDec.kt index 66987b648..800e72324 100644 --- a/app/src/main/java/com/google/android/stardroid/math/RaDec.kt +++ b/app/src/main/java/com/google/android/stardroid/math/RaDec.kt @@ -67,7 +67,7 @@ data class RaDec( } @JvmStatic - fun calculateRaDecDist(coords: Vector3): RaDec { + fun fromGeocentricCoords(coords: Vector3): RaDec { // find the RA and DEC from the rectangular equatorial coords val ra = mod2pi(atan2(coords.y, coords.x)) * RADIANS_TO_DEGREES val dec = @@ -76,20 +76,6 @@ data class RaDec( return RaDec(ra, dec) } - @JvmStatic - fun fromGeocentricCoords(coords: Vector3): RaDec { - var raRad = atan2(coords.y, coords.x) - if (raRad < 0) raRad += TWO_PI - val decRad = atan2( - coords.z, - sqrt(coords.x * coords.x + coords.y * coords.y) - ) - return RaDec( - raRad * RADIANS_TO_DEGREES, - decRad * RADIANS_TO_DEGREES - ) - } - @JvmStatic fun fromHoursMinutesSeconds( raHours: Float, raMinutes: Float, raSeconds: Float, diff --git a/app/src/main/java/com/google/android/stardroid/renderables/AbstractAstronomicalRenderable.kt b/app/src/main/java/com/google/android/stardroid/renderables/AbstractAstronomicalRenderable.kt index 0a98a01bd..166ea7875 100644 --- a/app/src/main/java/com/google/android/stardroid/renderables/AbstractAstronomicalRenderable.kt +++ b/app/src/main/java/com/google/android/stardroid/renderables/AbstractAstronomicalRenderable.kt @@ -35,10 +35,11 @@ abstract class AbstractAstronomicalRenderable : AstronomicalRenderable { /** Implementors of this method must implement [.getSearchLocation]. */ override val names: List get() = emptyList() - override val searchLocation: Vector3? + override val searchLocation: Vector3 get() { throw UnsupportedOperationException("Should not be called") } + override var isVisible = true override val images: List get() = emptyList() override val labels: List diff --git a/app/src/main/java/com/google/android/stardroid/renderables/AstronomicalRenderable.kt b/app/src/main/java/com/google/android/stardroid/renderables/AstronomicalRenderable.kt index 9de765e90..147d48512 100644 --- a/app/src/main/java/com/google/android/stardroid/renderables/AstronomicalRenderable.kt +++ b/app/src/main/java/com/google/android/stardroid/renderables/AstronomicalRenderable.kt @@ -36,7 +36,13 @@ interface AstronomicalRenderable : Renderable { * This is the point to which the user will be directed for a search. * @return */ - val searchLocation: Vector3? + val searchLocation: Vector3 + + /** + * True if the object should be visible in search results + */ + var isVisible: Boolean + /** * Returns the zoom level to which the user should be taken (in manual mode) * to completely see this object when searching. diff --git a/app/src/main/java/com/google/android/stardroid/renderables/proto/ProtobufAstronomicalRenderable.kt b/app/src/main/java/com/google/android/stardroid/renderables/proto/ProtobufAstronomicalRenderable.kt index 9e35865e1..464525a3f 100644 --- a/app/src/main/java/com/google/android/stardroid/renderables/proto/ProtobufAstronomicalRenderable.kt +++ b/app/src/main/java/com/google/android/stardroid/renderables/proto/ProtobufAstronomicalRenderable.kt @@ -124,7 +124,7 @@ class ProtobufAstronomicalRenderable( return names!! } */ - override val searchLocation: Vector3? + override val searchLocation: Vector3 get() = getCoords(proto.searchLocation) override val points: List diff --git a/app/src/main/java/com/google/android/stardroid/search/PrefixStore.java b/app/src/main/java/com/google/android/stardroid/search/PrefixStore.java index 3f5b479f4..66548b222 100644 --- a/app/src/main/java/com/google/android/stardroid/search/PrefixStore.java +++ b/app/src/main/java/com/google/android/stardroid/search/PrefixStore.java @@ -40,6 +40,9 @@ static private class TrieNode { private static final Set EMPTY_SET = Collections.unmodifiableSet(new HashSet()); + public void clear() { + root = new TrieNode(); + } /** * Search for any queries matching this prefix. Note that the prefix is * case-independent. diff --git a/app/src/main/java/com/google/android/stardroid/search/SearchResult.java b/app/src/main/java/com/google/android/stardroid/search/SearchResult.java index 4ac06046c..40045f54c 100644 --- a/app/src/main/java/com/google/android/stardroid/search/SearchResult.java +++ b/app/src/main/java/com/google/android/stardroid/search/SearchResult.java @@ -15,6 +15,7 @@ package com.google.android.stardroid.search; import com.google.android.stardroid.math.Vector3; +import com.google.android.stardroid.renderables.AstronomicalRenderable; /** * A single search result. @@ -22,18 +23,23 @@ * @author John Taylor */ public class SearchResult { - /** The coordinates of the object.*/ - public Vector3 coords; - /** The user-presentable name of the object, properly capitalized.*/ + public AstronomicalRenderable renderable; + /** + * The user-presentable name of the object, properly capitalized. + */ public String capitalizedName; /** * @param capitalizedName The user-presentable name of the object, properly capitalized. - * @param coords The geocentric coordinates of the object. + * @param renderable The searched for object.. */ - public SearchResult(String capitalizedName, Vector3 coords) { + public SearchResult(String capitalizedName, AstronomicalRenderable renderable) { this.capitalizedName = capitalizedName; - this.coords = coords; + this.renderable = renderable; + } + + public Vector3 coords() { + return renderable.getSearchLocation(); } @Override diff --git a/app/src/main/java/com/google/android/stardroid/space/SolarSystemObject.kt b/app/src/main/java/com/google/android/stardroid/space/SolarSystemObject.kt index 83665911c..1d560b630 100644 --- a/app/src/main/java/com/google/android/stardroid/space/SolarSystemObject.kt +++ b/app/src/main/java/com/google/android/stardroid/space/SolarSystemObject.kt @@ -5,7 +5,7 @@ import com.google.android.stardroid.ephemeris.SolarSystemBody import com.google.android.stardroid.math.* import java.util.* -import com.google.android.stardroid.math.RaDec.Companion.calculateRaDecDist +import com.google.android.stardroid.math.RaDec.Companion.fromGeocentricCoords import kotlin.math.cos import com.google.android.stardroid.math.Vector3 @@ -59,7 +59,7 @@ abstract class SolarSystemObject(protected val solarSystemBody : SolarSystemBody val moon: Vector3 = getGeocentricCoords(moonRaDec) val sunCoords: Vector3 = heliocentricCoordinatesFromOrbitalElements(SolarSystemBody.Earth.getOrbitalElements(time)) - val sunRaDec = calculateRaDecDist(sunCoords) + val sunRaDec = fromGeocentricCoords(sunCoords) val (x, y, z) = getGeocentricCoords(sunRaDec) return 180.0f - MathUtils.acos(x * moon.x + y * moon.y + z * moon.z) * RADIANS_TO_DEGREES @@ -75,6 +75,7 @@ abstract class SolarSystemObject(protected val solarSystemBody : SolarSystemBody val earthDistance = planetCoords.distanceFrom(earthCoords) // Finally, calculate the phase of the body. + // TODO(johntaylor): reexamine this. return MathUtils.acos( (earthDistance * earthDistance + planetCoords.length2 - diff --git a/app/src/main/java/com/google/android/stardroid/space/SunOrbitingObject.kt b/app/src/main/java/com/google/android/stardroid/space/SunOrbitingObject.kt index 60489eaa5..819842359 100644 --- a/app/src/main/java/com/google/android/stardroid/space/SunOrbitingObject.kt +++ b/app/src/main/java/com/google/android/stardroid/space/SunOrbitingObject.kt @@ -13,10 +13,10 @@ open class SunOrbitingObject(solarSystemBody : SolarSystemBody) : SolarSystemObj override fun getRaDec(date: Date): RaDec { val earthCoords = heliocentricCoordinatesFromOrbitalElements(SolarSystemBody.Earth.getOrbitalElements(date)) - var myCoords = getMyHeliocentricCoordinates(date) - myCoords.minusAssign(earthCoords) + val myCoords = getMyHeliocentricCoordinates(date) + myCoords -= earthCoords val equ = convertToEquatorialCoordinates(myCoords) - return RaDec.calculateRaDecDist(equ) + return RaDec.fromGeocentricCoords(equ) } protected open fun getMyHeliocentricCoordinates(date: Date) = diff --git a/app/src/main/res/drawable/comet.png b/app/src/main/res/drawable/comet.png new file mode 100644 index 000000000..8f866398e Binary files /dev/null and b/app/src/main/res/drawable/comet.png differ diff --git a/app/src/main/res/values/celestial_objects.xml b/app/src/main/res/values/celestial_objects.xml index 40653ecd8..80913a337 100644 --- a/app/src/main/res/values/celestial_objects.xml +++ b/app/src/main/res/values/celestial_objects.xml @@ -1,5 +1,56 @@ + + Andromeda + Aquarius + Aquila + Aries + Auriga + Bootes + Cancer + Canis Major + Capricornus + Carina + Cassiopeia + Centaurus + Cetus + Crux + Cygnus + Draco + Eridanus + Gemini + Grus + Hercules + Hydrus + Leo + Libra + Lupus + Lyra + Ophiuchus + Orion + Pavo + Pegasus + Perseus + Phoenix + Pisces + Piscis Austrinus + Puppis + Sagittarius + Scorpius + Taurus + Tucana + Ursa Major + Ursa Minor + Vela + Virgo + Capricorn + Southern Cross + Scorpio + Little Dipper + Little Bear + Big Dipper + Great Bear + Plough Antlia Apus Ara @@ -47,4 +98,250 @@ Triangulum Australe Volans Vulpecula + + + Sun + Moon + Mercury + Venus + Earth + Mars + Jupiter + Saturn + Uranus + Neptune + Pluto + + + Crab Nebula + Eagle Nebula + Andromeda Galaxy + Orion Nebula + Whirlpool Galaxy + Ring Nebula + Pinwheel Galaxy + Sombrero Galaxy + Hercules Globular Cluster + Pleiades + Cat\'s Eye Nebula + Omega Centauri Globular Cluster + V838 Monocerotis + Supernova 1987a + Hubble Deep Field + Hubble Deep Field + Rho Ophiuchus + + + + Sirius + Canopus + Arcturus + Rigel Kentaurus A + Rigil Kentaurus A + Vega + Rigel + Procyon + Betelgeuse + Achernar + Hadar + Capella + Altair + Acrux + Aldebaran + Spica + Antares + Pollux + Fomalhaut + Deneb + Regulus + Adhara + Shaula + Alnath + Alnilam + Alnair + Alioth + Mirphak + Kaus Australis + Dubhe + Alkaid + Alhena + Castor + Polaris + Alphard + Hamal + Algieba + Diphda + Nunki + Mirach + Alpheratz + Saiph + Kochab + Rasalhague + Algol + Denebola + Alphekka + Etamin + Kaus + Shedir + Caph + Izar + Merak + Enif + Ankaa + Phad + Alderamin + Scheat + Markab + Menkar + Arneb + Unukalhai + Algenib + Vindemiatrix + Alcyone + Dog Star + North Star + Pole Star + Alpha Centauri + Rigil Kent + ε Lyrae + + + M1 + M2 + M3 + M4 + M5 + M6 + M7 + M8 + M9 + M10 + M11 + M12 + M13 + M14 + M15 + M16 + M17 + M18 + M19 + M20 + M21 + M22 + M23 + M24 + M25 + M26 + M27 + M28 + M29 + M30 + M31 + M32 + M33 + M34 + M35 + M36 + M37 + M38 + M39 + M40 + M41 + M42 + M43 + M44 + M45 + M46 + M47 + M48 + M49 + M50 + M51 + M52 + M53 + M54 + M55 + M56 + M57 + M58 + M59 + M60 + M61 + M62 + M63 + M64 + M65 + M66 + M67 + M68 + M69 + M70 + M71 + M72 + M73 + M74 + M75 + M76 + M77 + M78 + M79 + M80 + M81 + M82 + M83 + M84 + M85 + M86 + M87 + M88 + M89 + M90 + M91 + M92 + M93 + M94 + M95 + M96 + M97 + M98 + M99 + M100 + M101 + M102 + M103 + M104 + M105 + M106 + M107 + M108 + M109 + M110 + NGC6543 + NGC5139 + Butterfly Cluster + Scorpion\'s Tail + Winnecke 4 + Beehive Cluster + Praesepe + Seven Sisters + + + International Space Station + Space Shuttle + + + Leonids + Geminids + Quadrantids + Lyrids + Eta Aquariids + Delta Aquariids + Perseids + Orionids + Puppid-Velids + Ursids + + + Comet Leonard + Comet Neowise diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6970902a7..77903a736 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,6 +39,7 @@ Horizon Sky Gradient Meteor Showers + Comets Satellites Hubble Images @@ -68,6 +69,9 @@ e.g. Mars No results No results were found. + + No results were found. Perhaps the object is not + visible at this time. Did you mean? Please select one Searching for %s... @@ -120,34 +124,7 @@ MISSING_LBL - Sun - Moon - Mercury - Venus - Earth - Mars - Jupiter - Saturn - Uranus - Neptune - Pluto - Crab Nebula - Eagle Nebula - Andromeda Galaxy - Orion Nebula - Whirlpool Galaxy - Ring Nebula - Pinwheel Galaxy - Sombrero Galaxy - Hercules Globular Cluster - Pleiades - Cat\'s Eye Nebula - Omega Centauri Globular Cluster - V838 Monocerotis - Supernova 1987a - Hubble Deep Field - Hubble Deep Field - Rho Ophiuchus + Terms of Service Diagnostics @@ -156,264 +133,11 @@ NP SP - Andromeda - Aquarius - Aquila - Aries - Auriga - Bootes - Cancer - Canis Major - Capricornus - Carina - Cassiopeia - Centaurus - Cetus - Crux - Cygnus - Draco - Eridanus - Gemini - Grus - Hercules - Hydrus - Leo - Libra - Lupus - Lyra - Ophiuchus - Orion - Pavo - Pegasus - Perseus - Phoenix - Pisces - Piscis Austrinus - Puppis - Sagittarius - Scorpius - Taurus - Tucana - Ursa Major - Ursa Minor - Vela - Virgo - Capricorn - Southern Cross - Scorpio - Little Dipper - Little Bear - Big Dipper - Great Bear - Plough - - Sirius - Canopus - Arcturus - Rigel Kentaurus A - Rigil Kentaurus A - Vega - Rigel - Procyon - Betelgeuse - Achernar - Hadar - Capella - Altair - Acrux - Aldebaran - Spica - Antares - Pollux - Fomalhaut - Deneb - Regulus - Adhara - Shaula - Alnath - Alnilam - Alnair - Alioth - Mirphak - Kaus Australis - Dubhe - Alkaid - Alhena - Castor - Polaris - Alphard - Hamal - Algieba - Diphda - Nunki - Mirach - Alpheratz - Saiph - Kochab - Rasalhague - Algol - Denebola - Alphekka - Etamin - Kaus - Shedir - Caph - Izar - Merak - Enif - Ankaa - Phad - Alderamin - Scheat - Markab - Menkar - Arneb - Unukalhai - Algenib - Vindemiatrix - Alcyone - Dog Star - North Star - Pole Star - Alpha Centauri - Rigil Kent - ε Lyrae - - M1 - M2 - M3 - M4 - M5 - M6 - M7 - M8 - M9 - M10 - M11 - M12 - M13 - M14 - M15 - M16 - M17 - M18 - M19 - M20 - M21 - M22 - M23 - M24 - M25 - M26 - M27 - M28 - M29 - M30 - M31 - M32 - M33 - M34 - M35 - M36 - M37 - M38 - M39 - M40 - M41 - M42 - M43 - M44 - M45 - M46 - M47 - M48 - M49 - M50 - M51 - M52 - M53 - M54 - M55 - M56 - M57 - M58 - M59 - M60 - M61 - M62 - M63 - M64 - M65 - M66 - M67 - M68 - M69 - M70 - M71 - M72 - M73 - M74 - M75 - M76 - M77 - M78 - M79 - M80 - M81 - M82 - M83 - M84 - M85 - M86 - M87 - M88 - M89 - M90 - M91 - M92 - M93 - M94 - M95 - M96 - M97 - M98 - M99 - M100 - M101 - M102 - M103 - M104 - M105 - M106 - M107 - M108 - M109 - M110 - NGC6543 - NGC5139 - Butterfly Cluster - Scorpion\'s Tail - Winnecke 4 - Beehive Cluster - Praesepe - Seven Sisters - International Space Station - Space Shuttle Failed to load KML file Load KML Load Send usage statistics Make Sky Map better by sending anonymous data to Google Analytics - Leonids - Geminids - Quadrantids - Lyrids - Eta Aquariids - Delta Aquariids - Perseids - Orionids - Puppid-Velids - Ursids Your device may lack orientation sensors - automode may not work Bad news! diff --git a/app/src/test/java/com/google/android/stardroid/layers/CometsLayerTest.kt b/app/src/test/java/com/google/android/stardroid/layers/CometsLayerTest.kt new file mode 100644 index 000000000..6cea0179f --- /dev/null +++ b/app/src/test/java/com/google/android/stardroid/layers/CometsLayerTest.kt @@ -0,0 +1,43 @@ +package com.google.android.stardroid.layers + +import com.google.common.truth.Truth.assertThat +import org.junit.Assert +import org.junit.Test +import java.lang.IllegalArgumentException +import java.lang.RuntimeException + +const val TOL = 0.0001f + +class CometsLayerTest { + @Test + fun testInterpolator_oneSegment() { + val interpolator = CometsLayer.Interpolator(listOf(0, 10), listOf(0f, 2f)) + assertThat(interpolator.interpolate(0)).isWithin(TOL).of(0f) + assertThat(interpolator.interpolate(1)).isWithin(TOL).of(0.2f) + assertThat(interpolator.interpolate(5)).isWithin(TOL).of(1.0f) + assertThat(interpolator.interpolate(8)).isWithin(TOL).of(1.6f) + } + + @Test + fun testInterpolator_twoSegments() { + val interpolator = CometsLayer.Interpolator(listOf(0, 10, 15), listOf(0f, 2f, 4f)) + assertThat(interpolator.interpolate(10)).isWithin(TOL).of(2f) + assertThat(interpolator.interpolate(15)).isWithin(TOL).of(4f) + assertThat(interpolator.interpolate(11)).isWithin(TOL).of(2.4f) + } + + @Test(expected = IllegalArgumentException::class) + fun testInterpolator_throwsOutOfRange() { + CometsLayer.Interpolator(listOf(0, 2, 5), listOf(0f, 2f, 4f)).interpolate(-1) + } + + @Test(expected = IllegalArgumentException::class) + fun testInterpolator_tooFew() { + CometsLayer.Interpolator(listOf(0), listOf(0f)) + } + + @Test(expected = IllegalArgumentException::class) + fun testInterpolator_mismathedsizes() { + CometsLayer.Interpolator(listOf(0, 1, 2), listOf(0f, 2f)) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/google/android/stardroid/math/CoordinateManipulationsTest.kt b/app/src/test/java/com/google/android/stardroid/math/CoordinateManipulationsTest.kt index b8ed87da4..feeb4d479 100644 --- a/app/src/test/java/com/google/android/stardroid/math/CoordinateManipulationsTest.kt +++ b/app/src/test/java/com/google/android/stardroid/math/CoordinateManipulationsTest.kt @@ -13,8 +13,6 @@ // limitations under the License. package com.google.android.stardroid.math -import com.google.android.stardroid.math.MathUtils.cos -import com.google.android.stardroid.math.MathUtils.sin import com.google.android.stardroid.math.RaDec.Companion.fromGeocentricCoords import com.google.common.truth.Truth.assertThat import org.junit.Test diff --git a/designdocs/cometlayer.md b/designdocs/cometlayer.md new file mode 100644 index 000000000..56ad8a387 --- /dev/null +++ b/designdocs/cometlayer.md @@ -0,0 +1,16 @@ +# Comet Layer +To track temporary events like comets we want to have a new layer that + +1 Only shows the objects between certain dates (like the meteor shower layer) +1 Can generate Ra, Dec from a table of positions. + +## Requirements +1 Easy of use: astro data tends to be published as a table of dates and Ra, Dec in hms or dms. +1 Outside the date range the object should disappear +1 Flexible. The initial usecase is comets but that might change later. + +## Quick design sketch +We'll have a new Layer for managing the objects (initially just comet leonard and maybe the +old comet from 2019). Rather than add a new enable/disable button we'll have it show along +with the planets or meteors layer. +