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 6 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 @@ -158,6 +158,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.12.13")
implementation("se.akerfeldt:okhttp-signpost:1.1.0")
Expand Down
55 changes: 37 additions & 18 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,20 +156,29 @@ layers:
streetcomplete_track2:
data: { source: streetcomplete_track2 }
current:
filter: { old: [false] }
draw:
track-lines:
color: global.track_color
width: [[14, 6px],[18, 12px]]
collide: false
join: round
order: 1000
filter: { old: [ false ], record: [ false ] }
goldbattle marked this conversation as resolved.
Show resolved Hide resolved
draw:
track-lines:
color: global.track_color
width: [ [ 14, 6px ],[ 18, 12px ] ]
collide: false
join: round
order: 1000
westnordost marked this conversation as resolved.
Show resolved Hide resolved
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] }
draw:
track-lines:
color: global.old_track_color
width: [[14, 6px],[18, 12px]]
collide: false
join: round
order: 1000
filter: { old: [ true ], record: [ false ] }
draw:
track-lines:
color: global.old_track_color
width: [ [ 14, 6px ],[ 18, 12px ] ]
collide: false
join: round
order: 1000
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,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 javax.inject.Singleton
Expand All @@ -32,5 +34,7 @@ object OsmApiModule {

@Provides fun notesApi(osm: OsmConnection): NotesApi = NotesApiImpl(osm)

@Provides fun tracksApi(osm: OsmConnection): TracksApi = TracksApiImpl(osm)

@Provides fun mapDataApi(osm: OsmConnection): MapDataApi = MapDataApiImpl(osm)
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,12 @@ import de.westnordost.streetcomplete.quests.oneway_suspects.data.WayTrafficFlowT
if (oldVersion <= 3 && newVersion > 3) {
db.execSQL("DROP TABLE new_achievements")
}
if (oldVersion <= 4 && newVersion > 4) {
db.execSQL("ALTER TABLE ${NoteEditsTable.NAME} ADD COLUMN ${NoteEditsTable.Columns.TRACKS} text DEFAULT '[]' NOT NULL")
db.execSQL("ALTER TABLE ${NoteEditsTable.NAME} ADD COLUMN ${NoteEditsTable.Columns.UPLOAD_DATA_MAP} text DEFAULT '{}' NOT NULL")
}
}

}

private const val DB_VERSION = 4
private const val DB_VERSION = 5
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,5 @@ interface NotesApi {
* @return the incoming notes
*/
fun getAll(bounds: BoundingBox, limit: Int, hideClosedNoteAfter: Int): List<Note>

}
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,14 @@ 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 tracks */
val tracks: List<Trackpoint>,
goldbattle marked this conversation as resolved.
Show resolved Hide resolved

/** contains partial upload responses index by type (e.g. image and track urls) */
val uploadedDataMap: Map<String, String>
goldbattle marked this conversation as resolved.
Show resolved Hide resolved
): 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,9 @@ 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 kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.lang.System.currentTimeMillis
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
Expand All @@ -21,7 +24,8 @@ import javax.inject.Singleton
action: NoteEditAction,
position: LatLon,
text: String? = null,
imagePaths: List<String> = emptyList()
imagePaths: List<String> = emptyList(),
tracks: List<Trackpoint> = emptyList()
goldbattle marked this conversation as resolved.
Show resolved Hide resolved
) {
val edit = NoteEdit(
0,
Expand All @@ -32,7 +36,9 @@ import javax.inject.Singleton
imagePaths,
currentTimeMillis(),
false,
imagePaths.isNotEmpty()
imagePaths.isNotEmpty(),
tracks,
mutableMapOf()
goldbattle marked this conversation as resolved.
Show resolved Hide resolved
)
synchronized(this) { editsDB.add(edit) }
onAddedEdit(edit)
Expand Down Expand Up @@ -85,6 +91,14 @@ import javax.inject.Singleton
}
}

fun updateData(edit: NoteEdit, name: String, data: String) {
synchronized(this) {
val tmpData = edit.uploadedDataMap.toMutableMap()
tmpData[name] = data
westnordost marked this conversation as resolved.
Show resolved Hide resolved
editsDB.updateUploadData(edit.noteId, Json.encodeToString(tmpData))
}
}

fun markSyncFailed(edit: NoteEdit): Boolean =
delete(edit)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import de.westnordost.streetcomplete.data.Database
import de.westnordost.streetcomplete.data.osm.mapdata.BoundingBox
import de.westnordost.streetcomplete.data.osm.mapdata.LatLon
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns.CREATED_TIMESTAMP
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns.TRACKS
import javax.inject.Inject
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns.ID
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns.IMAGES_NEED_ACTIVATION
Expand All @@ -15,6 +16,7 @@ import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns.
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.TYPE
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.Columns.UPLOAD_DATA_MAP
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsTable.NAME
import de.westnordost.streetcomplete.ktx.*
import kotlinx.serialization.decodeFromString
Expand Down Expand Up @@ -105,6 +107,9 @@ class NoteEditsDao @Inject constructor(private val db: Database) {
fun updateNoteId(oldNoteId: Long, newNoteId: Long): Int =
db.update(NAME, listOf(NOTE_ID to newNoteId), "$NOTE_ID = $oldNoteId")

fun updateUploadData(noteId: Long, data: String): Int =
db.update(NAME, listOf(UPLOAD_DATA_MAP to data), "$NOTE_ID = $noteId")

fun getOldestNeedingImagesActivation(): NoteEdit? =
db.queryOne(NAME, where = "$IS_SYNCED = 1 AND $IMAGES_NEED_ACTIVATION = 1", orderBy = CREATED_TIMESTAMP) { it.toNoteEdit() }

Expand All @@ -125,6 +130,8 @@ class NoteEditsDao @Inject constructor(private val db: Database) {
TEXT to text,
IMAGE_PATHS to Json.encodeToString(imagePaths),
IMAGES_NEED_ACTIVATION to if (imagesNeedActivation) 1 else 0,
TRACKS to Json.encodeToString(tracks),
UPLOAD_DATA_MAP to Json.encodeToString(uploadedDataMap),
TYPE to action.name
)

Expand All @@ -137,6 +144,8 @@ class NoteEditsDao @Inject constructor(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(TRACKS)),
Json.decodeFromString(getString(UPLOAD_DATA_MAP))
goldbattle marked this conversation as resolved.
Show resolved Hide resolved
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ object NoteEditsTable {
const val TEXT = "text"
const val IMAGE_PATHS = "image_paths"
const val IMAGES_NEED_ACTIVATION = "images_need_activation"
const val TRACKS = "tracks"
const val UPLOAD_DATA_MAP = "upload_data_map"
}

const val CREATE = """
Expand All @@ -27,6 +29,8 @@ object NoteEditsTable {
${Columns.TEXT} text,
${Columns.IMAGE_PATHS} text NOT NULL,
${Columns.IMAGES_NEED_ACTIVATION} int NOT NULL,
${Columns.TRACKS} text NOT NULL,
${Columns.UPLOAD_DATA_MAP} text NOT NULL,
${Columns.TYPE} varchar(255)
);"""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import android.util.Log
import de.westnordost.streetcomplete.data.osmnotes.*
import de.westnordost.streetcomplete.data.upload.ConflictException
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditAction.*
import de.westnordost.streetcomplete.data.osmtracks.Trackpoint
import de.westnordost.streetcomplete.data.osmtracks.TracksApi
import de.westnordost.streetcomplete.data.upload.OnUploadedChangeListener
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
Expand All @@ -14,6 +16,7 @@ class NoteEditsUploader @Inject constructor(
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 All @@ -26,15 +29,17 @@ class NoteEditsUploader @Inject constructor(
* Drops any edits where the upload failed because of a conflict but keeps any notes where
* the upload failed because attached photos could not be uploaded (so it can try again
* later). */
suspend fun upload() = mutex.withLock { withContext(Dispatchers.IO) {
// first look if any images have not been activated yet
uploadMissedImageActivations()
// then do the usual stuff
uploadEdits()
} }
suspend fun upload() = mutex.withLock {
withContext(Dispatchers.IO) {
// first look if any images have not been activated yet
uploadMissedImageActivations()
// then do the usual stuff
uploadEdits()
}
}
westnordost marked this conversation as resolved.
Show resolved Hide resolved

private suspend fun uploadMissedImageActivations() {
while(true) {
while (true) {
val edit = noteEditsController.getOldestNeedingImagesActivation() ?: break
/* see uploadEdits */
withContext(scope.coroutineContext) {
Expand All @@ -45,7 +50,7 @@ class NoteEditsUploader @Inject constructor(
}

private suspend fun uploadEdits() {
while(true) {
while (true) {
val edit = noteEditsController.getOldestUnsynced() ?: break
/* the sync of local change -> API and its response should not be cancellable because
* otherwise an inconsistency in the data would occur. F.e. a note could be uploaded
Expand All @@ -55,17 +60,31 @@ class NoteEditsUploader @Inject constructor(
}

private fun uploadEdit(edit: NoteEdit) {
val text = edit.text.orEmpty() + uploadAndGetAttachedPhotosText(edit.imagePaths)

// try to upload the image
val imageText = edit.uploadedDataMap.getOrDefault(
"images",
uploadAndGetAttachedPhotosText(edit.imagePaths)
FloEdelmann marked this conversation as resolved.
Show resolved Hide resolved
)
noteEditsController.updateData(edit, "images", imageText)

// try to upload the tracks
val tracksText =
edit.uploadedDataMap.getOrDefault("tracks", uploadAndGetAttachedTracksText(edit.tracks))
noteEditsController.updateData(edit, "tracks", tracksText)

// done, try to upload the note to OSM
val text = edit.text.orEmpty() + imageText + tracksText
try {
val note = when(edit.action) {
val note = when (edit.action) {
CREATE -> notesApi.create(edit.position, text)
COMMENT -> notesApi.comment(edit.noteId, text)
}

Log.d(TAG,
Log.d(
TAG,
"Uploaded a ${edit.action.name} to ${note.id}" +
" at ${edit.position.latitude}, ${edit.position.longitude}"
" at ${edit.position.latitude}, ${edit.position.longitude}"
)
uploadedChangeListener?.onUploaded(NOTE, edit.position)

Expand All @@ -79,9 +98,10 @@ class NoteEditsUploader @Inject constructor(
deleteImages(edit.imagePaths)

} catch (e: ConflictException) {
Log.d(TAG,
Log.d(
TAG,
"Dropped a ${edit.action.name} to ${edit.noteId}" +
" at ${edit.position.latitude}, ${edit.position.longitude}: ${e.message}"
" at ${edit.position.latitude}, ${edit.position.longitude}: ${e.message}"
)
uploadedChangeListener?.onDiscarded(NOTE, edit.position)

Expand All @@ -106,6 +126,15 @@ class NoteEditsUploader @Inject constructor(
return ""
}

private fun uploadAndGetAttachedTracksText(tracks: List<Trackpoint>): String {
if (tracks.isEmpty()) {
return ""
}
val track = tracksApi.create(tracks)
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,19 @@
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,
)
Loading