diff --git a/DESCRIPTION b/DESCRIPTION
index 1665f752..14a00fca 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -21,7 +21,9 @@ Imports:
magrittr
Suggests:
sf,
- sp
+ sp,
+ testthat (>= 3.0.0)
URL: https://github.com/trafficonese/leaflet.extras2
BugReports: https://github.com/trafficonese/leaflet.extras2/issues
RoxygenNote: 7.3.1
+Config/testthat/edition: 3
diff --git a/NAMESPACE b/NAMESPACE
index a75f5843..e768508e 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -36,6 +36,8 @@ export(removeReachability)
export(removeSidebar)
export(removeSidebyside)
export(removeVelocity)
+export(setBuildingData)
+export(setBuildingStyle)
export(showHexbin)
export(sidebar_pane)
export(sidebar_tabs)
diff --git a/R/buildings.R b/R/buildings.R
index ef165bbf..ce495f8d 100644
--- a/R/buildings.R
+++ b/R/buildings.R
@@ -5,49 +5,85 @@ buildingsDependency <- function() {
src = system.file("htmlwidgets/lfx-building", package = "leaflet.extras2"),
stylesheet = "osm-buildings.css",
script = c(
- # "osm-buildings.js",
- "OSMBuildings.js",
+ "osm-buildings.js",
"osm-buildings-bindings.js")
)
)
}
-#' Add OSM-Buildings
+#' Add OSM-Buildings to a Leaflet Map
#'
-#' @param map A map widget object created from \code{\link[leaflet]{leaflet}}
-#' @param options List of further options. See \code{\link{hexbinOptions}}
+#' This function adds 2.5D buildings to a Leaflet map using the OSM Buildings plugin.
#'
-#' @note Out of the box a legend image is only available for Pressure,
-#' Precipitation Classic, Clouds Classic, Rain Classic, Snow, Temperature and
-#' Wind Speed.
-#' @seealso https://osmbuildings.org/documentation/viewer/
+#' @param map A map widget object created from \code{\link[leaflet]{leaflet}}.
+#' @param buildingURL The URL template for the building data. Default is the OSM Buildings tile server: \cr
+#' \code{"https://{s}.data.osmbuildings.org/0.2/59fcc2e8/tile/{z}/{x}/{y}.json"}.
+#' @param group The name of the group the buildings will be added to.
+#' @param eachFn A JavaScript function (using \code{\link[htmlwidgets]{JS}}) that will be called for each building feature. Use this to apply custom logic to each feature.
+#' @param clickFn A JavaScript function (using \code{\link[htmlwidgets]{JS}}) that will be called when a building is clicked. Use this to handle click events on buildings.
+#' @param data A GeoJSON object containing Polygon features representing the buildings. The properties of these polygons can include attributes like \code{height}, \code{color}, \code{roofColor}, and others as specified in the OSM Buildings documentation.
+#'
+#' @details
+#' The `data` parameter allows you to provide custom building data as a GeoJSON object. The following properties can be used within the GeoJSON:
+#' \itemize{
+#' \item \strong{height}
+#' \item \strong{minHeight}
+#' \item \strong{color/wallColor}
+#' \item \strong{material}
+#' \item \strong{roofColor}
+#' \item \strong{roofMaterial}
+#' \item \strong{shape}
+#' \item \strong{roofShape}
+#' \item \strong{roofHeight}
+#' }
+#'
+#' See the OSM Wiki: \href{https://wiki.openstreetmap.org/wiki/Simple_3D_Buildings}
+#'
+#' @seealso \url{https://github.com/kekscom/osmbuildings/} for more details on the OSM Buildings plugin and available properties.
#' @family OSM-Buildings Plugin
#' @export
addBuildings <- function(
- map, layerId = NULL, group = NULL, opacity = 0.5,
- attribution = '© Map tiles Mapbox') {
-
- # if (is.null(apikey)) {
- # apikey <- Sys.getenv("MAPBOX")
- # if (apikey == "") {
- # stop("You must either pass an `apikey` directly or save it as ",
- # "system variable under `MAPBOX`.")
- # }
- # }
+ map,
+ buildingURL = "https://{s}.data.osmbuildings.org/0.2/59fcc2e8/tile/{z}/{x}/{y}.json",
+ group = NULL,
+ eachFn = NULL, clickFn = NULL, data = NULL) {
map$dependencies <- c(map$dependencies, buildingsDependency())
- invokeMethod(map, getMapData(map), "addBuilding", layerId, group,
- opacity, attribution)
+ invokeMethod(map, getMapData(map), "addBuilding",
+ buildingURL, group,
+ eachFn, clickFn, data)
}
#' Update the Shadows OSM-Buildings with a POSIXct timestamp
#'
-#' @param map A map widget object created from \code{\link[leaflet]{leaflet}}
-#' @seealso https://osmbuildings.org/documentation/viewer/
+#' @inheritParams addBuildings
+#' @param time a timestamp that can be converted to POSIXct
#' @family OSM-Buildings Plugin
#' @export
updateBuildingTime <- function(map, time) {
- invokeMethod(map, NULL, "updateBuildingTime", as.POSIXct(time))
+ invokeMethod(map, NULL, "updateBuildingTime", time)
+}
+
+#' Update the OSM-Buildings Style
+#'
+#' @inheritParams addBuildings
+#' @param style A named list of styles
+#' @family OSM-Buildings Plugin
+#' @export
+setBuildingStyle <- function(map, style = list(color = "#ffcc00",
+ wallColor = "#ffcc00",
+ roofColor = "orange",
+ shadows = TRUE)) {
+ invokeMethod(map, NULL, "setBuildingStyle", style)
+}
+
+#' Update the OSM-Buildings Data
+#'
+#' @inheritParams addBuildings
+#' @family OSM-Buildings Plugin
+#' @export
+setBuildingData <- function(map, data) {
+ invokeMethod(map, NULL, "setBuildingData", data)
}
diff --git a/inst/examples/Buildings_mini.geojson b/inst/examples/Buildings_mini.geojson
new file mode 100644
index 00000000..ad4a4c30
--- /dev/null
+++ b/inst/examples/Buildings_mini.geojson
@@ -0,0 +1 @@
+{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"Shape__Area":176.63671875},"geometry":{"type":"Polygon","coordinates":[[[-76.4804996045569,42.4337884600754],[-76.4805379680812,42.4338087538101],[-76.4805187879662,42.4338284557592],[-76.4805509401373,42.4338455919873],[-76.4804662851554,42.4339336674861],[-76.4803956694538,42.4338964165343],[-76.4804996045569,42.4337884600754]]]}},{"type":"Feature","properties":{"Shape__Area":345.98828125},"geometry":{"type":"Polygon","coordinates":[[[-76.4917147984674,42.437699019829],[-76.4917157586844,42.4377966250581],[-76.4917498459528,42.4377962921787],[-76.4917500189652,42.4378152011893],[-76.4917731792594,42.4378150396315],[-76.4917736193283,42.4378567289911],[-76.4917635795041,42.4378669861224],[-76.4917384137284,42.4378671460758],[-76.4917381362015,42.4378515689695],[-76.4917055523917,42.4378519030471],[-76.4916990310511,42.4378547787747],[-76.4916590285082,42.4378549268497],[-76.4916520236004,42.437846007438],[-76.491607409454,42.4378463318654],[-76.4916069674922,42.4378051826799],[-76.4915981458976,42.4378051756207],[-76.491596895207,42.437700185948],[-76.4917147984674,42.437699019829]]]}},{"type":"Feature","properties":{"Shape__Area":267.796875},"geometry":{"type":"Polygon","coordinates":[[[-76.5035337738459,42.435060557038],[-76.5034652989897,42.4350627924973],[-76.5034672332501,42.435094871332],[-76.5034455827945,42.4350955791824],[-76.503445912491,42.4351010622047],[-76.5033311542365,42.4351046265652],[-76.5033308344195,42.4350990283119],[-76.5033106802032,42.4350996498525],[-76.5033089967195,42.4350701784426],[-76.5033291084123,42.4350695379664],[-76.5033281113062,42.4350517717624],[-76.5033631216726,42.4350506852281],[-76.5033611850748,42.4350185757797],[-76.5033864087956,42.4350178056161],[-76.5033860950374,42.4350122181705],[-76.5035307575087,42.4350076842528],[-76.5035310657796,42.4350118591322],[-76.5035523405363,42.4350112437345],[-76.5035548324292,42.4350550339849],[-76.5035335016566,42.4350557231682],[-76.5035337738459,42.435060557038]]]}},{"type":"Feature","properties":{"Shape__Area":127.296875},"geometry":{"type":"Polygon","coordinates":[[[-76.4759524752853,42.4385379409239],[-76.4759592414087,42.4386270880435],[-76.4758745174658,42.4386306107282],[-76.4758676517972,42.4385414626099],[-76.4759524752853,42.4385379409239]]]}},{"type":"Feature","properties":{"Shape__Area":63.15234375},"geometry":{"type":"Polygon","coordinates":[[[-76.4754381677613,42.4318082068041],[-76.4754809329536,42.4318319275006],[-76.4754236956695,42.4318883302206],[-76.475381130989,42.4318646087915],[-76.4754381677613,42.4318082068041]]]}},{"type":"Feature","properties":{"Shape__Area":804.28515625},"geometry":{"type":"Polygon","coordinates":[[[-76.4933630558631,42.4419432576379],[-76.4933692794826,42.4421487364427],[-76.4933775947121,42.4421530652759],[-76.4933782705625,42.4421708934121],[-76.493359693205,42.4421908680421],[-76.4933323214576,42.442191386731],[-76.4932946494504,42.4421723591604],[-76.4932941754185,42.4421528197248],[-76.4930865251091,42.4421562576079],[-76.4930851801135,42.4421151977773],[-76.4930629211068,42.4421155403683],[-76.4930613919052,42.4420639460612],[-76.4931591505082,42.442062491605],[-76.493158476476,42.442042591894],[-76.4931673001391,42.4420424187761],[-76.493154275754,42.4420357454607],[-76.4931689368898,42.4420199100322],[-76.4931975913848,42.442034339059],[-76.4931958657208,42.4419789624667],[-76.4932659513436,42.4419778471739],[-76.4932655718912,42.4419627192486],[-76.4933062802541,42.4419621210212],[-76.4933056033396,42.4419442028537],[-76.4933630558631,42.4419432576379]]]}},{"type":"Feature","properties":{"Shape__Area":199.39453125},"geometry":{"type":"Polygon","coordinates":[[[-76.4787534084869,42.4409282484676],[-76.4787564658364,42.441018653313],[-76.4787448357081,42.4410188227611],[-76.4787455054505,42.4410378222625],[-76.4786649935411,42.4410392792755],[-76.4786643218583,42.4410207299185],[-76.4786422634225,42.4410212499511],[-76.4786392072846,42.4409302157987],[-76.4787534084869,42.4409282484676]]]}},{"type":"Feature","properties":{"Shape__Area":253.99609375},"geometry":{"type":"Polygon","coordinates":[[[-76.502347211727,42.4329232099141],[-76.5023493916232,42.4330217162944],[-76.5023405694816,42.4330217100617],[-76.5023412387351,42.4330465622682],[-76.5023100611824,42.4330469003542],[-76.5023095858613,42.4330271808645],[-76.5022996600756,42.4330272638781],[-76.5023000358518,42.433046893268],[-76.5022326685716,42.4330476558929],[-76.5022324954365,42.4330264061157],[-76.5022238738251,42.4330264000159],[-76.5022223644313,42.4329528349709],[-76.5022129404753,42.4329530083615],[-76.5022124760996,42.4329248252086],[-76.502347211727,42.4329232099141]]]}},{"type":"Feature","properties":{"Shape__Area":2687.15625},"geometry":{"type":"Polygon","coordinates":[[[-76.4858391006211,42.4359088550454],[-76.4857900357414,42.4358590756544],[-76.4857386961732,42.4358105727232],[-76.4856851389539,42.4357634030158],[-76.4856294284142,42.4357176224023],[-76.4855716313175,42.4356732849542],[-76.4855118132154,42.4356304420409],[-76.4854500469526,42.4355891441381],[-76.485386404159,42.4355494408201],[-76.4853209601144,42.4355113789633],[-76.4852537913189,42.4354750018444],[-76.4851849779165,42.4354403536434],[-76.4852414505764,42.4353742753687],[-76.4852158878537,42.4353622876811],[-76.4852569018754,42.4353250974117],[-76.485268131037,42.4353101387261],[-76.4852882151555,42.4353195576785],[-76.4853075348813,42.4352969516067],[-76.485379670956,42.4353332240042],[-76.4854501636469,42.4353712298199],[-76.4855189352367,42.4354109275769],[-76.4855859128702,42.4354522749024],[-76.4856510224806,42.4354952267215],[-76.4857141960773,42.435539737965],[-76.4857753656758,42.4355857590618],[-76.4858344645039,42.4356332422428],[-76.4858914294416,42.4356821352403],[-76.485946198585,42.4357323848877],[-76.4859987136745,42.4357839389215],[-76.4858724782898,42.4358536350754],[-76.4859073465379,42.4358882873278],[-76.4858813573948,42.4359026366327],[-76.4858891057631,42.4359103371334],[-76.4858683132227,42.4359218171136],[-76.4858494962843,42.435903115508],[-76.4858391006211,42.4359088550454]]]}},{"type":"Feature","properties":{"Shape__Area":175.1875},"geometry":{"type":"Polygon","coordinates":[[[-76.5046171160388,42.4445603064262],[-76.5046141072987,42.4445604844187],[-76.5046125265181,42.4445424747754],[-76.5046836173296,42.4445406329585],[-76.5046842029275,42.4445535093485],[-76.5046868105318,42.444553331079],[-76.5046878795991,42.444580074111],[-76.504709438602,42.4445792786365],[-76.5047123586526,42.4446490623367],[-76.5047017309835,42.4446490550472],[-76.504702608273,42.4446687747627],[-76.5046475567434,42.4446725191174],[-76.5046207246047,42.4446412560335],[-76.5046171160388,42.4445603064262]]]}},{"type":"Feature","properties":{"Shape__Area":361.671875},"geometry":{"type":"Polygon","coordinates":[[[-76.4787664531785,42.4360974471362],[-76.4787667275037,42.4361137445105],[-76.478836203562,42.4361136278054],[-76.4788364789675,42.4361885433297],[-76.4787807380484,42.4361886725631],[-76.4787807254763,42.4361962359224],[-76.4787425288341,42.4361963811372],[-76.4787426997305,42.4362142089169],[-76.4787197405384,42.4362143680257],[-76.4787197808222,42.4361901474503],[-76.4785839372267,42.4361905635766],[-76.4785835637769,42.4361133972268],[-76.4786761983154,42.4361131217193],[-76.4786759237309,42.4360977246386],[-76.4787664531785,42.4360974471362]]]}},{"type":"Feature","properties":{"Shape__Area":266.64453125},"geometry":{"type":"Polygon","coordinates":[[[-76.492889545482,42.4359950172782],[-76.4928904260726,42.4360097845978],[-76.4929335368892,42.4360084681616],[-76.4929368593737,42.436068798593],[-76.4929287378322,42.4360689722472],[-76.4929312836472,42.43611156357],[-76.492784505336,42.4361162201839],[-76.4927814679505,42.4360673255101],[-76.4928019203423,42.4360667114582],[-76.4927980097654,42.4359979159926],[-76.492889545482,42.4359950172782]]]}},{"type":"Feature","properties":{"Shape__Area":225.4296875},"geometry":{"type":"Polygon","coordinates":[[[-76.4855454154509,42.4546629304284],[-76.4855500292904,42.4546621241023],[-76.4855889591292,42.4547785802619],[-76.48550188817,42.4547946238953],[-76.4854885123842,42.4547540039442],[-76.4854660437321,42.4547580360733],[-76.485455262456,42.4547259729002],[-76.4854788352866,42.4547217607573],[-76.4854643632435,42.4546778078893],[-76.4854764003052,42.4546755674379],[-76.4854731081173,42.4546645801743],[-76.4854699142222,42.4546544923854],[-76.4854838664153,42.4546465808334],[-76.4854898882375,42.454643344025],[-76.4855366334238,42.4546351903658],[-76.4855454154509,42.4546629304284]]]}},{"type":"Feature","properties":{"Shape__Area":1277.921875},"geometry":{"type":"Polygon","coordinates":[[[-76.5077166801357,42.4314492344605],[-76.5073318212686,42.4314620000178],[-76.5073198920808,42.4312646222522],[-76.5077047497427,42.4312518567335],[-76.5077166801357,42.4314492344605]]]}},{"type":"Feature","properties":{"Shape__Area":291.640625},"geometry":{"type":"Polygon","coordinates":[[[-76.4948586428299,42.4441071255128],[-76.4948591371261,42.444112617681],[-76.4949084710594,42.4441104050148],[-76.4949104410154,42.444135798395],[-76.4950049976599,42.4441315489878],[-76.4950099269527,42.4441919705411],[-76.4947853148271,42.4442018823382],[-76.4947778229713,42.4441106642775],[-76.4948586428299,42.4441071255128]]]}},{"type":"Feature","properties":{"Shape__Area":196.15234375},"geometry":{"type":"Polygon","coordinates":[[[-76.5012518569606,42.4519254851065],[-76.5013112244919,42.452077516641],[-76.5012500379442,42.4520905288724],[-76.5012147566076,42.4520004627335],[-76.5011910837671,42.4520055783349],[-76.5011667971385,42.4519436127595],[-76.5012518569606,42.4519254851065]]]}},{"type":"Feature","properties":{"Shape__Area":274.3359375},"geometry":{"type":"Polygon","coordinates":[[[-76.5128729326646,42.4330934977998],[-76.5128593178432,42.4330766512196],[-76.512924410984,42.4330488685116],[-76.5129415285574,42.4330710298739],[-76.5129887681725,42.4330509795995],[-76.5129649433812,42.4330199003095],[-76.51301980553,42.4329966127398],[-76.5130672556474,42.433057870875],[-76.5130598334943,42.4330611974156],[-76.5130792536023,42.4330860610528],[-76.512982166132,42.4331273304389],[-76.5129653497309,42.4331053502269],[-76.5128569286158,42.4331513841109],[-76.5128319019752,42.4331191345722],[-76.51287783801,42.4330998037777],[-76.5128729326646,42.4330934977998]]]}},{"type":"Feature","properties":{"Shape__Area":1614.12109375},"geometry":{"type":"Polygon","coordinates":[[[-76.5066547319233,42.4462688306652],[-76.5065829021833,42.446182352706],[-76.5065292524807,42.4462067948279],[-76.5063545312796,42.4459964413011],[-76.5064902314624,42.4459346156233],[-76.5065096450377,42.4459579885284],[-76.506635877583,42.4459004768366],[-76.5068598606444,42.4461753732786],[-76.5067336277502,42.4462328861143],[-76.5067083816312,42.4462443875846],[-76.5066547319233,42.4462688306652]]]}},{"type":"Feature","properties":{"Shape__Area":445.71875},"geometry":{"type":"Polygon","coordinates":[[[-76.5240360887441,42.4490086919248],[-76.524023552753,42.4490102168364],[-76.5240259501387,42.4490203922786],[-76.5239782134868,42.449026130341],[-76.5238568682602,42.4490406539789],[-76.5238146336647,42.4488485745267],[-76.5239357793354,42.4488338698709],[-76.5239685289993,42.4489817346199],[-76.5240067374848,42.4489771629107],[-76.5240162657025,42.4489759074325],[-76.5240287007882,42.4489743824695],[-76.5240360887441,42.4490086919248]]]}},{"type":"Feature","properties":{"Shape__Area":295.9921875},"geometry":{"type":"Polygon","coordinates":[[[-76.4970539380426,42.4488135475477],[-76.4970545207572,42.4488274142861],[-76.4970619415409,42.4488270597544],[-76.4970612538494,42.4488161639012],[-76.4970997610872,42.4488149324668],[-76.4971004495454,42.4488243878529],[-76.4971907004508,42.4488215738755],[-76.4971948168207,42.4488906383796],[-76.4970085991923,42.4488968912696],[-76.4970070297547,42.4488721293529],[-76.4969417477618,42.4488743309016],[-76.4969383163932,42.4488174226492],[-76.4970539380426,42.4488135475477]]]}},{"type":"Feature","properties":{"Shape__Area":250.8828125},"geometry":{"type":"Polygon","coordinates":[[[-76.5141100742951,42.4326496022761],[-76.5141085760016,42.4326770207477],[-76.5140345500886,42.4326748018104],[-76.5140360484143,42.4326473833397],[-76.5139250083871,42.4326440552949],[-76.513927105866,42.4326056688974],[-76.5140566510685,42.4326095519027],[-76.5140584483163,42.4325766499168],[-76.5141694870164,42.4325799778315],[-76.5141655937095,42.4326512662219],[-76.5141100742951,42.4326496022761]]]}},{"type":"Feature","properties":{"Shape__Area":47.58984375},"geometry":{"type":"Polygon","coordinates":[[[-76.4827194651053,42.4359675432523],[-76.4827564261739,42.4359882851532],[-76.4827068218217,42.4360371338947],[-76.4826698619562,42.436016391979],[-76.4827194651053,42.4359675432523]]]}},{"type":"Feature","properties":{"Shape__Area":134.4375},"geometry":{"type":"Polygon","coordinates":[[[-76.5206949660285,42.4318772551954],[-76.5207949460262,42.4319450209583],[-76.5207411698055,42.4319885721819],[-76.5206409905743,42.4319207162342],[-76.5206949660285,42.4318772551954]]]}},{"type":"Feature","properties":{"Shape__Area":275.85546875},"geometry":{"type":"Polygon","coordinates":[[[-76.5037622460805,42.4413835965233],[-76.5037585992652,42.4413338914972],[-76.5038539538716,42.4413301755176],[-76.5038598663979,42.4414116669679],[-76.5038800207628,42.4414106005869],[-76.5038817157941,42.4414178050087],[-76.5038948402586,42.4414259176448],[-76.5038960258616,42.441439964843],[-76.5038818785837,42.4414476993617],[-76.5038820696997,42.4414551728291],[-76.5038369491119,42.4414570321633],[-76.503838020726,42.4414814344568],[-76.5037649258345,42.4414848048583],[-76.5037632529171,42.4414601320588],[-76.5037501176367,42.4414605739874],[-76.5037436968661,42.441384123822],[-76.5037622460805,42.4413835965233]]]}},{"type":"Feature","properties":{"Shape__Area":185.96875},"geometry":{"type":"Polygon","coordinates":[[[-76.5002915821999,42.4343135109081],[-76.5002919622306,42.4343294481957],[-76.5003348702596,42.4343288490641],[-76.5003374382312,42.4344339296889],[-76.5002397923992,42.4344354794815],[-76.5002371222885,42.4343314791338],[-76.5002417333782,42.4343314824762],[-76.5002413551441,42.4343141947481],[-76.5002915821999,42.4343135109081]]]}},{"type":"Feature","properties":{"Shape__Area":206.04296875},"geometry":{"type":"Polygon","coordinates":[[[-76.51542310518,42.4387214598557],[-76.5154977215911,42.4387338983793],[-76.5155025958537,42.4387178346208],[-76.5155351251647,42.4387232565273],[-76.5155302509099,42.4387393202872],[-76.5155374791076,42.4387405255541],[-76.515520685117,42.4387958710838],[-76.5155351415262,42.4387982807181],[-76.5155270177566,42.438825054251],[-76.515512560127,42.4388226437147],[-76.5155089044256,42.4388346919838],[-76.5153945303355,42.4388156262571],[-76.51542310518,42.4387214598557]]]}},{"type":"Feature","properties":{"Shape__Area":508.53515625},"geometry":{"type":"Polygon","coordinates":[[[-76.491182855391,42.4418611187597],[-76.4911840373707,42.4418757062609],[-76.4912420931221,42.4418736813408],[-76.4912571167957,42.4418844987287],[-76.4912613275837,42.4418843220522],[-76.491262400653,42.4419044021541],[-76.491273129269,42.4419040506534],[-76.4912895554197,42.4419158594858],[-76.4912906259948,42.4419376501425],[-76.4912741654642,42.4419501528009],[-76.4912637370714,42.4419505045428],[-76.491264717985,42.4419654519987],[-76.4912449440455,42.4419803827833],[-76.4911773630538,42.4419826701355],[-76.4911782478376,42.4419951867231],[-76.4911425524954,42.4419964184318],[-76.4911324148481,42.442004063671],[-76.4911170730835,42.4420045915062],[-76.4911080583685,42.4419982813011],[-76.4910316546426,42.4420009206859],[-76.4910233272454,42.4418683737119],[-76.4910475910778,42.4418676730137],[-76.4910484969768,42.4418657822269],[-76.491182855391,42.4418611187597]]]}},{"type":"Feature","properties":{"Shape__Area":450.80078125},"geometry":{"type":"Polygon","coordinates":[[[-76.5211636357724,42.4318629237444],[-76.5211485739174,42.4318879473736],[-76.521022202618,42.4318463688582],[-76.5210597572475,42.4317836306092],[-76.5210924926246,42.4317287231966],[-76.5211040173514,42.4317325115921],[-76.5211160663878,42.4317124388551],[-76.5211639693283,42.4317283118344],[-76.5211605546774,42.4317340727701],[-76.5211667677472,42.4317361468178],[-76.5212098452001,42.4316641366752],[-76.5212714768116,42.4316844293939],[-76.5212121322272,42.4317837132818],[-76.5212095225902,42.4317877640949],[-76.5212351766513,42.4317964217256],[-76.521208567238,42.4318407981479],[-76.5211822120151,42.4318320492014],[-76.5211636357724,42.4318629237444]]]}},{"type":"Feature","properties":{"Shape__Area":1199.02734375},"geometry":{"type":"Polygon","coordinates":[[[-76.5074596405895,42.4387032294976],[-76.5074762174834,42.439173346847],[-76.5073326430626,42.4391760426592],[-76.5073283739512,42.439058716254],[-76.5073096253367,42.4390588838804],[-76.5073020678644,42.4388415182518],[-76.5073224210271,42.4388411716318],[-76.5073177710823,42.4387061064787],[-76.5074596405895,42.4387032294976]]]}},{"type":"Feature","properties":{"Shape__Area":371.70703125},"geometry":{"type":"Polygon","coordinates":[[[-76.524039402419,42.4494290967578],[-76.5239449234349,42.4494502974433],[-76.5238894994253,42.4493137664252],[-76.5239026385904,42.4493108013737],[-76.5238872322372,42.4492726164975],[-76.5239939479372,42.449248630388],[-76.5240066523302,42.4492799716314],[-76.5240356384022,42.4492735036258],[-76.5240550462884,42.4493212354485],[-76.5240263616786,42.449327703615],[-76.5240401671164,42.449362016388],[-76.5240144919969,42.4493676758453],[-76.524039402419,42.4494290967578]]]}},{"type":"Feature","properties":{"Shape__Area":461.1328125},"geometry":{"type":"Polygon","coordinates":[[[-76.4973156884569,42.4431433917478],[-76.4973179300176,42.4431912953219],[-76.497364453904,42.443190069812],[-76.4973680631504,42.4432636363631],[-76.4973254495895,42.4432648648084],[-76.4973257430533,42.4432698175406],[-76.4971321239327,42.4432749838345],[-76.4971260809856,42.4431485619422],[-76.4973156884569,42.4431433917478]]]}},{"type":"Feature","properties":{"Shape__Area":81.375},"geometry":{"type":"Polygon","coordinates":[[[-76.4848729356669,42.4369763470277],[-76.484914760587,42.4370291473743],[-76.484846641082,42.4370588021726],[-76.4848046157971,42.4370059115999],[-76.4848729356669,42.4369763470277]]]}},{"type":"Feature","properties":{"Shape__Area":150.6328125},"geometry":{"type":"Polygon","coordinates":[[[-76.501458424139,42.4482359221601],[-76.5014988725808,42.4482830426302],[-76.5014098759754,42.4483250281106],[-76.5013427945415,42.448246914008],[-76.5013661722235,42.4482358562318],[-76.5013912562519,42.4482241685657],[-76.5014011678505,42.4482358812503],[-76.5014032705103,42.4482380434546],[-76.5014078866511,42.4482358860524],[-76.5014438064431,42.4482188934939],[-76.501458424139,42.4482359221601]]]}},{"type":"Feature","properties":{"Shape__Area":37.51953125},"geometry":{"type":"Polygon","coordinates":[[[-76.4953988733227,42.4476242849262],[-76.4954043180752,42.447674712676],[-76.4953602954291,42.4476773797762],[-76.4953548507105,42.4476269529247],[-76.4953988733227,42.4476242849262]]]}},{"type":"Feature","properties":{"Shape__Area":500.87890625},"geometry":{"type":"Polygon","coordinates":[[[-76.4816100925764,42.4559046856913],[-76.481623028388,42.4560910817124],[-76.4816035707431,42.4560919656369],[-76.4816044503168,42.4561066429609],[-76.4815546049749,42.4561084893244],[-76.4815537241503,42.4560953424936],[-76.4814717863042,42.4560983315995],[-76.4814692390066,42.4560609618587],[-76.481451688156,42.4560615764643],[-76.4814451213398,42.4559672975573],[-76.4814661822706,42.455966596043],[-76.4814639312408,42.4559324685124],[-76.4814995351326,42.4559311497117],[-76.4814979664817,42.4559088175001],[-76.4816100925764,42.4559046856913]]]}},{"type":"Feature","properties":{"Shape__Area":339.5},"geometry":{"type":"Polygon","coordinates":[[[-76.4951622249539,42.4305365794093],[-76.4951665115315,42.4306257236175],[-76.4950264644905,42.4306293979609],[-76.4950262704136,42.4306248054065],[-76.4949352439387,42.4306272569766],[-76.4949311532281,42.4305425243565],[-76.4951622249539,42.4305365794093]]]}},{"type":"Feature","properties":{"Shape__Area":51.90234375},"geometry":{"type":"Polygon","coordinates":[[[-76.500109774499,42.4513235458361],[-76.5001111270106,42.4513625348532],[-76.5000319055724,42.451364007806],[-76.5000305531087,42.4513250196883],[-76.500109774499,42.4513235458361]]]}},{"type":"Feature","properties":{"Shape__Area":175.33984375},"geometry":{"type":"Polygon","coordinates":[[[-76.5001763487755,42.4538330434631],[-76.5001777084079,42.4538667198481],[-76.5001439123971,42.4538673255301],[-76.5001452697491,42.4539027124673],[-76.500017907468,42.4539053217414],[-76.5000169392098,42.4538791189578],[-76.5000047047254,42.4538794701812],[-76.5000031566434,42.4538366998141],[-76.5001763487755,42.4538330434631]]]}},{"type":"Feature","properties":{"Shape__Area":303.42578125},"geometry":{"type":"Polygon","coordinates":[[[-76.495676549813,42.4427888273297],[-76.4956784915483,42.4428346600352],[-76.4956579362191,42.4428351844913],[-76.4956599815925,42.4428790366313],[-76.4955365508157,42.4428823632755],[-76.4955365558436,42.442878762107],[-76.495483915361,42.4428801622464],[-76.4954281654277,42.4428816500064],[-76.4954249467577,42.4428172675791],[-76.4955356427926,42.4428144714779],[-76.4955352562519,42.4428040259813],[-76.4956088529871,42.442802191711],[-76.4956084664331,42.4427917471149],[-76.495676549813,42.4427888273297]]]}},{"type":"Feature","properties":{"Shape__Area":177.21875},"geometry":{"type":"Polygon","coordinates":[[[-76.4988140132576,42.4501073108812],[-76.498817662108,42.4501529646755],[-76.4987998113441,42.4501537617756],[-76.4988023779128,42.4501842880667],[-76.4987451163408,42.4501868566728],[-76.4987442293354,42.4501756914978],[-76.4986662088713,42.450179235065],[-76.4986659132081,42.4501758137372],[-76.4986383353759,42.4501769637439],[-76.4986347842801,42.4501337408045],[-76.4986597547258,42.4501324088143],[-76.498662963119,42.4501322311259],[-76.4986625674045,42.4501281795198],[-76.4987278516113,42.4501252558595],[-76.4987264661747,42.4501113885403],[-76.4988140132576,42.4501073108812]]]}},{"type":"Feature","properties":{"Shape__Area":53.24609375},"geometry":{"type":"Polygon","coordinates":[[[-76.497508734167,42.436405922941],[-76.4975706203941,42.4364431847151],[-76.4975357460392,42.4364740080965],[-76.4974746422525,42.4364360545637],[-76.497508734167,42.436405922941]]]}},{"type":"Feature","properties":{"Shape__Area":56.96875},"geometry":{"type":"Polygon","coordinates":[[[-76.513021343019,42.4448938802498],[-76.5130319428538,42.4449199088142],[-76.5129128935404,42.4449471182148],[-76.512902094397,42.4449210895171],[-76.513021343019,42.4448938802498]]]}},{"type":"Feature","properties":{"Shape__Area":163.6015625},"geometry":{"type":"Polygon","coordinates":[[[-76.5006424915573,42.4528178060056],[-76.5006451451923,42.4529354012977],[-76.5005622119888,42.4529364217929],[-76.5005598589106,42.4528187366869],[-76.5006424915573,42.4528178060056]]]}},{"type":"Feature","properties":{"Shape__Area":173.72265625},"geometry":{"type":"Polygon","coordinates":[[[-76.4946454047698,42.44934745522],[-76.4946453941005,42.4493550185654],[-76.4947612137795,42.4493537576775],[-76.4947615884344,42.4493725758717],[-76.4947718048244,42.4493803280776],[-76.494771987016,42.4493933842538],[-76.4947620495788,42.449400759871],[-76.4947622239048,42.4494193978519],[-76.4947187039043,42.4494199052924],[-76.4947188897663,42.4494303497242],[-76.4946829909564,42.4494306820838],[-76.4946828045926,42.4494205977684],[-76.4946448002735,42.4494209284876],[-76.4946444242575,42.4494030997131],[-76.4946075224206,42.4494035213023],[-76.49460648049,42.4493596708706],[-76.4946103915029,42.4493594938394],[-76.4946102071851,42.4493479681577],[-76.4946454047698,42.44934745522]]]}},{"type":"Feature","properties":{"Shape__Area":229.1015625},"geometry":{"type":"Polygon","coordinates":[[[-76.5038543430378,42.4313430445788],[-76.5038458211981,42.4313431286996],[-76.5038463685065,42.4313860794489],[-76.5038306300551,42.431386248594],[-76.5038310974221,42.43141263026],[-76.5037391705458,42.4314133776358],[-76.5037388028736,42.4313869951384],[-76.5037188545986,42.4313871613451],[-76.5037175600857,42.4313009004886],[-76.5038142983038,42.4313000673383],[-76.5038142932967,42.4313040286326],[-76.503853690303,42.4313036958362],[-76.5038543430378,42.4313430445788]]]}},{"type":"Feature","properties":{"Shape__Area":261.51953125},"geometry":{"type":"Polygon","coordinates":[[[-76.4789573776346,42.4357708602767],[-76.478931586262,42.4357866846685],[-76.4788951478182,42.4357543264146],[-76.4788872190447,42.4357591816796],[-76.4788550850478,42.4357307886289],[-76.4788632135913,42.4357256643603],[-76.4788586091328,42.4357216079399],[-76.4789538478647,42.435662627298],[-76.4789582516365,42.4356667735613],[-76.4789998998375,42.435640879401],[-76.4790408430755,42.4356772930446],[-76.4790174591078,42.4356918592344],[-76.4790553977534,42.435726919697],[-76.4789670882299,42.4357791536248],[-76.4789573776346,42.4357708602767]]]}},{"type":"Feature","properties":{"Shape__Area":718.62109375},"geometry":{"type":"Polygon","coordinates":[[[-76.517464474647,42.4554843051011],[-76.5175172753939,42.4555809726802],[-76.5174142133289,42.4556118402596],[-76.5174433931017,42.4556652614051],[-76.517314568933,42.4557038466835],[-76.5172853904686,42.455650424606],[-76.5172080937587,42.4556735751425],[-76.5171552946267,42.4555769065231],[-76.517464474647,42.4554843051011]]]}},{"type":"Feature","properties":{"Shape__Area":95.26171875},"geometry":{"type":"Polygon","coordinates":[[[-76.4984061125545,42.4530596421427],[-76.4984064051748,42.4530653150962],[-76.4984373941459,42.4530643477308],[-76.498439648091,42.4531030664354],[-76.4984086834531,42.4531040059104],[-76.4984113565519,42.453150113618],[-76.4983620166185,42.4531516075496],[-76.498356772692,42.4530611360721],[-76.4984061125545,42.4530596421427]]]}},{"type":"Feature","properties":{"Shape__Area":303.2890625},"geometry":{"type":"Polygon","coordinates":[[[-76.4949854628129,42.4443337667218],[-76.4949894407455,42.4444284931995],[-76.4947989270682,42.44443293861],[-76.4947948496199,42.4443383020783],[-76.4949854628129,42.4443337667218]]]}},{"type":"Feature","properties":{"Shape__Area":278.40625},"geometry":{"type":"Polygon","coordinates":[[[-76.4910922911708,42.4559586294125],[-76.4912063418815,42.4560096839064],[-76.4911181330126,42.4561174822445],[-76.4910084926955,42.4560683218273],[-76.4910379289046,42.4560321484325],[-76.4910338199031,42.4560302545129],[-76.4910922911708,42.4559586294125]]]}},{"type":"Feature","properties":{"Shape__Area":188.2578125},"geometry":{"type":"Polygon","coordinates":[[[-76.5077459893097,42.4430307186514],[-76.5077480490469,42.4430686277503],[-76.5077760241838,42.4430679250581],[-76.5077798388936,42.4431465339615],[-76.5077175723106,42.4431482034619],[-76.5077168848054,42.4431366783567],[-76.5076786830253,42.4431376434732],[-76.5076778011163,42.443119994446],[-76.5076719857863,42.4431201706664],[-76.5076697333298,42.4430757793292],[-76.5076591042769,42.4430761324302],[-76.507656850207,42.4430330906306],[-76.5077459893097,42.4430307186514]]]}},{"type":"Feature","properties":{"Shape__Area":335.328125},"geometry":{"type":"Polygon","coordinates":[[[-76.5134925519712,42.435729751882],[-76.5134747646607,42.4357680989805],[-76.5134415897226,42.4357600652398],[-76.5134258126737,42.435794001214],[-76.5133339055512,42.4357713450434],[-76.5133360154199,42.4357670240191],[-76.5133020391437,42.4357582695133],[-76.5133256575219,42.435705069347],[-76.513359735151,42.4357133737606],[-76.5133984256187,42.4356294782421],[-76.513489629477,42.435653755363],[-76.5134664152213,42.4357032637032],[-76.5134981860555,42.4357115675699],[-76.5135072295072,42.4356932043751],[-76.5135422080746,42.4357023195481],[-76.5135257252003,42.4357393170074],[-76.5134925519712,42.435729751882]]]}},{"type":"Feature","properties":{"Shape__Area":211.44140625},"geometry":{"type":"Polygon","coordinates":[[[-76.492905106307,42.4306838973992],[-76.4927274754054,42.430689690085],[-76.492725357434,42.4306540511335],[-76.4927512626367,42.4306532064225],[-76.4927488195563,42.430612085318],[-76.4929005450557,42.4306071373494],[-76.492905106307,42.4306838973992]]]}},{"type":"Feature","properties":{"Shape__Area":94.53515625},"geometry":{"type":"Polygon","coordinates":[[[-76.5022813947639,42.4331206241242],[-76.5022818165337,42.4331828428844],[-76.5021914916543,42.4331833191625],[-76.5021908695588,42.4331210102308],[-76.5022813947639,42.4331206241242]]]}},{"type":"Feature","properties":{"Shape__Area":231.63671875},"geometry":{"type":"Polygon","coordinates":[[[-76.5068127427235,42.4381541766599],[-76.5068192816695,42.4383001384632],[-76.50674007618,42.4383021562448],[-76.5067368496648,42.4382348931363],[-76.5067090772151,42.4382355948109],[-76.5067054636839,42.4381568058802],[-76.5068127427235,42.4381541766599]]]}},{"type":"Feature","properties":{"Shape__Area":231.0},"geometry":{"type":"Polygon","coordinates":[[[-76.4947247309657,42.4423683230631],[-76.4947251173704,42.4423787676628],[-76.4946860129818,42.442379547693],[-76.4946851394988,42.4423593777497],[-76.494725646964,42.4423583287164],[-76.4947227639819,42.4422701760822],[-76.4947760047635,42.4422694069612],[-76.4947758168827,42.4422604029839],[-76.4948580353729,42.4422589350726],[-76.4948609108167,42.4423525803863],[-76.4947845085883,42.442353961863],[-76.4947770702971,42.4423672822565],[-76.4947247309657,42.4423683230631]]]}},{"type":"Feature","properties":{"Shape__Area":219.6953125},"geometry":{"type":"Polygon","coordinates":[[[-76.4951943150519,42.4499869013655],[-76.4951945069266,42.4499931144278],[-76.4952588863508,42.4499921735901],[-76.4952590809273,42.4499955948491],[-76.4952905686562,42.4499950788629],[-76.4952912407966,42.4500168691455],[-76.495312599349,42.4500165254313],[-76.4953136474543,42.4500563245425],[-76.4952611013985,42.4500571844742],[-76.4952612971897,42.4500606066344],[-76.4951668352442,42.4500620645147],[-76.4951645284122,42.4500617026236],[-76.4951163953496,42.450062475854],[-76.4951140830897,42.4499949431746],[-76.4951299273733,42.4499945952506],[-76.495129636452,42.4499879319659],[-76.4951943150519,42.4499869013655]]]}},{"type":"Feature","properties":{"Shape__Area":160.47265625},"geometry":{"type":"Polygon","coordinates":[[[-76.5018523561849,42.4454819281819],[-76.5019023091313,42.4455449031398],[-76.5017876369683,42.4455947941678],[-76.5017411879842,42.4455360548308],[-76.501768476044,42.445524278616],[-76.5017650733982,42.4455197738311],[-76.5018523561849,42.4454819281819]]]}},{"type":"Feature","properties":{"Shape__Area":103.828125},"geometry":{"type":"Polygon","coordinates":[[[-76.4935497698235,42.4516229993331],[-76.4934089899866,42.4516209182415],[-76.4934101724357,42.4515770218457],[-76.4935509521743,42.4515791029358],[-76.4935497698235,42.4516229993331]]]}},{"type":"Feature","properties":{"Shape__Area":23.90234375},"geometry":{"type":"Polygon","coordinates":[[[-76.5069119060813,42.4405913944768],[-76.5068600712482,42.440592756264],[-76.5068587578376,42.4405653324486],[-76.5069105926482,42.440563970662],[-76.5069119060813,42.4405913944768]]]}},{"type":"Feature","properties":{"Shape__Area":243.8203125},"geometry":{"type":"Polygon","coordinates":[[[-76.4886789173937,42.4421091232796],[-76.4886870065237,42.4422634609168],[-76.4886003751556,42.4422660002066],[-76.4885911505999,42.4422659925829],[-76.4885848180394,42.442143981418],[-76.4885938412087,42.4421437187878],[-76.4885922863736,42.4421115725344],[-76.4886789173937,42.4421091232796]]]}},{"type":"Feature","properties":{"Shape__Area":41.60546875},"geometry":{"type":"Polygon","coordinates":[[[-76.5012759119437,42.4372593864179],[-76.5012758911506,42.4372753243074],[-76.501198293604,42.4372754487939],[-76.5011981141076,42.4372593307021],[-76.5011981348018,42.4372434837422],[-76.5012759326182,42.4372435394579],[-76.5012759119437,42.4372593864179]]]}},{"type":"Feature","properties":{"Shape__Area":378.23046875},"geometry":{"type":"Polygon","coordinates":[[[-76.4999847334096,42.4497315255511],[-76.4999856098849,42.4497507951384],[-76.5000094767099,42.449750092256],[-76.5000115151699,42.4498006973511],[-76.4999883509787,42.4498014007448],[-76.4999894215955,42.4498255320498],[-76.4997624913183,42.4498308595121],[-76.4997584040081,42.4497368534423],[-76.4999847334096,42.4497315255511]]]}},{"type":"Feature","properties":{"Shape__Area":282.97265625},"geometry":{"type":"Polygon","coordinates":[[[-76.4813002663968,42.4537079456378],[-76.4813227135076,42.4537178697215],[-76.4813257288868,42.453713911123],[-76.4813921672559,42.4537435033848],[-76.4813114784798,42.4538431973821],[-76.4812452402034,42.4538138744393],[-76.4812540825104,42.453803257976],[-76.4812413556374,42.4537975739059],[-76.4812649691902,42.453768241836],[-76.4812760924549,42.4537732033424],[-76.4812803136136,42.4537678953817],[-76.4811742909714,42.4537209794888],[-76.4811849427455,42.4537078429272],[-76.4811891622544,42.4537028041554],[-76.4812176936084,42.453671134819],[-76.4813002663968,42.4537079456378]]]}},{"type":"Feature","properties":{"Shape__Area":226.625},"geometry":{"type":"Polygon","coordinates":[[[-76.4948460240142,42.4430283298089],[-76.4948462163411,42.4430341827626],[-76.4948907354929,42.4430331367677],[-76.4948916023399,42.4430580782552],[-76.4949444432939,42.4430568586011],[-76.4949455045989,42.4430870237382],[-76.4949193343827,42.4430877237909],[-76.4948957717368,42.4430882457907],[-76.4948964485337,42.4431057137987],[-76.4947334122641,42.4431093700334],[-76.4947321100697,42.4430371556206],[-76.4947888603083,42.4430375595873],[-76.4947883697041,42.4430294556705],[-76.4948460240142,42.4430283298089]]]}},{"type":"Feature","properties":{"Shape__Area":344.06640625},"geometry":{"type":"Polygon","coordinates":[[[-76.5153385187076,42.4327554604066],[-76.5153829707507,42.4327554867641],[-76.5153830003916,42.4327280457985],[-76.515457087098,42.4327280896892],[-76.5154570574894,42.4327555306549],[-76.5156237526521,42.4327556292342],[-76.5156237053946,42.4327995347791],[-76.5155496186038,42.432799490996],[-76.5155495831219,42.4328324201544],[-76.5154310441949,42.4328323500017],[-76.5154310797389,42.4327994208433],[-76.5154199667204,42.4327994142602],[-76.5154199370956,42.4328268552255],[-76.5153458502729,42.4328268113107],[-76.51534587993,42.4327993703454],[-76.515338471251,42.4327993659513],[-76.5153385187076,42.4327554604066]]]}},{"type":"Feature","properties":{"Shape__Area":887.9296875},"geometry":{"type":"Polygon","coordinates":[[[-76.4914269246193,42.4478784168943],[-76.4915386104451,42.4479590933488],[-76.4914872137932,42.4479981302657],[-76.491496629642,42.4480050709605],[-76.4914470391558,42.4480425788092],[-76.4914935170579,42.4480762014589],[-76.4913642226331,42.4481742422566],[-76.491299614478,42.4481275498622],[-76.4913165789416,42.44811486756],[-76.491284225588,42.448091160297],[-76.4912656545157,42.4481051917424],[-76.4912050535941,42.4480615635064],[-76.4912238255095,42.4480473521735],[-76.4912118048013,42.4480386078782],[-76.4912514573994,42.4480087464334],[-76.4912426423897,42.4480024364059],[-76.4912833980234,42.4479714045558],[-76.4912415288646,42.4479410274755],[-76.4912848947442,42.4479081071077],[-76.4913334755539,42.4479433520378],[-76.4913592742023,42.447923833697],[-76.4913631806559,42.4479267186669],[-76.4914269246193,42.4478784168943]]]}},{"type":"Feature","properties":{"Shape__Area":88.48046875},"geometry":{"type":"Polygon","coordinates":[[[-76.496536643606,42.4464024811247],[-76.49654186995,42.4464664148544],[-76.4964596431355,42.4464699537378],[-76.496454617447,42.4464060210567],[-76.496536643606,42.4464024811247]]]}},{"type":"Feature","properties":{"Shape__Area":149.44140625},"geometry":{"type":"Polygon","coordinates":[[[-76.4883767856649,42.4394781332616],[-76.4884104260529,42.4395755863947],[-76.4883244792964,42.4395916322492],[-76.488291039311,42.4394943602163],[-76.4883767856649,42.4394781332616]]]}},{"type":"Feature","properties":{"Shape__Area":275.29296875},"geometry":{"type":"Polygon","coordinates":[[[-76.5068616830315,42.4265963862588],[-76.5069292670959,42.4266595501516],[-76.5068192272592,42.4267241269907],[-76.5067860859848,42.4266930401643],[-76.5067729451534,42.4267005956732],[-76.5067473126908,42.4266765370564],[-76.5067185243799,42.42669335605],[-76.506664857463,42.4266428964111],[-76.5067230375645,42.4266086295399],[-76.5067683949947,42.4266509791408],[-76.5068616830315,42.4265963862588]]]}},{"type":"Feature","properties":{"Shape__Area":1025.6953125},"geometry":{"type":"Polygon","coordinates":[[[-76.4916226080664,42.4397987119803],[-76.4915943071961,42.4398177782594],[-76.491594304043,42.4398199398617],[-76.4914761945425,42.439822276072],[-76.4914775435692,42.4398598247961],[-76.4913569274267,42.4398623389336],[-76.4913555787352,42.4398246101497],[-76.4913446501853,42.4398247814391],[-76.4913432974487,42.4397906547261],[-76.4913556289071,42.4397903045037],[-76.4913551472284,42.4397772480599],[-76.4913496323839,42.4397774236936],[-76.491348955158,42.4397609459773],[-76.4913295044441,42.439761470544],[-76.4913281523455,42.4397260834199],[-76.4913522146642,42.4397254725245],[-76.4913520246485,42.4397182682228],[-76.4913548326417,42.4397180904172],[-76.491354349119,42.4397062952831],[-76.491371293259,42.4397059487601],[-76.4913696511888,42.4396635373129],[-76.491284528092,42.4396653605085],[-76.4912828819656,42.4396249306028],[-76.4914475132819,42.4396216406681],[-76.4914473282539,42.4396110152548],[-76.4915186152347,42.4396094518456],[-76.4915190958946,42.4396232285231],[-76.4916299863696,42.4396208864936],[-76.4916315263206,42.4396650984428],[-76.4915929254392,42.4396659678507],[-76.4915946573477,42.4397152215977],[-76.4916168939776,42.4397296458889],[-76.4916177605657,42.4397546783448],[-76.4916147488683,42.4397570166984],[-76.4916193557582,42.4397601723122],[-76.4916341947202,42.439759824066],[-76.4916355513249,42.4397922393208],[-76.4916223170368,42.4397924087922],[-76.4916226080664,42.4397987119803]]]}},{"type":"Feature","properties":{"Shape__Area":186.44140625},"geometry":{"type":"Polygon","coordinates":[[[-76.4964349798465,42.4492246584467],[-76.4964358621965,42.4492388855312],[-76.4964625371455,42.4492380954867],[-76.4964661689359,42.4492948247429],[-76.4963052193484,42.4493005554599],[-76.4963007058818,42.4492292389969],[-76.4964349798465,42.4492246584467]]]}},{"type":"Feature","properties":{"Shape__Area":201.7265625},"geometry":{"type":"Polygon","coordinates":[[[-76.5029280877555,42.4320337377787],[-76.5028586144976,42.4320352195304],[-76.5028575415112,42.4320113582637],[-76.5028389952838,42.4320117053605],[-76.5028385090896,42.4320003595038],[-76.5028552505654,42.4320000111401],[-76.5028547685147,42.4319854242251],[-76.502822086235,42.4319863015711],[-76.5028191736888,42.431912735535],[-76.5028453379549,42.4319120336725],[-76.5028446621771,42.4318921339814],[-76.5028944867936,42.4318909985703],[-76.5028951674232,42.431907116126],[-76.5029194282697,42.43190659297],[-76.5029226409509,42.431981239568],[-76.5029154229451,42.4319814145637],[-76.502916101163,42.4319994236367],[-76.5029267281398,42.4319992510321],[-76.5029280877555,42.4320337377787]]]}},{"type":"Feature","properties":{"Shape__Area":227.16015625},"geometry":{"type":"Polygon","coordinates":[[[-76.5148246469273,42.4397513575577],[-76.5148028889565,42.4397523348754],[-76.5148076354999,42.4398135657813],[-76.5148000151298,42.4398141014019],[-76.5148004929439,42.4398351712588],[-76.5147159698658,42.4398389028296],[-76.5147140893907,42.4398167517804],[-76.5147096768414,42.4398171092564],[-76.5147023620607,42.4397213910619],[-76.5147109851827,42.4397208551478],[-76.5147095990011,42.4397046472321],[-76.5147895102179,42.4397010929671],[-76.5147903984545,42.4397136093802],[-76.5148187738126,42.4397122759027],[-76.5148239842081,42.4397148007392],[-76.5148246469273,42.4397513575577]]]}},{"type":"Feature","properties":{"Shape__Area":40.96875},"geometry":{"type":"Polygon","coordinates":[[[-76.5053546293148,42.4387415549726],[-76.5052657438289,42.438743209534],[-76.5052648140285,42.4387157769499],[-76.5053536982602,42.4387141223883],[-76.5053546293148,42.4387415549726]]]}},{"type":"Feature","properties":{"Shape__Area":273.7109375},"geometry":{"type":"Polygon","coordinates":[[[-76.4981891012887,42.452543814874],[-76.4981961706987,42.4526551114668],[-76.4981871447219,42.4526554648792],[-76.4981880282433,42.4526700520592],[-76.4980719982594,42.4526740180265],[-76.498070619141,42.452655558323],[-76.498066908526,42.4526557356212],[-76.4980617019834,42.4525765853167],[-76.4980510712717,42.4525769375247],[-76.4980496949707,42.4525564071522],[-76.4980613286446,42.4525560556904],[-76.4980608370578,42.4525483119169],[-76.4981891012887,42.452543814874]]]}},{"type":"Feature","properties":{"Shape__Area":13.65625},"geometry":{"type":"Polygon","coordinates":[[[-76.5138629464916,42.4337553270122],[-76.5138924959692,42.4337570014577],[-76.5138896704505,42.4337843623986],[-76.5138601221745,42.4337826888535],[-76.5138629464916,42.4337553270122]]]}},{"type":"Feature","properties":{"Shape__Area":206.1015625},"geometry":{"type":"Polygon","coordinates":[[[-76.5172527041264,42.44941575134],[-76.5172680838696,42.4494754576734],[-76.5172546440116,42.4494773405401],[-76.5172639336285,42.4495124617876],[-76.5171466935001,42.4495294123753],[-76.5171227232741,42.4494380963108],[-76.5171507044605,42.449434240313],[-76.5171496058409,42.4494304584519],[-76.5172527041264,42.44941575134]]]}},{"type":"Feature","properties":{"Shape__Area":440.33203125},"geometry":{"type":"Polygon","coordinates":[[[-76.4763613350457,42.4335221121253],[-76.4763624018424,42.4335430026544],[-76.4763747333714,42.433542654043],[-76.4763754040066,42.4335606632599],[-76.4763609674915,42.433561009907],[-76.4763619344771,42.4335819903723],[-76.4762581722525,42.4335844143239],[-76.4762538644571,42.4335233622297],[-76.4762517267812,42.4334247644782],[-76.4764501254003,42.4334223387792],[-76.4764525634248,42.4335210268366],[-76.4763613350457,42.4335221121253]]]}},{"type":"Feature","properties":{"Shape__Area":35.4375},"geometry":{"type":"Polygon","coordinates":[[[-76.5210428586448,42.4397386079831],[-76.5210907779576,42.4397428653918],[-76.521083516331,42.4397862619036],[-76.5210355969865,42.4397820035916],[-76.5210428586448,42.4397386079831]]]}},{"type":"Feature","properties":{"Shape__Area":214.75},"geometry":{"type":"Polygon","coordinates":[[[-76.4974403608084,42.4524343065907],[-76.4974462302434,42.452542000266],[-76.4973277949185,42.4525456935903],[-76.4973219247167,42.4524378198502],[-76.4974403608084,42.4524343065907]]]}},{"type":"Feature","properties":{"Shape__Area":1350.09765625},"geometry":{"type":"Polygon","coordinates":[[[-76.510679170562,42.4390985104166],[-76.5106623764513,42.4389029766801],[-76.510740018962,42.4388993184648],[-76.5107284933675,42.4387651286832],[-76.510835712867,42.4387600765713],[-76.5108472386903,42.4388942654421],[-76.5108731195204,42.4388930466013],[-76.5108726769506,42.4388878975416],[-76.5109946858634,42.4388821487983],[-76.51101192237,42.4390828306456],[-76.510679170562,42.4390985104166]]]}},{"type":"Feature","properties":{"Shape__Area":158.56640625},"geometry":{"type":"Polygon","coordinates":[[[-76.4949745272327,42.43184978153],[-76.4951086890523,42.4318987771716],[-76.4950711170769,42.4319553839958],[-76.494936954913,42.4319065683694],[-76.4949745272327,42.43184978153]]]}},{"type":"Feature","properties":{"Shape__Area":46.26171875},"geometry":{"type":"Polygon","coordinates":[[[-76.5145932846731,42.4490729307952],[-76.5145803200032,42.4490993952103],[-76.51449281411,42.4490691784043],[-76.514511694944,42.4490419973173],[-76.5145932846731,42.4490729307952]]]}},{"type":"Feature","properties":{"Shape__Area":479.625},"geometry":{"type":"Polygon","coordinates":[[[-76.4934245693376,42.4468859451085],[-76.4934249525337,42.4468976510115],[-76.4934395925954,42.4468973023763],[-76.4934413438321,42.4469350314049],[-76.4934272058073,42.4469353804341],[-76.4934299280566,42.446994539885],[-76.4934442678858,42.4469941910137],[-76.4934463174539,42.4470332698139],[-76.4934328808133,42.4470336193938],[-76.4934340407756,42.4470572358741],[-76.4932734854023,42.4470614330496],[-76.4932710751327,42.447011162428],[-76.4932668640069,42.4470113391778],[-76.493265984413,42.4469948613346],[-76.4932712993099,42.446994685452],[-76.493268862246,42.4469461505725],[-76.4932628459478,42.4469463259038],[-76.4932618708031,42.4469269670495],[-76.4932681873544,42.4469267919541],[-76.4932663349946,42.4468901431943],[-76.4934245693376,42.4468859451085]]]}},{"type":"Feature","properties":{"Shape__Area":15.3125},"geometry":{"type":"Polygon","coordinates":[[[-76.5135141120794,42.4331957269346],[-76.5135292280829,42.433214735058],[-76.5134935223576,42.433230200179],[-76.5134785061192,42.4332111020823],[-76.5135141120794,42.4331957269346]]]}},{"type":"Feature","properties":{"Shape__Area":237.8125},"geometry":{"type":"Polygon","coordinates":[[[-76.4810180734764,42.4392891075593],[-76.4811175593711,42.4393329559837],[-76.4810426140478,42.4394263522121],[-76.4810375042623,42.4394240969183],[-76.4810275585913,42.4394365138948],[-76.4809331823749,42.4393949206916],[-76.4810180734764,42.4392891075593]]]}},{"type":"Feature","properties":{"Shape__Area":348.703125},"geometry":{"type":"Polygon","coordinates":[[[-76.4883830576446,42.4397734743654],[-76.4883837398022,42.4397861708702],[-76.4883941577932,42.4397924824541],[-76.4883948379147,42.4398065293974],[-76.488384797781,42.4398156149469],[-76.4883808876164,42.4398156117081],[-76.4883810821867,42.4398195740612],[-76.4883982282766,42.4398191381156],[-76.4884011525755,42.4398739756169],[-76.4883802976628,42.439874678579],[-76.4883807855222,42.4398832326721],[-76.4883238357103,42.4398848969432],[-76.4883240279664,42.4398903888931],[-76.4882900382615,42.4398913519401],[-76.4882895490551,42.4398836972388],[-76.4882476390747,42.4398848337648],[-76.4882471519249,42.4398750183605],[-76.4882006300477,42.4398764202314],[-76.4881959592595,42.4397822330455],[-76.4882435845765,42.4397809221211],[-76.4882433891969,42.4397775008434],[-76.4883830576446,42.4397734743654]]]}},{"type":"Feature","properties":{"Shape__Area":263.453125},"geometry":{"type":"Polygon","coordinates":[[[-76.4821451221005,42.4376440603867],[-76.4822805680765,42.4376441798666],[-76.4822804744777,42.4377024360003],[-76.4822864908472,42.4377024413038],[-76.4822864418071,42.4377329657242],[-76.4822795235865,42.4377329596257],[-76.4822795069488,42.4377433147911],[-76.4822331871529,42.4377439041546],[-76.482207220728,42.4377438812507],[-76.482207168611,42.437776295385],[-76.4821449094445,42.4377762404448],[-76.4821451221005,42.4376440603867]]]}},{"type":"Feature","properties":{"Shape__Area":2796.37109375},"geometry":{"type":"Polygon","coordinates":[[[-76.479558481699,42.4489025009929],[-76.4795580733414,42.449028378563],[-76.4793278449801,42.4490278097863],[-76.4793285705957,42.4488919383623],[-76.4793278066679,42.448808468887],[-76.4793067490061,42.4488086298374],[-76.4793061503546,42.4487457808016],[-76.4793295149851,42.4487458020028],[-76.4793252975799,42.4483249449034],[-76.479317776307,42.4483250281081],[-76.4793184795389,42.4483246686293],[-76.4793339217301,42.4483246826405],[-76.4795201402573,42.4483179182889],[-76.4795201387707,42.4483188185798],[-76.4795587439347,42.4483186734773],[-76.4795610542772,42.4487417795727],[-76.4795787017446,42.4487417955474],[-76.4795790902787,42.4488105881202],[-76.4795582321307,42.4488106592682],[-76.479558481699,42.4489025009929]]]}},{"type":"Feature","properties":{"Shape__Area":581.98046875},"geometry":{"type":"Polygon","coordinates":[[[-76.5105126807259,42.4380216146797],[-76.5107431113647,42.4381597938845],[-76.5106439529298,42.4382506733345],[-76.5104132210622,42.4381122227525],[-76.5105126807259,42.4380216146797]]]}},{"type":"Feature","properties":{"Shape__Area":213.33984375},"geometry":{"type":"Polygon","coordinates":[[[-76.5130995179448,42.4345775680406],[-76.513148453015,42.4346358119492],[-76.5131232916169,42.4346474112398],[-76.5131389515351,42.4346660488217],[-76.5130760492017,42.4346950483837],[-76.5130603905103,42.434676410794],[-76.5129534580106,42.434725709236],[-76.5129240960511,42.4346907633837],[-76.5130687706315,42.4346240647107],[-76.5130491963822,42.4346007674961],[-76.5130995179448,42.4345775680406]]]}},{"type":"Feature","properties":{"Shape__Area":325.55859375},"geometry":{"type":"Polygon","coordinates":[[[-76.4866712742378,42.4403282297401],[-76.4866721473074,42.4403477695454],[-76.4867016250068,42.4403470741633],[-76.4867049048263,42.4404306349646],[-76.4866977866924,42.4404308090229],[-76.4867000044664,42.4404880776585],[-76.4866071591059,42.4404900700285],[-76.4866068684375,42.4404838568582],[-76.4865948370417,42.4404842068229],[-76.4865934835764,42.4404514304978],[-76.4866053139961,42.4404513504517],[-76.4866044385597,42.4404341523075],[-76.4865699481742,42.4404348434334],[-76.4865685941688,42.4404024272246],[-76.4865532530349,42.4404027743911],[-76.4865523846807,42.4403809838553],[-76.486567925148,42.4403806368573],[-76.4865667687899,42.4403501114313],[-76.4865700788176,42.4403499341667],[-76.4865694063338,42.4403303945299],[-76.4866712742378,42.4403282297401]]]}},{"type":"Feature","properties":{"Shape__Area":263.59765625},"geometry":{"type":"Polygon","coordinates":[[[-76.5211837341797,42.4431637974202],[-76.5211858407845,42.4431034474147],[-76.5211265809584,42.443102312413],[-76.5211277305977,42.4430693938195],[-76.5211869903929,42.4430705288206],[-76.5211887129794,42.4430211513783],[-76.5212850093164,42.4430229951251],[-76.5212800307351,42.4431656411714],[-76.5211837341797,42.4431637974202]]]}},{"type":"Feature","properties":{"Shape__Area":178.80078125},"geometry":{"type":"Polygon","coordinates":[[[-76.5044793524047,42.4365610223193],[-76.5044811954281,42.4366100959175],[-76.5044209413325,42.4366112248377],[-76.5044205602426,42.4365952875693],[-76.5044078271814,42.4365956389209],[-76.5044046099119,42.4365230630979],[-76.5044081187894,42.4365230655138],[-76.5044069512133,42.4364956021384],[-76.5044032417954,42.4364955995844],[-76.5044016864994,42.4364565203471],[-76.5044579309395,42.4364552986488],[-76.5044592860936,42.4364931173358],[-76.5044803398002,42.4364927717022],[-76.5044830618265,42.4365610248708],[-76.5044793524047,42.4365610223193]]]}},{"type":"Feature","properties":{"Shape__Area":12.05859375},"geometry":{"type":"Polygon","coordinates":[[[-76.5152045940483,42.433306144265],[-76.5152502055743,42.4333076127348],[-76.5152492867348,42.4333233691457],[-76.5152036740786,42.4333218115457],[-76.5152045940483,42.433306144265]]]}},{"type":"Feature","properties":{"Shape__Area":255.30078125},"geometry":{"type":"Polygon","coordinates":[[[-76.5063093986339,42.4327939393838],[-76.5063100643852,42.4328239232457],[-76.5063239986386,42.4328235724894],[-76.5063243790861,42.4328404109566],[-76.5063915472238,42.4328392856754],[-76.5063930799933,42.4328977230318],[-76.5062652605533,42.4328997087498],[-76.506265743181,42.4329144757076],[-76.5062213327988,42.4329150760489],[-76.5062179708167,42.4327954084178],[-76.5063093986339,42.4327939393838]]]}},{"type":"Feature","properties":{"Shape__Area":10.91796875},"geometry":{"type":"Polygon","coordinates":[[[-76.5201983960754,42.418695111203],[-76.520195912413,42.4187169870509],[-76.5201663882209,42.4187151476857],[-76.5201688718935,42.4186932718384],[-76.5201983960754,42.418695111203]]]}},{"type":"Feature","properties":{"Shape__Area":388.21484375},"geometry":{"type":"Polygon","coordinates":[[[-76.4967412330852,42.4457920655272],[-76.4967173673551,42.4457930378184],[-76.4967164928029,42.4457731379895],[-76.4967030698466,42.4457637639024],[-76.4967023878317,42.4457488167287],[-76.4967140320378,42.4457399108304],[-76.4967133544854,42.4457217226064],[-76.4967346123894,42.4457210184338],[-76.4967425343483,42.4457210244182],[-76.4967420383886,42.4457166117093],[-76.4969096965498,42.4457114256075],[-76.4969095009282,42.4457078242895],[-76.4969951341056,42.4457050970115],[-76.4969997336779,42.445787578085],[-76.4969137991707,42.4457901250806],[-76.4969072700654,42.4457982236922],[-76.4968836063703,42.4457990161118],[-76.4968769969181,42.4457921680028],[-76.4967857487056,42.4457949809823],[-76.4967859414702,42.4458006529715],[-76.4967709002893,42.4458011826901],[-76.4967704072778,42.4457955095739],[-76.4967414277042,42.4457963879791],[-76.4967412330852,42.4457920655272]]]}},{"type":"Feature","properties":{"Shape__Area":105.328125},"geometry":{"type":"Polygon","coordinates":[[[-76.4958774380224,42.45298300668],[-76.4958792514554,42.4530497286659],[-76.4958333216896,42.4530503238192],[-76.495833988446,42.4530752660034],[-76.4958022999247,42.453075691956],[-76.4958016302101,42.4530520101774],[-76.4957972183425,42.4530520068083],[-76.4957952069969,42.4529842943494],[-76.4958774380224,42.45298300668]]]}}]}
\ No newline at end of file
diff --git a/inst/examples/buildings_app.R b/inst/examples/buildings_app.R
index 85034c05..94586338 100644
--- a/inst/examples/buildings_app.R
+++ b/inst/examples/buildings_app.R
@@ -1,32 +1,104 @@
library(shiny)
library(leaflet)
+library(yyjsonr)
+library(sf)
library(leaflet.extras2)
+options("shiny.autoreload" = TRUE)
+cols <- c("green","orange","red","pink","yellow","blue","lightblue")
+darkcols <- c("lightgray","gray","#c49071","#876302","#443408")
+
+## Custom GeoJSON ###########
+## Get a Sample Building Dataset from
+# https://hub.arcgis.com/datasets/IthacaNY::buildings/explore?location=42.432557%2C-76.486649%2C13.42
+geojson <- yyjsonr::read_geojson_file("Buildings_mini.geojson")
+geojson$height= sample(seq(50,100,5), nrow(geojson), replace = TRUE)
+geojson$color= sample(cols, nrow(geojson), replace = TRUE)
+geojson$wallColor= sample(cols, nrow(geojson), replace = TRUE)
+geojson$roofColor= sample(darkcols, nrow(geojson), replace = TRUE)
+geojson$shape= sample(c("cylinder","sphere",""), nrow(geojson), replace = TRUE)
+geojson$roofHeight= geojson$height + sample(seq(1,10,1), nrow(geojson), replace = TRUE)
+geojson$roofShape= sample(c("dome","pyramidal", "butterfly","gabled","half-hipped",
+ "gambrel","onion"), nrow(geojson), replace = TRUE)
+geojson <- yyjsonr::write_geojson_str(geojson)
+class(geojson) <- "json"
+
+## UI ###########
ui <- fluidPage(
- leafletOutput("map", height = "700px"),
- dateInput("date", "Date"),
- sliderInput("time", "Time", 0, max = 24, value = 4, step = 1)
- # actionButton("update", "Update Date")
+ titlePanel("OSM Buildings (2.5D)"),
+ sidebarLayout(
+ sidebarPanel(
+ h4("Use the OSM Buildings or a Custom GeoJSON")
+ , selectInput("src", label = "Data Source", choices = c("OSM", "GeoJSON"))
+ , h4("Change the Date and Time-Slider to Adapt the Shadow")
+ , dateInput("date", "Date")
+ , sliderInput("time", "Time", 7, max =20, value = 11, step = 1)
+ , h4("Change the Style and the Data")
+ , actionButton("style", "Update Style")
+ , actionButton("data", "Update Data")
+ ),
+ mainPanel(
+ leafletOutput("map", height = "700px")
+ ),
+ fluid = TRUE
)
+)
+## SERVER ###########
server <- function(input, output, session) {
output$map <- renderLeaflet({
- leaflet() %>%
- # addTiles() %>%
- # addProviderTiles("CartoDB.DarkMatter") %>%
- addBuildings() %>%
- addMarkers(data = breweries91) %>%
- setView(lng = 13.40438, lat = 52.51836, zoom = 16)
+ m <- leaflet() %>%
+ addProviderTiles("CartoDB")
+
+ if (input$src == "OSM") {
+ m <- m %>%
+ addBuildings(
+ group = "Buildings"
+ # , eachFn = leaflet::JS("function(e) { console.log('each feature:', e); }")
+ # , clickFn = leaflet::JS("function(e) { console.log('clicked:', e); }")
+ )
+ } else {
+ m <- m %>%
+ addBuildings(
+ group = "Buildings"
+ , buildingURL = NULL
+ , data = geojson
+ )
+ }
+
+ m %>%
+ addLayersControl(overlayGroups = "Buildings") %>%
+ setView(lng = -76.51, lat = 42.433, zoom = 15)
})
observe({
- # observeEvent(input$update, {
- # browser()
- date <- input$date
time <- formatC(input$time, width = 2, format = "d", flag = "0")
- updatetime <- paste0(date, " ", time, ":00:00")
+ updatetime <- paste0(input$date, " ", time, ":00:00")
leafletProxy("map") %>%
updateBuildingTime(time = as.POSIXct(updatetime))
})
+ observeEvent(input$style, {
+ leafletProxy("map") %>%
+ setBuildingStyle(style = list(color = sample(cols, 1),
+ wallColor = sample(cols, 1),
+ roofColor = sample(cols, 1),
+ roofShape = sample(c("dome","pyramidal", "butterfly","gabled","half-hipped",
+ "gambrel","onion"), 1),
+ shadows = sample(c(TRUE, FALSE), 1)))
+ })
+ observeEvent(input$data, {
+ geojson <- yyjsonr::read_geojson_file("Buildings_mini.geojson")
+ filtered <- geojson[sample(1:nrow(geojson), 10, F),]
+ filtered$height= sample(seq(50,140,5), nrow(filtered), replace = TRUE)
+ filtered$color= sample(cols, nrow(filtered), replace = TRUE)
+ filtered$wallColor= sample(cols, nrow(filtered), replace = TRUE)
+ filtered$roofColor= sample(cols, nrow(filtered), replace = TRUE)
+ filtered <- yyjsonr::write_geojson_str(filtered)
+ class(filtered) <- "json"
+
+ leafletProxy("map") %>%
+ setBuildingData(data = filtered)
+ })
}
+
shinyApp(ui, server)
diff --git a/inst/htmlwidgets/lfx-building/OSMBuildings.js b/inst/htmlwidgets/lfx-building/OSMBuildings.js
deleted file mode 100644
index 2c141700..00000000
--- a/inst/htmlwidgets/lfx-building/OSMBuildings.js
+++ /dev/null
@@ -1,2466 +0,0 @@
-const OSMBuildings = (function() {
-
-const
- m = Math,
- exp = m.exp,
- log = m.log,
- sin = m.sin,
- cos = m.cos,
- tan = m.tan,
- atan = m.atan,
- atan2 = m.atan2,
- min = m.min,
- max = m.max,
- sqrt = m.sqrt,
- ceil = m.ceil,
- pow = m.pow;
-
-
-/**
- * @class
- */
-class Qolor {
-
- /**
- * @constructor
- * @param r {Number} 0.0 .. 1.0 red value of a color
- * @param g {Number} 0.0 .. 1.0 green value of a color
- * @param b {Number} 0.0 .. 1.0 blue value of a color
- * @param a {Number} 0.0 .. 1.0 alpha value of a color, default 1
- */
- constructor (r, g, b, a = 1) {
- this.r = this._clamp(r, 1);
- this.g = this._clamp(g, 1);
- this.b = this._clamp(b, 1);
- this.a = this._clamp(a, 1);
- }
-
- /**
- * @param str {String} can be any color dfinition like: 'red', '#0099ff', 'rgb(64, 128, 255)', 'rgba(64, 128, 255, 0.5)'
- */
- static parse (str) {
- if (typeof str === 'string') {
- str = str.toLowerCase();
- str = Qolor.w3cColors[str] || str;
-
- let m;
-
- if ((m = str.match(/^#?(\w{2})(\w{2})(\w{2})$/))) {
- return new Qolor(parseInt(m[1], 16)/255, parseInt(m[2], 16)/255, parseInt(m[3], 16)/255);
- }
-
- if ((m = str.match(/^#?(\w)(\w)(\w)$/))) {
- return new Qolor(parseInt(m[1]+m[1], 16)/255, parseInt(m[2]+m[2], 16)/255, parseInt(m[3]+m[3], 16)/255);
- }
-
- if ((m = str.match(/rgba?\((\d+)\D+(\d+)\D+(\d+)(\D+([\d.]+))?\)/))) {
- return new Qolor(
- parseFloat(m[1])/255,
- parseFloat(m[2])/255,
- parseFloat(m[3])/255,
- m[4] ? parseFloat(m[5]) : 1
- );
- }
- }
-
- return new Qolor();
- }
-
- static fromHSL (h, s, l, a) {
- const qolor = new Qolor().fromHSL(h, s, l);
- qolor.a = a === undefined ? 1 : a;
- return qolor;
- }
-
- //***************************************************************************
-
- _hue2rgb(p, q, t) {
- if (t<0) t += 1;
- if (t>1) t -= 1;
- if (t<1/6) return p + (q - p)*6*t;
- if (t<1/2) return q;
- if (t<2/3) return p + (q - p)*(2/3 - t)*6;
- return p;
- }
-
- _clamp(v, max) {
- if (v === undefined) {
- return;
- }
- return Math.min(max, Math.max(0, v || 0));
- }
-
- //***************************************************************************
-
- isValid () {
- return this.r !== undefined && this.g !== undefined && this.b !== undefined;
- }
-
- toHSL () {
- if (!this.isValid()) {
- return;
- }
-
- const max = Math.max(this.r, this.g, this.b);
- const min = Math.min(this.r, this.g, this.b);
- const range = max - min;
- const l = (max + min)/2;
-
- // achromatic
- if (!range) {
- return { h: 0, s: 0, l: l };
- }
-
- const s = l > 0.5 ? range/(2 - max - min) : range/(max + min);
-
- let h;
- switch (max) {
- case this.r:
- h = (this.g - this.b)/range + (this.g 0 ? WINDING_CLOCKWISE : WINDING_COUNTER_CLOCKWISE;
-}
-
-// enforce a polygon winding direcetion. Needed for proper backface culling.
-function makeWinding (points, direction) {
- let winding = getWinding(points);
- if (winding === direction) {
- return points;
- }
- let revPoints = [];
- for (let i = points.length-2; i >= 0; i -= 2) {
- revPoints.push(points[i], points[i+1]);
- }
- return revPoints;
-}
-
-function alignProperties(prop) {
- const item = {};
-
- prop = prop || {};
-
- item.height = prop.height || (prop.levels ? prop.levels *METERS_PER_LEVEL : DEFAULT_HEIGHT);
- item.minHeight = prop.minHeight || (prop.minLevel ? prop.minLevel*METERS_PER_LEVEL : 0);
-
- const wallColor = prop.material ? getMaterialColor(prop.material) : (prop.wallColor || prop.color);
- if (wallColor) {
- item.wallColor = wallColor;
- }
-
- const roofColor = prop.roofMaterial ? getMaterialColor(prop.roofMaterial) : prop.roofColor;
- if (roofColor) {
- item.roofColor = roofColor;
- }
-
- switch (prop.shape) {
- case 'cylinder':
- case 'cone':
- case 'dome':
- case 'sphere':
- item.shape = prop.shape;
- item.isRotational = true;
- break;
-
- case 'pyramid':
- item.shape = prop.shape;
- break;
- }
-
- switch (prop.roofShape) {
- case 'cone':
- case 'dome':
- item.roofShape = prop.roofShape;
- item.isRotational = true;
- break;
-
- case 'pyramid':
- item.roofShape = prop.roofShape;
- break;
- }
-
- if (item.roofShape && prop.roofHeight) {
- item.roofHeight = prop.roofHeight;
- item.height = max(0, item.height-item.roofHeight);
- } else {
- item.roofHeight = 0;
- }
-
- return item;
-}
-
-function getGeometries (geometry) {
- let
- polygon,
- geometries = [], sub;
-
- switch (geometry.type) {
- case 'GeometryCollection':
- geometries = [];
- for (let i = 0, il = geometry.geometries.length; i < il; i++) {
- if ((sub = getGeometries(geometry.geometries[i]))) {
- geometries.push.apply(geometries, sub);
- }
- }
- return geometries;
-
- case 'MultiPolygon':
- geometries = [];
- for (let i = 0, il = geometry.coordinates.length; i < il; i++) {
- if ((sub = getGeometries({ type: 'Polygon', coordinates: geometry.coordinates[i] }))) {
- geometries.push.apply(geometries, sub);
- }
- }
- return geometries;
-
- case 'Polygon':
- polygon = geometry.coordinates;
- break;
-
- default: return [];
- }
-
- let
- p, lat = 1, lon = 0,
- outer = [], inner = [];
-
- p = polygon[0];
- for (let i = 0, il = p.length; i < il; i++) {
- outer.push(p[i][lat], p[i][lon]);
- }
- outer = makeWinding(outer, WINDING_CLOCKWISE);
-
- for (let i = 0, il = polygon.length-1; i < il; i++) {
- p = polygon[i+1];
- inner[i] = [];
- for (let j = 0, jl = p.length; j < jl; j++) {
- inner[i].push(p[j][lat], p[j][lon]);
- }
- inner[i] = makeWinding(inner[i], WINDING_COUNTER_CLOCKWISE);
- }
-
- return [{
- outer: outer,
- inner: inner.length ? inner : null
- }];
-}
-
-function clone (obj) {
- let res = {};
- for (const p in obj) {
- if (obj.hasOwnProperty(p)) {
- res[p] = obj[p];
- }
- }
- return res;
-}
-
-class GeoJSON {
-
- static read (geojson) {
- if (!geojson || geojson.type !== 'FeatureCollection') {
- return [];
- }
-
- const collection = geojson.features;
- const res = [];
-
- for (let i = 0, il = collection.length; i < il; i++) {
- const feature = collection[i];
-
- if (feature.type !== 'Feature' || onEach(feature) === false) {
- continue;
- }
-
- const baseItem = alignProperties(feature.properties);
- const geometries = getGeometries(feature.geometry);
-
- for (let j = 0, jl = geometries.length; j < jl; j++) {
- const item = clone(baseItem);
- item.footprint = geometries[j].outer;
- if (item.isRotational) {
- item.radius = getLonDelta(item.footprint);
- }
-
- if (geometries[j].inner) {
- item.holes = geometries[j].inner;
- }
- if (feature.id || feature.properties.id) {
- item.id = feature.id || feature.properties.id;
- }
-
- if (feature.properties.relationId) {
- item.relationId = feature.properties.relationId;
- }
-
- res.push(item); // TODO: clone base properties!
- }
- }
-
- return res;
- }
-}
-
-let
- VERSION = '0.3.2',
- ATTRIBUTION = '© OSM Buildings',
-
- DATA_SRC = 'https://{s}.data.osmbuildings.org/0.2/{k}/tile/{z}/{x}/{y}.json',
-
- PI = Math.PI,
- HALF_PI = PI/2,
- QUARTER_PI = PI/4,
-
- MAP_TILE_SIZE = 256, // map tile size in pixels
- ZOOM, MAP_SIZE,
-
- MIN_ZOOM = 15,
-
- LAT = 'latitude', LON = 'longitude',
-
- WIDTH = 0, HEIGHT = 0,
- CENTER_X = 0, CENTER_Y = 0,
- ORIGIN_X = 0, ORIGIN_Y = 0,
-
- WALL_COLOR = Qolor.parse('rgba(200, 190, 180)'),
- ALT_COLOR = WALL_COLOR.lightness(0.8),
- ROOF_COLOR = WALL_COLOR.lightness(1.2),
-
- WALL_COLOR_STR = ''+ WALL_COLOR,
- ALT_COLOR_STR = ''+ ALT_COLOR,
- ROOF_COLOR_STR = ''+ ROOF_COLOR,
-
- PIXEL_PER_DEG = 0,
-
- MAX_HEIGHT, // taller buildings will be cut to this
- DEFAULT_HEIGHT = 5,
-
- CAM_X, CAM_Y, CAM_Z = 450,
-
- IS_ZOOMING;
-
-function onEach () {}
-
-function onClick () {}
-
-
-function getDistance (p1, p2) {
- const
- dx = p1.x-p2.x,
- dy = p1.y-p2.y;
- return dx*dx + dy*dy;
-}
-
-function isRotational (polygon) {
- const length = polygon.length;
- if (length < 16) {
- return false;
- }
-
- let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
- for (let i = 0; i < length-1; i+=2) {
- minX = Math.min(minX, polygon[i]);
- maxX = Math.max(maxX, polygon[i]);
- minY = Math.min(minY, polygon[i+1]);
- maxY = Math.max(maxY, polygon[i+1]);
- }
-
- const
- width = maxX-minX,
- height = (maxY-minY),
- ratio = width/height;
-
- if (ratio < 0.85 || ratio > 1.15) {
- return false;
- }
-
- const
- center = { x:minX+width/2, y:minY+height/2 },
- radius = (width+height)/4,
- sqRadius = radius*radius;
-
- for (let i = 0; i < length-1; i+=2) {
- const dist = getDistance({ x:polygon[i], y:polygon[i+1] }, center);
- if (dist/sqRadius < 0.8 || dist/sqRadius > 1.2) {
- return false;
- }
- }
-
- return true;
-}
-
-function getSquareSegmentDistance (px, py, p1x, p1y, p2x, p2y) {
- let
- dx = p2x-p1x,
- dy = p2y-p1y,
- t;
- if (dx !== 0 || dy !== 0) {
- t = ((px-p1x) * dx + (py-p1y) * dy) / (dx*dx + dy*dy);
- if (t > 1) {
- p1x = p2x;
- p1y = p2y;
- } else if (t > 0) {
- p1x += dx*t;
- p1y += dy*t;
- }
- }
- dx = px-p1x;
- dy = py-p1y;
- return dx*dx + dy*dy;
-}
-
-function simplifyPolygon (buffer) {
- let
- sqTolerance = 2,
- len = buffer.length/2,
- markers = new Uint8Array(len),
-
- first = 0, last = len-1,
-
- maxSqDist,
- sqDist,
- index,
- firstStack = [], lastStack = [],
- newBuffer = [];
-
- markers[first] = markers[last] = 1;
-
- while (last) {
- maxSqDist = 0;
- for (let i = first+1; i < last; i++) {
- sqDist = getSquareSegmentDistance(
- buffer[i *2], buffer[i *2 + 1],
- buffer[first*2], buffer[first*2 + 1],
- buffer[last *2], buffer[last *2 + 1]
- );
- if (sqDist > maxSqDist) {
- index = i;
- maxSqDist = sqDist;
- }
- }
-
- if (maxSqDist > sqTolerance) {
- markers[index] = 1;
-
- firstStack.push(first);
- lastStack.push(index);
-
- firstStack.push(index);
- lastStack.push(last);
- }
-
- first = firstStack.pop();
- last = lastStack.pop();
- }
-
- for (let i = 0; i < len; i++) {
- if (markers[i]) {
- newBuffer.push(buffer[i*2], buffer[i*2 + 1]);
- }
- }
-
- return newBuffer;
-}
-
-function getCenter (footprint) {
- let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
- for (let i = 0, il = footprint.length-3; i < il; i += 2) {
- minX = min(minX, footprint[i]);
- maxX = max(maxX, footprint[i]);
- minY = min(minY, footprint[i+1]);
- maxY = max(maxY, footprint[i+1]);
- }
- return { x:minX+(maxX-minX)/2 <<0, y:minY+(maxY-minY)/2 <<0 };
-}
-
-let EARTH_RADIUS = 6378137;
-
-function getLonDelta (footprint) {
- let minLon = 180, maxLon = -180;
- for (let i = 0, il = footprint.length; i < il; i += 2) {
- minLon = min(minLon, footprint[i+1]);
- maxLon = max(maxLon, footprint[i+1]);
- }
- return (maxLon-minLon)/2;
-}
-
-
-function rad (deg) {
- return deg * PI / 180;
-}
-
-function deg (rad) {
- return rad / PI * 180;
-}
-
-function pixelToGeo (x, y) {
- const res = {};
- x /= MAP_SIZE;
- y /= MAP_SIZE;
- res[LAT] = y <= 0 ? 90 : y >= 1 ? -90 : deg(2 * atan(exp(PI * (1 - 2*y))) - HALF_PI);
- res[LON] = (x === 1 ? 1 : (x%1 + 1) % 1) * 360 - 180;
- return res;
-}
-
-function geoToPixel (lat, lon) {
- const
- latitude = min(1, max(0, 0.5 - (log(tan(QUARTER_PI + HALF_PI * lat / 180)) / PI) / 2)),
- longitude = lon/360 + 0.5;
- return {
- x: longitude*MAP_SIZE <<0,
- y: latitude *MAP_SIZE <<0
- };
-}
-
-function fromRange (sVal, sMin, sMax, dMin, dMax) {
- sVal = min(max(sVal, sMin), sMax);
- const rel = (sVal-sMin) / (sMax-sMin),
- range = dMax-dMin;
- return min(max(dMin + rel*range, dMin), dMax);
-}
-
-function isVisible (polygon) {
- const
- maxX = WIDTH+ORIGIN_X,
- maxY = HEIGHT+ORIGIN_Y;
-
- // TODO: checking footprint is sufficient for visibility - NOT VALID FOR SHADOWS!
- for (let i = 0, il = polygon.length-3; i < il; i+=2) {
- if (polygon[i] > ORIGIN_X && polygon[i] < maxX && polygon[i+1] > ORIGIN_Y && polygon[i+1] < maxY) {
- return true;
- }
- }
- return false;
-}
-
-
-let cacheData = {};
-let cacheIndex = [];
-let cacheSize = 0;
-let maxCacheSize = 1024*1024 * 5; // 5MB
-
-function xhr (url, callback) {
- if (cacheData[url]) {
- if (callback) {
- callback(cacheData[url]);
- }
- return;
- }
-
- const req = new XMLHttpRequest();
-
- req.onreadystatechange = function () {
- if (req.readyState !== 4) {
- return;
- }
- if (!req.status || req.status < 200 || req.status > 299) {
- return;
- }
- if (callback && req.responseText) {
- const responseText = req.responseText;
-
- cacheData[url] = responseText;
- cacheIndex.push({ url: url, size: responseText.length });
- cacheSize += responseText.length;
-
- callback(responseText);
-
- while (cacheSize > maxCacheSize) {
- let item = cacheIndex.shift();
- cacheSize -= item.size;
- delete cacheData[item.url];
- }
- }
- };
-
- req.open('GET', url);
- req.send(null);
-
- return req;
-}
-
-class Request {
-
- static loadJSON (url, callback) {
- return xhr(url, responseText => {
- let json;
- try {
- json = JSON.parse(responseText);
- } catch(ex) {}
-
- callback(json);
- });
- }
-}
-
-
-class Data {
-
- static getPixelFootprint (buffer) {
- let footprint = new Int32Array(buffer.length),
- px;
-
- for (let i = 0, il = buffer.length-1; i < il; i+=2) {
- px = geoToPixel(buffer[i], buffer[i+1]);
- footprint[i] = px.x;
- footprint[i+1] = px.y;
- }
-
- footprint = simplifyPolygon(footprint);
- if (footprint.length < 8) { // 3 points & end==start (*2)
- return;
- }
-
- return footprint;
- }
-
- static resetItems () {
- this.items = [];
- this.cache = {};
- Picking.reset();
- }
-
- static addRenderItems (data, allAreNew) {
- let item, scaledItem, id;
- let geojson = GeoJSON.read(data);
- for (let i = 0, il = geojson.length; i < il; i++) {
- item = geojson[i];
- id = item.id || [item.footprint[0], item.footprint[1], item.height, item.minHeight].join(',');
- if (!this.cache[id]) {
- if ((scaledItem = this.scaleItem(item))) {
- scaledItem.scale = allAreNew ? 0 : 1;
- this.items.push(scaledItem);
- this.cache[id] = 1;
- }
- }
- }
- fadeIn();
- }
-
- static scalePolygon (buffer, factor) {
- return buffer.map(coord => coord*factor);
- }
-
- static scale (factor) {
- Data.items = Data.items.map(item => {
- // item.height = Math.min(item.height*factor, MAX_HEIGHT); // TODO: should be filtered by renderer
-
- item.height *= factor;
- item.minHeight *= factor;
-
- item.footprint = Data.scalePolygon(item.footprint, factor);
- item.center.x *= factor;
- item.center.y *= factor;
-
- if (item.radius) {
- item.radius *= factor;
- }
-
- if (item.holes) {
- for (let i = 0, il = item.holes.length; i < il; i++) {
- item.holes[i] = Data.scalePolygon(item.holes[i], factor);
- }
- }
-
- item.roofHeight *= factor;
-
- return item;
- });
- }
-
- static scaleItem (item) {
- let
- res = {},
- // TODO: calculate this on zoom change only
- zoomScale = 6 / pow(2, ZOOM-MIN_ZOOM); // TODO: consider using HEIGHT / (devicePixelRatio || 1)
-
- if (item.id) {
- res.id = item.id;
- }
-
- res.height = min(item.height/zoomScale, MAX_HEIGHT);
-
- res.minHeight = isNaN(item.minHeight) ? 0 : item.minHeight / zoomScale;
- if (res.minHeight > MAX_HEIGHT) {
- return;
- }
-
- res.footprint = this.getPixelFootprint(item.footprint);
- if (!res.footprint) {
- return;
- }
- res.center = getCenter(res.footprint);
-
- if (item.radius) {
- res.radius = item.radius*PIXEL_PER_DEG;
- }
- if (item.shape) {
- res.shape = item.shape;
- }
- if (item.roofShape) {
- res.roofShape = item.roofShape;
- }
- if ((res.roofShape === 'cone' || res.roofShape === 'dome') && !res.shape && isRotational(res.footprint)) {
- res.shape = 'cylinder';
- }
-
- if (item.holes) {
- res.holes = [];
- let innerFootprint;
- for (let i = 0, il = item.holes.length; i < il; i++) {
- // TODO: simplify
- if ((innerFootprint = this.getPixelFootprint(item.holes[i]))) {
- res.holes.push(innerFootprint);
- }
- }
- }
-
- let color;
-
- if (item.wallColor) {
- if ((color = Qolor.parse(item.wallColor))) {
- res.altColor = ''+ color.lightness(0.8);
- res.wallColor = ''+ color;
- }
- }
-
- if (item.roofColor) {
- if ((color = Qolor.parse(item.roofColor))) {
- res.roofColor = ''+ color;
- }
- }
-
- if (item.relationId) {
- res.relationId = item.relationId;
- }
- res.hitColor = Picking.idToColor(item.relationId || item.id);
-
- res.roofHeight = isNaN(item.roofHeight) ? 0 : item.roofHeight/zoomScale;
-
- if (res.height+res.roofHeight <= res.minHeight) {
- return;
- }
-
- return res;
- }
-
- static set (data) {
- this.resetItems();
- this._staticData = data;
- this.addRenderItems(this._staticData, true);
- }
-
- static load (src, key) {
- this.src = src || DATA_SRC.replace('{k}', (key || 'anonymous'));
- this.update();
- }
-
- static update () {
- this.resetItems();
-
- if (ZOOM < MIN_ZOOM) {
- return;
- }
-
- if (this._staticData) {
- this.addRenderItems(this._staticData);
- }
-
- if (this.src) {
- let
- tileZoom = 16,
- tileSize = 256,
- zoomedTileSize = ZOOM > tileZoom ? tileSize << (ZOOM - tileZoom) : tileSize >> (tileZoom - ZOOM),
- minX = ORIGIN_X / zoomedTileSize << 0,
- minY = ORIGIN_Y / zoomedTileSize << 0,
- maxX = ceil((ORIGIN_X + WIDTH) / zoomedTileSize),
- maxY = ceil((ORIGIN_Y + HEIGHT) / zoomedTileSize),
- x, y;
-
- let scope = this;
-
- function callback (json) {
- scope.addRenderItems(json);
- }
-
- for (y = minY; y <= maxY; y++) {
- for (x = minX; x <= maxX; x++) {
- this.loadTile(x, y, tileZoom, callback);
- }
- }
- }
- }
-
- static loadTile (x, y, zoom, callback) {
- let s = 'abcd'[(x+y) % 4];
- let url = this.src.replace('{s}', s).replace('{x}', x).replace('{y}', y).replace('{z}', zoom);
- return Request.loadJSON(url, callback);
- }
-}
-
-Data.cache = {}; // maintain a list of cached items in order to avoid duplicates on tile borders
-Data.items = [];
-
-class Extrusion {
-
- static draw (context, polygon, innerPolygons, height, minHeight, color, altColor, roofColor) {
- let
- roof = this._extrude(context, polygon, height, minHeight, color, altColor),
- innerRoofs = [];
-
- if (innerPolygons) {
- for (let i = 0, il = innerPolygons.length; i < il; i++) {
- innerRoofs[i] = this._extrude(context, innerPolygons[i], height, minHeight, color, altColor);
- }
- }
-
- context.fillStyle = roofColor;
-
- context.beginPath();
- this._ring(context, roof);
- if (innerPolygons) {
- for (let i = 0, il = innerRoofs.length; i < il; i++) {
- this._ring(context, innerRoofs[i]);
- }
- }
- context.closePath();
- context.fill();
- }
-
- static _extrude (context, polygon, height, minHeight, color, altColor) {
- let
- scale = CAM_Z / (CAM_Z-height),
- minScale = CAM_Z / (CAM_Z-minHeight),
- a = { x:0, y:0 },
- b = { x:0, y:0 },
- _a, _b,
- roof = [];
-
- for (let i = 0, il = polygon.length-3; i < il; i += 2) {
- a.x = polygon[i ]-ORIGIN_X;
- a.y = polygon[i+1]-ORIGIN_Y;
- b.x = polygon[i+2]-ORIGIN_X;
- b.y = polygon[i+3]-ORIGIN_Y;
-
- _a = Buildings.project(a, scale);
- _b = Buildings.project(b, scale);
-
- if (minHeight) {
- a = Buildings.project(a, minScale);
- b = Buildings.project(b, minScale);
- }
-
- // backface culling check
- if ((b.x-a.x) * (_a.y-a.y) > (_a.x-a.x) * (b.y-a.y)) {
- // depending on direction, set wall shading
- if ((a.x < b.x && a.y < b.y) || (a.x > b.x && a.y > b.y)) {
- context.fillStyle = altColor;
- } else {
- context.fillStyle = color;
- }
-
- context.beginPath();
- this._ring(context, [
- b.x, b.y,
- a.x, a.y,
- _a.x, _a.y,
- _b.x, _b.y
- ]);
- context.closePath();
- context.fill();
- }
-
- roof[i] = _a.x;
- roof[i+1] = _a.y;
- }
-
- return roof;
- }
-
- static _ring (context, polygon) {
- context.moveTo(polygon[0], polygon[1]);
- for (let i = 2, il = polygon.length-1; i < il; i += 2) {
- context.lineTo(polygon[i], polygon[i+1]);
- }
- }
-
- static simplified (context, polygon, innerPolygons) {
- context.beginPath();
- this._ringAbs(context, polygon);
- if (innerPolygons) {
- for (let i = 0, il = innerPolygons.length; i < il; i++) {
- this._ringAbs(context, innerPolygons[i]);
- }
- }
- context.closePath();
- context.fill();
- }
-
- static _ringAbs (context, polygon) {
- context.moveTo(polygon[0]-ORIGIN_X, polygon[1]-ORIGIN_Y);
- for (let i = 2, il = polygon.length-1; i < il; i += 2) {
- context.lineTo(polygon[i]-ORIGIN_X, polygon[i+1]-ORIGIN_Y);
- }
- }
-
- static shadow (context, polygon, innerPolygons, height, minHeight) {
- let
- mode = null,
- a = { x:0, y:0 },
- b = { x:0, y:0 },
- _a, _b;
-
- for (let i = 0, il = polygon.length-3; i < il; i += 2) {
- a.x = polygon[i ]-ORIGIN_X;
- a.y = polygon[i+1]-ORIGIN_Y;
- b.x = polygon[i+2]-ORIGIN_X;
- b.y = polygon[i+3]-ORIGIN_Y;
-
- _a = Shadows.project(a, height);
- _b = Shadows.project(b, height);
-
- if (minHeight) {
- a = Shadows.project(a, minHeight);
- b = Shadows.project(b, minHeight);
- }
-
- // mode 0: floor edges, mode 1: roof edges
- if ((b.x-a.x) * (_a.y-a.y) > (_a.x-a.x) * (b.y-a.y)) {
- if (mode === 1) {
- context.lineTo(a.x, a.y);
- }
- mode = 0;
- if (!i) {
- context.moveTo(a.x, a.y);
- }
- context.lineTo(b.x, b.y);
- } else {
- if (mode === 0) {
- context.lineTo(_a.x, _a.y);
- }
- mode = 1;
- if (!i) {
- context.moveTo(_a.x, _a.y);
- }
- context.lineTo(_b.x, _b.y);
- }
- }
-
- if (innerPolygons) {
- for (let i = 0, il = innerPolygons.length; i < il; i++) {
- this._ringAbs(context, innerPolygons[i]);
- }
- }
- }
-
- static hitArea (context, polygon, innerPolygons, height, minHeight, color) {
- let
- mode = null,
- a = { x:0, y:0 },
- b = { x:0, y:0 },
- scale = CAM_Z / (CAM_Z-height),
- minScale = CAM_Z / (CAM_Z-minHeight),
- _a, _b;
-
- context.fillStyle = color;
- context.beginPath();
-
- for (let i = 0, il = polygon.length-3; i < il; i += 2) {
- a.x = polygon[i ]-ORIGIN_X;
- a.y = polygon[i+1]-ORIGIN_Y;
- b.x = polygon[i+2]-ORIGIN_X;
- b.y = polygon[i+3]-ORIGIN_Y;
-
- _a = Buildings.project(a, scale);
- _b = Buildings.project(b, scale);
-
- if (minHeight) {
- a = Buildings.project(a, minScale);
- b = Buildings.project(b, minScale);
- }
-
- // mode 0: floor edges, mode 1: roof edges
- if ((b.x-a.x) * (_a.y-a.y) > (_a.x-a.x) * (b.y-a.y)) {
- if (mode === 1) { // mode is initially undefined
- context.lineTo(a.x, a.y);
- }
- mode = 0;
- if (!i) {
- context.moveTo(a.x, a.y);
- }
- context.lineTo(b.x, b.y);
- } else {
- if (mode === 0) { // mode is initially undefined
- context.lineTo(_a.x, _a.y);
- }
- mode = 1;
- if (!i) {
- context.moveTo(_a.x, _a.y);
- }
- context.lineTo(_b.x, _b.y);
- }
- }
-
- context.closePath();
- context.fill();
- }
-}
-
-class Cylinder {
-
- static draw (context, center, radius, topRadius, height, minHeight, color, altColor, roofColor) {
- let
- c = { x:center.x-ORIGIN_X, y:center.y-ORIGIN_Y },
- scale = CAM_Z / (CAM_Z-height),
- minScale = CAM_Z / (CAM_Z-minHeight),
- apex = Buildings.project(c, scale),
- a1, a2;
-
- topRadius *= scale;
-
- if (minHeight) {
- c = Buildings.project(c, minScale);
- radius = radius*minScale;
- }
-
- // common tangents for ground and roof circle
- let tangents = this._tangents(c, radius, apex, topRadius);
-
- // no tangents? top circle is inside bottom circle
- if (!tangents) {
- a1 = 1.5*PI;
- a2 = 1.5*PI;
- } else {
- a1 = atan2(tangents[0].y1-c.y, tangents[0].x1-c.x);
- a2 = atan2(tangents[1].y1-c.y, tangents[1].x1-c.x);
- }
-
- context.fillStyle = color;
- context.beginPath();
- context.arc(apex.x, apex.y, topRadius, HALF_PI, a1, true);
- context.arc(c.x, c.y, radius, a1, HALF_PI);
- context.closePath();
- context.fill();
-
- context.fillStyle = altColor;
- context.beginPath();
- context.arc(apex.x, apex.y, topRadius, a2, HALF_PI, true);
- context.arc(c.x, c.y, radius, HALF_PI, a2);
- context.closePath();
- context.fill();
-
- context.fillStyle = roofColor;
- this._circle(context, apex, topRadius);
- }
-
- static simplified (context, center, radius) {
- this._circle(context, { x:center.x-ORIGIN_X, y:center.y-ORIGIN_Y }, radius);
- }
-
- static shadow (context, center, radius, topRadius, height, minHeight) {
- let
- c = { x:center.x-ORIGIN_X, y:center.y-ORIGIN_Y },
- apex = Shadows.project(c, height),
- p1, p2;
-
- if (minHeight) {
- c = Shadows.project(c, minHeight);
- }
-
- // common tangents for ground and roof circle
- let tangents = this._tangents(c, radius, apex, topRadius);
-
- // TODO: no tangents? roof overlaps everything near cam position
- if (tangents) {
- p1 = atan2(tangents[0].y1-c.y, tangents[0].x1-c.x);
- p2 = atan2(tangents[1].y1-c.y, tangents[1].x1-c.x);
- context.moveTo(tangents[1].x2, tangents[1].y2);
- context.arc(apex.x, apex.y, topRadius, p2, p1);
- context.arc(c.x, c.y, radius, p1, p2);
- } else {
- context.moveTo(c.x+radius, c.y);
- context.arc(c.x, c.y, radius, 0, 2*PI);
- }
- }
-
- static hitArea (context, center, radius, topRadius, height, minHeight, color) {
- let
- c = { x:center.x-ORIGIN_X, y:center.y-ORIGIN_Y },
- scale = CAM_Z / (CAM_Z-height),
- minScale = CAM_Z / (CAM_Z-minHeight),
- apex = Buildings.project(c, scale),
- p1, p2;
-
- topRadius *= scale;
-
- if (minHeight) {
- c = Buildings.project(c, minScale);
- radius = radius*minScale;
- }
-
- // common tangents for ground and roof circle
- let tangents = this._tangents(c, radius, apex, topRadius);
-
- context.fillStyle = color;
- context.beginPath();
-
- // TODO: no tangents? roof overlaps everything near cam position
- if (tangents) {
- p1 = atan2(tangents[0].y1-c.y, tangents[0].x1-c.x);
- p2 = atan2(tangents[1].y1-c.y, tangents[1].x1-c.x);
- context.moveTo(tangents[1].x2, tangents[1].y2);
- context.arc(apex.x, apex.y, topRadius, p2, p1);
- context.arc(c.x, c.y, radius, p1, p2);
- } else {
- context.moveTo(c.x+radius, c.y);
- context.arc(c.x, c.y, radius, 0, 2*PI);
- }
-
- context.closePath();
- context.fill();
- }
-
- static _circle (context, center, radius) {
- context.beginPath();
- context.arc(center.x, center.y, radius, 0, PI*2);
- context.fill();
- }
-
- // http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Tangents_between_two_circles
- static _tangents (c1, r1, c2, r2) {
- let
- dx = c1.x-c2.x,
- dy = c1.y-c2.y,
- dr = r1-r2,
- sqdist = (dx*dx) + (dy*dy);
-
- if (sqdist <= dr*dr) {
- return;
- }
-
- let dist = sqrt(sqdist),
- vx = -dx/dist,
- vy = -dy/dist,
- c = dr/dist,
- res = [],
- h, nx, ny;
-
- // Let A, B be the centers, and C, D be points at which the tangent
- // touches first and second circle, and n be the normal vector to it.
- //
- // We have the system:
- // n * n = 1 (n is a unit vector)
- // C = A + r1 * n
- // D = B + r2 * n
- // n * CD = 0 (common orthogonality)
- //
- // n * CD = n * (AB + r2*n - r1*n) = AB*n - (r1 -/+ r2) = 0, <=>
- // AB * n = (r1 -/+ r2), <=>
- // v * n = (r1 -/+ r2) / d, where v = AB/|AB| = AB/d
- // This is a linear equation in unknown vector n.
- // Now we're just intersecting a line with a circle: v*n=c, n*n=1
-
- h = sqrt(max(0, 1 - c*c));
- for (let sign = 1; sign >= -1; sign -= 2) {
- nx = vx*c - sign*h*vy;
- ny = vy*c + sign*h*vx;
- res.push({
- x1: c1.x + r1*nx <<0,
- y1: c1.y + r1*ny <<0,
- x2: c2.x + r2*nx <<0,
- y2: c2.y + r2*ny <<0
- });
- }
-
- return res;
- }
-}
-
-class Pyramid {
-
- static draw (context, polygon, center, height, minHeight, color, altColor) {
- let
- c = { x:center.x-ORIGIN_X, y:center.y-ORIGIN_Y },
- scale = CAM_Z / (CAM_Z-height),
- minScale = CAM_Z / (CAM_Z-minHeight),
- apex = Buildings.project(c, scale),
- a = { x:0, y:0 },
- b = { x:0, y:0 };
-
- for (let i = 0, il = polygon.length-3; i < il; i += 2) {
- a.x = polygon[i ]-ORIGIN_X;
- a.y = polygon[i+1]-ORIGIN_Y;
- b.x = polygon[i+2]-ORIGIN_X;
- b.y = polygon[i+3]-ORIGIN_Y;
-
- if (minHeight) {
- a = Buildings.project(a, minScale);
- b = Buildings.project(b, minScale);
- }
-
- // backface culling check
- if ((b.x-a.x) * (apex.y-a.y) > (apex.x-a.x) * (b.y-a.y)) {
- // depending on direction, set shading
- if ((a.x < b.x && a.y < b.y) || (a.x > b.x && a.y > b.y)) {
- context.fillStyle = altColor;
- } else {
- context.fillStyle = color;
- }
-
- context.beginPath();
- this._triangle(context, a, b, apex);
- context.closePath();
- context.fill();
- }
- }
- }
-
- static _triangle (context, a, b, c) {
- context.moveTo(a.x, a.y);
- context.lineTo(b.x, b.y);
- context.lineTo(c.x, c.y);
- }
-
- static _ring (context, polygon) {
- context.moveTo(polygon[0]-ORIGIN_X, polygon[1]-ORIGIN_Y);
- for (let i = 2, il = polygon.length-1; i < il; i += 2) {
- context.lineTo(polygon[i]-ORIGIN_X, polygon[i+1]-ORIGIN_Y);
- }
- }
-
- static shadow (context, polygon, center, height, minHeight) {
- let
- a = { x:0, y:0 },
- b = { x:0, y:0 },
- c = { x:center.x-ORIGIN_X, y:center.y-ORIGIN_Y },
- apex = Shadows.project(c, height);
-
- for (let i = 0, il = polygon.length-3; i < il; i += 2) {
- a.x = polygon[i ]-ORIGIN_X;
- a.y = polygon[i+1]-ORIGIN_Y;
- b.x = polygon[i+2]-ORIGIN_X;
- b.y = polygon[i+3]-ORIGIN_Y;
-
- if (minHeight) {
- a = Shadows.project(a, minHeight);
- b = Shadows.project(b, minHeight);
- }
-
- // backface culling check
- if ((b.x-a.x) * (apex.y-a.y) > (apex.x-a.x) * (b.y-a.y)) {
- // depending on direction, set shading
- this._triangle(context, a, b, apex);
- }
- }
- }
-
- static hitArea (context, polygon, center, height, minHeight, color) {
- let
- c = { x:center.x-ORIGIN_X, y:center.y-ORIGIN_Y },
- scale = CAM_Z / (CAM_Z-height),
- minScale = CAM_Z / (CAM_Z-minHeight),
- apex = Buildings.project(c, scale),
- a = { x:0, y:0 },
- b = { x:0, y:0 };
-
- context.fillStyle = color;
- context.beginPath();
-
- for (let i = 0, il = polygon.length-3; i < il; i += 2) {
- a.x = polygon[i ]-ORIGIN_X;
- a.y = polygon[i+1]-ORIGIN_Y;
- b.x = polygon[i+2]-ORIGIN_X;
- b.y = polygon[i+3]-ORIGIN_Y;
-
- if (minHeight) {
- a = Buildings.project(a, minScale);
- b = Buildings.project(b, minScale);
- }
-
- // backface culling check
- if ((b.x-a.x) * (apex.y-a.y) > (apex.x-a.x) * (b.y-a.y)) {
- this._triangle(context, a, b, apex);
- }
- }
-
- context.closePath();
- context.fill();
- }
-}
-
-let animTimer;
-
-function fadeIn() {
- if (animTimer) {
- return;
- }
-
- animTimer = setInterval(t => {
- let dataItems = Data.items,
- isNeeded = false;
-
- for (let i = 0, il = dataItems.length; i < il; i++) {
- if (dataItems[i].scale < 1) {
- dataItems[i].scale += 0.5*0.2; // amount*easing
- if (dataItems[i].scale > 1) {
- dataItems[i].scale = 1;
- }
- isNeeded = true;
- }
- }
-
- Layers.render();
-
- if (!isNeeded) {
- clearInterval(animTimer);
- animTimer = null;
- }
- }, 33);
-}
-
-class Layers {
-
- static init () {
- Layers.container.className = 'osmb-container';
-
- // TODO: improve this
- Shadows.init(Layers.createContext(Layers.container));
- Simplified.init(Layers.createContext(Layers.container));
- Buildings.init(Layers.createContext(Layers.container));
- Picking.init(Layers.createContext());
- }
-
- static clear () {
- Shadows.clear();
- Simplified.clear();
- Buildings.clear();
- Picking.clear();
- }
-
- static setOpacity (opacity) {
- Shadows.setOpacity(opacity);
- Simplified.setOpacity(opacity);
- Buildings.setOpacity(opacity);
- Picking.setOpacity(opacity);
- }
-
- static render (quick) {
- // show on high zoom levels only
- if (ZOOM < MIN_ZOOM) {
- Layers.clear();
- return;
- }
-
- // don't render during zoom
- if (IS_ZOOMING) {
- return;
- }
-
- requestAnimationFrame(f => {
- if (!quick) {
- Shadows.render();
- Simplified.render();
- //HitAreas.render(); // TODO: do this on demand
- }
- Buildings.render();
- });
- }
-
- static createContext (container) {
- let canvas = document.createElement('CANVAS');
- canvas.className = 'osmb-layer';
-
- let context = canvas.getContext('2d');
- context.lineCap = 'round';
- context.lineJoin = 'round';
- context.lineWidth = 1;
- context.imageSmoothingEnabled = false;
-
- Layers.items.push(canvas);
- if (container) {
- container.appendChild(canvas);
- }
-
- return context;
- }
-
- static appendTo (parentNode) {
- parentNode.appendChild(Layers.container);
- }
-
- static remove () {
- Layers.container.parentNode.removeChild(Layers.container);
- }
-
- static setSize (width, height) {
- Layers.items.forEach(canvas => {
- canvas.width = width;
- canvas.height = height;
- });
- }
-
- // usually called after move: container jumps by move delta, cam is reset
- static setPosition (x, y) {
- Layers.container.style.left = x +'px';
- Layers.container.style.top = y +'px';
- }
-}
-
-Layers.container = document.createElement('DIV');
-Layers.items = [];
-
-class Buildings {
-
- static init (context) {
- this.context = context;
- }
-
- static clear () {
- this.context.clearRect(0, 0, WIDTH, HEIGHT);
- }
-
- static setOpacity (opacity) {
- this.context.canvas.style.opacity = opacity;
- }
-
- static project (p, m) {
- return {
- x: (p.x-CAM_X) * m + CAM_X <<0,
- y: (p.y-CAM_Y) * m + CAM_Y <<0
- };
- }
-
- static render () {
- this.clear();
-
- let
- context = this.context,
- item,
- h, mh,
- sortCam = { x:CAM_X+ORIGIN_X, y:CAM_Y+ORIGIN_Y },
- footprint,
- wallColor, altColor, roofColor,
- dataItems = Data.items;
-
- dataItems.sort((a, b) => {
- return (a.minHeight-b.minHeight) || getDistance(b.center, sortCam) - getDistance(a.center, sortCam) || (b.height-a.height);
- });
-
- for (let i = 0, il = dataItems.length; i < il; i++) {
- item = dataItems[i];
-
- if (Simplified.isSimple(item)) {
- continue;
- }
-
- footprint = item.footprint;
-
- if (!isVisible(footprint)) {
- continue;
- }
-
- // when fading in, use a dynamic height
- h = item.scale < 1 ? item.height*item.scale : item.height;
-
- mh = 0;
- if (item.minHeight) {
- mh = item.scale < 1 ? item.minHeight*item.scale : item.minHeight;
- }
-
- wallColor = item.wallColor || WALL_COLOR_STR;
- altColor = item.altColor || ALT_COLOR_STR;
- roofColor = item.roofColor || ROOF_COLOR_STR;
- context.strokeStyle = altColor;
-
- switch (item.shape) {
- case 'cylinder': Cylinder.draw(context, item.center, item.radius, item.radius, h, mh, wallColor, altColor, roofColor); break;
- case 'cone': Cylinder.draw(context, item.center, item.radius, 0, h, mh, wallColor, altColor); break;
- case 'dome': Cylinder.draw(context, item.center, item.radius, item.radius/2, h, mh, wallColor, altColor); break;
- case 'sphere': Cylinder.draw(context, item.center, item.radius, item.radius, h, mh, wallColor, altColor, roofColor); break;
- case 'pyramid': Pyramid.draw(context, footprint, item.center, h, mh, wallColor, altColor); break;
- default: Extrusion.draw(context, footprint, item.holes, h, mh, wallColor, altColor, roofColor);
- }
-
- switch (item.roofShape) {
- case 'cone': Cylinder.draw(context, item.center, item.radius, 0, h+item.roofHeight, h, roofColor, ''+ Qolor.parse(roofColor).lightness(0.9)); break;
- case 'dome': Cylinder.draw(context, item.center, item.radius, item.radius/2, h+item.roofHeight, h, roofColor, ''+ Qolor.parse(roofColor).lightness(0.9)); break;
- case 'pyramid': Pyramid.draw(context, footprint, item.center, h+item.roofHeight, h, roofColor, Qolor.parse(roofColor).lightness(0.9)); break;
- }
- }
- }
-}
-
-class Simplified {
-
- static init (context) {
- this.context = context;
- }
-
- static clear () {
- this.context.clearRect(0, 0, WIDTH, HEIGHT);
- }
-
- static setOpacity (opacity) {
- this.context.canvas.style.opacity = opacity;
- }
-
- static isSimple (item) {
- return (ZOOM <= Simplified.MAX_ZOOM && item.height+item.roofHeight < Simplified.MAX_HEIGHT);
- }
-
- static render () {
- this.clear();
-
- let context = this.context;
-
- // show on high zoom levels only and avoid rendering during zoom
- if (ZOOM > Simplified.MAX_ZOOM) {
- return;
- }
-
- let
- item,
- footprint,
- dataItems = Data.items;
-
- for (let i = 0, il = dataItems.length; i < il; i++) {
- item = dataItems[i];
-
- if (item.height >= Simplified.MAX_HEIGHT) {
- continue;
- }
-
- footprint = item.footprint;
-
- if (!isVisible(footprint)) {
- continue;
- }
-
- context.strokeStyle = item.altColor || ALT_COLOR_STR;
- context.fillStyle = item.roofColor || ROOF_COLOR_STR;
-
- switch (item.shape) {
- case 'cylinder':
- case 'cone':
- case 'dome':
- case 'sphere': Cylinder.simplified(context, item.center, item.radius); break;
- default: Extrusion.simplified(context, footprint, item.holes);
- }
- }
- }
-}
-
-Simplified.MAX_ZOOM = 16; // max zoom where buildings could render simplified
-Simplified.MAX_HEIGHT = 5; // max building height in order to be simple
-
-class Shadows {
-
- static init (context) {
- this.context = context;
- }
-
- static clear () {
- this.context.clearRect(0, 0, WIDTH, HEIGHT);
- }
-
- static setOpacity (opacity) {
- this.opacity = opacity;
- }
-
- static project (p, h) {
- return {
- x: p.x + this.direction.x*h,
- y: p.y + this.direction.y*h
- };
- }
-
- static render () {
- this.clear();
-
- let
- context = this.context,
- screenCenter,
- sun, length, alpha;
-
- // TODO: calculate this just on demand
- screenCenter = pixelToGeo(CENTER_X+ORIGIN_X, CENTER_Y+ORIGIN_Y);
- sun = getSunPosition(this.date, screenCenter.latitude, screenCenter.longitude);
-
- if (sun.altitude <= 0) {
- return;
- }
-
- length = 1 / tan(sun.altitude);
- alpha = length < 5 ? 0.75 : 1/length*5;
-
- this.direction.x = cos(sun.azimuth) * length;
- this.direction.y = sin(sun.azimuth) * length;
-
- let
- i, il,
- item,
- h, mh,
- footprint,
- dataItems = Data.items;
-
- context.canvas.style.opacity = alpha / (this.opacity * 2);
- context.shadowColor = this.blurColor;
- context.fillStyle = this.color;
- context.beginPath();
-
- for (i = 0, il = dataItems.length; i < il; i++) {
- item = dataItems[i];
-
- footprint = item.footprint;
-
- if (!isVisible(footprint)) {
- continue;
- }
-
- // when fading in, use a dynamic height
- h = item.scale < 1 ? item.height*item.scale : item.height;
-
- mh = 0;
- if (item.minHeight) {
- mh = item.scale < 1 ? item.minHeight*item.scale : item.minHeight;
- }
-
- switch (item.shape) {
- case 'cylinder': Cylinder.shadow(context, item.center, item.radius, item.radius, h, mh); break;
- case 'cone': Cylinder.shadow(context, item.center, item.radius, 0, h, mh); break;
- case 'dome': Cylinder.shadow(context, item.center, item.radius, item.radius/2, h, mh); break;
- case 'sphere': Cylinder.shadow(context, item.center, item.radius, item.radius, h, mh); break;
- case 'pyramid': Pyramid.shadow(context, footprint, item.center, h, mh); break;
- default: Extrusion.shadow(context, footprint, item.holes, h, mh);
- }
-
- switch (item.roofShape) {
- case 'cone': Cylinder.shadow(context, item.center, item.radius, 0, h+item.roofHeight, h); break;
- case 'dome': Cylinder.shadow(context, item.center, item.radius, item.radius/2, h+item.roofHeight, h); break;
- case 'pyramid': Pyramid.shadow(context, footprint, item.center, h+item.roofHeight, h); break;
- }
- }
-
- context.closePath();
- context.fill();
- }
-}
-
-Shadows.color = '#666666';
-Shadows.blurColor = '#000000';
-Shadows.date = new Date();
-Shadows.direction = { x:0, y:0 };
-Shadows.opacity = 1;
-
-
-
-class Picking {
-
- static init (context) {
- this.context = context;
- }
-
- static setOpacity (opacity) {}
-
- static clear () {}
-
- static reset () {
- this._idMapping = [null];
- }
-
- static render () {
- if (this._timer) {
- return;
- }
- let self = this;
- this._timer = setTimeout(t => {
- self._timer = null;
- self._render();
- }, 500);
- }
-
- static _render () {
- this.clear();
-
- let
- context = this.context,
- item,
- h, mh,
- sortCam = { x:CAM_X+ORIGIN_X, y:CAM_Y+ORIGIN_Y },
- footprint,
- color,
- dataItems = Data.items;
-
- dataItems.sort((a, b) => {
- return (a.minHeight-b.minHeight) || getDistance(b.center, sortCam) - getDistance(a.center, sortCam) || (b.height-a.height);
- });
-
- for (let i = 0, il = dataItems.length; i < il; i++) {
- item = dataItems[i];
-
- if (!(color = item.hitColor)) {
- continue;
- }
-
- footprint = item.footprint;
-
- if (!isVisible(footprint)) {
- continue;
- }
-
- h = item.height;
-
- mh = 0;
- if (item.minHeight) {
- mh = item.minHeight;
- }
-
- switch (item.shape) {
- case 'cylinder': Cylinder.hitArea(context, item.center, item.radius, item.radius, h, mh, color); break;
- case 'cone': Cylinder.hitArea(context, item.center, item.radius, 0, h, mh, color); break;
- case 'dome': Cylinder.hitArea(context, item.center, item.radius, item.radius/2, h, mh, color); break;
- case 'sphere': Cylinder.hitArea(context, item.center, item.radius, item.radius, h, mh, color); break;
- case 'pyramid': Pyramid.hitArea(context, footprint, item.center, h, mh, color); break;
- default: Extrusion.hitArea(context, footprint, item.holes, h, mh, color);
- }
-
- switch (item.roofShape) {
- case 'cone': Cylinder.hitArea(context, item.center, item.radius, 0, h+item.roofHeight, h, color); break;
- case 'dome': Cylinder.hitArea(context, item.center, item.radius, item.radius/2, h+item.roofHeight, h, color); break;
- case 'pyramid': Pyramid.hitArea(context, footprint, item.center, h+item.roofHeight, h, color); break;
- }
- }
-
- // otherwise fails on size 0
- if (WIDTH && HEIGHT) {
- this._imageData = this.context.getImageData(0, 0, WIDTH, HEIGHT).data;
- }
- }
-
- static getIdFromXY (x, y) {
- let imageData = this._imageData;
- if (!imageData) {
- return;
- }
- let pos = 4*((y|0) * WIDTH + (x|0));
- let index = imageData[pos] | (imageData[pos+1]<<8) | (imageData[pos+2]<<16);
- return this._idMapping[index];
- }
-
- static idToColor (id) {
- let index = this._idMapping.indexOf(id);
- if (index === -1) {
- this._idMapping.push(id);
- index = this._idMapping.length-1;
- }
- let r = index & 0xff;
- let g = (index >>8) & 0xff;
- let b = (index >>16) & 0xff;
- return 'rgb('+ [r, g, b].join(',') +')';
- }
-}
-
-Picking._idMapping = [null];
-
-
-class Debug {
-
- static point (x, y, color, size) {
- const context = this.context;
- context.fillStyle = color || '#ffcc00';
- context.beginPath();
- context.arc(x, y, size || 3, 0, 2*PI);
- context.closePath();
- context.fill();
- }
-
- static line (ax, ay, bx, by, color) {
- const context = this.context;
- context.strokeStyle = color || '#ffcc00';
- context.beginPath();
- context.moveTo(ax, ay);
- context.lineTo(bx, by);
- context.closePath();
- context.stroke();
- }
-}
-
-
-function setOrigin (origin) {
- ORIGIN_X = origin.x;
- ORIGIN_Y = origin.y;
-}
-
-function moveCam (offset) {
- CAM_X = CENTER_X + offset.x;
- CAM_Y = HEIGHT + offset.y;
- Layers.render(true);
-}
-
-function setSize (size) {
- WIDTH = size.width;
- HEIGHT = size.height;
- CENTER_X = WIDTH /2 <<0;
- CENTER_Y = HEIGHT/2 <<0;
-
- CAM_X = CENTER_X;
- CAM_Y = HEIGHT;
-
- Layers.setSize(WIDTH, HEIGHT);
- MAX_HEIGHT = CAM_Z-50;
-}
-
-function setZoom (z) {
- ZOOM = z;
- MAP_SIZE = MAP_TILE_SIZE < fadeIn() => Layers.render()
-}
-
-function onZoomStart () {
- IS_ZOOMING = true;
-}
-
-function onZoomEnd (e) {
- IS_ZOOMING = false;
- const factor = Math.pow(2, e.zoom-ZOOM);
-
- setZoom(e.zoom);
- // Layers.render(); // TODO: requestAnimationFrame() causes flickering because layers are already cleared
-
- // show on high zoom levels only
- if (ZOOM <= MIN_ZOOM) {
- Layers.clear();
- return;
- }
-
- Data.scale(factor);
-
- Shadows.render();
- Simplified.render();
- Buildings.render();
-
- Data.update(); // => fadeIn()
-}
-
-
-class OSMBuildings extends L.Layer {
-
- constructor (map) {
- super(map);
-
- this.offset = {x: 0, y: 0};
- Layers.init();
- if (map) {
- map.addLayer(this);
- }
- }
-
- addTo (map) {
- map.addLayer(this);
- return this;
- }
-
- onAdd (map) {
- this.map = map;
- Layers.appendTo(map._panes.overlayPane);
-
- let
- off = this.getOffset(),
- po = map.getPixelOrigin();
- setSize({width: map._size.x, height: map._size.y});
- setOrigin({x: po.x - off.x, y: po.y - off.y});
- setZoom(map._zoom);
-
- Layers.setPosition(-off.x, -off.y);
-
- map.on({
- move: this.onMove,
- moveend: this.onMoveEnd,
- zoomstart: this.onZoomStart,
- zoomend: this.onZoomEnd,
- resize: this.onResize,
- viewreset: this.onViewReset,
- click: this.onClick
- }, this);
-
- if (map.options.zoomAnimation) {
- map.on('zoomanim', this.onZoom, this);
- }
-
- if (map.attributionControl) {
- map.attributionControl.addAttribution(ATTRIBUTION);
- }
-
- Data.update();
- }
-
- onRemove () {
- let map = this.map;
- if (map.attributionControl) {
- map.attributionControl.removeAttribution(ATTRIBUTION);
- }
-
- map.off({
- move: this.onMove,
- moveend: this.onMoveEnd,
- zoomstart: this.onZoomStart,
- zoomend: this.onZoomEnd,
- resize: this.onResize,
- viewreset: this.onViewReset,
- click: this.onClick
- }, this);
-
- if (map.options.zoomAnimation) {
- map.off('zoomanim', this.onZoom, this);
- }
- Layers.remove();
- map = null;
- }
-
- onMove (e) {
- let off = this.getOffset();
- moveCam({x: this.offset.x - off.x, y: this.offset.y - off.y});
- }
-
- onMoveEnd (e) {
- if (this.noMoveEnd) { // moveend is also fired after zoom
- this.noMoveEnd = false;
- return;
- }
-
- let
- map = this.map,
- off = this.getOffset(),
- po = map.getPixelOrigin();
-
- this.offset = off;
- Layers.setPosition(-off.x, -off.y);
- moveCam({x: 0, y: 0});
-
- setSize({width: map._size.x, height: map._size.y}); // in case this is triggered by resize
- setOrigin({x: po.x - off.x, y: po.y - off.y});
- onMoveEnd(e);
- }
-
- onZoomStart (e) {
- onZoomStart(e);
- }
-
- onZoom (e) {
- let center = this.map.latLngToContainerPoint(e.center);
- let scale = Math.pow(2, e.zoom - ZOOM);
-
- let dx = WIDTH / 2 - center.x;
- let dy = HEIGHT / 2 - center.y;
-
- let x = WIDTH / 2;
- let y = HEIGHT / 2;
-
- if (e.zoom > ZOOM) {
- x -= dx * scale;
- y -= dy * scale;
- } else {
- x += dx;
- y += dy;
- }
-
- Layers.container.classList.add('zoom-animation');
- Layers.container.style.transformOrigin = x + 'px ' + y + 'px';
- Layers.container.style.transform = 'translate3d(0, 0, 0) scale(' + scale + ')';
- }
-
- onZoomEnd (e) {
- Layers.clear();
- Layers.container.classList.remove('zoom-animation');
- Layers.container.style.transform = 'translate3d(0, 0, 0) scale(1)';
-
- let
- map = this.map,
- off = this.getOffset(),
- po = map.getPixelOrigin();
-
- setOrigin({x: po.x - off.x, y: po.y - off.y});
- onZoomEnd({zoom: map._zoom});
- this.noMoveEnd = true;
- }
-
- onResize () {
- }
-
- onViewReset () {
- let off = this.getOffset();
-
- this.offset = off;
- Layers.setPosition(-off.x, -off.y);
- moveCam({x: 0, y: 0});
- }
-
- onClick (e) {
- let id = Picking.getIdFromXY(e.containerPoint.x, e.containerPoint.y);
- if (id) {
- onClick({feature: id, lat: e.latlng.lat, lon: e.latlng.lng});
- }
- }
-
- getOffset () {
- return L.DomUtil.getPosition(this.map._mapPane);
- }
-
- //*** COMMON PUBLIC METHODS ***
-
- style (style) {
- style = style || {};
- let color;
- if ((color = style.color || style.wallColor)) {
- WALL_COLOR = Qolor.parse(color);
- WALL_COLOR_STR = '' + WALL_COLOR;
-
- ALT_COLOR = WALL_COLOR.lightness(0.8);
- ALT_COLOR_STR = '' + ALT_COLOR;
-
- ROOF_COLOR = WALL_COLOR.lightness(1.2);
- ROOF_COLOR_STR = '' + ROOF_COLOR;
- }
-
- if (style.roofColor) {
- ROOF_COLOR = Qolor.parse(style.roofColor);
- ROOF_COLOR_STR = '' + ROOF_COLOR;
- }
-
- Layers.render();
-
- return this;
- }
-
- date (date) {
- Shadows.date = date;
- Shadows.render();
- return this;
- }
-
- load (url) {
- Data.load(url);
- return this;
- }
-
- set (data) {
- Data.set(data);
- return this;
- }
-
- each (handler) {
- onEach = function (payload) {
- return handler(payload);
- };
- return this;
- }
-
- click (handler) {
- onClick = function (payload) {
- return handler(payload);
- };
- return this;
- }
-}
-
-OSMBuildings.VERSION = VERSION;
-OSMBuildings.ATTRIBUTION = ATTRIBUTION;
-
- return OSMBuildings;
-}());
diff --git a/inst/htmlwidgets/lfx-building/osm-buildings-bindings.js b/inst/htmlwidgets/lfx-building/osm-buildings-bindings.js
index c530b252..db1f5853 100644
--- a/inst/htmlwidgets/lfx-building/osm-buildings-bindings.js
+++ b/inst/htmlwidgets/lfx-building/osm-buildings-bindings.js
@@ -1,46 +1,76 @@
-LeafletWidget.methods.addBuilding = function(layerId, group, opacity, attribution) {
-// (function(){
+LeafletWidget.methods.addBuilding = function(buildingURL, group, eachFn, clickFn, data) {
+ (function(){
var map = this;
if (map.osmb) {
map.osmb.remove();
delete map.osmb;
}
- console.log(("Schaff ich es hjier"))
-
- map.setView([52.51836, 13.40438], 16, false);
-
- new L.TileLayer('https://tile-a.openstreetmap.fr/hot/{z}/{x}/{y}.png', {
- attribution: '© Data OpenStreetMap',
- maxZoom: 18,
- maxNativeZoom: 20
- }).addTo(map);
var osmb = new OSMBuildings(map)
- .load('https://{s}.data.osmbuildings.org/0.2/59fcc2e8/tile/{z}/{x}/{y}.json');
+ .date(new Date())
+ if (data) {
+ if (data.features && data.features[0].properties.height && data.features[0].geometry.type == "Polygon") {
+ console.log("data is defined"); console.log(data);
+ osmb.set(data);
+ } else {
+ console.error("The data is not a correct GeoJSON of type 'Polygon' or has no property 'height'.");
+ }
+ } else {
+ osmb.load(buildingURL);
+ }
- osmb.date(new Date(2017, 15, 1, 19, 30))
+ if (eachFn && typeof eachFn === 'function') {
+ osmb.each(eachFn);
+ }
+ if (clickFn && typeof clickFn === 'function') {
+ osmb.click(clickFn);
+ }
+ map.layerManager.addLayer(osmb, "building", null, group);
map.osmb = osmb;
-// }).call(this);
+ }).call(this);
};
LeafletWidget.methods.updateBuildingTime = function(date) {
- var map = this;
- if (map.osmb) {
- var now = new Date(date);
- console.log("now"); console.log(now)
- var Y = now.getFullYear(),
- M = now.getMonth(),
- D = now.getDate(),
- h = now.getHours(),
- m = now.getMinutes();
-
- // Update the date on the OSMBuildings instance
- map.osmb.date(new Date(Y, M, D, h, m));
- } else {
- console.error("OSMBuildings instance is not initialized.");
- }
+ (function(){
+ var map = this;
+ if (map.osmb) {
+ var now = new Date(date);
+ var Y = now.getFullYear(),
+ M = now.getMonth(),
+ D = now.getDate(),
+ h = now.getHours(),
+ m = now.getMinutes();
+
+ // Update the date on the OSMBuildings instance
+ map.osmb.date(new Date(Y, M, D, h, m));
+ } else {
+ console.error("OSMBuildings instance is not initialized.");
+ }
+ }).call(this);
+};
+
+LeafletWidget.methods.setBuildingStyle = function(style) {
+ (function(){
+ var map = this;
+ if (map.osmb) {
+ map.osmb.style(style);
+ } else {
+ console.error("OSMBuildings instance is not initialized.");
+ }
+ }).call(this);
+};
+
+LeafletWidget.methods.setBuildingData = function(data) {
+ (function(){
+ var map = this;
+ if (map.osmb) {
+ map.osmb.set(data);
+ } else {
+ console.error("OSMBuildings instance is not initialized.");
+ }
+ }).call(this);
};
diff --git a/inst/htmlwidgets/lfx-building/osm-buildings.css b/inst/htmlwidgets/lfx-building/osm-buildings.css
index c656a9ee..e3b5a5ae 100644
--- a/inst/htmlwidgets/lfx-building/osm-buildings.css
+++ b/inst/htmlwidgets/lfx-building/osm-buildings.css
@@ -1,3 +1,6 @@
+/*
+
+*/
.osmb-container {
transform: translate3d(0, 0, 0);
pointerEvents: none;
@@ -19,3 +22,4 @@
left: 0;
top: 0;
}
+
diff --git a/man/addBuildings.Rd b/man/addBuildings.Rd
index 97a5fcea..56ba1b86 100644
--- a/man/addBuildings.Rd
+++ b/man/addBuildings.Rd
@@ -2,33 +2,56 @@
% Please edit documentation in R/buildings.R
\name{addBuildings}
\alias{addBuildings}
-\title{Add OSM-Buildings}
+\title{Add OSM-Buildings to a Leaflet Map}
\usage{
addBuildings(
map,
- layerId = NULL,
+ buildingURL = "https://{s}.data.osmbuildings.org/0.2/59fcc2e8/tile/{z}/{x}/{y}.json",
group = NULL,
- opacity = 0.5,
- attribution = "© Map tiles Mapbox"
+ eachFn = NULL,
+ clickFn = NULL,
+ data = NULL
)
}
\arguments{
-\item{map}{A map widget object created from \code{\link[leaflet]{leaflet}}}
+\item{map}{A map widget object created from \code{\link[leaflet]{leaflet}}.}
-\item{options}{List of further options. See \code{\link{hexbinOptions}}}
+\item{buildingURL}{The URL template for the building data. Default is the OSM Buildings tile server: \cr
+\code{"https://{s}.data.osmbuildings.org/0.2/59fcc2e8/tile/{z}/{x}/{y}.json"}.}
+
+\item{group}{The name of the group the buildings will be added to.}
+
+\item{eachFn}{A JavaScript function (using \code{\link[htmlwidgets]{JS}}) that will be called for each building feature. Use this to apply custom logic to each feature.}
+
+\item{clickFn}{A JavaScript function (using \code{\link[htmlwidgets]{JS}}) that will be called when a building is clicked. Use this to handle click events on buildings.}
+
+\item{data}{A GeoJSON object containing Polygon features representing the buildings. The properties of these polygons can include attributes like \code{height}, \code{color}, \code{roofColor}, and others as specified in the OSM Buildings documentation.}
}
\description{
-Add OSM-Buildings
+This function adds 2.5D buildings to a Leaflet map using the OSM Buildings plugin.
+}
+\details{
+The `data` parameter allows you to provide custom building data as a GeoJSON object. The following properties can be used within the GeoJSON:
+\itemize{
+ \item \strong{height}
+ \item \strong{minHeight}
+ \item \strong{color/wallColor}
+ \item \strong{material}
+ \item \strong{roofColor}
+ \item \strong{roofMaterial}
+ \item \strong{shape}
+ \item \strong{roofShape}
+ \item \strong{roofHeight}
}
-\note{
-Out of the box a legend image is only available for Pressure,
- Precipitation Classic, Clouds Classic, Rain Classic, Snow, Temperature and
- Wind Speed.
+
+See the OSM Wiki: \href{https://wiki.openstreetmap.org/wiki/Simple_3D_Buildings}
}
\seealso{
-https://osmbuildings.org/documentation/viewer/
+\url{https://github.com/kekscom/osmbuildings/} for more details on the OSM Buildings plugin and available properties.
Other OSM-Buildings Plugin:
+\code{\link{setBuildingData}()},
+\code{\link{setBuildingStyle}()},
\code{\link{updateBuildingTime}()}
}
\concept{OSM-Buildings Plugin}
diff --git a/man/setBuildingData.Rd b/man/setBuildingData.Rd
new file mode 100644
index 00000000..2d720884
--- /dev/null
+++ b/man/setBuildingData.Rd
@@ -0,0 +1,23 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/buildings.R
+\name{setBuildingData}
+\alias{setBuildingData}
+\title{Update the OSM-Buildings Data}
+\usage{
+setBuildingData(map, data)
+}
+\arguments{
+\item{map}{A map widget object created from \code{\link[leaflet]{leaflet}}.}
+
+\item{data}{A GeoJSON object containing Polygon features representing the buildings. The properties of these polygons can include attributes like \code{height}, \code{color}, \code{roofColor}, and others as specified in the OSM Buildings documentation.}
+}
+\description{
+Update the OSM-Buildings Data
+}
+\seealso{
+Other OSM-Buildings Plugin:
+\code{\link{addBuildings}()},
+\code{\link{setBuildingStyle}()},
+\code{\link{updateBuildingTime}()}
+}
+\concept{OSM-Buildings Plugin}
diff --git a/man/setBuildingStyle.Rd b/man/setBuildingStyle.Rd
new file mode 100644
index 00000000..4e10a510
--- /dev/null
+++ b/man/setBuildingStyle.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/buildings.R
+\name{setBuildingStyle}
+\alias{setBuildingStyle}
+\title{Update the OSM-Buildings Style}
+\usage{
+setBuildingStyle(
+ map,
+ style = list(color = "#ffcc00", wallColor = "#ffcc00", roofColor = "orange", shadows =
+ TRUE)
+)
+}
+\arguments{
+\item{map}{A map widget object created from \code{\link[leaflet]{leaflet}}.}
+
+\item{style}{A named list of styles}
+}
+\description{
+Update the OSM-Buildings Style
+}
+\seealso{
+Other OSM-Buildings Plugin:
+\code{\link{addBuildings}()},
+\code{\link{setBuildingData}()},
+\code{\link{updateBuildingTime}()}
+}
+\concept{OSM-Buildings Plugin}
diff --git a/man/updateBuildingTime.Rd b/man/updateBuildingTime.Rd
index 2897a111..f5770b5f 100644
--- a/man/updateBuildingTime.Rd
+++ b/man/updateBuildingTime.Rd
@@ -7,15 +7,17 @@
updateBuildingTime(map, time)
}
\arguments{
-\item{map}{A map widget object created from \code{\link[leaflet]{leaflet}}}
+\item{map}{A map widget object created from \code{\link[leaflet]{leaflet}}.}
+
+\item{time}{a timestamp that can be converted to POSIXct}
}
\description{
Update the Shadows OSM-Buildings with a POSIXct timestamp
}
\seealso{
-https://osmbuildings.org/documentation/viewer/
-
Other OSM-Buildings Plugin:
-\code{\link{addBuildings}()}
+\code{\link{addBuildings}()},
+\code{\link{setBuildingData}()},
+\code{\link{setBuildingStyle}()}
}
\concept{OSM-Buildings Plugin}
diff --git a/tests/testthat.R b/tests/testthat.R
new file mode 100644
index 00000000..fb2bfdf9
--- /dev/null
+++ b/tests/testthat.R
@@ -0,0 +1,12 @@
+# This file is part of the standard setup for testthat.
+# It is recommended that you do not modify it.
+#
+# Where should you do additional test configuration?
+# Learn more about the roles of various files in:
+# * https://r-pkgs.org/testing-design.html#sec-tests-files-overview
+# * https://testthat.r-lib.org/articles/special-files.html
+
+library(testthat)
+library(leaflet.extras2)
+
+test_check("leaflet.extras2")
diff --git a/tests/testthat/test-osmbuildings.R b/tests/testthat/test-osmbuildings.R
new file mode 100644
index 00000000..55569af4
--- /dev/null
+++ b/tests/testthat/test-osmbuildings.R
@@ -0,0 +1,148 @@
+# library(testthat)
+# library(leaflet)
+
+create_test_map <- function() {
+ leaflet() %>% addTiles()
+}
+
+# Test suite for addBuildings
+test_that("addBuildings adds dependencies and invokes method correctly", {
+ map <- create_test_map()
+
+ # Call addBuildings without additional arguments
+ map <- addBuildings(map)
+
+ # Check if the dependencies are added
+ expect_true(any(sapply(map$dependencies, function(dep) dep$name) == "lfx-building"))
+
+ # Check if invokeMethod is called with correct arguments
+ expect_equal(map$x$calls[[2]]$method, "addBuilding")
+ expect_equal(map$x$calls[[2]]$args[[1]], "https://{s}.data.osmbuildings.org/0.2/59fcc2e8/tile/{z}/{x}/{y}.json")
+})
+
+test_that("addBuildings handles custom eachFn, clickFn, and data", {
+ map <- create_test_map()
+
+ # Define custom JavaScript functions using htmlwidgets::JS
+ each_fn <- htmlwidgets::JS("function(e) { console.log('each:', e); }")
+ click_fn <- htmlwidgets::JS("function(e) { console.log('click:', e); }")
+
+ # Define custom GeoJSON data
+ geojson_data <- list(
+ type = "FeatureCollection",
+ features = list(
+ list(
+ type = "Feature",
+ properties = list(height = 100, color = "#ff0000"),
+ geometry = list(
+ type = "Polygon",
+ coordinates = list(
+ list(
+ c(13.39631974697113, 52.52184840804295),
+ c(13.39496523141861, 52.521166220963536),
+ c(13.395150303840637, 52.52101770514734),
+ c(13.396652340888977, 52.52174559105107),
+ c(13.39631974697113, 52.52184840804295)
+ )
+ )
+ )
+ )
+ )
+ )
+
+ map <- addBuildings(map, eachFn = each_fn, clickFn = click_fn, data = geojson_data)
+
+ # Check if the JavaScript functions and data are passed correctly
+ expect_equal(map$x$calls[[2]]$args[[3]], each_fn)
+ expect_equal(map$x$calls[[2]]$args[[4]], click_fn)
+ expect_equal(map$x$calls[[2]]$args[[5]], geojson_data)
+})
+
+# Test suite for updateBuildingTime
+test_that("updateBuildingTime updates the time correctly", {
+ map <- create_test_map()
+ time <- Sys.time()
+
+ map <- addBuildings(map) %>%
+ updateBuildingTime(time) %>%
+ setView(13.40, 52.51836,15)
+
+ # Check if invokeMethod is called with the correct timestamp
+ expect_equal(map$x$calls[[3]]$method, "updateBuildingTime")
+ expect_equal(map$x$calls[[3]]$args[[1]], time)
+})
+
+# Test suite for setBuildingStyle
+test_that("setBuildingStyle applies styles correctly", {
+ map <- create_test_map()
+ style <- list(color = "#0000ff", wallColor = "#0000ff", roofColor = "blue", shadows = FALSE)
+
+ map <- addBuildings(map) %>%
+ setBuildingStyle(style) %>%
+ setView(13.40, 52.51836,15)
+
+ # Check if invokeMethod is called with the correct style
+ expect_equal(map$x$calls[[3]]$method, "setBuildingStyle")
+ expect_equal(map$x$calls[[3]]$args[[1]], style)
+})
+
+test_that("setBuildingStyle uses default styles if not provided", {
+ map <- create_test_map()
+
+ map <- addBuildings(map) %>%
+ setBuildingStyle() %>%
+ setView(13.40, 52.51836,15)
+ # map
+
+ # Check if invokeMethod is called with the default styles
+ default_style <- list(color = "#ffcc00", wallColor = "#ffcc00", roofColor = "orange", shadows = TRUE)
+ expect_equal(map$x$calls[[3]]$"method", "setBuildingStyle")
+ expect_equal(map$x$calls[[3]]$args[[1]], default_style)
+})
+
+# Test suite for setBuildingData
+test_that("setBuildingData updates the building data correctly", {
+ map <- create_test_map()
+
+ # Define custom GeoJSON data
+ geojson_data <- list(
+ type = "FeatureCollection",
+ features = list(
+ list(
+ type = "Feature",
+ properties = list(height = 100, color = "#ff0000"),
+ geometry = list(
+ type = "Polygon",
+ coordinates = list(
+ list(
+ c(13.39631974697113, 52.52184840804295),
+ c(13.39496523141861, 52.521166220963536),
+ c(13.395150303840637, 52.52101770514734),
+ c(13.396652340888977, 52.52174559105107),
+ c(13.39631974697113, 52.52184840804295)
+ )
+ )
+ )
+ )
+ )
+ )
+
+ map <- addBuildings(map,
+ buildingURL = NULL,
+ data = geojson_data) %>%
+ setView(13.40, 52.51836,15)
+ # map
+ # Check if invokeMethod is called with the correct data
+ expect_equal(map$x$calls[[2]]$method, "addBuilding")
+ expect_equal(map$x$calls[[2]]$args[[5]], geojson_data)
+
+ map <- addBuildings(create_test_map(), buildingURL = NULL) %>%
+ setBuildingData(geojson_data) %>%
+ setView(13.40, 52.51836,15)
+ # map
+ # Check if invokeMethod is called with the correct data
+ expect_equal(map$x$calls[[2]]$method, "addBuilding")
+ expect_true(is.null(unlist(map$x$calls[[2]]$args)))
+ expect_equal(map$x$calls[[3]]$method, "setBuildingData")
+ expect_equal(map$x$calls[[3]]$args[[1]], geojson_data)
+})