1if (typeof window.dmpLoaded === "undefined") { 2 window.dmpLoaded = window.dmpLoadStarted = false; 3} 4 5function BingMapv8Wrapper(options, parent) { 6 "use strict"; 7 8 var _self = this, 9 _map = null, 10 _boundaryCount = 1, 11 _boundaryShapeLayerName = null, 12 _boundaryShapeLayerCount = 0, 13 _currentShapeName = null, 14 _mapContainer, 15 _loaded = false, 16 _mapMoveOrigin = null, 17 _mapState, 18 _mapH, 19 _mapW, 20 _layerCollections = {}, 21 _pinIdLayers = {}, 22 _layers = {}, 23 _shapes = {}, 24 _mapMoveCallback = null, 25 _infoBox, 26 _visibleBubblePosition = 0, 27 _infoboxTimer = null, 28 _infoboxLoadCount = 0, 29 _activeDialog = null, 30 _poiEnabled = true, 31 _trafficManager, 32 _tryingToZoomOut = false, 33 _currentDrawType = false, 34 _drawing = false, 35 _drawMouseDownHandler, 36 _drawMouseMoveHandler, 37 _drawMouseUpHandler, 38 _enableMouseMoveTimer = null, 39 _enableMouseMoveHandler = true, 40 _initialShapes = {}, 41 _initialShapesTimer = null, 42 _loadingInitialShapes = true, 43 _addingShapes = {}, 44 _addingShapesTimer = null, 45 _addingShapesTryCount = 0, 46 _bubbleLoading = false, 47 _firstViewChangeEnd = true, 48 _currentView = null, 49 _currentPinId = 0, 50 _dmpInitState = 1, 51 _publicRecords = {}, 52 _parcelPropertyIds = {}, 53 _parcelHover = { 54 lastMouseLocation: { x: 0, y: 0 }, 55 mousePauseThreshold: 250, 56 pauseThread: null, 57 lastLocation: null, 58 lastPropertyId: null 59 }; 60 61 // Zoom Level offsets for the Html based pushpins 62 // Because they are dynamic and we are trying to place them 63 // over the real property, we have to apply diffent anchors 64 // for different zoom levels, otherwise the map places them incorrectly 65 var _zoomLevelOffsets = [ 66 { x: 23, y: 25 }, // zoom level 0 67 { x: 23, y: 25 }, // zoom level 1 68 { x: 23, y: 25 }, // zoom level 2 69 { x: 23, y: 25 }, // zoom level 3 70 { x: 23, y: 25 }, // zoom level 4 71 { x: 23, y: 25 }, // zoom level 5 72 { x: 23, y: 25 }, // zoom level 6 73 { x: 23, y: 25 }, // zoom level 7 74 { x: 23, y: 25 }, // zoom level 8 75 { x: 23, y: 25 }, // zoom level 9 76 { x: 23, y: 25 }, // zoom level 10 77 { x: 23, y: 25 }, // zoom level 11 78 { x: 23, y: 25 }, // zoom level 12 79 { x: 23, y: 27 }, // zoom level 13 80 { x: 23, y: 27 }, // zoom level 14 81 { x: 23, y: 25 }, // zoom level 15 82 { x: 23, y: 25 }, // zoom level 16 83 { x: 25, y: 20 }, // zoom level 17 84 { x: 30, y: 18 }, // zoom level 18 85 { x: 30, y: 15 }, // zoom level 19 86 { x: 30, y: 10 } // zoom level 20 87 ]; 88 var cachedInfo = false; 89 var _maxPricePinsToRender = 200, 90 _enableClusteringForPricePins = false, 91 _totalPropertiesPinCount = 0, 92 _layerCharacteristics = [], 93 _hullPolygon = null; 94 95 var MM = window.Microsoft.Maps; 96 var defaultOptions = { 97 disableStreetside: true, 98 pinMouseOverDelay: 1000, 99 pinMouseOutDelay: 1000, 100 shapeLineColor: new MM.Color(255, 186, 0, 255), 101 shapeFillColor: new MM.Color(25, 131, 185, 187), 102 shapeFillOpacity: 25, 103 shapeLineThickness: 2, 104 strokeDashArray: "5 2", 105 clusterPinColor: "rgb(110, 122, 141)", 106 maxItemsPerBubble: 30, 107 showClusterCount: false, 108 dmpLayers: [ 109 { id: "ParcelLine", label: Utils.translate("Parcel lines", "Parcel lines"), resource: "SS.Base.Parcels/Parcels", sld: "RED.Public.Styles.Parcels/Default.sld.xml", lsld: "RED.Public.SLD/ReInsight_style_labels.sld.xml", zIndex: 3, zoomRange: { min: 16, max: 20 }, isVisible: true, labelIsVisible: false }, 110 { id: "FloodZone", label: Utils.translate("Flood zones", "Flood zones"), resource: "SS.Hazards.DFIRM/DFIRMTiles", sld: "", lsld: "", zIndex: 3, zoomRange: { min: 11, max: 20 }, isVisible: false, labelIsVisible: false } 111 ], 112 mapText: { 113 "menuBar": { 114 "draw": Utils.translate("Draw", "Draw"), 115 "layer": Utils.translate("Layers", "Layers"), 116 "poi": Utils.translate("POIs", "POIs"), 117 "pin": Utils.translate("Pins", "Pins") 118 }, 119 "navBar": { 120 "zoomIn": Utils.translate("Zoom in", ""), 121 "zoomOut": Utils.translate("Zoom out", ""), 122 "rotateRight": Utils.translate("Rotate right", ""), 123 "rotateLeft": Utils.translate("Rotate left", ""), 124 "reset": Utils.translate("Reset orientation", ""), 125 }, 126 "mapTypeName": { 127 "a": Utils.translate("Aerial", "Aerial"), 128 "r": Utils.translate("Road", "Road"), 129 "be": Utils.translate("Birds eye", "Birds eye"), 130 "label": Utils.translate("Labels", "Labels"), 131 }, 132 "mapTypeDescription": { 133 "a": Utils.translate("A detailed look from above", "A detailed look from above"), 134 "r": Utils.translate("A standard road map", "A standard road map"), 135 "be": Utils.translate("A better angle of aerial photography", "A better angle of aerial photography"), 136 "label": Utils.translate("for Aerial and Bird's eye", "for Aerial and Bird's eye"), 137 }, 138 "clusterBubble": { 139 "propertyCount": Utils.translate("View {PropertyCount} properties", "View {PropertyCount} properties"), 140 "singlePropertyCount": Utils.translate("View {PropertyCount} property", "View {PropertyCount} property"), 141 "priceRange": Utils.translate("ranging in price from<br/>{MinPrice} to {MaxPrice}", "ranging in price from<br/>{MinPrice} to {MaxPrice}"), 142 "singlePrice": Utils.translate("priced at {ListPrice}", "priced at {ListPrice}"), 143 "averagePrice": Utils.translate("with an average list price of<br/>{AveragePrice}", "with an average list price of<br/>{AveragePrice}") 144 }, 145 "dialogs": { 146 "draw": { 147 "drawTab": Utils.translate("Draw Shape", "Draw Shape"), 148 "radiusTab": Utils.translate("Draw Radius", "Draw Radius"), 149 "rectangleTab": Utils.translate("Draw Rectangle", "Draw Rectangle"), 150 "drawHeader": Utils.translate("Click Draw to begin drawing your shape", "Click Draw to begin drawing your shape. Hold down on the mouse button or c
150ontinuously press down with your finger to draw out your shape."), 151 "radiusHeader": Utils.translate("Click Draw to begin drawing out your radius", "Click Draw to begin drawing out your radius after selecting your starting location. Click and hold on the mouse and move it to expand the radius."), 152 "radiusFree": Utils.translate("Draw out a radius manually", "Draw out a radius manually"), 153 "radiusMyLocation": Utils.translate("Enter radius from my current location in miles:", "Enter radius from my current location in miles"), 154 "radiusMyLocationNote": Utils.translate("Note: You must allow the map the use of your location.", "Note: You must allow the map the use of your location."), 155 "radiusCenter": Utils.translate("Enter radius from the map center in miles:", "Enter radius from the map center in miles:"), 156 "rectangleHeader": Utils.translate("Click Draw to begin drawing out your rectangle or square on the map.", "Click Draw to begin drawing out your rectangle or square on the map."), 157 "footer": Utils.translate("Name your shape (Optional):", "Name your shape (Optional):"), 158 "buttonText": Utils.translate("Draw", "Draw"), 159 }, 160 "layers": { 161 "headerMapView": Utils.translate("Select your map view:", "Select your map view"), 162 "header": Utils.translate("Select layer(s) to be added to the map:", "Select layer(s) to be added to the map:"), 163 "note": Utils.translate("Note: layers will only appear at a certain zoom level.", "Note: layers will only appear at a certain zoom level."), 164 "boundaryHeader": Utils.translate("Show boundary", "Show boundary"), 165 "labelHeader": Utils.translate("Show label", "Show label"), 166 "disclaimer": "", 167 "clear": Utils.translate("Clear all", "Clear all"), 168 }, 169 "poi": { 170 "header": Utils.translate("Select points of interest to display on the map:", "Select points of interest to display on the map:"), 171 "note": Utils.translate("Note: POIs will only appear at a certain zoom level.", "Note: POIs will only appear at a certain zoom level."), 172 "clear": Utils.translate("Clear all", "Clear all"), 173 }, 174 "pins": { 175 "header": Utils.translate("Select pin(s) to display on the map:", "Select pin(s) to display on the map:"), 176 "none": Utils.translate("No pins have been added to the map.", "No pins have been added to the map."), 177 } 178 }, 179 "overlayText": { 180 "shapeRemover": Utils.translate("Remove", "Remove"), 181 } 182 }, 183 184 // default values set in setDefaultPinTypeLayerNames() below 185 pinTypeLayerNames: {}, 186 minZoom: 1 187 }; 188 var LayerType = { 189 "Pin": 1, 190 "Poi": 2, 191 "Shape": 3, 192 "Cluster": 4 193 }; 194 195 // define the type of BingMap v8 module that's used for the layer 196 var LayerModuleType = { 197 HtmlPushpinModule: "HtmlPushpinModule", 198 ClusteredModule: "ClusteredModule", 199 StandardModule: "StandardModule", 200 }; 201 202 var customMapStyles = { 203 "Faded": { 204 "version": "1.0", 205 "elements": { 206 "vegetation": { 207 "fillColor": "#c5dea2" 208 }, 209 "naturalPoint": { 210 "visible": false, 211 "labelVisible": false 212 }, 213 "transportation": { 214 "labelOutlineColor": "#ffffff", 215 "fillColor": "#ffffff", 216 "strokeColor": "#d7d6d5" 217 }, 218 "water": { 219 "fillColor": "#b1bdd6", 220 "labelColor": "#ffffff", 221 "labelOutlineColor": "#9aa9ca" 222 }, 223 "structure": { 224 "fillColor": "#d7d6d5" 225 }, 226 "indigenousPeoplesReserve": { 227 "visible": false 228 }, 229 "military": { 230 "visible": false 231 } 232 }, 233 "settings": { 234 "landColor": "#e7e6e5", 235 "shadedReliefVisible": false 236 } 237 } 238 }; 239 240 // PinType enum is defined in /include/js/mapsearch/bingmap.js 241 (function setDefaultPinTypeLayerNames() { 242 function set(label, pinTypeKeys) { 243 if (typeof pinTypeKeys === "string") 244 pinTypeKeys = [pinTypeKeys]; 245 246 pinTypeKeys.forEach(function (key) { 247 defaultOptions.pinTypeLayerNames[BingMap.PinType[key]] = label; 248 }); 249 } 250 set(Utils.translate("Property"), ["ListingActive", "ListingOther", "ListingMultiple"]);
251 set(Utils.translate("Company Office"), "Office"); 252 set(Utils.translate("New Property"), ["ListingNew", "ListingNewNoDoor"]); 253 set(Utils.translate("Open House Available"), ["ListingOpenHouse", "ListingOpenHouseMultiple"]); 254 set(Utils.translate("Sale Pending"), ["ListingPending", "ListingPendingMultiple", "ListingPendingNoDoor"]); 255 set(Utils.translate("Sold Property"), ["ListingSold", "ListingSoldMultiple"]); 256 set(Utils.translate("Commercial Property"), ["ListingCommercialActive", "ListingCommercialMultiple", "ListingCommercialNew", "ListingCommercialOther"]); 257 set(Utils.translate("Commercial Sale Pending"), ["ListingCommercialPending", "ListingCommercialPendingMultiple"]); 258 set(Utils.translate("Sold Commercial"), ["ListingCommercialSold", "ListingCommercialSoldMultiple"]); 259 set(Utils.translate("Valuation", "Valuation"), "Valuation"); 260 set(Utils.translate("Coming Soon"), "ListingComingSoon"); 261 set(Utils.translate("Auction"), "ListingAuction"); 262 set(Utils.translate("Rent"), "ListingRent"); 263 })(); 264 265 // Collection functions 266 267 function translatePinTypeToLayerName(pinType, fallbackLayerName) { 268 return typeof options.pinTypeLayerNames[pinType] !== "undefined" ? 269 options.pinTypeLayerNames[pinType] : (fallbackLayerName || "Properties"); 270 } 271 272 function getLayerCollection(layerName) { 273 if (typeof _layerCollections[layerName] === "undefined") { 274 _layerCollections[layerName] = { 275 name: layerName, 276 pins: [], 277 pinIds: {}, 278 layers: {}, 279 cluster: null 280 }; 281 } 282 283 return _layerCollections[layerName]; 284 } 285 286 function clearCollectionLayer(collection) { 287 collection.pins = []; 288 collection.pinIds = {}; 289
290 Object.keys(collection.layers).forEach(function (layerName) { 291 clearLayer(collection.layers[layerName]); 292 }); 293 } 294 295 // Layer functions 296 function getLayer(layerName, cluster, layerType, isVisible) { 297 var layer = _layers[layerName]; 298 if (typeof (layer) === "undefined") { 299 if (typeof cluster === "undefined" || cluster === null) { 300 cluster = true; 301 } 302 if (typeof isVisible === "undefined" || isVisible === null) { 303 isVisible = true; 304 } 305 if (typeof layerType === "undefined" || layerType === null) { 306 layerType = LayerType.Pin; 307 } 308 309 var mapLayer = createMapLayer(layerName); 310 311 layer = { 312 name: layerName, 313 pins: [], 314 pinIds: {}, 315 layer: mapLayer.mapLayerObject, 316 cluster: cluster, 317 isVisible: isVisible, 318 layerType: layerType, 319 layerModule: mapLayer.layerModule 320 }; 321 322 if (layer.layerModule === LayerModuleType.StandardModule) { 323 if (layerType === LayerType.Shape) { 324 layer.layer.setZIndex(10002); 325 } else { 326 layer.layer.setZIndex(10003); 327 } 328 } 329 _layers[layerName] = layer; 330 } 331 return layer; 332 } 333 334 function clearLayer(layer) { 335 layer.pins = []; 336 var msLayer = layer.layer; 337 if (typeof msLayer !== "undefined" && msLayer != null) { 338 _map.layers.remove(msLayer); 339 } 340 layer.layer = createMapLayer(layer.name).mapLayerObject; 341 } 342 343 function createMapLayer(layerName) { 344 var layerModule, mapLayerObject; 345 346 if (isSupportedHtmlPushpin(layerName)) { 347 if (_enableClusteringForPricePins) { 348 mapLayerObject = new MM.ClusterLayer(); 349 layerModule = LayerModuleType.ClusteredModule; 350 } else { 351 mapLayerObject = new HtmlPushpinLayer(); 352 layerModule = LayerModuleType.HtmlPushpinModule; 353 } 354 } else { 355 mapLayerObject = new MM.Layer(); 356 layerModule = LayerModuleType.StandardModule; 357 } 358 359 return { mapLayerObject: mapLayerObject, layerModule: layerModule }; 360 } 361 362 //pass an address and return a promise 363 function getLatLongFromAddress(address) { 364 var deferred = $.Deferred(); 365 $.getJSON('https://dev.virtualearth.net/REST/v1/Locations?query=' + address + '&key=' + Utils.BingMapKey + '&jsonp=?').done(function (data) { 366 if (data && data.resourceSets.length && data.resourceSets[0].resources.length && data.resourceSets[0].resources[0].geocodePoints.length) { 367 var lat = data.resourceSets[0].resources[0].geocodePoints[0].coordinates[0]; 368 var long = data.resourceSets[0].resources[0].geocodePoints[0].coordinates[1]; 369 if (lat && long) { 370 deferred.resolve([lat, long]); 371 } 372 } 373 }); 374 return deferred; 375 } 376 377 this.directionsManager; 378 379 this.getDirections = function (location) { 380 MM.loadModule('Microsoft.Maps.Directions', (function () { 381 this.directionsManager = this.directionsManager || new MM.Directions.DirectionsManager(_map); 382 // Set the element in which the itinerary will be rendered 383 this.directionsManager.setRenderOptions({ itineraryContainer: document.getElementById('dd-route') }); 384 //this.directionsManager.showInputPanel('directionsInputContainer'); 385 //clear any existing directions 386 this.directionsManager.clearAll(); 387 // Set Route Mode to driving 388 this.directionsManager.setRequestOptions({ routeMode: MM.Directions.RouteMode.driving }); 389 $.when(getLatLongFromAddress(location[0]), getLatLongFromAddress(location[1])).done((function (start, finish) { 390 var waypoint1 = new MM.Directions.Waypoint({ address: location[0], location: new MM.Location(start[0], start[1]) }); 391 var waypoint2 = new MM.Directions.Waypoint({ address: location[1], location: new MM.Location(finish[0], finish[1]) }); 392 this.directionsManager.addWaypoint(waypoint1); 393 this.directionsManager.addWaypoint(waypoint2); 394 this.directionsManager.calculateDirections(); 395 }).bind(this)); 396 }).bind(this)); 397 }; 398 399 this.setupDrivingDirections = function (fromLocation, toLocation, routeID, directionsID) { 400 MM.loadModule('Microsoft.Maps.Directions', (function () { 401 this.directionsManager = this.directionsManager || new MM.Directions.DirectionsManager(_map); 402 // Set the element in which the itinerary will be rendered 403 this.directionsManager.setRenderOptions({ itineraryContainer: document.getElementById(routeID) }); 404 this.directionsManager.showInputPanel(directionsID); 405 406 // Set Route Mode to driving 407 this.directionsManager.setRequestOptions({ routeMode: MM.Directions.RouteMode.driving });
408 409 /*$.when(getLatLongFromAddress(fromLocation), getLatLongFromAddress(toLocation)).done((function (start, finish) { 410 var waypoint1 = new MM.Directions.Waypoint({ address: fromLocation, location: new MM.Location(start[0], start[1]) }); 411 var waypoint2 = new MM.Directions.Waypoint({ address: toLocation, location: new MM.Location(finish[0], finish[1]) }); 412 this.directionsManager.addWaypoint(waypoint1); 413 this.directionsManager.addWaypoint(waypoint2); 414 }).bind(this));*/ 415 }).bind(this)); 416 }; 417 418 function removeCustomShape(e) { 419 var $div = $(e.target); 420 $div.hide(); 421 var shapeName = $div.attr("data-shapename"); 422 _self.clearPolygon(shapeName); 423 $(document).trigger("removecriteria", [ 424 { 425 name: "Criteria/Location", 426 val: shapeName 427 } 428 ]); 429 _mapMoveOrigin = "RemoveCustomShape"; 430 _self.onChangeView(); 431 } 432 433 function hideShapeRemover() { 434 var $div = $("div.map-shape-remover", _mapContainer); 435 if ($div.length && $div.is(":visible")) { 436 var shapeName = $div.attr("data-shapename"); 437 var shape = _shapes[shapeName]; 438 if (shape && shape.length) { 439 var shapeOpts = { 440 strokeColor: options.shapeLineColor, 441 fillColor: options.shapeFillColor, 442 strokeThickness: options.shapeLineThickness 443 }; 444 shape[0].setOptions(shapeOpts); 445 } 446 $div.hide(); 447 } 448 } 449 450 function showShapeRemover(e) { 451 var shape = e.target; 452 var locs = shape.getLocations(); 453 var minLat = 90, maxLat = 0, minLon = 0, maxLon = -180; 454 var midLat, midLon; 455 var topRightLoc, offset; 456 for (var i = 0; i < locs.length; i++) { 457 var loc = locs[i]; 458 minLat = Math.min(minLat, loc.latitude); 459 maxLat = Math.max(maxLat, loc.latitude); 460 minLon = Math.min(minLon, loc.longitude); 461 maxLon = Math.max(maxLon, loc.longitude); 462 } 463 if (shape.center) { // Radius 464 midLat = (maxLat + (shape.center.latitude + maxLat) / 2) / 2; 465 midLon = (maxLon + (shape.center.longitude + maxLon) / 2) / 2; 466 topRightLoc = new MM.Location(midLat, midLon); 467 offset = _map.tryLocationToPixel(topRightLoc, MM.PixelReference.control); 468 } else if (shape.start) { // Rectangle 469 topRightLoc = new MM.Location(maxLat, maxLon); 470 offset = _map.tryLocationToPixel(topRightLoc, MM.PixelReference.control); 471 } else { 472 midLat = (maxLat + ((maxLat + minLat) / 2)) / 2; 473 midLon = (maxLon + ((maxLon + minLon) / 2)) / 2; 474 topRightLoc = locs.reduce(function (x, y) { 475 var xLatDist = x.latitude - midLat; 476 var xLonDist = x.longitude - midLon; 477 var yLatDist = y.latitude - midLat; 478 var yLonDist = y.longitude - midLon; 479 return (xLatDist + xLonDist > yLatDist + yLonDist) ? x : y; 480 }); 481 offset = _map.tryLocationToPixel(topRightLoc, MM.PixelReference.control); 482 } 483 // Redraw the shape with a different color 484 var shapeOpts = { 485 strokeColor: new MM.Color(255, 235, 40, 40), 486 fillColor: new MM.Color(25, 235, 40, 40), 487 strokeThickness: options.shapeLineThickness 488 }; 489 shape.setOptions(shapeOpts); 490 491 $("div.map-shape-remover", _mapContainer)
492 .attr("data-shapename", shape.name) 493 .css({ top: offset.y, left: offset.x }) 494 .show(); 495 } 496 497 function addShapesToLayer(shapes, layer, cb) { 498 // Clear out any existing shapes and layers 499 clearLayer(layer); 500 shapes = typeof shapes === "undefined" || shapes === null 501 ? [] 502 : shapes; 503 504 // Add the shapes to the layer 505 layer.layer.add(shapes); 506 // If this is a custom shape, add the click event to remove 507 if (shapes && shapes.length && shapes[0].isCustom) { 508 MM.Events.addHandler(layer.layer, "click", showShapeRemover); 509 } 510 511 _map.layers.insert(layer.layer); 512 513 _shapes[layer.name] = shapes; 514 // Call the callback 515 if (typeof cb === "function") { 516 cb(shapes); 517 } 518 } 519 520 // Bubble/infobox functions 521 522 function setInfoBoxOffset(pin, afterOffset) { 523 // Try to get the body from the popup. Retry on delay 524 if (_infoboxTimer !== null) { 525 clearTimeout(_infoboxTimer); 526 _infoboxTimer = null; 527 } 528 var bodyHtml = $(".pushpinPopupBody", _mapContainer).html(); 529 if (!bodyHtml || !bodyHtml.length) { 530 // Only try 40 times (8 seconds), in case there's something wrong 531 if (_infoboxLoadCount++ < 40) { 532 _infoboxTimer = setTimeout(function () { 533 setInfoBoxOffset(pin, afterOffset); 534 }, 200); 535 } 536 return; 537 } 538 // Reset the retry count 539 _infoboxLoadCount = 0; 540 // Get the current dimensions 541 _mapW = $(_mapContainer).outerWidth(); 542 _mapH = $(_mapContainer).outerHeight(); 543 544 var html, top, left; 545 // Get the initial location of the infobox as set by Bing 546 var loc = _map.tryLocationToPixel(_infoBox.getLocation(), MM.PixelReference.control); 547 // Get the current height and width of the body, this will be used to determine the offset 548 var boxH = $(".pushpinPopupBody").height(); 549 var boxW = $(".pushpinPopupBody").width(); 550 551 //Incase the popup element not loaded properly 552 if (pin.iconTemplates !== undefined && pin.iconTemplates.length > 1) { 553 if (boxH < 260) { 554 boxH = 260; 555 } 556 } else { 557 if (boxH < 210) { 558 boxH = 210; 559 } 560 } 561 562 var pinType = getPinType(pin); 563 var arrowTop, arrowDir; 564 565 if (pinType === BingMap.PinType.Valuation) { 566 top = 30; 567 left = -25; 568 var propertyId = pin.id.replace('val_1_', ''); 569 var forceTop = $(".pushpinPopupBody").find(".bubble-loading").length ? -40 : -25; 570 html = "<div class='pushpinPopup valuationPopup' style='top: " + 571 forceTop + 572 "px;' data-property-id='" + 573 propertyId + 574 "'>" + 575 "<div class='pushpinPopupArrowBottomSmall'></div>" + 576 "<div class='pushpinPopupBody'>" + 577 bodyHtml + 578 "</div>" + 579 "</div>"; 580 } else if (pin.tag === 'ClusterPin') { 581 top = 30; 582 var pinRadius = (pin.image.width / 2) || 18; 583 if (loc.x > _mapW / 2) { // Right half of map 584 // The box will be on the left side of the pin, so offset the arrow likewise 585 left = -1 * (pinRadius + 25 + boxW); 586 arrowDir = "Right"; 587 } else { // Left half 588 // On the right side of the pin, offset 45 px to the left. 589 // Limitation here, depends on the arrow always being styled to be the same size 590 left = pinRadius + 15; 591 arrowDir = "Left"; 592 } 593 // Override the min-height if this is a cluster pin 594 html = "<div class='pushpinPopup' style='min-height: 45px !important; top: 0 !important'>" + 595 "<div class='pushpinPopupArrow" + arrowDir + "' style='margin-top: 3px !important'></div>" + 596 "<div class='pushpinPopupBody' style='min-height: 45px !important;'>" + bodyHtml + "</div>" + 597 "</div>"; 598 599 } else { 600 if (loc.y > _mapH / 2) { // Bottom half of map 601 // Get the midway point between the pin and the bottom 602 // of the map, and position the top the height of the 603 // infobox above it 604 top = boxH - ((_mapH - loc.y) / 2); 605 } else { // Top half 606 // Get the midway point between the pin and the top of the map, and 607 // make that the top 608 top = (loc.y / 2); 609 } 610 // If the top is too close to the pin location, the 611 // arrow won't look good, so adjust 612 if (top < 60) top = 60; 613 // If the top is beyond the top of the map, move it down to the top of the map 614 if (top > loc.y) { 615 top = loc.y; 616 } 617 // if the bottom of the box is too high, bring it down next to 618 // the pin so the arrow looks good 619 if (boxH - top < 40) { 620 top = boxH / 2; 621 } 622 623 var scroll = 624 window.scrollY //Modern Way (Chrome, Firefox) 625 || window.pageYOffset 626 || document.documentElement.scrollTop; 627 628 629 var verticalHidden = $(_mapContainer).offset().top + _mapH - $(window).height() - scroll; 630 verticalHidden = verticalHidden > 0 ? verticalHidden : 0; 631 632 // If the bottom of the infobox is lower than the bottom of 633 // the map, move it up a little 634 // (If bubbles are taller than the map, not currently supported, 635 // but maybe we can add something to put a scrollbar in the pushPinBody) 636 if (top !== loc.y && loc.y - top + boxH + verticalHidden > _mapH) { 637 top = loc.y - (_mapH - boxH - verticalHidden); 638 arrowTop = top - 35; 639 } else if (loc.y < 83) {
640 // Boundary case for adjusting display when pin too close to top edge 641 top = -25 + loc.y; 642 arrowTop = top - 35; 643 } else { 644 // The default arrow offset. Honestly don't know how this 35 works, but this 645 // always puts it on the pin no matter where the top is 646 arrowTop = top - 35; 647 } 648 649 var leftOffset = 40; 650 if (loc.x > _mapW / 2) { // Right half of map 651 // The box will be on the left side of the pin, so offset the arrow likewise 652 left = -1 * (leftOffset + boxW); 653 arrowDir = "Right"; 654 } else { // Left half 655 // On the right side of the pin, offset 20 px to the left. 656 // Limitation here, depends on the arrow always being styled to be the same size 657 left = leftOffset; 658 arrowDir = "Left"; 659 } 660 661 html = "<div class='pushpinPopup'>" + 662 "<div class='pushpinPopupArrow" + arrowDir + "' style='top: " + arrowTop + "px;'></div>" + 663 "<div class='pushpinPopupBody'>" + bodyHtml + "</div>" + 664 "</div>"; 665 } 666 667 // Update the infobox with the new offset. We have to reset the html in the options 668 // or it goes back to the original html 669 var offset = new MM.Point(left, top); 670 _infoBox.setOptions({ 671 visible: true, 672 showCloseButton: false, 673 offset: offset, 674 htmlContent: html, 675 zIndex: 100 676 }); 677 // Sometimes after the image loads the offset is wrong 678 if (!afterOffset) { 679 $("#listingPhoto", $(".pushpinPopup")).one("load", function () { 680 var newBoxH = $(".pushpinPopupBody").height(); 681 if (boxH !== newBoxH) { 682 setInfoBoxOffset(pin, true); 683 } 684 }); 685 } 686 // If the pin is near the top of the map, hide the menu bar so the infobox 687 // won't be hidden 688 if (cachedInfo) { 689 setTimeout(function () { 690 var mapTop = $(_mapContainer).offset().top; 691 var popupTop = $(".pushpinPopup").offset().top; 692 if (popupTop - mapTop < 80) { 693 $("div.map-menubar", _mapContainer).slideUp(250, function () { $("div.map-menubar", _mapContainer).css("overflow", "visible") }); 694 } 695 }, 1000); 696 697 } 698 699 if (pinType === BingMap.PinType.Valuation) { 700 // If needed, hide the zoom buttons. Pick the correct #MapNavBar (i.e., not the one from Public Records overlay). 701 var $zoomButtons = $("#MapNavBar", "#mapsearch-mainmap"); 702 if (isOverlap($zoomButtons.get(0), $(".pushpinPopup").get(0))) { 703 $zoomButtons.hide(); 704 } 705 } 706 } 707 708 function isOverlap(elem1, elem2) { 709 if (!elem1 || !elem2) return false; 710 711 var rect1 = elem1.getBoundingClientRect(); 712 var rect2 = elem2.getBoundingClientRect(); 713 714 return !(rect1.right < rect2.left || 715 rect1.left > rect2.right || 716 rect1.bottom < rect2.top || 717 rect1.top > rect2.bottom); 718 } 719 720 function showProperBubbleDiv(pin) { 721 // The _visibleBubblePosition is set when mousing over a listing in 722 // the grid below the map. It tells us which listing to display 723 // in a multi-listing bubble. 724 if (_visibleBubblePosition > 0 && !_bubbleLoading) { 725 $("#pin_" + _visibleBubblePosition, _mapContainer).show(); 726 $("#pin_0", _mapContainer).hide(); 727 _visibleBubblePosition = 0; 728 } 729 setInfoBoxOffset(pin); 730 } 731 732 function getToggleBubbleHtml(count, curPosition, displayName) { 733 var html = new StringBuilder(); 734 735 var displayNames; 736 if (displayName.substring(displayName.length - 1, 1) === "y") { 737 displayNames = displayName.substring(0, displayName.length - 2) + "ies"; 738 } else { 739 displayNames = displayName + "s"; 740 } 741 742 html.append("<tr><td valign='top'><table cellspacing='0' cellpadding='0' border='0' class='txt_blk_graybg' width='100%'>"); 743 html.append("<tr><td colspan='3'>Multiple " + displayNames + " found here</td></tr>"); 744 html.append("<tr><td align='left' width='30%'>"); 745 if (curPosition > 0) { 746 html.append("<a href='javascript:;' onclick='javascript:Search.togglePinBubble(" + (curPosition - 1) + "," + curPosition + " )'><" + Utils.translate("Prev", "Prev") + "</a>"); 747 } 748 html.append("</td><td align='center' width='40%'>"); 749 html.append("<b>" + (curPosition + 1) + " of " + count + "</b>"); 750 html.append("</td><td align='right' width='30%'>"); 751 if (curPosition < count - 1) { 752 html.append("<a href='javascript:;' onclick='javascript:Search.togglePinBubble(" + (curPosition + 1) + "," + curPosition + ")'> >" + Utils.translate("Next", "Next") + "</a>"); 753 } 754 html.append("</td></tr></table></td></tr>"); 755 756 return html.toString(); 757 } 758 759 function getToggleBubbleHtml2(count, curPosition, displayName) { 760 var html = ""; 761 var cleanedDisplayName; 762 if (displayName.substring(displayName.length - 1, 1) === "y") { 763 cleanedDisplayName = displayName.substring(0, displayName.length - 2) + "ies"; 764 cleanedDisplayName = Utils.translate(cleanedDisplayName, cleanedDisplayName); 765 } else { 766 cleanedDisplayName = displayName + "s"; 767 cleanedDisplayName = Utils.translate(cleanedDisplayName, cleanedDisplayName); 768 } 769 770 html += "<tr><td valign='top'><table cellspacing='0' cellpadding='0' border='0' width='100%'>"; 771 html += "<tr><td class='r-bubble-navigation-left'>"; 772 if (curPosition > 0) { 773 html += "<a href='javascript:;' onclick='javascript:Search.togglePinBubble(" + (curPosition - 1) + "," + curPosition + " )'><i class='fa fa-angle-left'></i> " + Utils.translate('Prev', 'Prev') + "</a>"; 774 } 775 html += "</td><td class='r-bubble-navigation-center'>"; 776 html += "<b>" + Utils.translate('{[0]} of {[1]}', '{[0]} of {[1]}').replace('{[0]}', curPosition + 1).replace('{[1]}', count) + " " + cleanedDisplayName + "</b>"; 777 html += "</td><td class='r-bubble-navigation-right'>"; 778 if (curPosition < count - 1) { 779 html += "<a href='javascript:;' onclick='javascript:Search.togglePinBubble(" + (curPosition + 1) + "," + curPosition + ")'>" + Utils.translate('Next', 'Next') + " <i class='fa fa-angle-right'></i></a>"; 780 } 781 html += "</td></tr></table></td></tr>"; 782 783 return html; 784 } 785 786 function getPinType(pin) { 787 if (!pin || !pin.id) return 0; 788 789 var joinedPinIds = pin.id; 790 var firstPinId = joinedPinIds.split('-').shift(); 791 var pinLayer = _pinIdLayers[firstPinId]; 792 var pinInfo = pinLayer.pinIds[joinedPinIds]; 793 return pinInfo.pinType; 794 } 795 796 function getBubbleHtmlAsync(pin) { 797 if (getPinType(pin) === BingMap.PinType.Valuation) { 798 getBubbleHtmlAsyncValuation(pin); 799 } else { 800 getBubbleHtmlAsyncListing(pin); 801 } 802 } 803
804 function getPinClusterLabel(pinsInCluster) { 805 return pinsInCluster != null ? pinsInCluster.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : null; 806 } 807 808 function toMapFriendlyPrice(price) { 809 // Ensure number has max 3 significant digits (no rounding up can happen) 810 var i = Math.pow(10, Math.max(0, Math.log10(price) - 2)); 811 price = price / i * i; 812 813 if (price >= 1000000000) { 814 return parseFloat((price / 1000000000.0).toFixed(2)) + "B"; 815 } 816 817 if (price >= 1000000) { 818 return parseFloat((price / 1000000.0).toFixed(2)) + "M"; 819 } 820 821 if (price >= 1000) { 822 return Math.floor(price / 1000.0) + "K"; 823 } 824 825 return Math.floor(price); 826 } 827 828 function getBubbleHtmlCluster(pin) { 829 var propertyCountText = pin.pinsInCluster === 1 830 ? options.mapText.clusterBubble.singlePropertyCount 831 : options.mapText.clusterBubble.propertyCount; 832 var bubbleHtml = '<div id="clusterBubble"><div id="countBubble" style="text-align:center;">' + 833 propertyCountText.replace('{PropertyCount}', '<strong>' + getPinClusterLabel(pin.pinsInCluster) + '</strong>') + 834 '</div>'; 835 var minPrice = toMapFriendlyPrice(pin.minPrice); 836 var maxPrice = toMapFriendlyPrice(pin.maxPrice); 837 if (minPrice !== maxPrice) { 838 if (options.mapText.clusterBubble.priceRange.length > 0) { 839 bubbleHtml += '<div id="priceBubble" style="text-align:center">' + 840 options.mapText.clusterBubble.priceRange 841 .replace('{MinPrice}', '<strong>$' + minPrice + '</strong>') 842 .replace('{MaxPrice}', '<strong>$' + maxPrice + '</strong>') + 843 '</div>'; 844 } 845 if (pin.avgPrice > 0 && options.mapText.clusterBubble.averagePrice.length > 0) { 846 bubbleHtml += '<div id="avgPriceBubble" style="text-align:center">' + 847 options.mapText.clusterBubble.averagePrice 848 .replace('{AveragePrice}', '<strong>$' + toMapFriendlyPrice(pin.avgPrice) + '</strong>') + 849 '</div>'; 850 } 851 } else if (pin.maxPrice > 0 && options.mapText.clusterBubble.singlePrice.length > 0) { 852 bubbleHtml += '<div id="priceBubble" style="text-align:center">' + 853 options.mapText.clusterBubble.singlePrice 854 .replace('{ListPrice}', '<strong>$' + maxPrice + '</strong>') + 855 '</div>'; 856 } 857 bubbleHtml += '</div>'; 858 return bubbleHtml; 859 } 860 861 function getBubbleHtmlAsyncListing(pin) { 862 _bubbleLoading = true; 863 864 var joinedPinIds = pin.id; 865 866 var firstPinId = joinedPinIds.split("-").shift(); 867 var pinLayer = _pinIdLayers[firstPinId]; 868 var pinInfo = pinLayer.pinIds[joinedPinIds]; 869 var pinType = pinInfo.pinType; 870 871 // The ListingIDs are embedded in the PinIDs 872 var url = Utils.AppPath + "/Include/AJAX/MapSearch/GetListingBubble.aspx?searchoverride=" + options.cookieGuid; 873 874 if (pinType === 3) { 875 url += "&" + 876 "Office" + 877 "ID=" + 878 joinedPinIds.replace(/3_/g, ""); 879 } else { 880 url += "&ListingID=" + joinedPinIds.replace(/1_/g, ""); 881 } 882 883 // Special layout overrides for preview functionality 884 var qs = new Querystring(); 885 if (qs.contains("LayoutID")) { 886 url += "&LayoutID=" + qs.get("LayoutID") + "&LayoutVersion=" + qs.get("LayoutVersion"); 887 } 888 if (qs.contains("embed")) { 889 url += "&embed=" + qs.get("embed"); 890 } 891 if (qs.contains("collab") || qs.contains("mozaic")) { 892 url += "&mozaic=1"; 893 } 894 895 $.ajax({
896 mode: "abort", 897 port: "bubble", 898 type: "GET", 899 url: url, 900 cache: false, 901 success: function (resp) { 902 // Do we have a multi-pin here? 903 if (resp.indexOf("~~") > 0) { 904 var desc = new StringBuilder(); 905 var arrDesc = resp.split("~~"); 906 for (var i = 0, len = arrDesc.length; i < len; i++) { 907 desc.append("<table width='100%' valign='top' cellspacing='1' cellpadding='1' class='txt_blk' "); 908 if (i > 0) { 909 desc.append("style='display:none;'"); 910 } 911 desc.append(" id='pin_" + i + "''>"); 912 if (options != null && options.toggleBubbleVersion === 2) { 913 desc.append(getToggleBubbleHtml2(len, i, BingMap.PinInfo[pinType].DisplayName)); 914 } else { 915 desc.append(getToggleBubbleHtml(len, i, BingMap.PinInfo[pinType].DisplayName)); 916 } 917 desc.append("<tr><td>" + arrDesc[i] + "</td></tr>"); 918 desc.append("</table>"); 919 } 920 resp = desc.toString(); 921 } 922 923 // This fills the bubble immediately 924 $("div.pushpinPopupBody", _mapContainer).html(resp); 925 // After the bubble is shown, set it to the right div in a multi-listing bubble 926 _bubbleLoading = false; 927 showProperBubbleDiv(pin); 928 // This fills it for the next open 929 pin.pinDesc = resp; 930 // Raise an event to say the bubble is filled and displaying 931 $(document).trigger("mapbubbledisplay", [joinedPinIds]); 932 } 933 }); 934 } 935 936 function getBubbleHtmlAsyncValuation(pin) { 937 _bubbleLoading = true; 938 939 // The PropertyId is embedded in the Pin ID 940 var propertyId = pin.id.replace('val_1_', ''); 941 var url = Utils.AppPath + "/Include/AJAX/MapSearch/GetValuationBubble.aspx?propertyId=" + propertyId; 942 $.ajax({
943 mode: "abort", 944 port: "bubble", 945 type: "GET", 946 url: url, 947 cache: false, 948 success: function (resp) { 949 // This fills the bubble immediately 950 if (resp) { 951 $("div.pushpinPopupBody", _mapContainer).html(resp); 952 _bubbleLoading = false; 953 setInfoBoxOffset(pin); 954 // This fills it for the next open 955 pin.pinDesc = resp; 956 // Raise an event to say the bubble is filled and displaying 957 $(document).trigger("mapbubbledisplay"); 958 } else { 959 _self.hideListingBubble(); 960 } 961 } 962 }); 963 } 964 965 function getBubbleHtml(pin) { 966 var pinid = pin.id; 967 if (typeof pinid === 'undefined') { 968 return ""; 969 } 970 971 // If we've retrieved the bubble html via ajax already, just display that 972 if (pin.pinDesc && pin.pinDesc.length > 0) { 973 cachedInfo = true; 974 return pin.pinDesc; 975 } else { 976 // Get the bubble html 977 cachedInfo = false; 978 getBubbleHtmlAsync(pin); 979 // Set the 'loading' text until our ajax request returns 980 return "<div style='width:100%; text-align:center;' class='bubble-loading'>" + 981 "<span style='height:50px;'><br/><br/><img src='" + options.imageBase + "/search/Images/spinner.gif' border='0' /> " + Utils.translate("Loading", "Loading") + "...</span>" + 982 "</div>"; 983 } 984 } 985 986 function showBubble(pin) { 987 if (!options.showBubble) return; 988 if (_activeDialog !== null) return; 989 990 if (typeof _infoBox === "undefined" || _infoBox == null) { 991 _infoBox = new MM.Infobox(new MM.Location(0, 0), { visible: false, offset: new MM.Point(0, 20) }); 992 MM.Events.addHandler(_infoBox, 'mouseleave', onPinMouseOut); 993 MM.Events.addHandler(_infoBox, 'click', onInfoBoxClick); 994 _infoBox.setMap(_map); 995 } 996 var pinid = pin.id; 997 if (pinid && pinid != "1_0") { 998 // Set the initial visibilty hidden so it doesn't jump around 999 var bubbleHtml = getBubbleHtml(pin); 1000 var pinType = getPinType(pin); 1001 var desc = pinType === BingMap.PinType.Valuation 1002 ? '<div class="pushpinPopup" style="visibility: hidden;"><div class="pushpinPopupBody">' + bubbleHtml + '</div></div>' 1003 : '<div class="pushpinPopup valuationPopup" style="visibility: hidden;"><div class="pushpinPopupBody">' + bubbleHtml + '</div></div>'; 1004 _infoBox.setLocation(pin.getLocation()); 1005 _infoBox.setOptions({ 1006 visible: true, 1007 showCloseButton: false, 1008 htmlContent: desc, 1009 zIndex: 100 1010 }); 1011 1012 // After the bubble is shown, set it to the right div in a multi-listing bubble 1013 showProperBubbleDiv(pin); 1014 1015 // Raise an event to say the bubble is filled and displaying 1016 $(document).trigger("mapbubbledisplay", [pinid]); 1017 } 1018 } 1019 1020 // Parcel event handlers 1021 1022 function parcelHoverHandler(event) { 1023 if (!event) return; 1024 1025 window.clearTimeout(_parcelHover.pauseThread); 1026 if (event.eventName === 'mousemove') { 1027 _parcelHover.pauseThread = window.setTimeout( 1028 function () { 1029 if (Math.abs(event.getX() - _parcelHover.lastMouseLocation.x) > 1 || 1030 Math.abs(event.getY() - _parcelHover.lastMouseLocation.y) > 1) { 1031 _parcelHover.lastMouseLocation.x = event.getX(); 1032 _parcelHover.lastMouseLocation.y = event.getY(); 1033 hoverParcel(event); 1034 } 1035 }, _parcelHover.mousePauseThreshold); 1036 } 1037 } 1038 1039 function hoverParcel(event) { 1040 var layer = null; 1041 var highlightLayerIndex = getLayerIndexById('Highlight');; 1042 1043 var checkHoveringClass = function (propertyId, add) { 1044 if (propertyId) { 1045 $('#pin-val_1_' + propertyId).find('.pin-valuation')[add ? 'addClass' : 'removeClass']('hovering'); 1046 } 1047 _parcelHover.lastPropertyId = add ? propertyId : null; 1048 }; 1049 1050 var clearParcelLayer = function () { 1051 removeDmpLayer(highlightLayerIndex, false); 1052 checkHoveringClass(_parcelHover.lastPropertyId, false); 1053 }; 1054 1055 var processParcelResult = function (json) { 1056 if (!json || !json.Response || json.Response.Results.totalRecords <= 0) { 1057 clearParcelLayer(); 1058 _parcelHover.lastLocation = null; 1059 return; 1060 } 1061 1062 var recordSet = json.Response.Results; 1063 var parcelRec = null; 1064 if (recordSet.Data && recordSet.Data.Row.length > 1) { 1065 parcelRec = recordSet.Data.Row[0]; 1066 } else if (recordSet.Data) { 1067 parcelRec = recordSet.Data.Row; 1068 } 1069 1070 // Use the LOCID (LOCATION_ID) to identify the record that hovered. 1071 var keyField = "LOCID"; 1072 var keyValue = parcelRec[keyField]; 1073 1074 if (_parcelHover.lastLocation === keyValue) { 1075 // Same location so no need to re-draw. 1076 return; 1077 } 1078 clearParcelLayer(); 1079 1080 var parcelNumber = parcelRec["APN"]; 1081 if (parcelNumber) { 1082 checkHoveringClass(_parcelPropertyIds[parcelNumber.replace(new RegExp('-', 'g'), '')], true); 1083 } 1084 1085 1086 // add the server side highlight layer to highlight the hovered parcel 1087 addDmpLayer(highlightLayerIndex, false, { 1088 showField: keyField, 1089 showValues: keyValue, 1090 antiAlias: true, 1091 ignoreHoles: true 1092 }); 1093 1094 _parcelHover.lastLocation = keyValue; 1095 }; 1096 1097 if (!event) return; 1098 1099 if (!highlightLayerIndex) return; 1100 1101 layer = options.dmpLayers[highlightLayerIndex]; 1102 if (layer === undefined || layer === null || 1103 layer.zoomRange.min > Math.round(_mapState.Zoom) || 1104 layer.zoomRange.max < Math.round(_mapState.Zoom)) { 1105 clearParcelLayer(); 1106 _parcelHover.lastLocation = null; 1107 return; 1108 } 1109 1110 // Querystring parameters: 1111 // nested: 1112 // "true" will nest the related records with the base records. This makes the result easier to work with. 1113 // showSchema: 1114 // "false" will tell the service to return the schema information. This can be turned to true during development 1115 // but should stay false in production because it will lessen the payload from the result giving better performance. 1116 // fields: 1117 // the wild card will return all fields in the base resource (parcels); below we specify the fields 1118 // that we want to return, e.g., "&fields=APN,FIPS,LOCID" 1119 var point = new MM.Point(event.getX(), event.getY()); 1120 var pixelLocation = _map.tryPixelToLocation(point); 1121 if (!pixelLocation) return; 1122 1123 var url = 'getByGeometry.aspx?returnGeoType=1&dataSource=' + layer.resource + 1124 '&geo=POINT(' + pixelLocation.longitude + ' ' + pixelLocation.latitude + ')' + 1125 '&nested=true&showSchema=false&fields=APN,LOCID'; 1126 1127 Dmp.Env.Connections['SS'].getJson(url, processParcelResult, function (err) { 1128 console.log('Error getting parcel hover data: ' + err); 1129 });
1130 } 1131 1132 function getLayerIndexById(layerId) { 1133 // Example layerId: "Highlight" 1134 var layerIndex = 0; 1135 if (layerId) { 1136 for (var idx = 0; idx < options.dmpLayers.length; idx++) { 1137 if (options.dmpLayers[idx].id === layerId) { 1138 layerIndex = idx; 1139 break; 1140 } 1141 } 1142 } 1143 1144 return layerIndex; 1145 } 1146 1147 // Pin event handlers 1148 1149 function onPinClick(e) { 1150 if (e.target && e.target.tag === 'ClusterPin') { 1151 clusterClicked(e.target); 1152 } else if (e.target && e.target.pinType && e.target.pinType === 'Valuation') { 1153 var propertyId = e.target.id.replace('val_1_', ''); 1154 triggerValuationClick(propertyId); 1155 } else if (e.targetType === "pushpin" || e.targetType === 'HtmlPushpin') { 1156 showBubble(e.target); 1157 $(_mapContainer).trigger("map-pushpin-click", e); 1158 } 1159 1160 return true; 1161 } 1162 1163 function onPinDoubleClick(e) { 1164 if (e.target && e.target.tag && e.target.tag === 'ClusterPin') { 1165 // Zoom in, don't show the bubble 1166 clusterClicked(e.target); 1167 } else if (e.targetType === "pushpin") { 1168 showBubble(e.target); 1169 $(_mapContainer).trigger("map-pushpin-doubleclick", e); 1170 } 1171 } 1172 1173 function onPinMouseOver(e) { 1174 if (!e) return; 1175 var time = options.pinMouseOverDelay; 1176 if (e.target && e.target.tag && e.target.tag === 'ClusterPin') { 1177 e.target.setOptions(e.target.hoverOpts); 1178 time = 400; 1179 } 1180 if (e.targetType === "pushpin" || e.targetType === "HtmlPushpin") { 1181 _currentPinId = e.target.id; 1182 setTimeout(function () { 1183 if (_currentPinId === e.target.id) { 1184 showBubble(e.target); 1185 $(_mapContainer).trigger("map-pushpin-mouseover", e); 1186 } 1187 }, time); 1188 } 1189 } 1190 1191 function onPinMouseOut(e) { 1192 _currentPinId = 0; 1193 var time = options.pinMouseOutDelay; 1194 if (e.target && e.target.tag && e.target.tag === 'ClusterPin') { 1195 e.target.setOptions(e.target.pinOpts); 1196 time = 500; 1197 } 1198 _currentPinId = 0; 1199 setTimeout(function () { 1200 if ($("div.pushpinPopup:hover").length === 0 && $("div.pin:hover").length === 0) { 1201 _self.hideListingBubble(); 1202 } 1203 $(_mapContainer).trigger("map-pushpin-mouseout", e); 1204 }, time); 1205 } 1206 1207 function onInfoBoxClick(e) { 1208 if (typeof (options.isSM3) !== 'undefined' && options.isSM3) { 1209 var propertyId = $(e.originalEvent.target).parents('.valuationPopup').first().data('property-id'); 1210 if (propertyId) { 1211 triggerValuationClick(propertyId); 1212 } 1213 } 1214 } 1215 1216 function triggerValuationClick(propertyId) { 1217 if (propertyId && _publicRecords[propertyId]) { 1218 event.preventDefault(); 1219 event.stopImmediatePropagation(); 1220 $(document).trigger('map-valuation-click', { 1221 propertyId: propertyId, 1222 url: _publicRecords[propertyId].url 1223 }); 1224 } 1225 } 1226 1227 1228 // Pin management functions 1229 1230 function gotoLocation(loc, addPin, zoom) { 1231 if (typeof zoom !== "number") 1232 zoom = 16; // Default zoom level 1233 1234 $.getJSON('https://dev.virtualearth.net/REST/v1/Locations?query=' + loc + '&key=' + Utils.BingMapKey + '&jsonp=?', function (data) { 1235 if (data && data.resourceSets.length && data.resourceSets[0].resources.length && data.resourceSets[0].resources[0].geocodePoints.length) { 1236 var lat = data.resourceSets[0].resources[0].geocodePoints[0].coordinates[0]; 1237 var lon = data.resourceSets[0].resources[0].geocodePoints[0].coordinates[1]; 1238 if (lat && lon) { 1239 _self.setCenterAndZoom(lat, lon, zoom); 1240 setTimeout(function () { 1241 if (addPin) { 1242 var center = _map.getCenter(); 1243 _self.startAddingPins("Address"); 1244 _self.addPin("Address", "1", center.latitude, center.longitude, BingMap.PinType.Address); 1245 _self.endAddingPins("Address"); 1246 } 1247 }, 500); 1248 } 1249 } 1250 }); 1251 } 1252 1253 function setMapViewBoundaries(pins) { 1254 if (!pins || !pins.length) { 1255 _mapMoveOrigin = null; 1256 return; 1257 } 1258 if (pins.length === 1) { 1259 var pin = pins[0]; 1260 _self.setCenterAndZoom(pin.latitude, pin.longitude, 16); 1261 } else if (options.enableMyNeighborhoodPins) { 1262 var bound = MM.LocationRect.fromLocations(pins);
1263 _map.setView({ bounds: bound, padding: 100 }); // padding value can be set to width/height of pushpin 1264 } else { 1265 var rect = MM.LocationRect.fromLocations(pins); 1266 var northLat = rect.getNorth(), 1267 southLat = rect.getSouth(), 1268 eastLon = rect.getEast(), 1269 westLon = rect.getWest(); 1270 var centerLat = (northLat + southLat) / 2; 1271 var centerLon = (eastLon + westLon) / 2; 1272 1273 // Handle the situation where one of the listings has wrong lat/lon 1274 // Example, you search for Los Angeles and the lat/lon for one of the entries is in San Diego 1275 // To see if that happened, we must compare calculated boundaries (northLat,southLat,eastLat,westLat) 1276 // to the boundaries of Los Angeles. If calculated exceeds the boundary of Los Angeles 1277 // by a significant margin, then reduce the box to Los Angeles boundary, then decrease the zoom by 1 1278 // to pick up any stragglers that are just outside of Los Angeles. 1279 if (_boundaryShapeLayerCount == 1 && _boundaryShapeLayerName && 1280 _shapes[_boundaryShapeLayerName] && _shapes[_boundaryShapeLayerName].length > 0) { 1281 1282 var index_NorthLat = 0, index_SouthLat = 2, index_EastLon = 1, index_WestLon = 3; 1283 1284 // set the defaults so that any legit coordinates will overwrite them 1285 var boundaryNorthLat = -1000, boundarySouthLat = 1000, 1286 boundaryEastLon = -1000, boundaryWestLon = 1000; 1287 1288 // note that there maybe multiple boundaries even in a single city 1289 // thus we loop through all the bounds in the shape and set the edges of the total boundary 1290 for (var i = 0; i < _shapes[_boundaryShapeLayerName].length; i++) { 1291 var currentBounds = _shapes[_boundaryShapeLayerName][i].geometry.bounds; 1292 1293 if (currentBounds[index_NorthLat] > boundaryNorthLat) boundaryNorthLat = currentBounds[index_NorthLat]; 1294 if (currentBounds[index_SouthLat] < boundarySouthLat) boundarySouthLat = currentBounds[index_SouthLat]; 1295 if (currentBounds[index_EastLon] > boundaryEastLon) boundaryEastLon = currentBounds[index_EastLon]; 1296 if (currentBounds[index_WestLon] < boundaryWestLon) boundaryWestLon = currentBounds[index_WestLon]; 1297 } 1298 1299 var pinsLatDistance = northLat - southLat; 1300 var boundaryLatDistance = boundaryNorthLat - boundarySouthLat; 1301 1302 var pinsLonDistance = eastLon - westLon; 1303 var boundaryLonDistance = boundaryEastLon - boundaryWestLon; 1304 1305 if (Math.abs(pinsLatDistance - boundaryLatDistance) > 0.5 || 1306 Math.abs(pinsLonDistance - boundaryLonDistance) > 0.5) { 1307 1308 // The difference between pins and boundary is big enough 1309 // that we need to constrain the pins and recalculate the center lat / lon 1310 northLat = boundaryNorthLat; 1311 southLat = boundarySouthLat; 1312 eastLon = boundaryEastLon; 1313 westLon = boundaryWestLon; 1314 1315 centerLat = (northLat + southLat) / 2; 1316 centerLon = (eastLon + westLon) / 2; 1317 } 1318 } 1319 1320 // BROKER-4675, Bing maps throws exception at zoom level 12, keep here until they fix 1321 var zoom = calculateZoom(northLat, southLat, eastLon, westLon); 1322 zoom = zoom === 12 ? 11 : zoom; 1323 _self.setCenterAndZoom(centerLat, centerLon, zoom); 1324 } 1325 1326 // Sometimes the set view doesn't trigger an onChangeView, let's make sure 1327 setTimeout(function () { 1328 if (_mapMoveOrigin === "SetMapView") { 1329 _self.onChangeView(); 1330 } 1331 }, 1000); 1332 } 1333 1334 function getFullPinImagePath(pinInfo, defaultPath) { 1335 //if pins not search motif v3 1336 var imageFilename = typeof pinInfo.Image !== "undefined" ? pinInfo.Image : pinInfo.ImageOn; 1337 return imageFilename.indexOf("/") !== -1 ? imageFilename : defaultPath + "/" + imageFilename; 1338 } 1339 1340 function calculateZoom(maxLat, minLat, maxLon, minLon) { 1341 // Single point, zoom to 16 1342 if (maxLon === minLon && maxLat === minLat) { 1343 return 18; 1344 } 1345 1346 //Determine the best zoom level based on the map scale and bounding coordinate information 1347 //best zoom level based on map width 1348 var zoom1 = Math.round(Math.log(360.0 / 256.0 * (_mapW - 2) / (maxLon - minLon)) / Math.log(2)); 1349 //best zoom level based on map height 1350 var zoom2 = Math.round(Math.log(180.0 / 256.0 * (_mapH - 2) / (maxLat - minLat)) / Math.log(2)); 1351 1352 //use the most zoomed out of the two zoom levels 1353 return (zoom1 < zoom2) ? zoom1 : zoom2; 1354 } 1355 1356 function clusterClicked(pin) { 1357 // Get the center zoom level for this pin 1358 var centerLat = (pin.northLat + pin.southLat) / 2; 1359 var centerLon = (pin.eastLon + pin.westLon) / 2; 1360 var calculatedZoom = calculateZoom(pin.northLat, pin.southLat, pin.eastLon, pin.westLon); 1361 1362 // Let's make sure we are actually going to zoom in. In some cases, especially with
1363 // a narrow map, this doesn't result in a new zoom 1364 var oldZoom = _map.getZoom(); 1365 if (calculatedZoom <= oldZoom) calculatedZoom += 1; 1366 1367 _self.setCenterAndZoom(centerLat, centerLon, calculatedZoom); 1368 } 1369 1370 function displayPins(pinLayer) { 1371 if (isSupportedHtmlPushpin(pinLayer.name)) { 1372 if (_enableClusteringForPricePins) { 1373 displayPinsWithClusters(pinLayer); 1374 } else if (options.enableMyNeighborhoodPins) { 1375 displayMyNeighborhoodPins(pinLayer); 1376 } else if (pinLayer.name === 'Multi Listing') { 1377 // We have special handling for multi listing layer. 1378 displayPinsWithPriceCollection(pinLayer); 1379 } else { 1380 displayPinsWithPrice(pinLayer); 1381 } 1382 1383 } else { 1384 displayPinsNonPrice(pinLayer); 1385 } 1386 } 1387 1388 function displayPinsWithClusters(pinLayer) { 1389 var pins = pinLayer.pins; 1390 1391 pinLayer.layer.clear(); 1392 pinLayer.pinIds = {}; 1393 1394 for (var i = 0; i < pins.length; i++) { 1395 var pin = pins[i]; 1396 // validate each cluster 1397 if (pin.pinsInCluster < 1) continue; 1398 1399 var location = new MM.Location(pin.latitude, pin.longitude); 1400 var clusterPinDimensions = getClusterPinRadius(pin, false); 1401 var clusterPinHoverDimensions = getClusterPinRadius(pin, true); 1402 var clusterPinOpts = { 1403 icon: createCustomClusteredPinSvg(clusterPinDimensions.radius, 1404 clusterPinDimensions.outlineWidth, 1405 pin.pinsInCluster), 1406 anchor: new MM.Point(clusterPinDimensions.radius, clusterPinDimensions.radius) 1407 }; 1408 var clusterPinHoverOpts = { 1409 icon: createCustomClusteredPinSvg(clusterPinHoverDimensions.radius, 1410 clusterPinHoverDimensions.outlineWidth, 1411 pin.pinsInCluster), 1412 anchor: new MM.Point(clusterPinHoverDimensions.radius, clusterPinHoverDimensions.radius) 1413 }; 1414 var clusterPin = new MM.Pushpin(location, clusterPinOpts); 1415 1416 clusterPin.id = pin.id; 1417 clusterPin.pinDesc = pin.pinDesc; 1418 clusterPin.pinType = pin.pinType; 1419 1420 // set a tag on this object so that it can be identified 1421 // in onPinMouseOver and onPinClick methods 1422 clusterPin.tag = 'ClusterPin'; 1423 clusterPin.northLat = pin.northLat; 1424 clusterPin.southLat = pin.southLat; 1425 clusterPin.westLon = pin.westLon; 1426 clusterPin.eastLon = pin.eastLon; 1427 clusterPin.pinsInCluster = pin.pinsInCluster; 1428 clusterPin.avgPrice = pin.avgPrice; 1429 clusterPin.minPrice = pin.minPrice; 1430 clusterPin.maxPrice = pin.maxPrice; 1431 clusterPin.pinOpts = clusterPinOpts; 1432 clusterPin.hoverOpts = clusterPinHoverOpts; 1433 1434 // setup events for the pushpin 1435 MM.Events.addHandler(clusterPin, 'mouseover', onPinMouseOver); 1436 MM.Events.addHandler(clusterPin, 'mouseout', onPinMouseOut); 1437 MM.Events.addHandler(clusterPin, 'click', onPinClick); 1438 MM.Events.addHandler(clusterPin, 'dblclick', onPinDoubleClick); 1439 pinLayer.layer.add(clusterPin); 1440 pinLayer.pinIds[pin.id] = pin; 1441 } 1442 //Ensure we are displaying the pinlayer layer that was set to visible false in UpdatePOI when Zoom level<12 1443 if (pinLayer.isVisible && pinLayer.layer) { 1444 pinLayer.layer.setVisible(true); 1445 } 1446 _map.layers.insert(pinLayer.layer); 1447 } 1448 1449 function getClusterPinRadius(clusterPin, hoverFlag) { 1450 //Get the number of pushpins in the cluster 1451 var clusterSize = clusterPin.pinsInCluster; 1452 1453 // Define variables for minimum cluster radius, and how wide the outline area of the circle should be. 1454 var outlineWidth = hoverFlag ? 5 : 3; 1455 var radius = hoverFlag ? 14 : 10; 1456 if (clusterSize >= 10 && clusterSize <= 99) { 1457 radius = hoverFlag ? 18 : 14; 1458 } else if (clusterSize >= 100 && clusterSize <= 999) { 1459 radius = hoverFlag ? 22 : 18; 1460 } else if (clusterSize >= 1000 && clusterSize <= 9999) { 1461 radius = hoverFlag ? 30 : 26; 1462 } else if (clusterSize >= 10000) { 1463 radius = hoverFlag ? 38 : 34; 1464 } 1465 1466 return { radius: radius, outlineWidth: outlineWidth }; 1467 } 1468 1469 function createCustomClusteredPinSvg(radius, outlineWidth, count) { 1470 //Create an SVG string of two circles, one on top of the other, with the specified radius and color. 1471 return [ 1472 '<svg xmlns="http://www.w3.org/2000/svg" width="', (radius * 2), '" height="', (radius * 2), '">', 1473 '<style>.count { font: bold 0.6em sans-serif; }</style>', 1474 '<circle cx="', radius, '" cy="', radius, '" r="', radius, '" fill="', options.clusterPinColor, '" fill-opacity="0.4" />', // outer 1475 '<circle cx="', radius, '" cy="', radius, '" r="', radius - outlineWidth, '" fill="', options.clusterPinColor, '" />', 1476 '<text x="50%" y="50%" text-anchor="middle" dy="0.3em" fill="#ffffff" fill-opacity="0.9" class="count">
1476', count.toLocaleString(), '</text>', // inner 1477 '</svg>' 1478 ].join(''); 1479 } 1480 1481 function displayPinsWithPrice(pinLayer) { 1482 var openHouseHtml = '<i class="listing-price-open-new-house">Open</i>'; 1483 var newHouseHtml = '<i class="listing-price-open-new-house">New</i>'; 1484 var customHtml = '<div class="listing-price {listing-price-no-favorite} {listing-price-home-for-sale-class} {isVisible}">{listing-price-open-new-houseHouseHtml}{homeValue}</div>'; 1485 var pins = pinLayer.pins; 1486 1487 pinLayer.layer.clear(); 1488 pinLayer.pinIds = {}; 1489 1490 var layerCharacteristic = getLayerCharacteristics(pinLayer); 1491 1492 for (var i = 0; i < pins.length; i++) { 1493 var pin = pins[i]; 1494 var location = new MM.Location(pin.latitude, pin.longitude); 1495 if (!pin.isVisible) 1496 continue; 1497 var constructedHtml = customHtml.replace('{homeValue}', pin.pinPrice) 1498 .replace('{listing-price-home-for-sale-class}', layerCharacteristic.homeSaleClass) 1499 .replace('{listing-price-open-new-houseHouseHtml}', (layerCharacteristic.isOpenHouse ? openHouseHtml : (layerCharacteristic.isNewHouse ? newHouseHtml : ''))) 1500 .replace('{listing-price-no-favorite}', (pin.pinIsFavorite ? '' : 'listing-price-no-favorite')); 1501 1502 var offset = _zoomLevelOffsets[_map.getZoom()]; 1503 var htmlPin = new HtmlPushpin(location, constructedHtml, { anchor: new MM.Point(offset.x, offset.y) }); 1504 1505 htmlPin.id = pin.id; 1506 htmlPin.pinDesc = pin.pinDesc; 1507 htmlPin.pinType = pin.pinType; 1508 1509 pinLayer.layer.add(htmlPin); 1510 pinLayer.pinIds[pin.id] = pin; 1511 1512 // setup events for the pushpin 1513 MM.Events.addHandler(htmlPin, 'mouseover', function htmlPinMouseOver(event) { 1514 // bring pin to forefront when mouse is over them 1515 // it helps when there are too many pins on the map 1516 event.target._element.style.zIndex = 1000; 1517 onPinMouseOver(event); 1518 }); 1519 1520 MM.Events.addHandler(htmlPin, 'mouseout', function htmlPinMouseOut(event) { 1521 event.target._element.style.zIndex = ''; 1522 onPinMouseOut(event); 1523 }); 1524 1525 MM.Events.addHandler(htmlPin, 'click', onPinClick); 1526 MM.Events.addHandler(htmlPin, 'dblclick', onPinDoubleClick); 1527 } 1528 1529 _map.layers.insert(pinLayer.layer); 1530 } 1531 1532 function displayPinsWithPriceCollection(pinLayer) { 1533 var openHouseHtml = '<i class="listing-price-open-new-house">Open</i>'; 1534 var newHouseHtml = '<i class="listing-price-open-new-house">New</i>'; 1535 var customHtml = '<div class="listing-price {listing-price-no-favorite} {listing-price-home-for-sale-class}">{listing-price-open-new-houseHouseHtml}{homeValue}</div>'; 1536 1537 var pins = pinLayer.pins; 1538 pinLayer.layer.clear(); 1539 pinLayer.pinIds = {}; 1540 1541 for (var i = 0; i < pins.length; i++) { 1542 var pin = pins[i]; 1543 var count = pin.id.split('-').length; 1544 var location = new MM.Location(pin.latitude, pin.longitude); 1545 1546 var constructedHtml = createCustomClusteredPinSvg(15, 5, count); 1547 1548 var offset = _zoomLevelOffsets[_map.getZoom()]; 1549 var htmlPin = new HtmlPushpin(location, constructedHtml, { anchor: new MM.Point(offset.x, offset.y) }); 1550 1551 htmlPin.id = pin.id; 1552 htmlPin.pinDesc = pin.pinDesc; 1553 htmlPin.pinType = pin.pinType; 1554 1555 pinLayer.layer.add(htmlPin); 1556 pinLayer.pinIds[pin.id] = pin; 1557 1558 // setup events for the pushpin 1559 MM.Events.addHandler(htmlPin, 'mouseover', function htmlPinMouseOver(event) { 1560 // bring pin to forefront when mouse is over them 1561 // it helps when there are too many pins on the map 1562 event.target._element.style.zIndex = 1000; 1563 onPinMouseOver(event); 1564 }); 1565 1566 MM.Events.addHandler(htmlPin, 'mouseout', function htmlPinMouseOut(event) { 1567 event.target._element.style.zIndex = ''; 1568 onPinMouseOut(event); 1569 }); 1570 1571 MM.Events.addHandler(htmlPin, 'click', onPinClick); 1572 MM.Events.addHandler(htmlPin, 'dblclick', onPinDoubleClick); 1573 } 1574 1575 _map.layers.insert(pinLayer.layer); 1576 } 1577 1578 function getImagePricePinHtml(pinInfo) { 1579 1580 var template = [ 1581 '<div class="mapPin">', 1582 '<div class="price">' + (pinInfo.pinPrice ? "$" + pinInfo.pinPrice.toString().replace(/(\d)(?=(\d{3})+$)/g, "$1,") : "$0") + '</div>', 1583 '<div class="img" style="background:transparent url(' + pinInfo.pinImageUrl + ') no-repeat center;"></div>', 1584 '</div>' 1585 ]; 1586 1587 return template.join('\n'); 1588 } 1589 1590 function displayMyNeighborhoodPins(pinLayer) { 1591 1592 var pins = pinLayer.pins; 1593 1594 pinLayer.layer.clear(); 1595 pinLayer.pinIds = {}; 1596 1597 $.each(pins, function (index, pin) { 1598 var location = new MM.Location(pin.latitude, pin.longitude); 1599 1600 var pinHtml = getImagePricePinHtml(pin); 1601 1602 var offset = _zoomLevelOffsets[_map.getZoom()]; 1603 var htmlPin = new HtmlPushpin(location, pinHtml, { anchor: new MM.Point(offset.x, offset.y) }); 1604 1605 htmlPin.id = pin.id; 1606 htmlPin.pinDesc = pin.pinDesc; 1607 htmlPin.pinType = pin.pinType; 1608 1609 pinLayer.layer.add(htmlPin); 1610 pinLayer.pinIds[pin.id] = pin; 1611 1612 // setup events for the pushpin 1613 MM.Events.addHandler(htmlPin, 'mouseover', function htmlPinMouseOver(event) { 1614 // bring pin to forefront when mouse is over them 1615 // it helps when there are too many pins on the map 1616 event.target._element.style.zIndex = 1000; 1617 onPinMouseOver(event); 1618 }); 1619 1620 MM.Events.addHandler(htmlPin, 'mouseout', function htmlPinMouseOut(event) { 1621 event.target._element.style.zIndex = ''; 1622 onPinMouseOut(event); 1623 }); 1624
1625 MM.Events.addHandler(htmlPin, 'click', onPinClick); 1626 MM.Events.addHandler(htmlPin, 'dblclick', onPinDoubleClick); 1627 }); 1628 1629 _map.layers.insert(pinLayer.layer); 1630 } 1631 1632 function displayPinsNonPrice(pinLayer) { 1633 var pins = pinLayer.pins; 1634 pinLayer.layer.clear(); 1635 pinLayer.pinIds = {}; 1636 1637 var sm3ListingPinTypes = [ 1638 BingMap.PinType.ListingActive, 1639 BingMap.PinType.ListingNew, 1640 BingMap.PinType.ListingOpenHouse, 1641 BingMap.PinType.ListingPending, 1642 BingMap.PinType.ListingSold]; 1643 1644 for (var i = 0; i < pins.length; i++) { 1645 var pin = pins[i]; 1646 var clusterCount = 1; 1647 if (typeof pin.id === "string") { 1648 clusterCount = pin.id.split("-").length; 1649 } 1650 var pinLatLong = new MM.Location(pin.latitude, pin.longitude); 1651 var text = ""; 1652 var textOffset = new MM.Point(0, 5); 1653 var iconUrl = ""; 1654 var color = null; 1655 1656 //Show Office pins on Map view based on RecoSupportsFlag (only for SM3) 1657 if (pin.pinType === BingMap.PinType.Office && !options.showOfficePinsOnMap) 1658 continue; 1659 1660 //Allow ClusterCount get populated for all the listing statuses ) { 1661 text = clusterCount > 1 ? clusterCount.toString() : ""; //can't pass a number to the text field 1662 1663 if ((sm3ListingPinTypes.indexOf(pin.pinType) > -1)) { 1664 /** 1665 * The following function `getOverrideIconUrlForSM3ListingPinTypes` is used to override or customize 1666 * the listing pin icons on the map for specific RECo (Real Estate Company) on Listing Detail Page (LDP). 1667 * 1668 * If the system is unable to locate this function or if the function does not return icon URL, 1669 * the system will fall back to the default implementation by using the `getDefaultIconUrlForSM3ListingPinTypes` function. 1670 * 1671 * Any RECo can override this functionality by defining the function in the Layout Property Detail - Top Summary 1672 * or any other Property Detail's Layout to customize the pins for Listing Detail Page (LDP). 1673 * To use custom images, place the new icon files under the path `/images/recos/{recoid}/icons/`. 1674 * 1675 * Note: Currently, these pins are only being used for the LDP. The Search Result Page (SRP) uses new price pins, 1676 * so overriding is not necessary there. However, the same technique can be applied to the SRP if customization is required in the future. 1677 */ 1678 1679 if (typeof getOverrideIconUrlForSM3ListingPinTypes === "function") 1680 iconUrl = getOverrideIconUrlForSM3ListingPinTypes(pin.pinType, clusterCount > 1); 1681 1682 // Fallback to the default implementation if no URL is returned or the function does not provide a valid string. 1683 if (!iconUrl || typeof iconUrl !== 'string') 1684 iconUrl = getDefaultIconUrlForSM3ListingPinTypes(pin.pinType, clusterCount > 1); 1685 1686 } 1687 else if (pin.pinType === BingMap.PinType.Valuation) { 1688 text = pin.pinTitle; 1689 iconUrl = getFullPinImagePath(BingMap.PinInfo[pin.pinType], "/images/common/map/bingmapv8/"); 1690 } 1691 else { 1692 var pinType = pin.pinType; 1693 if (clusterCount > 1 && typeof pin.multipleType !== "undefined") { 1694 pinType = pin.multipleType; 1695 } 1696 if (clusterCount === 1) { 1697 //Should not set the text when the description is like a HTML Markup 1698 if (!isNaN(pin.pinDesc)) { 1699 text = pin.pinDesc; 1700 } 1701 textOffset = new MM.Point(0, 12); 1702 } 1703 1704 if (typeof pinType !== "undefined" && pinType !== "") { 1705 iconUrl = getFullPinImagePath(BingMap.PinInfo[pinType], "/images/common/map/bingmapv7/"); 1706 } 1707 } 1708 1709 var pinOpts = { 1710 text: text, 1711 textOffset: textOffset, 1712 id: "pin-" + pin.id, 1713 icon: iconUrl 1714 }; 1715 1716 if (color !== null) { 1717 pinOpts.color = color; 1718 } 1719 1720 var pushPin = new MM.Pushpin(pinLatLong, pinOpts); 1721 pushPin.id = pin.id; 1722 pushPin.pinDesc = pin.pinDesc; 1723 pushPin.pinType = pinLayer.name; 1724 MM.Events.addHandler(pushPin, "mouseover", onPinMouseOver); 1725 MM.Events.addHandler(pushPin, "mouseout", onPinMouseOut);
1726 MM.Events.addHandler(pushPin, "click", onPinClick); 1727 MM.Events.addHandler(pushPin, "dblclick", onPinDoubleClick); 1728 1729 pinLayer.layer.add(pushPin); 1730 pinLayer.pinIds[pin.id] = pin; 1731 } 1732 //Ensure we are displaying the pinlayer layer that was set to visible false in UpdatePOI when Zoom level<12 1733 if (pinLayer.isVisible && pinLayer.layer) { 1734 pinLayer.layer.setVisible(true); 1735 } 1736 _map.layers.insert(pinLayer.layer); 1737 1738 return; 1739 } 1740 1741 function getDefaultIconUrlForSM3ListingPinTypes(pinType, isCluster) { 1742 1743 if (isCluster) 1744 return "/images/common/map/images/icons/search-motif-v3-clustered.png"; 1745 1746 //Green dot image for Active,New and Openhouse listings 1747 if (pinType === BingMap.PinType.ListingActive || pinType === BingMap.PinType.ListingNew || pinType === BingMap.PinType.ListingOpenHouse) 1748 return "/images/common/map/images/icons/search-motif-v3-listing.png"; 1749 1750 //Yellow dot image for Pending,Contingent and Back up offers 1751 if (pinType === BingMap.PinType.ListingPending) 1752 return "/images/common/map/images/icons/search-motif-v3-listing-pending.png"; 1753 1754 //Red dot image for anything that denotes the property is no longer active nor in pending / contingent status 1755 return "/images/common/map/images/icons/search-motif-v3-listing-sold.png"; 1756 } 1757 function getLayerCharacteristics(pinLayer) { 1758 // populate dictionary on first use 1759 if (Object.keys(_layerCharacteristics).length === 0) { 1760 var colorSold = '#C81845', 1761 colorSale = '#009934', 1762 colorPending = '#FBA902', 1763 colorComingSoon = '#026AA2', 1764 colorAuction = '#ba24d5', 1765 colorRent = '#7C3FDE'; 1766 1767 _layerCharacteristics['Property'] = { 1768 isOpenHouse: false, 1769 isNewHouse: false, 1770 homeSaleClass: 'listing-price-home-for-sale', 1771 color: colorSale 1772 }; 1773 _layerCharacteristics['Open House Available'] = { 1774 isOpenHouse: true, 1775 isNewHouse: false, 1776 homeSaleClass: 'listing-price-home-for-sale', 1777 color: colorSale 1778 }; 1779 _layerCharacteristics['Sale Pending'] = { 1780 isOpenHouse: false, 1781 isNewHouse: false, 1782 homeSaleClass: 'listing-price-home-sale-pending', 1783 color: colorPending 1784 }; 1785 _layerCharacteristics['Sold Property'] = { 1786 isOpenHouse: false, 1787 isNewHouse: false, 1788 homeSaleClass: 'listing-price-home-sold', 1789 color: colorSold 1790 }; 1791 _layerCharacteristics['Rent'] = { 1792 isOpenHouse: false, 1793 isNewHouse: false, 1794 homeSaleClass: 'listing-price-home-rent', 1795 color: colorRent 1796 }; 1797 _layerCharacteristics['New Property'] = { 1798 isOpenHouse: false, 1799 isNewHouse: true, 1800 homeSaleClass: 'listing-price-home-for-sale', 1801 color: colorSale 1802 }; 1803 _layerCharacteristics['Commercial Property'] = { 1804 isOpenHouse: false, 1805 isNewHouse: false, 1806 homeSaleClass: 'listing-price-home-for-sale', 1807 color: colorSale 1808 }; 1809 _layerCharacteristics['Commercial Sale Pending'] = { 1810 isOpenHouse: false, 1811 isNewHouse: false, 1812 homeSaleClass: 'listing-price-home-sale-pending', 1813 color: colorPending 1814 }; 1815 _layerCharacteristics['Sold Commercial'] = { 1816 isOpenHouse: false, 1817 isNewHouse: false, 1818 homeSaleClass: 'listing-price-home-sold', 1819 color: colorSold 1820 }; 1821 _layerCharacteristics['Coming Soon'] = { 1822 isOpenHouse: false, 1823 isNewHouse: false, 1824 homeSaleClass: 'listing-price-home-coming-soon', 1825 color: colorComingSoon 1826 }; 1827 _layerCharacteristics['Auction'] = { 1828 isOpenHouse: false, 1829 isNewHouse: false, 1830 homeSaleClass: 'listing-price-home-auction', 1831 color: colorAuction 1832 }; 1833 } 1834 1835 var characteristic = _layerCharacteristics[pinLayer.name]; 1836 if (characteristic === undefined) { 1837 // return default 1838 characteristic = _layerCharacteristics['Property']; 1839 console.warn('Unknown layer name encountered: ' + pinLayer.name); 1840 } 1841 1842 return characteristic; 1843 } 1844 1845 function meanAverageGridCluster(pins) { 1846 var i, j, len; 1847 var gridCells; 1848 var gridSize = 35; 1849 var shapeCount = pins.length; 1850 if (shapeCount === 0) { 1851 return []; 1852 } 1853 _mapW = $(_mapContainer).outerWidth(); 1854 _mapH = $(_mapContainer).outerHeight(); 1855 1856 // Break the map up into a grid 1857 var numXCells = parseInt(Math.ceil(_mapW / gridSize), 10); 1858 var numYCells = parseInt(Math.ceil(_mapH / gridSize), 10); 1859 1860 //create an array to store all the grid data. 1861 gridCells = new Array(numXCells * numYCells); 1862 //Iterate through the shapes in the base layer 1863 for (i = 0; i < shapeCount; i++) { 1864 //convert the shapes latlong to a pixel location 1865 var shape = pins[i]; 1866 var latLong = new MM.Location(shape.latitude, shape.longitude); 1867 var pixel = _map.tryLocationToPixel(latLong, MM.PixelReference.control); 1868 var xPixel = pixel.x; 1869 var yPixel = pixel.y; 1870 1871 //check to see if the shape is within the bounds of the viewable map 1872 if (_mapW >= xPixel && _mapH >= yPixel && xPixel >= 0 && yPixel >= 0) { 1873 //calculate the grid position on the map of where the shape is located 1874 var a = Math.floor(xPixel / gridSize); 1875 var b = Math.floor(yPixel / gridSize); 1876 //calculates the grid location in the array 1877 var key = a + b * numXCells; 1878 1879 var spec = gridCells[key]; 1880 if (spec == null) { 1881 spec = {}; 1882 spec.Pins = []; 1883 gridCells[key] = spec; 1884 } 1885 spec.Pins.push(shape); 1886 } 1887 } 1888 1889 //Iterate through the clustered data in the grid array 1890 var cluster = []; 1891 for (i = 0, len = gridCells.length; i < len; i++) { 1892 var cell = gridCells[i]; 1893 //calculate mean clustered coordinate 1894 if (cell != null && cell.Pins.length > 0) { 1895 var size = cell.Pins.length; 1896 var latSum = 0; 1897 var lonSum = 0; 1898 for (j = 0; j < size; j++) { 1899 var pin = cell.Pins[j]; 1900 latSum += pin.latitude; 1901 lonSum += pin.longitude; 1902 } 1903 cell.LatLong = new MM.Location(latSum / size, lonSum / size); 1904 cluster.push(cell); 1905 } 1906 } 1907 1908 return cluster; 1909 } 1910 1911 function sortShapes(a, b, shapesSortOrder) { 1912 var swapOrder = 0; 1913 var index = 0; 1914 while (!swapOrder && index <= shapesSortOrder.length) { 1915 swapOrder = (a ^ shapesSortOrder[index]) ? (b ^ shapesSortOrder[index]) ? 0 : 1 : -1; 1916 index++; 1917 } 1918 return swapOrder; 1919 } 1920 1921 function getPinsFromClusterSpecs(clusters) { 1922 var clusterPins = []; 1923 // sort the shapes by order of PinType as follows: 1924 var pinsSortOrder = [ 1925 BingMap.PinType.ListingActive, 1926 BingMap.PinType.ListingSold, 1927 BingMap.PinType.ListingPending, 1928 BingMap.PinType.ListingNew, 1929 BingMap.PinType.ListingOpenHouse, 1930 BingMap.PinType.ListingCommercialActive, 1931 BingMap.PinType.ListingCommercialSold, 1932 BingMap.PinType.ListingCommercialPending 1933 ]; 1934 for (var i = 0, clusterCount = clusters.length; i < clusterCount; ++i) { 1935 var clusterSpec = clusters[i]; 1936 var pins = clusterSpec.Pins; 1937 pins.sort(function (a, b) { return sortShapes(a.pinType, b.pinType, pinsSortOrder); }); 1938 var clusterPinType = pins[0].pinType; 1939 var pinInfo = BingMap.PinInfo[clusterPinType]; 1940 var pinClass; 1941 if (pinInfo) { 1942 if (pins.length === 1) { 1943 pinClass = pinInfo.PinClass; 1944 } else { 1945 if (pinInfo.PinType === BingMap.PinType.ListingSold) { 1946 pinClass = BingMap.PinInfo[BingMap.PinType.ListingSoldMultiple].PinClass; 1947 } else if (pinInfo.PinType === BingMap.PinType.ListingOpenHouse) { 1948 pinClass = BingMap.PinInfo[BingMap.PinType.ListingOpenHouseMultiple].PinClass; 1949 } else if (pinInfo.PinType === BingMap.PinType.ListingPending) { 1950 pinClass = BingMap.PinInfo[BingMap.PinType.ListingPendingMultiple].PinClass; 1951 } else if (pinInfo.PinType === BingMap.PinType.ListingActive || pinInfo.PinType === BingMap.PinType.ListingNew) { 1952 pinClass = BingMap.PinInfo[BingMap.PinType.ListingMultiple].PinClass; 1953 } else if (pinInfo.PinType === BingMap.PinType.ListingCommercialActive || pinInfo.PinType === BingMap.PinType.ListingCommercialNew) { 1954 pinClass = BingMap.PinInfo[BingMap.PinType.ListingCommercialMultiple].PinClass; 1955 } else if (pinInfo.PinType === BingMap.PinType.ListingCommercialPending) { 1956 pinClass = BingMap.PinInfo[BingMap.PinType.ListingCommercialPendingMultiple].PinClass; 1957 } else if (pinInfo.PinType === BingMap.PinType.ListingCommercialSold) { 1958 pinClass = BingMap.PinInfo[BingMap.PinType.ListingCommercialSoldMultiple].PinClass; 1959 } else { 1960 pinClass = pinInfo.PinClass; 1961 } 1962 } 1963 } else { 1964 pinClass = BingMap.PinInfo[BingMap.PinType.ListingActive].PinClass; 1965 } 1966 var pinIds = new StringBuilder(); 1967 for (var j = 0, shapeCountj = pins.length; j < shapeCountj; j++) { 1968 if (j > 0) { 1969 pinIds.append("-"); 1970 } 1971 pinIds.append(pins[j].id); 1972 } 1973 var clusterPin = {}; 1974 clusterPin.id = pinIds.toString(); 1975 clusterPin.latitude = clusterSpec.LatLong.latitude; 1976 clusterPin.longitude = clusterSpec.LatLong.longitude; 1977 clusterPin.pinType = pinInfo.PinType; 1978 clusterPin.pinClass = pinClass; 1979 clusterPins.push(clusterPin); 1980 } 1981 return clusterPins; 1982 } 1983 1984 function getClusteredPins(pins) { 1985 var clusterSpecs = meanAverageGridCluster(pins); 1986 return getPinsFromClusterSpecs(clusterSpecs); 1987 } 1988 1989 function clusterPinLayer(pinLayer) { 1990 pinLayer.pins = getClusteredPins(pinLayer.pins); 1991 _map.layers.remove(pinLayer.layer); 1992 displayPins(pinLayer); 1993 } 1994 1995 // Draw rectangle functions 1996 1997 function drawRectangle(currentMouseLoc, startingLoc, polyName) { 1998 var north = Math.max(currentMouseLoc.latitude, startingLoc.latitude); 1999 var south = Math.min(currentMouseLoc.latitude, startingLoc.latitude); 2000 var east = Math.max(currentMouseLoc.longitude, startingLoc.longitude); 2001 var west = Math.min(currentMouseLoc.longitude, startingLoc.longitude); 2002 var points = [ 2003 new MM.Location(north, west), 2004 new MM.Location(north, east), 2005 new MM.Location(south, east), 2006 new MM.Location(south, west), 2007 new MM.Location(north, west) 2008 ]; 2009 var shape = new MM.Polygon(points, { fillColor: options.shapeFillColor, strokeColor: options.shapeLineColor, strokeThickness: options.shapeLineThickness });
2010 shape.name = polyName; 2011 shape.isCustom = true; 2012 shape.start = startingLoc; 2013 var layer = getLayer(polyName, false, LayerType.Shape, true); 2014 addShapesToLayer([shape], layer); 2015 } 2016 2017 // Draw polygon functions 2018 2019 /* 2020 The following functions are from simplify-js, copyright notice here: 2021 2022 Copyright (c) 2015, Vladimir Agafonkin 2023 All rights reserved. 2024 2025 Redistribution and use in source and binary forms, with or without modification, are 2026 permitted provided that the following conditions are met: 2027 2028 1. Redistributions of source code must retain the above copyright notice, this list of 2029 conditions and the following disclaimer. 2030 2031 2. Redistributions in binary form must reproduce the above copyright notice, this list 2032 of conditions and the following disclaimer in the documentation and/or other materials 2033 provided with the distribution. 2034 2035 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 2036 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 2037 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 2038 COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 2039 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 2040 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2041 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 2042 TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 2043 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2044 */ 2045 2046 // square distance between 2 points 2047 function getSqDist(p1, p2) { 2048 2049 var dx = p1.longitude - p2.longitude, 2050 dy = p1.latitude - p2.latitude; 2051 2052 return dx * dx + dy * dy; 2053 } 2054 2055 // square distance from a point to a segment 2056 function getSqSegDist(p, p1, p2) { 2057 2058 var x = p1.longitude, 2059 y = p1.latitude, 2060 dx = p2.longitude - x, 2061 dy = p2.latitude - y; 2062 2063 if (dx !== 0 || dy !== 0) { 2064 2065 var t = ((p.longitude - x) * dx + (p.latitude - y) * dy) / (dx * dx + dy * dy); 2066 2067 if (t > 1) { 2068 x = p2.longitude; 2069 y = p2.latitude; 2070 2071 } else if (t > 0) { 2072 x += dx * t; 2073 y += dy * t; 2074 } 2075 } 2076 2077 dx = p.longitude - x; 2078 dy = p.latitude - y; 2079 2080 return dx * dx + dy * dy; 2081 } 2082 // rest of the code doesn't care about point format 2083 2084 // basic distance-based simplification 2085 function simplifyRadialDist(points, sqTolerance) { 2086 2087 var prevPoint = points[0], 2088 newPoints = [prevPoint], 2089 point; 2090 2091 for (var i = 1, len = points.length; i < len; i++) { 2092 point = points[i]; 2093 2094 if (getSqDist(point, prevPoint) > sqTolerance) { 2095 newPoints.push(point); 2096 prevPoint = point; 2097 } 2098 } 2099 2100 if (prevPoint !== point) newPoints.push(point); 2101 2102 return newPoints; 2103 } 2104 2105 function simplifyDPStep(points, first, last, sqTolerance, simplified) { 2106 var maxSqDist = sqTolerance, 2107 index; 2108 2109 for (var i = first + 1; i < last; i++) { 2110 var sqDist = getSqSegDist(points[i], points[first], points[last]); 2111 2112 if (sqDist > maxSqDist) { 2113 index = i; 2114 maxSqDist = sqDist; 2115 } 2116 } 2117 2118 if (maxSqDist > sqTolerance) { 2119 if (index - first > 1) simplifyDPStep(points, first, index, sqTolerance, simplified); 2120 simplified.push(points[index]); 2121 if (last - index > 1) simplifyDPStep(points, index, last, sqTolerance, simplified); 2122 } 2123 } 2124 2125 // simplification using Ramer-Douglas-Peucker algorithm 2126 function simplifyDouglasPeucker(points, sqTolerance) { 2127 var last = points.length - 1; 2128 2129 var simplified = [points[0]]; 2130 simplifyDPStep(points, 0, last, sqTolerance, simplified); 2131 simplified.push(points[last]); 2132 2133 return simplified; 2134 } 2135 2136 // both algorithms combined for awesome performance 2137 function simplify(points, tolerance, highestQuality) { 2138 2139 if (points.length <= 4) return points; 2140 2141 var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1; 2142 2143 points = highestQuality ? points : simplifyRadialDist(points, sqTolerance); 2144 points = simplifyDouglasPeucker(points, sqTolerance); 2145 2146 return points; 2147 } 2148 /* End copied from simplify-js */ 2149 2150 function getToleranceByZoomLevel() { 2151 var zoom = _map.getZoom(); 2152 switch (zoom) { 2153 case 24: 2154 case 23: 2155 case 22: 2156 case 21: 2157 case 20: 2158 case 19: 2159 return 0.000001; 2160 case 18: 2161 return 0.000003; 2162 case 17: 2163 return 0.000006; 2164 case 16: 2165 return 0.00001; 2166 case 15: 2167 return 0.000025; 2168 case 14: 2169 return 0.00005; 2170 case 13: 2171 return 0.0001; 2172 case 12: 2173 return 0.0002; 2174 case 11: 2175 return 0.0004; 2176 case 10: 2177 return 0.0008; 2178 case 9: 2179 return 0.0015; 2180 case 8: 2181 return 0.003; 2182 case 7: 2183 return 0.009; 2184 case 6: 2185 return 0.02; 2186 case 5: 2187 return 0.04; 2188 case 4: 2189 return 0.07; 2190 default: 2191 return 0.1; 2192 } 2193 } 2194
2195 function finishDrawing(shape) { 2196 var shapeLocations = shape[0].getLocations(); 2197 var firstLocation = shapeLocations[0]; 2198 var lastLocation = shapeLocations.slice(-1)[0]; 2199 if (firstLocation.latitude !== lastLocation.latitude || firstLocation.longitude !== lastLocation.longitude) { 2200 shapeLocations.push(firstLocation); 2201 } 2202 if (shapeLocations.length < 4) { 2203 return null; 2204 } 2205 var tolerance = getToleranceByZoomLevel(); 2206 2207 var simplifiedPoints = simplify(shapeLocations, tolerance); 2208 var polygon = new MM.Polygon(simplifiedPoints, { fillColor: options.shapeFillColor, strokeColor: options.shapeLineColor, strokeThickness: options.shapeLineThickness }); 2209 polygon.name = _currentShapeName; 2210 polygon.isCustom = true; 2211 shape = [polygon]; 2212 var layer = getLayer(_currentShapeName, false, LayerType.Shape, true); 2213 addShapesToLayer(shape, layer); 2214 return shape; 2215 } 2216 2217 function drawPolyLine(loc, polyName) { 2218 var shape = new MM.Polyline([loc], { strokeColor: options.shapeLineColor, strokeThickness: options.shapeLineThickness }); 2219 shape.name = polyName; 2220 var layer = getLayer(polyName, false, LayerType.Shape, true); 2221 addShapesToLayer([shape], layer); 2222 } 2223 2224 // Draw radius functions 2225 2226 function distance(lat1, lon1, lat2, lon2, unit) { 2227 var radlat1 = Math.PI * lat1 / 180; 2228 var radlat2 = Math.PI * lat2 / 180; 2229 var theta = lon1 - lon2; 2230 var radtheta = Math.PI * theta / 180; 2231 var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); 2232 dist = Math.acos(dist); 2233 dist = dist * 180 / Math.PI; 2234 dist = dist * 60 * 1.1515; 2235 if (unit === "K") { dist = dist * 1.609344 }; 2236 if (unit === "N") { dist = dist * 0.8684 }; 2237 return dist; 2238 } 2239 2240 function drawRadiusDistance(position, radius) { 2241 var point = _map.tryLocationToPixel(position, MM.PixelReference.control); 2242 $("div.map-radius-distance", _mapContainer) 2243 .text(Math.round(radius * 100) / 100 + " miles") 2244 .css({ top: point.y + 15, left: point.x + 15 }) 2245 .show(); 2246 } 2247 2248 function removeRadiusLine(polyName) { 2249 _self.clearPolygon("radiusline-" + polyName); 2250 $("div.map-radius-distance", _mapContainer).hide(); 2251 } 2252 2253 function drawRadiusLine(loc1, loc2, polyName) { 2254 var shape = new MM.Polyline([loc1, loc2], { strokeColor: options.shapeLineColor, strokeThickness: options.shapeLineThickness, strokeDashArray: options.strokeDashArray }); 2255 var layer = getLayer("radiusline-" + polyName, false, LayerType.Shape, true); 2256 addShapesToLayer([shape], layer); 2257 } 2258 2259 function drawRadiusShape(radius, latitude, longitude, polyName, cb) { 2260 var lat = (latitude * Math.PI) / 180; 2261 var lon = (longitude * Math.PI) / 180; 2262 var R = 3959; // earth's mean radius in km 2263 var d = parseFloat(radius) / R; 2264 var circlePoints = []; 2265 2266 for (var x = 0; x <= 360; x += 5) { 2267 var p2 = new MM.Location(0, 0); 2268 var brng = x * Math.PI / 180; 2269 p2.latitude = Math.asin(Math.sin(lat) * Math.cos(d) + Math.cos(lat) * Math.sin(d) * Math.cos(brng)); 2270 2271 p2.longitude = ((lon + Math.atan2(Math.sin(brng) * Math.sin(d) * Math.cos(lat), 2272 Math.cos(d) - Math.sin(lat) * Math.sin(p2.latitude))) * 180) / Math.PI; 2273 p2.latitude = (p2.latitude * 180) / Math.PI; 2274 circlePoints.push(p2); 2275 } 2276 var shape = new MM.Polygon(circlePoints, { fillColor: options.shapeFillColor, strokeColor: options.shapeLineColor, strokeThickness: options.shapeLineThickness }); 2277 shape.center = new MM.Location(latitude, longitude); 2278 shape.radius = radius; 2279 shape.isCustom = true; 2280 shape.name = polyName; 2281 var layer = getLayer(polyName, false, LayerType.Shape, true); 2282 addShapesToLayer([shape], layer, cb); 2283 } 2284 2285 // Functions for adding pre-defined shapes to map 2286 2287 function addShapesByPolyPoints(points, polyLayer, cb) { 2288 var ctr = 0; 2289 try { 2290 var locations = []; 2291 var pointsSplit = points.split(","); 2292 for (var i = 0, len = pointsSplit.length; i < len / 2; i++) { 2293 var latVal = parseFloat(pointsSplit[ctr]); 2294 var longVal = MM.Location.normalizeLongitude(parseFloat(pointsSplit[ctr + 1])); 2295 2296 locations.push(new MM.Location(latVal, longVal)); 2297 ctr += 2; 2298 } 2299 var shapes = []; 2300 if (locations.length >= 4) { 2301 var shape = new MM.Polygon(locations, { fillColor: options.shapeFillColor, strokeColor: options.shapeLineColor, strokeThickness: options.shapeLineThickness });
2302 shape.name = polyLayer.name; 2303 shape.isCustom = true; 2304 addShapesToLayer([shape], polyLayer); 2305 shapes.push(shape); 2306 } 2307 2308 if (cb && typeof cb === "function") { 2309 cb(shapes); 2310 } 2311 } catch (err) { 2312 Utils.Logger.warn("Error adding polygon from points: " + err.message); 2313 } 2314 } 2315 2316 function addShapesByRadius(polyLayer, pointsRadius, cb) { 2317 try { 2318 var pointsStr = pointsRadius.split('~')[0]; 2319 var radius = parseFloat(pointsRadius.split('~')[1]); 2320 var pointsSplit = pointsStr.split(','); 2321 var latVal = parseFloat(pointsSplit[0]); 2322 var longVal = MM.Location.normalizeLongitude(parseFloat(pointsSplit[1])); 2323 drawRadiusShape(radius, latVal, longVal, polyLayer.name, cb); 2324 2325 } catch (err) { 2326 Utils.Logger.warn("Error adding polygon from radius: " + err.message); 2327 } 2328 } 2329 2330 function addShapeByDriveTime(polyName, layer, qsParams, cb) { 2331 if (typeof qsParams == "undefined" || qsParams == null) { 2332 return; 2333 } 2334 //Clean up some parameter value 2335 var paramStr = qsParams.split(","); 2336 2337 // AGENT-1672 - Using range type to form boundary polygon 2338 var param = { address: polyName, time: paramStr[0], duration: paramStr[1], rangeType: paramStr[2] }; 2339 2340 $.ajax({
2341 type: "GET", 2342 url: Utils.AppPath + "/Include/AJAX/mapsearch/GetDriveTimeBoundary.aspx", 2343 data: param, 2344 dataType: "json", 2345 error: function (msg) { 2346 console.log(msg); 2347 }, 2348 success: function (data) { 2349 //data object contains two children, Boundary and CenterOfMap 2350 //Boundary is a list of poly points and Cetner of Map is a Point indicating the target of the search 2351 var boundary = data.Boundary; 2352 var centerOfSearch = data.CenterOfMap; 2353 2354 var shapes = []; 2355 try { 2356 var latlongs = []; 2357 var j = boundary.Points.length; 2358 // Weird loop structure for performance 2359 if (j > 0) { 2360 do { 2361 var point = boundary.Points[j - 1]; 2362 // Retrieve the latitude and longitude values- normalize the longitude value 2363 var latVal = parseFloat(point[0]); 2364 var longVal = MM.Location.normalizeLongitude(parseFloat(point[1])); 2365 latlongs.push(new MM.Location(latVal, longVal)); 2366 } while (--j); 2367 } 2368 var shape = new MM.Polygon(latlongs, { fillColor: options.shapeFillColor, strokeColor: options.shapeLineColor, strokeThickness: options.shapeLineThickness }); 2369 shapes.push(shape); 2370 } catch (ex) { 2371 Utils.Logger.warn(ex.message); 2372 } 2373 2374 if (shapes.length > 0) { 2375 addShapesToLayer(shapes, layer, cb); 2376 } 2377 if (centerOfSearch != null) { 2378 _self.startAddingPins("target", false); 2379 _self.addPin("target", "pinTarget", centerOfSearch.X, centerOfSearch.Y, 40); 2380 _self.endAddingPins("target"); 2381 2382 //Adding a boot strap pop over to the target 2383 var pinLayer = getLayer("target"); 2384 var pins = pinLayer.pins; 2385 if (pins != null && pins.length > 0) { 2386 //there should only be one target 2387 var pin = pins[0]; 2388 var pinTarget = $("#pin_" + pin.ID); 2389 $(pinTarget.parent()).addClass("bootstrap"); 2390 pinTarget.popover({ content: polyName, placement: 'right', trigger: 'hover', title: 'Drive Time Search' }); 2391 } 2392 } 2393 // The InrixDriveTimeSearch.js is a current listener to this event, It checked 2394 // if getting drive time boundary is return from the server to prevent user from 2395 // requesting another search before the current search is finished. 2396 $(_mapContainer).trigger("drivetime-shapedrawn"); 2397 2398 if (typeof cb === "function") { 2399 cb(shapes); 2400 } 2401 } 2402 }); 2403 } 2404 2405 function addShapesByBoundary(qsParams, layer, cb) { 2406 if (typeof qsParams === "undefined") { return; } 2407 2408 MM.loadModule('Microsoft.Maps.WellKnownText', function () { 2409 qsParams.wkt = "true"; 2410 2411 $.ajax({
2412 type: "GET", 2413 url: Utils.AppPath + "/Include/AJAX/mapsearch/GetBoundary.aspx", 2414 data: qsParams, 2415 dataType: "json", 2416 success: function (wkt) { 2417 //Parse well known text string. 2418 var shapes; 2419 2420 if (!wkt || wkt.length <= 0 || typeof wkt !== "string") { 2421 shapes = []; 2422 } else { 2423 if (!wkt.toUpperCase().contains('POLYGON')) { 2424 // Is just a string of coordinates, "lat,long,lat,long" 2425 // Convert to WKT, MULTIPOLYGON(((long lat, long lat))) 2426 2427 var polygonString = 'MULTIPOLYGON((('; 2428 var coordArr = wkt.split(','); 2429 2430 for (var i = 0; i < coordArr.length; i += 2) { 2431 polygonString += coordArr[i + 1] + ' ' + coordArr[i] + ','; 2432 } 2433 2434 wkt = polygonString.substring(0, polygonString.length - 1) + ')))'; 2435 } 2436 2437 shapes = MM.WellKnownText.read(wkt, 2438 { 2439 polygonOptions: 2440 { 2441 fillColor: options.shapeFillColor, 2442 strokeColor: options.shapeLineColor, 2443 strokeThickness: options.shapeLineThickness 2444 } 2445 } 2446 ); 2447 } 2448 2449 _boundaryShapeLayerName = layer.name; 2450 _boundaryShapeLayerCount++; 2451 addShapesToLayer(shapes, layer, cb); 2452 } 2453 }); 2454 }); 2455 2456 } 2457 2458 function addShapes(polyLayer, polyType, polyValue, cb) { 2459 if (!polyType || polyType === "points") { 2460 addShapesByPolyPoints(polyValue, polyLayer, cb); 2461 } else if (polyType === "drivetime") { 2462 addShapeByDriveTime(polyLayer.name, polyLayer, polyValue, cb); 2463 } else if (polyType === "radius") { 2464 addShapesByRadius(polyLayer, polyValue, cb); 2465 } else { 2466 var state = ""; 2467 if (polyType === "city" || polyType === "county") { 2468 state = window.defaultState; 2469 if (polyValue.indexOf(",") > -1) { // includes state 2470 var locationState = polyValue.split(","); 2471 polyValue = locationState[0].trim(); 2472 state = locationState[1].trim(); 2473 } 2474 } 2475 2476 addShapesByBoundary({ 2477 val: polyValue, 2478 type: polyType, 2479 state: state 2480 }, polyLayer, cb); 2481 } 2482 } 2483 2484 // Functions for responding to new custom shapes 2485 2486 function getPointsString(points) { 2487 var pointsStr = ''; 2488 for (var i = 0; i < points.length; i++) { 2489 var point = points[i]; 2490 pointsStr += point.latitude + ',' + point.longitude + ','; 2491 } 2492 if (pointsStr.length > 0) { 2493 pointsStr = pointsStr.substr(0, pointsStr.length - 1); 2494 } 2495 return pointsStr; 2496 } 2497 2498 function onCustomShapeAdded(shapes) { 2499 var shape = shapes[0]; 2500 var pointsStr; 2501 if (shape.center) { 2502 pointsStr = getPointsString([shape.center]); 2503 } else { 2504 pointsStr = getPointsString(shape.getLocations()); 2505 } 2506 // When a shape is added we trigger an event so that listeners can take action 2507 // MapSearch.js uses this to add a location to its criteria 2508 $(_mapContainer).trigger("shapedrawn", [shape.name, pointsStr, shape.radius]); 2509 } 2510 2511 // Map type functions 2512 2513 function getMapTypeId(mapView) { 2514 // These are some 'legacy' view strings from way back that are still in use 2515 if (mapView === "b" || mapView === "o" || mapView === "be") { 2516 return MM.MapTypeId.birdseye; 2517 } else if (mapView === "a") { 2518 return MM.MapTypeId.aerial; 2519 } else { 2520 return MM.MapTypeId.road; 2521 } 2522 } 2523 2524 // Drawing mouse event handlers 2525 2526 function drawMouseDown(e) { 2527 _map.setOptions({ disablePanning: true }); 2528 _drawing = true; 2529 _enableMouseMoveTimer = window.setInterval(function () { 2530 _enableMouseMoveHandler = true; 2531 }, 50); 2532 var center = _map.tryPixelToLocation(new MM.Point(e.getX(), e.getY())); 2533 if (_currentDrawType === "radius") { 2534 drawRadiusShape(0.0, center.latitude, center.longitude, _currentShapeName); 2535 } else if (_currentDrawType === "rectangle") { 2536 drawRectangle(center, center, _currentShapeName); 2537 } else { 2538 drawPolyLine(center, _currentShapeName); 2539 } 2540 //e.handled = true; 2541 } 2542 2543 function drawMouseMove(e) { 2544 if (!_drawing || !_enableMouseMoveHandler) return; 2545 _enableMouseMoveHandler = false; 2546 var shape = _shapes[_currentShapeName][0]; 2547 var currentMousePoint = new MM.Point(e.getX(), e.getY()); 2548 var currentMouseLocation = _map.tryPixelToLocation(currentMousePoint); 2549 if (_currentDrawType === "radius") { 2550 var radius = distance(currentMouseLocation.latitude, currentMouseLocation.longitude, shape.center.latitude, shape.center.longitude); 2551 drawRadiusShape(radius, shape.center.latitude, shape.center.longitude, _currentShapeName); 2552 drawRadiusLine(currentMouseLocation, shape.center, _currentShapeName); 2553 drawRadiusDistance(currentMouseLocation, radius); 2554 } else if (_currentDrawType === "draw") { 2555 var shapeLocations = shape.getLocations(); 2556 var lastLocation = shapeLocations.slice(-1)[0]; 2557 if (!lastLocation || currentMouseLocation.latitude !== lastLocation.latitude || currentMouseLocation.longitude !== lastLocation.longitude) { 2558 shapeLocations.push(currentMouseLocation); 2559 shape.setLocations(shapeLocations); 2560 var layer = getLayer(_currentShapeName, false, LayerType.Shape, true); 2561 addShapesToLayer([shape], layer); 2562 } 2563 } else if (_currentDrawType === "rectangle") { 2564 drawRectangle(currentMouseLocation, shape.start, _currentShapeName); 2565 } 2566 //e.handled = true; 2567 } 2568 2569 function drawMouseUp(e) { 2570 _map.setOptions({ disablePanning: false }); 2571 _drawing = false; 2572 clearInterval(_enableMouseMoveTimer); 2573 $(_map.getRootElement()).css("cursor", "default"); 2574 _self.showAllPins(true); 2575 var shape = _shapes[_currentShapeName]; 2576 if (shape) { 2577 if (_currentDrawType === "radius") { 2578 removeRadiusLine(_currentShapeName); 2579 } 2580 if (_currentDrawType === "draw") { 2581 // When we're drawing this is a polyline. We need to close it 2582 // off and add it as a polygon
2583 shape = finishDrawing(shape); 2584 } 2585 // It might be null if it was an invalid shape 2586 if (shape != null) { 2587 onCustomShapeAdded(shape); 2588 } 2589 } 2590 _currentShapeName = null; 2591 _currentDrawType = null; 2592 2593 MM.Events.removeHandler(_drawMouseDownHandler); 2594 MM.Events.removeHandler(_drawMouseMoveHandler); 2595 MM.Events.removeHandler(_drawMouseUpHandler); 2596 2597 //if (e) { 2598 // e.handled = true; 2599 //} 2600 } 2601 2602 function startDrawing(drawType) { 2603 _currentDrawType = drawType; 2604 _self.hideAllPins(false); 2605 $(_map.getRootElement()).css("cursor", "crosshair"); 2606 2607 _drawMouseDownHandler = MM.Events.addHandler(_map, "mousedown", drawMouseDown); 2608 _drawMouseMoveHandler = MM.Events.addHandler(_map, "mousemove", drawMouseMove); 2609 _drawMouseUpHandler = MM.Events.addHandler(_map, "mouseup", drawMouseUp); 2610 } 2611 2612 // Poi functions 2613 2614 function updatePoi(removeExisting) { 2615 if (_mapState.Zoom >= 12) { 2616 var pinTypes = ""; 2617 for (var i = 0; i < BingMap.PinInfo.length; i++) { 2618 var pinInfo = BingMap.PinInfo[i]; 2619 if (pinInfo.PinClass && pinInfo.PinClass.indexOf("poi-") === 0 && pinInfo.Disabled !== true) { 2620 var pinLayer = getLayer(pinInfo.ShortName, false, LayerType.Poi); 2621 if (pinLayer.isVisible) { 2622 if (pinTypes.length > 0) { 2623 pinTypes += ","; 2624 } 2625 pinTypes += pinInfo.PinType; 2626 } else if (removeExisting) { 2627 _self.deleteAllPins(pinLayer.name); 2628 } 2629 } 2630 } 2631 if (pinTypes.length === 0) { 2632 if (!_poiEnabled) { 2633 $(_mapContainer).trigger({ 2634 type: "poichange", poiEnabled: true 2635 }); 2636 _poiEnabled = true; 2637 } 2638 } else { 2639 var url = Utils.AppPath + "/Include/AJAX/MapSearch/GetPOI.aspx" + 2640 "?neLat=" + _mapState.NELat + 2641 "&neLong=" + _mapState.NELon + 2642 "&swLat=" + _mapState.SWLat + 2643 "&swLong=" + _mapState.SWLon + 2644 "&types=" + pinTypes; 2645 2646 $.ajax({
2647 mode: "abort", 2648 port: "poi", 2649 type: "GET", 2650 url: url, 2651 cache: true, 2652 dataType: "json", 2653 success: function (data) { 2654 //add poi pins to the map 2655 for (var i = 0, len = data.length; i < len; i++) { 2656 var pinType = data[i].pinType; 2657 var layerName = BingMap.PinInfo[pinType].ShortName; 2658 _self.startAddingPins(layerName); 2659 for (var j = 0, poiLen = data[i].poi.length; j < poiLen; j++) { 2660 var poi = data[i].poi[j]; 2661 _self.addPin(layerName, "poi_" + pinType + "_" + j, poi.latitude, poi.longitude, pinType, poi.name, poi.html); 2662 } 2663 _self.endAddingPins(layerName); 2664 } 2665 if (!_poiEnabled) { 2666 $(_mapContainer).trigger({ type: "poichange", poiEnabled: true }); 2667 _poiEnabled = true; 2668 } 2669 } 2670 }); 2671 } 2672 } else { 2673 if (_poiEnabled) { 2674 //Hide the pin layer and keep the pinlayer visible (avoid uncheck the checkbox) 2675 _self.hideAllPOI(false); 2676 _poiEnabled = false; 2677 $(_mapContainer).trigger({ type: "poichange", poiEnabled: false }); 2678 } 2679 } 2680 } 2681 2682 // Valuation functions 2683 2684 function updateValuations() { 2685 var highlightIndex = getLayerIndexById('Highlight'); 2686 var layer = highlightIndex && options ? options.dmpLayers[highlightIndex] : null; 2687 2688 if (!layer) return; 2689 2690 if (Math.round(_mapState.Zoom) < layer.zoomRange.min || 2691 Math.round(_mapState.Zoom) > layer.zoomRange.max) { 2692 _self.deleteAllPins('Valuations'); 2693 } else { 2694 var boundingBox = { 2695 NeLat: _mapState.NELat, 2696 SwLat: _mapState.SWLat, 2697 NeLon: _mapState.NELon, 2698 SwLon: _mapState.SWLon 2699 }; 2700 $.ajax({
2701 mode: 'abort', 2702 port: 'valuationpins', 2703 type: 'POST', 2704 url: Utils.AppPath + '/Include/AJAX/MapSearch/GetValuationPins.aspx', 2705 data: boundingBox, 2706 dataType: 'json', 2707 beforeSend: function (jqXHR, settings) { 2708 jqXHR.setRequestHeader("X-Form", JSON.stringify(boundingBox)); 2709 } 2710 }) 2711 .done(function (data) { 2712 // Initialize Valuation layer as non-cluster. 'Valuations' layer holds 'Valuation' pins. 2713 getLayer('Valuation', false); 2714 2715 // Add the valuation "pins" 2716 if (data.IsSuccess && data.Entity && data.Entity.length) { 2717 _self.startAddingPins('Valuations', false); 2718 $.each(data.Entity, function (index, prop) { 2719 if (prop.ParcelNumber) { 2720 _parcelPropertyIds[prop.ParcelNumber] = prop.PropertyId; 2721 } 2722 _publicRecords[prop.PropertyId] = { 2723 parcelNumber: prop.ParcelNumber, 2724 url: prop.Url 2725 }; 2726 _self.addPin('Valuations', 2727 'val_1_' + prop.PropertyId, 2728 prop.Lat, 2729 prop.Lon, 2730 BingMap.PinType.Valuation, 2731 prop.EstimatedValue, 2732 null); 2733 }); 2734 _self.endAddingPins('Valuations'); 2735 } else { 2736 _self.deleteAllPins('Valuations'); 2737 } 2738 }); 2739 } 2740 } 2741 2742 // Layers functions 2743 2744 function removeDmpLayer(layerIndex, isLabels) { 2745 var layerDef = options.dmpLayers[layerIndex]; 2746 var layerId; 2747 if (isLabels) { 2748 layerId = layerDef.id + "L" + options.instanceId; 2749 layerDef.labelIsVisible = false; 2750 } else { 2751 layerId = layerDef.id + "B" + options.instanceId; 2752 layerDef.isVisible = false; 2753 } 2754 var layer = (typeof _map.getDmpLayerById !== "undefined") && _map.getDmpLayerById(layerId); 2755 if (layer) { 2756 _map.removeEntity(layer); 2757 } 2758 } 2759 2760 function addDmpLayer(layerIndex, isLabels, params) { 2761 var layerDef = options.dmpLayers[layerIndex]; 2762 var layerId, layerSld; 2763 if (isLabels) { 2764 layerId = layerDef.id + "L" + options.instanceId; 2765 layerSld = layerDef.lsld; 2766 layerDef.labelIsVisible = true; 2767 } else { 2768 layerId = layerDef.id + "B" + options.instanceId; 2769 layerSld = layerDef.sld; 2770 layerDef.isVisible = true; 2771 } 2772 2773 if ((typeof _map.getDmpLayerById === "undefined") || _map.getDmpLayerById(layerId)) { 2774 return; 2775 } 2776 2777 var layer; 2778 var layerParams = $.extend({ antiAlias: true }, params); 2779 if (layerDef.id === "ParcelLine" || layerDef.id === "FloodZone") { 2780 layer = new Dmp.Layer.TileLayer(layerId, "SS", layerParams); 2781 } else { 2782 layer = new Dmp.Layer.WMSLayer(layerId, "SS", layerParams); 2783 } 2784 layer.addChild(layerId, layerDef.resource, layerSld, { zIndex: layerDef.zIndex, zoomRange: layerDef.zoomRange }); 2785 _map.addEntity(layer); 2786 } 2787 2788 function showBingTraffic(layerDef) { 2789 if (_trafficManager) { 2790 _trafficManager.showFlow(); 2791 _trafficManager.showLegend(); 2792 layerDef.isVisible = true; 2793 } 2794 } 2795 2796 function hideBingTraffic(layerDef) { 2797 if (_trafficManager) { 2798 _trafficManager.hide(); 2799 } 2800 layerDef.isVisible = false; 2801 } 2802 2803 // Nav bar functions 2804 2805 function zoomIn() { 2806 var zoom = _map.getZoom(); 2807 if (zoom < _map.getZoomRange().max) {
2808 _map.setView({ 2809 'zoom': zoom + 1 2810 }); 2811 } 2812 2813 return false; 2814 } 2815 2816 function zoomOut() { 2817 var zoom = _map.getZoom(); 2818 if (zoom > _map.getZoomRange().min) { 2819 _map.setView({ 2820 'zoom': zoom - 1 2821 }); 2822 } 2823 return false; 2824 } 2825 2826 function rotateLeft() { 2827 var heading = _map.getHeading(); 2828 var newHeading = heading === 0 ? 270 : heading - 90; 2829 _map.setView({ heading: newHeading }); 2830 return false; 2831 } 2832 2833 function rotateRight() { 2834 var heading = _map.getHeading(); 2835 var newHeading = heading === 270 ? 0 : heading + 90; 2836 _map.setView({ heading: newHeading }); 2837 return false; 2838 } 2839 2840 function resetOrientation() { 2841 _map.setView({ heading: 0 }); 2842 return false; 2843 } 2844 2845 function toggleRotateButtons() { 2846 var imageryId = _map.getImageryId(); 2847 if (imageryId.indexOf("Birdseye") !== -1) { 2848 $("#RotateButtonContainer", _mapContainer).show(); 2849 } else { 2850 $("#RotateButtonContainer", _mapContainer).hide(); 2851 } 2852 } 2853 2854 // Menu functions 2855 2856 function showHideMenuItems($menubar) { 2857 var countShown = 0; 2858 2859 var showHide = function (className, show) { 2860 $("li.map-menuitem a." + className, $menubar).parent()[show ? "show" : "hide"](); 2861 if (show) countShown += 1; 2862 }; 2863 2864 showHide('drawIcon', options.showDrawingTools); 2865 showHide('layersIcon', options.showLayerMenu); 2866 showHide('poiIcon', options.showPoi); 2867 2868 var showPinsIcon = !enablePricePins() && ((typeof options.showPinLayerPicker === "undefined" || options.showPinLayerPicker == null) 2869 ? options.showLayerMenu 2870 : options.showPinLayerPicker); 2871 showHide('pinsIcon', showPinsIcon); 2872 2873 $menubar[countShown > 0 ? "show" : "hide"](); 2874 } 2875 2876 function onMapWidthChanged() { 2877 if ($(_mapContainer).width() < 300) { 2878 $("div.map-menubar", _mapContainer).removeClass("narrow verynarrow").addClass("narrow verynarrow"); 2879 _map.setOptions({ navigationBarMode: MM.NavigationBarMode.minified }); 2880 } else if ($(_mapContainer).width() < 525) { 2881 $("div.map-menubar", _mapContainer).removeClass("narrow verynarrow").addClass("narrow"); 2882 _map.setOptions({ navigationBarMode: MM.NavigationBarMode.minified }); 2883 } else { 2884 $("div.map-menubar", _mapContainer).removeClass("narrow verynarrow"); 2885 _map.setOptions({ navigationBarMode: MM.NavigationBarMode.default }); 2886 } 2887 } 2888 2889 function getBarColorClass() { 2890 var mapTypeId = _map.getMapTypeId(); 2891 return mapTypeId === "r" ? "map-light" : "map-dark"; 2892 } 2893 2894 function hideAllDialogs() { 2895 _self.hideListingBubble(); 2896 hideMenuDialog(); 2897 hideMapTypeMenu(); 2898 hideShapeRemover(); 2899 } 2900 2901 function toggleMapLabelsSelector(view) { 2902 if (typeof view === "undefined") { 2903 view = _map.getMapTypeId(); 2904 } 2905 if (view !== "r") { 2906 $(".labelToggle", _mapContainer).removeClass("disabled"); 2907 $("#labels-onoff", _mapContainer).prop("disabled", false); 2908 } else { 2909 $(".labelToggle", _mapContainer).removeClass("disabled").addClass("disabled"); 2910 $("#labels-onoff", _mapContainer).prop("disabled", true); 2911 } 2912 } 2913 2914 function updateMapTypeMenu(newView) { 2915 var mapTypeName = options.mapText.mapTypeName[newView]; 2916 var mapTypeNameClass = mapTypeName.replace(" ", "").replace("'", ""); 2917 $("#MapTypeText a", _mapContainer).text(mapTypeName); 2918 $("#MapTypeIcon", _mapContainer) 2919 .removeClass("selectorIconRoad selectorIconAerial selectorIconBirdseye") 2920 .addClass("selectorIcon" + mapTypeNameClass); 2921 $(".map-navbar,.map-menubar", _mapContainer).removeClass("map-dark").removeClass("map-light"); 2922 if (newView === "r") { 2923 $(".map-navbar,.map-menubar", _mapContainer).addClass("map-light"); 2924 } else { 2925 $(".map-navbar,.map-menubar", _mapContainer).addClass("map-dark"); 2926 } 2927 toggleMapLabelsSelector(newView); 2928 } 2929 2930 function hideMapTypeMenu() { 2931 $("#MapTypeMenu", _mapContainer).hide(250); 2932 $("#MapStyleSelector", _mapContainer).removeClass("active-dialog"); 2933 return false; 2934 } 2935 2936 function showMapTypeMenu() { 2937 if (!$("#MapTypeMenu", _mapContainer).is(":visible") || $("#MapTypeMenu", _mapContainer).is(":animated")) { 2938 hideAllDialogs(); 2939 $("#MapTypeMenu", _mapContainer).show(250); 2940 $("#MapStyleSelector", _mapContainer).addClass("active-dialog"); 2941 } else { 2942 hideMapTypeMenu(); 2943 } 2944 return false; 2945 } 2946 2947 function hideMenuDialog() { 2948 if ($(".map-dialog", _mapContainer).is(":visible")) { 2949 $(".map-dialog", _mapContainer).slideUp(250, function () { 2950 $(".map-dialog", _mapContainer).css("overflow", "visible"); 2951 }); 2952 $(".map-menuitem a", _mapContainer).removeClass("active-dialog"); 2953 2954 _activeDialog = null; 2955 } 2956 return false; 2957 } 2958 2959 function showMenuDialog(dialogName, body) { 2960 var show = false; 2961 if (_activeDialog != null) { 2962 var previousDialog = _activeDialog; 2963 if (previousDialog === dialogName) { 2964 hideMenuDialog(); 2965 return; 2966 } 2967 } else { 2968 hideAllDialogs(); 2969 show = true; 2970 } 2971 _activeDialog = dialogName; 2972 $(".map-menuitem a", _mapContainer).removeClass("active-dialog"); 2973 $("a." + dialogName + "Icon", _mapContainer).addClass("active-dialog"); 2974 $(".map-dialog-header-icon", _mapContainer) 2975 .removeClass() 2976 .addClass("map-dialog-header-icon") 2977 .addClass(dialogName + "Icon"); 2978 $(".map-dialog-body", _mapContainer).html(body); 2979 if (show) { 2980 $(".map-dialog", _mapContainer) 2981 .css("opacity", 0) 2982 .slideDown("fast") 2983 .animate( 2984 { opacity: 1 }, 2985 { queue: false, duration: "slow" } 2986 ); 2987 $(".map-dialog", _mapContainer).css("overflow", "visible"); 2988 } 2989 } 2990 2991 function showDrawMenu() { 2992 // Header 2993 var bodyHtml = "<ul class='map-draw-header'>" + 2994 "<li class='map-draw-header-item drawIcon selected'><div class='map-menu-icon map-menu-icon-draw'></div><span>" + options.mapText.dialogs.draw.drawTab + "</span></li>" + 2995 "<li class='map-draw-header-item radiusIcon'><div class='map-menu-icon map-menu-icon-radius'></div><span>" + options.mapText.dialogs.draw.radiusTab + "</span></li>" + 2996 "<li class='map-draw-header-item rectangleIcon'><div class='map-menu-icon map-menu-icon-square'></div><span>" + options.mapText.dialogs.draw.rectangleTab + "</span></li>" + 2997 "</ul>"; 2998 // Draw menu 2999 bodyHtml += "<div class='map-draw-container draw'>
2999" + 3000 "<label class='dialog-section-header'>" + options.mapText.dialogs.draw.drawHeader + "</label>" + 3001 "</div>"; 3002 // Radius menu 3003 bodyHtml += "<div class='map-draw-container radius' style='display: none'>" + 3004 "<label class='dialog-section-header'>" + options.mapText.dialogs.draw.radiusHeader + "</label>" + 3005 "<ul class='map-radius-list map-checkbox-list'>" + 3006 "<li><label class='radio'><input type='radio' name='map-radius-select' value='click' checked='checked' /> " + options.mapText.dialogs.draw.radiusFree + "</label></li>" + 3007 "<li><label class='radio'><input type='radio' name='map-radius-select' value='center' /> " + options.mapText.dialogs.draw.radiusCenter + " </label>" + 3008 "<select id='map-dialog-radius-distance-center' class='map-dialog-radius-distance' >" + 3009 "<option value='.5'>.5 " + Utils.translate('miles') + "</option>" + 3010 "<option value='1'>1 " + Utils.translate('mile') + "</option>" + 3011 "<option value='2' selected='selected'>2 " + Utils.translate('miles') + "</option>" + 3012 "<option value='3'>3 " + Utils.translate('miles') + "</option>" + 3013 "<option value='4'>4 " + Utils.translate('miles') + "</option>" + 3014 "<option value='5'>5 " + Utils.translate('miles') + "</option>" + 3015 "<option value='10'>10 " + Utils.translate('miles') + "</option>" + 3016 "<option value='15'>15 " + Utils.translate('miles') + "</option>" + 3017 "<option value='20'>20 " + Utils.translate('miles') + "</option>" + 3018 "</select>" + 3019 "</li>"; 3020 if (rbw.geolocation.browserSupportsGeolocation) { 3021 bodyHtml += "<li><label class='radio'><input type='radio' name='map-radius-select' value='mylocation' /> " + options.mapText.dialogs.draw.radiusMyLocation + " </label>" + 3022 "<select id='map-dialog-radius-distance-mylocation' class='map-dialog-radius-distance' >" + 3023 "<option value='.5'>.5 " + Utils.translate('miles') + "</option>" + 3024 "<option value='1'>1 " + Utils.translate('mile') + "</option>" + 3025 "<option value='2' selected='selected'>2 " + Utils.translate('miles') + "</option>" + 3026 "<option value='3'>3 " + Utils.translate('miles') + "</option>" + 3027 "<option value='4'>4 " + Utils.translate('miles') + "</option>" + 3028 "<option value='5'>5 " + Utils.translate('miles') + "</option>" + 3029 "<option value='10'>10 " + Utils.translate('miles') + "</option>" + 3030 "<option value='15'>15 " + Utils.translate('miles') + "</option>" + 3031 "<option value='20'>20 " + Utils.translate('miles') + "</option>" + 3032 "</select>" + 3033 "<label class='dialog-note'>" + options.mapText.dialogs.draw.radiusMyLocationNote + "</label>" + 3034 "</li>"; 3035 } 3036 bodyHtml += "</ul></div>"; 3037 // Rectangle menu 3038 bodyHtml += "<div class='map-draw-container rectangle' style='display: none'>" + 3039 "<label class='dialog-section-header'>" + options.mapText.dialogs.draw.rectangleHeader + "</label>" + 3040 "</div>"; 3041 // Shape name and Draw button 3042 bodyHtml += "<div class='map-draw-footer'>" + 3043 "<label class='map-draw-shapename-container'>" + options.mapText.dialogs.draw.footer + "<br/><input type='text' class='input-block-level' id='map-dialog-draw-shapename' value='Drawn Shape " + _boundaryCount + "' /></label>" + 3044 "<button id='map-draw-button' class='btn btn-primary'>" + options.mapText.dialogs.draw.buttonText + "</button></div>" + 3045 "</div>"; 3046 showMenuDialog("draw", bodyHtml); 3047 3048 var drawType, shapeName; 3049 function getDrawType($selectedTab) { 3050 if ($selectedTab.hasClass("rectangleIcon")) { 3051 drawType = "rectangle"; 3052 shapeName = "Rectangle"; 3053 } else if ($selectedTab.hasClass("radiusIcon")) { 3054 drawType = "radius"; 3055 shapeName = "Radius"; 3056 } else { 3057 drawType = "draw"; 3058 shapeName = "Drawn Shape"; 3059 } 3060 } 3061 $("li.map-draw-header-item", _mapContainer).on("click", function () { 3062 getDrawType($(this)); 3063 $("#map-dialog-draw-shapename", _mapContainer).val(shapeName + " " + _boundaryCount); 3064 $("li.map-draw-header-item", _mapContainer).removeClass("selected"); 3065 $("li.map-draw-header-item." + drawType + "Icon", _mapContainer).addClass("selected"); 3066 $(".map-draw-container", _mapContainer).hide(); 3067 $(".map-draw-container." + drawType, _mapContainer).show(); 3068 });
3069 3070 $("#map-draw-button", _mapContainer).on("click", function () { 3071 getDrawType($("li.map-draw-header-item.selected", _mapContainer)); 3072 _boundaryCount++; 3073 _currentShapeName = $("#map-dialog-draw-shapename", _mapContainer).val(); 3074 if (_currentShapeName.length === 0) { 3075 _currentShapeName = shapeName; 3076 $("#map-dialog-draw-shapename", _mapContainer).val(_currentShapeName); 3077 } 3078 if (drawType === "radius") { 3079 var selected = $(".map-radius-list input[name='map-radius-select']:checked", _mapContainer).val(); 3080 if (selected === "click") { 3081 startDrawing("radius"); 3082 } else { 3083 var distance = Number($("#map-dialog-radius-distance-" + selected, _mapContainer).val()); 3084 if (selected === "center") { 3085 var loc = _map.getCenter(); 3086 drawRadiusShape(distance, loc.latitude, loc.longitude, _currentShapeName, onCustomShapeAdded); 3087 } else { 3088 rbw.geolocation.getCurrentPosition(function (position) { 3089 drawRadiusShape(distance, position.coords.latitude, position.coords.longitude, _currentShapeName, onCustomShapeAdded); 3090 }); 3091 } 3092 } 3093 } else { 3094 startDrawing(drawType); 3095 } 3096 hideMenuDialog(); 3097 return false; 3098 }); 3099 $("#map-dialog-draw-shapename", _mapContainer).on("focus", function () { 3100 this.select(); 3101 }); 3102 return false; 3103 } 3104 3105 function showLayersMenu() { 3106 // Map view selector 3107 var bodyHtml = "<div class='map-view-selector-container'>" + 3108 "<label class='dialog-section-header'>" + options.mapText.dialogs.layers.headerMapView + "</label>" + 3109 "<ul class='map-views'>" + 3110 "<li class='map-view Road' data-view='r'><label class='radio'><input type='radio' name='map-view-select' value='r' /> " + options.mapText.mapTypeName.r + "</label><div class='slot'><div class='icon'></div></div></li>" + 3111 "<li class='map-view Aerial' data-view='a'><label class='radio'><input type='radio' name='map-view-select' value='a' /> " + options.mapText.mapTypeName.a + "</label><div class='slot'><div class='icon'></div></div></li>" + 3112 "</ul></div>"; 3113 3114 var dmpHtml = ""; 3115 if (options.dmpLayers.length > 0) { 3116 dmpHtml = "<div class='map-dmp-selector-container'>" + 3117 "<label class='dialog-section-header'>" + options.mapText.dialogs.layers.header + "</label>" + 3118 "<label class='dialog-note'>" + options.mapText.dialogs.layers.note + "</label>" + 3119 "<ul class='map-dmp-list map-checkbox-list'>" + 3120 "<li class='boundary-select select-header'>" + options.mapText.dialogs.layers.boundaryHeader + "</li><li class='label-select select-header'>" + options.mapText.dialogs.layers.labelHeader + "</li>"; 3121 for (var i = 0; i < options.dmpLayers.length; i++) { 3122 var layerDef = options.dmpLayers[i]; 3123 dmpHtml += "<li class='boundary-select'><label class='checkbox' for='checkbox-" + i + "'><input id='checkbox-" + i + "' type='checkbox'"; 3124 if (layerDef.isVisible) { 3125 dmpHtml += " checked='checked'"; 3126 } 3127 dmpHtml += " />" + layerDef.label + "</label></li>"; 3128 dmpHtml += "<li class='label-select'>"; 3129 if (layerDef.lsld && layerDef.lsld.length > 0) { 3130 dmpHtml += "<input type='checkbox' id='checkbox-label-" + i + "'"; 3131 if (layerDef.labelIsVisible) { 3132 dmpHtml += " checked='checked'"; 3133 } 3134 dmpHtml += " />"; 3135 } 3136 dmpHtml += "</li>"; 3137 } 3138 dmpHtml += "<li class='clearall'><a href='#' id='map-layers-clearall'>" + options.mapText.dialogs.layers.clear + "</a></li>"; 3139 dmpHtml += "</ul></div>"; 3140 } 3141 bodyHtml += dmpHtml; 3142 3143 // Show the dialog 3144 showMenuDialog("layers", bodyHtml); 3145 $("ul.map-views input[value='" + _map.getMapTypeId() + "']").prop("checked", true); 3146 $("ul.map-views li.map-view").on("click", function () { 3147 var mapView = $(this).data("view"); 3148 _self.setView(mapView); 3149 $("ul.map-views input[value='" + mapView + "']").prop("checked", true); 3150 });
3151 $("ul.map-dmp-list li.boundary-select input[type='checkbox']", _mapContainer).change(function () { 3152 var layerIndex = this.id.replace("checkbox-", ""); 3153 var layerDef = options.dmpLayers[layerIndex]; 3154 if (this.checked) { 3155 if (layerDef.id === "BingTraffic") { 3156 showBingTraffic(layerDef); 3157 } else { 3158 addDmpLayer(layerIndex, false); 3159 } 3160 } else { 3161 if (layerDef.id === "BingTraffic") { 3162 hideBingTraffic(layerDef); 3163 } else { 3164 removeDmpLayer(layerIndex, false); 3165 } 3166 } 3167 }); 3168 $("ul.map-dmp-list li.label-select input[type='checkbox']", _mapContainer).change(function () { 3169 var layerIndex = this.id.replace("checkbox-label-", ""); 3170 if (this.checked) { 3171 addDmpLayer(layerIndex, true); 3172 } else { 3173 removeDmpLayer(layerIndex, true); 3174 } 3175 }); 3176 $("ul.map-dmp-list li.clearall a").on("click", function () { 3177 for (var i = 0; i < options.dmpLayers.length; i++) { 3178 var layerDef = options.dmpLayers[i]; 3179 if (layerDef.id === "BingTraffic") { 3180 hideBingTraffic(layerDef); 3181 } else { 3182 removeDmpLayer(i, false); 3183 removeDmpLayer(i, true); 3184 } 3185 } 3186 $("ul.map-dmp-list input[type='checkbox']").prop("checked", false); 3187 return false; 3188 }); 3189 return false; 3190 } 3191 3192 function showPoiMenu() { 3193 // Create the body with list of POI 3194 var bodyHtml = "<label class='dialog-section-header'>" + options.mapText.dialogs.poi.header + "</label>" + 3195 "<label class='dialog-note'>" + options.mapText.dialogs.poi.note + "</label>" + 3196 "<ul class='map-poi-list map-checkbox-list'>"; 3197 for (var i = 0; i < BingMap.PinInfo.length; i++) { 3198 var pinInfo = BingMap.PinInfo[i]; 3199 if (pinInfo.PinClass && pinInfo.PinClass.indexOf("poi-") === 0 && pinInfo.Disabled !== true) { 3200 var pinLayer = getLayer(pinInfo.ShortName); 3201 bodyHtml += "<li><label class='checkbox' for='checkbox-" + pinInfo.ShortName + "'><input id='checkbox-" + pinInfo.ShortName + "' type='checkbox'"; 3202 if (pinLayer.isVisible) { 3203 bodyHtml += " checked='checked'"; 3204 } 3205 bodyHtml += " /><span class='" + pinInfo.PinClass + " poi-icon'>" + pinInfo.DisplayName + "</span></label></li>"; 3206 } 3207 } 3208 bodyHtml += "<li class='clearall'><a href='#' id='map-poi-clearall'>" + options.mapText.dialogs.poi.clear + "</a></li>" + 3209 "</ul><div style='clear:left;'></div>"; 3210 3211 // Show the dialog 3212 showMenuDialog("poi", bodyHtml); 3213 // Activate the checkboxes 3214 $("ul.map-poi-list li input[type='checkbox']", _mapContainer).change(function () { 3215 var pinType = this.id.replace("checkbox-", ""); 3216 if (this.checked) { 3217 _self.showPOI(pinType); 3218 } else { 3219 _self.hidePOI(pinType); 3220 } 3221 }); 3222 $("#map-poi-clearall", _mapContainer).on("click", function () { 3223 _self.hideAllPOI(true); 3224 return false; 3225 }); 3226 return false; 3227 } 3228 3229 function showPinsMenu() { 3230 // Create the body with the list of layers 3231 var bodyHtml = ""; 3232 var i = 0; 3233 3234 var addedLegendNames = []; 3235
3236 Object.keys(_layers).forEach(function (layerName) { 3237 var pinLayer = _layers[layerName]; 3238 var pinTypeClass = {}; 3239 3240 if (pinLayer.name === 'ClusterPins') return; 3241 //Don't add POI (layerType 2) to Pins Section 3242 if (pinLayer.pins && pinLayer.pins.length > 0 && pinLayer.layerType === LayerType.Pin) { 3243 for (var j = 0; j < pinLayer.pins.length; ++j) { 3244 if (pinLayer.pins[j].tag === 'ClusterPin') continue; 3245 3246 var hasCustomLayerName = translatePinTypeToLayerName(pinLayer.pins[j].pinType, false); 3247 3248 if (hasCustomLayerName && typeof pinTypeClass[pinLayer.pins[j].pinType] === "undefined") 3249 pinTypeClass[pinLayer.pins[j].pinType] = pinLayer.pins[j].pinClass; 3250 } 3251 } 3252 3253 Object.keys(pinTypeClass).forEach(function (pinType) { 3254 var legendLayerName = translatePinTypeToLayerName(pinType); 3255 3256 if (addedLegendNames.indexOf(legendLayerName) !== -1) 3257 return; 3258 3259 addedLegendNames.push(legendLayerName); 3260 3261 bodyHtml += "<li><label class='checkbox' for='checkbox-" + ++i + "'><input id='checkbox-" + i + "' type='checkbox'"; 3262 3263 if (pinLayer.isVisible) 3264 bodyHtml += " checked='checked'"; 3265 3266 bodyHtml += " /><span class='" + pinTypeClass[pinType] + " pin'>" + legendLayerName + "</span></label></li>"; 3267 }); 3268 3269 if (bodyHtml == "" && pinLayer.pins && pinLayer.pins.length > 0 && pinLayer.layerType === LayerType.Pin) { 3270 var pinClass = pinLayer.pins[0].pinClass; 3271 // Currently in the pins we can have different statuses and such, we only 3272 // want the active one to show here. In the near future we'll be making this 3273 // A full legend where you can click on and off all the different statuses 3274 // and we can fix this 3275 if (pinClass.indexOf("pin-listing") === 0) { 3276 pinClass = "pin-listing-active"; 3277 } 3278 bodyHtml += "<li><label class='checkbox' for='checkbox-" + ++i + "'><input id='checkbox-" + i + "' type='checkbox'"; 3279 if (pinLayer.isVisible) { 3280 bodyHtml += " checked='checked'"; 3281 } 3282 bodyHtml += " /><span class='" + pinClass + " pin'>" + layerName + "</span></label></li>"; 3283 } 3284 }); 3285 3286 if (bodyHtml.length === 0) { 3287 bodyHtml = "<label class='dialog-section-header'>" + options.mapText.dialogs.pins.none + "</label>"; 3288 } else { 3289 bodyHtml = "<label class='dialog-section-header'>" + options.mapText.dialogs.pins.header + "</label>" + 3290 "<ul class='map-pinlayers-list map-checkbox-list'>" + bodyHtml + "</ul>"; 3291 } 3292 // Show the dialog 3293 showMenuDialog("pins", bodyHtml); 3294 // Activate the checkboxes 3295 $("ul.map-pinlayers-list li input[type='checkbox']", _mapContainer).change(function () { 3296 if (this.checked) { 3297 _self.showPins($(this).parent().text()); 3298 } else { 3299 _self.hidePins($(this).parent().text()); 3300 } 3301 }); 3302 return false; 3303 } 3304 3305 function checkClick(e) { 3306 // If we click out of the map, close the dialogs 3307 if ($(e.target).closest('#' + options.container).length) { 3308 return; 3309 } 3310 hideAllDialogs(); 3311 } 3312 3313 function switchToBirdseye() { 3314 //Verify that birdseye imagery is available before trying to switch into it. 3315 MM.getIsBirdseyeAvailable(_map.getCenter(), 3316 _map.getHeading(), 3317 function (isAvailable) { 3318 if (isAvailable) {
3319 _map.setView({ mapTypeId: MM.MapTypeId.birdseye, heading: _map.getHeading() }); 3320 } else { 3321 _map.setView({ mapTypeId: MM.MapTypeId.aerial }); 3322 } 3323 }); 3324 } 3325 3326 function onMapTypeChange() { 3327 var newView = _map.getMapTypeId(); 3328 if (newView === MM.MapTypeId.birdseye) { 3329 if (_currentView === MM.MapTypeId.birdseye) { 3330 $(".MicrosoftMap .NavBar_Container .NavBar_MapTypeButtonContainerWrapper") 3331 .css('visibility', 'hidden'); 3332 } else { 3333 $(".MicrosoftMap .NavBar_Container .NavBar_MapTypeButtonContainerWrapper") 3334 .css('visibility', 'visible'); 3335 } 3336 } 3337 //When we switch back from Bird's eye view MapType Container should be visble 3338 else 3339 $(".MicrosoftMap .NavBar_Container .NavBar_MapTypeButtonContainerWrapper") 3340 .css('visibility', 'visible'); 3341 3342 _currentView = newView; 3343 } 3344 3345 // Add menu bar, nav bar, and related events 3346 function addEvents() { 3347 // If the menu bar is resized under 550px, hide the menubar labels 3348 $(_mapContainer).on("widthChanged", onMapWidthChanged); 3349 onMapWidthChanged(); 3350 3351 //resize event no longer supported? 3352 MM.Events.addHandler(_map, "mapresize", function (evt) { 3353 _self.onMapResize(); 3354 }); 3355 3356 // If we click out of the map, close the dialogs 3357 $(document).on('click', checkClick); 3358 3359 MM.Events.addHandler(_map, 3360 "maptypechanged", 3361 onMapTypeChange); 3362 3363 // This will do handy things like close the autocomplete lists 3364 MM.Events.addHandler(_map, "click", function () { 3365 hideAllDialogs(); 3366 document.activeElement.blur(); 3367 }); 3368 if (!options.fixedMap && options.fireOnChange) { 3369 MM.Events.addHandler(_map, "viewchangeend", function () { 3370 // When the map first loads, it fires an event. We have our own 3371 // first load event when the shapes have finished loading, so 3372 // we don't need to call this twice 3373 if (_firstViewChangeEnd) { 3374 _firstViewChangeEnd = false; 3375 return; 3376 } 3377 _self.onChangeView(); 3378 }); 3379 MM.Events.addHandler(_map, "viewchangestart", function () { 3380 // When the map first loads, it fires an event. We have our own 3381 // first load event when the shapes have finished loading, so 3382 // we don't need to call this twice 3383 if (_firstViewChangeEnd) { 3384 _firstViewChangeEnd = false; 3385 return; 3386 } 3387 _self.onChangingView(); 3388 }); 3389 } 3390 // Show or hide the birds eye rotate buttons depending on the current imagery 3391 toggleRotateButtons(); 3392 3393 // The menu events 3394 // Map type selector 3395 $(".mapTypeButtonContainerWrapper", _mapContainer).on("click", showMapTypeMenu); 3396 $("#MapTypeMenu .slot", _mapContainer).on("click", function () { 3397 var mapTypeId = $(this).attr("data-maptypeid"); 3398 _self.setView(mapTypeId); 3399 hideMapTypeMenu(); 3400 return false; 3401 }); 3402 $("#labels-onoff", _mapContainer).change(function () { 3403 var overlay = $(this).is(":checked") 3404 ? MM.LabelOverlay.visible 3405 : MM.LabelOverlay.hidden;
3406 _map.setView({ labelOverlay: overlay }); 3407 }); 3408 3409 $(".map-menuitem .drawIcon", _mapContainer).on("click", showDrawMenu); 3410 $(".map-menuitem .layersIcon", _mapContainer).on("click", showLayersMenu); 3411 $(".map-menuitem .poiIcon", _mapContainer).on("click", showPoiMenu); 3412 $(".map-menuitem .pinsIcon", _mapContainer).on("click", showPinsMenu); 3413 $(".map-dialog-header .close", _mapContainer).on("click", hideMenuDialog); 3414 $(".map-shape-remover", _mapContainer).on("click", removeCustomShape); 3415 3416 // Nav bar events 3417 $("#ZoomInButton", _mapContainer).on("click", zoomIn); 3418 $("#ZoomOutButton", _mapContainer).on("click", zoomOut); 3419 $("#RotateLeftButton", _mapContainer).on("click", rotateLeft); 3420 $("#RotateButton", _mapContainer).on("click", resetOrientation); 3421 $("#RotateRightButton", _mapContainer).on("click", rotateRight); 3422 } 3423 3424 function addNavBar() { 3425 var navbar = "<div id='MapNavBar' class='map-navbar " + getBarColorClass() + "'>" + 3426 "<a id='ZoomInButton' class='map-navbar-button map-navbar-zoomin' href='#' title='" + options.mapText.navBar.zoomIn + "'></a>" + 3427 "<a id='ZoomOutButton' class='map-navbar-button map-navbar-zoomout' href='#' title='" + options.mapText.navBar.zoomOut + "'></a>" + 3428 "<div id='RotateButtonContainer' class='map-navbar-button-container'>" + 3429 "<a id='RotateLeftButton' class='map-navbar-button map-navbar-rotateleft' href='#' title='" + options.mapText.navBar.rotateLeft + "'></a>" + 3430 "<a id='RotateButton' class='map-navbar-button map-navbar-rotate' href='#' title='" + options.mapText.navBar.reset + "'></a>" + 3431 "<a id='RotateRightButton' class='map-navbar-button map-navbar-rotateright' href='#' title='" + options.mapText.navBar.rotateRight + "'></a>" + 3432 "</div>" + 3433 "</div>"; 3434 // $(_mapContainer).prepend(navbar); 3435 } 3436 3437 function addMenuBar() { 3438 // Menu item and menu bar containers 3439 // The bootstrap class is for old legacy sites that don't generally already have bootstrap 3440 var $menubar = $("<div class='map-menubar " + getBarColorClass() + " bootstrap'></div>"); 3441 3442 // Menu dialog box. This is the container for all dialogs, and initially is hidden 3443 $menubar.append("<div class='map-menubar-container map-dialog'>" + 3444 "<div class='map-dialog-panel'>" + 3445 "<div class='map-dialog-header'>" + 3446 "<div class='map-dialog-header-icon'></div>" + 3447 "<div class='map-dialog-header-title map-menu-item'></div>" + 3448 "<button class='close'>Close <span class='fa fa-times'></span></button>" + 3449 "</div>" + 3450 "<div class='map-dialog-body'></div>" + 3451 "</div></div>"); 3452 3453 // The individual menu items 3454 var $menuItems = $("<ul class='map-menuitems map-menubar-container'></ul>"); 3455 $menuItems.append("<li class='map-menuitem'><a href='#' class='drawIcon map-selector'><div class='map-menu-icon map-menu-icon-draw'></div><span class='map-menuitem-label'>" + options.mapText.menuBar.draw + "</span></a></li>"); 3456 $menuItems.append("<li class='map-menuitem'><a href='#' class='layersIcon map-selector'><div class='map-menu-icon map-menu-icon-layers'></div><span class='map-menuitem-label'>" + options.mapText.menuBar.layer + "</span></a></li>"); 3457 $menuItems.append("<li class='map-menuitem'><a href='#' class='poiIcon map-selector'><div class='map-menu-icon map-menu-icon-poi'></div><span class='map-menuitem-label'>" + options.mapText.menuBar.poi + "</span></a></li>"); 3458 // for sm3 sites do not display this pin menu on map 3459 if ((typeof (options.isSM3) !== 'undefined' && !options.isSM3)) { 3460 $menuItems.append("<li class='map-menuitem'><a href='#' class='pinsIcon map-selector'><div class='map-menu-icon map-menu-icon-pin'></div><span class='map-menuitem-label'>" + options.mapText.menuBar.pin + "</span></a></li>"); 3461 } 3462 $menubar.append($menuItems); 3463 3464 // These overlays are used with drawn shapes 3465 $(_mapContainer).append("<div class='map-radius-distance'></div>"); 3466 $(_mapContainer).append("<div class='map-shape-remover'>" + options.mapText.overlayText.shapeRemover + "</div>"); 3467 3468 // The map view selector 3469 var mapTypeName = options.mapText.mapTypeName[getMapTypeId(options.mapView)]; 3470 var mapTypeNameClass = mapTypeName.replace(" ", "").replace("'", ""); 3471 /* 3472 $menubar.append("<div class='mapTypeButtonContainerWrapper map-menubar-container'>" 3473 + "<div id='MapStyleSelector' class='mapTypeContainer mapTypeButtonHeight map-selector'>" 3474 + "<div class='mapTypeButtonDropDownIcon mapTypeButtonHeight selectorIconDropDown'></div>" 3475 + "<div id='MapTypeText' class='mapTypeButtonText mapTypeButtonHeight'><a href='#'>" + mapTypeName + "</a></div>" 3476 + "<div id='MapTypeIcon' class='mapTypeButtonIcon mapTypeButtonHeight selectorIcon" + mapTypeNameClass + "'></div>" 3477 + "</div>" 3478 + "</div>"); 3479 3480 // Map view selector dropdown 3481 var $menu = "<div id='MapTypeMenu' class='map-menubar-dropdown menu'>" 3482 + "<div class='slot Road' data-maptypeid='r'>" 3483 + "<div class='icon'></div><div class='pie-label labelStyle'>" + options.mapText.mapTypeName.r + "</div><div class='pie-label-description labelStyleDescription'>" + options.mapText.mapTypeDescription.r + "</div></div>" 3484 + "<div class='slot Aerial' title='Aerial' data-maptypeid='a'>" 3485 + "<div class='icon'></div><div class='pie-label labelStyle'>" + options.mapText.mapTypeName.a + "</div><div class='pie-label-description labelStyleDescription'>" + options.mapText.mapTypeDescription.a + "</div></div>" 3486 + "<div class='slot Birdseye' title='Bird\'s eye' data-maptypeid='be'>" 3487 + "<div class='icon'></div><div class='pie-label labelStyle'>" + options.mapText.mapTypeName.be + "</div><div class='pie-label-description labelStyleDescription'>" + options.mapText.mapTypeDescription.be + "</div></div>" 3488 + "<div class='switchSlot labelToggle'>" 3489 + "<div class='onoffswitchcontainer'><div class='onoffswitch'>" + 3490 "<input type='checkbox' name='labels-onoff' class='onoffswitch-checkbox' id='labels-onoff' checked='checked' />" 3491 + "<label class='onoffswitch-label' for='labels-onoff'>" 3492 + "<span class='onoffswitch-inner'></span>" 3493 + "<span class='onoffswitch-switch'></span>" 3494 + "</label>" 3495 + "</div></div>" 3496 + "<div class='pie-label labelStyle'>" + options.mapText.mapTypeName.label + "</div><div class='pie-label-description labelStyleDescription'>" + options.mapText.mapTypeDescription.label + "</div></div>" 3497 + "</div>" 3498 + "</div>"; 3499 $menubar.append($menu); 3500 */ 3501 3502 $("#MapTypeMenu ." + mapTypeNameClass, _mapContainer).addClass("maptype-selected"); 3503 3504 $(_mapContainer).append($menubar); 3505 $("div.map-menubar .onoffswitch-inner").attr('data-content-on', Utils.translate('ON')); 3506 $("div.map-menubar .onoffswitch-inner").attr('data-content-off', Utils.translate('OFF')); 3507 showHideMenuItems($menuItems); 3508 3509 toggleMapLabelsSelector(); 3510 } 3511 3512 function initDmpConnection(dmpKey) { 3513 rbw.utils.wait.until(function () { 3514 return window.dmpLoaded; 3515 }).then(function () { 3516 // If for some reason Dmp is not available, just get out 3517 if (typeof Dmp === "undefined" || typeof Dmp.Map === "undefined") { 3518 return; 3519 } 3520 // This never calls the callback if the key is bad 3521 Dmp.Map.init(_map); 3522 Dmp.Env.Connections["SS"].init(dmpKey, 3523 function () { 3524 _dmpInitState = 2; 3525 // This adds the parcel lines layer and makes it visible 3526 addDmpLayer(0, false); 3527 }); 3528 if (_dmpInitState === 0) { 3529 setTimeout(function () { 3530 if (_dmpInitState === 0) { 3531 eraseCookie("dmp-key"); 3532 initializeDmpLayers(); 3533 } 3534 }, 3535 2000); 3536 } 3537 });
3538 } 3539 3540 function initializeDmpLayers() { 3541 // This makes the current traffic layer available 3542 MM.loadModule('Microsoft.Maps.Traffic', { 3543 callback: function () { 3544 _trafficManager = new MM.Traffic.TrafficManager(_map); 3545 } 3546 }); 3547 3548 function loadDmp(dmpKey, dontSetDmpCookie) { 3549 if (dmpKey && dmpKey.length) { 3550 if (!dontSetDmpCookie) { 3551 createCookieInSeconds("dmp-key", dmpKey, 1200); 3552 _dmpInitState = 1; 3553 } 3554 if (typeof window.Dmp !== "undefined") { 3555 window.dmpLoaded = true; 3556 window.dmpLoadStarted = true; 3557 } 3558 if (window.dmpLoadStarted && !window.ldpOverlayVisible) { 3559 initDmpConnection(dmpKey); 3560 } else { 3561 window.dmpLoadStarted = true; 3562 rbw.utils.wait.until(function () { 3563 return _self.isLoaded(); 3564 }).then(function () { 3565 var script = document.createElement("script"); 3566 script.type = "text/javascript"; 3567 script.async = true; 3568 script.defer = true; 3569 script.src = "/include/js/mapsearch/DmpApi_BingV8.js?_=" + rbw.utils.version; 3570 script.onload = function () { 3571 window.dmpLoaded = true; 3572 initDmpConnection(dmpKey); 3573 }; 3574 script.onerror = function () { 3575 Utils.logger.warn("Error loading DMP"); 3576 }; 3577 document.body.appendChild(script); 3578 }); 3579 } 3580 } 3581 } 3582 // Get the Dmp key. Try from the cookie first to cut down 3583 // on server transactions 3584 var dmpCookie = readCookie("dmp-key"); 3585 if (dmpCookie && dmpCookie.length > 0) { 3586 dmpCookie = unescape(dmpCookie); 3587 loadDmp(dmpCookie, true); 3588 } else { 3589 $.ajax({
3590 type: "GET", 3591 url: Utils.AppPath + "/include/ajax/api.aspx", 3592 data: "op=GetDmpApiKey", 3593 dataType: "json", 3594 contentType: "application/json; charset=utf-8", 3595 success: function (data) { 3596 loadDmp(data, false); 3597 } 3598 }); 3599 } 3600 } 3601 3602 function initializePoiLayers() { 3603 for (var i = 0; i < BingMap.PinInfo.length; i++) { 3604 var pinInfo = BingMap.PinInfo[i]; 3605 if (pinInfo.PinClass && pinInfo.PinClass.indexOf("poi-") === 0 && pinInfo.Disabled !== true) { 3606 // Pre-load the pin layer and initialize 3607 // them all to hidden for the menu checkboxes 3608 getLayer(pinInfo.ShortName, false, LayerType.Poi, false); 3609 } 3610 } 3611 } 3612 3613 function addInitialShapes() { 3614 var dontSetMapView; 3615 // if poly params not empty draw polygon on load 3616 if (options.startingLocations && options.startingLocations.length > 0) { 3617 var names = searchLocation.getFlattenedLocations("name"); 3618 var types = searchLocation.getFlattenedLocations("type"); 3619 var values = searchLocation.getFlattenedLocations("value"); 3620 for (var i = 0; i < names.length; i++) { 3621 var polyName = names[i]; 3622 var polyType = types[i].toLowerCase(); 3623 if (polyType === "zip code") { 3624 polyType = "zip"; 3625 } 3626 var polyVal = values[i]; 3627 if (!polyVal || !polyVal.length) { 3628 polyVal = polyName; 3629 } 3630 3631 // If this is neighborhood or points, we'll do it immediately, otherwise 3632 // we'll defer until after search execution. This is because complex 3633 // polygons can really delay things. 3634 if (!polyType || polyType === "radius" || polyType === "neighborhood" || polyType === "township" || polyType === "borough" || polyType === "points" || options.container === "community-main-map") { 3635 // Get the latest boundary count from the drawn boundaries 3636 if (polyName.indexOf("Drawn ") === 0 || polyName.indexOf("Radius ") === 0 || polyName.indexOf("Rectangle ") === 0) { 3637 var polyNameWords = polyName.split(" "); 3638 var boundaryIndex = parseInt(polyNameWords[polyNameWords.length - 1]); 3639 if (boundaryIndex >= _boundaryCount) { 3640 _boundaryCount = boundaryIndex + 1; 3641 } 3642 } 3643 _initialShapes[polyName] = false; 3644 _self.gotoPoly(polyName, polyType, polyVal, true); 3645 } else { 3646 _initialShapes[polyName] = true; 3647 parent.gotoPolyDeferred(polyName, polyType, polyVal, true); 3648 } 3649 } 3650 } else if (options.startingPolyValue && options.startingPolyValue !== "") { 3651 // If this is neighborhood or points, we'll do it immediately, otherwise 3652 // we'll defer until after search execution. This is because complex 3653 // polygons can really delay things. 3654 if (!options.startingPolyType || options.startingPolyType === "" || options.startingPolyType === "neighborhood" || options.startingPolyType === "township" || options.startingPolyType === "borough" || options.startingPolyType === "points" || options.container === "community-main-map") { 3655 // If this is the default map view, set the map view 3656 dontSetMapView = typeof options.defaultBoundingBox !== "undefined" && 3657 options.defaultBoundingBox !== null && 3658 JSON.stringify(options.startingBoundingBox) !== JSON.stringify(options.defaultBoundingBox); 3659 _initialShapes[options.startingPolyValue] = false; 3660 _self.gotoPoly(options.startingPolyValue, options.startingPolyType, options.startingPolyValue, dontSetMapView); 3661 } else { 3662 _initialShapes[options.startingPolyValue] = true; 3663 parent.gotoPolyDeferred(options.startingPolyValue, options.startingPolyType, options.startingPolyValue); 3664 } 3665 } 3666 } 3667 3668 // Thisdoes the very first onChangeView call when the map is loaded, 3669 // to initiate the search. 3670 // It is called in a loop until all of the initial shapes (cities, 3671 // drawn shapes, etc) are loaded on the map. 3672 var firstOnChangeViewTries = 0; 3673 function firstOnChangeView() { 3674 return new Promise(function (resolve) { 3675 if (_initialShapesTimer !== null) { 3676 clearTimeout(_initialShapesTimer); 3677 _initialShapesTimer = null; 3678 } 3679 3680 var initialShapesAreLoaded = true; 3681 for (var polyName in _initialShapes) { 3682 if (_initialShapes.hasOwnProperty(polyName)) { 3683 if (_initialShapes[polyName] === false) { 3684 initialShapesAreLoaded = false; 3685 break; 3686 } 3687 } 3688 } 3689 // All shapes are loaded, call onChangeView 3690 if (initialShapesAreLoaded) { 3691 _loadingInitialShapes = false; 3692 return resolve(_self.onChangeView()); 3693 } 3694 // If we got here, then there are some shapes that aren't loaded, 3695 // so try again in 200ms. If we've tried for 1 second, 3696 // go ahead anyway. 3697 if (firstOnChangeViewTries++ > 4) { 3698 return resolve(_self.onChangeView()); 3699 } 3700 _initialShapesTimer = setTimeout(firstOnChangeView, 500); 3701 });
3702 } 3703 3704 function loadMap() { 3705 var customMapStyle = undefined; 3706 if (typeof options.customMapStyle === "string") { 3707 customMapStyle = customMapStyles[options.customMapStyle]; 3708 } else if (typeof options.customMapStyle === "object") { 3709 customMapStyle = options.customMapStyle; 3710 } 3711 3712 var mapLoadOpts = { 3713 credentials: Utils.BingMapKey, 3714 mapTypeId: getMapTypeId(options.mapView), 3715 showLocateMeButton: false, 3716 showMapTypeSelector: options.showMapTypeSelector, 3717 enableSearchLogo: false, 3718 enableClickableLogo: false, 3719 disableMapTypeSelectorMouseOver: true, 3720 disableScrollWheelZoom: options.disableMouseWheel, 3721 disableStreetside: options.disableStreetside, 3722 customMapStyle: customMapStyle, 3723 minZoom: options.minZoom 3724 }; 3725 3726 var isDefaultMapView = typeof options.startingBoundingBox !== "undefined" && options.startingBoundingBox !== null 3727 && typeof options.defaultBoundingBox !== "undefined" && options.defaultBoundingBox !== null 3728 && JSON.stringify(options.startingBoundingBox) === JSON.stringify(options.defaultBoundingBox); 3729 3730 // Use boundary to load map if it for default map view, else you have to use map center. See story for details. 3731 if (isDefaultMapView) { 3732 mapLoadOpts.bounds = MM.LocationRect.fromEdges( 3733 options.defaultBoundingBox.NeLat, 3734 options.defaultBoundingBox.SwLon, 3735 options.defaultBoundingBox.SwLat, 3736 options.defaultBoundingBox.NeLon 3737 ); 3738 } else { 3739 mapLoadOpts.center = new MM.Location(options.startingMidLat, options.startingMidLong); 3740 mapLoadOpts.zoom = options.startingZoom; 3741 } 3742 3743 _map = new MM.Map(_mapContainer, mapLoadOpts); 3744 } 3745 3746 /* Start of externally visible functions */ 3747 3748 this.onMapResize = function () { 3749 var center = _map.getCenter(); 3750 var rect = _map.getBounds(); 3751 _mapState = { 3752 Lat: center.latitude, 3753 Lon: center.longitude, 3754 NELat: rect.getNorth(), 3755 NELon: rect.getEast(), 3756 SWLat: rect.getSouth(), 3757 SWLon: rect.getWest(), 3758 Zoom: _map.getZoom(), 3759 Style: _map.getMapTypeId(), 3760 MapH: _mapH, 3761 MapW: _mapW 3762 }; 3763 } 3764 3765 this.onChangingView = function () { 3766 $(_mapContainer).trigger("mapchanging", null); 3767 }; 3768 3769 this.onChangeView = function () { 3770 if (_map == null) return; 3771 3772 var mapMoveOrigin = _mapMoveOrigin; 3773 _mapMoveOrigin = null; 3774 3775 hideAllDialogs(); 3776 3777 if (mapMoveOrigin === "Resize") { 3778 return; 3779 } 3780 3781 var oldMapState = _mapState; 3782 var center = _map.getCenter(); 3783 var rect = _map.getBounds(); 3784 _mapState = { 3785 Lat: center.latitude, 3786 Lon: center.longitude, 3787 NELat: rect.getNorth(), 3788 NELon: rect.getEast(), 3789 SWLat: rect.getSouth(), 3790 SWLon: rect.getWest(), 3791 Zoom: _map.getZoom(), 3792 Style: _map.getMapTypeId(), 3793 MapW: $(_mapContainer).outerWidth(), 3794 MapH: $(_mapContainer).outerHeight() 3795 }; 3796 3797 _loaded = true; 3798 $(_mapContainer).trigger("mapchange", [mapMoveOrigin]); 3799 3800 if (typeof _mapMoveCallback === "function" && _mapMoveCallback != null) { 3801 _mapMoveCallback(); 3802 _mapMoveCallback = null; 3803 } 3804 3805 // Update POI 3806 if (mapMoveOrigin !== "SetMapView" && options.poiEnabled) { 3807 updatePoi(true); 3808 } 3809 // Update office pins 3810 if (mapMoveOrigin !== "SetMapView" && options.getOfficePins) { 3811 parent.updateOffice(); 3812 } 3813 3814 // Update Valuation pins 3815 if (typeof (options.isSM3) !== 'undefined' && options.isSM3) { 3816 updateValuations(); 3817 } 3818 }; 3819 3820 this.isLoaded = function () { 3821 return _loaded; 3822 }; 3823 3824 function enablePricePins() { 3825 return (typeof (options.enablePricePins) !== 'undefined' && options.enablePricePins); 3826 } 3827 3828 function enableMyNeighborhoodPins() { 3829 return (typeof (options.enableMyNeighborhoodPins) !== 'undefined' && options.enableMyNeighborhoodPins); 3830 } 3831 3832 function isLayerNameForPricePins(layerName) { 3833 var layerNamesForPricePins = [ 3834 'Property', 3835 'Open House Available', 3836 'Sale Pending', 3837 'Sold Property', 3838 'New Property', 3839 'Commercial Property', 3840 'Commercial Sale Pending', 3841 'Sold Commercial', 3842 'Coming Soon', 3843 'Auction', 3844 'Multi Listing', 3845 'Rent' 3846 ]; 3847 3848 return layerNamesForPricePins.indexOf(layerName) > -1; 3849 } 3850 3851 function isSupportedHtmlPushpin(layerName, layerType) { 3852 return (typeof layerType === 'undefined' || layerType !== LayerType.Cluster) && 3853 (enablePricePins() && isLayerNameForPricePins(layerName) || enableMyNeighborhoodPins()); 3854 } 3855 3856 this.resize = function (w, h) { 3857 if (typeof h === "undefined") { 3858 h = _mapH; 3859 } 3860 3861 if (w === 0 || h === 0) { 3862 return; 3863 } 3864 3865 if (w !== _mapW || h !== _mapH) { 3866 _mapW = w; 3867 _mapH = h; 3868 3869 var currCenter = _map.getCenter(); 3870 // This will cause the first move to not search 3871 // The second one will 3872 _mapMoveOrigin = "Resize"; 3873 $(_mapContainer).width(w); 3874 $(_mapContainer).height(h);
3875 _map.setView({ center: currCenter }); 3876 } 3877 }; 3878 3879 this.dispose = function () { 3880 if (_map) { 3881 try { 3882 _map.dispose(); 3883 } catch (e) { 3884 // Just ignore any errors on dispose 3885 } 3886 } 3887 _loaded = false; 3888 $(document).off('click', checkClick); 3889 }; 3890 3891 this.setMapExtent = function (boundingBox) { 3892 if (!boundingBox) { 3893 Utils.Logger.warn("Bounding box is null"); 3894 return; 3895 } 3896 // If this is getting called from a single pin being centered, leave the 3897 // _mapMoveOrigin alone 3898 if (_mapMoveOrigin !== "SetMapView") { 3899 _mapMoveOrigin = "SetCenterAndZoom"; 3900 } 3901 3902 var rect = MM.LocationRect.fromEdges(boundingBox.NeLat, 3903 boundingBox.SwLon, 3904 boundingBox.SwLat, 3905 boundingBox.NeLon); 3906 _map.setView({ bounds: rect, padding: 35 }); 3907 3908 // Sometimes the map change doesn't fire an event 3909 setTimeout(function () { 3910 if (_mapMoveOrigin === "SetCenterAndZoom") { 3911 _mapMoveOrigin = null; 3912 _self.onChangeView(); 3913 } 3914 }, 500); 3915 } 3916 3917 this.setCenterAndZoom = function (lat, lon, zoom) { 3918 // If this is getting called from a single pin being centered, leave the 3919 // _mapMoveOrigin alone 3920 if (_mapMoveOrigin !== "SetMapView") { 3921 _mapMoveOrigin = "SetCenterAndZoom"; 3922 }
3923 _map.setView({ 3924 zoom: zoom, 3925 center: new MM.Location(lat, lon) 3926 }); 3927 // Sometimes the map change doesn't fire an event 3928 setTimeout(function () { 3929 if (_mapMoveOrigin === "SetCenterAndZoom") { 3930 _mapMoveOrigin = null; 3931 _self.onChangeView(); 3932 } 3933 }, 500); 3934 }; 3935 3936 this.setZoomLevel = function (zoom) { 3937 var range = _map.getZoomRange(); 3938 if (zoom >= range.min && zoom <= range.max) { 3939 _map.setView({ zoom: zoom }); 3940 } 3941 }; 3942 3943 this.setView = function (view) { 3944 var oldView = _map.getMapTypeId(); 3945 var newView = getMapTypeId(view); 3946 if (newView !== oldView) { 3947 // Set the view 3948 if (newView === "be") { 3949 switchToBirdseye(); 3950 } else { 3951 _map.setMapType(getMapTypeId(newView)); 3952 } 3953 // Update the menu selector 3954 // updateMapTypeMenu(newView); 3955 } 3956 if (_mapState) { 3957 _mapState.Style = newView; 3958 } 3959 }; 3960 3961 this.getMapState = function () { 3962 return _mapState; 3963 }; 3964 3965 // rather than creating a single layer per the requested layerName, 3966 // we create a "collection" linking all the pins to be added and 3967 // maintaining the actual layers to which they are linked 3968 this.startAddingPins = function (layerName, cluster) { 3969 var collection = getLayerCollection(layerName); 3970 clearCollectionLayer(collection); 3971 collection.cluster = cluster; 3972 } 3973 3974 this.addPin = function (layerName, id, pinLat, pinLong, pinType, pinTitle, pinDesc, pinPrice, pinIsFavorite, pinsInCluster, northLat, southLat, westLon, eastLon, avgPrice, minPrice, maxPrice, pinImageUrl, isVisible = true) { 3975 var collection = getLayerCollection(layerName); 3976 var clusterPin = typeof northLat !== 'undefined'; 3977 layerName = clusterPin ? 'ClusterPins' : translatePinTypeToLayerName(pinType, layerName); 3978 var pinLayer = getLayer(layerName, collection.cluster); 3979 var pinClass = ""; 3980 3981 var pin = { 3982 id: id, 3983 latitude: pinLat, 3984 longitude: pinLong, 3985 pinType: pinType, 3986 pinClass: pinClass, 3987 pinTitle: pinTitle, 3988 pinDesc: pinDesc, 3989 pinPrice: pinPrice, 3990 pinIsFavorite: pinIsFavorite, 3991 pinsInCluster: pinsInCluster, 3992 pinImageUrl: pinImageUrl, 3993 isVisible: isVisible 3994 }; 3995 3996 // if it's a cluster, add the additional data 3997 if (clusterPin) { 3998 pin.northLat = northLat; 3999 pin.southLat = southLat; 4000 pin.westLon = westLon; 4001 pin.eastLon = eastLon; 4002 pin.avgPrice = avgPrice; 4003 pin.minPrice = minPrice; 4004 pin.maxPrice = maxPrice; 4005 // Must come after the prices are set 4006 pin.pinDesc = getBubbleHtmlCluster(pin); 4007 } 4008 4009 pinLayer.pins.push(pin); 4010 4011 _pinIdLayers[id] = pinLayer; 4012 4013 collection.pins.push(pin); 4014 collection.pinIds[id] = pin; 4015 4016 if (typeof collection.layers[layerName] === "undefined") 4017 collection.layers[layerName] = pinLayer; 4018 } 4019 4020 this.endAddingPins = function (layerName, zoomMapToThisPinCollection, includeShapesInZoom) { 4021 var collection = getLayerCollection(layerName); 4022
4023 Object.keys(collection.layers).forEach(function (innerLayerName) { 4024 var pinLayer = collection.layers[innerLayerName]; 4025 try { 4026 var useCustomClustering = 4027 (layerName === 'Properties' || (pinLayer.cluster && !zoomMapToThisPinCollection)) && 4028 pinLayer.name !== 'ClusterPins' && 4029 !isSupportedHtmlPushpin(pinLayer.name); 4030 4031 if (useCustomClustering) { 4032 pinLayer.pins = getClusteredPins(pinLayer.pins); 4033 } 4034 4035 if (pinLayer.pins.length > 0) { 4036 if (pinLayer.name === 'ClusterPins') { 4037 displayPinsWithClusters(pinLayer); 4038 } else { 4039 displayPins(pinLayer); 4040 } 4041 } 4042 } catch (e) { 4043 Utils.Logger.warn("Error adding " + layerName + " pins to map: " + e.message); 4044 } 4045 }); 4046 4047 if (zoomMapToThisPinCollection) { 4048 // <strike>cannot fit map to pins on any given individual layer; pass null 4049 // so it will fit to all pins on all layers</strike> 4050 // Passing the pins since null created BROKER-2855 4051 if (typeof includeShapesInZoom === 'undefined') includeShapesInZoom = true; 4052 _self.fitMapToPins(collection.pins, includeShapesInZoom, true); 4053 } 4054 4055 // When pins are loaded for the first time, one of the MS Canvas overlay elements 4056 // have a zIndex that is greater than the HTMLPushpins zIndex, until the mouse is zoomed into or resized. 4057 // This causes pin pointer events to not work. Set zindex of the canvas element to 0 as a workaround. 4058 var mapOverlayCanvas = $(".labelCanvas").prev("canvas"); 4059 if(mapOverlayCanvas.length) { 4060 mapOverlayCanvas.css("zIndex", 0); 4061 } 4062 } 4063 4064 /** 4065 * This removes pins which are located at the same cordinates and adds a single multi pin 4066 * so that on hovering over use may see all of the available properties at that location 4067 * 4068 * This should be called after adding all pins and before calling endAddingPins() methods. 4069 * 4070 * Intended to use with 'Property' top layer. 4071 * @param {any} layerName 4072 */ 4073 this.collocatePins = function (layerName) { 4074 /** 4075 * The evaluates two pins, whether or not 4076 * should they collapse to one multi pin. 4077 * @param {any} pin1 First pin,. 4078 * @param {any} pin2 Second pin. 4079 * @returns true, if give pin's cordinates should collapse to single multi property pin, otherwise, false. 4080 */ 4081 let isSameLocation = function (pin1, pin2) { 4082 // Breaking down conditions in multiple statements for better understanding. 4083 if (!pin1 || !pin2) 4084 return false; 4085 4086 if (!pin1.longitude || !pin1.latitude) 4087 return false; 4088 4089 if (!pin2.longitude || !pin2.latitude) 4090 return false; 4091 4092 if (pin1.longitude === pin2.longitude && pin1.latitude == pin2.latitude) 4093 return true; 4094 4095 if ((pin1.longitude.toString().contains(pin2.longitude.toString()) || pin2.longitude.toString().contains(pin1.longitude.toString())) 4096 && (pin1.latitude.toString().contains(pin2.latitude.toString()) || pin2.latitude.toString().contains(pin1.latitude.toString()))) 4097 return true; 4098 4099 return false; 4100 }; 4101 4102 /** 4103 * Find and removes specified pin from the layer which contains it within layerCollection. 4104 * @param {any} layerCollection 4105 * @param {any} pinId 4106 * @param {any} pinType 4107 * @returns 4108 */ 4109 let removePin = function (layerCollection, pinId, pinType) { 4110 let layerName = translatePinTypeToLayerName(pinType); 4111 let subLayer = layerCollection.layers[layerName]; 4112 4113 if (typeof subLayer === 'undefined') 4114 return; 4115 4116 // Remove this item from the layer which contains this pin. 4117 let index; 4118 while ((index = subLayer.pins.findIndex(item => item.id === pinId)) >= 0) { 4119 subLayer.pins.splice(index, 1); 4120 } 4121 4122 if (typeof _pinIdLayers[pinId] !== 'undefined') 4123 delete _pinIdLayers[pinId]; 4124 } 4125 4126 let multiPropertyLayerName = 'Multi Listing';
4127 let collection = getLayerCollection(layerName); 4128 4129 let pinLayer = getLayer(multiPropertyLayerName, false, LayerType.Pin, true); 4130 let matchingPins = []; 4131 4132 for (let i = 0; i < collection.pins.length; i++) { 4133 if (matchingPins.some(value => value.pins.find(w => w.id === collection.pins[i].id))) 4134 continue; 4135 4136 let match = collection.pins.filter(t => 4137 //t.id !== collection.pins[i].id && 4138 isSameLocation(t, collection.pins[i])); 4139 4140 if (match.length > 1) { 4141 match.forEach(v => { 4142 removePin(collection, v.id, v.pinType); 4143 _pinIdLayers[v.id] = pinLayer; 4144 }); 4145 4146 matchingPins.push({ 4147 longitude: collection.pins[i].longitude, 4148 latitude: collection.pins[i].latitude, 4149 ids: match.map(value => value.id), 4150 pins: match 4151 }); 4152 } 4153 } 4154 4155 let pins = matchingPins.map(function (v) { 4156 let pin = { 4157 id: v.pins.map(value => value.id).join('-'), 4158 latitude: v.latitude, 4159 longitude: v.longitude, 4160 pinClass: '', 4161 pinImageUrl: null, 4162 pinDesc: null, 4163 pinIsFavorite: false, 4164 pinPrice: null, 4165 pinTitle: null, 4166 pinType: BingMap.PinType.ListingMultiple, 4167 pinInCluster: null 4168 }; 4169 return pin; 4170 }); 4171 4172 pinLayer.pins = pins; 4173 4174 if (typeof collection.layers[multiPropertyLayerName] === "undefined") 4175 collection.layers[multiPropertyLayerName] = pinLayer; 4176 } 4177 4178 this.setPropertiesPinCount = function (count) { 4179 _totalPropertiesPinCount = count; 4180 _enableClusteringForPricePins = _totalPropertiesPinCount > _maxPricePinsToRender; 4181 } 4182 4183 this.clearPolygon = function (polyName, change) { 4184 delete _shapes[polyName]; 4185 if (_boundaryShapeLayerCount > 0) _boundaryShapeLayerCount--; 4186 clearLayer(getLayer(polyName, false)); 4187 if (change) { 4188 _self.onChangeView(); 4189 } 4190 }; 4191 4192 this.clearPolygons = function (change) { 4193 for (var shape in _shapes) { 4194 if (_shapes.hasOwnProperty(shape)) { 4195 _self.clearPolygon(shape, false); 4196 } 4197 } 4198 if (change) { 4199 _self.onChangeView(); 4200 } 4201 }; 4202 4203 this.gotoPoly = function (polyName, polyType, polyValue, dontSetMapView, dontExecSearch) {
4204 if (typeof polyName === "undefined" || typeof polyType === "undefined" || polyName == null || polyType == null || polyName.length === 0 || polyType.length === 0) { 4205 return; 4206 } 4207 if (typeof polyValue === "undefined" || polyValue == null || polyValue.length === 0) { 4208 polyValue = polyName; 4209 } 4210 _addingShapes[polyName] = true; 4211 addShapes(getLayer(polyName, false, LayerType.Shape), polyType, polyValue, function (shapes) { 4212 _initialShapes[polyName] = true; 4213 if (!dontSetMapView) { 4214 _mapMoveOrigin = "GotoPoly"; 4215 if (shapes && shapes.length > 0) { 4216 // position map over the drawn boundary. this will submit the search too, if the map moves 4217 var shapePoints = []; 4218 for (var i = 0; i < shapes.length; i++) { 4219 $.merge(shapePoints, shapes[i].getLocations()); 4220 } 4221 setMapViewBoundaries(shapePoints); 4222 } else { 4223 gotoLocation(polyValue); 4224 } 4225 // Sometimes the map change doesn't fire an event 4226 setTimeout(function () { 4227 if (_mapMoveOrigin === "GotoPoly") { 4228 _mapMoveOrigin = null; 4229 $(_mapContainer).trigger("mapchange"); 4230 } 4231 }, 500); 4232 } else { 4233 if (!dontExecSearch) { 4234 _mapMoveOrigin = "GotoPoly"; 4235 _self.onChangeView(); 4236 } 4237 } 4238 delete _addingShapes[polyName]; 4239 }); 4240 }; 4241 4242 this.showListingBubble = function (bubbleId) { 4243 4244 var collection = getLayerCollection("Properties"); 4245 4246 for (var innerLayerName in collection.layers) { 4247 // Get the clustered pin that includes this ID 4248 var pinLayer = getLayer(innerLayerName); 4249 4250 if (pinLayer.layerModule === LayerModuleType.ClusteredModule || pinLayer.name === "ClusterPins" || !pinLayer.isVisible) { 4251 continue; // not valid for Clustered modules 4252 } 4253 4254 var pinIndex = 0; 4255 var foundBubble = false; 4256 var pinToShow = undefined; 4257 var layerPins = pinLayer.layerModule === LayerModuleType.HtmlPushpinModule ? pinLayer.layer.getPushpins() : pinLayer.layer.getPrimitives(); 4258 var layerCount = layerPins.length; 4259 for (var i = 0; i < layerCount; i++) { 4260 var pin = layerPins[i]; 4261 var pinIdSplit = pin.id.split('-'); 4262 for (var j = 0; j < pinIdSplit.length; j++) { 4263 if (pinIdSplit[j] === bubbleId) { 4264 pinToShow = pin; 4265 pinIndex = j; 4266 foundBubble = true; 4267 break; 4268 } 4269 } 4270 if (foundBubble) { 4271 break; 4272 } 4273 } 4274 try { 4275 // Setting the visible bubble 4276 if (typeof pinToShow !== "undefined") { 4277 _visibleBubblePosition = pinIndex; 4278 showBubble(pinToShow); 4279 break; 4280 } 4281 } catch (ex) { 4282 console.log(ex.message); 4283 } 4284 } 4285 } 4286 // Applicable to Listing bubbles and Valuation bubbles (the assessor info hover data) 4287 this.hideListingBubble = function () { 4288 if (typeof _infoBox === "undefined" || _infoBox == null) return; 4289 _infoBox.setOptions({ visible: false }); 4290 _visibleBubblePosition = 0; 4291 $("div.map-menubar", _mapContainer).slideDown(250); 4292 $("div.map-menubar", _mapContainer).css("overflow", "visible"); 4293 // Ensure zoom buttons are visible. Pick the correct #MapNavBar (i.e., not the one from Public Records overlay). 4294 $("#MapNavBar", "#mapsearch-mainmap").show(); 4295 }; 4296 4297 this.showAllPins = function (checkIsVisible) { 4298 for (var layerName in _layers) { 4299 if (_layers.hasOwnProperty(layerName)) { 4300 var layer = getLayer(layerName); 4301 if (layer.layerType === LayerType.Pin) { 4302 _self.showPins(layerName, checkIsVisible); 4303 } 4304 } 4305 } 4306 }; 4307 4308 this.hideAllPins = function (setIsVisible) { 4309 for (var layerName in _layers) { 4310 if (_layers.hasOwnProperty(layerName)) { 4311 var layer = getLayer(layerName); 4312 if (layer.layerType === LayerType.Pin) { 4313 _self.hidePins(layerName, setIsVisible); 4314 } 4315 } 4316 } 4317 }; 4318
4319 this.hidePins = function (layerName, setIsVisible) { 4320 setIsVisible = typeof setIsVisible === "undefined" || setIsVisible; 4321 var pinLayer = getLayer(layerName); 4322 if (pinLayer.isVisible && pinLayer.layer && typeof pinLayer.layer.setVisible === 'function') { 4323 pinLayer.layer.setVisible(false); 4324 } 4325 if (setIsVisible) { 4326 pinLayer.isVisible = false; 4327 } 4328 }; 4329 4330 this.showPins = function (layerName, checkIsVisible) { 4331 var layer = getLayer(layerName); 4332 var show = (checkIsVisible && layer.isVisible) || (!checkIsVisible); 4333 if (show && layer.layer && typeof layer.layer.setVisible === 'function') { 4334 layer.layer.setVisible(true); 4335 layer.isVisible = true; 4336 } 4337 }; 4338 4339 this.pinsAreVisible = function (layerName) { 4340 var pinLayer = getLayer(layerName); 4341 return pinLayer.isVisible; 4342 }; 4343 4344 this.deleteAllPins = function (layerName) { 4345 var collection = getLayerCollection(layerName); 4346 clearCollectionLayer(collection); 4347 }; 4348 4349 this.hidePOI = function (poiType) { 4350 _self.hidePins(poiType); 4351 }; 4352 4353 this.showPOI = function (poiType) { 4354 var layer = getLayer(poiType, false); 4355 _self.showPins(poiType); 4356 if (layer.pins.length === 0) { 4357 updatePoi(false); 4358 } 4359 }; 4360 4361 this.hideAllPOI = function (shouldHide) { 4362 if (shouldHide) $("ul.map-poi-list input[type='checkbox']").prop("checked", false); 4363 $.each(_layers, function (layerName, layer) { 4364 if (layer.layerType === LayerType.Poi) { 4365 _self.hidePins(layerName, shouldHide); 4366 } 4367 }); 4368 } 4369 4370 this.showAllPOI = function () { 4371 var update = false; 4372 $("ul.map-poi-list input[type='checkbox']").prop("checked", true); 4373 $.each(_layers, function (layerName, layer) { 4374 if (layer.layerType === LayerType.Poi) { 4375 _self.showPins(layerName); 4376 if (layer.pins && layer.pins.length === 0) { 4377 update = true; 4378 } 4379 } 4380 }); 4381 if (update) { 4382 updatePoi(false); 4383 } 4384 } 4385 4386 this.addAddressPin = function (address) { 4387 if (address.indexOf(", USA") === -1 && address.indexOf("mx") === -1) 4388 address += ", USA"; 4389 gotoLocation(address, true); 4390 }; 4391 4392 this.addLocationPin = function (latitude, longitude, address, zoom) { 4393 if (address.indexOf(", USA") === -1 && address.indexOf("mx") === -1) 4394 address += ", USA"; 4395 _self.startAddingPins("Address", false); 4396 _self.addPin("Address", "1", latitude, longitude, BingMap.PinType.Address, "", address); 4397 _self.endAddingPins("Address", true); 4398 if (zoom) { 4399 _self.setZoomLevel(zoom); 4400 } 4401 }; 4402 4403 this.plotComps = function (latLons) { 4404 var arrLatLon = latLons.split(","); 4405 var len = arrLatLon.length; 4406 var latLon, lat, lon; 4407 _self.startAddingPins("Comps", false); 4408 for (var i = 0; i < len; i++) { 4409 if (arrLatLon[i] === "") { 4410 continue; 4411 } 4412 latLon = arrLatLon[i].split(" "); 4413 lat = latLon[0]; 4414 lon = latLon[1]; 4415 if (lat !== "" && lon !== "" && lat !== "undefined" && lon !== "undefined") { 4416 _self.addPin("Comps", "comp_" + i, lat, lon, BingMap.PinType["listing_" + (i + 1)], "", ""); 4417 } 4418 } 4419 _self.endAddingPins("Comps", true); 4420 _self.showPins("Comps"); 4421 }; 4422 4423 this.fitMapToPins = function (pins, includeShapes) { 4424 // Make sure the asynchronous shapes have all loaded. Retry on delay 4425 if (_addingShapesTimer !== null) { 4426 clearTimeout(_addingShapesTimer); 4427 _addingShapesTimer = null; 4428 } 4429 if (includeShapes === true && Object.keys(_addingShapes).length > 0 && _addingShapesTryCount++ < 30) { 4430 _addingShapesTimer = setTimeout(function () { 4431 _self.fitMapToPins(pins, includeShapes); 4432 }, 50); 4433 return; 4434 } 4435 _addingShapesTryCount = 0; 4436 _mapMoveOrigin = "SetMapView"; 4437 4438 // If no pins passed in, then use all current pins 4439 if (typeof pins === "undefined" || pins === null) { 4440 pins = []; 4441 for (var pinLayer in _layers) { 4442 if (_layers.hasOwnProperty(pinLayer)) { 4443 var layer = _layers[pinLayer]; 4444 if (layer.isVisible && layer.layerType === LayerType.Pin) { 4445 pins = pins.concat(layer.pins); 4446 } 4447 } 4448 } 4449 } 4450 4451 // If we didn't get any pins, try to zoom to fit the current 4452 // state of the location criteria 4453 if (pins.length === 0) { 4454 if (includeShapes === true && Object.keys(_shapes).length > 0) { 4455 var shapePoints = []; 4456 for (var key in _shapes) { 4457 if (_shapes.hasOwnProperty(key)) { 4458 for (var shapeIdx = 0; shapeIdx < _shapes[key].length; shapeIdx++) { 4459 shapePoints = shapePoints.concat(_shapes[key][shapeIdx].getLocations()); 4460 } 4461 } 4462 } 4463 setMapViewBoundaries(shapePoints); 4464 } 4465 } else { 4466 // Set the map view to the max of the pins and shapes 4467 var mapViewPins = pins.slice(0); 4468 if (includeShapes && Object.keys(_shapes).length > 0) { 4469 for (var s in _shapes) { 4470 if (_shapes.hasOwnProperty(s)) { 4471 for (var i = 0; i < _shapes[s].length; i++) { 4472 mapViewPins = mapViewPins.concat(_shapes[s][i].getLocations()); 4473 } 4474 } 4475 } 4476 } 4477 setMapViewBoundaries(mapViewPins);
4478 } 4479 } 4480 4481 this.gotoLocation = function (loc, addPin, zoom) { 4482 gotoLocation(loc, addPin, zoom); 4483 } 4484 4485 this.setOptions = function (opts) { 4486 $.extend(true, options, opts); 4487 4488 showHideMenuItems($("ul.map-menuitems")); 4489 } 4490 4491 this.load = function () { 4492 return new Promise(function (resolve) { 4493 options = $.extend(true, {}, defaultOptions, options); 4494 4495 // create a unique instance id that gets appends to layer id so different instances of map does not overlap 4496 options.instanceId = new Date().valueOf(); 4497 4498 if (typeof window.imageBase !== "undefined") { 4499 options.imageBase = window.imageBase; 4500 } 4501 4502 _mapContainer = document.getElementById(options.container); 4503 _mapW = $(_mapContainer).outerHeight(); 4504 _mapH = $(_mapContainer).outerWidth(); 4505 4506 // Load the base map 4507 loadMap(); 4508 4509 // initialize the Html Layers module 4510 MM.registerModule('HtmlPushpinLayerModule', '/include/js/mapsearch/HtmlPushpinLayerModule.js'); 4511 4512 // load all modules 4513 MM.loadModule(['HtmlPushpinLayerModule', 'Microsoft.Maps.Clustering', 'Microsoft.Maps.SpatialMath'], 4514 function modulesLoaded() { 4515 4516 // Initialize the POI layers 4517 if (options.poiEnabled) { 4518 initializePoiLayers(); 4519 } 4520 4521 // Initialize the DMP layers 4522 if (options.showParcelLines) { 4523 initializeDmpLayers(); 4524 if (typeof (options.isSM3) !== 'undefined' && options.isSM3) { 4525 MM.Events.addHandler(_map, "mousemove", parcelHoverHandler); 4526 } 4527 } 4528 4529 // Load the navigation and events 4530 if (!options.fixedMap) { 4531 addMenuBar(); 4532 addNavBar(); 4533 addEvents(); 4534 } 4535 // Load the initial shapes 4536 addInitialShapes(); 4537 4538 // IF we're adding an initial pin, do that now 4539 if (options.centerPin) { 4540 var centerLat, centerLon; 4541 if (typeof options.startingMidLat !== "undefined") { 4542 centerLat = options.startingMidLat; 4543 centerLon = options.startingMidLong; 4544 } else { 4545 var center = _map.getCenter(); 4546 centerLat = center.latitude; 4547 centerLon = center.longitude; 4548 } 4549 4550 var pinType = options.pinType || 1; 4551 4552 _self.startAddingPins("Center", false); 4553 _self.addPin("Center", "1", centerLat, centerLon, pinType); 4554 _self.endAddingPins("Center"); 4555 } 4556 4557 // Fire off the initial load notification 4558 return resolve(firstOnChangeView()); 4559 }); 4560 }); 4561 } 4562} 4563 4564$.event.special.widthChanged = { 4565 remove: function () { 4566 $(this).children("iframe.width-changed").remove(); 4567 }, 4568 add: function () { 4569 var elm = $(this); 4570 var iframe = elm.children("iframe.width-changed"); 4571 if (!iframe.length) { 4572 iframe = $("<iframe/>").addClass("width-changed").attr("role","none").prependTo(this); 4573 } 4574 var oldWidth = elm.width(); 4575 const threshold = 20; 4576 function elmResized() { 4577 var width = elm.width(); 4578 if (Math.abs(oldWidth - width) >= threshold) { 4579 elm.trigger("widthChanged", [width, oldWidth]); 4580 oldWidth = width; 4581 } 4582 } 4583 4584 var timer = 0; 4585 var ielm = iframe[0]; 4586 (ielm.contentWindow || ielm).onresize = function () { 4587 clearTimeout(timer); 4588 timer = setTimeout(elmResized, 20); 4589 }; 4590 } 4591};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.