Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recording and Uploading of GPX Traces #3573

Merged
merged 35 commits into from
May 15, 2022
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
fb76556
Add super simple visualization of GPX track recording
goldbattle Dec 7, 2021
28496a7
add insertion of track and partial upload information into database
goldbattle Dec 8, 2021
749f296
Add uploading of GPX traces to the OSM Api
goldbattle Dec 11, 2021
d52f54e
Move things to osmtracks, rename GPX -> Tracks, other small review ch…
goldbattle Dec 14, 2021
4744750
Time units are in milliseconds
goldbattle Dec 14, 2021
70d32df
small change
goldbattle Dec 14, 2021
2245aa0
Update app/src/main/java/de/westnordost/streetcomplete/data/osmtracks…
goldbattle Dec 18, 2021
37d164e
Update app/src/main/java/de/westnordost/streetcomplete/data/osmtracks…
goldbattle Dec 18, 2021
d2d11d5
changes requested in PR
goldbattle Dec 18, 2021
0b3e732
custom dialog for users that need to upgrade their permissions to upl…
goldbattle Dec 18, 2021
2d01383
PR comments on TracksMapComponent datastruct and fix upload text for …
goldbattle Jan 3, 2022
fe85ce5
More PR comment changes
goldbattle Jan 4, 2022
6090b5e
pr changes
goldbattle Jan 9, 2022
6988216
ask the user when they create a track if they want to upgrade their p…
goldbattle Jan 17, 2022
47be8a7
Merge branch 'master' into record_gps_tracks
goldbattle Jan 22, 2022
40b410c
fix merge issue
goldbattle Jan 22, 2022
fd81de2
Merge branch 'master' into record_gps_tracks
FloEdelmann Feb 2, 2022
808580c
Fix lint issues
FloEdelmann Feb 2, 2022
aff77f5
Merge branch 'master' into record_gps_tracks
FloEdelmann Mar 17, 2022
260e32e
Fix remaining merge conflicts (Koin migration)
FloEdelmann Mar 17, 2022
ad1ec72
Lint
FloEdelmann Mar 17, 2022
484faa4
Merge branch 'master' into record_gps_tracks
FloEdelmann Apr 11, 2022
8f9b845
Merge remote-tracking branch 'origin/master' into record_gps_tracks
goldbattle Apr 29, 2022
7a04dea
Store if the recording is active.
goldbattle Apr 30, 2022
4e7fea1
Always mark as old track
goldbattle Apr 30, 2022
231d178
Apply suggestions from code review
goldbattle May 1, 2022
e0b4766
tracks -> track
goldbattle May 1, 2022
754f902
remove unneeded check
goldbattle May 1, 2022
10c58db
use 4 space in yaml
goldbattle May 1, 2022
6720ed0
bracket spacing
goldbattle May 1, 2022
04aa856
variable renames
goldbattle May 1, 2022
cec6cda
Improve comments
FloEdelmann May 2, 2022
b684380
remove untrue comment
westnordost May 15, 2022
13d47c8
use elvis
westnordost May 15, 2022
b8413a1
shortened/cleared wording a bit
westnordost May 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ dependencies {
implementation("de.westnordost:osmapi-map:2.0")
implementation("de.westnordost:osmapi-changesets:2.0")
implementation("de.westnordost:osmapi-notes:2.0")
implementation("de.westnordost:osmapi-traces:2.0")
implementation("de.westnordost:osmapi-user:2.0")
implementation("com.squareup.okhttp3:okhttp:3.14.9")
implementation("se.akerfeldt:okhttp-signpost:1.1.0")
Expand Down
27 changes: 23 additions & 4 deletions app/src/main/assets/map_theme/jawg/streetcomplete.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ global:
geometry_line_color: '#44D14000'
geometry_point_color: '#88D14000'
track_color: '#44536dfe'
track_color_record: '#44fe1616'
old_track_color: '#22536dfe'

textures:
Expand Down Expand Up @@ -126,16 +127,25 @@ layers:
streetcomplete_track:
data: { source: streetcomplete_track }
current:
filter: { old: [false] }
filter: { old: [false], record: [false] }
draw:
track-lines:
color: global.track_color
width: [[14, 6px],[18, 12px]]
collide: false
join: round
order: 1000
record:
filter: { record: [true] }
draw:
track-lines:
color: global.track_color_record
width: [[14, 6px],[18, 12px]]
collide: false
join: round
order: 1000
old:
filter: { old: [true] }
filter: { old: [true], record: [false] }
draw:
track-lines:
color: global.old_track_color
Expand All @@ -146,16 +156,25 @@ layers:
streetcomplete_track2:
data: { source: streetcomplete_track2 }
current:
filter: { old: [false] }
filter: { old: [false], record: [false] }
draw:
track-lines:
color: global.track_color
width: [[14, 6px],[18, 12px]]
collide: false
join: round
order: 1000
record:
filter: { record: [true] }
draw:
track-lines:
color: global.track_color_record
width: [[14, 6px],[18, 12px]]
collide: false
join: round
order: 1000
old:
filter: { old: [true] }
filter: { old: [true], record: [false] }
draw:
track-lines:
color: global.old_track_color
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/de/westnordost/streetcomplete/Prefs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ object Prefs {
const val OSM_USER_ID = "osm.userid"
const val OSM_USER_NAME = "osm.username"
const val OSM_UNREAD_MESSAGES = "osm.unread_messages"
const val OSM_HAS_UPLOAD_TRACES_PERMISSION = "osm.upload_traces_permission"
const val USER_DAYS_ACTIVE = "days_active"
const val USER_GLOBAL_RANK = "user_global_rank"
const val USER_LAST_TIMESTAMP_ACTIVE = "last_timestamp_active"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import de.westnordost.streetcomplete.data.osm.mapdata.MapDataApi
import de.westnordost.streetcomplete.data.osm.mapdata.MapDataApiImpl
import de.westnordost.streetcomplete.data.osmnotes.NotesApi
import de.westnordost.streetcomplete.data.osmnotes.NotesApiImpl
import de.westnordost.streetcomplete.data.osmtracks.TracksApi
import de.westnordost.streetcomplete.data.osmtracks.TracksApiImpl
import de.westnordost.streetcomplete.data.user.OAuthStore
import oauth.signpost.OAuthConsumer
import org.koin.androidx.workmanager.dsl.worker
Expand All @@ -17,6 +19,7 @@ val osmApiModule = module {
factory { Cleaner(get(), get(), get()) }
factory<MapDataApi> { MapDataApiImpl(get()) }
factory<NotesApi> { NotesApiImpl(get()) }
factory<TracksApi> { TracksApiImpl(get()) }
factory { Preloader(get(named("CountryBoundariesFuture")), get(named("FeatureDictionaryFuture"))) }
factory { UserApi(get()) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,10 @@ class StreetCompleteSQLiteOpenHelper(context: Context, dbName: String) :
)
db.execSQL("DROP TABLE $oldGeometryTableName;")
}
if (oldVersion <= 5 && newVersion > 5) {
db.execSQL("ALTER TABLE ${NoteEditsTable.NAME} ADD COLUMN ${NoteEditsTable.Columns.TRACK} text DEFAULT '[]' NOT NULL")
}
}
}

private const val DB_VERSION = 5
private const val DB_VERSION = 6
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package de.westnordost.streetcomplete.data.osmnotes.edits
import de.westnordost.streetcomplete.data.edithistory.Edit
import de.westnordost.streetcomplete.data.edithistory.NoteEditKey
import de.westnordost.streetcomplete.data.osm.mapdata.LatLon
import de.westnordost.streetcomplete.data.osmtracks.Trackpoint

/** Contains all necessary information to create/comment on an OSM note. */
data class NoteEdit(
Expand Down Expand Up @@ -31,8 +32,11 @@ data class NoteEdit(
/** whether this edit has been uploaded already */
override val isSynced: Boolean,

/** Whether the images attached still need activation. Already true if imagePaths is empty */
val imagesNeedActivation: Boolean
/** whether the images attached still need activation. Already true if imagePaths is empty */
val imagesNeedActivation: Boolean,

/** attached GPS location history */
val track: List<Trackpoint>,
) : Edit {
override val isUndoable: Boolean get() = !isSynced
override val key: NoteEditKey get() = NoteEditKey(id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package de.westnordost.streetcomplete.data.osmnotes.edits
import de.westnordost.streetcomplete.data.osm.mapdata.BoundingBox
import de.westnordost.streetcomplete.data.osm.mapdata.LatLon
import de.westnordost.streetcomplete.data.osmnotes.Note
import de.westnordost.streetcomplete.data.osmtracks.Trackpoint
import java.lang.System.currentTimeMillis
import java.util.concurrent.CopyOnWriteArrayList

Expand All @@ -19,7 +20,8 @@ class NoteEditsController(
action: NoteEditAction,
position: LatLon,
text: String? = null,
imagePaths: List<String> = emptyList()
imagePaths: List<String> = emptyList(),
track: List<Trackpoint> = emptyList(),
) {
val edit = NoteEdit(
0,
Expand All @@ -30,7 +32,8 @@ class NoteEditsController(
imagePaths,
currentTimeMillis(),
false,
imagePaths.isNotEmpty()
imagePaths.isNotEmpty(),
track,
)
synchronized(this) { editsDB.add(edit) }
onAddedEdit(edit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns.
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns.LONGITUDE
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns.NOTE_ID
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns.TEXT
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns.TRACK
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns.TYPE
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.NAME
import kotlinx.serialization.decodeFromString
Expand Down Expand Up @@ -123,6 +124,7 @@ class NoteEditsDao(private val db: Database) {
TEXT to text,
IMAGE_PATHS to Json.encodeToString(imagePaths),
IMAGES_NEED_ACTIVATION to if (imagesNeedActivation) 1 else 0,
TRACK to Json.encodeToString(track),
TYPE to action.name
)

Expand All @@ -135,6 +137,7 @@ class NoteEditsDao(private val db: Database) {
Json.decodeFromString(getString(IMAGE_PATHS)),
getLong(CREATED_TIMESTAMP),
getInt(IS_SYNCED) == 1,
getInt(IMAGES_NEED_ACTIVATION) == 1
getInt(IMAGES_NEED_ACTIVATION) == 1,
Json.decodeFromString(getString(TRACK)),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import org.koin.dsl.module
val noteEditsModule = module {
factory { NoteEditsDao(get()) }

single { NoteEditsUploader(get(), get(), get(), get()) }
single { NoteEditsUploader(get(), get(), get(), get(), get()) }
single { NoteEditsController(get()) }
single<NoteEditsSource> { get<NoteEditsController>() }
single { NotesWithEditsSource(get(), get(), get()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ object NoteEditsTable {
const val TEXT = "text"
const val IMAGE_PATHS = "image_paths"
const val IMAGES_NEED_ACTIVATION = "images_need_activation"
const val TRACK = "track"
}

const val CREATE = """
Expand All @@ -27,6 +28,7 @@ object NoteEditsTable {
${Columns.TEXT} text,
${Columns.IMAGE_PATHS} text NOT NULL,
${Columns.IMAGES_NEED_ACTIVATION} int NOT NULL,
${Columns.TRACK} text NOT NULL,
${Columns.TYPE} varchar(255)
);
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import de.westnordost.streetcomplete.data.osmnotes.StreetCompleteImageUploader
import de.westnordost.streetcomplete.data.osmnotes.deleteImages
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditAction.COMMENT
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditAction.CREATE
import de.westnordost.streetcomplete.data.osmtracks.Trackpoint
import de.westnordost.streetcomplete.data.osmtracks.TracksApi
import de.westnordost.streetcomplete.data.upload.ConflictException
import de.westnordost.streetcomplete.data.upload.OnUploadedChangeListener
import kotlinx.coroutines.CoroutineName
Expand All @@ -21,6 +23,7 @@ class NoteEditsUploader(
private val noteEditsController: NoteEditsController,
private val noteController: NoteController,
private val notesApi: NotesApi,
private val tracksApi: TracksApi,
private val imageUploader: StreetCompleteImageUploader
) {
var uploadedChangeListener: OnUploadedChangeListener? = null
Expand Down Expand Up @@ -62,8 +65,12 @@ class NoteEditsUploader(
}

private fun uploadEdit(edit: NoteEdit) {
val text = edit.text.orEmpty() + uploadAndGetAttachedPhotosText(edit.imagePaths)
// try to upload the image and track if we have them
val imageText = uploadAndGetAttachedPhotosText(edit.imagePaths)
val trackText = uploadAndGetAttachedTrackText(edit.track, edit.text)
val text = edit.text.orEmpty() + imageText + trackText

// done, try to upload the note to OSM
try {
val note = when (edit.action) {
CREATE -> notesApi.create(edit.position, text)
Expand Down Expand Up @@ -112,6 +119,15 @@ class NoteEditsUploader(
return ""
}

private fun uploadAndGetAttachedTrackText(
trackpoints: List<Trackpoint>,
noteText: String?
): String {
if (trackpoints.isEmpty()) return ""
val track = tracksApi.create(trackpoints, noteText)
return "\n\nGPS Trace: \nhttps://www.openstreetmap.org/user/${track.userName}/traces/${track.id}"
}

companion object {
private const val TAG = "NoteEditsUploader"
private const val NOTE = "NOTE"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.westnordost.streetcomplete.data.osmtracks

import de.westnordost.streetcomplete.data.osm.mapdata.LatLon
import kotlinx.serialization.Serializable

@Serializable
data class Track(
val id: Long,
val userName: String,
)

@Serializable
data class Trackpoint(
val position: LatLon,
val time: Long,
val horizontalDilutionOfPrecision: Float,
val elevation: Float,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package de.westnordost.streetcomplete.data.osmtracks

import de.westnordost.streetcomplete.data.download.ConnectionException
import de.westnordost.streetcomplete.data.user.AuthorizationException

/**
* Creates GPS / GPX trackpoint histories
* All interactions with this class require an OsmConnection with a logged in user.
westnordost marked this conversation as resolved.
Show resolved Hide resolved
*/
interface TracksApi {

/**
* Create a new GPX track history
*
* @param trackpoints history of recorded trackpoints
* @param noteText optional text appended to the track
*
* @throws AuthorizationException if this application is not authorized to write notes
* (Permission.READ_GPS_TRACES, Permission.WRITE_GPS_TRACES)
* @throws ConnectionException if a temporary network connection problem occurs
*
* @return the new track
*/
fun create(trackpoints: List<Trackpoint>, noteText: String?): Track
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package de.westnordost.streetcomplete.data.osmtracks

import de.westnordost.osmapi.OsmConnection
import de.westnordost.osmapi.common.errors.OsmApiException
import de.westnordost.osmapi.common.errors.OsmApiReadResponseException
import de.westnordost.osmapi.common.errors.OsmAuthorizationException
import de.westnordost.osmapi.common.errors.OsmConnectionException
import de.westnordost.osmapi.map.data.OsmLatLon
import de.westnordost.osmapi.traces.GpsTraceDetails
import de.westnordost.osmapi.traces.GpsTracesApi
import de.westnordost.osmapi.traces.GpsTrackpoint
import de.westnordost.streetcomplete.ApplicationConstants
import de.westnordost.streetcomplete.data.download.ConnectionException
import de.westnordost.streetcomplete.data.user.AuthorizationException
import java.time.Instant
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter

class TracksApiImpl(osm: OsmConnection) : TracksApi {
private val api: GpsTracesApi = GpsTracesApi(osm)

override fun create(trackpoints: List<Trackpoint>, noteText: String?): Track = wrapExceptions {
// Filename is just the start of the track
// https://stackoverflow.com/a/49862573/7718197
val name = DateTimeFormatter
.ofPattern("yyyy_MM_dd'T'HH_mm_ss.SSSSSS'Z'")
.withZone(ZoneOffset.UTC)
.format(Instant.ofEpochSecond(trackpoints[0].time)) + ".gpx"
val visibility = GpsTraceDetails.Visibility.IDENTIFIABLE
val description = noteText.orEmpty().ifBlank { "Uploaded via ${ApplicationConstants.USER_AGENT}" }
westnordost marked this conversation as resolved.
Show resolved Hide resolved
val tags = listOf(ApplicationConstants.NAME.lowercase())

// Generate history of trackpoints
val history = trackpoints.mapIndexed { idx, it ->
GpsTrackpoint(
OsmLatLon(it.position.latitude, it.position.longitude),
Instant.ofEpochMilli(it.time),
idx == 0,
it.horizontalDilutionOfPrecision,
it.elevation
)
}

// Finally query the API and return!
val traceId = api.create(name, visibility, description, tags, history)
val details = api.get(traceId)
Track(details.id, details.userName)
}
}

private inline fun <T> wrapExceptions(block: () -> T): T =
try {
block()
} catch (e: OsmAuthorizationException) {
throw AuthorizationException(e.message, e)
} catch (e: OsmConnectionException) {
throw ConnectionException(e.message, e)
} catch (e: OsmApiReadResponseException) {
// probably a temporary connection error
throw ConnectionException(e.message, e)
} catch (e: OsmApiException) {
// request timeout is a temporary connection error
throw if (e.errorCode == 408) ConnectionException(e.message, e) else e
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditAction
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsController
import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuest
import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuestController
import de.westnordost.streetcomplete.data.osmtracks.Trackpoint
import de.westnordost.streetcomplete.osm.KEYS_THAT_SHOULD_BE_REMOVED_WHEN_SHOP_IS_REPLACED
import de.westnordost.streetcomplete.osm.removeCheckDates
import de.westnordost.streetcomplete.quests.note_discussion.NoteAnswer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.collections.ArrayList

/** Controls the workflow of quests: Solving them, hiding them instead, splitting the way instead,
* undoing, etc. */
Expand Down Expand Up @@ -66,10 +66,11 @@ class QuestController(
suspend fun createNote(
text: String,
imagePaths: List<String>,
position: LatLon
position: LatLon,
track: List<Trackpoint>,
) = withContext(Dispatchers.IO) {
val fullText = "$text\n\nvia ${ApplicationConstants.USER_AGENT}"
noteEditsController.add(0, NoteEditAction.CREATE, position, fullText, imagePaths)
noteEditsController.add(0, NoteEditAction.CREATE, position, fullText, imagePaths, track)
}

/** Split a way for the given OSM Quest.
Expand Down
Loading