diff --git a/server/API_CHANGES.md b/server/API_CHANGES.md index 52d133b9e..0f14514de 100644 --- a/server/API_CHANGES.md +++ b/server/API_CHANGES.md @@ -44,7 +44,7 @@ | /anyplace/position/predictFloorAlgo1 | /api/position/predictFloorAlgo1 | | | /anyplace/position/estimate_position | /api/position/estimate | | | /anyplace/position/radio/delete | /api/radiomap/delete | | -| /anyplace/position/radio_by_building_floor_all | /api/radiomap/floor/all | | +| /anyplace/position/radio_by_building_floor_all | /api/radiomap/floors | | | /anyplace/position/radio_by_floor_bbox | /api/radiomap/floor/bbox | | | /anyplace/position/radio_upload | /api/radiomap/upload | | | /anyplace/position/radio_download_floor | /api/radiomap/floor | | diff --git a/server/CHANGELOG.md b/server/CHANGELOG.md index 650b61e6a..89e82813e 100644 --- a/server/CHANGELOG.md +++ b/server/CHANGELOG.md @@ -1,4 +1,34 @@ # CHANGELOG + +# Version 4.2.6 +- search bar (pac-input): + - accepts coordinates in google maps format: LAT, LON + - moves map to that location + - previous functionality is not working due to GMap.js update +- uploading floorplans + - `OverlayMode`: + - keep the previous floor's floorplan when uploading a new one to aid in aligning + - no longer rotating when zooming in. the `rotatable` plugin was also updated + - while zooming, rotating and panning a floorplan: + - most of the gmaps functionality is disabled (e.g, rotation, zoom, etc) + - using the proper callbacks now (from rotatable, resizable, etc) + - not required to zoom at full level to improve quality + - image is uploaded at max quality + - canvas is re-drawn before saving at full resolution + +- share urls (and everything based on BASE_URL) is now relative + - e.g. sharing a building on ap-dev will generate an endpoint for ap-dev + +- BUGFIX: MDB issue when storing new documents + - affected vessel/building, floor creation, etc +- BUGFIX: architect no longer loads 3x on `angular init` +- BUGFIX: better handling of the cases where architect requests fingerprints/heatmaps/APs of unmapped floors. +- BUGFIX: backup now works: + - related bugs: (endpoints updated) + - `/api/floorplans64/all/{buid}/{floors}` + - `/api/radiomap/floors` +- BUGFIX: POI Connection delete + #### Archive: [changelog](changelog/README.md) --- diff --git a/server/FAQ.md b/server/FAQ.md index baf1c8eec..b4669c87c 100644 --- a/server/FAQ.md +++ b/server/FAQ.md @@ -1,4 +1,54 @@ # FAQ: -##### 1. Swagger documentation not generated correctly: -Try the sbt `swagger` command that the `iheart/sbt-swagger-play` plugin adds. \ No newline at end of file +### A. Documentation +Documentation is automatically generated using `iheart-radio/swagger`. +The additions are in `conf/swagger.yml` and `conf/api.routes` + +##### A1. Swagger documentation not generated correctly: +Try the sbt `swagger` command that the `iheart/sbt-swagger-play` plugin adds. + +##### A2. An endpoint on `/developers` does not get the response. +
+ +Reply example in swagger + + +Either it's a bug or it's missing the response tag. +Update the relevant `api.routes` entry with: +```bash +# responses: +# '200': +# description: Successful operation +``` +See other examples on how to put a sample response. +
+ +##### A3. How to put a reply example in swagger: +
+ +Reply example in swagger + + +Way 1: +```bash +# examples: +# application/json: | +# { +# "all_floors": [ +# "", +# "" +# ] +# } +``` + +Way 2: +```bash +# responses: +# 200: +# description: success +# schema: +# $ref: '#/definitions/Version' +``` +This requires a definition in `conf/swagger.yml`, under `definitions:`. +
+ diff --git a/server/app/controllers/HeatmapController.scala b/server/app/controllers/HeatmapController.scala index 57c2df682..54361fa91 100644 --- a/server/app/controllers/HeatmapController.scala +++ b/server/app/controllers/HeatmapController.scala @@ -39,10 +39,12 @@ class HeatmapController @Inject()(cc: ControllerComponents, if (!anyReq.assertJsonBody()) return RESPONSE.BAD(RESPONSE.ERROR_JSON_PARSE) val json = anyReq.getJsonBody() LOG.D2("Heatmap: floorWifiAVG1: " + Utils.stripJsValueStr(json)) + val checkRequirements = VALIDATE.checkRequirements(json, SCHEMA.fBuid, SCHEMA.fFloor) if (checkRequirements != null) return checkRequirements val buid = (json \ SCHEMA.fBuid).as[String] val floor = (json \ SCHEMA.fFloor).as[String] + if (!pds.db.floorHasFingerprints(buid, floor)) return RESPONSE.BAD_NO_FINGERPRINTS try { val radioPoints = pds.db.getRadioHeatmapByBuildingFloorAverage1(buid, floor) if (radioPoints == null) return RESPONSE.BAD_CANNOT_RETRIEVE_SPACE @@ -71,6 +73,7 @@ class HeatmapController @Inject()(cc: ControllerComponents, if (checkRequirements != null) return checkRequirements val buid = (json \ SCHEMA.fBuid).as[String] val floor = (json \ SCHEMA.fFloor).as[String] + if (!pds.db.floorHasFingerprints(buid, floor)) return RESPONSE.BAD_NO_FINGERPRINTS try { val radioPoints = pds.db.getRadioHeatmapByBuildingFloorAverage2(buid, floor) if (radioPoints == null) return RESPONSE.BAD_CANNOT_RETRIEVE_SPACE @@ -105,6 +108,7 @@ class HeatmapController @Inject()(cc: ControllerComponents, if (checkRequirements != null) return checkRequirements val buid = (json \ SCHEMA.fBuid).as[String] val floor = (json \ SCHEMA.fFloor).as[String] + if (!pds.db.floorHasFingerprints(buid, floor)) return RESPONSE.BAD_NO_FINGERPRINTS try { val radioPoints = pds.db.getRadioHeatmapByBuildingFloorAverage3(buid, floor) if (radioPoints == null) return RESPONSE.BAD_CANNOT_RETRIEVE_SPACE @@ -138,6 +142,7 @@ class HeatmapController @Inject()(cc: ControllerComponents, if (checkRequirements != null) return checkRequirements val buid = (json \ SCHEMA.fBuid).as[String] val floor = (json \ SCHEMA.fFloor).as[String] + if (!pds.db.floorHasFingerprints(buid, floor)) return RESPONSE.BAD_NO_FINGERPRINTS val tileX = (json \ SCHEMA.fX).as[Int] val tileY = (json \ SCHEMA.fY).as[Int] val zoomLevel = (json \ "z").as[Int] @@ -181,6 +186,7 @@ class HeatmapController @Inject()(cc: ControllerComponents, if (checkRequirements != null) return checkRequirements val buid = (json \ SCHEMA.fBuid).as[String] val floor = (json \ SCHEMA.fFloor).as[String] + if (!pds.db.floorHasFingerprints(buid, floor)) return RESPONSE.BAD_NO_FINGERPRINTS val timestampX = (json \ SCHEMA.fTimestampX).as[String] val timestampY = (json \ SCHEMA.fTimestampY).as[String] try { @@ -211,6 +217,7 @@ class HeatmapController @Inject()(cc: ControllerComponents, if (checkRequirements != null) return checkRequirements val buid = (json \ SCHEMA.fBuid).as[String] val floor = (json \ SCHEMA.fFloor).as[String] + if (!pds.db.floorHasFingerprints(buid, floor)) return RESPONSE.BAD_NO_FINGERPRINTS val timestampX = (json \ SCHEMA.fTimestampX).as[String] val timestampY = (json \ SCHEMA.fTimestampY).as[String] try { @@ -248,6 +255,7 @@ class HeatmapController @Inject()(cc: ControllerComponents, if (checkRequirements != null) return checkRequirements val buid = (json \ SCHEMA.fBuid).as[String] val floor = (json \ SCHEMA.fFloor).as[String] + if (!pds.db.floorHasFingerprints(buid, floor)) return RESPONSE.BAD_NO_FINGERPRINTS val timestampX = (json \ SCHEMA.fTimestampX).as[String] val timestampY = (json \ SCHEMA.fTimestampY).as[String] @@ -284,6 +292,7 @@ class HeatmapController @Inject()(cc: ControllerComponents, if (checkRequirements != null) return checkRequirements val buid = (json \ SCHEMA.fBuid).as[String] val floor = (json \ SCHEMA.fFloor).as[String] + if (!pds.db.floorHasFingerprints(buid, floor)) return RESPONSE.BAD_NO_FINGERPRINTS val timestampX = (json \ SCHEMA.fTimestampX).as[String] val timestampY = (json \ SCHEMA.fTimestampY).as[String] val x = (json \ SCHEMA.fX).as[Int] @@ -313,7 +322,6 @@ class HeatmapController @Inject()(cc: ControllerComponents, case e: DatasourceException => return RESPONSE.ERROR(e) } } - inner(request) } diff --git a/server/app/controllers/MapCampusController.scala b/server/app/controllers/MapCampusController.scala index 2630c83de..cc6be93c8 100644 --- a/server/app/controllers/MapCampusController.scala +++ b/server/app/controllers/MapCampusController.scala @@ -247,7 +247,8 @@ class MapCampusController @Inject()(cc: ControllerComponents, case e: DatasourceException => return RESPONSE.ERROR(e) } - return RESPONSE.OK("Successfully deleted everything related to building.") + + RESPONSE.OK("Deleted indoor campus (cascading).") } inner(request) diff --git a/server/app/controllers/MapFloorplanController.scala b/server/app/controllers/MapFloorplanController.scala index 900b7caf1..bb090e674 100644 --- a/server/app/controllers/MapFloorplanController.scala +++ b/server/app/controllers/MapFloorplanController.scala @@ -1,5 +1,6 @@ package controllers +import controllers.FloorplanSettings.MIN_ZOOM_UPLOAD import datasources.{DatasourceException, ProxyDataSource, SCHEMA} import models._ import models.oauth.OAuth2Request @@ -13,6 +14,12 @@ import java.io._ import java.util import javax.inject.{Inject, Singleton} import scala.jdk.CollectionConverters.CollectionHasAsScala + +object FloorplanSettings { + /** It no longer affects upload quality (fixed JS), but it might affect accuracy. */ + var MIN_ZOOM_UPLOAD=18 +} + @Singleton class MapFloorplanController @Inject()(cc: ControllerComponents, tilerHelper: AnyPlaceTilerHelper, @@ -152,15 +159,15 @@ class MapFloorplanController @Inject()(cc: ControllerComponents, * @param floorNum * @return */ - def getAllBase64(buid: String, floorNum: String): Action[AnyContent] = Action { + def getAllBase64(buid: String, requestedFloors: String): Action[AnyContent] = Action { implicit request => def inner(request: Request[AnyContent]): Result = { val anyReq = new OAuth2Request(request) if (!anyReq.assertJsonBody()) return RESPONSE.BAD(RESPONSE.ERROR_JSON_PARSE) val json = anyReq.getJsonBody() - LOG.D2("Floorplan: getAllBase64: " + Utils.stripJsValueStr(json) + " " + floorNum) - val floors = floorNum.split(" ") + LOG.D2("Floorplan: getAllBase64: " + Utils.stripJsValueStr(json) + " " + requestedFloors) + val floors = requestedFloors.split(" ") val all_floors = new util.ArrayList[String] var z = 0 while (z < floors.length) { @@ -174,20 +181,21 @@ class MapFloorplanController @Inject()(cc: ControllerComponents, all_floors.add(s) } catch { case _: IOException => - return RESPONSE.BAD("Requested floorplan cannot be encoded in base64 properly: " + floors(z)) + return RESPONSE.BAD("Cannot encode floorplan: " + floors(z)) } catch { - case _: Exception => - return RESPONSE.ERROR_INTERNAL("Unknown server error during floorplan delivery.") + case e: Exception => + return RESPONSE.ERROR_INTERNAL("Error while getting floorplans: " + requestedFloors +" : " + + e.getMessage) } z += 1 } val res: JsValue = Json.obj("all_floors" -> all_floors.asScala) - try + try { RESPONSE.gzipJsonOk(res.toString) - catch { + } catch { case _: IOException => - return RESPONSE.OK(res, "Successfully retrieved all floors.") + RESPONSE.OK(res, "Floors retrieved.") } } @@ -286,9 +294,8 @@ class MapFloorplanController @Inject()(cc: ControllerComponents, if (checkRequirements != null) return checkRequirements val buid = (json \ SCHEMA.fBuid).as[String] val zoom = (json \ SCHEMA.fZoom).as[String] - val zoom_number = zoom.toInt - if (zoom_number < 20) - return RESPONSE.BAD("You have provided zoom level " + zoom + ". You have to zoom at least to level 20 to upload the floorplan.") + if (zoom.toInt < MIN_ZOOM_UPLOAD) return RESPONSE.BAD_FLOORPLAN_ZOOM_LEVEL(zoom) + val floorNum = (json \ SCHEMA.fFloorNumber).as[String] val bottom_left_lat = (json \ SCHEMA.fLatBottomLeft).as[String] val bottom_left_lng = (json \ SCHEMA.fLonBottomLeft).as[String] @@ -304,26 +311,27 @@ class MapFloorplanController @Inject()(cc: ControllerComponents, storedFloor = storedFloor.as[JsObject] + (SCHEMA.fLatTopRight -> JsString(top_right_lat)) storedFloor = storedFloor.as[JsObject] + (SCHEMA.fLonTopRight -> JsString(top_right_lng)) if (!pds.db.replaceJsonDocument(SCHEMA.cFloorplans, SCHEMA.fFuid, fuid, storedFloor.toString)) { - return RESPONSE.BAD("floorplan could not be updated in the database.") + return RESPONSE.BAD("Could not update floorplan.") } } catch { - case e: DatasourceException => return RESPONSE.ERROR_INTERNAL("Error while reading from our backend service.") + case _: DatasourceException => return RESPONSE.ERROR_INTERNAL("Error while reading from backend.") } var floor_file: File = null try { floor_file = tilerHelper.storeFloorPlanToServer(buid, floorNum, floorplan.ref.path.toFile) } catch { - case e: AnyPlaceException => return RESPONSE.BAD("Cannot save floorplan on the server.") + case _: AnyPlaceException => return RESPONSE.BAD("Cannot save floorplan.") } val top_left_lat = top_right_lat val top_left_lng = bottom_left_lng try { tilerHelper.tileImageWithZoom(floor_file, top_left_lat, top_left_lng, zoom) } catch { - case _: AnyPlaceException => return RESPONSE.BAD("Could not create floorplan tiles on the server.") + case _: AnyPlaceException => return RESPONSE.BAD("Cannot create floorplan tiles.") } LOG.I("Successfully tiled: " + floor_file.toString) - return RESPONSE.OK("Successfully updated floorplan.") + + RESPONSE.OK("Uploaded floorplan.") } inner(request) diff --git a/server/app/controllers/MapPoiConnectionController.scala b/server/app/controllers/MapPoiConnectionController.scala index ea7a777a8..acc7efb99 100644 --- a/server/app/controllers/MapPoiConnectionController.scala +++ b/server/app/controllers/MapPoiConnectionController.scala @@ -14,6 +14,7 @@ import java.text.{NumberFormat, ParseException} import java.util.Locale import javax.inject.{Inject, Singleton} import scala.jdk.CollectionConverters.CollectionHasAsScala + @Singleton class MapPoiConnectionController @Inject()(cc: ControllerComponents, pds: ProxyDataSource, @@ -151,13 +152,12 @@ class MapPoiConnectionController @Inject()(cc: ControllerComponents, def delete(): Action[AnyContent] = Action { implicit request => def inner(request: Request[AnyContent]): Result = { - + LOG.D2("PoiConnection: delete") val anyReq = new OAuth2Request(request) val apiKey = anyReq.getAccessToken() if (apiKey == null) return anyReq.NO_ACCESS_TOKEN() if (!anyReq.assertJsonBody()) return RESPONSE.BAD(RESPONSE.ERROR_JSON_PARSE) var json = anyReq.getJsonBody() - LOG.D2("PoiConnection: delete: " + Utils.stripJsValueStr(json)) val checkRequirements = VALIDATE.checkRequirements(json, SCHEMA.fPoisA, SCHEMA.fPoisB, SCHEMA.fBuidA, SCHEMA.fBuidB) if (checkRequirements != null) return checkRequirements val owner_id = user.authorize(apiKey) @@ -300,4 +300,4 @@ class MapPoiConnectionController @Inject()(cc: ControllerComponents, } GeoPoint.getDistanceBetweenPoints(lat_a, lon_a, lat_b, lon_b, "K") } -} \ No newline at end of file +} diff --git a/server/app/controllers/MapSpaceController.scala b/server/app/controllers/MapSpaceController.scala index da8aa8ab7..9210043dc 100644 --- a/server/app/controllers/MapSpaceController.scala +++ b/server/app/controllers/MapSpaceController.scala @@ -264,8 +264,8 @@ class MapSpaceController @Inject()(cc: ControllerComponents, if (!anyReq.assertJsonBody()) return RESPONSE.BAD(RESPONSE.ERROR_JSON_PARSE) val json = anyReq.getJsonBody() LOG.D2("spaceGetOne: " + Utils.stripJsValueStr(json)) - val checkRequirements = VALIDATE.checkRequirements(json, SCHEMA.fBuid) - if (checkRequirements != null) return checkRequirements + val check = VALIDATE.checkRequirements(json, SCHEMA.fBuid) + if (check!= null) return check val buid = (json \ SCHEMA.fBuid).as[String] try { var space = pds.db.getFromKeyAsJson(SCHEMA.cSpaces, SCHEMA.fBuid, buid) @@ -276,14 +276,14 @@ class MapSpaceController @Inject()(cc: ControllerComponents, (space \ SCHEMA.fName) != JsDefined(JsNull) && (space \ SCHEMA.fDescription) != JsDefined(JsNull)) { space = space.as[JsObject] - SCHEMA.fOwnerId - SCHEMA.fCoOwners - SCHEMA.fId - SCHEMA.fSchema - val res: JsValue = Json.obj("space" -> space) try { - return RESPONSE.gzipJsonOk(res.toString) + return RESPONSE.gzipJsonOk(space.toString) } catch { - case ioe: IOException => return RESPONSE.OK(res, "Successfully retrieved the space!") + case _: IOException => return RESPONSE.OK(space, "Space retrieved.") } } - return RESPONSE.NOT_FOUND("Space not found.") + + RESPONSE.NOT_FOUND("Space not found.") } catch { case e: DatasourceException => return RESPONSE.ERROR(e) } @@ -301,7 +301,7 @@ class MapSpaceController @Inject()(cc: ControllerComponents, if (!anyReq.assertJsonBody()) return RESPONSE.BAD(RESPONSE.ERROR_JSON_PARSE) val json = anyReq.getJsonBody() - LOG.D2("spaceAccessible: " + Utils.stripJsValueStr(json)) + LOG.D2("userAccessible: " + Utils.stripJsValueStr(json)) val checkRequirements = VALIDATE.checkRequirements(json) // , SCHEMA.fAccessToken if (checkRequirements != null) return checkRequirements @@ -314,7 +314,7 @@ class MapSpaceController @Inject()(cc: ControllerComponents, try { RESPONSE.gzipJsonOk(res.toString) } catch { - case ioe: IOException => return RESPONSE.OK(res, "Successfully retrieved all spaces.") + case ioe: IOException => return RESPONSE.OK(res, "Retrieved user spaces.") } } catch { case e: DatasourceException => return RESPONSE.ERROR(e) @@ -415,10 +415,10 @@ class MapSpaceController @Inject()(cc: ControllerComponents, try { RESPONSE.gzipJsonOk(res.toString) } catch { - case ioe: IOException => return RESPONSE.OK(res, "Successfully retrieved all spaces near your position!") + case _: IOException => RESPONSE.OK(res, "Retrieved all spaces near user position") } } catch { - case e: DatasourceException => return RESPONSE.ERROR(e) + case e: DatasourceException => RESPONSE.ERROR(e) } } diff --git a/server/app/controllers/RadiomapController.scala b/server/app/controllers/RadiomapController.scala index 5198a32c6..339543d2d 100644 --- a/server/app/controllers/RadiomapController.scala +++ b/server/app/controllers/RadiomapController.scala @@ -393,27 +393,26 @@ class RadiomapController @Inject()(cc: ControllerComponents, def inner(request: Request[AnyContent]): Result = { val anyReq = new OAuth2Request(request) - if (!anyReq.assertJsonBody()) { - return RESPONSE.BAD(RESPONSE.ERROR_JSON_PARSE) - } + if (!anyReq.assertJsonBody()) { return RESPONSE.BAD(RESPONSE.ERROR_JSON_PARSE) } + val json = anyReq.getJsonBody() - LOG.D2("radioDownloadByBuildingFloorall: " + json.toString) - val checkRequirements = VALIDATE.checkRequirements(json, SCHEMA.fFloor, SCHEMA.fBuid) + LOG.D2("getByFloorsAll: " + json.toString) + val checkRequirements = VALIDATE.checkRequirements(json, SCHEMA.fFloors, SCHEMA.fBuid) if (checkRequirements != null) return checkRequirements - val floor_number = (json \ SCHEMA.fFloor).as[String] + val floorNumbers = (json \ SCHEMA.fFloors).as[String] val buid = (json \ SCHEMA.fBuid).as[String] - val floors = floor_number.split(" ") + val floors = floorNumbers.split(" ") val radiomap_mean_filename = new java.util.ArrayList[String]() val rss_log_files = new java.util.ArrayList[String]() - for (floor_number <- floors) { - val rmapDir = fu.getDirFrozenFloor(buid, floor_number) - val radiomapFile = fu.getRadiomapFile(buid, floor_number) - val meanFile = fu.getMeanFile(buid, floor_number) + for (floorNum <- floors) { + val rmapDir = fu.getDirFrozenFloor(buid, floorNum) + val radiomapFile = fu.getRadiomapFile(buid, floorNum) + val meanFile = fu.getMeanFile(buid, floorNum) if (rmapDir.exists() && radiomapFile.exists() && meanFile.exists()) { try { val folder = rmapDir.toString - val radiomap_filename = fu.getRadioMapFileName(buid, floor_number).getAbsolutePath + val radiomap_filename = fu.getRadioMapFileName(buid, floorNum).getAbsolutePath var radiomap_mean_filename = radiomap_filename.replace(".txt", "-mean.txt") var radiomap_rbf_weights_filename = radiomap_filename.replace(".txt", "-weights.txt") var radiomap_parameters_filename = radiomap_filename.replace(".txt", "-parameters.txt") @@ -430,7 +429,7 @@ class RadiomapController @Inject()(cc: ControllerComponents, } if (!rmapDir.exists()) if (!rmapDir.mkdirs()) { - return RESPONSE.ERROR_INTERNAL("Error while creating Radio Map on-the-fly.") + return RESPONSE.ERROR_INTERNAL("getByFloorsAll: Can't create radiomap on-the-fly.") } val radio = new File(rmapDir.getAbsolutePath + api.sep + "rss-log") var fout: FileOutputStream = null @@ -438,31 +437,30 @@ class RadiomapController @Inject()(cc: ControllerComponents, fout = new FileOutputStream(radio) } catch { case e: FileNotFoundException => return RESPONSE.ERROR_INTERNAL( - "Cannot create radiomap:3: " + e.getMessage) + "getByFloorsAll: Cannot create radiomap: " + e.getMessage) } var floorFetched: Long = 0L try { - floorFetched = pds.db.dumpRssLogEntriesByBuildingFloor(fout, buid, floor_number) + floorFetched = pds.db.dumpRssLogEntriesByBuildingFloor(fout, buid, floorNum) try { fout.close() } catch { - case e: IOException => LOG.E("Error while closing the file output stream for the dumped rss logs") + case _: IOException => LOG.E("Error while closing the file output stream for the dumped rss logs") } } catch { case e: DatasourceException => return RESPONSE.ERROR_INTERNAL("500: " + e.getMessage) } if (floorFetched != 0) { - try { val folder = rmapDir.toString - val radiomap_filename = fu.getRadioMapFileName(buid, floor_number).getAbsolutePath + val radiomap_filename = fu.getRadioMapFileName(buid, floorNum).getAbsolutePath var radiomap_mean_filename = radiomap_filename.replace(".txt", "-mean.txt") var radiomap_rbf_weights_filename = radiomap_filename.replace(".txt", "-weights.txt") var radiomap_parameters_filename = radiomap_filename.replace(".txt", "-parameters.txt") val rm = new RadioMap(new File(folder), radiomap_filename, "", -110) val resCreate = rm.createRadioMap() if (resCreate != null) { - return RESPONSE.ERROR_INTERNAL("radioDownloadByBuildingFloorall: Error: on-the-fly radioMap: " + resCreate) + return RESPONSE.ERROR_INTERNAL("getByFloorsAll: Error: on-the-fly radioMap: " + resCreate) } val url = api.SERVER_API_ROOT var pos = fu.getFilePos(radiomap_mean_filename) @@ -472,12 +470,12 @@ class RadiomapController @Inject()(cc: ControllerComponents, pos = fu.getFilePos(radiomap_parameters_filename) radiomap_parameters_filename = url + radiomap_parameters_filename.substring(pos) } catch { - case e: Exception => return RESPONSE.ERROR_INTERNAL("Error while creating Radio Map on-the-fly! : " + e.getMessage) + case e: Exception => return RESPONSE.ERROR_INTERNAL("Cant create radiomap on-the-fly: " + e.getMessage) } val source = scala.io.Source.fromFile(rmapDir.getAbsolutePath + api.sep + "indoor-radiomap.txt") val lines = try source.mkString finally source.close() - radiomap_mean_filename.add(floor_number) + radiomap_mean_filename.add(floorNum) rss_log_files.add(lines) } else { @@ -485,9 +483,11 @@ class RadiomapController @Inject()(cc: ControllerComponents, } } - val res: JsValue = Json.obj("map_url_mean" -> radiomap_mean_filename.asScala, + val res: JsValue = Json.obj( + "map_url_mean" -> radiomap_mean_filename.asScala, "rss_log_files" -> rss_log_files.asScala) - return RESPONSE.OK(res, "Successfully served radio map.") + + RESPONSE.OK(res, "Successfully served radio map.") } inner(request) @@ -516,22 +516,20 @@ class RadiomapController @Inject()(cc: ControllerComponents, inner(request) } - def getFrozen(building: String, floor: String, fileName: String): Action[AnyContent] = Action { + def getFrozen(space: String, floor: String, fileName: String): Action[AnyContent] = Action { def inner(): Result = { val radioMapsFrozenDir = conf.get[String]("radioMapFrozenDir") - val filePath = radioMapsFrozenDir + api.sep + building + api.sep + - floor + - api.sep + - fileName - LOG.D2("serveFrozenRadioMap: requested: " + filePath) + val S = api.sep + val filePath = radioMapsFrozenDir + S + space + S + floor + S + fileName + LOG.D2("getFrozen: requested: " + filePath) val file = new File(filePath) try { - if (!file.exists()) return RESPONSE.BAD("Requested file does not exist"); - if (!file.canRead()) return RESPONSE.BAD("Requested file cannot be read: " + - fileName) + if (!file.exists()) return RESPONSE.BAD("Requested file does not exist") + if (!file.canRead) return RESPONSE.BAD("Requested file cannot be read: " + fileName) + Ok.sendFile(file) } catch { - case e: FileNotFoundException => return RESPONSE.ERROR_INTERNAL("500: " + e.getMessage) + case e: FileNotFoundException => RESPONSE.ERROR_INTERNAL("500: " + e.getMessage) } } diff --git a/server/app/datasources/IDatasource.scala b/server/app/datasources/IDatasource.scala index ab4a119a3..5232189e1 100644 --- a/server/app/datasources/IDatasource.scala +++ b/server/app/datasources/IDatasource.scala @@ -109,6 +109,7 @@ trait IDatasource { def getRadioHeatmapByBuildingFloorAverage1(buid: String, floor: String): List[JsValue] def getRadioHeatmapByBuildingFloorAverage2(buid: String, floor: String): List[JsValue] + /** * if heatmap do not exist, it creates them (heatmapWifi3). * @@ -173,4 +174,7 @@ trait IDatasource { def generateHeatmaps(): Boolean def isAdmin(): Boolean def deleteAllByXsYs(buid: String, floor: String, x: String, y: String): java.util.List[String] + + // Helper methods + def floorHasFingerprints(buid: String, floor: String): Boolean } diff --git a/server/app/datasources/MongodbDatasource.scala b/server/app/datasources/MongodbDatasource.scala index 0288a0684..5fae8f4e7 100644 --- a/server/app/datasources/MongodbDatasource.scala +++ b/server/app/datasources/MongodbDatasource.scala @@ -590,6 +590,7 @@ class MongodbDatasource @Inject() () extends IDatasource { } override def addJson(col: String, json: JsValue): Boolean = { + LOG.D5("addJson") var finalJson = json if ((json \ SCHEMA.fSchema).toOption.isEmpty) { finalJson = json.as[JsObject] + (SCHEMA.fSchema -> JsNumber(SCHEMA.VERSION)) @@ -599,10 +600,8 @@ class MongodbDatasource @Inject() () extends IDatasource { val addJson = collection.insertOne(Document.apply(document)) val awaited = Await.result(addJson.toFuture(), Duration.Inf) val res = awaited - if (res.toString == "The operation completed successfully") - true - else - false + + res.getInsertedId != null } override def fingerprintExists(col: String, buid: String, floor: String, x: String, y: String, heading: String): Boolean = { @@ -1001,8 +1000,7 @@ class MongodbDatasource @Inject() () extends IDatasource { LOG.D("size = " + foundHeatmaps.size) if (foundHeatmaps.size == 0) { foundHeatmaps = generateHeatmapsOnFly(SCHEMA.cHeatmapWifi1, buid, floor, query, 1, false) - if (foundHeatmaps == null) - return null + if (foundHeatmaps == null) return null } val heatmaps = new util.ArrayList[JsValue]() @@ -1162,6 +1160,17 @@ class MongodbDatasource @Inject() () extends IDatasource { return null } + def floorHasFingerprints(buid: String, floor: String): Boolean = { + LOG.I("onRequestCreateHeatmaps") + val collection = mdb.getCollection(SCHEMA.cFingerprintsWifi) + val query: BsonDocument = BsonDocument(SCHEMA.fBuid -> buid, SCHEMA.fFloor -> floor) + val fingerprintsLookup = collection.find(query) + val awaited = Await.result(fingerprintsLookup.toFuture(), Duration.Inf) + val res = awaited.toList + + res.nonEmpty + } + /** * Creates heatmaps and stores them in a collection according to the zoom level. * @@ -1180,7 +1189,7 @@ class MongodbDatasource @Inject() () extends IDatasource { val res = awaited.toList val fingerprints = convertJson(res) if (fingerprints.size == 0) { - LOG.I("No fingerprints in the building.Can't generate heatmaps.") + LOG.I("Space has not fingerprints.") return false } diff --git a/server/app/datasources/ProxyDataSource.scala b/server/app/datasources/ProxyDataSource.scala index 54c0cf2b1..8afb226d3 100644 --- a/server/app/datasources/ProxyDataSource.scala +++ b/server/app/datasources/ProxyDataSource.scala @@ -87,6 +87,11 @@ class ProxyDataSource @Inject() (conf: Configuration) extends IDatasource { activeDB.deleteFromKey(col, key, value) } + def floorHasFingerprints(buid: String, floor: String): Boolean = { + checkHasActiveDB() + activeDB.floorHasFingerprints(buid, floor) + } + override def getFromKey(collection:String, key: String, value: String):JsValue = { checkHasActiveDB() activeDB.getFromKey(collection, key, value) diff --git a/server/app/datasources/SCHEMA.scala b/server/app/datasources/SCHEMA.scala index 1b10c5091..368952f89 100644 --- a/server/app/datasources/SCHEMA.scala +++ b/server/app/datasources/SCHEMA.scala @@ -42,6 +42,7 @@ object SCHEMA { val fEmail = "email" val fExternal = "external" val fFloor = "floor" + val fFloors = "floors" // separated by space: e.g. 0 -1 1 val fFloorA = "floor_a" val fFloorB = "floor_b" val fFloorName = "floor_name" diff --git a/server/app/utils/AnyPlaceTilerHelper.scala b/server/app/utils/AnyPlaceTilerHelper.scala index ab7d5d18c..c4f7b9d32 100644 --- a/server/app/utils/AnyPlaceTilerHelper.scala +++ b/server/app/utils/AnyPlaceTilerHelper.scala @@ -78,17 +78,22 @@ class AnyPlaceTilerHelper @Inject()(cc: ControllerComponents, * @return */ def storeFloorPlanToServer(buid: String, floor_number: String, file: File): File = { + LOG.D2("storeFloorPlanToServer") val dirS = getRootFloorPlansDirFor(buid, floor_number) val dir = new File(dirS) + + LOG.D3("storeFloorPlanToServer: dir: " + dir.getAbsolutePath) + LOG.D3("storeFloorPlanToServer: file: " + file.getAbsolutePath) + LOG.D3("storeFloorPlanToServer: file size: " + file.length()) dir.mkdirs() - if (!dir.isDirectory || !dir.canWrite() || !dir.canExecute()) { + if (!dir.isDirectory || !dir.canWrite || !dir.canExecute) { throw new AnyPlaceException("Floor plans directory is inaccessible!") } val name = "fl" + "_" + floor_number val dest_f = new File(dir, name) var fout: FileOutputStream = null fout = new FileOutputStream(dest_f) - Files.copy(file.toPath(), fout) + Files.copy(file.toPath, fout) fout.close() dest_f } @@ -145,16 +150,14 @@ class AnyPlaceTilerHelper @Inject()(cc: ControllerComponents, } def tileImageWithZoom(imageFile: File, lat: String, lng: String, zoom:String): Boolean = { - if (!imageFile.isFile || !imageFile.canRead()) { - return false - } + if (!imageFile.isFile || !imageFile.canRead) { return false } + val imageDir = imageFile.getParentFile - if (!imageDir.isDirectory || !imageDir.canWrite() || !imageDir.canRead()) { + if (!imageDir.isDirectory || !imageDir.canWrite || !imageDir.canRead) { throw new AnyPlaceException("Server do not have the permissions to tile the passed argument[" + - imageFile.toString + - "]") + imageFile.toString + "]") } - val pb = new ProcessBuilder(getTilerScriptStart(), imageFile.getAbsolutePath().toString(), lat, + val pb = new ProcessBuilder(getTilerScriptStart(), imageFile.getAbsolutePath, lat, lng,"-DISLOG",zoom) val log = new File(imageDir, "anyplace_tiler_" + imageFile.getName + ".log") pb.redirectErrorStream(true) diff --git a/server/app/utils/RESPONSE.scala b/server/app/utils/RESPONSE.scala index 8df2543ec..92cf76327 100644 --- a/server/app/utils/RESPONSE.scala +++ b/server/app/utils/RESPONSE.scala @@ -35,12 +35,15 @@ */ package utils +import controllers.FloorplanSettings.MIN_ZOOM_UPLOAD + import java.util import play.api.libs.json.Json import play.api.libs.json.{JsNumber, JsObject, JsString, JsValue} import play.api.mvc.Results.Ok import play.api.mvc._ import utils.RESPONSE.Response.Response + import scala.jdk.CollectionConverters.CollectionHasAsScala import scala.language.implicitConversions @@ -136,6 +139,7 @@ object RESPONSE { def BAD_CANNOT_ADD_CONNECTION: Result = BAD_CANNOT_ADD("Connection") def BAD_CANNOT_ADD_FLOOR: Result = BAD_CANNOT_ADD("Floor") + def BAD_NO_FINGERPRINTS: Result = BAD("No fingerprints yet.") def BAD_CANNOT_RETRIEVE(str: String): Result = BAD("Cannot retrieve " + str) def BAD_CANNOT_RETRIEVE_SPACE: Result = BAD_CANNOT_RETRIEVE("Space") def BAD_CANNOT_RETRIEVE_CAMPUS: Result = BAD_CANNOT_RETRIEVE("Campus") @@ -147,6 +151,7 @@ object RESPONSE { def BAD_CANNOT_READ(element: String, floorNum: String): Result = BAD("Cannot read "+ element +": " + floorNum) def BAD_CANNOT_READ_FLOORPLAN(floorNum: String): Result = BAD_CANNOT_READ("Floorplan", floorNum) + def BAD_FLOORPLAN_ZOOM_LEVEL(z: String): Result = BAD("Minimum zoom level is "+ MIN_ZOOM_UPLOAD +". Provided: " + z) private def prettyException(e: Exception): String = s"500: ${e.getClass}: ${e.getMessage}" diff --git a/server/conf/api.routes b/server/conf/api.routes index ebadfa28b..75cd6c417 100644 --- a/server/conf/api.routes +++ b/server/conf/api.routes @@ -20,9 +20,6 @@ GET /api/version controllers.MainCont ################ ### USERS ################ -# TODO:NN /developers object responses/inputs, and mention `deleteCachedDocuments` in a few places.. -# {"name":"Alang Turning","owner_id":"123_google","external":"google","type":"user","access_token":"apGoogle_ABC123ap", -# "_schema":0,"status":"success","message":"User Exists.","status_code":200} ### # tags: @@ -31,9 +28,9 @@ GET /api/version controllers.MainCont # description: |- # Registers a local user. # +# Google login is also available. +# See endpoint: [/api/user/login/google](/#/User/loginGoogle) # -# One could use an external account (Google) to use the service. -# For those see endpoint XXX. # operationId: userRegister # consumes: # - application/json @@ -157,7 +154,7 @@ POST /api/user/login/google controllers.UserCont ### # tags: -# - User +# - User:Admin # summary: Update user # description: | # Provide a `user_id` and at least one optional of the sample parameters to update a user. @@ -492,50 +489,190 @@ POST /api/auth/mapping/floor/add controllers.MapFloor ### POST /api/auth/mapping/floor/delete controllers.MapFloorController.delete() -# TODO:NN /developers put others from routes here as well (and rename) -# `ag` on the master branch to see if the others are used. -# PROBABLY this is how viewer gets its floorplans. Maybe the android apps as well.. -# TAG: SPACE:FLOORPLAN - ################ # Floorplan ################ -# TODO:NN fill the rest endpoints ### # tags: # - Space:Floorplan -# summary: "" +# summary: "Download a floorplan in base64" # description: "" # operationId: floorplan64BuidFloor # consumes: # - application/json # produces: # - application/json +# parameters: +# - in: body +# name: Body +# description: Empty json +# required: true +# schema: +# type: object +# responses: +# '200': +# description: Successful operation +### +POST /api/floorplans64/:buid/:floor_number controllers.MapFloorplanController.getBase64(buid: String, floor_number: String) + +### +# tags: +# - Space:Floorplan +# summary: "Download multiple `floors` floorplans in base64" +# description: |- +# Used by the backup operation. +# Floors are space separated: `-1 0 1` +# operationId: floorplan64AllBuidFloor +# consumes: +# - application/json +# produces: +# - application/json +# parameters: +# - in: body +# name: Body +# required: true +# schema: +# type: object +# responses: +# '200': +# description: Successful operation +# examples: +# application/json: | +# { +# "all_floors": [ +# "", +# "" +# ] +# } +### +POST /api/floorplans64/all/:buid/:floors controllers.MapFloorplanController.getAllBase64( buid: String, floors: String ) + +### +# tags: +# - Space:Floorplan +# summary: "Uploads a floorplan" +# description: |- +# Used by architect. +# Zoom level no longer affects the quality (handled in frontend). +# `MIN_ZOOM_UPLOAD` has to be respected though (to ensure that the space +# is accurately placed on the map). +# operationId: floorplanUpload +# consumes: +# - application/json +# produces: +# - application/json +# parameters: +# - in: body +# name: Body +# required: true +# schema: +# type: object ### -POST /api/floorplans64/:buid/:floor_number controllers.MapFloorplanController.getBase64( buid: String, floor_number: String ) -### NoDocs ### -POST /api/floorplans64/all/:buid/:floor_number controllers.MapFloorplanController.getAllBase64( buid: String, floor_number: String ) -# TAG: SPACE:FLOORPLAN -# floor/floorplan/upload POST /api/mapping/floor/floorplan/upload controllers.MapFloorplanController.uploadWithZoom() +### +# tags: +# - Space:Floorplan +# summary: "Get link of a zip containing all the floor tiles" +# description: "" +# operationId: floorplanTilesZipLink +# consumes: +# - application/json +# produces: +# - application/json +# parameters: +# - in: body +# name: Body +# required: true +# schema: +# type: object +### POST /api/floortiles/:buid/:floor_number controllers.MapFloorplanController.getZipLink( buid: String, floor_number: String ) + +### +# tags: +# - Space:Floorplan +# summary: "Download a specific tile file" +# description: "Not sure about that.." +# operationId: floorplanTilesFile +# consumes: +# - application/json +# produces: +# - application/json +# parameters: +# - in: body +# name: Body +# required: true +# schema: +# type: object +### GET /api/floortiles/:buid/:floor_number/*file controllers.MapFloorplanController.getStaticTiles( buid: String, floor_number: String, file: String ) -# USED BY: Android Navigator: + +### +# tags: +# - Space:Floorplan +# summary: "Gets a tile zip per floor?" +# description: "Used by Android Navigator" +# operationId: floorplanTilesZipFloor +# consumes: +# - application/json +# produces: +# - application/json +# parameters: +# - in: body +# name: Body +# required: true +# schema: +# type: object +### POST /api/floortiles/zip/:buid/:floor_number controllers.MapFloorplanController.getTilesZip( buid: String, floor_number: String ) -# USED BY: Web apps -POST /api/radiomaps_frozen/:building/:floor/:filename controllers.RadiomapController.getFrozen(building: String, floor: String, filename: String) -POST /api/radiomaps/:radio_folder/:filename controllers.RadiomapController.get( radio_folder: String, filename: String ) + +### +# tags: +# - Radiomap +# summary: "Gets a frozen radiomap" +# description: "Used by Web apps\nHow about Android though?" +# operationId: radiomapFrozenBuidFlrnumName +# consumes: +# - application/json +# produces: +# - application/json +# parameters: +# - in: body +# name: Body +# required: true +# schema: +# type: object +### +POST /api/radiomaps_frozen/:space/:floor/:filename controllers.RadiomapController.getFrozen(space: String, floor: String, filename: String) +### +# tags: +# - Radiomap +# summary: "Gets a particular radiomap." +# description: "Probably used in a conjuction with another endpoint?\n to know how to build those URLs" +# operationId: radiomapFolderFilename +# consumes: +# - application/json +# produces: +# - application/json +# parameters: +# - in: body +# name: Body +# required: true +# schema: +# type: object +### +POST /api/radiomaps/:radio_folder/:filename controllers.RadiomapController.get(radio_folder: String, filename: String ) ################ -# Campus +# Space:Campus ################ ### # tags: -# - Campus +# - Space:Campus # summary: Get the SpaceSet of a campus # description: "Returns a group of Spaces that belong to a Campus" # operationId: campusGet @@ -558,7 +695,7 @@ POST /api/mapping/campus/get controllers.MapCampu ### # tags: -# - Campus +# - Space:Campus # summary: Add Campus # description: "" # operationId: campusAdd @@ -583,7 +720,7 @@ POST /api/auth/mapping/campus/add controllers.MapCampu ### # tags: -# - Campus +# - Space:Campus # summary: Update Campus # description: | # CHECK: On frontend(js) buildings and greeklish are not set in the form. @@ -609,7 +746,7 @@ POST /api/auth/mapping/campus/update controllers.MapCampu ### # tags: -# - Campus +# - Space:Campus # summary: Delete Campus # description: "" # operationId: campusDelete @@ -634,7 +771,7 @@ POST /api/auth/mapping/campus/delete controllers.MapCampu ### # tags: -# - Campus +# - Space:Campus # summary: Get user's Campus # description: "" # operationId: campusUser @@ -657,6 +794,11 @@ POST /api/auth/mapping/campus/delete controllers.MapCampu ### POST /api/auth/mapping/campus/user controllers.MapCampusController.byOwner() + +################ +# Space:POI +################ + ### # tags: # - Space:POI @@ -703,8 +845,6 @@ POST /api/mapping/pois/floor/all controllers.MapPoiCo ### POST /api/mapping/pois/space/all controllers.MapPoiController.bySpace() -# RENAMED: all_pois -> search - ### # tags: # - Space:POI @@ -1028,7 +1168,27 @@ POST /api/navigation/pois/id controllers.Navigati ### POST /api/position/predictFloorAlgo1 controllers.PositioningController.predictFloorAlgo1() - +### +# tags: +# - Position +# summary: Estimate position. +# description: "Not sure if this is properly implemented" +# operationId: estimatePosition +# consumes: +# - application/json +# produces: +# - application/json +# parameters: +# - in: body +# name: Body +# description: TODO +# required: true +# schema: +# object +# responses: +# '200': +# description: Successful operation +### POST /api/position/estimate controllers.PositioningController.estimatePosition() @@ -1037,18 +1197,13 @@ POST /api/position/estimate controllers.Position ### ACCESS POINTS ################ - -################ -### RADIOMAPS -################ - -# this endpoint has bug. fails to identify access points location - ### # tags: -# - Radiomaps +# - AccessPoint # summary: Get access points location -# description: "These are cached into the collection `accessPointsWifi`" +# description: |- +# These are cached into the collection `accessPointsWifi`. +# This endpoint is buggy. It fails to identify access points location. # operationId: getAPs # consumes: # - application/json @@ -1069,7 +1224,7 @@ POST /api/wifi/access_points/floor controllers.AccessPo ### # tags: -# - Radiomaps +# - AccessPoint # summary: Get access point's manufacturer # description: "" # operationId: APsId @@ -1091,15 +1246,41 @@ POST /api/wifi/access_points/floor controllers.AccessPo POST /api/wifi/access_points/ids controllers.AccessPointController.getIDs() -# TODO TEST:NN TEST:PM test on mobile app +################ +### RADIOMAP +################ +### +# tags: +# - Radiomap +# summary: Upload a radiomap +# description: |- +# TODO docs. Also make specific for radiomap type (CV, WiFi, BLE?) +# TODO require authentication? And test on mobile.. +# operationId: radiomapUpload +# consumes: +# - application/json +# produces: +# - application/json +# parameters: +# - in: body +# name: Body +# description: "" +# required: true +# schema: +# object +# responses: +# '200': +# description: Successful operation +### POST /api/radiomap/upload controllers.RadiomapController.upload() + # TODO TEST:NN TEST:PM test on mobile app ### # tags: -# - Radiomaps +# - Radiomap # summary: Get radiomaps of floor # description: "" # operationId: radiomapFloor @@ -1122,10 +1303,10 @@ POST /api/radiomap/floor controllers.Radiomap ### # tags: -# - Radiomaps -# summary: Get radiomaps of all floors +# - Radiomap +# summary: Get Wi-Fi radiomaps for the given floors # description: "" -# operationId: radiomapFloorAll +# operationId: radiomapFloors # consumes: # - application/json # produces: @@ -1136,16 +1317,16 @@ POST /api/radiomap/floor controllers.Radiomap # description: "" # required: true # schema: -# $ref: '#/definitions/RadiomapFloorAll' +# $ref: '#/definitions/RadiomapFloors' # responses: # '200': # description: Successful operation ### -POST /api/radiomap/floor/all controllers.RadiomapController.getByFloorsAll() +POST /api/radiomap/floors controllers.RadiomapController.getByFloorsAll() ### # tags: -# - Radiomaps +# - Radiomap # summary: Get radiomaps in a bounding box # description: "" # operationId: radiomapFloorBbox @@ -1170,7 +1351,7 @@ POST /api/radiomap/floor/bbox controllers.Radiomap ### # tags: -# - Radiomaps +# - Radiomap # summary: Get radiomaps of a space # description: "" # operationId: radiomapSpace @@ -1193,7 +1374,7 @@ POST /api/radiomap/space controllers.Radiomap ### # tags: -# - Radiomaps +# - Radiomap # summary: Generate time-based heatmaps for all zoom levels # description: "" # operationId: radiomapTime @@ -1216,7 +1397,7 @@ POST /api/radiomap/time controllers.Radiomap ### # tags: -# - Radiomaps +# - Radiomap # summary: Delete radiomaps in a bounding box in a time-span # description: "" # operationId: radiomapDeleteTime @@ -1241,7 +1422,7 @@ POST /api/auth/radiomap/delete/time controllers.Rad ### # tags: -# - Radiomaps +# - Radiomap # summary: Delete radiomaps in a bounding box # description: "" # operationId: radiomapDelete diff --git a/server/conf/app.base.conf b/server/conf/app.base.conf index 575f723a5..247e089cd 100644 --- a/server/conf/app.base.conf +++ b/server/conf/app.base.conf @@ -3,7 +3,7 @@ ### application.name="anyplace" -application.version="4.2.5" +application.version="4.2.6" # modify the amount of information that is printed. See utils.LOG.scala application.debug.level=2 diff --git a/server/conf/swagger.yml b/server/conf/swagger.yml index b5b0203af..ede8da487 100644 --- a/server/conf/swagger.yml +++ b/server/conf/swagger.yml @@ -1,6 +1,19 @@ --- +# INFO: +# - IGNORE warning underline in IntelliJ. This is because this file is complementary to the api.routes docs, +# hence it is missing some fields. swagger: "2.0" schemes: [https, http] +consumes: + - application/json +produces: + - application/json +parameters: + - in: body + name: Body + required: true + schema: + type: object info: title: "Anyplace API" description: | @@ -12,7 +25,7 @@ info: Google sign in is supported. Anyplace specific key is provided for those cases as well. - #### API KEY: + #### API KEY (access_token): Find your key from [/architect](../architect/#tab-user) license: name: "MIT License" @@ -20,36 +33,50 @@ info: contact: email: "anyplace@cs.ucy.ac.cy" -consumes: - - application/json -produces: - - application/json - +# DESCRIPTION OF EACH ENDPOINT CATEGORY tags: - name: "User" - description: "Anyplace accounts (local or a Google account)." + description: "Anyplace account: local or a Google account." + - name: "User:Admin" + description: "Priviledged tasks for `admin` or `moderators`" - name: "Space" - description: "An indoor space (`building` or a `vessel`)." + description: "An indoor space: a `building` or a `vessel`." - name: "Space:Floor" description: "A `floor` within a space." + - name: "Space:Floorplan" + description: "Floorplan images and floor tiles" - name: "Space:POI" description: "A Point of Interest (POI) in a floor." - - name: "Space:Campus" - description: "A group of spaces." - name: "Space:Connection" description: "A route in a building. It joins two connector POIs." + - name: "Space:Campus" + description: "A collection of spaces." + - name: "Radiomap" + description: "Maps constructed from WiFi fingerprints." + - name: "AccessPoint" + description: "Data related to access points, i.e., Wi-Fi routers." - name: "Navigation" description: "Indoor navigation endpoints." + - name: "Position" + description: "Acquiring a user's position." - name: "Heatmap" description: "Heatmap visualization endpoints. These are cached in MongoDB, created on first request." + - name: "Misc" + description: "Miscellaneous functionalities." + +# API KEY DEFINITION securityDefinitions: api_key: - description: "Obtain the API key from /developers.\nAll keys must end with `ap`.\n\n\n" + description: |- + Obtain the API key from [/architect](/architect/#tab-user). + A valid key ends with `ap`. + type: "apiKey" name: "access_token" in: "header" +# EXAMPLE MODELS. It is better to use json directly in api.routes (see some examples there, e.g. /api/user/login) definitions: SpaceId: type: object @@ -636,15 +663,15 @@ definitions: floor_number: type: string example: "1" - RadiomapFloorAll: + RadiomapFloors: type: object properties: buid: type: string example: building_3ae47293-69d1-45ec-96a3-f59f95a70705_1423000957534 - floor: + floors: type: string - example: 0 + example: -1 0 1 RadiomapBbox: type: object properties: diff --git a/server/database/MONGO.GUIDE.md b/server/database/MONGO.GUIDE.md index af89a24c5..45ffb3e0a 100644 --- a/server/database/MONGO.GUIDE.md +++ b/server/database/MONGO.GUIDE.md @@ -97,6 +97,12 @@ filter: { 'name': /George/ } ``` +##### Find in which documents a field exists +FILTER: +```bash +{"space_type":{$exists:true}} + + ##### Find objects within a bounding box: ```bash {geometry: { $geoWithin: { $box: [ [ 33.0, 33.0 ], [ 35.0, 35.0 ] ] } }} @@ -188,7 +194,7 @@ Upon download of mongodb compass mongosh is also download. Can be accessed from #### Switch to a database: -``` +```bash use anyplace ``` @@ -198,10 +204,36 @@ db.users.deleteMany({external: "anyplace"}) ``` ### Delete a cache collection: -``` +```bash db.heatmapWifiTimestamp1.deleteMany() ``` + +#### Rename all fields of a collection: +In the collection spaces of the anyplace collection, it will +rename all objects that have a field `type` to a field `space_type`. + +```bash +use anyplace +db.spaces.update( + {}, + { $rename: { 'type': 'space_type' } }, + { multi: true } +) +``` + +###### Sample output: +We should use a more up-to-date command actually. +```bash + +'DeprecationWarning: Collection.update() is deprecated. Use updateOne, updateMany, or bulkWrite.' +{ acknowledged: true, + insertedId: null, + matchedCount: 4464, + modifiedCount: 4438, + upsertedCount: 0 } +``` + ### Delete a database that only admin as access to: **NOTE:** proceed with caution ``` @@ -213,5 +245,6 @@ use databaseToDelete; db.dropDatabase(); ``` + diff --git a/server/deploy/watchdog.sh b/server/deploy/watchdog.sh index 9732c574c..380601a86 100755 --- a/server/deploy/watchdog.sh +++ b/server/deploy/watchdog.sh @@ -5,7 +5,7 @@ PORT= ./sync.sh watchmedo shell-command \ - --patterns="*.scala;*.js;*.css;*.vue;*.html;*.htm;*.json;*.routes;*.yml" \ + --patterns="*.scala;*.js;*.css;*.vue;*.html;*.htm;*.json;*.routes;*.yml;*.png;*.svg;*.jpg" \ --recursive \ --command="./sync.sh" \ .. diff --git a/server/public/anyplace_architect/app.js b/server/public/anyplace_architect/app.js index a43036775..9ff8dd549 100644 --- a/server/public/anyplace_architect/app.js +++ b/server/public/anyplace_architect/app.js @@ -25,7 +25,11 @@ THE SOFTWARE. */ -var app = angular.module('anyArchitect', ['ngCookies', 'angularjs-dropdown-multiselect', 'ui.bootstrap', 'ui.select', 'ngSanitize']); + +// TODO:PV app.js must be unified in the lib (one for architect, viewer, etc..) + +var app = angular.module('anyArchitect', + ['ngCookies', 'angularjs-dropdown-multiselect', 'ui.bootstrap', 'ui.select', 'ngSanitize']); app.service('GMapService', function () { this.gmap = {}; @@ -208,15 +212,23 @@ app.service('GMapService', function () { google.maps.event.addListener(self.gmap, 'tilesloaded', function(){ // once some tiles are shown, show the maps search box $("#pac-input").fadeIn(500); + $("#pac-input").on('keypress',function(e) { + if(e.which == 13) { + $('#sub_btn').trigger('click'); + var coords = get_coordinates($("#pac-input").val()); + if (coords != null) { + self.gmap.panTo(coords); + self.gmap.setZoom(17); + } + return false; + } + }); }); }); google.maps.event.addListener(self.searchBox, 'places_changed', function () { var places = self.searchBox.getPlaces(); - - if (places.length == 0) { - return; - } + if (places.length == 0) { return; } self.gmap.panTo(places[0].geometry.location); self.gmap.setZoom(17); @@ -233,6 +245,7 @@ app.service('GMapService', function () { app.factory('AnyplaceService', function () { var anyService = {}; + anyService.prevBuilding = undefined; anyService.selectedBuilding = undefined; anyService.selectedFloor = undefined; anyService.selectedPoi = undefined; @@ -247,100 +260,68 @@ app.factory('AnyplaceService', function () { anyService.radioHeatmapRSSTimeMode = false; anyService.alerts = []; - anyService.jsonReq = { - username: 'username', - password: 'password' - }; - anyService.BASE_URL = "https://ap.cs.ucy.ac.cy"; + anyService.jsonReq = { }; - anyService.getBuilding = function () { - return this.selectedBuilding; - }; + anyService.BASE_URL = location.origin; + anyService.VIEWER_URL = location.origin + "/viewer"; - anyService.getCampus = function () { - return this.selectedCampus; - }; + anyService.getBuilding = function () { return this.selectedBuilding; }; + anyService.getCampus = function () { return this.selectedCampus; }; anyService.getBuildingId = function () { - if (!this.selectedBuilding) { - return undefined; - } + if (!this.selectedBuilding) { return undefined; } return this.selectedBuilding.buid; }; anyService.getBuildingName = function () { - if (!this.selectedBuilding) { - return 'N/A'; - } + if (!this.selectedBuilding) { return 'N/A'; } return this.selectedBuilding.name; }; anyService.getCampusName = function () { - if (!this.selectedCampus) { - return 'N/A'; - } + if (!this.selectedCampus) { return 'N/A'; } return this.selectedCampus.name; }; - anyService.getFloor = function () { - return this.selectedFloor; - }; + anyService.getFloor = function () { return this.selectedFloor; }; + anyService.hasSelectedFloor = function () { return this.selectedFloor !== undefined; }; anyService.getFloorNumber = function () { - if (!this.selectedFloor) { - return 'N/A'; - } + if (!this.selectedFloor) { return 'N/A'; } return String(this.selectedFloor.floor_number); }; - anyService.getFloorName = function () { - return this.selectedFloor.floor_name; - }; + anyService.getFloorName = function () {return this.selectedFloor.floor_name; }; + // TODO:PV make this a stack. and pop on ($scope.deleteBuilding). and always select keep the top. + // if empty (after pop), then select using the normal way anyService.setBuilding = function (b) { + this.prevBuilding = this.selectedBuilding; this.selectedBuilding = b; }; - anyService.setFloor = function (f) { - this.selectedFloor = f; - }; - - anyService.addAlert = function (type, msg) { - // this.alerts[0] = ({msg: msg, type: type}); - this.alerts[0] = ({msg: msg, type: type}); - - }; - - anyService.closeAlert = function (index) { - this.alerts.splice(index, 1); - }; + anyService.setFloor = function (f) { this.selectedFloor = f; }; + anyService.addAlert = function (type, msg) { this.alerts[0] = ({msg: msg, type: type}); }; + anyService.closeAlert = function (index) { this.alerts.splice(index, 1); }; anyService.getBuildingViewerUrl = function () { - if (!this.selectedBuilding || !this.selectedBuilding.buid) { - return "N/A"; - } + if (!this.selectedBuilding || !this.selectedBuilding.buid) { return "N/A"; } return this.selectedBuilding.buid; }; anyService.getBuildingViewerUrlEncoded = function () { - if (!this.selectedBuilding || !this.selectedBuilding.buid) { - return "N/A"; - } - return encodeURIComponent("https://ap.cs.ucy.ac.cy/viewer/?buid=" + this.selectedBuilding.buid); + if (!this.selectedBuilding || !this.selectedBuilding.buid) { return "N/A"; } + return encodeURIComponent(anyService.VIEWER_URL+"/?buid=" + this.selectedBuilding.buid); }; anyService.getCampusViewerUrl = function () { - if (!this.selectedCampus || !this.selectedCampus.cuid) { - return "N/A"; - } - return "https://ap.cs.ucy.ac.cy/viewer/?cuid=" + this.selectedCampus.cuid; + if (!this.selectedCampus || !this.selectedCampus.cuid) { return "N/A"; } + return anyService.VIEWER_URL+"/?cuid=" + this.selectedCampus.cuid; }; anyService.getCampusViewerUrlEncoded = function () { - if (!this.selectedCampus || !this.selectedCampus.cuid) { - return "N/A"; - } - return encodeURIComponent("https://ap.cs.ucy.ac.cy/viewer/?cuid=" + this.selectedCampus.cuid); + if (!this.selectedCampus || !this.selectedCampus.cuid) { return "N/A"; } + return encodeURIComponent(anyService.VIEWER_URL+"/viewer/?cuid=" + this.selectedCampus.cuid); }; anyService.setAllPois = function (p) { @@ -354,29 +335,25 @@ app.factory('AnyplaceService', function () { }; anyService.getAllPois = function () { - if (!this.allPois) { - return 'N/A'; - } + if (!this.allPois) { return 'N/A'; } return this.allPois; }; anyService.getAllConnections = function () { - if (!this.allConnections) { - return 'N/A'; - } + if (!this.allConnections) { return 'N/A'; } return this.allConnections; }; anyService.clearAllData = function () { anyService.selectedPoi = undefined; anyService.selectedFloor = undefined; + anyService.prevBuilding = undefined; anyService.selectedBuilding = undefined; anyService.selectedCampus = undefined; anyService.ShowShareProp = undefined; anyService.allPois = {}; anyService.allConnections = {}; }; - return anyService; }); @@ -389,9 +366,7 @@ app.factory('Alerter', function () { app.factory('formDataObject', function () { return function (data, headersGetter) { var formData = new FormData(); - angular.forEach(data, function (value, key) { - formData.append(key, value); - }); + angular.forEach(data, function (value, key) { formData.append(key, value); }); var headers = headersGetter(); delete headers['Content-Type']; @@ -400,15 +375,17 @@ app.factory('formDataObject', function () { }); app.config(['$locationProvider', function ($location) { - //now there won't be a hashbang within URLs for browsers that support HTML5 history - $location.html5Mode(true); + // now there won't be a hashbang within URLs for browsers that support HTML5 history + $location.html5Mode({ + enabled: true, + requireBase: false + }); }]); -//from: https://stackoverflow.com/a/57713216/776345 +// from: https://stackoverflow.com/a/57713216/776345 app.filter('propsFilter', function() { return function(items, props) { var out = []; - if (angular.isArray(items)) { var keys = Object.keys(props); var propCache = {}; @@ -421,34 +398,26 @@ app.filter('propsFilter', function() { items.forEach(function(item) { var itemMatches = false; - for (var i = 0; i < keys.length; i++) { var prop = keys[i]; var text = propCache[props[prop]]; // BUG: not sure what is this for. It doesn't work. - if(prop == null || item[prop] == null) { - continue; - } + if(prop == null || item[prop] == null) { continue; } + if (item[prop].toString().toLowerCase().indexOf(text) !== -1) { itemMatches = true; break; } } - - if (itemMatches) { - out.push(item); - } + if (itemMatches) { out.push(item); } }); - } else { - // Let the output be the input untouched + } else { // Let the output be the input untouched out = items; } - return out; }; }); - app.factory('requestInterceptor', [function () { // Intercepting /api/auth requests and adding in the headers the anyplace access_token var requestInterceptor = { @@ -456,18 +425,13 @@ app.factory('requestInterceptor', [function () { if (config.url !== undefined) { var loggedIn = (app.user != null) if (config.url.startsWith(API.url+"/auth/")) { - if (!loggedIn) LOG.E("ERROR: user not logged in and requested: " + config.url) if (loggedIn) config.headers.access_token = app.user.access_token; - - // CLR:PM - // if (config.data) { if (loggedIn) config.data.access_token = app.user.access_token; } } } return config; } }; - return requestInterceptor; }]); diff --git a/server/public/anyplace_architect/controllers/AlertController.js b/server/public/anyplace_architect/controllers/AlertController.js index e96bee1c7..3c7d8787a 100644 --- a/server/public/anyplace_architect/controllers/AlertController.js +++ b/server/public/anyplace_architect/controllers/AlertController.js @@ -25,12 +25,11 @@ THE SOFTWARE. */ -app.controller("AlertController", ['$rootScope', '$scope', 'AnyplaceService', function ($rootScope, $scope, AnyplaceService) { +app.controller("AlertController", + ['$rootScope', '$scope', 'AnyplaceService', + function ($rootScope, $scope, AnyplaceService) { $scope.anyService = AnyplaceService; - $scope.alerts = AnyplaceService.alerts; - $scope.closeable = true; - }]); \ No newline at end of file diff --git a/server/public/anyplace_architect/controllers/BuildingController.js b/server/public/anyplace_architect/controllers/BuildingController.js index 4adf8f82b..fe353bad6 100644 --- a/server/public/anyplace_architect/controllers/BuildingController.js +++ b/server/public/anyplace_architect/controllers/BuildingController.js @@ -62,7 +62,6 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa $scope.crudTabSelected = 1; - $scope.fetchVersion = function () { var jsonReq = {}; var promise = $scope.anyAPI.version(jsonReq); @@ -101,6 +100,9 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa }; $scope.$on("loggedIn", function (event, mass) { + // BUG: loggedIn event is raised multiple times (3) for some reason, + // even with the code below + LOG.D3("loggedIn: Handled") $scope.getSpacesAccessible(); $scope.getUserCampuses(); }); @@ -135,57 +137,57 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa reader.readAsDataURL(e.target.files[0]); }); - $scope.setLogoPlan = function (cuid) { - - var newFl = { - is_published: 'true', - cuid: cuid, - logo: String($scope.newFloorNumber), - description: String($scope.newFloorNumber), - floor_number: String($scope.newFloorNumber) - }; - - $scope.myFloors[$scope.myFloorId] = newFl; - $scope.myFloorId++; - - // create the proper image inside the canvas - canvasOverlay.drawBoundingCanvas(); - - // create the ground overlay and destroy the canvasOverlay object - // and also set the floor_plan_coords in $scope.data - var bl = canvasOverlay.bottom_left_coords; - var tr = canvasOverlay.top_right_coords; - $scope.data.floor_plan_coords.bottom_left_lat = bl.lat(); - $scope.data.floor_plan_coords.bottom_left_lng = bl.lng(); - $scope.data.floor_plan_coords.top_right_lat = tr.lat(); - $scope.data.floor_plan_coords.top_right_lng = tr.lng(); - $scope.data.floor_plan_coords.zoom = GMapService.gmap.getZoom() + ""; - var data = canvasOverlay.getCanvas().toDataURL("image/png"); // defaults to png - $scope.data.floor_plan_base64_data = data; - var imageBounds = new google.maps.LatLngBounds( - new google.maps.LatLng(bl.lat(), bl.lng()), - new google.maps.LatLng(tr.lat(), tr.lng())); - $scope.data.floor_plan_groundOverlay = new USGSOverlay(imageBounds, data, GMapService.gmap); - - canvasOverlay.setMap(null); // remove the canvas overlay since the groundoverlay is placed - $('#input-floor-plan').prop('disabled', false); - $scope.isCanvasOverlayActive = false; - - if (_floorNoExists($scope.newFloorNumber)) { - for (var i = 0; i < $scope.xFloors.length; i++) { - var f = $scope.xFloors[i]; - if (!LPUtils.isNullOrUndefined(f)) { - if (f.floor_number === String($scope.newFloorNumber)) { - $scope.uploadWithZoom($scope.anyService.selectedBuilding, f, $scope.data); - break; - } - } - } - } else { - $scope.addFloorObject(newFl, $scope.anyService.selectedBuilding, $scope.data); - } - - }; + // CHECK: what is LogoPlan ?!? + // $scope.setLogoPlan = function (cuid) { + // LOG.D("setLogoPlan") + // var newFl = { + // is_published: 'true', + // cuid: cuid, + // logo: String($scope.newFloorNumber), + // description: String($scope.newFloorNumber), + // floor_number: String($scope.newFloorNumber) + // }; + // + // $scope.myFloors[$scope.myFloorId] = newFl; + // $scope.myFloorId++; + // + // // create the proper image inside the canvas + // canvasOverlay.drawBoundingCanvas(); + // + // // create the ground overlay and destroy the canvasOverlay object + // // and also set the floor_plan_coords in $scope.data + // var bl = canvasOverlay.bottom_left_coords; + // var tr = canvasOverlay.top_right_coords; + // $scope.data.floor_plan_coords.bottom_left_lat = bl.lat(); + // $scope.data.floor_plan_coords.bottom_left_lng = bl.lng(); + // $scope.data.floor_plan_coords.top_right_lat = tr.lat(); + // $scope.data.floor_plan_coords.top_right_lng = tr.lng(); + // $scope.data.floor_plan_coords.zoom = GMapService.gmap.getZoom() + ""; + // var data = canvasOverlay.getCanvas().toDataURL("image/png"); // defaults to png + // $scope.data.floor_plan_base64_data = data; + // var imageBounds = new google.maps.LatLngBounds( + // new google.maps.LatLng(bl.lat(), bl.lng()), + // new google.maps.LatLng(tr.lat(), tr.lng())); + // $scope.data.floor_plan_groundOverlay = new USGSOverlay(imageBounds, data, GMapService.gmap); + // + // canvasOverlay.setMap(null); // remove the canvas overlay since the groundoverlay is placed + // $('#input-floor-plan').prop('disabled', false); + // $scope.isCanvasOverlayActive = false; + // + // if (_floorNoExists($scope.newFloorNumber)) { + // for (var i = 0; i < $scope.xFloors.length; i++) { + // var f = $scope.xFloors[i]; + // if (!LPUtils.isNullOrUndefined(f)) { + // if (f.floor_number === String($scope.newFloorNumber)) { + // $scope.uploadWithZoom($scope.anyService.selectedBuilding, f, $scope.data); + // break; + // } + // } + // } + // } else { + // $scope.addFloorObject(newFl, $scope.anyService.selectedBuilding, $scope.data); + // } + // }; $scope.$watch('anyService.selectedBuilding', function (newVal, oldVal) { if (newVal && newVal.coordinates_lat && newVal.coordinates_lon) { @@ -235,10 +237,8 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa localStorage.setItem("lastCampus", newVal.cuid); } } - }); - var _clearBuildingMarkersAndModels = function () { for (var b in $scope.myBuildingsHashT) { if ($scope.myBuildingsHashT.hasOwnProperty(b)) { @@ -288,7 +288,13 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa } else { b.is_published = false; } - var marker = getMapsIconBuildingArchitect(GMapService.gmap, _latLngFromBuilding(b)) + var marker; + if (b.space_type == "vessel") { + marker = getMapsIconVesselArchitect(GMapService.gmap, _latLngFromBuilding(b)) + } else { + marker = getMapsIconBuildingArchitect(GMapService.gmap, _latLngFromBuilding(b)) + } + var htmlContent = '
' + '
'+b.name+'
' + '
BUID:
' @@ -309,17 +315,17 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa infowindow.open(GMapService.gmap, this); var self = this; $scope.$apply(function () { - $scope.anyService.selectedBuilding = self.building; + $scope.anyService.setBuilding(self.building) }); }); } - console.log("Loaded " + $scope.myBuildings.length + " buildings!") + LOG.D("Loaded spaces: " + $scope.myBuildings.length) - // using the latest building form localStorage + // using the latest building from localStorage if (localStoredBuildingIndex >= 0) { - $scope.anyService.selectedBuilding = $scope.myBuildings[localStoredBuildingIndex]; + $scope.anyService.setBuilding($scope.myBuildings[localStoredBuildingIndex]); } else if ($scope.myBuildings[0]) { - $scope.anyService.selectedBuilding = $scope.myBuildings[0]; + $scope.anyService.setBuilding($scope.myBuildings[0]); } // _suc('Successfully fetched buildings.'); @@ -354,13 +360,11 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa if (building.name && building.description && building.is_published && building.url && building.address && building.space_type) { var promise = $scope.anyAPI.addBuilding(building); promise.then( - function (resp) { - // on success + function (resp) { // on success var data = resp.data; - console.log("new buid: " + data.buid); building.buid = data.buid; - if (building.is_published === 'true' || building.is_published == true) { + if (building.is_published === 'true' || building.is_published === true) { building.is_published = true; } else { building.is_published = false; @@ -368,10 +372,18 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa // insert the newly created building inside the loadedBuildings $scope.myBuildings.push(building); - - $scope.anyService.selectedBuilding = $scope.myBuildings[$scope.myBuildings.length - 1]; + $scope.anyService.setBuilding($scope.myBuildings[$scope.myBuildings.length - 1]); $scope.myMarkers[id].marker.setDraggable(false); + if (building.space_type === "vessel") { + var icon_vessel= { + url: IMG_VESSEL_ARCHITECT, + scaledSize: new google.maps.Size(50, 50), // scaled size + origin: new google.maps.Point(0,0), // origin + anchor: new google.maps.Point(0, 0) // anchor + }; + $scope.myMarkers[id].marker.icon = icon_vessel; + } $scope.myBuildingsHashT[building.buid] = { marker: $scope.myMarkers[id].marker, @@ -383,8 +395,7 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa $scope.myMarkers[id].infowindow.close(); } - _suc($scope, "Building added successfully."); - + _info_autohide($scope, "Added " + building.space_type); }, function (resp) { ShowError($scope, resp); @@ -404,9 +415,12 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa promise.then( function (resp) { // on success var data = resp.data; - console.log("building deleted: ", b); - // delete the building from the loadedBuildings - $scope.myBuildingsHashT[b.buid].marker.setMap(null); + // BUG does not disappear. They must be keeping references to the space in multiple places.. + if($scope.myBuildingsHashT[b.buid].marker) { + $scope.myBuildingsHashT[b.buid].marker.setMap(null); + delete $scope.myBuildingsHashT[b.buid].marker; + } + delete $scope.myBuildingsHashT[b.buid]; var bs = $scope.myBuildings; var sz = bs.length; @@ -416,25 +430,22 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa break; } } + // update the selected building - if ($scope.myBuildings && $scope.myBuildings.length > 0) { - $scope.anyService.selectedBuilding = $scope.myBuildings[0]; + if ($scope.myBuildings && $scope.myBuildings.length > 0) { // TODO:PV fix the logic + // $scope.anyService.setBuilding($scope.anyService.prevBuilding); + $scope.anyService.selectedBuilding = undefined; } $scope.setCrudTabSelected(1); - _suc($scope, "Successfully deleted indoor space."); + _info_autohide($scope, "Deleted " + b.space_type + ". Please refresh."); }, function (resp) { ShowError($scope, resp, - "Something went wrong." +"" + - "It's likely that everything related to the indoor space is deleted " + - "but please refresh to make sure or try again.", true) + "Something's wrong. Please refresh. Info:", true) } ); - }; - - $scope.deleteRadiomaps = function () { var jsonReq = {"buid": $scope.anyService.getBuildingId(), "floor": $scope.anyService.getFloorNumber()}; jsonReq.username = $scope.creds.username; @@ -687,9 +698,8 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa $("#draggable-building").draggable({ helper: 'clone', - start: function() { - // BUG: setting the size of the draggable building. - // The first time it does not work for some reason. + create: function(event, ui) { + // Resize as image is larger than the html element $(this).height(50).width(50); }, stop: function (e) { @@ -700,7 +710,6 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa }); $scope.placeMarker = function (location) { - var prevMarker = $scope.myMarkers[$scope.myMarkerId - 1]; if (prevMarker && prevMarker.marker && prevMarker.marker.getMap() && prevMarker.marker.getDraggable()) { @@ -721,11 +730,10 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa var marker = new google.maps.Marker({ position: location, map: GMapService.gmap, - icon: icon_building, + icon: icon_building, // assuming a building draggable: true }); - var infowindow = new google.maps.InfoWindow({ content: '-', maxWidth: 500 @@ -743,6 +751,7 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa bucode: "", type: undefined }; + $scope.myMarkers[marker.myId].marker = marker; $scope.myMarkers[marker.myId].infowindow = infowindow; $scope.myMarkerId++; @@ -750,21 +759,21 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa var htmlContent = '
' + '
' - + '' + + '' + '
' + '
' - + '' + + '' + '
' + '
' - + '' + + '' + '
' + '
' + '' + '
' + '
' - + ' Make building public to view.' + + ' Make space public to view.' + '
' + '
' + '
' @@ -782,7 +791,7 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa + '
'; var htmlContent2 = '
' - + '
Building:
' + + '
Space:
' + '{{myMarkers[' + marker.myId + '].model.name}}' + '
Description:
' + '{{myMarkers[' + marker.myId + '].model.description}}' @@ -1303,7 +1312,7 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa $scope.zip = new JSZip(); $scope.DownloadBackup = function () { - + LOG.D3("DownloadBackup") var b = $scope.anyService.selectedBuilding; var xFloors = []; var jsonReq = AnyplaceService.jsonReq; @@ -1311,25 +1320,26 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa $scope.anyService.progress = 0; var promise = AnyplaceAPIService.allBuildingFloors(jsonReq); promise.then( - function (resp) { + function (resp) { // success + _info_autohide_timeout($scope, "Fetched floors..", 1500) xFloors = resp.data.floors; var floor = 0; - var floor_number = ""; + var floorNumbers = ""; for (var i = 0; i < xFloors.length; i++) { if (i == 0) { - floor_number = xFloors[i].floor_number; + floorNumbers = xFloors[i].floor_number; } else { - floor_number = floor_number + " " + xFloors[i].floor_number; + floorNumbers = floorNumbers + " " + xFloors[i].floor_number; } } $scope.anyService.progress = 10; var buid = b.buid; var jsonReq2 = AnyplaceService.jsonReq; - var promise2 = AnyplaceAPIService.downloadFloorPlanAll(jsonReq2, buid, floor_number); + var promise2 = AnyplaceAPIService.downloadFloorPlanAll(jsonReq2, buid, floorNumbers); promise2.then( - function (resp) { - // on success + function (resp) { // on success + _info_autohide_timeout($scope, "Fetched floorplans..", 1500) var data = resp.data; var img = $scope.zip.folder("floor_plans"); for (var si = 0; si < data.all_floors.length; si++) { @@ -1340,27 +1350,25 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa $scope.anyService.progress = 25; var jsonReq3 = AnyplaceService.jsonReq; jsonReq3.buid = buid; - jsonReq3.floor = floor_number; + jsonReq3.floors = floorNumbers; var promise3 = AnyplaceAPIService.getRadioByBuildingFloorAll(jsonReq3); - promise3.then( - function (resp) { + promise3.then(function (resp) { + _info_autohide_timeout($scope, "Fetched radiomaps..", 1500) var data2 = resp.data; var logs = $scope.zip.folder("radiomaps"); if (data2.rss_log_files) { - var urls = ""; for (var si2 = 0; si2 < data2.rss_log_files.length; si2++) { logs.file(xFloors[si2].floor_number + "-radiomap.txt", data2.rss_log_files[si2]); } } $scope.anyService.progress = 70; $scope.exportPoisBuildingToJson(); - }, - function (resp) { - - } - ); + }, function (resp) { + ShowError($scope, resp, ERR_FETCH_ALL_RADIOMAPS, true); + }); }, function (resp) { + ShowError($scope, resp, ERR_FETCH_ALL_FLOORPLANS, true); } ); }, @@ -1397,28 +1405,20 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa $scope.anyService.progress = 80; if (floors) { for (var i = 0; i < floors.length; i++) { - (function (jreq) { var promise = AnyplaceAPIService.retrievePoisByBuildingFloor(jreq); promise.then( function (resp) { var data = resp.data; - var poisArray = data.pois; - if (poisArray) { - var flPois = []; - if (poisArray[0] != undefined) { var fNo = poisArray[0].floor_number; - for (var j = 0; j < poisArray.length; j++) { var sPoi = poisArray[j]; - if (sPoi.pois_type == "None") { - continue; - } + if (sPoi.pois_type == "None") { continue; } if (sPoi.overwrite) { var tmp = { name: sPoi.name, @@ -1429,8 +1429,7 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa coordinates_lon: sPoi.coordinates_lon, overwrite: sPoi.overwrite }; - } - else { + } else { var tmp = { name: sPoi.name, description: sPoi.description, @@ -1444,20 +1443,16 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa if (sPoi.is_building_entrance == 'true') { tmp.is_building_entrance = 'true'; - } - else { + } else { tmp.is_building_entrance = 'false'; } - flPois.push(tmp); } - resFloors.push( - { + resFloors.push({ floor_number: fNo, pois: flPois - } - ); + }); } count++; if (count === floors.length) { @@ -1471,7 +1466,7 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa }, function (resp) { var data = resp.data; - console.log(data.message); + LOG.E(data.message); }); }({ buid: building.buid, @@ -1481,14 +1476,12 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa } }, function (resp) { - // TODO: alert failure - console.log(resp.data.message); + LOG.E(resp.data.message); // TODO: alert failure } ); }; $scope.exportConnectionBuildingToJson = function () { - var building = $scope.anyService.selectedBuilding; if (LPUtils.isNullOrUndefined(building)) { _err('No building selected'); @@ -1502,39 +1495,29 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa var jsonReq = AnyplaceService.jsonReq; jsonReq.buid = building.buid; - var count = 0; - var promise = AnyplaceAPIService.allBuildingFloors(jsonReq); promise.then( function (resp) { var floors = resp.data.floors; - var resFloors = []; if (floors) { for (var i = 0; i < floors.length; i++) { - (function (jreq) { var promise = AnyplaceAPIService.retrieveConnectionsByBuildingFloor(jreq); promise.then( function (resp) { $scope.anyService.progress = 100; var data = resp.data; - var connArray = data.connections; if (connArray) { - var flConnections = []; - if (connArray[0] != undefined) { - var fNo = connArray[0].floor_a; - for (var j = 0; j < connArray.length; j++) { var sConnection = connArray[j]; - var tmp = { name: sConnection.name, description: sConnection.description, @@ -1548,21 +1531,17 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa buid_a: sConnection.buid_a, buid_b: sConnection.buid_b }; - flConnections.push(tmp); } - resFloors.push( - { + resFloors.push({ floor_number: fNo, connections: flConnections - } - ); + }); } - count++; + count++; if (count === floors.length) { - $scope.Connectionsresult.building.floors = resFloors; $scope.zip.file("allconnections.json", JSON.stringify($scope.Connectionsresult, null, 4)); @@ -1587,7 +1566,7 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa } }, function (resp) { - console.log(resp.data.message); + LOG.E(resp.data.message); } ); }; @@ -1598,7 +1577,6 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa if (typeof(Storage) !== "undefined" && localStorage) { localStorage.setItem('dismissClicked', 'YES'); } - }); if (localStorage.getItem('dismissClicked') !== 'YES') { @@ -1608,5 +1586,4 @@ app.controller('BuildingController', ['$cookieStore', '$scope', '$compile', 'GMa window.onload = showWelcomeMessage; } - }]); diff --git a/server/public/anyplace_architect/controllers/ControlBarController.js b/server/public/anyplace_architect/controllers/ControlBarController.js index 21828e604..b492019fc 100644 --- a/server/public/anyplace_architect/controllers/ControlBarController.js +++ b/server/public/anyplace_architect/controllers/ControlBarController.js @@ -20,8 +20,10 @@ THE SOFTWARE. */ app.controller('ControlBarController', - ['$scope', '$rootScope', 'AnyplaceService', 'GMapService', 'AnyplaceAPIService', - function ($scope, $rootScope, AnyplaceService, GMapService, AnyplaceAPIService) { + ['$scope', '$rootScope', + 'AnyplaceService', 'GMapService', 'AnyplaceAPIService', + function ($scope, $rootScope, + AnyplaceService, GMapService, AnyplaceAPIService) { $scope.anyService = AnyplaceService; $scope.gmapService = GMapService; @@ -43,7 +45,6 @@ app.controller('ControlBarController', } $scope.user = $scope.emptyUser - var self = this; //to be able to reference to it in a callback, you could use $scope instead angular.element(document).ready(function () { @@ -53,20 +54,13 @@ app.controller('ControlBarController', } }); - $scope.setAuthenticated = function (bool) { - $scope.isAuthenticated = bool; - }; - $scope.showFullControls = true; + $scope.setAuthenticated = function (bool) { $scope.isAuthenticated = bool; }; + $scope.toggleFullControls = function () { $scope.showFullControls = !$scope.showFullControls; }; - $scope.toggleFullControls = function () { - $scope.showFullControls = !$scope.showFullControls; - }; - - // // not called - // var apiClientLoaded = function () { - // gapi.client.plus.people.get({userId: 'me'}).execute(handleEmailResponse); - // }; + // not called + // var apiClientLoaded = function () { gapi.client.plus.people.get({userId: 'me'}).execute(handleEmailResponse); }; + // var handleEmailResponse = function (resp) { $scope.personLookUp(resp, googleAuth); }; $scope.copyApiKey = function () { LOG.W("Copying api key") @@ -77,11 +71,6 @@ app.controller('ControlBarController', _info($scope, "API key copied!"); } - // var handleEmailResponse = function (resp) { - // console.log("handleEmailResponse ?"); - // $scope.personLookUp(resp, googleAuth); - // }; - $scope.showGoogleID = function () { if (!$scope.user.google) { return; } AnyplaceService.addAlert('success', 'Google ID is: ' + $scope.user.id); @@ -95,22 +84,20 @@ app.controller('ControlBarController', $scope.onSignIn = function (googleUser) { if ($scope.getCookie("reloadedAfterLogin") === "") { $scope.setCookie("reloadedAfterLogin", "true", 365); + LOG.D2("onSignIn: reloading") location.reload(); } + $scope.setAuthenticated(true); $scope.user = $scope.emptyUser $scope.user.google = {} - var googleAuth = gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse(); LOG.D4("user.google.auth") LOG.D4(googleAuth) - $scope.googleUserLookup(googleUser, googleAuth); }; - $scope.onSignInFailure = function () { - LOG.E('Signin failed'); - }; + $scope.onSignInFailure = function () { LOG.E('Signin failed'); }; window.onSignIn = $scope.onSignIn; window.onSignInFailure = $scope.onSignInFailure; @@ -137,7 +124,6 @@ app.controller('ControlBarController', // google id $scope.user.google.id = $scope.user.google._id + '_' + $scope.user.accountType; - var promise = AnyplaceAPIService.loginGoogle({ name: $scope.user.name, external: "google", @@ -164,7 +150,6 @@ app.controller('ControlBarController', $scope.refreshLocalLogin = function () { LOG.D3("refreshLocalLogin"); - var jsonReq = {}; var cookieAccessToken = $scope.getCookie("localAccessToken"); if (cookieAccessToken === "") { return; } @@ -172,7 +157,6 @@ app.controller('ControlBarController', jsonReq.access_token = cookieAccessToken; LOG.D2("Refreshing local login. token:" + cookieAccessToken); - // if ($scope.getCookie("localAccessToken") === "") { var promise = AnyplaceAPIService.refreshLocalAccount(jsonReq); promise.then( function (resp) { // on success @@ -189,10 +173,7 @@ app.controller('ControlBarController', $scope.user.access_token = data.user.access_token; app.user=$scope.user; $scope.setAuthenticated(true); - - if ($scope.user && $scope.user.id) { - $scope.$broadcast('loggedIn', []); - } + if ($scope.user && $scope.user.id) { $scope.$broadcast('loggedIn', []); } }, function (resp) { ShowError($scope, resp,"Login refresh failed.", true) @@ -218,7 +199,6 @@ app.controller('ControlBarController', $scope.user.id = data.user.owner_id; $scope.user.accountType = "local"; $scope.user.type = data.user.type; - $scope.user.access_token = data.user.access_token; app.user=$scope.user; $scope.setAuthenticated(true); @@ -245,9 +225,7 @@ app.controller('ControlBarController', jsonReq.password = $scope.user.password; var promise = AnyplaceAPIService.registerLocalAccount(jsonReq); - promise.then( - function (resp) { - // on success + promise.then(function (resp) { // on success var data = resp.data; _suc($scope, "Successfully registered!"); }, @@ -264,10 +242,8 @@ app.controller('ControlBarController', $scope.$broadcast('loggedOff', []); $scope.user={}; - clearFingerprintCoverage(); clearFingerprintHeatmap(); - $scope.deleteCookie("reloadedAfterLogin"); $scope.deleteCookie("localAccessToken"); }; @@ -275,7 +251,6 @@ app.controller('ControlBarController', function clearFingerprintCoverage() { var check = 0; if (heatMap[check] !== undefined && heatMap[check] !== null) { - var i = heatMap.length; while (i--) { heatMap[i].rectangle.setMap(null); @@ -323,9 +298,7 @@ app.controller('ControlBarController', document.getElementById("fingerPrints-mode").classList.remove('quickaction-selected'); _HEATMAP_F_IS_ON = false; var i = heatmapFingerprints.length; - while (i--) { - heatmapFingerprints[i] = null; - } + while (i--) { heatmapFingerprints[i] = null; } heatmapFingerprints = []; } } @@ -335,12 +308,8 @@ app.controller('ControlBarController', var ca = document.cookie.split(';'); for (var i = 0; i < ca.length; i++) { var c = ca[i]; - while (c.charAt(0) == ' ') { - c = c.substring(1); - } - if (c.indexOf(name) == 0) { - return c.substring(name.length, c.length); - } + while (c.charAt(0) == ' ') { c = c.substring(1); } + if (c.indexOf(name) == 0) { return c.substring(name.length, c.length); } } return ""; }; @@ -357,14 +326,8 @@ app.controller('ControlBarController', }; $scope.tab = 1; - - $scope.setTab = function (num) { - $scope.tab = num; - }; - - $scope.isTabSet = function (num) { - return $scope.tab === num; - }; + $scope.setTab = function (num) { $scope.tab = num; }; + $scope.isTabSet = function (num) { return $scope.tab === num; }; $scope.isAdmin = function () { if ($scope.user == null) { @@ -386,22 +349,15 @@ app.controller('ControlBarController', } }; - var _err = function (msg) { - $scope.anyService.addAlert('danger', msg); - }; - var myLocMarker = undefined; $scope.userPosition = undefined; var pannedToUserPosOnce = false; $scope.isUserLocVisible = false; - $scope.getIsUserLocVisible = function () { - return $scope.isUserLocVisible; - }; + $scope.getIsUserLocVisible = function () { return $scope.isUserLocVisible; }; $scope.panToUserLocation = function () { - if (!$scope.userPosition) - return; + if (!$scope.userPosition) return; GMapService.gmap.panTo($scope.userPosition); GMapService.gmap.setZoom(20); @@ -420,8 +376,7 @@ app.controller('ControlBarController', return; } var s = new google.maps.Size(20, 20); - if ($scope.isFirefox) - s = new google.maps.Size(48, 48); + if ($scope.isFirefox) s = new google.maps.Size(48, 48); myLocMarker = new google.maps.Marker({ position: posLatlng, @@ -443,28 +398,19 @@ app.controller('ControlBarController', $scope.isUserLocVisible = false; }; - $scope.showUserLocation = function () { - if ($scope.getIsUserLocVisible()) { $scope.hideUserLocation(); - - if (navigator.geolocation) - navigator.geolocation.clearWatch(watchPosNum); - + if (navigator.geolocation) navigator.geolocation.clearWatch(watchPosNum); return; } if (navigator.geolocation) { watchPosNum = navigator.geolocation.watchPosition( function (position) { - var posLatlng = {lat: position.coords.latitude, lng: position.coords.longitude}; - //var radius = position.coords.accuracy; - $scope.userPosition = posLatlng; $scope.displayMyLocMarker(posLatlng); - var infowindow = new google.maps.InfoWindow({ content: 'Your current location.', maxWidth: 500 @@ -509,7 +455,7 @@ app.controller('ControlBarController', $scope.centerViewToSelectedItem = function () { if ($scope.anyService.selectedBuilding == null || $scope.anyService.selectedBuilding == undefined) { - _err($scope, "You have to select a building first"); + _warn_autohide_timeout($scope, "Select a space first", 2000); return; } var position = {}; diff --git a/server/public/anyplace_architect/controllers/FloorController.js b/server/public/anyplace_architect/controllers/FloorController.js index b752c4888..fa31b30c9 100644 --- a/server/public/anyplace_architect/controllers/FloorController.js +++ b/server/public/anyplace_architect/controllers/FloorController.js @@ -25,44 +25,33 @@ THE SOFTWARE. */ - - var changedfloor = false; -app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', 'AnyplaceAPIService', function ($scope, AnyplaceService, GMapService, AnyplaceAPIService) { +app.controller('FloorController', + ['$scope', 'AnyplaceService', 'GMapService', 'AnyplaceAPIService', + function ($scope, AnyplaceService, GMapService, AnyplaceAPIService) { $scope.anyService = AnyplaceService; $scope.anyAPI = AnyplaceAPIService; $scope.gmapService = GMapService; - //$scope.controlBarService = ControlBarController; $scope.xFloors = []; - $scope.myFloors = {}; $scope.myFloorId = 0; - $scope.newFloorNumber = 0; - var heatmap; - + $scope.isUploadingFloorplan = false; + var heatmap; $scope.crudTabSelected = 1; - $scope.setCrudTabSelected = function (n) { - $scope.crudTabSelected = n; - }; - $scope.isCrudTabSelected = function (n) { - - return $scope.crudTabSelected === n; - }; - + $scope.setCrudTabSelected = function (n) { $scope.crudTabSelected = n; }; + $scope.isCrudTabSelected = function (n) { return $scope.crudTabSelected === n; }; $scope.data = { floor_plan_coords: {}, floor_plan_base64_data: {}, - floor_plan_groundOverlay: null + floor_plan_groundOverlay: null, + floorPlanPrevOverlay: null, }; - $scope.$on("loggedOff", function (event, mass) { - _clearFloors(); - }); - + $scope.$on("loggedOff", function (event, mass) { _clearFloors(); }); var _clearFloors = function () { $scope.removeFloorPlan(); $scope.xFloors = []; @@ -80,23 +69,20 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' $scope.$watch('anyService.selectedBuilding', function (newVal, oldVal) { if (newVal) { changedfloor = false; - $scope.fetchAllFloorsForBuilding(newVal); } }); - /** - $scope.$watch('anyService.selectedPoi', function (newVal, oldVal) { - if (newVal && _latLngFromPoi(newVal)) { - $scope.showRadioHeatmapPoi(); - } - }); - */ - $scope.$watch('newFloorNumber', function (newVal, oldVal) { - //if (_floorNoExists(newVal)) { - // _setNextFloor(); - //} - }); + // $scope.$watch('anyService.selectedPoi', function (newVal, oldVal) { + // if (newVal && _latLngFromPoi(newVal)) { + // $scope.showRadioHeatmapPoi(); + // } + // }); + // $scope.$watch('newFloorNumber', function (newVal, oldVal) { + // if (_floorNoExists(newVal)) { + // _setNextFloor(); + // } + // }); var _latLngFromBuilding = function (b) { if (b && b.coordinates_lat && b.coordinates_lon) { @@ -109,36 +95,25 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' }; $scope.$watch('anyService.selectedFloor', function (newVal, oldVal) { - if (newVal !== undefined && newVal !== null && !_.isEqual(newVal, oldVal)) { - $scope.fetchFloorPlanOverlay(newVal); - GMapService.gmap.panTo(_latLngFromBuilding($scope.anyService.selectedBuilding)); - GMapService.gmap.setZoom(19); - - if (typeof(Storage) !== "undefined" && localStorage) { - localStorage.setItem("lastFloor", newVal.floor_number); + if (!$scope.isUploadingFloorplan) { // if we are still uploading a floorplan, the refresh will fail. + if (newVal !== undefined && newVal !== null && !_.isEqual(newVal, oldVal)) { + $scope.fetchFloorPlanOverlay(newVal); + changedfloor = false; } - - changedfloor = false; } - }); $scope.fetchAllFloorsForBuilding = function (b) { - // TODO: check for b.buid var jsonReq = AnyplaceService.jsonReq; jsonReq.buid = b.buid; - var promise = AnyplaceAPIService.allBuildingFloors(jsonReq); promise.then( function (resp) { - $scope.xFloors = resp.data.floors; - $scope.xFloors = $scope.xFloors.sort(function (a, b) { return parseInt(a.floor_number) - parseInt(b.floor_number) }); - $scope.anyService.availableFloors = []; $scope.anyService.availableFloors = $scope.xFloors; @@ -168,16 +143,12 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' } else { $scope.anyService.selectedFloor = undefined; } - _setNextFloor(); -// _suc($scope, "Successfully fetched all floors."); - }, function (resp) { ShowError($scope, resp, ERR_FETCH_ALL_FLOORS, true); } ); - }; var _setNextFloor = function () { @@ -191,50 +162,58 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' }; var _isValidFloorNumber = function (fl) { - if (fl === null || fl == undefined) { - return false; - } + if (fl === null || fl == undefined) { return false; } - if (fl.floor_number === null || fl.floor_number === undefined) { - return false; - } + if (fl.floor_number === null || fl.floor_number === undefined) { return false; } return true; }; $scope.fetchFloorPlanOverlay = function () { - if (!_isValidFloorNumber(this.anyService.selectedFloor)) { - // TODO: alert - _warn_autohide($scope, 'Something is wrong with the floor'); - console.log('Something is wrong with the floor'); + _warn_autohide($scope, 'Something is wrong with the floor'); return; } var floor_number = this.anyService.selectedFloor.floor_number; - var buid = this.anyService.selectedBuilding.buid; - + var space = this.anyService.selectedBuilding; + var buid = space.buid; var promise = AnyplaceAPIService.downloadFloorPlan(this.anyService.jsonReq, buid, floor_number); promise.then( - function (resp) { - + function (resp) { // on success + LOG.D3("fetched floorplan overlay") + var data = resp.data; $scope.data.floor_plan_file = null; $scope.data.floor_plan = null; + + // hide this and the previous overlay if ($scope.data.floor_plan_groundOverlay != null) { $scope.data.floor_plan_groundOverlay.setMap(null); $scope.data.floor_plan_groundOverlay = null; + // hide the previous of the last overlay (maximum overlay history: 1) + if ($scope.data.floorPlanPrevOverlay) { + LOG.D3("hiding previous"); + $scope.data.floorPlanPrevOverlay.setMap(null); + $scope.data.floorPlanPrevOverlay = null; + } } - // on success - var data = resp.data; - // load the correct coordinates from the selected floor var fl = $scope.anyService.selectedFloor; var imageBounds = new google.maps.LatLngBounds( new google.maps.LatLng(fl.bottom_left_lat, fl.bottom_left_lng), new google.maps.LatLng(fl.top_right_lat, fl.top_right_lng)); - $scope.data.floor_plan_groundOverlay = new USGSOverlay(imageBounds, "data:image/png;base64," + data, GMapService.gmap); + $scope.data.floorPlanPrevOverlay = $scope.data.floor_plan_groundOverlay; + $scope.data.floor_plan_groundOverlay = + new USGSOverlay(imageBounds, "data:image/png;base64," + data, GMapService.gmap); + + // pan to location and cache the floor num + GMapService.gmap.panTo(_latLngFromBuilding($scope.anyService.selectedBuilding)); + if (GMapService.gmap.getZoom() < 19) { GMapService.gmap.setZoom(19); } + if (typeof(Storage) !== "undefined" && localStorage) { + localStorage.setItem("lastFloor", floor_number); + } }, function (resp) { ShowWarningAutohide($scope, resp, "Error downloading floor plan"); @@ -244,38 +223,48 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' var canvasOverlay = null; $scope.isCanvasOverlayActive = false; - var floorPlanInputElement = $('#input-floor-plan'); floorPlanInputElement.change(function handleImage(e) { var reader = new FileReader(); reader.onload = function (event) { var imgObj = new Image(); - imgObj.src = event.target.result; + imgObj.src = reader.result; + imgObj.onload = function () { - canvasOverlay = new CanvasOverlay(imgObj, GMapService.gmap); + canvasOverlay = new CanvasOverlay(imgObj, GMapService.gmap, $scope); + GMapService.gmap.panTo(_latLngFromBuilding($scope.anyService.selectedBuilding)); + GMapService.gmap.setZoom(19); $scope.$apply($scope.isCanvasOverlayActive = true); - // hide previous floorplan - if ($scope.data.floor_plan_groundOverlay && $scope.data.floor_plan_groundOverlay.getMap()) { - $scope.data.floor_plan_groundOverlay.setMap(null); + if ($scope.data.floor_plan_groundOverlay != null + && $scope.data.floor_plan_groundOverlay.getMap()) { + var overlayMode = $('#overlay-mode').prop("checked"); + if (!overlayMode) { // hide the last overlay + $scope.data.floor_plan_groundOverlay.setMap(null); + $scope.data.floor_plan_groundOverlay = null; + } else { + // hide the previous of the last overlay (maximum overlay history: 1) + if ($scope.data.floorPlanPrevOverlay) { + LOG.D3("hiding previous (on new image upload)"); + $scope.data.floorPlanPrevOverlay.setMap(null); + $scope.data.floorPlanPrevOverlay = null; + } + $scope.data.floorPlanPrevOverlay = $scope.data.floor_plan_groundOverlay; + $scope.data.floor_plan_groundOverlay = null; + } } - } - }; reader.readAsDataURL(e.target.files[0]); - this.disabled = true; }); $scope.setFloorPlan = function () { - if (!canvasOverlay) { - return; - } + if (!canvasOverlay) { return; } - if (GMapService.gmap.getZoom() < 20) { - _err($scope, "Minimum zoom level required: 20. Current: " + GMapService.gmap.getZoom()); + if (GMapService.gmap.getZoom() < 18) { + _warn_autohide($scope, "Minimum zoom level: 18. (current: " + GMapService.gmap.getZoom() + ")"); return; } @@ -298,7 +287,6 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' // create the proper image inside the canvas canvasOverlay.drawBoundingCanvas(); - // create the ground overlay and destroy the canvasOverlay object // and also set the floor_plan_coords in $scope.data var bl = canvasOverlay.bottom_left_coords; @@ -309,6 +297,7 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' $scope.data.floor_plan_coords.top_right_lng = tr.lng(); $scope.data.floor_plan_coords.zoom = GMapService.gmap.getZoom() + ""; var data = canvasOverlay.getCanvas().toDataURL("image/png"); // defaults to png + $scope.data.floor_plan_base64_data = data; var imageBounds = new google.maps.LatLngBounds( new google.maps.LatLng(bl.lat(), bl.lng()), @@ -319,47 +308,44 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' $('#input-floor-plan').prop('disabled', false); $scope.isCanvasOverlayActive = false; - // This if is never true (?) CHECK - if (_floorNoExists($scope.newFloorNumber)) { + if (_floorNoExists($scope.newFloorNumber)) { // This if is never true (?) CHECK for (var i = 0; i < $scope.xFloors.length; i++) { var f = $scope.xFloors[i]; if (!LPUtils.isNullOrUndefined(f)) { if (f.floor_number === String($scope.newFloorNumber)) { + LOG.D3("setFloorPlan: uploadWithZoom: data: " + $scope.data.size) $scope.uploadWithZoom($scope.anyService.selectedBuilding, f, $scope.data); break; } } } } else { + LOG.D3("setFloorPlan: addFloorObject") $scope.addFloorObject(newFl, $scope.anyService.selectedBuilding, $scope.data); } }; var _checkFloorFormat = function (bobj) { - if (bobj === null) { - bobj = {} - } else { - bobj = JSON.parse(JSON.stringify(bobj)) - } - if (LPUtils.isNullOrUndefined(bobj.buid)) { - bobj.buid = "" - } - if (LPUtils.isNullOrUndefined(bobj.floor_name)) { - bobj.floor_name = "" - } - if (LPUtils.isNullOrUndefined(bobj.floor_number)) { - bobj.floor_number = "" - } - if (LPUtils.isNullOrUndefined(bobj.description)) { - bobj.description = "" - } - if (LPUtils.isNullOrUndefined(bobj.is_published)) { - bobj.is_published = 'true' - } + if (bobj === null) bobj = {} + else bobj = JSON.parse(JSON.stringify(bobj)) + + if (LPUtils.isNullOrUndefined(bobj.buid)) { bobj.buid = "" } + if (LPUtils.isNullOrUndefined(bobj.floor_name)) { bobj.floor_name = "" } + if (LPUtils.isNullOrUndefined(bobj.floor_number)) { bobj.floor_number = "" } + if (LPUtils.isNullOrUndefined(bobj.description)) { bobj.description = "" } + if (LPUtils.isNullOrUndefined(bobj.is_published)) { bobj.is_published = 'true' } return bobj; }; + /** + * Creates a Floor entry before uploading with the zoom. That is two API calls. + * + * @param flJson + * @param selectedBuilding + * @param flData + */ $scope.addFloorObject = function (flJson, selectedBuilding, flData) { + LOG.D("addFloorObject:") var obj = _checkFloorFormat(flJson); var promise = $scope.anyAPI.addFloor(obj); // make the request at AnyplaceAPI promise.then( @@ -367,13 +353,12 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' var data = resp.data; // insert the newly created building inside the loadedBuildings $scope.xFloors.push(obj); - $scope.anyService.selectedFloor = $scope.xFloors[$scope.xFloors.length - 1]; - _suc($scope, "Successfully added new floor"); + LOG.D2("Created floor."); $scope.uploadWithZoom(selectedBuilding, obj, flData); + $scope.anyService.selectedFloor = $scope.xFloors[$scope.xFloors.length - 1]; }, function (resp) { - ShowError($scope, resp, - "Something went wrong while adding a new floor.", true); + ShowError($scope, resp, "Could not create floor.", true); } ); @@ -383,26 +368,20 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' $scope.data.floor_plan_file = null; $scope.data.floor_plan = null; - if (canvasOverlay) { - canvasOverlay.setMap(null); - } - - - if ($scope.data.floor_plan_groundOverlay) { - $scope.data.floor_plan_groundOverlay.setMap(null); - } + if (canvasOverlay) { canvasOverlay.setMap(null); } + if ($scope.data.floor_plan_groundOverlay) { $scope.data.floor_plan_groundOverlay.setMap(null); } var x = $('#input-floor-plan'); x.replaceWith(x = x.clone(true)); - x.prop('disabled', false); - $scope.isCanvasOverlayActive = false; }; $scope.deleteFloor = function () { var bobj = $scope.anyService.getFloor(); - if (LPUtils.isNullOrUndefined(bobj) || LPUtils.isStringBlankNullUndefined(bobj.floor_number) || LPUtils.isStringBlankNullUndefined(bobj.buid)) { + if (LPUtils.isNullOrUndefined(bobj) + || LPUtils.isStringBlankNullUndefined(bobj.floor_number) + || LPUtils.isStringBlankNullUndefined(bobj.buid)) { _err($scope, "No floor seems to be selected."); return; } @@ -429,7 +408,7 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' } else { $scope.anyService.selectedFloor = undefined; } - _suc($scope, "Successfully deleted floor."); + _suc($scope, "Floor deleted."); }, function (resp) { ShowError($scope, resp, "Something went wrong while deleting the floor.", true); @@ -437,7 +416,6 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' ); }; - var _floorNoExists = function (n) { for (var i = 0; i < $scope.xFloors.length; i++) { var f = $scope.xFloors[i]; @@ -452,28 +430,23 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' }; var _cloneCoords = function (obj) { - if (LPUtils.isNullOrUndefined(obj)) { - return {} - } + if (LPUtils.isNullOrUndefined(obj)) { return {} } var n = JSON.parse(JSON.stringify(obj)); return n; }; - $scope.uploadWithZoom = function (sb, sf, flData) { - if (LPUtils.isNullOrUndefined(canvasOverlay)) { - return; - } + LOG.D3("uploadWithZoom") + if (LPUtils.isNullOrUndefined(canvasOverlay)) { return; } var bobj = _cloneCoords(flData.floor_plan_coords); - if (LPUtils.isNullOrUndefined(bobj) || LPUtils.isStringBlankNullUndefined(bobj.bottom_left_lat) || LPUtils.isStringBlankNullUndefined(bobj.bottom_left_lng) || LPUtils.isStringBlankNullUndefined(bobj.top_right_lat) || LPUtils.isStringBlankNullUndefined(bobj.top_right_lng)) { - console.log('error with floor coords'); - _err($scope, "Something went wrong. It seems like no valid coordinates have been set up for this floor plan."); + LOG.E('error with floor coords'); + _err($scope, "No valid coordinates for this floorplan."); return; } @@ -491,12 +464,11 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' if (!LPUtils.isNullOrUndefined(sb.buid)) { bobj.buid = sb.buid; } else { - _err($scope, "Something went wrong with the selected building's id."); + _err($scope, "Something wrong with the space id."); return; } - } else { - // no building selected - _err($scope, "Something went wrong. It seems like there is no building selected."); + } else { // no building selected + _err($scope, "No space selected."); return; } @@ -504,41 +476,36 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' if (!LPUtils.isNullOrUndefined(sf.floor_number)) { bobj.floor_number = sf.floor_number; } else { - _err($scope, "Something went wrong. It seems there is no floor number associated with the selected floor."); + _err($scope, "No number associated with the selected floor."); return; } - } else { - // no floor selected - _err($scope, "Something went wrong. It seems there is no floor selected."); + } else { // no floor selected + _err($scope, "No floor selected."); return; } bobj.owner_id = $scope.owner_id; - var json_req = JSON.stringify(bobj); - if (LPUtils.isNullOrUndefined(flData.floor_plan_base64_data) || LPUtils.isStringEmptyNullUndefined(flData.floor_plan_base64_data)) { - console.log('no floor plan file'); + LOG.E('No floor plan file'); return; } - // make the request at AnyplaceAPI + $scope.isUploadingFloorplan=true; + _info($scope, "Uploading and processing floorplan ..."); var promise = $scope.anyAPI.uploadFloorPlan64(json_req, $scope.data.floor_plan_base64_data); promise.then( - function (resp) { - // on success - var data = resp.data; - _suc($scope, "Successfully uploaded new floor plan."); + function (resp) { // on success + $scope.isUploadingFloorplan=false; + _suc("Floorplan uploaded and tiled."); + $scope.fetchFloorPlanOverlay(sf.floor_number); }, - function (resp) { - // on error - var data = resp.data; - //TODO: alert error - _suc($scope, "Successfully uploaded new floor plan."); - }); - + function (resp) { // on error + $scope.isUploadingFloorplan=false; + ShowError($scope, resp, "Upload error", true); + }); // on error }; $scope.showRadioHeatmapPoi = function () { @@ -550,43 +517,31 @@ app.controller('FloorController', ['$scope', 'AnyplaceService', 'GMapService', ' "range": "1" }; - jsonReq.username = $scope.creds.username; - jsonReq.password = $scope.creds.password; - var promise = $scope.anyAPI.getRadioHeatmapPoi(jsonReq); promise.then( function (resp) { // on success var data = resp.data; - var heatMapData = []; - var i = resp.data.radioPoints.length; if (i <= 0) { - _err($scope, "This floor seems not to be WiFi mapped. Download the Anyplace app from the Google Play store to map the floor."); + _err($scope, "Floor not mapped. Use logger (Google Play) to map it."); return; } while (i--) { var rp = resp.data.radioPoints[i]; - heatMapData.push( - {location: new google.maps.LatLng(rp.x, rp.y), weight: 1} - ); + heatMapData.push( {location: new google.maps.LatLng(rp.x, rp.y), weight: 1} ); resp.data.radioPoints.splice(i, 1); } - if (heatmap && heatmap.getMap()) { - heatmap.setMap(null); - } - - heatmap = new google.maps.visualization.HeatmapLayer({ - data: heatMapData - }); + if (heatmap && heatmap.getMap()) { heatmap.setMap(null); } + heatmap = new google.maps.visualization.HeatmapLayer({ data: heatMapData }); heatmap.setMap($scope.gmapService.gmap); }, function (resp) { - ShowError($scope, resp, "Something went wrong while fetching radio heatmap.", true); + ShowWarningAutohide($scope, resp, "", false); } ); } diff --git a/server/public/anyplace_architect/controllers/PoiController.js b/server/public/anyplace_architect/controllers/PoiController.js index fe302c1d0..d4fb8b065 100644 --- a/server/public/anyplace_architect/controllers/PoiController.js +++ b/server/public/anyplace_architect/controllers/PoiController.js @@ -1095,7 +1095,7 @@ app.controller('PoiController', ['$scope', '$compile', 'GMapService', 'AnyplaceS break; } } - _suc($scope, "Successfully deleted POI."); + _info_autohide($scope, "POI deleted.", 1000); }, function (resp) { // on error diff --git a/server/public/anyplace_architect/controllers/SelectFloorController.js b/server/public/anyplace_architect/controllers/SelectFloorController.js index 1f672549c..6e9fa2eca 100644 --- a/server/public/anyplace_architect/controllers/SelectFloorController.js +++ b/server/public/anyplace_architect/controllers/SelectFloorController.js @@ -47,28 +47,23 @@ app.controller('SelectFloorController', ['$scope', 'AnyplaceService', 'GMapServi }; $scope.floorUp = function () { - //here new changedfloor = true; var next; for (var i = 0; i < $scope.xFloors.length; i++) { next = i + 1; if ($scope.xFloors[i].floor_number === $scope.anyService.selectedFloor.floor_number) { - if (next < $scope.xFloors.length) { $scope.anyService.selectedFloor = $scope.xFloors[next]; return; } else { - //_warn("There is no other floor above."); return; } } } - _err($scope, "Floor not found."); }; $scope.floorDown = function () { - changedfloor = true; var prev; for (var i = 0; i < $scope.xFloors.length; i++) { @@ -79,12 +74,10 @@ app.controller('SelectFloorController', ['$scope', 'AnyplaceService', 'GMapServi $scope.anyService.selectedFloor = $scope.xFloors[prev]; return; } else { - //_warn("There is no other floor below."); return; } } } - _err($scope, "Floor not found."); }; diff --git a/server/public/anyplace_architect/controllers/WiFiController.js b/server/public/anyplace_architect/controllers/WiFiController.js index 1126fca47..fa72c305f 100644 --- a/server/public/anyplace_architect/controllers/WiFiController.js +++ b/server/public/anyplace_architect/controllers/WiFiController.js @@ -513,9 +513,7 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' if (_APs_IS_ON) { var i = APmap.length; - - //hide Access Points - while (i--) { + while (i--) { //hide Access Points APmap[i].setMap(null); APmap[i] = null; $scope.example9data[i] = null; @@ -537,9 +535,7 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' } if (_FINGERPRINTS_IS_ON) { var i = fingerPrintsMap.length; - - //hide fingerPrints - while (i--) { + while (i--) { //hide fingerPrints fingerPrintsMap[i].setMap(null); fingerPrintsMap[i] = null; } @@ -547,11 +543,9 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' $scope.showFingerprintHeatmap(); if ($scope.fingerPrintsTimeMode && !$scope.radioHeatmapRSSTimeMode) { - d3.selectAll("svg > *").remove(); $("svg").remove(); $scope.getFingerPrintsTime(); - } } @@ -560,9 +554,7 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' //hide fingerPrints heatmap heatmap.setMap(null); var i = heatmapFingerprints.length; - while (i--) { - heatmapFingerprints[i] = null; - } + while (i--) { heatmapFingerprints[i] = null; } heatmapFingerprints = []; _HEATMAP_F_IS_ON = false; @@ -576,8 +568,7 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' } } - if (heatmapAcc && heatmapAcc.getMap()) { - //hide acces heatmap + if (heatmapAcc && heatmapAcc.getMap()) { // hide acces heatmap heatmapAcc.setMap(null); $scope.showLocalizationAccHeatmap(); } @@ -592,18 +583,14 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' for (var key in connectionsMap) { if (connectionsMap.hasOwnProperty(key)) { var con = connectionsMap[key]; - if (con && con.polyLine) { - con.polyLine.setMap(null); - } + if (con && con.polyLine) { con.polyLine.setMap(null); } } - } $scope.anyService.setAllConnection(connectionsMap); connectionsMap = {}; } } } - } if (!_POIS_IS_ON) { @@ -611,34 +598,36 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' if (POIsMap !== undefined) { var key = Object.keys(POIsMap); if (POIsMap[key[check]] !== undefined) { - if (POIsMap[key[check]].marker.getMap() !== null) { - for (var key in POIsMap) { if (POIsMap.hasOwnProperty(key)) { var p = POIsMap[key]; - if (p && p.marker) { - p.marker.setMap(null); - - } + if (p && p.marker) { p.marker.setMap(null); } } } - $scope.anyService.setAllPois(POIsMap); POIsMap = {}; - } } } } changedfloor = false; } - }); + /** + * Shows the Wi-Fi Coverage: + * - draws boxes of colors green/yellow/darkYellow/Magenta/Red + * - these denote the signal coverage in the building. + * - It walls getRadioHeatmapRSS_N according to the zoom level (1 to 3, where 3 is max). + */ $scope.toggleCoverage = function () { LOG.D2("toggleCoverage"); + if (!$scope.anyService.hasSelectedFloor()) { + _warn_autohide($scope, "No floor selected (for coverage).") + return; + } // if coverage map is combined with timestamp, on hide remove crossfilter bar if (_HEATMAP_FINGERPRINT_COVERAGE && $scope.fingerPrintsTimeMode) { @@ -655,11 +644,8 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' $scope.anyService.fingerPrintsTimeMode = !$scope.anyService.fingerPrintsTimeMode; } - // if () - var check = 0; if ((heatMap[check] !== undefined && heatMap[check] !== null) || $scope.radioHeatmapRSSTimeMode) { - var i = heatMap.length; while (i--) { heatMap[i].rectangle.setMap(null); @@ -714,13 +700,9 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' }; $scope.toggleAPs = function () { - var check = 0; - - if (APmap[check] !== undefined && APmap[check] !== null) { + if (APmap[check] !== undefined && APmap[check] !== null) { // hide Access Points var i = APmap.length; - - //hide Access Points while (i--) { APmap[i].setMap(null); APmap[i] = null; @@ -733,60 +715,63 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' $scope.example8model[i] = null; } - APmap = []; - $scope.example9data = []; - $scope.example9model = []; - $scope.example8data = []; - $scope.example8model = []; - _APs_IS_ON = false; - $scope.filterByMAC = false; - $scope.filterByMAN = false; - document.getElementById("APs-mode").classList.remove('quickaction-selected'); - $scope.APsMode = false; - if (typeof (Storage) !== "undefined" && localStorage) { - localStorage.setItem('APsMode', 'NO'); - } + $scope.prefDisableApsMode(); return; - } - _APs_IS_ON = true; - - $scope.APsMode = true; - if (typeof (Storage) !== "undefined" && localStorage) { - localStorage.setItem('APsMode', 'YES'); + if ($scope.anyService.hasSelectedFloor()) { + $scope.prefEnableApsMode(); + $scope.showAPs(); + } else { + $scope.prefDisableApsMode(); + _warn_autohide($scope, "No floor selected (for access points)."); } + }; + $scope.prefEnableApsMode = function () { + _APs_IS_ON = true; + $scope.APsMode = true; + if (typeof (Storage) !== "undefined" && localStorage) { localStorage.setItem('APsMode', 'YES'); } document.getElementById("APs-mode").classList.add('quickaction-selected'); - - $scope.showAPs(); - }; + $scope.prefDisableApsMode = function () { + APmap = []; + $scope.example9data = []; + $scope.example9model = []; + $scope.example8data = []; + $scope.example8model = []; + _APs_IS_ON = false; + $scope.filterByMAC = false; + $scope.filterByMAN = false; + document.getElementById("APs-mode").classList.remove('quickaction-selected'); + $scope.APsMode = false; + if (typeof (Storage) !== "undefined" && localStorage) { localStorage.setItem('APsMode', 'NO'); } + } + $scope.toggleFingerPrints = function () { LOG.D2("toggleFingerPrints"); + if (!$scope.anyService.hasSelectedFloor()) { + _warn_autohide($scope, "No floor selected (for fingerprints).") + return; + } + // if coverage and time are pressed, remove them when heatmaps are requested. - if (_HEATMAP_FINGERPRINT_COVERAGE && $scope.fingerPrintsTimeMode) { // + if (_HEATMAP_FINGERPRINT_COVERAGE && $scope.fingerPrintsTimeMode) { + // is the logic here correct? + LOG.D2("coverage & time pressed"); $scope.toggleCoverage(); - $scope.toggleFingerPrints(); + $scope.toggleFingerPrints(); // calling itself?! return } - $scope.fingerPrintsMode = !$scope.fingerPrintsMode; - if ($scope.fingerPrintsMode) { - document.getElementById("fingerPrints-mode").classList.add('quickaction-selected'); - if (typeof (Storage) !== "undefined" && localStorage) { - localStorage.setItem('fingerprintsMode', 'YES'); - } + if (!$scope.fingerPrintsMode) { + $scope.enableFingerprintsMode(); } else { - document.getElementById("fingerPrints-mode").classList.remove('quickaction-selected'); - if (typeof (Storage) !== "undefined" && localStorage) { - localStorage.setItem('fingerprintsMode', 'NO'); - } + $scope.disableFingerprintsMode(); } var check = 0; - if (fingerPrintsMap[check] !== undefined && fingerPrintsMap[check] !== null) { var i = fingerPrintsMap.length; @@ -811,12 +796,9 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' document.getElementById("fingerPrints-time-mode").classList.remove('quickaction-selected'); } return; - } - if (heatmap && heatmap.getMap()) { - //hide fingerPrints heatmap - + if (heatmap && heatmap.getMap()) { // hide fingerPrints heatmap heatmap.setMap(null); _FINGERPRINTS_IS_ON = false; document.getElementById("fingerPrints-mode").classList.remove('quickaction-selected'); @@ -834,9 +816,7 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' document.getElementById("fingerPrints-time-mode").classList.remove('quickaction-selected'); } var i = heatmapFingerprints.length; - while (i--) { - heatmapFingerprints[i] = null; - } + while (i--) { heatmapFingerprints[i] = null; } heatmapFingerprints = []; return; } @@ -859,11 +839,27 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' /** + * NOTE: the ACCES map functionality is now removed from the backend. + * * This methods asynchronoysly calls showLocalizationAccHeatmap, that will * eventually show the ACCES map. No UI changes should happen here as it returns immediately. * - * */ + * It is a time consuming method that prepares in the background the access map. + * The code is buggy and based on obsolete Play/Scala libraries, so it was eventually removed. + * + */ $scope.toggleLocalizationAccuracy = function () { + if ((true)) { // ACCES is now removed. Show warning and disable when toggled + _warn_autohide($scope, WARN_ACCES_REMOVED) + $scope.localizationAccMode = false; + if (typeof (Storage) !== "undefined" && localStorage) { + localStorage.setItem('localizationAccMode', 'NO'); + } + var el = document.getElementById("localizationAccuracy-mode") + el.classList.remove('quickaction-selected'); + return; + } + var check = 0; if ((heatMapAcces[check] !== undefined && heatMapAcces[check] !== null) || @@ -874,7 +870,6 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' heatMapAcces[i] = null; } heatMapAcces = []; - _HEATMAP_ACCES = false; // CHECK what is this? setColorClicked('g', false); @@ -893,15 +888,19 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' } $scope.showLocalizationAccHeatmap(); - document.getElementById("localizationAccuracy-mode").classList.add('quickaction-selected'); - return; }; $scope.toggleFingerPrintsTime = function () { LOG.D2("toggleFingerPrintsTime"); - if ($scope.fingerPrintsMode) { + if (!$scope.anyService.hasSelectedFloor()) { + _warn_autohide($scope, "No floor selected (for fingerprints:time)") + return; + } + + if ($scope.fingerPrintsMode) { // gmap heatmap mode (normal red/green/yellow maps heatmap) + LOG.D("fingerPrintsMode") $scope.fingerPrintsTimeMode = !$scope.fingerPrintsTimeMode; if ($scope.fingerPrintsTimeMode) { if (typeof (Storage) !== "undefined" && localStorage) { @@ -915,7 +914,8 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' $scope.anyService.fingerPrintsTimeMode = !$scope.anyService.fingerPrintsTimeMode; } - if ($scope.radioHeatmapRSSMode) { + if ($scope.radioHeatmapRSSMode) { // square boxes overlay (showing quality of coverate) + LOG.D("radioHeatmapRSSMode") $scope.radioHeatmapRSSTimeMode = !$scope.radioHeatmapRSSTimeMode; if ($scope.radioHeatmapRSSTimeMode) { if (typeof (Storage) !== "undefined" && localStorage) { @@ -930,6 +930,7 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' } if ($scope.radioHeatmapLocalization) { + LOG.D("radioHeatmapLocalization") clearLocalization(); $scope.showFingerprintCoverage(); $scope.radioHeatmapRSSMode = true; @@ -942,6 +943,7 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' } if (!$scope.fingerPrintsTimeMode && $scope.fingerPrintsMode) { + LOG.D("!fingerPrintsTimeMode && fingerPrintsMode: showFingerprintHeatmap") clearFingerprintHeatmap(); $scope.showFingerprintHeatmap(); document.getElementById("fingerPrints-mode").classList.add('quickaction-selected'); @@ -949,6 +951,7 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' } if (!$scope.radioHeatmapRSSTimeMode && $scope.radioHeatmapRSSMode) { + LOG.D("!radioHeatmapRSSTimeMode && radioHeatmapRSSMode") clearFingerprintCoverage(); $scope.showFingerprintCoverage(); $scope.radioHeatmapRSSMode = true; @@ -966,20 +969,22 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' }; $scope.togglePOIs = function () { - POIsMap = $scope.anyService.getAllPois(); var key = Object.keys(POIsMap); var check = 0; + if (!$scope.anyService.hasSelectedFloor()) { + _warn_autohide($scope, "No floor selected (for POIs)") + return; + } + if (!POIsMap.hasOwnProperty(key[check])) { - _err($scope, "No POIs yet.") + _warn_autohide($scope, "No POIs yet.") return; } if (POIsMap[key[check]].marker.getMap() !== null && POIsMap[key[check]].marker.getMap() !== undefined) { - for (var key in POIsMap) { if (POIsMap.hasOwnProperty(key)) { - var p = POIsMap[key]; if (p && p.marker) { p.marker.setMap(null); @@ -998,7 +1003,6 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' for (var key in POIsMap) { if (POIsMap.hasOwnProperty(key)) { - var p = POIsMap[key]; if (p && p.marker) { p.marker.setMap(GMapService.gmap); @@ -1015,30 +1019,26 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' $scope.toggleConnections = function () { - connectionsMap = $scope.anyService.getAllConnections(); var key = Object.keys(connectionsMap); var check = 0; + if (!$scope.anyService.hasSelectedFloor()) { + _warn_autohide($scope, "No floor selected (for edges)") + return; + } if (!connectionsMap.hasOwnProperty(key[check])) { - _warn_autohide($scope, "No edges yet.") + LOG.D2("No edges yet.") return; } if (connectionsMap[key[check]].polyLine !== undefined) { - if (connectionsMap[key[check]].polyLine.getMap() !== undefined) { if (connectionsMap[key[check]].polyLine.getMap() !== null) { - for (var key in connectionsMap) { if (connectionsMap.hasOwnProperty(key)) { - var con = connectionsMap[key]; - if (con && con.polyLine) { - - con.polyLine.setMap(null); - } + if (con && con.polyLine) { con.polyLine.setMap(null); } } - } $scope.anyService.setAllConnection(connectionsMap); @@ -1057,10 +1057,8 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' $scope.getHeatMapButtonText = function () { - var check = 0; return heatMap[check] !== undefined && heatMap[check] !== null ? "Hide WiFi Map" : "Show WiFi Map"; - }; $scope.getAPsButtonText = function () { @@ -1088,7 +1086,6 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' } $scope.getPOIsButtonText = function () { - POIsMap = $scope.anyService.getAllPois(); var key = Object.keys(POIsMap); var check = 0; @@ -1104,7 +1101,6 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' document.getElementById("POIs-mode").classList.remove('quickaction-selected'); $scope.POIsMode = false; return "Show POIs"; - }; $scope.getConnectionsButtonText = function () { @@ -1655,7 +1651,7 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' $cookieStore.put('RSSClicked', 'YES'); }, function (resp) { - ShowError($scope, resp, "Something went wrong while fetching radio heatmap.", true); + ShowWarningAutohide($scope, resp, "", false); if (!$scope.radioHeatmapRSSTimeMode) { $scope.radioHeatmapRSSMode = false; if (typeof (Storage) !== "undefined" && localStorage && !$scope.fingerPrintsTimeMode) { @@ -1707,29 +1703,18 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' ShowError($scope, resp, "Something went wrong while fetching the ids of access points.", true); } ); - - }; - - $scope.showAPs = function () { - //request for access points + $scope.showAPs = function () { // request for access points var jsonReq = {"buid": $scope.anyService.getBuildingId(), "floor": $scope.anyService.getFloorNumber()}; - jsonReq.username = $scope.creds.username; - jsonReq.password = $scope.creds.password; - var i; - var promise = $scope.anyAPI.getAPs(jsonReq); promise.then( - function (resp) { - // on success - + function (resp) { // on success i = resp.data.accessPoints.length; - if (i <= 0) { - _warn_autohide($scope, "This floor seems not to be Access Point mapped. Download the Anyplace app from the Google Play store to map the floor."); + _warn_autohide($scope, "Floor not mapped. Use logger to map it"); $scope.APsMode = false; document.getElementById("APs-mode").classList.remove('quickaction-selected'); return; @@ -1760,13 +1745,11 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' } else { x = values[i].x; y = values[i].y; - } + $scope.example9data.push({id: values[i].AP, label: values[i].AP}); $scope.example9model.push({id: values[i].AP, label: values[i].AP}); - //alert("x: "+x+" y: "+y); - var accessPoint = new google.maps.Marker({ id: values[i].AP, position: new google.maps.LatLng(x, y), @@ -1781,34 +1764,26 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' ) }); - APmap.push( - accessPoint - ); - + APmap.push(accessPoint); jsonInfo.push(accessPoint.id); - var infowindow = new google.maps.InfoWindow(); if (!infowindow.getMap()) { APmap[c].addListener('click', function () { - if (this.mun !== "N/A") { infowindow.setContent(this.id + "
-

" + this.mun); } else { infowindow.setContent(this.id); } - infowindow.open(this.gmap, this); }); } - - c++; } $scope.getAPsIds(jsonInfo); }, function (resp) { $scope.APsMode = false; - ShowError($scope, resp, 'Something went wrong while fetching Access Points.', true); + ShowError($scope, resp, 'Can\'t fetch Access Points:', true); document.getElementById("APs-mode").classList.remove('quickaction-selected'); } ); @@ -1833,12 +1808,29 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' initializeTimeFunction(); }, function (resp) { - console.log(ERR_FETCH_FINGERPRINTS + ": timestamp."); - // ShowError($scope, resp, ERR_FETCH_FINGERPRINTS + ": timestamp.", true); + LOG.W(ERR_FETCH_FINGERPRINTS + " (getFingerPrintsTime)"); + $scope.disableFingerprintsMode(); + ShowWarningAutohide($scope, resp, "", false); } ); }; + $scope.enableFingerprintsMode = function () { + document.getElementById("fingerPrints-mode").classList.add('quickaction-selected'); + $scope.fingerPrintsMode = true; + if (typeof (Storage) !== "undefined" && localStorage) { + localStorage.setItem('fingerprintsMode', 'YES'); + } + } + + $scope.disableFingerprintsMode = function () { + document.getElementById("fingerPrints-mode").classList.remove('quickaction-selected'); + $scope.fingerPrintsMode = false; + if (typeof (Storage) !== "undefined" && localStorage) { + localStorage.setItem('fingerprintsMode', 'NO'); + } + } + /** * * Shows Google Maps Heatmap for fingerprints @@ -1895,15 +1887,9 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' _FINGERPRINTS_IS_ON = true; }, function (resp) { - console.log(ERR_FETCH_FINGERPRINTS + ": timestamp."); - // ShowError($scope, resp, ERR_FETCH_FINGERPRINTS, true); - if (!$scope.fingerPrintsMode) { - document.getElementById("fingerPrints-mode").classList.remove('quickaction-selected'); - $scope.fingerPrintsMode = false; - if (typeof (Storage) !== "undefined" && localStorage) { - localStorage.setItem('fingerprintsMode', 'NO'); - } - } + LOG.E(ERR_FETCH_FINGERPRINTS + ": showFingerprintHeatmap (disabling option)."); + $scope.disableFingerprintsMode(); + ShowWarningAutohide($scope, resp, "", false); } ); return null; @@ -1959,14 +1945,8 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' }, function (resp) { console.log(ERR_FETCH_FINGERPRINTS + ": timestamp."); - // ShowError($scope, resp, ERR_FETCH_FINGERPRINTS, true); - if (!$scope.fingerPrintsMode) { - document.getElementById("fingerPrints-mode").classList.remove('quickaction-selected'); - $scope.fingerPrintsMode = false; - if (typeof (Storage) !== "undefined" && localStorage) { - localStorage.setItem('fingerprintsMode', 'NO'); - } - } + $scope.disableFingerprintsMode(); + ShowWarningAutohide($scope, resp, "", false); } ); var url = null; @@ -2042,14 +2022,8 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' }, function (resp) { console.log(ERR_FETCH_FINGERPRINTS + ": timestamp."); - // ShowError($scope, resp, ERR_FETCH_FINGERPRINTS, true); - if (!$scope.fingerPrintsMode) { - document.getElementById("fingerPrints-mode").classList.remove('quickaction-selected'); - $scope.fingerPrintsMode = false; - if (typeof (Storage) !== "undefined" && localStorage) { - localStorage.setItem('fingerprintsMode', 'NO'); - } - } + $scope.disableFingerprintsMode(); + ShowWarningAutohide($scope, resp, "", false); } ); } @@ -2073,14 +2047,7 @@ app.controller('WiFiController', ['$cookieStore', '$scope', 'AnyplaceService', ' laButton.prop('disabled', true); laButtonProgress.removeClass("hidden"); - jsonReq.username = $scope.creds.username; - jsonReq.password = $scope.creds.password; - var promise = $scope.anyAPI.getHeatmapAcces(jsonReq); // ACCES is removed - if ((true)) { - _warn_autohide($scope, WARN_ACCES_REMOVED) - return; - } var circleRadius = 1.5; // zoom diff --git a/server/public/anyplace_architect/index.html b/server/public/anyplace_architect/index.html index a58c705e0..57c9ec3fc 100644 --- a/server/public/anyplace_architect/index.html +++ b/server/public/anyplace_architect/index.html @@ -17,7 +17,8 @@ - + +
-
+
@@ -732,11 +705,12 @@
- Note: - Zooming in the map as much as possible while placing the floor plan -
-
- will result to better image quality. + NOTES: + + + +
• Use high zoom level to improve placement accuracy.
+
• Zoom level affects real-life size of the floorplan.
@@ -1235,6 +1209,7 @@

Wi-Fi:

+
@@ -1597,5 +1572,36 @@ }); + + diff --git a/server/public/anyplace_architect/libs/jquery.ui.rotatable.js b/server/public/anyplace_architect/libs/jquery.ui.rotatable.js new file mode 100644 index 000000000..4c943b9aa --- /dev/null +++ b/server/public/anyplace_architect/libs/jquery.ui.rotatable.js @@ -0,0 +1,363 @@ +/* globals define jQuery */ + +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory) + } else { + // Browser globals + factory(jQuery) + } +}(function ($) { + $.widget('ui.rotatable', $.ui.mouse, { + widgetEventPrefix: 'rotate', + + options: { + angle: false, // specify an angle in radians (for backward compatability) + degrees: false, // specify angle in degrees + handle: false, // an image to use for a handle + handleOffset: { // where the handle should appear + top: 0, + left: 0 + }, + radians: false, // specify angle in radians + rotate: null, // a callback for during rotation + rotationCenterOffset: { // offset the center of the element for rotation + top: 0, + left: 0 + }, + snap: false, // boolean flag, should the element snap to a certain rotation? + start: null, // callback when rotation starts + step: 22.5, // angle in degrees that the rotation should be snapped to + stop: null, // callback when rotation stops + transforms: null, // other transforms to performed on the element + wheelRotate: true // boolean flag, should the element rotate when the mousewheel is rotated? + }, + + // accessor for the angle in radians + angle: function (angle) { + if (angle === undefined) { + return this.options.angle + } + this.options.angle = angle + this.elementCurrentAngle = angle + this._performRotation(this.options.angle) + }, + + // calculates the element center if needed and returns it + getElementCenter: function () { + this.elementCenter = this._calculateElementCenter() + return this.elementCenter + }, + + // accessor for the handle + handle: function (handle) { + if (handle === undefined) { + return this.options.handle + } + this.options.handle = handle + }, + + plugins: {}, + + /* accessor for the center of rotation + * takes an object with keys of top and left + */ + rotationCenterOffset: function (offset) { + if (offset === undefined) { + return this.options.rotationCenterOffset + } + if (offset.top !== null) { + this.options.rotationCenterOffset.top = offset.top + } + if (offset.left !== null) { + this.options.rotationCenterOffset.left = offset.left + } + }, + + // listener for rotating the element + rotateElement: function (event) { + if (!this.element || this.element.disabled || this.options.disabled) { + return false + } + + if (!event.which) { + this.stopRotate(event) + return false + } + + var rotateAngle = this._calculateRotateAngle(event) + var previousRotateAngle = this.elementCurrentAngle + this.elementCurrentAngle = rotateAngle + + // Plugins callbacks need to be called first. + this._propagate('rotate', event) + + if (this._propagate('rotate', event) === false) { + this.elementCurrentAngle = previousRotateAngle + return false + } + var ui = this.ui() + if (this._trigger('rotate', event, ui) === false) { + this.elementCurrentAngle = previousRotateAngle + return false + } else if (ui.angle.current !== rotateAngle) { + rotateAngle = ui.angle.current + this.elementCurrentAngle = rotateAngle + } + + this._performRotation(rotateAngle) + + if (previousRotateAngle !== rotateAngle) { + this.hasRotated = true + } + + return false + }, + + // listener for starting rotation + startRotate: function (event) { + var center = this.getElementCenter() + var startXFromCenter = event.pageX - center.x + var startYFromCenter = event.pageY - center.y + this.mouseStartAngle = Math.atan2(startYFromCenter, startXFromCenter) + this.elementStartAngle = this.elementCurrentAngle + this.hasRotated = false + + this._propagate('start', event) + + $(document).bind('mousemove', this.listeners.rotateElement) + $(document).bind('mouseup', this.listeners.stopRotate) + + return false + }, + + // listener for stopping rotation + stopRotate: function (event) { + if (!this.element || this.element.disabled) { + return + } + + $(document).unbind('mousemove', this.listeners.rotateElement) + $(document).unbind('mouseup', this.listeners.stopRotate) + + this.elementStopAngle = this.elementCurrentAngle + + this._propagate('stop', event) + + setTimeout(function () { this.element = false }, 10) + return false + }, + + // listener for mousewheel rotation + wheelRotate: function (event) { + if (!this.element || this.element.disabled || this.options.disabled) { + return + } + event.preventDefault() + var angle = this._angleInRadians(Math.round(event.originalEvent.deltaY / 10)) + if (this.options.snap || event.shiftKey) { + angle = this._calculateSnap(angle) + } + angle = this.elementCurrentAngle + angle + this.angle(angle) + this._trigger('rotate', event, this.ui()) + }, + + // for callbacks + ui: function () { + return { + api: this, + element: this.element, + angle: { + start: this.elementStartAngle, + current: this.elementCurrentAngle, + degrees: this._normalizeDegrees(this._angleInDegrees(this.elementCurrentAngle)), + stop: this.elementStopAngle + } + } + }, + + /* *********************** private functions ************************** */ + // calculates the radians for a given angle in degrees + _angleInRadians: function (degrees) { + return degrees * Math.PI / 180 + }, + + // calculates the degrees for a given angle in radians + _angleInDegrees: function (radians) { + return radians * 180 / Math.PI + }, + + _normalizeDegrees: function (degrees) { + return ((degrees % 360) + 360) % 360; + }, + + // calculates the center of the element + _calculateElementCenter: function () { + var elementOffset = this._getElementOffset() + + // Rotation center given via options + if (this._isRotationCenterSet()) { + return { + x: elementOffset.left + this.rotationCenterOffset().left, + y: elementOffset.top + this.rotationCenterOffset().top + } + } + + // Deduce rotation center from transform-origin + if (this.element.css('transform-origin') !== undefined) { + var originPx = this.element.css('transform-origin').match(/([\d.]+)px +([\d.]+)px/) + if (originPx != null) { + return { + x: elementOffset.left + parseFloat(originPx[1]), + y: elementOffset.top + parseFloat(originPx[2]) + } + } + } + + // Default rotation center: middle of the element + return { + x: elementOffset.left + this.element.width() / 2, + y: elementOffset.top + this.element.height() / 2 + } + }, + + // calculates the angle that the element should snap to and returns it in radians + _calculateSnap: function (radians) { + var degrees = this._angleInDegrees(radians) + degrees = Math.round(degrees / this.options.step) * this.options.step + return this._angleInRadians(degrees) + }, + + // calculates the angle to rotate the element to, based on input + _calculateRotateAngle: function (event) { + var center = this.getElementCenter() + + var xFromCenter = event.pageX - center.x + var yFromCenter = event.pageY - center.y + var mouseAngle = Math.atan2(yFromCenter, xFromCenter) + var rotateAngle = mouseAngle - this.mouseStartAngle + this.elementStartAngle + + if (this.options.snap || event.shiftKey) { + rotateAngle = this._calculateSnap(rotateAngle) + } + + return rotateAngle + }, + + // constructor + _create: function () { + var handle + if (!this.options.handle) { + handle = $(document.createElement('div')) + handle.addClass('ui-rotatable-handle') + if (this.options.handleOffset.top !== 0 || this.options.handleOffset.left !== 0) { + handle.css('position', 'relative') + handle.css('top', this.options.handleOffset.top + 'px') + handle.css('left', this.options.handleOffset.left + 'px') + } + } else { + handle = this.options.handle + } + + this.listeners = { + rotateElement: $.proxy(this.rotateElement, this), + startRotate: $.proxy(this.startRotate, this), + stopRotate: $.proxy(this.stopRotate, this), + wheelRotate: $.proxy(this.wheelRotate, this) + } + + if (this.options.wheelRotate) { + this.element.bind('wheel', this.listeners.wheelRotate) + } + + handle.draggable({ helper: 'clone', start: this._dragStart, handle: handle }) + handle.bind('mousedown', this.listeners.startRotate) + + if (!handle.closest(this.element).length) { + handle.appendTo(this.element) + } + this.rotationCenterOffset(this.options.rotationCenterOffset) + + if (this.options.degrees) { + this.elementCurrentAngle = this._angleInRadians(this.options.degrees) + } + else { + this.elementCurrentAngle = this.options.radians || this.options.angle || 0 + } + this._performRotation(this.elementCurrentAngle) + }, + + // destructor + _destroy: function () { + this.element.removeClass('ui-rotatable') + this.element.find('.ui-rotatable-handle').remove() + + if (this.options.wheelRotate) { + this.element.unbind('wheel', this.listeners.wheelRotate) + } + }, + + // used for the handle + _dragStart: function (event) { + if (this.element) { + return false + } + }, + + // retrieves the element offset + _getElementOffset: function () { + this._performRotation(0) + var offset = this.element.offset() + this._performRotation(this.elementCurrentAngle) + return offset + }, + + _getTransforms: function (angle) { + var transforms = 'rotate(' + angle + 'rad)' + + if (this.options.transforms) { + transforms += ' ' + (function (transforms) { + var t = [] + for (var i in transforms) { + if (transforms.hasOwnProperty(i) && transforms[i]) { + t.push(i + '(' + transforms[i] + ')') + } + } + return t.join(' ') + }(this.options.transforms)) + } + return transforms + }, + + // checks to see if the element has a rotationCenterOffset set + _isRotationCenterSet: function () { + return (this.options.rotationCenterOffset.top !== 0 || this.options.rotationCenterOffset.left !== 0) + }, + + // performs the actual rotation on the element + _performRotation: function (angle) { + if (this._isRotationCenterSet()) { + this.element.css('transform-origin', this.options.rotationCenterOffset.left + 'px ' + this.options.rotationCenterOffset.top + 'px') + this.element.css('-ms-transform-origin', this.options.rotationCenterOffset.left + 'px ' + this.options.rotationCenterOffset.top + 'px') /* IE 9 */ + this.element.css('-webkit-transform-origin', this.options.rotationCenterOffset.left + 'px ' + this.options.rotationCenterOffset + 'px') /* Chrome, Safari, Opera */ + } + + var transforms = this._getTransforms(angle) + + this.element.css('transform', transforms) + this.element.css('-moz-transform', transforms) + this.element.css('-webkit-transform', transforms) + this.element.css('-o-transform', transforms) + }, + + // propagates events + _propagate: function (n, event) { + $.ui.plugin.call(this, n, [event, this.ui()]); + (n !== 'rotate' && this._trigger(n, event, this.ui())) + } + }) + + return $.ui.rotatable +})) \ No newline at end of file diff --git a/server/public/anyplace_architect/libs/jquery.ui.rotatable.min.js b/server/public/anyplace_architect/libs/jquery.ui.rotatable.min.js deleted file mode 100644 index 7847adb35..000000000 --- a/server/public/anyplace_architect/libs/jquery.ui.rotatable.min.js +++ /dev/null @@ -1,8 +0,0 @@ -(function(c,d){c.widget("ui.rotatable",c.ui.mouse,{options:{handle:!1,angle:!1,rotationCenterX:!1,rotationCenterY:!1,start:null,rotate:null,stop:null},rotationCenterX:function(a){if(a===d)return this.options.rotationCenterX;this.options.rotationCenterX=a},rotationCenterY:function(a){if(a===d)return this.options.rotationCenterY;this.options.rotationCenterY=a},handle:function(a){if(a===d)return this.options.handle;this.options.handle=a},angle:function(a){if(a===d)return this.options.angle;this.elementCurrentAngle= - this.options.angle=a;this.performRotation(this.options.angle)},_create:function(){var a;this.options.handle?a=this.options.handle:(a=c(document.createElement("div")),a.addClass("ui-rotatable-handle"));this.listeners={rotateElement:c.proxy(this.rotateElement,this),startRotate:c.proxy(this.startRotate,this),stopRotate:c.proxy(this.stopRotate,this),wheelRotate:c.proxy(this.wheelRotate,this)};this.element.bind("wheel",this.listeners.wheelRotate);a.draggable({helper:"clone",start:this.dragStart,handle:a}); - a.bind("mousedown",this.listeners.startRotate);a.appendTo(this.element);0!=this.options.angle?(this.elementCurrentAngle=this.options.angle,this.performRotation(this.elementCurrentAngle)):this.elementCurrentAngle=0},_destroy:function(){this.element.removeClass("ui-rotatable");this.element.find(".ui-rotatable-handle").remove();this.element.unbind("wheel",this.listeners.wheelRotate)},performRotation:function(a){this.element.css("transform-origin",this.options.rotationCenterX+"% "+this.options.rotationCenterY+ - "%");this.element.css("-ms-transform-origin",this.options.rotationCenterX+"% "+this.options.rotationCenterY+"%");this.element.css("-webkit-transform-origin",this.options.rotationCenterX+"% "+this.options.rotationCenterY+"%");this.element.css("transform","rotate("+a+"rad)");this.element.css("-moz-transform","rotate("+a+"rad)");this.element.css("-webkit-transform","rotate("+a+"rad)");this.element.css("-o-transform","rotate("+a+"rad)")},getElementOffset:function(){this.performRotation(0);var a=this.element.offset(); - this.performRotation(this.elementCurrentAngle);return a},getElementCenter:function(){var a=this.getElementOffset();if(!1===this.options.rotationCenterX)var b=a.left+this.element.width()/2,a=a.top+this.element.height()/2;else b=a.left+this.element.width()/100*this.options.rotationCenterX,a=a.top+this.element.height()/100*this.options.rotationCenterY;return[b,a]},dragStart:function(a){if(this.element)return!1},startRotate:function(a){var b=this.getElementCenter();this.mouseStartAngle=Math.atan2(a.pageY- - b[1],a.pageX-b[0]);this.elementStartAngle=this.elementCurrentAngle;this.hasRotated=!1;this._propagate("start",a);c(document).bind("mousemove",this.listeners.rotateElement);c(document).bind("mouseup",this.listeners.stopRotate);return!1},rotateElement:function(a){if(!this.element||this.element.disabled)return!1;var b=this.getElementCenter(),b=Math.atan2(a.pageY-b[1],a.pageX-b[0])-this.mouseStartAngle+this.elementStartAngle;if(a.shiftKey){var c=15/180*Math.PI;0>b&&(c*=-1);b-=(b+c/2)%c-c/2}this.performRotation(b); - c=this.elementCurrentAngle;this.elementCurrentAngle=b;this._propagate("rotate",a);c!=b&&(this._trigger("rotate",a,this.ui()),this.hasRotated=!0);return!1},stopRotate:function(a){if(this.element&&!this.element.disabled)return c(document).unbind("mousemove",this.listeners.rotateElement),c(document).unbind("mouseup",this.listeners.stopRotate),this.elementStopAngle=this.elementCurrentAngle,this.hasRotated&&this._propagate("stop",a),setTimeout(function(){this.element=!1},10),!1},wheelRotate:function(a){var b= - Math.round(a.originalEvent.deltaY/10)*Math.PI/180,b=this.elementCurrentAngle+b;this.angle(b);this._trigger("rotate",a,this.ui())},_propagate:function(a,b){c.ui.plugin.call(this,a,[b,this.ui()]);"rotate"!==a&&this._trigger(a,b,this.ui())},plugins:{},ui:function(){return{api:this,element:this.element,angle:{start:this.elementStartAngle,current:this.elementCurrentAngle,stop:this.elementStopAngle}}}})})(jQuery); \ No newline at end of file diff --git a/server/public/anyplace_architect/scripts/CanvasOverlay.js b/server/public/anyplace_architect/scripts/CanvasOverlay.js index dbd1ad178..4c16c39ff 100644 --- a/server/public/anyplace_architect/scripts/CanvasOverlay.js +++ b/server/public/anyplace_architect/scripts/CanvasOverlay.js @@ -3,14 +3,21 @@ CanvasOverlay.prototype = new google.maps.OverlayView(); // https://github.com/wbyoko/angularjs-google-maps-components /** @constructor */ -function CanvasOverlay(image, map) { - - // Now initialize all properties. +function CanvasOverlay(image, map, angularScope) { + this.angularScope=angularScope; this.top = 0; this.left = 0; + + this.FullWidth = image.width; + this.FullHeight = image.height; + this.width = image.width; this.height = image.height; + // INFO this makes the image quality horrendous. + // The solution should have used scale instead of resizing. + // In any case, as a workaround, the getCanvas method builds a new HTML5 canvas + // with full image quality. while (window && (this.width > window.innerWidth || this.height > window.innerHeight)) { this.width /= 2; this.height /= 2; @@ -19,10 +26,9 @@ function CanvasOverlay(image, map) { this.image_ = image; this.map_ = map; - // We define a property to hold the canvas's - // div. We'll actually create this div - // upon receipt of the add() method so we'll - // leave it null for now. + // We define a property to hold the canvas's div. + // We'll actually create this div upon receipt of + // the add() method so we'll leave it null for now. this.div_ = null; this.canvas = null; this.ctx = null; @@ -31,18 +37,12 @@ function CanvasOverlay(image, map) { this.latlng = map.getCenter(); - - this.new_left = 0; - this.new_top = 0; - - // Explicitly call setMap on this overlay this.setMap(map); + LOG.D3("Centering map to: " + this.latlng) } CanvasOverlay.prototype.onAdd = function () { - - // Note: an overlay's receipt of add() indicates that // the map's panes are now available for attaching // the overlay to the map via the DOM. @@ -67,17 +67,14 @@ CanvasOverlay.prototype.onAdd = function () { var self = this; -// https://github.com/unlocomqx/jQuery-ui-resizable-rotation-patch/blob/master/resizable-rotation.patch.js -// fixes problems in resizing roatated image + // https://github.com/unlocomqx/jQuery-ui-resizable-rotation-patch/blob/master/resizable-rotation.patch.js + // fixes problems in resizing rotated image function n(e) { return parseInt(e, 10) || 0 } - //patch: totally based on andyzee work here, thank you + //patch: based on: andyzee //patch: https://github.com/andyzee/jquery-resizable-rotation-patch/blob/master/resizable-rotation.patch.js - //patch: search for "patch:" comments for modifications - //patch: based on version jquery-ui-1.10.3 - //patch: can be easily reproduced with your current version //patch: start of patch /** * Calculate the size correction for resized rotated element @@ -89,8 +86,7 @@ CanvasOverlay.prototype.onAdd = function () { * @returns {object} correction css object {left, top} */ jQuery.getCorrection = function(init_w, init_h, delta_w, delta_h, angle){ - //Convert angle from degrees to radians - var angle = angle * Math.PI / 180 + var angle = angle * Math.PI / 180 //Convert angle from degrees to radians //Get position after rotation with original size var x = -init_w/2; @@ -115,15 +111,12 @@ CanvasOverlay.prototype.onAdd = function () { } jQuery.ui.resizable.prototype._mouseStart = function(event) { - var curleft, curtop, cursor, o = this.options, el = this.element; this.resizing = true; - this._renderProxy(); - curleft = n(this.helper.css("left")); curtop = n(this.helper.css("top")); @@ -163,21 +156,23 @@ CanvasOverlay.prototype.onAdd = function () { this.lastData = this.originalPosition; this.aspectRatio = (typeof o.aspectRatio === "number") ? - o.aspectRatio : - ((this.originalSize.width / this.originalSize.height) || 1); + o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1); cursor = $(".ui-resizable-" + this.axis).css("cursor"); $("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor); - el.addClass("ui-resizable-resizing"); this._propagate("start", event); return true; }; jQuery.ui.resizable.prototype._mouseDrag = function(event) { - //patch: get the angle + var alwaysRespectAspectRatio = true // force to always respect image ratio + + // patch: get the angle var angle = getAngle(this.element[0]); var angle_rad = angle * Math.PI / 180; + var isShiftHold = event.shiftKey + if (alwaysRespectAspectRatio) isShiftHold = true var data, el = this.helper, props = {}, @@ -194,9 +189,7 @@ CanvasOverlay.prototype.onAdd = function () { var init_w = this.size.width; var init_h = this.size.height; - if (!trigger) { - return false; - } + if (!trigger) { return false; } //patch: cache cosine & sine var _cos = Math.cos(angle_rad); @@ -212,16 +205,12 @@ CanvasOverlay.prototype.onAdd = function () { data = trigger.apply(this, [event, dx, dy]); // Put this in the mouseDrag handler since the user can start pressing shift while resizing - this._updateVirtualBoundaries(event.shiftKey); - if (this._aspectRatio || event.shiftKey) { - data = this._updateRatio(data, event); - } + this._updateVirtualBoundaries(isShiftHold); + if (this._aspectRatio || isShiftHold ) { data = this._updateRatio(data, event); } data = this._respectSize(data, event); - //patch: backup the position var oldPosition = {left: this.position.left, top: this.position.top}; - this._updateCache(data); //patch: revert to old position @@ -254,40 +243,26 @@ CanvasOverlay.prototype.onAdd = function () { // plugins callbacks need to be called first this._propagate("resize", event); - //patch: calculate the difference in size var diff_w = init_w - this.size.width; var diff_h = init_h - this.size.height; //patch: get the offset based on angle var offset = $.getCorrection(init_w, init_h, diff_w, diff_h, angle); - //patch: update the position this.position.left += offset.left; this.position.top -= offset.top; - if (this.position.top !== prevTop) { - props.top = this.position.top + "px"; - } - if (this.position.left !== prevLeft) { - props.left = this.position.left + "px"; - } - if (this.size.width !== prevWidth) { - props.width = this.size.width + "px"; - } - if (this.size.height !== prevHeight) { - props.height = this.size.height + "px"; - } + if (this.position.top !== prevTop) { props.top = this.position.top + "px"; } + if (this.position.left !== prevLeft) { props.left = this.position.left + "px"; } + if (this.size.width !== prevWidth) { props.width = this.size.width + "px"; } + if (this.size.height !== prevHeight) { props.height = this.size.height + "px"; } el.css(props); - if (!this._helper && this._proportionallyResizeElements.length) { - this._proportionallyResize(); - } + if (!this._helper && this._proportionallyResizeElements.length) { this._proportionallyResize(); } // Call the user callback if the element was resized - if ( ! $.isEmptyObject(props) ) { - this._trigger("resize", event, this.ui()); - } + if ( ! $.isEmptyObject(props) ) { this._trigger("resize", event, this.ui()); } return false; } @@ -312,54 +287,83 @@ CanvasOverlay.prototype.onAdd = function () { var angle = Math.round(Math.atan2(b, a) * (180/Math.PI)); while(angle >= 360) angle = 360-angle; while(angle < 0) angle = 360+angle; + // LOG.D("angle: " + angle) // CLR:PM return angle; - } - else - return 0; + } else return 0; } - function _parseFloat(e) { - return isNaN(parseFloat(e)) ? 0: parseFloat(e); - } + function _parseFloat(e) { return isNaN(parseFloat(e)) ? 0: parseFloat(e); } - function _round(e) { - return Math.round((e + 0.00001) * 100) / 100 - } + function _round(e) { return Math.round((e + 0.00001) * 100) / 100 } /* end of patch functions */ + // https://github.com/godswearhats/jquery-ui-rotatable jQuery(div).resizable({ // aspectRatio: (this.image_.width / this.image_.height), ghost: true, handles: "sw, se, nw, ne", helper: "resizable-helper", aspectRatio: false, + start: function (event, ui) { + freezeMap(self); + }, stop: function (event, ui) { - self.ctx.clearRect(0, 0, self.ctx.canvas.width, self.ctx.canvas.height); + unfreezeMap(self); + self.ctx.clearRect(0, 0, self.ctx.canvas.width, self.ctx.canvas.height); self.width = ui.size.width; self.height = ui.size.height; self.setCanvasSize(); - self.ctx.save(); self.ctx.translate((self.ctx.canvas.width / 2), (self.ctx.canvas.height / 2)); - self.ctx.drawImage(self.image_, -(self.width / 2), -(self.height / 2), self.width, self.height); self.ctx.restore(); + var tmpProjection = self.getProjection() ; + if (tmpProjection != null) { + // south-west + var BL = projection.fromContainerPixelToLatLng(new google.maps.Point(self.left, self.top + self.height), true); + // north-east + var TR = projection.fromContainerPixelToLatLng(new google.maps.Point(self.left + self.width, self.top), true); + var TL = new google.maps.LatLng(BL.lat(), TR.lng()); // north-west + + LOG.D3("Resized: BOTTOM LEFT: " + BL) + LOG.D3("Resized: TOP RIGHT: " + TR) + var width_top = round(calculate_distance(TR.lat(), TR.lng(), TL.lat(), TL.lng()), 1); + var height_left = round(calculate_distance(TL.lat(), TL.lng(), BL.lat(), BL.lng()), 1); + + // for verification: + // var BR = new google.maps.LatLng(TR.lat(), BL.lng()); // south-east + // var width_bottom = round(calculate_distance(BR.lat(), BR.lng(), BL.lat(), BL.lng()), 1); + // var height_right = round(calculate_distance(TR.lat(), TR.lng(), BR.lat(), BR.lng()), 1); + // var diagonal = round(calculate_distance(TR.lat(), TR.lng(), BL.lat(), BL.lng()), 1); + var msg = "Dimensions: width: " + width_top + " height:" + height_left + " (meters)" + LOG.D(msg); + _info(self.angularScope, msg); + } } }).rotatable({ + wheelRotate: false, + start: function (event, ui) { + freezeMap(self); + }, stop: function (event, ui) { - //self.angle = ui.angle.stop; + unfreezeMap(self); + self.angle = ui.angle.degrees; } }); jQuery(div).draggable({ + start: function (event, ui) { + freezeMap(self); + }, stop: function (event, ui) { - // update the coordinates - if (projection != null) { + unfreezeMap(self); + if (projection != null) { // update the coordinates var left = $(this).position().left; var top = $(this).position().top; self.latlng = projection.fromDivPixelToLatLng(new google.maps.Point(left, top), true); + LOG.D1("Moved: left:top " + left + " " + top) } } }); @@ -376,66 +380,36 @@ CanvasOverlay.prototype.onAdd = function () { // load the floor this.initImage(); - // We add an overlay to a map via one of the map's panes. // We'll add this overlay to the overlayImage pane. var panes = this.getPanes(); panes.overlayImage.appendChild(this.div_); -//////////////////////////////////////// - var that=this; - // CHECK why that? - var container=div; - google.maps.event.addDomListener(this.get('map').getDiv(), - 'mouseleave', - function(){ - google.maps.event.trigger(container,'mouseup'); - } - ); - - - google.maps.event.addDomListener(container, - 'mousedown', - function(e){ - this.style.cursor='move'; - that.map_.set('draggable',false); - that.set('origin',e); } - ); - - google.maps.event.addDomListener(container,'mouseup',function(){ - // BUG - that.map_.set('draggable',true); - this.style.cursor='default'; - google.maps.event.removeListener(that.moveHandler); - }); - return this; } -CanvasOverlay.prototype.draw = function () { - var div = this.div_; - - if (this.canvas == null) { - alert("error creating the canvas"); - } +function freezeMap(canvasOverlay) { + document.body.style.cursor='move'; + canvasOverlay.map_.set('draggable', false); + canvasOverlay.map_.setOptions({draggable: false, zoomControl: false, scrollwheel: false, disableDoubleClickZoom: true}); } -CanvasOverlay.prototype.onRemove = function () { - this.div_.parentNode.removeChild(this.div_); +function unfreezeMap(canvasOverlay) { + document.body.style.cursor='default'; + canvasOverlay.map_.setOptions({draggable: true, zoomControl: true, scrollwheel: true, disableDoubleClickZoom: false}); } -// Note that the visibility property must be a string enclosed in quotes -CanvasOverlay.prototype.hide = function () { - if (this.div_) { - this.div_.style.visibility = 'hidden'; - } -} -CanvasOverlay.prototype.show = function () { - if (this.div_) { - this.div_.style.visibility = 'visible'; - } +CanvasOverlay.prototype.draw = function () { + var div = this.div_; + if (this.canvas == null) { alert("error creating the canvas"); } } +CanvasOverlay.prototype.onRemove = function () { this.div_.parentNode.removeChild(this.div_); } + +// Note that the visibility property must be a string enclosed in quotes +CanvasOverlay.prototype.hide = function () { if (this.div_) { this.div_.style.visibility = 'hidden'; } } +CanvasOverlay.prototype.show = function () { if (this.div_) { this.div_.style.visibility = 'visible'; } } + CanvasOverlay.prototype.toggle = function () { if (this.div_) { if (this.div_.style.visibility == 'hidden') { @@ -447,29 +421,50 @@ CanvasOverlay.prototype.toggle = function () { } CanvasOverlay.prototype.toggleDOM = function () { - if (this.getMap()) { - this.setMap(null); - } else { - this.setMap(this.map_); - } + if (this.getMap()) this.setMap(null); + else this.setMap(this.map_); } -/************************* - * CANVAS METHODS +/** + * CANVAS METHODS */ -CanvasOverlay.prototype.getCanvas = function () { - return this.canvas; -} -CanvasOverlay.prototype.getContext2d = function () { - return this.ctx; +/** + * WORKAROUND: creating a new canvas in which we place again the image + * without resizing it as this significantly decreases the quality. + * + * @returns {HTMLCanvasElement} a new canvas with the full quality of the original image upload + */ +CanvasOverlay.prototype.getCanvas = function () { + // Create a Canvas element and attach it to the DIV. + var new_canvas= document.createElement('canvas'); + new_canvas.setAttribute('width', this.FullWidth); + new_canvas.setAttribute('height', this.FullHeight); + + // scaling not needed as the original image (full quality) + // will be aligned to the already selected dimensions (from the lower-quality canvas_ + var scaleW = this.FullWidth/this.width; + var scaleH = this.FullHeight/this.height; + LOG.D4("scaleW: " + scaleW) + LOG.D4("scaleH: " + scaleH) + + var rdata = calculateRotationCorners(this.lastCenter, this.lastRads, this.FullWidth, this.FullHeight); + + var new_ctx = new_canvas.getContext('2d'); + new_ctx.canvas.width = rdata.w; + new_ctx.canvas.height = rdata.h; + + new_ctx.translate(rdata.pCenterX- rdata.left, rdata.pCenterY - rdata.top); + new_ctx.rotate(this.lastRads); + new_ctx.drawImage(this.image_, -(this.FullWidth / 2), -(this.FullHeight/ 2), this.FullWidth, this.FullHeight); + return new_canvas; } +CanvasOverlay.prototype.getContext2d = function () { return this.ctx; } -/***************************** +/** * EDITING METHODS */ - CanvasOverlay.prototype.setCanvasSize = function () { this.ctx.canvas.width = this.width; this.ctx.canvas.height = this.height; @@ -481,20 +476,66 @@ CanvasOverlay.prototype.setCanvasSize = function () { } CanvasOverlay.prototype.initImage = function () { + LOG.D4("initImage") this.setCanvasSize(); this.ctx.save(); this.ctx.translate((this.ctx.canvas.width / 2), (this.ctx.canvas.height / 2)); this.ctx.rotate(this.angle); - this.ctx.drawImage(this.image_, -(this.width / 2), -(this.height / 2), this.width, this.height); this.ctx.restore(); } +/** + * Calculate the 4 new corners for rotation + * - http://stackoverflow.com/a/622172/1066790 + * - https://en.wikipedia.org/wiki/Transformation_matrix#Rotation + * + * @param pCenter old center + * @param r radians + * @param w widdth + * @param h height + * @returns {{}} + */ +function calculateRotationCorners(pCenter, r, w, h) { + // p in the below vars stands for previous + var pLeft = pCenter.left; + var pTop = pCenter.top; + LOG.D4("calculateRotationCorners: pLeft: " + pLeft) + LOG.D4("calculateRotationCorners: pTop: " + pTop) + var pCenterX = pLeft + (w) / 2; + var pCenterY = pTop + (h) / 2; + + var topLeftX = pCenterX + (pLeft - pCenterX) * Math.cos(r) + (pTop - pCenterY) * Math.sin(r); + var topLeftY = pCenterY - (pLeft - pCenterX) * Math.sin(r) + (pTop - pCenterY) * Math.cos(r); + var topRightX = pCenterX + (pLeft + (w) - pCenterX) * Math.cos(r) + (pTop - pCenterY) * Math.sin(r); + var topRightY = pCenterY - (pLeft + (w) - pCenterX) * Math.sin(r) + (pTop - pCenterY) * Math.cos(r); + var bottomLeftX = pCenterX + (pLeft - pCenterX) * Math.cos(r) + (pTop + (h) - pCenterY) * Math.sin(r); + var bottomLeftY = pCenterY - (pLeft - pCenterX) * Math.sin(r) + (pTop + (h) - pCenterY) * Math.cos(r); + var bottomRightX = pCenterX + (pLeft + (w) - pCenterX) * Math.cos(r) + (pTop + (h) - pCenterY) * Math.sin(r); + var bottomRightY = pCenterY - (pLeft + (w) - pCenterX) * Math.sin(r) + (pTop + (h) - pCenterY) * Math.cos(r); + + // calculate new coordinates finding the top left + var maxx = Math.max(topLeftX, topRightX, bottomLeftX, bottomRightX); + var maxy = Math.max(topLeftY, topRightY, bottomLeftY, bottomRightY); + var minx = Math.min(topLeftX, topRightX, bottomLeftX, bottomRightX); + var miny = Math.min(topLeftY, topRightY, bottomLeftY, bottomRightY); + + var res = {}; + res.pCenterX= pCenterX; + res.pCenterY= pCenterY; + res.top = miny; + res.left = minx; + res.w = maxx - minx; + res.h = maxy - miny + + return res; +} + CanvasOverlay.prototype.drawBoundingCanvas = function () { - // convert degress rotation to angle radians + LOG.D3("drawBoundingCanvas") + // convert degrees rotation to angle radians var degrees = getRotationDegrees($('#canvas_editor')); - //var degrees= parseFloat($('#rot_degrees').val()); var rads = deg2rad(degrees); $('#canvas_editor').css({ @@ -502,72 +543,49 @@ CanvasOverlay.prototype.drawBoundingCanvas = function () { '-webkit-transform': 'rotate(0deg)', '-ms-transform': 'rotate(0deg)', '-o-transform': 'rotate(0deg)', - 'transform': 'rotate(0deg)' - }); + 'transform': 'rotate(0deg)'}); // get the center which we use to rotate the image // this is the center when the canvas is rotated at 0 degrees var oldCenter = getPositionData($('#canvas_editor')); - var oldLeft = oldCenter.left; - var oldTop = oldCenter.top; - var oldCenterX = oldLeft + this.width / 2; - var oldCenterY = oldTop + this.height / 2; - - // calculate the 4 new corners - http://stackoverflow.com/a/622172/1066790 - // - https://en.wikipedia.org/wiki/Transformation_matrix#Rotation - var top_left_x = oldCenterX + (oldLeft - oldCenterX) * Math.cos(rads) + (oldTop - oldCenterY) * Math.sin(rads); - var top_left_y = oldCenterY - (oldLeft - oldCenterX) * Math.sin(rads) + (oldTop - oldCenterY) * Math.cos(rads); - var top_right_x = oldCenterX + (oldLeft + this.width - oldCenterX) * Math.cos(rads) + (oldTop - oldCenterY) * Math.sin(rads); - var top_right_y = oldCenterY - (oldLeft + this.width - oldCenterX) * Math.sin(rads) + (oldTop - oldCenterY) * Math.cos(rads); - var bottom_left_x = oldCenterX + (oldLeft - oldCenterX) * Math.cos(rads) + (oldTop + this.height - oldCenterY) * Math.sin(rads); - var bottom_left_y = oldCenterY - (oldLeft - oldCenterX) * Math.sin(rads) + (oldTop + this.height - oldCenterY) * Math.cos(rads); - var bottom_right_x = oldCenterX + (oldLeft + this.width - oldCenterX) * Math.cos(rads) + (oldTop + this.height - oldCenterY) * Math.sin(rads); - var bottom_right_y = oldCenterY - (oldLeft + this.width - oldCenterX) * Math.sin(rads) + (oldTop + this.height - oldCenterY) * Math.cos(rads); - // calculate new coordinates finding the top left - var maxx = Math.max(top_left_x, top_right_x, bottom_left_x, bottom_right_x); - var maxy = Math.max(top_left_y, top_right_y, bottom_left_y, bottom_right_y); - var minx = Math.min(top_left_x, top_right_x, bottom_left_x, bottom_right_x); - var miny = Math.min(top_left_y, top_right_y, bottom_left_y, bottom_right_y); - var newTop = miny; - var newLeft = minx; + this.lastCenter=oldCenter; + this.lastRads = rads; + LOG.D5("drawBoundingCanvas: lastRads: " + rads) - var w = maxx - minx; - var h = maxy - miny; + var rdata = calculateRotationCorners(oldCenter, rads, this.width, this.height); // move the canvas to the new top left position $('#canvas_editor').css({ - 'top': newTop + 'px', - 'left': newLeft + 'px' + 'top': rdata.top + 'px', + 'left': rdata.left + 'px' }) - this.ctx.canvas.width = w; - this.ctx.canvas.height = h; + this.ctx.canvas.width = rdata.w; + this.ctx.canvas.height = rdata.h; this.ctx.save(); this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); - this.ctx.translate(oldCenterX - newLeft, oldCenterY - newTop); + this.ctx.translate(rdata.pCenterX - rdata.left, rdata.pCenterY - rdata.top); this.ctx.rotate(rads); this.ctx.drawImage(this.image_, -(this.width / 2), -(this.height / 2), this.width, this.height); this.ctx.restore(); // we should now update the coordinates for the new image - this.top = newTop; - this.left = newLeft; + this.top = rdata.top; + this.left = rdata.left; var projection; if ((projection = this.getProjection()) != null) { - this.bottom_left_coords = projection.fromContainerPixelToLatLng(new google.maps.Point(this.left, this.top + h), true); - this.top_right_coords = projection.fromContainerPixelToLatLng(new google.maps.Point(this.left + w, this.top), true); + this.bottom_left_coords = projection.fromContainerPixelToLatLng(new google.maps.Point(this.left, this.top + rdata.h), true); + this.top_right_coords = projection.fromContainerPixelToLatLng(new google.maps.Point(this.left + rdata.w, this.top), true); + LOG.D("drawBoundingCanvas: BOTTOM LEFT: " + this.bottom_left_coords) + LOG.D("drawBoundingCanvas: TOP RIGHT: " + this.top_right_coords) } } -/*************************************** +/** * HELPER FUNCTIONS */ -function deg2rad(deg) { - return deg * Math.PI / 180; -} -function rad2deg(rad) { - return rad / Math.PI * 180; -} +function deg2rad(deg) { return deg * Math.PI / 180; } +function rad2deg(rad) { return rad / Math.PI * 180; } function getRotationDegrees(obj) { var matrix = obj.css("-webkit-transform") || @@ -583,16 +601,18 @@ function getRotationDegrees(obj) { var d = values[3]; var sin = b / scale; - var scale = Math.sqrt(a * a + b * b); var angle = Math.round(Math.atan2(b, a) * (180 / Math.PI)); //var angle = Math.atan2(b, a) * (180/Math.PI); } else { var angle = 0; } + + LOG.D4("getRotationDegrees: angle: " + angle) return angle; } -// src= http://jsfiddle.net/Y8d6k/ + +// http://jsfiddle.net/Y8d6k/ var getPositionData = function (el) { return $.extend({ width: el.outerWidth(false), diff --git a/server/public/anyplace_architect/style/jquery.ui.rotatable.css b/server/public/anyplace_architect/style/jquery.ui.rotatable.css index 169e85b87..26d7d6931 100644 --- a/server/public/anyplace_architect/style/jquery.ui.rotatable.css +++ b/server/public/anyplace_architect/style/jquery.ui.rotatable.css @@ -1,10 +1,10 @@ .ui-rotatable-handle { - height: 16px; - width: 16px; + height: 28px; + width: 28px; cursor: pointer; - background-image: url(../images/jquery-ui-rotatable/alternate_rotate.png); + background-image: url(../images/icon-rotate.svg); background-size: 100%; position: absolute; - left: 50%; - top: -18px; + left: calc(50% - 14px); + bottom: -14px; } \ No newline at end of file diff --git a/server/public/anyplace_architect/style/map.css b/server/public/anyplace_architect/style/map.css index b147c0266..1feeb33e3 100644 --- a/server/public/anyplace_architect/style/map.css +++ b/server/public/anyplace_architect/style/map.css @@ -100,7 +100,7 @@ footer{ display: none; background-color: #fff; padding: 0 11px 0 13px; - border-width: 2px; + border-width: 3px; border-color: gray; border-radius: 5px; width: 400px; @@ -112,8 +112,8 @@ footer{ } #pac-input:focus { - border-color: #202B7B; - border-width: 2px; + border-color: #03878E; + border-width: 3px; border-radius: 5px; margin-left: -2px; padding-left: 15px; /* Regular padding-left + 1. */ @@ -499,7 +499,7 @@ padding: 8px !important; .btn-quickactions { - border: gray !important; + border: darkgray !important; border-style: solid !important; border-radius: 4px; margin-top: 2px; @@ -510,7 +510,7 @@ padding: 8px !important; /* Show border colour when unselected */ .quickaction-selected .btn-quickactions { - border-color: #202B7B !important; + border-color: #03878E !important; } /* Fixed placement of buttons */ @@ -577,35 +577,19 @@ margin-left: -0.15em; max-width:none; } -/* */ -/* .draggable-border-green { */ - /* border: 3px solid green; */ - /* border-radius: 6px; */ - /* padding: 4px; */ - /* display: inline-block; */ -/* } */ -/* .draggable-border-blue{ */ - /* border: 3px solid #202B7B; */ - /* border-radius: 6px; */ - /* padding: 4px; */ - /* display: inline-block; */ -/* } */ - .draggable-border { - border: 2px solid #ddd; + border: 3px solid #ddd; border-radius: 6px; padding: 4px; display: inline-block; } .draggable-border-selected { - border: 2px solid #202B7B; + border: 3px solid #03878E; /*border-radius: 6px;*/ /*padding: 4px;*/ /*display: inline-block;*/ } - - .divider-top-5 { margin-top: 5px; } @@ -757,7 +741,7 @@ margin-left: -0.15em; font: normal normal normal normal 18px/27px 'Helvetica Neue', Arial, Helvetica, sans-serif; margin: 10px 0px 0px 10px; position: absolute; - top: 70%; + top: 80%; right: 10px; } diff --git a/server/public/anyplace_viewer/app.js b/server/public/anyplace_viewer/app.js index 94a218e60..cfb40cdfb 100644 --- a/server/public/anyplace_viewer/app.js +++ b/server/public/anyplace_viewer/app.js @@ -350,12 +350,12 @@ app.factory('AnyplaceService', ['$rootScope', '$q', function ($rootScope, $q) { }; anyService.getFloorNumber = function () { - if (!this.selectedFloor) { - return 'N/A'; - } + if (!this.selectedFloor) { return 'N/A'; } return String(this.selectedFloor.floor_number); }; + anyService.hasSelectedFloor= function () { return this.selectedFloor !== undefined; }; + anyService.setBuilding = function (b) { this.selectedBuilding = b; }; diff --git a/server/public/anyplace_viewer/controllers/ControlBarController.js b/server/public/anyplace_viewer/controllers/ControlBarController.js index 7d033640e..7105ec165 100644 --- a/server/public/anyplace_viewer/controllers/ControlBarController.js +++ b/server/public/anyplace_viewer/controllers/ControlBarController.js @@ -34,7 +34,9 @@ * */ -app.controller('ControlBarController', ['$scope', '$rootScope', '$routeParams', '$location', '$compile', 'GMapService', 'AnyplaceService', function ($scope, $rootScope, $routeParams, $location, $compile, GMapService, AnyplaceService) { +app.controller('ControlBarController', + ['$scope', '$rootScope', '$routeParams', '$location', '$compile', 'GMapService', 'AnyplaceService', + function ($scope, $rootScope, $routeParams, $location, $compile, GMapService, AnyplaceService) { $scope.anyService = AnyplaceService; $scope.gmapService = GMapService; diff --git a/server/public/anyplace_viewer/controllers/FloorController.js b/server/public/anyplace_viewer/controllers/FloorController.js index ae6ca8bd8..76b3fe336 100644 --- a/server/public/anyplace_viewer/controllers/FloorController.js +++ b/server/public/anyplace_viewer/controllers/FloorController.js @@ -1,5 +1,5 @@ /* - * AnyPlace: A free and open Indoor Navigation Service with superb accuracy! + * Anyplace: A free and open Indoor Navigation Service with superb accuracy! * * Anyplace is a first-of-a-kind indoor information service offering GPS-less * localization, navigation and search inside buildings using ordinary smartphones. @@ -100,7 +100,6 @@ app.controller('FloorController', ['$scope', '$compile', 'AnyplaceService', 'GMa }; $scope.fetchAllFloorsForBuilding = function (b) { - // TODO: check for b.buid var jsonReq = AnyplaceService.jsonReq; jsonReq.buid = b.buid; @@ -108,7 +107,6 @@ app.controller('FloorController', ['$scope', '$compile', 'AnyplaceService', 'GMa promise.then( function (resp) { $scope.xFloors = resp.data.floors; - $scope.xFloors = $scope.xFloors.sort(function (a, b) { return parseInt(a.floor_number) - parseInt(b.floor_number) }); @@ -157,7 +155,7 @@ app.controller('FloorController', ['$scope', '$compile', 'AnyplaceService', 'GMa } }, function (resp) { - ShowError($scope, resp, "Something went wrong while fetching all floors", true); + ShowError($scope, resp, ERR_FETCH_ALL_FLOORS, true); } ); }; @@ -169,10 +167,12 @@ app.controller('FloorController', ['$scope', '$compile', 'AnyplaceService', 'GMa var promise = AnyplaceAPIService.downloadFloorPlan(this.anyService.jsonReq, buid, floor_number); promise.then( function (resp) { + LOG.D3("rendering floorplan: " + buid + " fl: " + floor_number) // in case the building was switched too fast, don't load the old building's // floorplan - if (buid == $scope.anyService.selectedBuilding.buid && floor_number == $scope.anyService.selectedFloor.floor_number) { + if (buid == $scope.anyService.selectedBuilding.buid && + floor_number == $scope.anyService.selectedFloor.floor_number) { $scope.data.floor_plan_file = null; $scope.data.floor_plan = null; @@ -204,9 +204,7 @@ app.controller('FloorController', ['$scope', '$compile', 'AnyplaceService', 'GMa $scope.isCanvasOverlayActive = false; $scope.setFloorPlan = function () { - if (!canvasOverlay) { - return; - } + if (!canvasOverlay) { return; } if (AnyplaceService.getBuildingId() === null || AnyplaceService.getBuildingId() === undefined) { console.log('building is undefined'); @@ -235,6 +233,7 @@ app.controller('FloorController', ['$scope', '$compile', 'AnyplaceService', 'GMa $scope.data.floor_plan_coords.bottom_left_lng = bl.lng(); $scope.data.floor_plan_coords.top_right_lat = tr.lat(); $scope.data.floor_plan_coords.top_right_lng = tr.lng(); + var data = canvasOverlay.getCanvas().toDataURL("image/png"); // defaults to png $scope.data.floor_plan_base64_data = data; var imageBounds = new google.maps.LatLngBounds( @@ -247,7 +246,6 @@ app.controller('FloorController', ['$scope', '$compile', 'AnyplaceService', 'GMa $scope.isCanvasOverlayActive = false; $scope.addFloorObject(newFl, $scope.anyService.selectedBuilding, $scope.data); - }; $scope.removeFloorPlan = function () { diff --git a/server/public/anyplace_viewer/style/floorplan.css b/server/public/anyplace_viewer/style/floorplan.css deleted file mode 100644 index 3baac5d5c..000000000 --- a/server/public/anyplace_viewer/style/floorplan.css +++ /dev/null @@ -1,11 +0,0 @@ -.ui-resizable-se, .ui-resizable-sw, .ui-resizable-nw, .ui-resizable-ne { - background-color: red; -} - -.ui-resizable-se { - cursor: se-resize; - width: 9px; - height: 9px; - right: -5px; - bottom: -5px; -} \ No newline at end of file diff --git a/server/public/anyplace_viewer_campus/app.js b/server/public/anyplace_viewer_campus/app.js index c52790499..22daba0cd 100644 --- a/server/public/anyplace_viewer_campus/app.js +++ b/server/public/anyplace_viewer_campus/app.js @@ -326,42 +326,30 @@ app.factory('AnyplaceService', ['$rootScope', '$q', function ($rootScope, $q) { // notifications anyService.alerts = []; - anyService.getBuilding = function () { - return this.selectedBuilding; - }; + anyService.getBuilding = function () { return this.selectedBuilding; }; anyService.getBuildingId = function () { - if (!this.selectedBuilding) { - return undefined; - } + if (!this.selectedBuilding) { return undefined; } return this.selectedBuilding.buid; }; anyService.getBuildingName = function () { - if (!this.selectedBuilding) { - return 'N/A'; - } + if (!this.selectedBuilding) { return 'N/A'; } return this.selectedBuilding.name; }; - anyService.getFloor = function () { - return this.selectedFloor; - }; + anyService.getFloor = function () { return this.selectedFloor; }; + + anyService.hasSelectedFloor= function () { return this.selectedFloor !== undefined; }; anyService.getFloorNumber = function () { - if (!this.selectedFloor) { - return 'N/A'; - } + if (!this.selectedFloor) { return 'N/A'; } return String(this.selectedFloor.floor_number); }; - anyService.setBuilding = function (b) { - this.selectedBuilding = b; - }; + anyService.setBuilding = function (b) { this.selectedBuilding = b; }; - anyService.setFloor = function (f) { - this.selectedFloor = f; - }; + anyService.setFloor = function (f) { this.selectedFloor = f; }; anyService.setSelectedFloorByNum = function (fnum) { this.selectedFloor = anyService.availableFloors[fnum]; diff --git a/server/public/anyplace_viewer_campus/controllers/FloorController.js b/server/public/anyplace_viewer_campus/controllers/FloorController.js index f47355f72..9b570c71a 100644 --- a/server/public/anyplace_viewer_campus/controllers/FloorController.js +++ b/server/public/anyplace_viewer_campus/controllers/FloorController.js @@ -166,7 +166,7 @@ app.controller('FloorController', ['$scope', '$compile', 'AnyplaceService', 'GMa } }, function (resp) { - ShowError($scope, resp, "Something went wrong while fetching all floors", true); + ShowError($scope, resp, ERR_FETCH_ALL_FLOORS, true); } ); }; diff --git a/server/public/anyplace_viewer_campus/style/floorplan.css b/server/public/anyplace_viewer_campus/style/floorplan.css deleted file mode 100644 index 3baac5d5c..000000000 --- a/server/public/anyplace_viewer_campus/style/floorplan.css +++ /dev/null @@ -1,11 +0,0 @@ -.ui-resizable-se, .ui-resizable-sw, .ui-resizable-nw, .ui-resizable-ne { - background-color: red; -} - -.ui-resizable-se { - cursor: se-resize; - width: 9px; - height: 9px; - right: -5px; - bottom: -5px; -} \ No newline at end of file diff --git a/server/public/anyplace_architect/style/floorplan.css b/server/public/shared/css/floorplan.css similarity index 77% rename from server/public/anyplace_architect/style/floorplan.css rename to server/public/shared/css/floorplan.css index 3baac5d5c..dfad3ef2f 100644 --- a/server/public/anyplace_architect/style/floorplan.css +++ b/server/public/shared/css/floorplan.css @@ -1,5 +1,6 @@ .ui-resizable-se, .ui-resizable-sw, .ui-resizable-nw, .ui-resizable-ne { - background-color: red; + background-color: darkred; + border-radius: 2px; } .ui-resizable-se { diff --git a/server/public/shared/images/icon-rotate-orig.png b/server/public/shared/images/icon-rotate-orig.png new file mode 100644 index 000000000..1bed11ce3 Binary files /dev/null and b/server/public/shared/images/icon-rotate-orig.png differ diff --git a/server/public/shared/images/icon-rotate.svg b/server/public/shared/images/icon-rotate.svg new file mode 100644 index 000000000..52099e628 --- /dev/null +++ b/server/public/shared/images/icon-rotate.svg @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/server/public/shared/images/vessel-icon.png b/server/public/shared/images/vessel-icon.png new file mode 100644 index 000000000..eb5d2e05c Binary files /dev/null and b/server/public/shared/images/vessel-icon.png differ diff --git a/server/public/shared/images/vessel-icon.svg b/server/public/shared/images/vessel-icon.svg new file mode 100644 index 000000000..7ca8429f9 --- /dev/null +++ b/server/public/shared/images/vessel-icon.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/server/public/shared/js/anyplace-core-js/api.js b/server/public/shared/js/anyplace-core-js/api.js index 33874966e..c87c0346f 100644 --- a/server/public/shared/js/anyplace-core-js/api.js +++ b/server/public/shared/js/anyplace-core-js/api.js @@ -68,8 +68,8 @@ API.Mapping.RADIO_HEATMAP_BY_TIME_TILES_URL = API.url + API.Mapping.RADIO_HEATMA API.Mapping.RADIOMAP_DELETE = "/api/auth/radiomap/delete" API.Mapping.RADIOMAP_DELETE_URL = API.url + API.Mapping.RADIOMAP_DELETE; -API.Mapping.RADIOMAP_FLOOR_ALL = "/radiomap/floor/all"; -API.Mapping.RADIOMAP_FLOOR_ALL_URL = API.url + API.Mapping.RADIOMAP_FLOOR_ALL; +API.Mapping.RADIOMAP_FLOORS = "/radiomap/floors"; +API.Mapping.RADIOMAP_FLOORS_URL = API.url + API.Mapping.RADIOMAP_FLOORS; API.Mapping.SPACE_ADD = "/auth/mapping/space/add"; API.Mapping.SPACE_ADD_URL = API.url + API.Mapping.SPACE_ADD; @@ -125,7 +125,7 @@ API.Mapping.ALL_POIS_URL = API.url + API.Mapping.ALL_POIS; API.Mapping.CONNECTION_ADD = "/auth/mapping/connection/add"; API.Mapping.CONNECTION_ADD_URL = API.url + API.Mapping.CONNECTION_ADD; -API.Mapping.CONNECTION_DELETE = "/mapping/connection/delete"; +API.Mapping.CONNECTION_DELETE = "/auth/mapping/connection/delete"; API.Mapping.CONNECTION_DELETE_URL = API.url + API.Mapping.CONNECTION_DELETE; API.Mapping.CONNECTION_ALL_FLOOR = "/mapping/connection/floor/all"; API.Mapping.CONNECTION_ALL_FLOOR_URL = API.url + API.Mapping.CONNECTION_ALL_FLOOR; @@ -169,7 +169,7 @@ app.factory('AnyplaceAPIService', ['$http', '$q', 'formDataObject', function ($h }; apiService.getRadioHeatmapRSS_1 = function (json_req) { - LOG.D2("getRadioHeatmapRSS_1"); + LOG.D3("getRadioHeatmapRSS_1"); return $http({ method: "POST", url: API.Mapping.RADIO_HEATMAP_RSS_URL_1, @@ -182,7 +182,7 @@ app.factory('AnyplaceAPIService', ['$http', '$q', 'formDataObject', function ($h }; apiService.getRadioHeatmapRSS_2 = function (json_req) { - LOG.D2("getRadioHeatmapRSS_2"); + LOG.D3("getRadioHeatmapRSS_2"); return $http({ method: "POST", url: API.Mapping.RADIO_HEATMAP_RSS_URL_2, @@ -195,7 +195,7 @@ app.factory('AnyplaceAPIService', ['$http', '$q', 'formDataObject', function ($h }; apiService.getRadioHeatmapRSS_3 = function (json_req) { - LOG.D2("getRadioHeatmapRSS_3"); + LOG.D3("getRadioHeatmapRSS_3"); return $http({ method: "POST", url: API.Mapping.RADIO_HEATMAP_RSS_URL_3, @@ -208,7 +208,7 @@ app.factory('AnyplaceAPIService', ['$http', '$q', 'formDataObject', function ($h }; apiService.getRadioHeatmapRSS_3_Tiles = function (json_req) { - LOG.D2("getRadioHeatmapRSS_3_Tiles"); + LOG.D3("getRadioHeatmapRSS_3_Tiles"); return $http({ method: "POST", url: API.Mapping.RADIO_HEATMAP_RSS_URL_3_TILES, @@ -222,7 +222,7 @@ app.factory('AnyplaceAPIService', ['$http', '$q', 'formDataObject', function ($h apiService.getRadioHeatmapRSSByTime_1 = function (json_req) { - LOG.D2("getRadioHeatmapRSSByTime_1"); + LOG.D3("getRadioHeatmapRSSByTime_1"); return $http({ method: "POST", url: API.Mapping.RADIO_HEATMAP_RSS_BY_TIME_URL_1, @@ -235,7 +235,7 @@ app.factory('AnyplaceAPIService', ['$http', '$q', 'formDataObject', function ($h }; apiService.getRadioHeatmapRSSByTime_2 = function (json_req) { - LOG.D2("getRadioHeatmapRSSByTime_2"); + LOG.D3("getRadioHeatmapRSSByTime_2"); return $http({ method: "POST", url: API.Mapping.RADIO_HEATMAP_RSS_BY_TIME_URL_2, @@ -248,7 +248,7 @@ app.factory('AnyplaceAPIService', ['$http', '$q', 'formDataObject', function ($h }; apiService.getRadioHeatmapRSSByTime_3 = function (json_req) { - LOG.D2("getRadioHeatmapRSSByTime_3"); + LOG.D3("getRadioHeatmapRSSByTime_3"); return $http({ method: "POST", url: API.Mapping.RADIO_HEATMAP_RSS_BY_TIME_URL_3, @@ -261,7 +261,7 @@ app.factory('AnyplaceAPIService', ['$http', '$q', 'formDataObject', function ($h }; apiService.getRadioHeatmapRSSByTime_Tiles = function (json_req) { - LOG.D2("getRadioHeatmapRSSByTime_Tiles"); + LOG.D3("getRadioHeatmapRSSByTime_Tiles"); return $http({ method: "POST", url: API.Mapping.RADIO_HEATMAP_BY_TIME_TILES_URL, @@ -378,9 +378,10 @@ app.factory('AnyplaceAPIService', ['$http', '$q', 'formDataObject', function ($h }; apiService.getRadioByBuildingFloorAll = function (json_req) { + LOG.D3("getRadioByBuildingFloorAll") return $http({ method: "POST", - url: API.Mapping.RADIOMAP_FLOOR_ALL_URL, + url: API.Mapping.RADIOMAP_FLOORS_URL, data: json_req }).success(function (data, status) { return data; @@ -389,17 +390,18 @@ app.factory('AnyplaceAPIService', ['$http', '$q', 'formDataObject', function ($h }); }; - apiService.getRadioByBuildingFloorTxt = function (json_req) { - return $http({ - method: "POST", - url: API.Mapping.RADIOMAP_FLOOR_ALL_TXT_URL, - data: json_req - }).success(function (data, status) { - return data; - }).error(function (data, status) { - return data; - }); - }; + // UNUSED + // apiService.getRadioByBuildingFloorTxt = function (json_req) { + // return $http({ + // method: "POST", + // url: API.Mapping.RADIOMAP_FLOOR_ALL_TXT_URL, + // data: json_req + // }).success(function (data, status) { + // return data; + // }).error(function (data, status) { + // return data; + // }); + // }; /************************************************** * BUILDING FUNCTIONS @@ -553,7 +555,6 @@ app.factory('AnyplaceAPIService', ['$http', '$q', 'formDataObject', function ($h /**************************************************** * FLOOR FUNCTIONS */ - apiService.addFloor = function (json_req) { return $http({ method: "POST", @@ -589,11 +590,10 @@ app.factory('AnyplaceAPIService', ['$http', '$q', 'formDataObject', function ($h }).error(function (data, status) { return data; }); - }; - apiService.uploadFloorPlan = function (json_req, file) { + LOG.D3("uploadFloorPlan") return $http({ method: "POST", url: API.Mapping.FLOOR_PLAN_UPLOAD_URL, @@ -613,6 +613,7 @@ app.factory('AnyplaceAPIService', ['$http', '$q', 'formDataObject', function ($h }; apiService.uploadFloorPlan64 = function (json_req, file) { + LOG.D3("uploadFloorPlan64") var fl_data = file.replace('data:image/png;base64,', ''); var uarray = LPUtils.Base64Binary.decode(fl_data); var blob = new Blob([uarray]); @@ -648,10 +649,12 @@ app.factory('AnyplaceAPIService', ['$http', '$q', 'formDataObject', function ($h }); }; - apiService.downloadFloorPlanAll = function (json_req, buid, floor_number) { + apiService.downloadFloorPlanAll = function (json_req, buid, floors) { + LOG.D("downloadFloorPlanAll") + LOG.D(floors) return $http({ method: "POST", - url: API.Mapping.FLOOR_PLAN_DOWNLOAD_URL_ALL + buid + "/" + floor_number, + url: API.Mapping.FLOOR_PLAN_DOWNLOAD_URL_ALL + buid + "/" + floors, data: json_req }).success(function (data, status) { return data; diff --git a/server/public/shared/js/anyplace-core-js/const.js b/server/public/shared/js/anyplace-core-js/const.js index f7d0f1679..b58ea41df 100644 --- a/server/public/shared/js/anyplace-core-js/const.js +++ b/server/public/shared/js/anyplace-core-js/const.js @@ -6,15 +6,17 @@ var DEFAULT_MAP_TILES = "CartoLight"; // MESSAGES //// Error messages -ERR_FETCH_BUILDINGS="Something went wrong while fetching buildings."; -ERR_FETCH_ALL_FLOORS="Something went wrong while fetching all floors."; -ERR_USER_AUTH="Could not authorize user. Please refresh."; -ERR_FETCH_FINGERPRINTS="Something went wrong while fetching fingerprints."; -ERR_GEOLOC_DEVICE_SETTINGS="Please enable location access."; +ERR_FETCH_BUILDINGS="Error while fetching buildings"; +ERR_FETCH_ALL_FLOORS="Error while fetching floors"; +ERR_FETCH_ALL_FLOORPLANS="Error while fetching floorplans"; +ERR_FETCH_ALL_RADIOMAPS="Error while fetching radiomaps"; +ERR_USER_AUTH="Could not authorize user. Please refresh"; +ERR_FETCH_FINGERPRINTS="Error while fetching fingerprints"; +ERR_GEOLOC_DEVICE_SETTINGS="Please enable location access"; ERR_GEOLOC_NET_OR_SATELLITES="Position unavailable. The network is down or the positioning satellites couldn't be contacted."; -ERR_GEOLOC_TIMEOUT="Timeout. The request for retrieving your Geolocation was timed out."; -ERR_GEOLOC_UNKNOWN="There was an error while retrieving your Geolocation. Please try again."; -ERR_GEOLOC_NOT_SUPPORTED="The Geolocation feature is not supported by this browser."; +ERR_GEOLOC_TIMEOUT="Timeout on the geolocation request."; +ERR_GEOLOC_UNKNOWN="Error while geolocating. Please try again."; +ERR_GEOLOC_NOT_SUPPORTED="Geolocation not supported by the browser."; WARN_NO_FINGERPRINTS="This floor seems not to be FingerPrint mapped. Download the Anyplace app from the Google Play store to map the floor."; WARN_ACCES_REMOVED="ACCES map removed."; \ No newline at end of file diff --git a/server/public/shared/js/anyplace-core-js/shared.js b/server/public/shared/js/anyplace-core-js/shared.js index 70a5eee84..e804ef3f0 100644 --- a/server/public/shared/js/anyplace-core-js/shared.js +++ b/server/public/shared/js/anyplace-core-js/shared.js @@ -1,5 +1,5 @@ /* - * AnyPlace: A free and open Indoor Navigation Service with superb accuracy! + * Anyplace: A free and open Indoor Navigation Service with superb accuracy! * * Anyplace is a first-of-a-kind indoor information service offering GPS-less * localization, navigation and search inside buildings using ordinary smartphones. @@ -43,10 +43,13 @@ var IMG_ACCESS_POINT_ARCHITECT = 'build/images/wireless-router-icon-bg.png'; var IMG_BUILDING_ARCHITECT = 'build/images/building-icon.png'; +var IMG_VESSEL_ARCHITECT = 'build/images/vessel-icon.png'; // PM: For some reason different dimensions are used for viewer var IMG_BUILDING_VIEWER = 'build/images/building-icon-viewer.png'; var IMG_FINGERPRINT_RED_SPOT= 'build/images/red_dot.png'; +var DEFAULT_AUTOHIDE=5000 + // Activate tooltips $('document').ready(function(){ // modal focus fix @@ -65,37 +68,38 @@ function __addAlert(scope, level, msg) { // See more here: https://stackoverflow.com/a/14963641/776345 // msg = msg.replace(/(?:\r\n|\r|\n)/g, '\n'); scope.anyService.addAlert(level, msg); -}; +} -function _err(scope, msg) { - __addAlert(scope, 'danger', msg); -}; +var _err = function (scope, msg) { __addAlert(scope, 'danger', msg); } +var _suc = function (scope, msg) { __addAlert(scope, 'success', msg); } +var _info = function (scope, msg) { __addAlert(scope, "info", msg); } +var _warn = function (scope, msg) { __addAlert(scope, 'warning', msg); } -var _suc = function (scope, msg) { - __addAlert(scope, 'success', msg); -}; +var _warn_autohide = function (scope, msg) { _warn_autohide_timeout(scope, msg, DEFAULT_AUTOHIDE); } +var _info_autohide = function (scope, msg) { _info_autohide_timeout(scope, msg, DEFAULT_AUTOHIDE); } +var _suc_autohide = function (scope, msg) { _suc_autohide_timeout(scope, msg, DEFAULT_AUTOHIDE); } -var _warn = function (scope, msg) { - __addAlert('warning', msg); +var _warn_autohide_timeout = function (scope, msg, timeout) { + _msg_autohide(scope, msg, 'warning', timeout) }; -var _warn_autohide = function (scope, msg) { - __addAlert(scope, 'warning', msg) - window.setTimeout(function() { - $(".alert-warning").fadeTo(500, 0).slideUp(500, function(){ - $(this).remove(); - }); - }, 5000); -}; +var _info_autohide_timeout = function (scope, msg, timeout) { + _msg_autohide(scope, msg, 'info', timeout) +} + +var _suc_autohide_timeout = function (scope, msg, timeout) { + _msg_autohide(scope, msg, 'success', timeout) +} + +var _msg_autohide = function (scope, msg, msgType, timeout) { + __addAlert(scope, msgType, msg) + window.setTimeout(function() { + $(".alert-"+msgType).fadeTo(500, 0).slideUp(500, function(){ + $(this).remove(); + }); + }, timeout); +} -var _info = function (scope, msg) { - __addAlert(scope, 'info', msg); - window.setTimeout(function() { - $(".alert-info").fadeTo(500, 0).slideUp(500, function(){ - $(this).remove(); - }); - }, 10000); -}; function _ShowAlert(scope, func, response, defaultMsg, showDefaultMessage) { var data = response.data; @@ -184,6 +188,21 @@ function getMapsIconBuildingArchitect(gmap, latLong) { }); } +// for items that existed on the map +function getMapsIconVesselArchitect(gmap, latLong) { + return new google.maps.Marker({ + position: latLong, + map: gmap, + icon: new google.maps.MarkerImage( + IMG_VESSEL_ARCHITECT, + null, /* size is determined at runtime */ + null, /* origin is 0,0 */ + null, /* anchor is bottom center of the scaled image */ + new google.maps.Size(54, 54)), + draggable: false + }); +} + function getMapsIconFingerprint(gmaps, fingerPrintsData) { var size = new google.maps.Size(25, 25); return new google.maps.Marker({ @@ -270,12 +289,98 @@ function getPrettyVersion(version) { return s; } +var regex_ck_lat = /^(-?[1-8]?\d(?:\.\d{1,18})?|90(?:\.0{1,18})?)$/; +var regex_ck_lon = /^(-?(?:1[0-7]|[1-9])?\d(?:\.\d{1,18})?|180(?:\.0{1,18})?)$/; + +/** + * accepts coordinates in google maps format: + * @param str e.g., '57.693431580156464, 11.913933480401484' + * + * @returns {{}|null} + */ +function get_coordinates(str){ + LOG.D4("get_coordinates: " + str) + var spaces = str.split(" "); + if (spaces.length === 2) { + var lat = spaces[0].slice(0, -1); // remove last comma + var lon = spaces[1] + LOG.D3("lat: " + lat) + LOG.D3("lon: " + lon) + if (check_lat_lon(lat, lon)) { + return { + lat: parseFloat(spaces[0]), + lng: parseFloat(spaces[1]) + } + } + } + return null; +} + +function check_lat_lon(lat, lon){ + var validLat = regex_ck_lat.test(lat); + var validLon = regex_ck_lon.test(lon); + if(validLat && validLon) { + return true; + } else { + return false; + } +} + + +// Haversine formula: https://www.geodatasource.com/developers/javascript +/** + * //::: This routine calculates the distance between two points (given the ::: + //::: latitude/longitude of those points). It is being used to calculate ::: + //::: the distance between two locations using GeoDataSource (TM) prodducts ::: + //::: ::: + //::: Definitions: ::: + //::: South latitudes are negative, east longitudes are positive ::: + //::: ::: + //::: Passed to function: ::: + //::: lat1, lon1 = Latitude and Longitude of point 1 (in decimal degrees) ::: + //::: lat2, lon2 = Latitude and Longitude of point 2 (in decimal degrees) ::: + * @param lat1 + * @param lon1 + * @param lat2 + * @param lon2 + * @returns {number} + */ +function calculate_distance(lat1, lon1, lat2, lon2) { + if ((lat1 == lat2) && (lon1 == lon2)) { return 0; } + else { + var radlat1 = Math.PI * lat1/180; + var radlat2 = Math.PI * lat2/180; + var theta = lon1-lon2; + var radtheta = Math.PI * theta/180; + var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); + if (dist > 1) { + dist = 1; + } + dist = Math.acos(dist); + dist = dist * 180/Math.PI; + dist = dist * 60 * 1.1515; + + // convert to kilometers (from miles) + dist = dist * 1.609344 + // convert to meters + dist = dist*1000; + return dist; + } +} + +function round(num, decimal_points) { + if (decimal_points === 0) return num; + var tmp = Math.pow(10, decimal_points); + return Math.round(num*tmp)/tmp; +} + var LOG = {}; LOG.level = 2; LOG.DBG1 = function() { return 1 <= LOG.level; } LOG.DBG2 = function() { return 2 <= LOG.level; } LOG.DBG3 = function() { return 3 <= LOG.level; } LOG.DBG4 = function() { return 4 <= LOG.level; } +LOG.DBG5 = function() { return 5 <= LOG.level; } LOG.W = function(msg) { console.log("WARN: " + msg);} LOG.E = function(msg) { console.log("ERR: " + msg);} @@ -285,4 +390,5 @@ LOG.D1 = function(msg) { if (LOG.DBG1()) console.log(msg);} LOG.D2 = function(msg) { if (LOG.DBG2()) console.log(msg);} LOG.D3 = function(msg) { if (LOG.DBG3()) console.log(msg);} LOG.D4 = function(msg) { if (LOG.DBG4()) console.log(msg);} +LOG.D5 = function(msg) { if (LOG.DBG5()) console.log(msg);}