diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 274183bc2..5d17a9ff9 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { kotlin("jvm") - id("org.springframework.boot") version "3.1.2" + id("org.springframework.boot") version "3.1.3" id("io.spring.dependency-management") version "1.1.3" kotlin("plugin.spring") kotlin("kapt") diff --git a/api/objectbox-models/default.json b/api/objectbox-models/default.json index e7018148d..9ccf5766b 100644 --- a/api/objectbox-models/default.json +++ b/api/objectbox-models/default.json @@ -557,10 +557,77 @@ } ], "relations": [] + }, + { + "id": "7:8968304416388002032", + "lastPropertyId": "5:7830389679541367505", + "name": "TLEEntity", + "properties": [ + { + "id": "1:1418769500440679099", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:7290811067773705137", + "name": "source", + "indexId": "14:9187059019963520245", + "type": 6, + "flags": 8 + }, + { + "id": "3:2719054692725905613", + "name": "name", + "type": 9 + }, + { + "id": "4:7447454692522974010", + "name": "tle", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "8:3715657608088091992", + "lastPropertyId": "5:6338712844528940250", + "name": "TLESourceEntity", + "properties": [ + { + "id": "1:1484675797823198018", + "name": "id", + "type": 6, + "flags": 1 + }, + { + "id": "2:3112508440451107751", + "name": "url", + "indexId": "15:6936188058417201855", + "type": 9, + "flags": 2048 + }, + { + "id": "3:6679808789215869090", + "name": "updatedAt", + "type": 6 + }, + { + "id": "4:3330731028661288545", + "name": "enabled", + "type": 1 + }, + { + "id": "5:6338712844528940250", + "name": "deletable", + "type": 1 + } + ], + "relations": [] } ], - "lastEntityId": "6:2301362482019052453", - "lastIndexId": "13:1450764334546729064", + "lastEntityId": "8:3715657608088091992", + "lastIndexId": "15:6936188058417201855", "lastRelationId": "0:0", "lastSequenceId": "0:0", "modelVersion": 5, diff --git a/api/src/main/kotlin/nebulosa/api/controllers/AtlasController.kt b/api/src/main/kotlin/nebulosa/api/controllers/AtlasController.kt index aaf64a419..dcfdb4886 100644 --- a/api/src/main/kotlin/nebulosa/api/controllers/AtlasController.kt +++ b/api/src/main/kotlin/nebulosa/api/controllers/AtlasController.kt @@ -6,9 +6,7 @@ import jakarta.validation.Valid import jakarta.validation.constraints.Min import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Positive -import nebulosa.api.data.entities.DeepSkyObjectEntity -import nebulosa.api.data.entities.LocationEntity -import nebulosa.api.data.entities.StarEntity +import nebulosa.api.data.entities.* import nebulosa.api.data.responses.BodyPositionResponse import nebulosa.api.data.responses.MinorPlanetResponse import nebulosa.api.data.responses.TwilightResponse @@ -120,6 +118,28 @@ class AtlasController( return atlasService.positionOfDSO(locationRepository.withId(location)!!, deepSkyObjectRepository.withId(dso)!!, (date + time).noSeconds()) } + @GetMapping("positionOfSatellite") + fun positionOfSatellite( + @RequestParam location: Long, + @RequestParam @Valid @NotBlank tle: String, + @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam(required = false) date: LocalDate?, + @DateTimeFormat(pattern = "HH:mm") @RequestParam(required = false) time: LocalTime?, + ): BodyPositionResponse { + return atlasService.positionOfSatellite(locationRepository.withId(location)!!, tle, (date + time).noSeconds()) + } + + @GetMapping("searchSatellites") + fun searchSatellites( + @RequestParam(required = false, defaultValue = "") text: String, + ): List { + return atlasService.searchSatellites(text) + } + + @GetMapping("satelliteSources") + fun satelliteSources(): List { + return atlasService.satelliteSources() + } + @GetMapping("twilight") fun twilight( @RequestParam location: Long, @@ -178,6 +198,17 @@ class AtlasController( .altitudePointsOfDSO(locationRepository.withId(location)!!, deepSkyObjectRepository.withId(dso)!!, date.orNow(), stepSize) } + @GetMapping("altitudePointsOfSatellite") + fun altitudePointsOfSatellite( + @RequestParam location: Long, + @RequestParam @Valid @NotBlank tle: String, + @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam(required = false) date: LocalDate?, + @RequestParam(required = false, defaultValue = "1") stepSize: Int, + ): List { + return atlasService + .altitudePointsOfSatellite(locationRepository.withId(location)!!, tle, date.orNow(), stepSize) + } + @GetMapping("searchMinorPlanet") fun searchMinorPlanet(@RequestParam @Valid @NotBlank text: String): MinorPlanetResponse { return atlasService.searchMinorPlanet(text) diff --git a/api/src/main/kotlin/nebulosa/api/data/entities/TLEEntity.kt b/api/src/main/kotlin/nebulosa/api/data/entities/TLEEntity.kt new file mode 100644 index 000000000..4d1f77298 --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/data/entities/TLEEntity.kt @@ -0,0 +1,26 @@ +package nebulosa.api.data.entities + +import com.fasterxml.jackson.annotation.JsonIgnore +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id +import io.objectbox.annotation.Index + +@Entity +data class TLEEntity( + @Id(assignable = true) var id: Long = 0L, + @JsonIgnore @Index var source: Long = 0L, + var name: String = "", + var tle: String = "", +) { + + companion object { + + @JvmStatic + fun from(source: TLESourceEntity, lines: List): TLEEntity { + val name = lines[0] + val id = lines[1].substring(2..6).toLong() + val tle = lines.joinToString("\n") + return TLEEntity(id, source.id, name, tle) + } + } +} diff --git a/api/src/main/kotlin/nebulosa/api/data/entities/TLESourceEntity.kt b/api/src/main/kotlin/nebulosa/api/data/entities/TLESourceEntity.kt new file mode 100644 index 000000000..9b9fc9ae1 --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/data/entities/TLESourceEntity.kt @@ -0,0 +1,14 @@ +package nebulosa.api.data.entities + +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id +import io.objectbox.annotation.Index + +@Entity +data class TLESourceEntity( + @Id var id: Long = 0L, + @Index var url: String = "", + var updatedAt: Long = 0, + var enabled: Boolean = true, + var deletable: Boolean = false, +) diff --git a/api/src/main/kotlin/nebulosa/api/data/serializers/GuiderSerializer.kt b/api/src/main/kotlin/nebulosa/api/data/serializers/GuiderSerializer.kt new file mode 100644 index 000000000..a04742c53 --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/data/serializers/GuiderSerializer.kt @@ -0,0 +1,43 @@ +package nebulosa.api.data.serializers + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.ser.std.StdSerializer +import nebulosa.guiding.GuidePoint +import nebulosa.guiding.Guider +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.stereotype.Component + +@Component +@Qualifier("serializer") +class GuiderSerializer : StdSerializer(Guider::class.java) { + + override fun serialize( + guider: Guider, + gen: JsonGenerator, + provider: SerializerProvider, + ) { + gen.writeStartObject() + gen.writeFieldName("lockPosition") + gen.writeGuidePoint(guider.lockPosition) + gen.writeFieldName("primaryStar") + gen.writeGuidePoint(guider.primaryStar) + gen.writeNumberField("searchRegion", guider.searchRegion) + gen.writeBooleanField("looping", guider.isLooping) + gen.writeBooleanField("calibrating", guider.isCalibrating) + gen.writeBooleanField("guiding", guider.isGuiding) + gen.writeEndObject() + } + + companion object { + + @JvmStatic + private fun JsonGenerator.writeGuidePoint(point: GuidePoint) { + writeStartObject() + writeNumberField("x", point.x) + writeNumberField("y", point.y) + writeBooleanField("valid", point.valid) + writeEndObject() + } + } +} diff --git a/api/src/main/kotlin/nebulosa/api/repositories/BoxRepository.kt b/api/src/main/kotlin/nebulosa/api/repositories/BoxRepository.kt index c24000ff6..301ddb041 100644 --- a/api/src/main/kotlin/nebulosa/api/repositories/BoxRepository.kt +++ b/api/src/main/kotlin/nebulosa/api/repositories/BoxRepository.kt @@ -19,6 +19,8 @@ sealed class BoxRepository : Iterable { fun save(entity: T) = box.put(entity) + fun saveAll(entities: Collection) = box.put(entities) + fun delete(id: Long) = box.remove(id) fun delete(entity: T) = box.remove(entity) diff --git a/api/src/main/kotlin/nebulosa/api/repositories/TLERepository.kt b/api/src/main/kotlin/nebulosa/api/repositories/TLERepository.kt new file mode 100644 index 000000000..3b91deda0 --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/repositories/TLERepository.kt @@ -0,0 +1,123 @@ +package nebulosa.api.repositories + +import io.objectbox.BoxStore +import io.objectbox.query.QueryBuilder.StringOrder.CASE_INSENSITIVE +import jakarta.annotation.PostConstruct +import nebulosa.api.data.entities.TLEEntity +import nebulosa.api.data.entities.TLEEntity_ +import nebulosa.api.data.entities.TLESourceEntity +import nebulosa.log.loggerFor +import okhttp3.OkHttpClient +import okhttp3.Request +import org.springframework.stereotype.Service +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ExecutorService +import java.util.function.Supplier + +@Service +class TLERepository( + boxStore: BoxStore, + private val tleSourceRepository: TLESourceRepository, + private val systemExecutorService: ExecutorService, + private val okHttpClient: OkHttpClient, +) : BoxRepository() { + + override val box = boxStore.boxFor(TLEEntity::class.java)!! + + fun sources() = tleSourceRepository.all() + + fun withSources(source: TLESourceEntity): List = box.query() + .equal(TLEEntity_.source, source.id) + .build() + .find() + + fun withName(name: String): List = box.query() + .let { if (name.isBlank()) it else it.contains(TLEEntity_.name, name, CASE_INSENSITIVE) } + .order(TLEEntity_.name) + .build() + .find() + + fun deleteWithSource(source: TLESourceEntity) = box.query() + .equal(TLEEntity_.source, source.id) + .build() + .remove() + + @PostConstruct + fun updateIfOld() { + val currentTime = System.currentTimeMillis() + + for (source in sources()) { + if (source.enabled && currentTime - source.updatedAt >= TLE_UPDATE_INTERVAL) { + deleteWithSource(source) + + CompletableFuture + .supplyAsync(TLEUpdater(source), systemExecutorService) + .whenComplete { entities, e -> + e?.printStackTrace() + + if (!entities.isNullOrEmpty()) { + saveAll(entities) + + source.updatedAt = System.currentTimeMillis() + tleSourceRepository.save(source) + + LOG.info("updated {} satellites from {}", entities.size, source.url) + } + } + } else if (!source.enabled) { + deleteWithSource(source) + } + } + } + + private inner class TLEUpdater(private val source: TLESourceEntity) : Supplier> { + + override fun get(): List { + val request = Request.Builder() + .get() + .url(source.url) + .build() + + return okHttpClient.newCall(request) + .execute().use { + if (it.isSuccessful) { + source.parseTLEFile(it.body.byteStream()) + } else { + emptyList() + } + } + } + } + + companion object { + + const val TLE_UPDATE_INTERVAL = 1000L * 60 * 60 * 72 // 72 hours in ms + + @JvmStatic private val LOG = loggerFor() + + @JvmStatic + internal fun TLESourceEntity.parseTLEFile(data: ByteArray): List { + return parseTLEFile(ByteArrayInputStream(data)) + } + + @JvmStatic + internal fun TLESourceEntity.parseTLEFile(data: InputStream): List { + val entities = ArrayList(128) + val lines = ArrayList(3) + + for (line in data.bufferedReader().lines()) { + lines.add(line) + + if (lines.size == 3) { + val tle = TLEEntity.from(this, lines) + lines.clear() + entities.add(tle) + } + } + + return entities + } + } +} diff --git a/api/src/main/kotlin/nebulosa/api/repositories/TLESourceRepository.kt b/api/src/main/kotlin/nebulosa/api/repositories/TLESourceRepository.kt new file mode 100644 index 000000000..c53bcb6dc --- /dev/null +++ b/api/src/main/kotlin/nebulosa/api/repositories/TLESourceRepository.kt @@ -0,0 +1,81 @@ +package nebulosa.api.repositories + +import io.objectbox.BoxStore +import jakarta.annotation.PostConstruct +import nebulosa.api.data.entities.TLESourceEntity +import org.springframework.stereotype.Service + +@Service +class TLESourceRepository(boxStore: BoxStore) : BoxRepository() { + + override val box = boxStore.boxFor(TLESourceEntity::class.java)!! + + @PostConstruct + fun createIfEmpty() { + if (isEmpty()) { + for ((source, enabled) in DEFAULT_SOURCE_URLS) { + save(TLESourceEntity(url = source, enabled = enabled)) + } + } + } + + companion object { + + const val CELESTRAK_URL = "https://celestrak.org/NORAD/elements/gp.php" + + @JvmStatic private val DEFAULT_SOURCE_URLS = mapOf( + "$CELESTRAK_URL?GROUP=1982-092&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=1999-025&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=active&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=amateur&FORMAT=tle" to true, + "$CELESTRAK_URL?GROUP=analyst&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=argos&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=beidou&FORMAT=tle" to true, + "$CELESTRAK_URL?GROUP=cosmos-2251-debris&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=cubesat&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=dmc&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=education&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=engineering&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=galileo&FORMAT=tle" to true, + "$CELESTRAK_URL?GROUP=geo&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=geodetic&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=glo-ops&FORMAT=tle" to true, + "$CELESTRAK_URL?GROUP=globalstar&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=gnss&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=goes&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=gorizont&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=gps-ops&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=intelsat&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=iridium&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=iridium-33-debris&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=iridium-NEXT&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=last-30-days&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=military&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=molniya&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=musson&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=nnss&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=noaa&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=oneweb&FORMAT=tle" to true, + "$CELESTRAK_URL?GROUP=orbcomm&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=other&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=other-comm&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=planet&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=radar&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=raduga&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=resource&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=sarsat&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=satnogs&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=sbas&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=science&FORMAT=tle" to true, + "$CELESTRAK_URL?GROUP=ses&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=spire&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=starlink&FORMAT=tle" to true, + "$CELESTRAK_URL?GROUP=stations&FORMAT=tle" to true, + "$CELESTRAK_URL?GROUP=swarm&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=tdrss&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=visual&FORMAT=tle" to true, + "$CELESTRAK_URL?GROUP=weather&FORMAT=tle" to false, + "$CELESTRAK_URL?GROUP=x-comm&FORMAT=tle" to false, + ) + } +} diff --git a/api/src/main/kotlin/nebulosa/api/services/AtlasService.kt b/api/src/main/kotlin/nebulosa/api/services/AtlasService.kt index f48ec25f0..1d00c0b90 100644 --- a/api/src/main/kotlin/nebulosa/api/services/AtlasService.kt +++ b/api/src/main/kotlin/nebulosa/api/services/AtlasService.kt @@ -2,16 +2,11 @@ package nebulosa.api.services import jakarta.annotation.PostConstruct import jakarta.servlet.http.HttpServletResponse -import nebulosa.api.data.entities.AppPreferenceEntity -import nebulosa.api.data.entities.DeepSkyObjectEntity -import nebulosa.api.data.entities.LocationEntity -import nebulosa.api.data.entities.StarEntity +import nebulosa.api.data.entities.* import nebulosa.api.data.responses.BodyPositionResponse import nebulosa.api.data.responses.MinorPlanetResponse import nebulosa.api.data.responses.TwilightResponse -import nebulosa.api.repositories.AppPreferenceRepository -import nebulosa.api.repositories.DeepSkyObjectRepository -import nebulosa.api.repositories.StarRepository +import nebulosa.api.repositories.* import nebulosa.api.services.algorithms.TwilightDiscreteFunction import nebulosa.api.services.ephemeris.BodyEphemerisProvider import nebulosa.api.services.ephemeris.HorizonsEphemerisProvider @@ -57,6 +52,8 @@ class AtlasService( private val starRepository: StarRepository, private val deepSkyObjectRepository: DeepSkyObjectRepository, private val appPreferenceRepository: AppPreferenceRepository, + private val tleRepository: TLERepository, + private val tleSourceRepository: TLESourceRepository, private val okHttpClient: OkHttpClient, ) { @@ -111,6 +108,10 @@ class AtlasService( .copy(magnitude = dso.magnitude, constellation = dso.constellation, distance = dso.distance, distanceUnit = "ly") } + fun positionOfSatellite(location: LocationEntity, tle: String, dateTime: LocalDateTime): BodyPositionResponse { + return positionOfBody("TLE@$tle", location, dateTime)!! + } + private fun positionOfBody(target: Any, location: LocationEntity, dateTime: LocalDateTime): BodyPositionResponse? { return bodyEphemeris(target, location, dateTime) .withLocationAndDateTime(location, dateTime) @@ -125,6 +126,15 @@ class AtlasService( else horizonsEphemerisProvider.compute(target, position, dateTime, zoneId) } + fun searchSatellites(text: String): List { + return if (text.isBlank()) tleRepository.all() + else tleRepository.withName(text) + } + + fun satelliteSources(): List { + return tleRepository.sources() + } + fun twilight(location: LocationEntity, date: LocalDate): TwilightResponse { val civilDusk = doubleArrayOf(0.0, 0.0) val nauticalDusk = doubleArrayOf(0.0, 0.0) @@ -183,6 +193,26 @@ class AtlasService( return altitudePointsOfBody(ephemeris, stepSize) } + fun altitudePointsOfSatellite(location: LocationEntity, tle: String, date: LocalDate, stepSize: Int): List { + val ephemeris = bodyEphemeris("TLE@$tle", location, LocalDateTime.of(date, LocalTime.now())) + return altitudePointsOfBody(ephemeris, stepSize) + } + + fun enableSatelliteSource(source: TLESourceEntity, enabled: Boolean) { + source.enabled = enabled + + tleSourceRepository.save(source) + + if (!enabled) { + tleSourceRepository + .asSequence() + .onEach { it.updatedAt = 0 } + .onEach { tleSourceRepository.save(it) } + } + + tleRepository.updateIfOld() + } + private fun fixedStarOf(star: StarEntity): FixedStar { return stars.getOrPut(star.id) { FixedStar( diff --git a/api/src/main/kotlin/nebulosa/api/services/GuidingService.kt b/api/src/main/kotlin/nebulosa/api/services/GuidingService.kt index 961a746fc..393f1b426 100644 --- a/api/src/main/kotlin/nebulosa/api/services/GuidingService.kt +++ b/api/src/main/kotlin/nebulosa/api/services/GuidingService.kt @@ -337,7 +337,7 @@ class GuidingService( } override fun onLockPositionChanged(position: GuidePoint) { - // TODO + webSocketService.sendGuideLockPositionChanged(guider) } override fun onStarSelected(star: StarPoint) { @@ -365,11 +365,11 @@ class GuidingService( } override fun onStarLost() { - // TODO + webSocketService.sendGuideStarLost(guider) } override fun onLockPositionLost() { - // TODO + webSocketService.sendLockPositionLost(guider) } override fun onStartCalibration() { @@ -395,6 +395,7 @@ class GuidingService( // TODO } + // TODO: Move to nebulosa-io and improve it encoding on write (as same Base64InputStream). private class Base64OutputStream(size: Int) : ByteArrayOutputStream(size) { fun base64() = Base64.encode(buf, 0, count) diff --git a/api/src/main/kotlin/nebulosa/api/services/WebSocketService.kt b/api/src/main/kotlin/nebulosa/api/services/WebSocketService.kt index bfb2584c0..28889d622 100644 --- a/api/src/main/kotlin/nebulosa/api/services/WebSocketService.kt +++ b/api/src/main/kotlin/nebulosa/api/services/WebSocketService.kt @@ -4,6 +4,7 @@ import nebulosa.api.data.entities.SavedCameraImageEntity import nebulosa.api.data.events.CameraCaptureFinished import nebulosa.api.data.events.CameraCaptureProgressChanged import nebulosa.api.data.events.GuideExposureFinished +import nebulosa.guiding.Guider import nebulosa.indi.device.ConnectionEvent import nebulosa.indi.device.DeviceMessageReceived import nebulosa.indi.device.DevicePropertyEvent @@ -136,6 +137,20 @@ class WebSocketService(private val simpleMessageTemplate: SimpMessagingTemplate) sendMessage(GUIDE_EXPOSURE_FINISHED, event) } + // GUIDING + + fun sendGuideLockPositionChanged(guider: Guider) { + sendMessage(GUIDE_LOCK_POSITION_CHANGED, guider) + } + + fun sendGuideStarLost(guider: Guider) { + sendMessage(GUIDE_STAR_LOST, guider) + } + + fun sendLockPositionLost(guider: Guider) { + sendMessage(GUIDE_LOCK_POSITION_LOST, guider) + } + // DEVICE fun sendConnectionEvent(event: ConnectionEvent) { @@ -188,5 +203,8 @@ class WebSocketService(private val simpleMessageTemplate: SimpMessagingTemplate) const val GUIDE_OUTPUT_ATTACHED = "GUIDE_OUTPUT_ATTACHED" const val GUIDE_OUTPUT_DETACHED = "GUIDE_OUTPUT_DETACHED" const val GUIDE_EXPOSURE_FINISHED = "GUIDE_EXPOSURE_FINISHED" + const val GUIDE_LOCK_POSITION_CHANGED = "GUIDE_LOCK_POSITION_CHANGED" + const val GUIDE_STAR_LOST = "GUIDE_STAR_LOST" + const val GUIDE_LOCK_POSITION_LOST = "GUIDE_LOCK_POSITION_LOST" } } diff --git a/api/src/main/kotlin/nebulosa/api/services/ephemeris/HorizonsEphemerisProvider.kt b/api/src/main/kotlin/nebulosa/api/services/ephemeris/HorizonsEphemerisProvider.kt index 1d0a9cc43..3472d57b4 100644 --- a/api/src/main/kotlin/nebulosa/api/services/ephemeris/HorizonsEphemerisProvider.kt +++ b/api/src/main/kotlin/nebulosa/api/services/ephemeris/HorizonsEphemerisProvider.kt @@ -47,14 +47,25 @@ class HorizonsEphemerisProvider(private val horizonsService: HorizonsService) : ) } is String -> { - horizonsService - .observer( - target, - position.longitude, position.latitude, position.elevation, - startTime, endTime, - extraPrecision = true, - quantities = QUANTITIES, - ) + if (target.startsWith("TLE@")) { + horizonsService + .observerWithTLE( + target.substring(4), + position.longitude, position.latitude, position.elevation, + startTime, endTime, + extraPrecision = true, + quantities = QUANTITIES, + ) + } else { + horizonsService + .observer( + target, + position.longitude, position.latitude, position.elevation, + startTime, endTime, + extraPrecision = true, + quantities = QUANTITIES, + ) + } } else -> return emptyList() }.execute().body() ?: emptyList() diff --git a/build.gradle.kts b/build.gradle.kts index 8d8a090f6..2c9f0df3f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ buildscript { dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10") classpath("org.jetbrains.kotlin:kotlin-allopen:1.9.10") - classpath("io.objectbox:objectbox-gradle-plugin:3.6.0") + classpath("io.objectbox:objectbox-gradle-plugin:3.7.0") classpath("com.adarshr:gradle-test-logger-plugin:3.2.0") } diff --git a/desktop/.editorconfig b/desktop/.editorconfig index 1e12c42f4..0835e4d23 100644 --- a/desktop/.editorconfig +++ b/desktop/.editorconfig @@ -9,8 +9,8 @@ insert_final_newline = false tab_width = 4 trim_trailing_whitespace = true -[*.ts] +[*.ts, *.js] quote_type = single -[package*.json] -insert_final_newline = false +[*.md] +trim_trailing_whitespace = false diff --git a/desktop/.vscode/settings.json b/desktop/.vscode/settings.json index 0045659c3..da80f2e56 100644 --- a/desktop/.vscode/settings.json +++ b/desktop/.vscode/settings.json @@ -10,4 +10,6 @@ "html.format.wrapAttributes": "preserve-aligned", "html.format.wrapLineLength": 150, "explorer.excludeGitIgnore": true, + "typescript.tsdk": "node_modules/typescript/lib", + "json.format.keepLines": true, } \ No newline at end of file diff --git a/desktop/README.md b/desktop/README.md index 23c606ac2..2fdb1d1e9 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -31,8 +31,8 @@ The complete integrated solution for all of your astronomical imaging needs. ![](atlas.4.png) ![](atlas.5.png) ![](atlas.6.png) +![](atlas.7.png) ![](atlas.8.png) -![](atlas.9.png) ## Image diff --git a/desktop/atlas.1.png b/desktop/atlas.1.png index a5b5de751..fcd4d2cab 100644 Binary files a/desktop/atlas.1.png and b/desktop/atlas.1.png differ diff --git a/desktop/atlas.2.png b/desktop/atlas.2.png index 2319335b7..a847f89f4 100644 Binary files a/desktop/atlas.2.png and b/desktop/atlas.2.png differ diff --git a/desktop/atlas.3.png b/desktop/atlas.3.png index 8219ee489..a5fd172e0 100644 Binary files a/desktop/atlas.3.png and b/desktop/atlas.3.png differ diff --git a/desktop/atlas.4.png b/desktop/atlas.4.png index ff5fae218..5c242a240 100644 Binary files a/desktop/atlas.4.png and b/desktop/atlas.4.png differ diff --git a/desktop/atlas.5.png b/desktop/atlas.5.png index 514e48611..df104fe02 100644 Binary files a/desktop/atlas.5.png and b/desktop/atlas.5.png differ diff --git a/desktop/atlas.6.png b/desktop/atlas.6.png index b541e6c45..9bf0940af 100644 Binary files a/desktop/atlas.6.png and b/desktop/atlas.6.png differ diff --git a/desktop/atlas.7.png b/desktop/atlas.7.png new file mode 100644 index 000000000..325f9a682 Binary files /dev/null and b/desktop/atlas.7.png differ diff --git a/desktop/atlas.8.png b/desktop/atlas.8.png index 9a710cec4..7839fa143 100644 Binary files a/desktop/atlas.8.png and b/desktop/atlas.8.png differ diff --git a/desktop/atlas.9.png b/desktop/atlas.9.png deleted file mode 100644 index 000b77553..000000000 Binary files a/desktop/atlas.9.png and /dev/null differ diff --git a/desktop/package-lock.json b/desktop/package-lock.json index d8e3849d7..bd6ccb4b3 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -10,17 +10,17 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@angular/animations": "16.2.1", + "@angular/animations": "16.2.2", "@angular/cdk": "16.2.1", - "@angular/common": "16.2.1", - "@angular/compiler": "16.2.1", - "@angular/core": "16.2.1", - "@angular/forms": "16.2.1", - "@angular/language-service": "16.2.1", - "@angular/platform-browser": "16.2.1", - "@angular/platform-browser-dynamic": "16.2.1", - "@angular/router": "16.2.1", - "chart.js": "4.3.3", + "@angular/common": "16.2.2", + "@angular/compiler": "16.2.2", + "@angular/core": "16.2.2", + "@angular/forms": "16.2.2", + "@angular/language-service": "16.2.2", + "@angular/platform-browser": "16.2.2", + "@angular/platform-browser-dynamic": "16.2.2", + "@angular/router": "16.2.2", + "chart.js": "4.4.0", "cron": "2.4.1", "interactjs": "1.10.18", "leaflet": "1.9.4", @@ -35,15 +35,15 @@ "zone.js": "0.13.1" }, "devDependencies": { - "@angular-builders/custom-webpack": "16.0.0", + "@angular-builders/custom-webpack": "16.0.1", "@angular-devkit/build-angular": "16.2.0", "@angular/cli": "16.2.0", - "@angular/compiler-cli": "16.2.1", + "@angular/compiler-cli": "16.2.2", "@types/leaflet": "1.9.3", "@types/luxon": "3.3.1", - "@types/node": "20.5.1", + "@types/node": "20.5.6", "@types/uuid": "9.0.2", - "electron": "26.0.0", + "electron": "26.1.0", "electron-builder": "24.6.3", "electron-debug": "3.2.0", "electron-reloader": "1.2.3", @@ -71,9 +71,9 @@ } }, "node_modules/@angular-builders/custom-webpack": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-16.0.0.tgz", - "integrity": "sha512-CR2529DueVpYhS4hPm0TDm4sDeSzYKn+4IvFmujciy4uSO8W4YrBmRgSM0YKjHRGeIwwlHRy025lLjjT7XAPdQ==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-16.0.1.tgz", + "integrity": "sha512-C6INC8UOYDcp8LJwNhE0m66yp+nZX50JdgGI8oRn7fqw3gO58qhDgXrR/8BCrSeC8eOx8WxSuvBJ6u+9dozhyw==", "dev": true, "dependencies": { "@angular-devkit/architect": ">=0.1600.0 < 0.1700.0", @@ -298,9 +298,9 @@ } }, "node_modules/@angular/animations": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.1.tgz", - "integrity": "sha512-XVabK9fRKJaYPhW5wn8ySL4KL45N5Np+xOssWhLPDRDBdZjl62MExfpvMkamdkos6E1n1IGsy9wSemjnR4WKhg==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.2.tgz", + "integrity": "sha512-p0QefudkPGXjq9inZDrtW6WJrDcSeL+Nkc8lxubjg5fLQATKWKpsUBb+u2xEVu8OvWqj8BvrZUDnXYLyTdM4vw==", "dependencies": { "tslib": "^2.3.0" }, @@ -308,7 +308,7 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/core": "16.2.1" + "@angular/core": "16.2.2" } }, "node_modules/@angular/cdk": { @@ -362,9 +362,9 @@ } }, "node_modules/@angular/common": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.1.tgz", - "integrity": "sha512-druackA5JQpvfS8cD8DFtPRXGRKbhx3mQ778t1n6x3fXpIdGaAX+nSAgAKhIoF7fxWmu0KuHGzb+3BFlZRyTXw==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.2.tgz", + "integrity": "sha512-2ww8/heDHkfJEBwjakbQeleq610ljcvytNs6ZN1xiXib060xMP+xx17Oa9I3onhi369JsKCHkMR5Qs2U5af1uA==", "dependencies": { "tslib": "^2.3.0" }, @@ -372,14 +372,14 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/core": "16.2.1", + "@angular/core": "16.2.2", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.1.tgz", - "integrity": "sha512-dPauu+ESn79d66U9nBvnunNuBk/UMqnm7iL9Q31J8OKYN/4vrKbsO57pmULOft/GRAYsE3FdLBH0NkocFZKIMQ==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.2.tgz", + "integrity": "sha512-0X9i5NsqjX++0gmFy0fy2Uc5dHJMxDq6Yu/j1L3RdbvycL1GW+P8GgPfIvD/+v/YiDqpOHQswQXLbkcHw1+svA==", "dependencies": { "tslib": "^2.3.0" }, @@ -387,7 +387,7 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/core": "16.2.1" + "@angular/core": "16.2.2" }, "peerDependenciesMeta": { "@angular/core": { @@ -396,9 +396,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.1.tgz", - "integrity": "sha512-A5SyNZTZnXSCL5JVXHKbYj9p2dRYoeFnb6hGQFt2AuCcpUjVIIdwHtre3YzkKe5sFwepPctdoRe2fRXlTfTRjA==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.2.tgz", + "integrity": "sha512-+4i7o0yBc6xSljO8rdYL1G9AiZr2OW5dJAHfPuO21yNhp9BjIJ/TW+Sw1+o/WH4Gnim9adtnonL18UM+vuYeXg==", "dev": true, "dependencies": { "@babel/core": "7.22.5", @@ -419,7 +419,7 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/compiler": "16.2.1", + "@angular/compiler": "16.2.2", "typescript": ">=4.9.3 <5.2" } }, @@ -463,9 +463,9 @@ } }, "node_modules/@angular/core": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.1.tgz", - "integrity": "sha512-Y+0jssQnJPovxMv9cDKYlp6BBHeFBLOHd/+FPv5IIGD1c7NwBP/TImJxCaIV78a57xnO8L0SFacDg/kULzvKrg==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.2.tgz", + "integrity": "sha512-l6nJlppguroov7eByBIpbxn/mEPcQrL//Ru1TSPzTtXOLR1p41VqPMaeJXj7xYVx7im57YLTDPAjhtLzkUT/Ow==", "dependencies": { "tslib": "^2.3.0" }, @@ -478,9 +478,9 @@ } }, "node_modules/@angular/forms": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.1.tgz", - "integrity": "sha512-cCygiLfBAsVHdtKmNptlk2IgXu0wjRc8kSiiSnJkfK6U/NiNg8ADMiN7iYgKW2TD1ZRw+7dYZV856lxEy2n0+A==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.2.tgz", + "integrity": "sha512-Q3GmOCLSD5BXSjvlLkMsJLXWXb4SO0gA2Aya8JaG1y0doQT/CdGcYXrsCrCT3ot13wqp0HdGQ/ATNd0cNjmz2A==", "dependencies": { "tslib": "^2.3.0" }, @@ -488,24 +488,24 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/common": "16.2.1", - "@angular/core": "16.2.1", - "@angular/platform-browser": "16.2.1", + "@angular/common": "16.2.2", + "@angular/core": "16.2.2", + "@angular/platform-browser": "16.2.2", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/language-service": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-16.2.1.tgz", - "integrity": "sha512-B1eFYDUiXlx1xNf4rB1I+ACgD/aUE2M6HPET10FydFgPfzolX/xRdeUGYaAoEje4M9P9a93ovGeTPmg5TAUnLg==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-16.2.2.tgz", + "integrity": "sha512-n7TtG/FWkWUhRKO0QwgIcmrRgsYFuDZtPUdER7GJjQYEs6CvM+9fp73xJdbJAZIH/KF+8bAFdKCKsOGiiLSK+g==", "engines": { "node": "^16.14.0 || >=18.10.0" } }, "node_modules/@angular/platform-browser": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.1.tgz", - "integrity": "sha512-SH8zRiRAcw0B5/tVlEc5U/lN5F8g+JizSuu7BQvpCAQEDkM6IjF9LP36Bjav7JuadItbWLfT6peWYa1sJvax2w==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.2.tgz", + "integrity": "sha512-9RwUiHYCAmEirXqwWL/rPfXHMkU9PnpGinok6tmHF8agAmJs1kMWZedxG0GnreTzpTlBu/dI/4v6VDfR9S/D6Q==", "dependencies": { "tslib": "^2.3.0" }, @@ -513,9 +513,9 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/animations": "16.2.1", - "@angular/common": "16.2.1", - "@angular/core": "16.2.1" + "@angular/animations": "16.2.2", + "@angular/common": "16.2.2", + "@angular/core": "16.2.2" }, "peerDependenciesMeta": { "@angular/animations": { @@ -524,9 +524,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.2.1.tgz", - "integrity": "sha512-dKMCSrbD/joOMXM1mhDOKNDZ1BxwO9r9uu5ZxY0L/fWm/ousgMucNikLr38vBudgWM8CN6BuabzkxWKcqi3k4g==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.2.2.tgz", + "integrity": "sha512-EOGDZ+oABB/aNiBR//wxc6McycjF99/9ds74Q6WoHiNy8CYkzH3plr5pHoy4zkriSyqzoETg2tCu7jSiiMbjRg==", "dependencies": { "tslib": "^2.3.0" }, @@ -534,16 +534,16 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/common": "16.2.1", - "@angular/compiler": "16.2.1", - "@angular/core": "16.2.1", - "@angular/platform-browser": "16.2.1" + "@angular/common": "16.2.2", + "@angular/compiler": "16.2.2", + "@angular/core": "16.2.2", + "@angular/platform-browser": "16.2.2" } }, "node_modules/@angular/router": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.2.1.tgz", - "integrity": "sha512-C0WfcktsC25G37unxdH/5I7PbkVBSEB1o+0DJK9/HG97r1yzEkptF6fbRIzDBTS7dX0NfWN/PTAKF0ep7YlHvA==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.2.2.tgz", + "integrity": "sha512-r4KMVUVEWqjOZK0ZUsY8jRqscseGvgcigcikvYJwfxPqtCGYY7RoVAFY7HUtmXC0GAv1aIybK5o/MKTLaecD5Q==", "dependencies": { "tslib": "^2.3.0" }, @@ -551,9 +551,9 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/common": "16.2.1", - "@angular/core": "16.2.1", - "@angular/platform-browser": "16.2.1", + "@angular/common": "16.2.2", + "@angular/core": "16.2.2", + "@angular/platform-browser": "16.2.2", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -689,9 +689,9 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.10.tgz", - "integrity": "sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.11.tgz", + "integrity": "sha512-y1grdYL4WzmUDBRGK0pDbIoFd7UZKoDurDzWEoNMYoj1EL+foGRQNyPWDcC+YyegN5y1DUsFFmzjGijB3nSVAQ==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -1340,9 +1340,9 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.10.tgz", - "integrity": "sha512-eueE8lvKVzq5wIObKK/7dvoeKJ+xc6TvRn6aysIjS6pSCeLy7S/eVi7pEQknZqyqvzaNKdDtem8nUNTBgDVR2g==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.11.tgz", + "integrity": "sha512-0pAlmeRJn6wU84zzZsEOx1JV1Jf8fqO9ok7wofIJwUnplYo247dcd24P+cMJht7ts9xkzdtB0EPHmOb7F+KzXw==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.5", @@ -1421,12 +1421,12 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", - "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz", + "integrity": "sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.11", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, @@ -1523,9 +1523,9 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", - "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz", + "integrity": "sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1555,9 +1555,9 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", - "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz", + "integrity": "sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1603,9 +1603,9 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", - "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz", + "integrity": "sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1634,9 +1634,9 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", - "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz", + "integrity": "sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1681,12 +1681,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", - "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.11.tgz", + "integrity": "sha512-o2+bg7GDS60cJMgz9jWqRUsWkMzLCxp+jFDeDUT5sjRlAxcJWZ2ylNdI7QQ2+CH5hWu7OnN+Cv3htt7AkSf96g==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.9", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-simple-access": "^7.22.5" }, @@ -1698,13 +1698,13 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", - "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.11.tgz", + "integrity": "sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA==", "dev": true, "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.9", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.5" }, @@ -1763,9 +1763,9 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", - "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz", + "integrity": "sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1779,9 +1779,9 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", - "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz", + "integrity": "sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1795,13 +1795,13 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", - "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.11.tgz", + "integrity": "sha512-nX8cPFa6+UmbepISvlf5jhQyaC7ASs/7UxHmMkuJ/k5xSHvDPPaibMo+v3TXwU/Pjqhep/nFNpd3zn4YR59pnw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", + "@babel/compat-data": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.10", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-transform-parameters": "^7.22.5" @@ -1830,9 +1830,9 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", - "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz", + "integrity": "sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1846,9 +1846,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.10.tgz", - "integrity": "sha512-MMkQqZAZ+MGj+jGTG3OTuhKeBpNcO+0oCEbrGNEaOmiEn+1MzRyQlYsruGiU8RTK3zV6XwrVJTmwiDOyYK6J9g==", + "version": "7.22.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.12.tgz", + "integrity": "sha512-7XXCVqZtyFWqjDsYDY4T45w4mlx1rf7aOgkc/Ww76xkgBiOlmjPkx36PBLHa1k1rwWvVgYMPsbuVnIamx2ZQJw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1894,13 +1894,13 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", - "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz", + "integrity": "sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.11", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, @@ -3779,9 +3779,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.35", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", - "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "version": "4.17.36", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz", + "integrity": "sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==", "dev": true, "dependencies": { "@types/node": "*", @@ -3869,9 +3869,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.5.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz", - "integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==", + "version": "20.5.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.6.tgz", + "integrity": "sha512-Gi5wRGPbbyOTX+4Y2iULQ27oUPrefaB0PxGQJnfyWN3kvEDGM3mIB5M/gQLmitZf7A9FmLeaqxD3L1CXpm3VKQ==", "dev": true }, "node_modules/@types/plist": { @@ -5715,9 +5715,9 @@ "dev": true }, "node_modules/chart.js": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.3.tgz", - "integrity": "sha512-aTk7pBw+x6sQYhon/NR3ikfUJuym/LdgpTlgZRe2PaEhjUMKBKyNaFCMVRAyTEWYFNO7qRu7iQVqOw/OqzxZxQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz", + "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==", "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -6886,9 +6886,9 @@ "dev": true }, "node_modules/dns-packet": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.0.tgz", - "integrity": "sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "dev": true, "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" @@ -7028,9 +7028,9 @@ } }, "node_modules/electron": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-26.0.0.tgz", - "integrity": "sha512-x57bdCaDvgnlc41VOm/UWihJCCiI3OxJKiBgB/e5F7Zd6avo+61mO6IzQS7Bu/k/a1KPjou25EUORR6UPKznBQ==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-26.1.0.tgz", + "integrity": "sha512-qEh19H09Pysn3ibms5nZ0haIh5pFoOd7/5Ww7gzmAwDQOulRi8Sa2naeueOyIb1GKpf+6L4ix3iceYRAuA5r5Q==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -9274,9 +9274,9 @@ } }, "node_modules/immutable": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.2.tgz", - "integrity": "sha512-oGXzbEDem9OOpDWZu88jGiYCvIsLHMvGw+8OXlpsvTFvIQplQbjg1B1cvKg8f7Hoch6+NGjpPsH1Fr+Mc2D1aA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.3.tgz", + "integrity": "sha512-808ZFYMsIRAjLAu5xkKo0TsbY9LBy9H5MazTKIEHerNkg0ymgilGfBPMR/3G7d/ihGmuK2Hw8S1izY2d3kd3wA==", "dev": true }, "node_modules/import-fresh": { @@ -13515,9 +13515,9 @@ "optional": true }, "node_modules/rollup": { - "version": "3.28.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz", - "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==", + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", + "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", "dev": true, "bin": { "rollup": "dist/bin/rollup" diff --git a/desktop/package.json b/desktop/package.json index dea9c2402..c91384285 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -30,17 +30,17 @@ "lint": "ng lint" }, "dependencies": { - "@angular/animations": "16.2.1", + "@angular/animations": "16.2.2", "@angular/cdk": "16.2.1", - "@angular/common": "16.2.1", - "@angular/compiler": "16.2.1", - "@angular/core": "16.2.1", - "@angular/forms": "16.2.1", - "@angular/language-service": "16.2.1", - "@angular/platform-browser": "16.2.1", - "@angular/platform-browser-dynamic": "16.2.1", - "@angular/router": "16.2.1", - "chart.js": "4.3.3", + "@angular/common": "16.2.2", + "@angular/compiler": "16.2.2", + "@angular/core": "16.2.2", + "@angular/forms": "16.2.2", + "@angular/language-service": "16.2.2", + "@angular/platform-browser": "16.2.2", + "@angular/platform-browser-dynamic": "16.2.2", + "@angular/router": "16.2.2", + "chart.js": "4.4.0", "cron": "2.4.1", "interactjs": "1.10.18", "leaflet": "1.9.4", @@ -55,15 +55,15 @@ "zone.js": "0.13.1" }, "devDependencies": { - "@angular-builders/custom-webpack": "16.0.0", + "@angular-builders/custom-webpack": "16.0.1", "@angular-devkit/build-angular": "16.2.0", "@angular/cli": "16.2.0", - "@angular/compiler-cli": "16.2.1", + "@angular/compiler-cli": "16.2.2", "@types/leaflet": "1.9.3", "@types/luxon": "3.3.1", - "@types/node": "20.5.1", + "@types/node": "20.5.6", "@types/uuid": "9.0.2", - "electron": "26.0.0", + "electron": "26.1.0", "electron-builder": "24.6.3", "electron-debug": "3.2.0", "electron-reloader": "1.2.3", diff --git a/desktop/src/app/atlas/atlas.component.html b/desktop/src/app/atlas/atlas.component.html index 10c9a967f..c5bcac197 100644 --- a/desktop/src/app/atlas/atlas.component.html +++ b/desktop/src/app/atlas/atlas.component.html @@ -2,16 +2,25 @@
+ + +
- +
+ + +
- +
+ + +
+ + +
@@ -66,6 +78,9 @@
+ + +
@@ -80,8 +95,7 @@
+ selectionMode="single" dataKey="id" styleClass="p-datatable-striped"> Name @@ -102,6 +116,9 @@
+ + +
@@ -116,8 +133,7 @@
+ selectionMode="single" dataKey="id" styleClass="p-datatable-striped"> Name @@ -138,8 +154,43 @@
+ + + +
+
+ + + + +
+
+ +
+
+
+ + + + NORAD ID + Name + + + + + {{ item.id }} + {{ item.name }} + + + +
+ + +
@@ -166,72 +217,72 @@
- +
- +
- +
- +
- +
- +
- +
-
- +
- +
- +
- +
@@ -246,23 +297,21 @@
- + -
-
- - -
- +
- - -
diff --git a/desktop/src/app/atlas/atlas.component.scss b/desktop/src/app/atlas/atlas.component.scss index e207f7caa..6a376cc1b 100644 --- a/desktop/src/app/atlas/atlas.component.scss +++ b/desktop/src/app/atlas/atlas.component.scss @@ -5,24 +5,28 @@ } p-table ::ng-deep .p-datatable-wrapper { - max-height: 220px; + max-height: 217px; min-width: 100vw; } p-table.planet ::ng-deep .p-datatable-wrapper { - max-height: 194px; + max-height: 191px; } p-table.minorPlanet ::ng-deep .p-datatable-wrapper { - max-height: 143px; + max-height: 140px; } p-table.star ::ng-deep .p-datatable-wrapper { - max-height: 143px; + max-height: 140px; } p-table.dso ::ng-deep .p-datatable-wrapper { - max-height: 143px; + max-height: 140px; +} + +p-table.satellite ::ng-deep .p-datatable-wrapper { + max-height: 140px; } .p-input-icon-left span.pi:first-of-type { diff --git a/desktop/src/app/atlas/atlas.component.ts b/desktop/src/app/atlas/atlas.component.ts index 19eedce4a..65d9b54d6 100644 --- a/desktop/src/app/atlas/atlas.component.ts +++ b/desktop/src/app/atlas/atlas.component.ts @@ -11,7 +11,7 @@ import { ApiService } from '../../shared/services/api.service' import { BrowserWindowService } from '../../shared/services/browser-window.service' import { ElectronService } from '../../shared/services/electron.service' import { PreferenceService } from '../../shared/services/preference.service' -import { CONSTELLATIONS, Constellation, DeepSkyObject, EMPTY_BODY_POSITION, EMPTY_LOCATION, Location, MinorPlanet, SkyObjectType, Star, Union } from '../../shared/types' +import { CONSTELLATIONS, Constellation, DeepSkyObject, EMPTY_BODY_POSITION, EMPTY_LOCATION, Location, MinorPlanet, Satellite, SkyObjectType, Star, Union } from '../../shared/types' export interface PlanetItem { name: string @@ -42,12 +42,12 @@ export class AtlasComponent implements OnInit, AfterContentInit, OnDestroy { private settingsTabActivated = false get tab() { - return this.settingsTabActivated ? 6 : this.activeTab + return this.settingsTabActivated ? 7 : this.activeTab } set tab(value: number) { this.settingsTabActivated = false - if (value === 6) this.settingsTabActivated = true + if (value === 7) this.settingsTabActivated = true else this.activeTab = value } @@ -155,6 +155,10 @@ export class AtlasComponent implements OnInit, AfterContentInit, OnDestroy { dsoSearchText = '' showDSOFilterDialog = false + satellite?: Satellite + satelliteItems: Satellite[] = [] + satelliteSearchText = '' + readonly dsoFilter: SearchFilter = { text: '', rightAscension: '00h00m00s', @@ -408,7 +412,7 @@ export class AtlasComponent implements OnInit, AfterContentInit, OnDestroy { }, null, false) constructor( - title: Title, + private title: Title, private api: ApiService, private browserWindow: BrowserWindowService, private electron: ElectronService, @@ -483,6 +487,10 @@ export class AtlasComponent implements OnInit, AfterContentInit, OnDestroy { this.refreshTab(false, true) } + satelliteChanged() { + this.refreshTab(false, true) + } + async searchStar() { const constellation = this.starFilter.constellation === 'ALL' ? undefined : this.starFilter.constellation const type = this.starFilter.type === 'ALL' ? undefined : this.starFilter.type @@ -520,11 +528,21 @@ export class AtlasComponent implements OnInit, AfterContentInit, OnDestroy { } } - filterDSO() { - this.searchDSO() + async filterDSO() { + await this.searchDSO() this.showDSOFilterDialog = false } + async searchSatellite() { + this.refreshing = true + + try { + this.satelliteItems = await this.api.searchSatellites(this.satelliteSearchText) + } finally { + this.refreshing = false + } + } + addLocation() { const location = Object.assign({}, EMPTY_LOCATION) const dialog = LocationDialog.show(this.dialog, location) @@ -608,6 +626,8 @@ export class AtlasComponent implements OnInit, AfterContentInit, OnDestroy { this.dateTime.setMinutes(this.dateTimeMinute) } + this.title.setTitle(`Sky Atlas ・ ${this.location.name}`) + try { // Sun. if (this.activeTab === 0) { @@ -683,6 +703,19 @@ export class AtlasComponent implements OnInit, AfterContentInit, OnDestroy { Object.assign(this.bodyPosition, EMPTY_BODY_POSITION) } } + // TLE. + else if (this.activeTab === 6) { + this.tags = [] + + if (this.satellite) { + this.name = this.satellite.name + const bodyPosition = await this.api.positionOfSatellite(this.location!, this.satellite, this.dateTime) + Object.assign(this.bodyPosition, bodyPosition) + } else { + this.name = '-' + Object.assign(this.bodyPosition, EMPTY_BODY_POSITION) + } + } if (refreshTwilight) { const twilight = await this.api.twilight(this.location!, this.dateTime) @@ -743,6 +776,12 @@ export class AtlasComponent implements OnInit, AfterContentInit, OnDestroy { const points = await this.api.altitudePointsOfDSO(this.location!, this.dso, this.dateTime) AtlasComponent.belowZeroPoints(points) this.altitudeData.datasets[9].data = points + } + // Satellite. + else if (this.activeTab === 6 && this.satellite) { + const points = await this.api.altitudePointsOfSatellite(this.location!, this.satellite, this.dateTime) + AtlasComponent.belowZeroPoints(points) + this.altitudeData.datasets[9].data = points } else { return } diff --git a/desktop/src/app/camera/camera.component.ts b/desktop/src/app/camera/camera.component.ts index 581f424da..f2567eaa8 100644 --- a/desktop/src/app/camera/camera.component.ts +++ b/desktop/src/app/camera/camera.component.ts @@ -174,7 +174,7 @@ export class CameraComponent implements AfterContentInit, OnDestroy { ) { title.setTitle('Camera') - electron.ipcRenderer.on('CAMERA_UPDATED', (_, camera: Camera) => { + electron.on('CAMERA_UPDATED', (_, camera: Camera) => { if (camera.name === this.camera?.name) { ngZone.run(() => { Object.assign(this.camera!, camera) @@ -183,7 +183,7 @@ export class CameraComponent implements AfterContentInit, OnDestroy { } }) - electron.ipcRenderer.on('CAMERA_CAPTURE_PROGRESS_CHANGED', (_, event: CameraCaptureProgressChanged) => { + electron.on('CAMERA_CAPTURE_PROGRESS_CHANGED', (_, event: CameraCaptureProgressChanged) => { if (event.camera === this.camera?.name) { ngZone.run(() => { this.capturing = true @@ -192,7 +192,7 @@ export class CameraComponent implements AfterContentInit, OnDestroy { } }) - electron.ipcRenderer.on('CAMERA_CAPTURE_FINISHED', (_, event: CameraCaptureFinished) => { + electron.on('CAMERA_CAPTURE_FINISHED', (_, event: CameraCaptureFinished) => { if (event.camera === this.camera?.name) { ngZone.run(() => { this.capturing = false @@ -200,7 +200,7 @@ export class CameraComponent implements AfterContentInit, OnDestroy { } }) - electron.ipcRenderer.on('FILTER_WHEEL_CHANGED', (_, filterWheel?: FilterWheel) => { + electron.on('FILTER_WHEEL_CHANGED', (_, filterWheel?: FilterWheel) => { ngZone.run(() => { this.filterWheel = filterWheel }) diff --git a/desktop/src/app/filterwheel/filterwheel.component.ts b/desktop/src/app/filterwheel/filterwheel.component.ts index 451fbd9b7..b9b1742bb 100644 --- a/desktop/src/app/filterwheel/filterwheel.component.ts +++ b/desktop/src/app/filterwheel/filterwheel.component.ts @@ -47,7 +47,7 @@ export class FilterWheelComponent implements AfterContentInit, OnDestroy { ) { title.setTitle('Filter Wheel') - electron.ipcRenderer.on('FILTER_WHEEL_UPDATED', (_, filterWheel: FilterWheel) => { + electron.on('FILTER_WHEEL_UPDATED', (_, filterWheel: FilterWheel) => { if (filterWheel.name === this.filterWheel?.name) { ngZone.run(() => { Object.assign(this.filterWheel!, filterWheel) diff --git a/desktop/src/app/focuser/focuser.component.ts b/desktop/src/app/focuser/focuser.component.ts index 3d56ba643..65cbd64b8 100644 --- a/desktop/src/app/focuser/focuser.component.ts +++ b/desktop/src/app/focuser/focuser.component.ts @@ -43,7 +43,7 @@ export class FocuserComponent implements AfterViewInit, OnDestroy { ) { title.setTitle('Focuser') - electron.ipcRenderer.on('FOCUSER_UPDATED', (_, focuser: Focuser) => { + electron.on('FOCUSER_UPDATED', (_, focuser: Focuser) => { if (focuser.name === this.focuser?.name) { ngZone.run(() => { Object.assign(this.focuser!, focuser) @@ -52,7 +52,7 @@ export class FocuserComponent implements AfterViewInit, OnDestroy { } }) - electron.ipcRenderer.on('CAMERA_CHANGED', (_, camera?: Camera) => { + electron.on('CAMERA_CHANGED', (_, camera?: Camera) => { ngZone.run(() => { this.camera = camera }) diff --git a/desktop/src/app/framing/framing.component.ts b/desktop/src/app/framing/framing.component.ts index 96810ffba..fc7821cbe 100644 --- a/desktop/src/app/framing/framing.component.ts +++ b/desktop/src/app/framing/framing.component.ts @@ -53,7 +53,7 @@ export class FramingComponent implements AfterViewInit, OnDestroy { this.loadPreference() - electron.ipcRenderer.on('PARAMS_CHANGED', (_, data: FramingParams) => { + electron.on('PARAMS_CHANGED', (_, data: FramingParams) => { ngZone.run(() => this.frameFromParams(data)) }) } diff --git a/desktop/src/app/guider/guider.component.ts b/desktop/src/app/guider/guider.component.ts index a993dc70d..3bb60d771 100644 --- a/desktop/src/app/guider/guider.component.ts +++ b/desktop/src/app/guider/guider.component.ts @@ -4,7 +4,7 @@ import { ApiService } from '../../shared/services/api.service' import { BrowserWindowService } from '../../shared/services/browser-window.service' import { ElectronService } from '../../shared/services/electron.service' import { PreferenceService } from '../../shared/services/preference.service' -import { Camera, GuideOutput, Mount } from '../../shared/types' +import { Camera, GuideOutput, GuideTrackingBox, Guider, ImageStarSelected, Mount } from '../../shared/types' @Component({ selector: 'app-guider', @@ -25,8 +25,10 @@ export class GuiderComponent implements AfterViewInit, OnDestroy { guideOutput?: GuideOutput guideOutputConnected = false + private guider?: Guider looping = false guiding = false + calibrating = false constructor( title: Title, @@ -65,18 +67,34 @@ export class GuiderComponent implements AfterViewInit, OnDestroy { } }) - electron.ipcRenderer.on('GUIDE_OUTPUT_ATTACHED', (_, guideOutput: GuideOutput) => { + electron.on('GUIDE_OUTPUT_ATTACHED', (_, guideOutput: GuideOutput) => { ngZone.run(() => { this.guideOutputs.push(guideOutput) }) }) - electron.ipcRenderer.on('GUIDE_OUTPUT_DETACHED', (_, guideOutput: GuideOutput) => { + electron.on('GUIDE_OUTPUT_DETACHED', (_, guideOutput: GuideOutput) => { ngZone.run(() => { const index = this.guideOutputs.findIndex(e => e.name === guideOutput.name) if (index) this.guideOutputs.splice(index, 1) }) }) + + electron.on('IMAGE_STAR_SELECTED', async (_, star: ImageStarSelected) => { + if (!this.guiding && star.camera.name === this.camera?.name) { + await this.api.selectGuideStar(star.x, star.y) + } + }) + + electron.on('GUIDE_LOCK_POSITION_CHANGED', (_, guider: Guider) => { + this.guider = guider + + ngZone.run(() => { + this.updateGuideState() + }) + + this.drawTrackingBox() + }) } async ngAfterViewInit() { @@ -168,4 +186,17 @@ export class GuiderComponent implements AfterViewInit, OnDestroy { this.guideOutputConnected = this.guideOutput.connected } } + + private updateGuideState() { + if (this.guider) { + this.looping = this.guider.looping + this.calibrating = this.guider.calibrating + this.guiding = this.guider.guiding + } + } + + private drawTrackingBox() { + const trackingBox = { camera: this.camera!, guider: this.guider! } + this.electron.send('DRAW_GUIDE_TRACKING_BOX', trackingBox) + } } \ No newline at end of file diff --git a/desktop/src/app/home/home.component.ts b/desktop/src/app/home/home.component.ts index db15271a7..bff03d3e6 100644 --- a/desktop/src/app/home/home.component.ts +++ b/desktop/src/app/home/home.component.ts @@ -75,7 +75,7 @@ export class HomeComponent implements AfterContentInit, OnDestroy { onAdd: (device: T) => number, onRemove: (device: T) => number, ) { - this.electron.ipcRenderer.on(`${type}_ATTACHED`, (_, device: T) => { + this.electron.on(`${type}_ATTACHED`, (_, device: T) => { this.ngZone.run(() => { if (onAdd(device) === 1) { this.electron.send(`${type}_CHANGED`, device) @@ -83,7 +83,7 @@ export class HomeComponent implements AfterContentInit, OnDestroy { }) }) - this.electron.ipcRenderer.on(`${type}_DETACHED`, (_, device: T) => { + this.electron.on(`${type}_DETACHED`, (_, device: T) => { this.ngZone.run(() => { if (onRemove(device) === 0) { this.electron.send(`${type}_CHANGED`, undefined) diff --git a/desktop/src/app/image/image.component.html b/desktop/src/app/image/image.component.html index 435cda4ed..5a56ef56d 100644 --- a/desktop/src/app/image/image.component.html +++ b/desktop/src/app/image/image.component.html @@ -16,6 +16,15 @@ + + + + + +
X: {{ roiX }} Y: {{ roiY }} W: {{ roiWidth }} H: {{ roiHeight }} diff --git a/desktop/src/app/image/image.component.ts b/desktop/src/app/image/image.component.ts index 3db6edef6..6f83ab2f7 100644 --- a/desktop/src/app/image/image.component.ts +++ b/desktop/src/app/image/image.component.ts @@ -12,7 +12,8 @@ import { BrowserWindowService } from '../../shared/services/browser-window.servi import { ElectronService } from '../../shared/services/electron.service' import { PreferenceService } from '../../shared/services/preference.service' import { - Calibration, Camera, DeepSkyObject, EquatorialCoordinate, FITSHeaderItem, GuideExposureFinished, ImageAnnotation, ImageChannel, ImageInfo, ImageSource, + Calibration, Camera, DeepSkyObject, EquatorialCoordinate, FITSHeaderItem, GuideExposureFinished, GuideTrackingBox, + ImageAnnotation, ImageChannel, ImageInfo, ImageSource, ImageStarSelected, PlateSolverType, SCNRProtectionMethod, SCNR_PROTECTION_METHODS, SavedCameraImage, Star } from '../../shared/types' @@ -101,6 +102,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { roiInteractable?: Interactable guiding = false + guideTrackingBox?: GuideTrackingBox private readonly scnrMenuItem: MenuItem = { label: 'SCNR', @@ -284,7 +286,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { ) { title.setTitle('Image') - electron.ipcRenderer.on('CAMERA_IMAGE_SAVED', async (_, data: SavedCameraImage) => { + electron.on('CAMERA_IMAGE_SAVED', async (_, data: SavedCameraImage) => { if (data.camera === this.imageParams.camera?.name) { await this.closeImage() @@ -297,7 +299,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { } }) - electron.ipcRenderer.on('GUIDE_EXPOSURE_FINISHED', async (_, data: GuideExposureFinished) => { + electron.on('GUIDE_EXPOSURE_FINISHED', async (_, data: GuideExposureFinished) => { if (data.camera === this.imageParams.camera?.name) { await this.closeImage() @@ -310,12 +312,20 @@ export class ImageComponent implements AfterViewInit, OnDestroy { } }) - electron.ipcRenderer.on('PARAMS_CHANGED', async (_, data: ImageParams) => { + electron.on('PARAMS_CHANGED', async (_, data: ImageParams) => { await this.closeImage() this.loadImageFromParams(data) }) + electron.on('DRAW_GUIDE_TRACKING_BOX', (_, data: GuideTrackingBox) => { + if (data.camera.name === this.imageParams.camera?.name) { + ngZone.run(() => { + this.guideTrackingBox = data + }) + } + }) + this.solverPathOrUrl = this.preference.get('image.solver.pathOrUrl', '') this.solverRadius = this.preference.get('image.solver.radius', 4) this.solverDownsampleFactor = this.preference.get('image.solver.downsampleFactor', 1) diff --git a/desktop/src/app/indi/indi.component.ts b/desktop/src/app/indi/indi.component.ts index 2fa82ee73..1ba19d653 100644 --- a/desktop/src/app/indi/indi.component.ts +++ b/desktop/src/app/indi/indi.component.ts @@ -37,14 +37,14 @@ export class INDIComponent implements AfterViewInit, OnDestroy { this.api.indiStartListening() - electron.ipcRenderer.on('DEVICE_PROPERTY_CHANGED', (_, data: INDIProperty) => { + electron.on('DEVICE_PROPERTY_CHANGED', (_, data: INDIProperty) => { ngZone.run(() => { this.addOrUpdateProperty(data) this.updateGroups() }) }) - electron.ipcRenderer.on('DEVICE_PROPERTY_DELETED', (_, data: INDIProperty) => { + electron.on('DEVICE_PROPERTY_DELETED', (_, data: INDIProperty) => { const index = this.properties.findIndex((e) => e.name === data.name) if (index >= 0) { @@ -55,7 +55,7 @@ export class INDIComponent implements AfterViewInit, OnDestroy { } }) - electron.ipcRenderer.on('DEVICE_MESSAGE_RECEIVED', (_, data: INDIDeviceMessage) => { + electron.on('DEVICE_MESSAGE_RECEIVED', (_, data: INDIDeviceMessage) => { if (this.device && data.device === this.device.name) { ngZone.run(() => { this.messages.splice(0, 0, data.message) diff --git a/desktop/src/app/mount/mount.component.ts b/desktop/src/app/mount/mount.component.ts index 46ba66682..75a906e0a 100644 --- a/desktop/src/app/mount/mount.component.ts +++ b/desktop/src/app/mount/mount.component.ts @@ -148,7 +148,7 @@ export class MountComponent implements AfterContentInit, OnDestroy { ) { title.setTitle('Mount') - electron.ipcRenderer.on('MOUNT_UPDATED', (_, mount: Mount) => { + electron.on('MOUNT_UPDATED', (_, mount: Mount) => { if (mount.name === this.mount?.name) { ngZone.run(() => { Object.assign(this.mount!, mount) diff --git a/desktop/src/assets/icons/CREDITS.md b/desktop/src/assets/icons/CREDITS.md index 3a4b986f1..b8926b9ea 100644 --- a/desktop/src/assets/icons/CREDITS.md +++ b/desktop/src/assets/icons/CREDITS.md @@ -26,3 +26,11 @@ * https://www.flaticon.com/free-icon/setting_839374 * https://www.flaticon.com/free-icon/filter_679991 * https://www.flaticon.com/free-icon/target_10542035 +* https://www.flaticon.com/free-icon/contrast_439842 +* https://www.flaticon.com/free-icon/full-moon_9689786 +* https://www.flaticon.com/free-icon/jupiter_1086078 +* https://www.flaticon.com/free-icon/asteroid_1086068 +* https://www.flaticon.com/free-icon/stars_3266390 +* https://www.flaticon.com/free-icon/milky-way_1086076 +* https://www.flaticon.com/free-icon/satellite_1086093 +* https://www.flaticon.com/free-icon/gear_8753403 diff --git a/desktop/src/assets/icons/asteroid.png b/desktop/src/assets/icons/asteroid.png new file mode 100644 index 000000000..3c4b7f4d8 Binary files /dev/null and b/desktop/src/assets/icons/asteroid.png differ diff --git a/desktop/src/assets/icons/gear.png b/desktop/src/assets/icons/gear.png new file mode 100644 index 000000000..a57fd0a7a Binary files /dev/null and b/desktop/src/assets/icons/gear.png differ diff --git a/desktop/src/assets/icons/jupiter.png b/desktop/src/assets/icons/jupiter.png new file mode 100644 index 000000000..6d2365460 Binary files /dev/null and b/desktop/src/assets/icons/jupiter.png differ diff --git a/desktop/src/assets/icons/milky-way.png b/desktop/src/assets/icons/milky-way.png new file mode 100644 index 000000000..073c3817b Binary files /dev/null and b/desktop/src/assets/icons/milky-way.png differ diff --git a/desktop/src/assets/icons/moon.png b/desktop/src/assets/icons/moon.png new file mode 100644 index 000000000..f64202ee2 Binary files /dev/null and b/desktop/src/assets/icons/moon.png differ diff --git a/desktop/src/assets/icons/satellite.png b/desktop/src/assets/icons/satellite.png new file mode 100644 index 000000000..3a5a1291b Binary files /dev/null and b/desktop/src/assets/icons/satellite.png differ diff --git a/desktop/src/assets/icons/stars.png b/desktop/src/assets/icons/stars.png new file mode 100644 index 000000000..916705d97 Binary files /dev/null and b/desktop/src/assets/icons/stars.png differ diff --git a/desktop/src/assets/icons/sun.png b/desktop/src/assets/icons/sun.png new file mode 100644 index 000000000..545ac7b8e Binary files /dev/null and b/desktop/src/assets/icons/sun.png differ diff --git a/desktop/src/shared/dialogs/location/location.dialog.html b/desktop/src/shared/dialogs/location/location.dialog.html index 101633962..bf1c3a52d 100644 --- a/desktop/src/shared/dialogs/location/location.dialog.html +++ b/desktop/src/shared/dialogs/location/location.dialog.html @@ -1,38 +1,38 @@
-
+
-
+
+ + + + +
+
-
+
-
+
-
- - - - -
diff --git a/desktop/src/shared/services/api.service.ts b/desktop/src/shared/services/api.service.ts index e0e26abbb..69180ae6b 100644 --- a/desktop/src/shared/services/api.service.ts +++ b/desktop/src/shared/services/api.service.ts @@ -6,7 +6,7 @@ import { BodyPosition, Calibration, Camera, CameraStartCapture, ComputedCoordinates, Constellation, DeepSkyObject, Device, FilterWheel, Focuser, GuideOutput, GuidingChart, GuidingStar, HipsSurvey, INDIProperty, INDISendProperty, ImageAnnotation, ImageChannel, ImageInfo, Location, MinorPlanet, - Mount, Path, PlateSolverType, SCNRProtectionMethod, SavedCameraImage, SkyObjectType, SlewRate, Star, TrackMode, Twilight + Mount, Path, PlateSolverType, SCNRProtectionMethod, Satellite, SatelliteSource, SavedCameraImage, SkyObjectType, SlewRate, Star, TrackMode, Twilight } from '../types' @Injectable({ providedIn: 'root' }) @@ -386,6 +386,11 @@ export class ApiService { return this.get(`positionOfDSO?location=${location.id}&dso=${dso.id}&date=${date}&time=${time}`) } + positionOfSatellite(location: Location, satellite: Satellite, dateTime: Date) { + const [date, time] = moment(dateTime).format('YYYY-MM-DD HH:mm').split(' ') + return this.get(`positionOfSatellite?location=${location.id}&tle=${encodeURIComponent(satellite.tle)}&date=${date}&time=${time}`) + } + twilight(location: Location, dateTime: Date) { const date = moment(dateTime).format('YYYY-MM-DD') return this.get(`twilight?location=${location.id}&date=${date}`) @@ -416,6 +421,11 @@ export class ApiService { return this.get<[number, number][]>(`altitudePointsOfDSO?location=${location.id}&dso=${dso.id}&date=${date}&stepSize=5`) } + altitudePointsOfSatellite(location: Location, satellite: Satellite, dateTime: Date) { + const date = moment(dateTime).format('YYYY-MM-DD') + return this.get<[number, number][]>(`altitudePointsOfSatellite?location=${location.id}&tle=${encodeURIComponent(satellite.tle)}&date=${date}&stepSize=1`) + } + searchMinorPlanet(text: string) { return this.get(`searchMinorPlanet?text=${text}`) } @@ -479,4 +489,12 @@ export class ApiService { pointMountHere(mount: Mount, path: string, x: number, y: number, synchronized: boolean = true) { return this.post(`pointMountHere?name=${mount.name}&path=${path}&x=${x}&y=${y}&synchronized=${synchronized}`) } + + searchSatellites(text: string = '') { + return this.get(`searchSatellites?text=${text}`) + } + + satelliteSources() { + return this.get(`satelliteSources`) + } } diff --git a/desktop/src/shared/services/browser-window.service.ts b/desktop/src/shared/services/browser-window.service.ts index 364e4e6d1..35c037b3b 100644 --- a/desktop/src/shared/services/browser-window.service.ts +++ b/desktop/src/shared/services/browser-window.service.ts @@ -85,7 +85,7 @@ export class BrowserWindowService { this.openWindow({ ...options, id: 'atlas', path: 'atlas', icon: options.icon || 'atlas', - width: options.width || 512, height: options.height || 510, + width: options.width || 450, height: options.height || 510, }) } diff --git a/desktop/src/shared/services/electron.service.ts b/desktop/src/shared/services/electron.service.ts index 5731fcbda..5a9bfa28b 100644 --- a/desktop/src/shared/services/electron.service.ts +++ b/desktop/src/shared/services/electron.service.ts @@ -6,7 +6,7 @@ import { Injectable } from '@angular/core' import * as childProcess from 'child_process' import { ipcRenderer, webFrame } from 'electron' import * as fs from 'fs' -import { INDIEventType, InternalEventType, Mount } from '../types' +import { INDIEventType, InternalEventType, MainEventType, Mount } from '../types' @Injectable({ providedIn: 'root' }) export class ElectronService { @@ -44,17 +44,17 @@ export class ElectronService { return !!(window && window.process && window.process.type) } - send(channel: string, ...data: any[]) { + send(channel: INDIEventType | InternalEventType | MainEventType, ...data: any[]) { this.ipcRenderer.send(channel, ...data) } - sendSync(channel: string, ...data: any[]) { + sendSync(channel: INDIEventType | InternalEventType | MainEventType, ...data: any[]) { return this.ipcRenderer.sendSync(channel, ...data) } - on(event: INDIEventType | InternalEventType, + on(channel: INDIEventType | InternalEventType | 'PARAMS_CHANGED', listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void) { - return this.ipcRenderer.on(event, listener) + return this.ipcRenderer.on(channel, listener) } selectedMount(): Mount | undefined { diff --git a/desktop/src/shared/types.ts b/desktop/src/shared/types.ts index ad5ae2735..b8163f20f 100644 --- a/desktop/src/shared/types.ts +++ b/desktop/src/shared/types.ts @@ -1,3 +1,5 @@ +import { Point } from 'electron' + export interface Device { readonly name: string connected: boolean @@ -162,7 +164,6 @@ export interface CameraCaptureProgressChanged { indeterminate: boolean } - export interface CameraCaptureFinished { camera: string } @@ -453,6 +454,38 @@ export interface GuidingStar { snr: number } +export interface GuideStar extends Point { + valid: boolean +} + +export interface Guider { + lockPosition: GuideStar + primaryStar: GuideStar + searchRegion: number + looping: boolean + calibrating: boolean + guiding: boolean +} + +export interface GuideTrackingBox { + camera: Camera + guider: Guider +} + +export interface Satellite { + id: number + name: string + tle: string +} + +export interface SatelliteSource { + id: number + url: string + updatedAt: number + enabled: boolean + deletable: boolean +} + export enum ExposureTimeUnit { MINUTE = 'm', SECOND = 's', @@ -640,17 +673,24 @@ export const INDI_EVENT_TYPES = [ 'FOCUSER_UPDATED', 'FOCUSER_ATTACHED', 'FOCUSER_DETACHED', 'FILTER_WHEEL_UPDATED', 'FILTER_WHEEL_ATTACHED', 'FILTER_WHEEL_DETACHED', 'GUIDE_OUTPUT_ATTACHED', 'GUIDE_OUTPUT_DETACHED', 'GUIDE_OUTPUT_UPDATED', - 'GUIDE_EXPOSURE_FINISHED', + 'GUIDE_EXPOSURE_FINISHED', 'GUIDE_LOCK_POSITION_CHANGED', + 'GUIDE_STAR_LOST', 'GUIDE_LOCK_POSITION_LOST', ] as const export type INDIEventType = (typeof INDI_EVENT_TYPES)[number] +export const MAIN_EVENT_TYPES = [ + 'SAVE_FITS_AS', 'OPEN_FITS', 'OPEN_WINDOW', 'OPEN_DIRECTORY', 'CLOSE_WINDOW', +] + +export type MainEventType = (typeof MAIN_EVENT_TYPES)[number] + export const INTERNAL_EVENT_TYPES = [ 'SELECTED_CAMERA', 'SELECTED_FOCUSER', 'SELECTED_FILTER_WHEEL', 'SELECTED_MOUNT', - 'CAMERA_CHANGED', 'FOCUSER_CHANGED', 'MOUNT_CHANGED', - 'FILTER_WHEEL_CHANGED', 'FILTER_WHEEL_RENAMED', - 'IMAGE_STAR_SELECTED', 'GUIDE_OUTPUT_CHANGED', + 'CAMERA_CHANGED', 'FOCUSER_CHANGED', 'MOUNT_CHANGED', 'FILTER_WHEEL_CHANGED', + 'FILTER_WHEEL_RENAMED', 'IMAGE_STAR_SELECTED', 'GUIDE_OUTPUT_CHANGED', + 'DRAW_GUIDE_TRACKING_BOX', ] as const export type InternalEventType = (typeof INTERNAL_EVENT_TYPES)[number] diff --git a/nebulosa-guiding-internal/src/main/kotlin/nebulosa/guiding/internal/MultiStarGuider.kt b/nebulosa-guiding-internal/src/main/kotlin/nebulosa/guiding/internal/MultiStarGuider.kt index 6cc127753..583bd2e02 100644 --- a/nebulosa-guiding-internal/src/main/kotlin/nebulosa/guiding/internal/MultiStarGuider.kt +++ b/nebulosa-guiding-internal/src/main/kotlin/nebulosa/guiding/internal/MultiStarGuider.kt @@ -392,6 +392,9 @@ class MultiStarGuider( override val isGuiding get() = state == GuiderState.GUIDING + override val isCalibrating + get() = state == GuiderState.CALIBRATING + override fun startGuiding() { // We set the state to calibrating. The state machine will // automatically move from calibrating > calibrated > guiding diff --git a/nebulosa-guiding/src/main/kotlin/nebulosa/guiding/Guider.kt b/nebulosa-guiding/src/main/kotlin/nebulosa/guiding/Guider.kt index 0452fc519..686e07014 100644 --- a/nebulosa-guiding/src/main/kotlin/nebulosa/guiding/Guider.kt +++ b/nebulosa-guiding/src/main/kotlin/nebulosa/guiding/Guider.kt @@ -34,6 +34,8 @@ interface Guider : Iterable { fun reset(fullReset: Boolean) + val isCalibrating: Boolean + fun clearCalibration() fun loadCalibration(calibration: GuideCalibration) diff --git a/nebulosa-horizons/src/main/kotlin/nebulosa/horizons/HorizonsService.kt b/nebulosa-horizons/src/main/kotlin/nebulosa/horizons/HorizonsService.kt index 1ca715b92..aced52d7b 100644 --- a/nebulosa-horizons/src/main/kotlin/nebulosa/horizons/HorizonsService.kt +++ b/nebulosa-horizons/src/main/kotlin/nebulosa/horizons/HorizonsService.kt @@ -15,9 +15,9 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter class HorizonsService( - url: String = URL, + url: String = "", okHttpClient: OkHttpClient? = null, -) : RetrofitService(url, okHttpClient) { +) : RetrofitService(url.ifBlank { URL }, okHttpClient) { override val converterFactory: List = listOf(HorizonsEphemerisConverterFactory) @@ -31,14 +31,12 @@ class HorizonsService( apparent: ApparentRefractionCorrection = ApparentRefractionCorrection.AIRLESS, extraPrecision: Boolean = false, vararg quantities: HorizonsQuantity = HorizonsQuantity.ENTRIES, - ): Call { - return service.observer( - wrap(command), wrap("${longitude.degrees},${latitude.degrees},${elevation.kilometers}"), - wrap(startTime), wrap(endTime), wrap("${stepSize.toMinutes()}m"), - wrap(quantities.map { it.code }.toSortedSet().joinToString(",")), - wrap(apparent), wrap(if (extraPrecision) "YES" else "NO"), - ) - } + ) = service.observer( + wrap(command), wrap("${longitude.degrees},${latitude.degrees},${elevation.kilometers}"), + wrap(startTime), wrap(endTime), wrap("${stepSize.toMinutes()}m"), + wrap(quantities.map { it.code }.toSortedSet().joinToString(",")), + wrap(apparent), wrap(if (extraPrecision) "YES" else "NO"), + ) fun observerWithOsculationElements( name: String, @@ -59,18 +57,31 @@ class HorizonsService( apparent: ApparentRefractionCorrection = ApparentRefractionCorrection.AIRLESS, extraPrecision: Boolean = false, vararg quantities: HorizonsQuantity = HorizonsQuantity.ENTRIES, - ): Call { - return service.observerWithOsculationElements( - wrap(name), wrap(epoch), wrap(eccentricity), wrapNull(perihelionDistance), - wrapNull(perihelionJulianDayNumber), wrap(longitudeOfAscendingNode), - wrap(argumentOfPerihelion), wrap(inclination), wrapNull(meanAnomaly), - wrapNull(semiMajorAxis), wrapNull(meanMotion), wrapNull(absoluteMagnitude), - wrap("${longitude.degrees},${latitude.degrees},${elevation.kilometers}"), - wrap(startTime), wrap(endTime), wrap("${stepSize.toMinutes()}m"), - wrap(quantities.map { it.code }.toSortedSet().joinToString(",")), - wrap(apparent), wrap(if (extraPrecision) "YES" else "NO"), - ) - } + ) = service.observerWithOsculationElements( + wrap(name), wrap(epoch), wrap(eccentricity), wrapNull(perihelionDistance), + wrapNull(perihelionJulianDayNumber), wrap(longitudeOfAscendingNode), + wrap(argumentOfPerihelion), wrap(inclination), wrapNull(meanAnomaly), + wrapNull(semiMajorAxis), wrapNull(meanMotion), wrapNull(absoluteMagnitude), + wrap("${longitude.degrees},${latitude.degrees},${elevation.kilometers}"), + wrap(startTime), wrap(endTime), wrap("${stepSize.toMinutes()}m"), + wrap(quantities.map { it.code }.toSortedSet().joinToString(",")), + wrap(apparent), wrap(if (extraPrecision) "YES" else "NO"), + ) + + fun observerWithTLE( + tle: String, + longitude: Angle, latitude: Angle, elevation: Distance = Distance.ZERO, + startTime: LocalDateTime, endTime: LocalDateTime = startTime.plusDays(1L), + stepSize: Duration = DEFAULT_STEP_SIZE, + apparent: ApparentRefractionCorrection = ApparentRefractionCorrection.AIRLESS, + extraPrecision: Boolean = false, + vararg quantities: HorizonsQuantity = HorizonsQuantity.ENTRIES, + ) = service.observerWithTLE( + wrap(tle), wrap("${longitude.degrees},${latitude.degrees},${elevation.kilometers}"), + wrap(startTime), wrap(endTime), wrap("${stepSize.toMinutes()}m"), + wrap(quantities.map { it.code }.toSortedSet().joinToString(",")), + wrap(apparent), wrap(if (extraPrecision) "YES" else "NO"), + ) fun spk(id: Int, startTime: LocalDateTime, endTime: LocalDateTime): Call { return service.spk("'DES=$id;'", wrap(startTime), wrap(endTime)) diff --git a/nebulosa-retrofit/src/main/kotlin/nebulosa/retrofit/RetrofitService.kt b/nebulosa-retrofit/src/main/kotlin/nebulosa/retrofit/RetrofitService.kt index 01861ef7f..6c03b5512 100644 --- a/nebulosa-retrofit/src/main/kotlin/nebulosa/retrofit/RetrofitService.kt +++ b/nebulosa-retrofit/src/main/kotlin/nebulosa/retrofit/RetrofitService.kt @@ -17,6 +17,8 @@ abstract class RetrofitService( private val objectMapper: ObjectMapper? = null, ) { + protected val mapper by lazy { objectMapper ?: DEFAULT_MAPPER.copy()!! } + protected open val converterFactory = emptyList() protected open val callAdaptorFactory: CallAdapter.Factory? = null @@ -31,7 +33,6 @@ abstract class RetrofitService( builder.addConverterFactory(RawAsStringConverterFactory) builder.addConverterFactory(RawAsByteArrayConverterFactory) converterFactory.forEach { builder.addConverterFactory(it) } - val mapper = objectMapper ?: DEFAULT_MAPPER.copy() handleObjectMapper(mapper) builder.addConverterFactory(JacksonConverterFactory.create(mapper)) callAdaptorFactory?.also(builder::addCallAdapterFactory) diff --git a/settings.gradle.kts b/settings.gradle.kts index 22a800a4c..0757d7de4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,17 +20,17 @@ dependencyResolutionManagement { library("jackson-jsr310", "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2") library("retrofit", "com.squareup.retrofit2:retrofit:2.9.0") library("retrofit-jackson", "com.squareup.retrofit2:converter-jackson:2.9.0") - library("rx", "io.reactivex.rxjava3:rxjava:3.1.6") + library("rx", "io.reactivex.rxjava3:rxjava:3.1.7") library("logback", "ch.qos.logback:logback-classic:1.4.11") library("eventbus", "org.greenrobot:eventbus-java:3.3.1") - library("netty-transport", "io.netty:netty-transport:4.1.96.Final") - library("netty-codec", "io.netty:netty-codec:4.1.96.Final") + library("netty-transport", "io.netty:netty-transport:4.1.97.Final") + library("netty-codec", "io.netty:netty-codec:4.1.97.Final") library("xml", "com.fasterxml:aalto-xml:1.3.2") library("csv", "de.siegmar:fastcsv:2.2.2") library("apache-lang3", "org.apache.commons:commons-lang3:3.13.0") library("apache-codec", "commons-codec:commons-codec:1.16.0") library("apache-collections", "org.apache.commons:commons-collections4:4.4") - library("oshi", "com.github.oshi:oshi-core:6.4.4") + library("oshi", "com.github.oshi:oshi-core:6.4.5") library("timeshape", "net.iakovlev:timeshape:2022g.17") library("kotest-assertions-core", "io.kotest:kotest-assertions-core:5.6.2") library("kotest-runner-junit5", "io.kotest:kotest-runner-junit5:5.6.2")