-
Notifications
You must be signed in to change notification settings - Fork 0
Work on Changesets in API transmission #86
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
Changes from all commits
3585de0
e7ca4a6
61dac3f
71aebed
9d00b66
4da7da3
3822958
f04a339
5dda9ea
81d8ad2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| // | ||
| // ChangesetService.swift | ||
| // IOSAccessAssessment | ||
| // | ||
| // Created by Mariana Piz on 11.11.2024. | ||
| // | ||
|
|
||
| import Foundation | ||
|
|
||
| struct NodeData { | ||
| let latitude: Double | ||
| let longitude: Double | ||
| } | ||
|
|
||
| class ChangesetService { | ||
|
|
||
| private enum Constants { | ||
| static let baseUrl = "https://osm.workspaces-stage.sidewalks.washington.edu/api/0.6" | ||
| static let workspaceId = "168" | ||
| } | ||
|
|
||
| static let shared = ChangesetService() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the idea of using singletons, and imo we should have used something like that for the SharedImageData as well (along with adding it to a global store, instead of passing it to every view) |
||
| private init() {} | ||
|
|
||
| private let accessToken = KeychainService().getValue(for: .accessToken) | ||
| private(set) var changesetId: String? | ||
|
|
||
| func openChangeset(completion: @escaping (Result<String, Error>) -> Void) { | ||
| guard let url = URL(string: "\(Constants.baseUrl)/changeset/create"), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the fact that we now have two Constants structs will lead to confusion.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a problem because the Constants enum is private for each struct |
||
| let accessToken | ||
| else { return } | ||
|
|
||
| var request = URLRequest(url: url) | ||
| request.httpMethod = "PUT" | ||
| request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") | ||
| request.setValue(Constants.workspaceId, forHTTPHeaderField: "X-Workspace") | ||
| request.setValue("application/xml", forHTTPHeaderField: "Content-Type") | ||
|
|
||
| guard let xmlData = """ | ||
| <osm> | ||
| <changeset> | ||
| <tag k="created_by" v="GIG 1.0(4)" /> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the new PR that we raise for change sets (I think we will have to considering the conflicts), we should change this to match our own arguments.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both "created_by" and "comment" are according to the documentation (4 b Open a changeset): https://docs.google.com/document/d/1qPSW5pYmVb-RXOiryBm452sLfTvFl38f_TCO4SElkSY/edit?tab=t.0
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I resolved merge conflicts
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but that example input is for GoInfoGame. We are a different creator, so we should be having our own identifier.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah cool. Let's proceed with this PR then |
||
| <tag k="comment" v="iOS OSM client" /> | ||
| </changeset> | ||
| </osm> | ||
| """ | ||
| .data(using: .utf8) | ||
| else { | ||
| print("Failed to create XML data.") | ||
| return | ||
| } | ||
|
|
||
| request.httpBody = xmlData | ||
|
|
||
| URLSession.shared.dataTask(with: request) { data, response, error in | ||
| if let error = error { | ||
| completion(.failure(error)) | ||
| return | ||
| } | ||
|
|
||
| if let data = data, let changesetId = String(data: data, encoding: .utf8) { | ||
| self.changesetId = changesetId | ||
| completion(.success(changesetId)) | ||
| } else { | ||
| completion(.failure(NSError(domain: "ChangesetError", | ||
| code: -1, | ||
| userInfo: [NSLocalizedDescriptionKey: "Failed to open changeset"]))) | ||
| } | ||
| }.resume() | ||
| } | ||
|
|
||
| func uploadChanges(nodeData: NodeData, completion: @escaping (Result<Void, Error>) -> Void) { | ||
| guard let changesetId, | ||
| let accessToken, | ||
| let url = URL(string: "\(Constants.baseUrl)/changeset/\(changesetId)/upload") | ||
| else { return } | ||
|
|
||
| let xmlContent = | ||
| """ | ||
| <osmChange version="0.6" generator="GIG Change generator"> | ||
| <create> | ||
| <node id="-1" lat="\(nodeData.latitude)" lon="\(nodeData.longitude)" changeset="\(changesetId)" /> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We will have to of course, work on generalizing this on any kind of output that we get from the post-processed output of AnnotationView. |
||
| </create> | ||
| </osmChange> | ||
| """ | ||
|
|
||
| var request = URLRequest(url: url) | ||
| request.httpMethod = "POST" | ||
| request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") | ||
| request.setValue(Constants.workspaceId, forHTTPHeaderField: "X-Workspace") | ||
| request.setValue("application/xml", forHTTPHeaderField: "Content-Type") | ||
| request.httpBody = xmlContent.data(using: .utf8) | ||
|
|
||
| URLSession.shared.dataTask(with: request) { data, response, error in | ||
| if let error = error { | ||
| completion(.failure(error)) | ||
| return | ||
| } | ||
| completion(.success(())) | ||
| }.resume() | ||
| } | ||
|
|
||
| func closeChangeset(completion: @escaping (Result<Void, Error>) -> Void) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think if we should be closing the ChangeSet in the ContentView as well, every time the back button is clicked.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nevermind. Change sets are generally closed automatically when this happens |
||
| guard let changesetId, let accessToken else { return } | ||
|
|
||
| guard let url = URL(string: "\(Constants.baseUrl)/changeset/\(changesetId)/close") else { return } | ||
|
|
||
| var request = URLRequest(url: url) | ||
| request.httpMethod = "PUT" | ||
| request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") | ||
| request.setValue("application/json", forHTTPHeaderField: "Content-Type") | ||
|
|
||
| URLSession.shared.dataTask(with: request) { data, response, error in | ||
| if let error = error { | ||
| completion(.failure(error)) | ||
| return | ||
| } | ||
| self.changesetId = nil | ||
|
|
||
| completion(.success(())) | ||
| }.resume() | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,61 +29,64 @@ struct ContentView: View { | |
| var objectLocation = ObjectLocation() | ||
|
|
||
| var body: some View { | ||
| VStack { | ||
| if manager?.dataAvailable ?? false{ | ||
| ZStack { | ||
| HostedCameraViewController(session: manager!.controller.captureSession, | ||
| frameRect: VerticalFrame.getColumnFrame( | ||
| width: UIScreen.main.bounds.width, | ||
| height: UIScreen.main.bounds.height, | ||
| row: 0) | ||
| ) | ||
| HostedSegmentationViewController(segmentationImage: $segmentationModel.maskedSegmentationResults, | ||
| frameRect: VerticalFrame.getColumnFrame( | ||
| width: UIScreen.main.bounds.width, | ||
| height: UIScreen.main.bounds.height, | ||
| row: 1) | ||
| ) | ||
| } | ||
| Button { | ||
| segmentationModel.performPerClassSegmentationRequest(with: sharedImageData.cameraImage!) | ||
| objectLocation.setLocationAndHeading() | ||
| manager?.stopStream() | ||
| } label: { | ||
| Image(systemName: "camera.circle.fill") | ||
| .resizable() | ||
| .frame(width: 60, height: 60) | ||
| .foregroundColor(.white) | ||
| } | ||
| VStack { | ||
| if manager?.dataAvailable ?? false{ | ||
| ZStack { | ||
| HostedCameraViewController(session: manager!.controller.captureSession, | ||
| frameRect: VerticalFrame.getColumnFrame( | ||
| width: UIScreen.main.bounds.width, | ||
| height: UIScreen.main.bounds.height, | ||
| row: 0) | ||
| ) | ||
| HostedSegmentationViewController(segmentationImage: $segmentationModel.maskedSegmentationResults, | ||
| frameRect: VerticalFrame.getColumnFrame( | ||
| width: UIScreen.main.bounds.width, | ||
| height: UIScreen.main.bounds.height, | ||
| row: 1) | ||
| ) | ||
| } | ||
| else { | ||
| VStack { | ||
| SpinnerView() | ||
| Text("Camera settings in progress") | ||
| .padding(.top, 20) | ||
| } | ||
| Button { | ||
| segmentationModel.performPerClassSegmentationRequest(with: sharedImageData.cameraImage!) | ||
| objectLocation.setLocationAndHeading() | ||
| manager?.stopStream() | ||
| } label: { | ||
| Image(systemName: "camera.circle.fill") | ||
| .resizable() | ||
| .frame(width: 60, height: 60) | ||
| .foregroundColor(.white) | ||
| } | ||
| } | ||
| .navigationDestination(isPresented: $navigateToAnnotationView) { | ||
| AnnotationView(objectLocation: objectLocation, | ||
| classes: Constants.ClassConstants.classes, | ||
| selection: selection | ||
| ) | ||
| } | ||
| .navigationBarTitle("Camera View", displayMode: .inline) | ||
| .onAppear { | ||
| if (manager == nil) { | ||
| segmentationModel.updateSegmentationRequest(selection: selection, completion: updateSharedImageSegmentation) | ||
| segmentationModel.updatePerClassSegmentationRequest(selection: selection, | ||
| completion: updatePerClassImageSegmentation) | ||
| manager = CameraManager(sharedImageData: sharedImageData, segmentationModel: segmentationModel) | ||
| } else { | ||
| manager?.resumeStream() | ||
| else { | ||
| VStack { | ||
| SpinnerView() | ||
| Text("Camera settings in progress") | ||
| .padding(.top, 20) | ||
| } | ||
| } | ||
| .onDisappear { | ||
| manager?.stopStream() | ||
| } | ||
| .navigationDestination(isPresented: $navigateToAnnotationView) { | ||
| AnnotationView( | ||
| objectLocation: objectLocation, | ||
| classes: Constants.ClassConstants.classes, | ||
| selection: selection | ||
| ) | ||
| } | ||
| .navigationBarTitle("Camera View", displayMode: .inline) | ||
| .onAppear { | ||
| openChangeset() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to confirm, this is the only change that has been added to openChangeset right?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the change set creation fails, then I am not sure we should be proceeding with the ContentView recording since it will be for nothing. |
||
|
|
||
| if (manager == nil) { | ||
| segmentationModel.updateSegmentationRequest(selection: selection, completion: updateSharedImageSegmentation) | ||
| segmentationModel.updatePerClassSegmentationRequest(selection: selection, | ||
| completion: updatePerClassImageSegmentation) | ||
| manager = CameraManager(sharedImageData: sharedImageData, segmentationModel: segmentationModel) | ||
| } else { | ||
| manager?.resumeStream() | ||
| } | ||
| } | ||
| .onDisappear { | ||
| manager?.stopStream() | ||
| } | ||
| } | ||
|
|
||
| // Callbacks to the SegmentationModel | ||
|
|
@@ -108,4 +111,15 @@ struct ContentView: View { | |
| fatalError("Unable to process per-class segmentation \(error.localizedDescription)") | ||
| } | ||
| } | ||
|
|
||
| private func openChangeset() { | ||
| ChangesetService.shared.openChangeset { result in | ||
| switch result { | ||
| case .success(let changesetId): | ||
| print("Opened changeset with ID: \(changesetId)") | ||
| case .failure(let error): | ||
| print("Failed to open changeset: \(error.localizedDescription)") | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a small point, but we can later move the constants to the global constants file, for better centralization, or at least in a common folder for constants.