esri-leaflet-debug.js 149 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938
  1. /* esri-leaflet - v3.0.4 - Mon Dec 06 2021 15:19:55 GMT-0600 (Central Standard Time)
  2. * Copyright (c) 2021 Environmental Systems Research Institute, Inc.
  3. * Apache-2.0 */
  4. (function (global, factory) {
  5. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('leaflet')) :
  6. typeof define === 'function' && define.amd ? define(['exports', 'leaflet'], factory) :
  7. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.L = global.L || {}, global.L.esri = {}), global.L));
  8. }(this, (function (exports, leaflet) { 'use strict';
  9. var name = "esri-leaflet";
  10. var description = "Leaflet plugins for consuming ArcGIS Online and ArcGIS Server services.";
  11. var version$1 = "3.0.4";
  12. var author = "Patrick Arlt <parlt@esri.com> (http://patrickarlt.com)";
  13. var bugs = {
  14. url: "https://github.com/esri/esri-leaflet/issues"
  15. };
  16. var contributors = [
  17. "Patrick Arlt <parlt@esri.com> (http://patrickarlt.com)",
  18. "John Gravois <jgravois@esri.com> (https://johngravois.com)",
  19. "Gavin Rehkemper <grehkemper@esri.com> (https://gavinr.com)",
  20. "Jacob Wasilkowski <jwasilkowski@esri.com> (https://jwasilgeo.github.io)"
  21. ];
  22. var dependencies = {
  23. "@terraformer/arcgis": "^2.0.7",
  24. "tiny-binary-search": "^1.0.3"
  25. };
  26. var devDependencies = {
  27. "@rollup/plugin-json": "^4.1.0",
  28. "@rollup/plugin-node-resolve": "^13.0.0",
  29. chai: "4.3.4",
  30. "gh-release": "^6.0.0",
  31. "highlight.js": "^10.7.2",
  32. "http-server": "^0.12.3",
  33. husky: "^1.1.1",
  34. karma: "^6.3.2",
  35. "karma-chai-sinon": "^0.1.5",
  36. "karma-chrome-launcher": "^3.1.0",
  37. "karma-coverage": "^2.0.3",
  38. "karma-mocha": "^2.0.1",
  39. "karma-mocha-reporter": "^2.2.5",
  40. "karma-sourcemap-loader": "^0.3.8",
  41. leaflet: "^1.6.0",
  42. mkdirp: "^1.0.4",
  43. mocha: "^8.4.0",
  44. "npm-run-all": "^4.1.5",
  45. rollup: "^2.49.0",
  46. "rollup-plugin-uglify": "^6.0.4",
  47. semistandard: "^9.0.0",
  48. sinon: "^6.3.5",
  49. "sinon-chai": "3.2.0",
  50. snazzy: "^8.0.0",
  51. "uglify-js": "^2.8.29",
  52. watch: "^1.0.2"
  53. };
  54. var files = [
  55. "src/**/*.js",
  56. "dist/esri-leaflet.js",
  57. "dist/esri-leaflet.js.map",
  58. "dist/esri-leaflet-debug.js.map",
  59. "profiles/*.js"
  60. ];
  61. var homepage = "http://esri.github.io/esri-leaflet";
  62. var module = "src/EsriLeaflet.js";
  63. var jspm = {
  64. registry: "npm",
  65. format: "es6",
  66. main: "src/EsriLeaflet.js"
  67. };
  68. var keywords = [
  69. "arcgis",
  70. "esri",
  71. "esri leaflet",
  72. "gis",
  73. "leaflet plugin",
  74. "mapping"
  75. ];
  76. var license = "Apache-2.0";
  77. var main = "dist/esri-leaflet-debug.js";
  78. var peerDependencies = {
  79. leaflet: "^1.0.0"
  80. };
  81. var readmeFilename = "README.md";
  82. var repository = {
  83. type: "git",
  84. url: "git@github.com:Esri/esri-leaflet.git"
  85. };
  86. var scripts = {
  87. build: "rollup -c profiles/debug.js & rollup -c profiles/production.js",
  88. lint: "semistandard | snazzy",
  89. prebuild: "mkdirp dist",
  90. prepare: "npm run build",
  91. pretest: "npm run build",
  92. precommit: "npm run lint",
  93. fix: "semistandard --fix",
  94. release: "./scripts/release.sh",
  95. "start-watch": "watch \"npm run build\" src",
  96. start: "run-p start-watch serve",
  97. serve: "http-server -p 5000 -c-1 -o",
  98. test: "npm run lint && karma start",
  99. "test:ci": "npm run lint && karma start --browsers Chrome_travis_ci"
  100. };
  101. var semistandard = {
  102. globals: [
  103. "expect",
  104. "L",
  105. "XMLHttpRequest",
  106. "sinon",
  107. "xhr",
  108. "proj4"
  109. ]
  110. };
  111. var unpkg = "dist/esri-leaflet-debug.js";
  112. var packageInfo = {
  113. name: name,
  114. description: description,
  115. version: version$1,
  116. author: author,
  117. bugs: bugs,
  118. contributors: contributors,
  119. dependencies: dependencies,
  120. devDependencies: devDependencies,
  121. files: files,
  122. homepage: homepage,
  123. module: module,
  124. "jsnext:main": "src/EsriLeaflet.js",
  125. jspm: jspm,
  126. keywords: keywords,
  127. license: license,
  128. main: main,
  129. peerDependencies: peerDependencies,
  130. readmeFilename: readmeFilename,
  131. repository: repository,
  132. scripts: scripts,
  133. semistandard: semistandard,
  134. unpkg: unpkg
  135. };
  136. var cors = ((window.XMLHttpRequest && 'withCredentials' in new window.XMLHttpRequest()));
  137. var pointerEvents = document.documentElement.style.pointerEvents === '';
  138. var Support = {
  139. cors: cors,
  140. pointerEvents: pointerEvents
  141. };
  142. var options = {
  143. attributionWidthOffset: 55
  144. };
  145. var callbacks = 0;
  146. function serialize (params) {
  147. var data = '';
  148. params.f = params.f || 'json';
  149. for (var key in params) {
  150. if (params.hasOwnProperty(key)) {
  151. var param = params[key];
  152. var type = Object.prototype.toString.call(param);
  153. var value;
  154. if (data.length) {
  155. data += '&';
  156. }
  157. if (type === '[object Array]') {
  158. value = (Object.prototype.toString.call(param[0]) === '[object Object]') ? JSON.stringify(param) : param.join(',');
  159. } else if (type === '[object Object]') {
  160. value = JSON.stringify(param);
  161. } else if (type === '[object Date]') {
  162. value = param.valueOf();
  163. } else {
  164. value = param;
  165. }
  166. data += encodeURIComponent(key) + '=' + encodeURIComponent(value);
  167. }
  168. }
  169. return data;
  170. }
  171. function createRequest (callback, context) {
  172. var httpRequest = new window.XMLHttpRequest();
  173. httpRequest.onerror = function (e) {
  174. httpRequest.onreadystatechange = leaflet.Util.falseFn;
  175. callback.call(context, {
  176. error: {
  177. code: 500,
  178. message: 'XMLHttpRequest error'
  179. }
  180. }, null);
  181. };
  182. httpRequest.onreadystatechange = function () {
  183. var response;
  184. var error;
  185. if (httpRequest.readyState === 4) {
  186. try {
  187. response = JSON.parse(httpRequest.responseText);
  188. } catch (e) {
  189. response = null;
  190. error = {
  191. code: 500,
  192. message: 'Could not parse response as JSON. This could also be caused by a CORS or XMLHttpRequest error.'
  193. };
  194. }
  195. if (!error && response.error) {
  196. error = response.error;
  197. response = null;
  198. }
  199. httpRequest.onerror = leaflet.Util.falseFn;
  200. callback.call(context, error, response);
  201. }
  202. };
  203. httpRequest.ontimeout = function () {
  204. this.onerror();
  205. };
  206. return httpRequest;
  207. }
  208. function xmlHttpPost (url, params, callback, context) {
  209. var httpRequest = createRequest(callback, context);
  210. httpRequest.open('POST', url);
  211. if (typeof context !== 'undefined' && context !== null) {
  212. if (typeof context.options !== 'undefined') {
  213. httpRequest.timeout = context.options.timeout;
  214. }
  215. }
  216. httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
  217. httpRequest.send(serialize(params));
  218. return httpRequest;
  219. }
  220. function xmlHttpGet (url, params, callback, context) {
  221. var httpRequest = createRequest(callback, context);
  222. httpRequest.open('GET', url + '?' + serialize(params), true);
  223. if (typeof context !== 'undefined' && context !== null) {
  224. if (typeof context.options !== 'undefined') {
  225. httpRequest.timeout = context.options.timeout;
  226. if (context.options.withCredentials) {
  227. httpRequest.withCredentials = true;
  228. }
  229. }
  230. }
  231. httpRequest.send(null);
  232. return httpRequest;
  233. }
  234. // AJAX handlers for CORS (modern browsers) or JSONP (older browsers)
  235. function request (url, params, callback, context) {
  236. var paramString = serialize(params);
  237. var httpRequest = createRequest(callback, context);
  238. var requestLength = (url + '?' + paramString).length;
  239. // ie10/11 require the request be opened before a timeout is applied
  240. if (requestLength <= 2000 && Support.cors) {
  241. httpRequest.open('GET', url + '?' + paramString);
  242. } else if (requestLength > 2000 && Support.cors) {
  243. httpRequest.open('POST', url);
  244. httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
  245. }
  246. if (typeof context !== 'undefined' && context !== null) {
  247. if (typeof context.options !== 'undefined') {
  248. httpRequest.timeout = context.options.timeout;
  249. if (context.options.withCredentials) {
  250. httpRequest.withCredentials = true;
  251. }
  252. }
  253. }
  254. // request is less than 2000 characters and the browser supports CORS, make GET request with XMLHttpRequest
  255. if (requestLength <= 2000 && Support.cors) {
  256. httpRequest.send(null);
  257. // request is more than 2000 characters and the browser supports CORS, make POST request with XMLHttpRequest
  258. } else if (requestLength > 2000 && Support.cors) {
  259. httpRequest.send(paramString);
  260. // request is less than 2000 characters and the browser does not support CORS, make a JSONP request
  261. } else if (requestLength <= 2000 && !Support.cors) {
  262. return jsonp(url, params, callback, context);
  263. // request is longer then 2000 characters and the browser does not support CORS, log a warning
  264. } else {
  265. warn('a request to ' + url + ' was longer then 2000 characters and this browser cannot make a cross-domain post request. Please use a proxy http://esri.github.io/esri-leaflet/api-reference/request.html');
  266. return;
  267. }
  268. return httpRequest;
  269. }
  270. function jsonp (url, params, callback, context) {
  271. window._EsriLeafletCallbacks = window._EsriLeafletCallbacks || {};
  272. var callbackId = 'c' + callbacks;
  273. params.callback = 'window._EsriLeafletCallbacks.' + callbackId;
  274. window._EsriLeafletCallbacks[callbackId] = function (response) {
  275. if (window._EsriLeafletCallbacks[callbackId] !== true) {
  276. var error;
  277. var responseType = Object.prototype.toString.call(response);
  278. if (!(responseType === '[object Object]' || responseType === '[object Array]')) {
  279. error = {
  280. error: {
  281. code: 500,
  282. message: 'Expected array or object as JSONP response'
  283. }
  284. };
  285. response = null;
  286. }
  287. if (!error && response.error) {
  288. error = response;
  289. response = null;
  290. }
  291. callback.call(context, error, response);
  292. window._EsriLeafletCallbacks[callbackId] = true;
  293. }
  294. };
  295. var script = leaflet.DomUtil.create('script', null, document.body);
  296. script.type = 'text/javascript';
  297. script.src = url + '?' + serialize(params);
  298. script.id = callbackId;
  299. script.onerror = function (error) {
  300. if (error && window._EsriLeafletCallbacks[callbackId] !== true) {
  301. // Can't get true error code: it can be 404, or 401, or 500
  302. var err = {
  303. error: {
  304. code: 500,
  305. message: 'An unknown error occurred'
  306. }
  307. };
  308. callback.call(context, err);
  309. window._EsriLeafletCallbacks[callbackId] = true;
  310. }
  311. };
  312. leaflet.DomUtil.addClass(script, 'esri-leaflet-jsonp');
  313. callbacks++;
  314. return {
  315. id: callbackId,
  316. url: script.src,
  317. abort: function () {
  318. window._EsriLeafletCallbacks._callback[callbackId]({
  319. code: 0,
  320. message: 'Request aborted.'
  321. });
  322. }
  323. };
  324. }
  325. var get = ((Support.cors) ? xmlHttpGet : jsonp);
  326. get.CORS = xmlHttpGet;
  327. get.JSONP = jsonp;
  328. function warn () {
  329. if (console && console.warn) {
  330. console.warn.apply(console, arguments);
  331. }
  332. }
  333. // export the Request object to call the different handlers for debugging
  334. var Request = {
  335. request: request,
  336. get: get,
  337. post: xmlHttpPost
  338. };
  339. /* @preserve
  340. * @terraformer/arcgis - v2.0.6 - MIT
  341. * Copyright (c) 2012-2020 Environmental Systems Research Institute, Inc.
  342. * Mon May 18 2020 14:30:35 GMT-0700 (Pacific Daylight Time)
  343. */
  344. /* Copyright (c) 2012-2019 Environmental Systems Research Institute, Inc.
  345. * Apache-2.0 */
  346. var edgeIntersectsEdge = function edgeIntersectsEdge(a1, a2, b1, b2) {
  347. var uaT = (b2[0] - b1[0]) * (a1[1] - b1[1]) - (b2[1] - b1[1]) * (a1[0] - b1[0]);
  348. var ubT = (a2[0] - a1[0]) * (a1[1] - b1[1]) - (a2[1] - a1[1]) * (a1[0] - b1[0]);
  349. var uB = (b2[1] - b1[1]) * (a2[0] - a1[0]) - (b2[0] - b1[0]) * (a2[1] - a1[1]);
  350. if (uB !== 0) {
  351. var ua = uaT / uB;
  352. var ub = ubT / uB;
  353. if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
  354. return true;
  355. }
  356. }
  357. return false;
  358. };
  359. var coordinatesContainPoint = function coordinatesContainPoint(coordinates, point) {
  360. var contains = false;
  361. for (var i = -1, l = coordinates.length, j = l - 1; ++i < l; j = i) {
  362. if ((coordinates[i][1] <= point[1] && point[1] < coordinates[j][1] || coordinates[j][1] <= point[1] && point[1] < coordinates[i][1]) && point[0] < (coordinates[j][0] - coordinates[i][0]) * (point[1] - coordinates[i][1]) / (coordinates[j][1] - coordinates[i][1]) + coordinates[i][0]) {
  363. contains = !contains;
  364. }
  365. }
  366. return contains;
  367. };
  368. var pointsEqual = function pointsEqual(a, b) {
  369. for (var i = 0; i < a.length; i++) {
  370. if (a[i] !== b[i]) {
  371. return false;
  372. }
  373. }
  374. return true;
  375. };
  376. var arrayIntersectsArray = function arrayIntersectsArray(a, b) {
  377. for (var i = 0; i < a.length - 1; i++) {
  378. for (var j = 0; j < b.length - 1; j++) {
  379. if (edgeIntersectsEdge(a[i], a[i + 1], b[j], b[j + 1])) {
  380. return true;
  381. }
  382. }
  383. }
  384. return false;
  385. };
  386. /* Copyright (c) 2012-2019 Environmental Systems Research Institute, Inc.
  387. * Apache-2.0 */
  388. var closeRing = function closeRing(coordinates) {
  389. if (!pointsEqual(coordinates[0], coordinates[coordinates.length - 1])) {
  390. coordinates.push(coordinates[0]);
  391. }
  392. return coordinates;
  393. }; // determine if polygon ring coordinates are clockwise. clockwise signifies outer ring, counter-clockwise an inner ring
  394. // or hole. this logic was found at http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-
  395. // points-are-in-clockwise-order
  396. var ringIsClockwise = function ringIsClockwise(ringToTest) {
  397. var total = 0;
  398. var i = 0;
  399. var rLength = ringToTest.length;
  400. var pt1 = ringToTest[i];
  401. var pt2;
  402. for (i; i < rLength - 1; i++) {
  403. pt2 = ringToTest[i + 1];
  404. total += (pt2[0] - pt1[0]) * (pt2[1] + pt1[1]);
  405. pt1 = pt2;
  406. }
  407. return total >= 0;
  408. }; // This function ensures that rings are oriented in the right directions
  409. // from http://jsperf.com/cloning-an-object/2
  410. var shallowClone = function shallowClone(obj) {
  411. var target = {};
  412. for (var i in obj) {
  413. // both arcgis attributes and geojson props are just hardcoded keys
  414. if (obj.hasOwnProperty(i)) {
  415. // eslint-disable-line no-prototype-builtins
  416. target[i] = obj[i];
  417. }
  418. }
  419. return target;
  420. };
  421. /* Copyright (c) 2012-2019 Environmental Systems Research Institute, Inc.
  422. * Apache-2.0 */
  423. var coordinatesContainCoordinates = function coordinatesContainCoordinates(outer, inner) {
  424. var intersects = arrayIntersectsArray(outer, inner);
  425. var contains = coordinatesContainPoint(outer, inner[0]);
  426. if (!intersects && contains) {
  427. return true;
  428. }
  429. return false;
  430. }; // do any polygons in this array contain any other polygons in this array?
  431. // used for checking for holes in arcgis rings
  432. var convertRingsToGeoJSON = function convertRingsToGeoJSON(rings) {
  433. var outerRings = [];
  434. var holes = [];
  435. var x; // iterator
  436. var outerRing; // current outer ring being evaluated
  437. var hole; // current hole being evaluated
  438. // for each ring
  439. for (var r = 0; r < rings.length; r++) {
  440. var ring = closeRing(rings[r].slice(0));
  441. if (ring.length < 4) {
  442. continue;
  443. } // is this ring an outer ring? is it clockwise?
  444. if (ringIsClockwise(ring)) {
  445. var polygon = [ring.slice().reverse()]; // wind outer rings counterclockwise for RFC 7946 compliance
  446. outerRings.push(polygon); // push to outer rings
  447. } else {
  448. holes.push(ring.slice().reverse()); // wind inner rings clockwise for RFC 7946 compliance
  449. }
  450. }
  451. var uncontainedHoles = []; // while there are holes left...
  452. while (holes.length) {
  453. // pop a hole off out stack
  454. hole = holes.pop(); // loop over all outer rings and see if they contain our hole.
  455. var contained = false;
  456. for (x = outerRings.length - 1; x >= 0; x--) {
  457. outerRing = outerRings[x][0];
  458. if (coordinatesContainCoordinates(outerRing, hole)) {
  459. // the hole is contained push it into our polygon
  460. outerRings[x].push(hole);
  461. contained = true;
  462. break;
  463. }
  464. } // ring is not contained in any outer ring
  465. // sometimes this happens https://github.com/Esri/esri-leaflet/issues/320
  466. if (!contained) {
  467. uncontainedHoles.push(hole);
  468. }
  469. } // if we couldn't match any holes using contains we can try intersects...
  470. while (uncontainedHoles.length) {
  471. // pop a hole off out stack
  472. hole = uncontainedHoles.pop(); // loop over all outer rings and see if any intersect our hole.
  473. var intersects = false;
  474. for (x = outerRings.length - 1; x >= 0; x--) {
  475. outerRing = outerRings[x][0];
  476. if (arrayIntersectsArray(outerRing, hole)) {
  477. // the hole is contained push it into our polygon
  478. outerRings[x].push(hole);
  479. intersects = true;
  480. break;
  481. }
  482. }
  483. if (!intersects) {
  484. outerRings.push([hole.reverse()]);
  485. }
  486. }
  487. if (outerRings.length === 1) {
  488. return {
  489. type: 'Polygon',
  490. coordinates: outerRings[0]
  491. };
  492. } else {
  493. return {
  494. type: 'MultiPolygon',
  495. coordinates: outerRings
  496. };
  497. }
  498. };
  499. var getId = function getId(attributes, idAttribute) {
  500. var keys = idAttribute ? [idAttribute, 'OBJECTID', 'FID'] : ['OBJECTID', 'FID'];
  501. for (var i = 0; i < keys.length; i++) {
  502. var key = keys[i];
  503. if (key in attributes && (typeof attributes[key] === 'string' || typeof attributes[key] === 'number')) {
  504. return attributes[key];
  505. }
  506. }
  507. throw Error('No valid id attribute found');
  508. };
  509. var arcgisToGeoJSON$1 = function arcgisToGeoJSON(arcgis, idAttribute) {
  510. var geojson = {};
  511. if (arcgis.features) {
  512. geojson.type = 'FeatureCollection';
  513. geojson.features = [];
  514. for (var i = 0; i < arcgis.features.length; i++) {
  515. geojson.features.push(arcgisToGeoJSON(arcgis.features[i], idAttribute));
  516. }
  517. }
  518. if (typeof arcgis.x === 'number' && typeof arcgis.y === 'number') {
  519. geojson.type = 'Point';
  520. geojson.coordinates = [arcgis.x, arcgis.y];
  521. if (typeof arcgis.z === 'number') {
  522. geojson.coordinates.push(arcgis.z);
  523. }
  524. }
  525. if (arcgis.points) {
  526. geojson.type = 'MultiPoint';
  527. geojson.coordinates = arcgis.points.slice(0);
  528. }
  529. if (arcgis.paths) {
  530. if (arcgis.paths.length === 1) {
  531. geojson.type = 'LineString';
  532. geojson.coordinates = arcgis.paths[0].slice(0);
  533. } else {
  534. geojson.type = 'MultiLineString';
  535. geojson.coordinates = arcgis.paths.slice(0);
  536. }
  537. }
  538. if (arcgis.rings) {
  539. geojson = convertRingsToGeoJSON(arcgis.rings.slice(0));
  540. }
  541. if (typeof arcgis.xmin === 'number' && typeof arcgis.ymin === 'number' && typeof arcgis.xmax === 'number' && typeof arcgis.ymax === 'number') {
  542. geojson.type = 'Polygon';
  543. geojson.coordinates = [[[arcgis.xmax, arcgis.ymax], [arcgis.xmin, arcgis.ymax], [arcgis.xmin, arcgis.ymin], [arcgis.xmax, arcgis.ymin], [arcgis.xmax, arcgis.ymax]]];
  544. }
  545. if (arcgis.geometry || arcgis.attributes) {
  546. geojson.type = 'Feature';
  547. geojson.geometry = arcgis.geometry ? arcgisToGeoJSON(arcgis.geometry) : null;
  548. geojson.properties = arcgis.attributes ? shallowClone(arcgis.attributes) : null;
  549. if (arcgis.attributes) {
  550. try {
  551. geojson.id = getId(arcgis.attributes, idAttribute);
  552. } catch (err) {// don't set an id
  553. }
  554. }
  555. } // if no valid geometry was encountered
  556. if (JSON.stringify(geojson.geometry) === JSON.stringify({})) {
  557. geojson.geometry = null;
  558. }
  559. if (arcgis.spatialReference && arcgis.spatialReference.wkid && arcgis.spatialReference.wkid !== 4326) {
  560. console.warn('Object converted in non-standard crs - ' + JSON.stringify(arcgis.spatialReference));
  561. }
  562. return geojson;
  563. };
  564. /* Copyright (c) 2012-2019 Environmental Systems Research Institute, Inc.
  565. * Apache-2.0 */
  566. // outer rings are clockwise, holes are counterclockwise
  567. // used for converting GeoJSON Polygons to ArcGIS Polygons
  568. var orientRings = function orientRings(poly) {
  569. var output = [];
  570. var polygon = poly.slice(0);
  571. var outerRing = closeRing(polygon.shift().slice(0));
  572. if (outerRing.length >= 4) {
  573. if (!ringIsClockwise(outerRing)) {
  574. outerRing.reverse();
  575. }
  576. output.push(outerRing);
  577. for (var i = 0; i < polygon.length; i++) {
  578. var hole = closeRing(polygon[i].slice(0));
  579. if (hole.length >= 4) {
  580. if (ringIsClockwise(hole)) {
  581. hole.reverse();
  582. }
  583. output.push(hole);
  584. }
  585. }
  586. }
  587. return output;
  588. }; // This function flattens holes in multipolygons to one array of polygons
  589. // used for converting GeoJSON Polygons to ArcGIS Polygons
  590. var flattenMultiPolygonRings = function flattenMultiPolygonRings(rings) {
  591. var output = [];
  592. for (var i = 0; i < rings.length; i++) {
  593. var polygon = orientRings(rings[i]);
  594. for (var x = polygon.length - 1; x >= 0; x--) {
  595. var ring = polygon[x].slice(0);
  596. output.push(ring);
  597. }
  598. }
  599. return output;
  600. };
  601. var geojsonToArcGIS$1 = function geojsonToArcGIS(geojson, idAttribute) {
  602. idAttribute = idAttribute || 'OBJECTID';
  603. var spatialReference = {
  604. wkid: 4326
  605. };
  606. var result = {};
  607. var i;
  608. switch (geojson.type) {
  609. case 'Point':
  610. result.x = geojson.coordinates[0];
  611. result.y = geojson.coordinates[1];
  612. result.spatialReference = spatialReference;
  613. break;
  614. case 'MultiPoint':
  615. result.points = geojson.coordinates.slice(0);
  616. result.spatialReference = spatialReference;
  617. break;
  618. case 'LineString':
  619. result.paths = [geojson.coordinates.slice(0)];
  620. result.spatialReference = spatialReference;
  621. break;
  622. case 'MultiLineString':
  623. result.paths = geojson.coordinates.slice(0);
  624. result.spatialReference = spatialReference;
  625. break;
  626. case 'Polygon':
  627. result.rings = orientRings(geojson.coordinates.slice(0));
  628. result.spatialReference = spatialReference;
  629. break;
  630. case 'MultiPolygon':
  631. result.rings = flattenMultiPolygonRings(geojson.coordinates.slice(0));
  632. result.spatialReference = spatialReference;
  633. break;
  634. case 'Feature':
  635. if (geojson.geometry) {
  636. result.geometry = geojsonToArcGIS(geojson.geometry, idAttribute);
  637. }
  638. result.attributes = geojson.properties ? shallowClone(geojson.properties) : {};
  639. if (geojson.id) {
  640. result.attributes[idAttribute] = geojson.id;
  641. }
  642. break;
  643. case 'FeatureCollection':
  644. result = [];
  645. for (i = 0; i < geojson.features.length; i++) {
  646. result.push(geojsonToArcGIS(geojson.features[i], idAttribute));
  647. }
  648. break;
  649. case 'GeometryCollection':
  650. result = [];
  651. for (i = 0; i < geojson.geometries.length; i++) {
  652. result.push(geojsonToArcGIS(geojson.geometries[i], idAttribute));
  653. }
  654. break;
  655. }
  656. return result;
  657. };
  658. var BASE_LEAFLET_ATTRIBUTION_STRING = '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>';
  659. var POWERED_BY_ESRI_ATTRIBUTION_STRING = 'Powered by <a href="https://www.esri.com">Esri</a>';
  660. function geojsonToArcGIS (geojson, idAttr) {
  661. return geojsonToArcGIS$1(geojson, idAttr);
  662. }
  663. function arcgisToGeoJSON (arcgis, idAttr) {
  664. return arcgisToGeoJSON$1(arcgis, idAttr);
  665. }
  666. // convert an extent (ArcGIS) to LatLngBounds (Leaflet)
  667. function extentToBounds (extent) {
  668. // "NaN" coordinates from ArcGIS Server indicate a null geometry
  669. if (extent.xmin !== 'NaN' && extent.ymin !== 'NaN' && extent.xmax !== 'NaN' && extent.ymax !== 'NaN') {
  670. var sw = leaflet.latLng(extent.ymin, extent.xmin);
  671. var ne = leaflet.latLng(extent.ymax, extent.xmax);
  672. return leaflet.latLngBounds(sw, ne);
  673. } else {
  674. return null;
  675. }
  676. }
  677. // convert an LatLngBounds (Leaflet) to extent (ArcGIS)
  678. function boundsToExtent (bounds) {
  679. bounds = leaflet.latLngBounds(bounds);
  680. return {
  681. 'xmin': bounds.getSouthWest().lng,
  682. 'ymin': bounds.getSouthWest().lat,
  683. 'xmax': bounds.getNorthEast().lng,
  684. 'ymax': bounds.getNorthEast().lat,
  685. 'spatialReference': {
  686. 'wkid': 4326
  687. }
  688. };
  689. }
  690. var knownFieldNames = /^(OBJECTID|FID|OID|ID)$/i;
  691. // Attempts to find the ID Field from response
  692. function _findIdAttributeFromResponse (response) {
  693. var result;
  694. if (response.objectIdFieldName) {
  695. // Find Id Field directly
  696. result = response.objectIdFieldName;
  697. } else if (response.fields) {
  698. // Find ID Field based on field type
  699. for (var j = 0; j <= response.fields.length - 1; j++) {
  700. if (response.fields[j].type === 'esriFieldTypeOID') {
  701. result = response.fields[j].name;
  702. break;
  703. }
  704. }
  705. if (!result) {
  706. // If no field was marked as being the esriFieldTypeOID try well known field names
  707. for (j = 0; j <= response.fields.length - 1; j++) {
  708. if (response.fields[j].name.match(knownFieldNames)) {
  709. result = response.fields[j].name;
  710. break;
  711. }
  712. }
  713. }
  714. }
  715. return result;
  716. }
  717. // This is the 'last' resort, find the Id field from the specified feature
  718. function _findIdAttributeFromFeature (feature) {
  719. for (var key in feature.attributes) {
  720. if (key.match(knownFieldNames)) {
  721. return key;
  722. }
  723. }
  724. }
  725. function responseToFeatureCollection (response, idAttribute) {
  726. var objectIdField;
  727. var features = response.features || response.results;
  728. var count = features && features.length;
  729. if (idAttribute) {
  730. objectIdField = idAttribute;
  731. } else {
  732. objectIdField = _findIdAttributeFromResponse(response);
  733. }
  734. var featureCollection = {
  735. type: 'FeatureCollection',
  736. features: []
  737. };
  738. if (count) {
  739. for (var i = features.length - 1; i >= 0; i--) {
  740. var feature = arcgisToGeoJSON(features[i], objectIdField || _findIdAttributeFromFeature(features[i]));
  741. featureCollection.features.push(feature);
  742. }
  743. }
  744. return featureCollection;
  745. }
  746. // trim url whitespace and add a trailing slash if needed
  747. function cleanUrl (url) {
  748. // trim leading and trailing spaces, but not spaces inside the url
  749. url = leaflet.Util.trim(url);
  750. // add a trailing slash to the url if the user omitted it
  751. if (url[url.length - 1] !== '/') {
  752. url += '/';
  753. }
  754. return url;
  755. }
  756. /* Extract url params if any and store them in requestParams attribute.
  757. Return the options params updated */
  758. function getUrlParams (options) {
  759. if (options.url.indexOf('?') !== -1) {
  760. options.requestParams = options.requestParams || {};
  761. var queryString = options.url.substring(options.url.indexOf('?') + 1);
  762. options.url = options.url.split('?')[0];
  763. options.requestParams = JSON.parse('{"' + decodeURI(queryString).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') + '"}');
  764. }
  765. options.url = cleanUrl(options.url.split('?')[0]);
  766. return options;
  767. }
  768. function isArcgisOnline (url) {
  769. /* hosted feature services support geojson as an output format
  770. utility.arcgis.com services are proxied from a variety of ArcGIS Server vintages, and may not */
  771. return (/^(?!.*utility\.arcgis\.com).*\.arcgis\.com.*FeatureServer/i).test(url);
  772. }
  773. function geojsonTypeToArcGIS (geoJsonType) {
  774. var arcgisGeometryType;
  775. switch (geoJsonType) {
  776. case 'Point':
  777. arcgisGeometryType = 'esriGeometryPoint';
  778. break;
  779. case 'MultiPoint':
  780. arcgisGeometryType = 'esriGeometryMultipoint';
  781. break;
  782. case 'LineString':
  783. arcgisGeometryType = 'esriGeometryPolyline';
  784. break;
  785. case 'MultiLineString':
  786. arcgisGeometryType = 'esriGeometryPolyline';
  787. break;
  788. case 'Polygon':
  789. arcgisGeometryType = 'esriGeometryPolygon';
  790. break;
  791. case 'MultiPolygon':
  792. arcgisGeometryType = 'esriGeometryPolygon';
  793. break;
  794. }
  795. return arcgisGeometryType;
  796. }
  797. function calcAttributionWidth (map) {
  798. // either crop at 55px or user defined buffer
  799. return (map.getSize().x - options.attributionWidthOffset) + 'px';
  800. }
  801. function setEsriAttribution (map) {
  802. if (!map.attributionControl) {
  803. return;
  804. }
  805. if (!map.attributionControl._esriAttributionLayerCount) {
  806. map.attributionControl._esriAttributionLayerCount = 0;
  807. }
  808. if (map.attributionControl._esriAttributionLayerCount === 0) {
  809. // Dynamically creating the CSS rules, only run this once per page load:
  810. if (!map.attributionControl._esriAttributionAddedOnce) {
  811. var hoverAttributionStyle = document.createElement('style');
  812. hoverAttributionStyle.type = 'text/css';
  813. hoverAttributionStyle.innerHTML = '.esri-truncated-attribution:hover {' +
  814. 'white-space: normal;' +
  815. '}';
  816. document.getElementsByTagName('head')[0].appendChild(hoverAttributionStyle);
  817. // define a new css class in JS to trim attribution into a single line
  818. var attributionStyle = document.createElement('style');
  819. attributionStyle.type = 'text/css';
  820. attributionStyle.innerHTML = '.esri-truncated-attribution {' +
  821. 'vertical-align: -3px;' +
  822. 'white-space: nowrap;' +
  823. 'overflow: hidden;' +
  824. 'text-overflow: ellipsis;' +
  825. 'display: inline-block;' +
  826. 'transition: 0s white-space;' +
  827. 'transition-delay: 1s;' +
  828. 'max-width: ' + calcAttributionWidth(map) + ';' +
  829. '}';
  830. document.getElementsByTagName('head')[0].appendChild(attributionStyle);
  831. // update the width used to truncate when the map itself is resized
  832. map.on('resize', function (e) {
  833. if (map.attributionControl) {
  834. map.attributionControl._container.style.maxWidth = calcAttributionWidth(e.target);
  835. }
  836. });
  837. map.attributionControl._esriAttributionAddedOnce = true;
  838. }
  839. map.attributionControl.setPrefix(BASE_LEAFLET_ATTRIBUTION_STRING + ' | ' + POWERED_BY_ESRI_ATTRIBUTION_STRING);
  840. leaflet.DomUtil.addClass(map.attributionControl._container, 'esri-truncated-attribution:hover');
  841. leaflet.DomUtil.addClass(map.attributionControl._container, 'esri-truncated-attribution');
  842. }
  843. // Track the number of esri-leaflet layers that are on the map so we can know when we can remove the attribution (below in removeEsriAttribution)
  844. map.attributionControl._esriAttributionLayerCount = map.attributionControl._esriAttributionLayerCount + 1;
  845. }
  846. function removeEsriAttribution (map) {
  847. if (!map.attributionControl) {
  848. return;
  849. }
  850. // Only remove the attribution if we're about to remove the LAST esri-leaflet layer (_esriAttributionLayerCount)
  851. if (map.attributionControl._esriAttributionLayerCount && map.attributionControl._esriAttributionLayerCount === 1) {
  852. map.attributionControl.setPrefix(BASE_LEAFLET_ATTRIBUTION_STRING);
  853. leaflet.DomUtil.removeClass(map.attributionControl._container, 'esri-truncated-attribution:hover');
  854. leaflet.DomUtil.removeClass(map.attributionControl._container, 'esri-truncated-attribution');
  855. }
  856. map.attributionControl._esriAttributionLayerCount = map.attributionControl._esriAttributionLayerCount - 1;
  857. }
  858. function _setGeometry (geometry) {
  859. var params = {
  860. geometry: null,
  861. geometryType: null
  862. };
  863. // convert bounds to extent and finish
  864. if (geometry instanceof leaflet.LatLngBounds) {
  865. // set geometry + geometryType
  866. params.geometry = boundsToExtent(geometry);
  867. params.geometryType = 'esriGeometryEnvelope';
  868. return params;
  869. }
  870. // convert L.Marker > L.LatLng
  871. if (geometry.getLatLng) {
  872. geometry = geometry.getLatLng();
  873. }
  874. // convert L.LatLng to a geojson point and continue;
  875. if (geometry instanceof leaflet.LatLng) {
  876. geometry = {
  877. type: 'Point',
  878. coordinates: [geometry.lng, geometry.lat]
  879. };
  880. }
  881. // handle L.GeoJSON, pull out the first geometry
  882. if (geometry instanceof leaflet.GeoJSON) {
  883. // reassign geometry to the GeoJSON value (we are assuming that only one feature is present)
  884. geometry = geometry.getLayers()[0].feature.geometry;
  885. params.geometry = geojsonToArcGIS(geometry);
  886. params.geometryType = geojsonTypeToArcGIS(geometry.type);
  887. }
  888. // Handle L.Polyline and L.Polygon
  889. if (geometry.toGeoJSON) {
  890. geometry = geometry.toGeoJSON();
  891. }
  892. // handle GeoJSON feature by pulling out the geometry
  893. if (geometry.type === 'Feature') {
  894. // get the geometry of the geojson feature
  895. geometry = geometry.geometry;
  896. }
  897. // confirm that our GeoJSON is a point, line or polygon
  898. if (geometry.type === 'Point' || geometry.type === 'LineString' || geometry.type === 'Polygon' || geometry.type === 'MultiPolygon') {
  899. params.geometry = geojsonToArcGIS(geometry);
  900. params.geometryType = geojsonTypeToArcGIS(geometry.type);
  901. return params;
  902. }
  903. // warn the user if we havn't found an appropriate object
  904. warn('invalid geometry passed to spatial query. Should be L.LatLng, L.LatLngBounds, L.Marker or a GeoJSON Point, Line, Polygon or MultiPolygon object');
  905. return;
  906. }
  907. function _getAttributionData (url, map) {
  908. if (Support.cors) {
  909. request(url, {}, leaflet.Util.bind(function (error, attributions) {
  910. if (error) { return; }
  911. map._esriAttributions = [];
  912. for (var c = 0; c < attributions.contributors.length; c++) {
  913. var contributor = attributions.contributors[c];
  914. for (var i = 0; i < contributor.coverageAreas.length; i++) {
  915. var coverageArea = contributor.coverageAreas[i];
  916. var southWest = leaflet.latLng(coverageArea.bbox[0], coverageArea.bbox[1]);
  917. var northEast = leaflet.latLng(coverageArea.bbox[2], coverageArea.bbox[3]);
  918. map._esriAttributions.push({
  919. attribution: contributor.attribution,
  920. score: coverageArea.score,
  921. bounds: leaflet.latLngBounds(southWest, northEast),
  922. minZoom: coverageArea.zoomMin,
  923. maxZoom: coverageArea.zoomMax
  924. });
  925. }
  926. }
  927. map._esriAttributions.sort(function (a, b) {
  928. return b.score - a.score;
  929. });
  930. // pass the same argument as the map's 'moveend' event
  931. var obj = { target: map };
  932. _updateMapAttribution(obj);
  933. }, this));
  934. }
  935. }
  936. function _updateMapAttribution (evt) {
  937. var map = evt.target;
  938. var oldAttributions = map._esriAttributions;
  939. if (!map || !map.attributionControl) return;
  940. var attributionElement = map.attributionControl._container.querySelector('.esri-dynamic-attribution');
  941. if (attributionElement && oldAttributions) {
  942. var newAttributions = '';
  943. var bounds = map.getBounds();
  944. var wrappedBounds = leaflet.latLngBounds(
  945. bounds.getSouthWest().wrap(),
  946. bounds.getNorthEast().wrap()
  947. );
  948. var zoom = map.getZoom();
  949. for (var i = 0; i < oldAttributions.length; i++) {
  950. var attribution = oldAttributions[i];
  951. var text = attribution.attribution;
  952. if (!newAttributions.match(text) && attribution.bounds.intersects(wrappedBounds) && zoom >= attribution.minZoom && zoom <= attribution.maxZoom) {
  953. newAttributions += (', ' + text);
  954. }
  955. }
  956. newAttributions = newAttributions.substr(2);
  957. attributionElement.innerHTML = newAttributions;
  958. attributionElement.style.maxWidth = calcAttributionWidth(map);
  959. map.fire('attributionupdated', {
  960. attribution: newAttributions
  961. });
  962. }
  963. }
  964. var EsriUtil = {
  965. warn: warn,
  966. cleanUrl: cleanUrl,
  967. getUrlParams: getUrlParams,
  968. isArcgisOnline: isArcgisOnline,
  969. geojsonTypeToArcGIS: geojsonTypeToArcGIS,
  970. responseToFeatureCollection: responseToFeatureCollection,
  971. geojsonToArcGIS: geojsonToArcGIS,
  972. arcgisToGeoJSON: arcgisToGeoJSON,
  973. boundsToExtent: boundsToExtent,
  974. extentToBounds: extentToBounds,
  975. calcAttributionWidth: calcAttributionWidth,
  976. setEsriAttribution: setEsriAttribution,
  977. _setGeometry: _setGeometry,
  978. _getAttributionData: _getAttributionData,
  979. _updateMapAttribution: _updateMapAttribution,
  980. _findIdAttributeFromFeature: _findIdAttributeFromFeature,
  981. _findIdAttributeFromResponse: _findIdAttributeFromResponse
  982. };
  983. var Task = leaflet.Class.extend({
  984. options: {
  985. proxy: false,
  986. useCors: cors
  987. },
  988. // Generate a method for each methodName:paramName in the setters for this task.
  989. generateSetter: function (param, context) {
  990. return leaflet.Util.bind(function (value) {
  991. this.params[param] = value;
  992. return this;
  993. }, context);
  994. },
  995. initialize: function (endpoint) {
  996. // endpoint can be either a url (and options) for an ArcGIS Rest Service or an instance of EsriLeaflet.Service
  997. if (endpoint.request && endpoint.options) {
  998. this._service = endpoint;
  999. leaflet.Util.setOptions(this, endpoint.options);
  1000. } else {
  1001. leaflet.Util.setOptions(this, endpoint);
  1002. this.options.url = cleanUrl(endpoint.url);
  1003. }
  1004. // clone default params into this object
  1005. this.params = leaflet.Util.extend({}, this.params || {});
  1006. // generate setter methods based on the setters object implimented a child class
  1007. if (this.setters) {
  1008. for (var setter in this.setters) {
  1009. var param = this.setters[setter];
  1010. this[setter] = this.generateSetter(param, this);
  1011. }
  1012. }
  1013. },
  1014. token: function (token) {
  1015. if (this._service) {
  1016. this._service.authenticate(token);
  1017. } else {
  1018. this.params.token = token;
  1019. }
  1020. return this;
  1021. },
  1022. apikey: function (apikey) {
  1023. return this.token(apikey);
  1024. },
  1025. // ArcGIS Server Find/Identify 10.5+
  1026. format: function (boolean) {
  1027. // use double negative to expose a more intuitive positive method name
  1028. this.params.returnUnformattedValues = !boolean;
  1029. return this;
  1030. },
  1031. request: function (callback, context) {
  1032. if (this.options.requestParams) {
  1033. leaflet.Util.extend(this.params, this.options.requestParams);
  1034. }
  1035. if (this._service) {
  1036. return this._service.request(this.path, this.params, callback, context);
  1037. }
  1038. return this._request('request', this.path, this.params, callback, context);
  1039. },
  1040. _request: function (method, path, params, callback, context) {
  1041. var url = (this.options.proxy) ? this.options.proxy + '?' + this.options.url + path : this.options.url + path;
  1042. if ((method === 'get' || method === 'request') && !this.options.useCors) {
  1043. return Request.get.JSONP(url, params, callback, context);
  1044. }
  1045. return Request[method](url, params, callback, context);
  1046. }
  1047. });
  1048. function task (options) {
  1049. options = getUrlParams(options);
  1050. return new Task(options);
  1051. }
  1052. var Query = Task.extend({
  1053. setters: {
  1054. 'offset': 'resultOffset',
  1055. 'limit': 'resultRecordCount',
  1056. 'fields': 'outFields',
  1057. 'precision': 'geometryPrecision',
  1058. 'featureIds': 'objectIds',
  1059. 'returnGeometry': 'returnGeometry',
  1060. 'returnM': 'returnM',
  1061. 'transform': 'datumTransformation',
  1062. 'token': 'token'
  1063. },
  1064. path: 'query',
  1065. params: {
  1066. returnGeometry: true,
  1067. where: '1=1',
  1068. outSR: 4326,
  1069. outFields: '*'
  1070. },
  1071. // Returns a feature if its shape is wholly contained within the search geometry. Valid for all shape type combinations.
  1072. within: function (geometry) {
  1073. this._setGeometryParams(geometry);
  1074. this.params.spatialRel = 'esriSpatialRelContains'; // to the REST api this reads geometry **contains** layer
  1075. return this;
  1076. },
  1077. // Returns a feature if any spatial relationship is found. Applies to all shape type combinations.
  1078. intersects: function (geometry) {
  1079. this._setGeometryParams(geometry);
  1080. this.params.spatialRel = 'esriSpatialRelIntersects';
  1081. return this;
  1082. },
  1083. // Returns a feature if its shape wholly contains the search geometry. Valid for all shape type combinations.
  1084. contains: function (geometry) {
  1085. this._setGeometryParams(geometry);
  1086. this.params.spatialRel = 'esriSpatialRelWithin'; // to the REST api this reads geometry **within** layer
  1087. return this;
  1088. },
  1089. // Returns a feature if the intersection of the interiors of the two shapes is not empty and has a lower dimension than the maximum dimension of the two shapes. Two lines that share an endpoint in common do not cross. Valid for Line/Line, Line/Area, Multi-point/Area, and Multi-point/Line shape type combinations.
  1090. crosses: function (geometry) {
  1091. this._setGeometryParams(geometry);
  1092. this.params.spatialRel = 'esriSpatialRelCrosses';
  1093. return this;
  1094. },
  1095. // Returns a feature if the two shapes share a common boundary. However, the intersection of the interiors of the two shapes must be empty. In the Point/Line case, the point may touch an endpoint only of the line. Applies to all combinations except Point/Point.
  1096. touches: function (geometry) {
  1097. this._setGeometryParams(geometry);
  1098. this.params.spatialRel = 'esriSpatialRelTouches';
  1099. return this;
  1100. },
  1101. // Returns a feature if the intersection of the two shapes results in an object of the same dimension, but different from both of the shapes. Applies to Area/Area, Line/Line, and Multi-point/Multi-point shape type combinations.
  1102. overlaps: function (geometry) {
  1103. this._setGeometryParams(geometry);
  1104. this.params.spatialRel = 'esriSpatialRelOverlaps';
  1105. return this;
  1106. },
  1107. // Returns a feature if the envelope of the two shapes intersects.
  1108. bboxIntersects: function (geometry) {
  1109. this._setGeometryParams(geometry);
  1110. this.params.spatialRel = 'esriSpatialRelEnvelopeIntersects';
  1111. return this;
  1112. },
  1113. // if someone can help decipher the ArcObjects explanation and translate to plain speak, we should mention this method in the doc
  1114. indexIntersects: function (geometry) {
  1115. this._setGeometryParams(geometry);
  1116. this.params.spatialRel = 'esriSpatialRelIndexIntersects'; // Returns a feature if the envelope of the query geometry intersects the index entry for the target geometry
  1117. return this;
  1118. },
  1119. // only valid for Feature Services running on ArcGIS Server 10.3+ or ArcGIS Online
  1120. nearby: function (latlng, radius) {
  1121. latlng = leaflet.latLng(latlng);
  1122. this.params.geometry = [latlng.lng, latlng.lat];
  1123. this.params.geometryType = 'esriGeometryPoint';
  1124. this.params.spatialRel = 'esriSpatialRelIntersects';
  1125. this.params.units = 'esriSRUnit_Meter';
  1126. this.params.distance = radius;
  1127. this.params.inSR = 4326;
  1128. return this;
  1129. },
  1130. where: function (string) {
  1131. // instead of converting double-quotes to single quotes, pass as is, and provide a more informative message if a 400 is encountered
  1132. this.params.where = string;
  1133. return this;
  1134. },
  1135. between: function (start, end) {
  1136. this.params.time = [start.valueOf(), end.valueOf()];
  1137. return this;
  1138. },
  1139. simplify: function (map, factor) {
  1140. var mapWidth = Math.abs(map.getBounds().getWest() - map.getBounds().getEast());
  1141. this.params.maxAllowableOffset = (mapWidth / map.getSize().y) * factor;
  1142. return this;
  1143. },
  1144. orderBy: function (fieldName, order) {
  1145. order = order || 'ASC';
  1146. this.params.orderByFields = (this.params.orderByFields) ? this.params.orderByFields + ',' : '';
  1147. this.params.orderByFields += ([fieldName, order]).join(' ');
  1148. return this;
  1149. },
  1150. run: function (callback, context) {
  1151. this._cleanParams();
  1152. // services hosted on ArcGIS Online and ArcGIS Server 10.3.1+ support requesting geojson directly
  1153. if (this.options.isModern || (isArcgisOnline(this.options.url) && this.options.isModern === undefined)) {
  1154. this.params.f = 'geojson';
  1155. return this.request(function (error, response) {
  1156. this._trapSQLerrors(error);
  1157. callback.call(context, error, response, response);
  1158. }, this);
  1159. // otherwise convert it in the callback then pass it on
  1160. } else {
  1161. return this.request(function (error, response) {
  1162. this._trapSQLerrors(error);
  1163. callback.call(context, error, (response && responseToFeatureCollection(response)), response);
  1164. }, this);
  1165. }
  1166. },
  1167. count: function (callback, context) {
  1168. this._cleanParams();
  1169. this.params.returnCountOnly = true;
  1170. return this.request(function (error, response) {
  1171. callback.call(this, error, (response && response.count), response);
  1172. }, context);
  1173. },
  1174. ids: function (callback, context) {
  1175. this._cleanParams();
  1176. this.params.returnIdsOnly = true;
  1177. return this.request(function (error, response) {
  1178. callback.call(this, error, (response && response.objectIds), response);
  1179. }, context);
  1180. },
  1181. // only valid for Feature Services running on ArcGIS Server 10.3+ or ArcGIS Online
  1182. bounds: function (callback, context) {
  1183. this._cleanParams();
  1184. this.params.returnExtentOnly = true;
  1185. return this.request(function (error, response) {
  1186. if (response && response.extent && extentToBounds(response.extent)) {
  1187. callback.call(context, error, extentToBounds(response.extent), response);
  1188. } else {
  1189. error = {
  1190. message: 'Invalid Bounds'
  1191. };
  1192. callback.call(context, error, null, response);
  1193. }
  1194. }, context);
  1195. },
  1196. distinct: function () {
  1197. // geometry must be omitted for queries requesting distinct values
  1198. this.params.returnGeometry = false;
  1199. this.params.returnDistinctValues = true;
  1200. return this;
  1201. },
  1202. // only valid for image services
  1203. pixelSize: function (rawPoint) {
  1204. var castPoint = leaflet.point(rawPoint);
  1205. this.params.pixelSize = [castPoint.x, castPoint.y];
  1206. return this;
  1207. },
  1208. // only valid for map services
  1209. layer: function (layer) {
  1210. this.path = layer + '/query';
  1211. return this;
  1212. },
  1213. _trapSQLerrors: function (error) {
  1214. if (error) {
  1215. if (error.code === '400') {
  1216. warn('one common syntax error in query requests is encasing string values in double quotes instead of single quotes');
  1217. }
  1218. }
  1219. },
  1220. _cleanParams: function () {
  1221. delete this.params.returnIdsOnly;
  1222. delete this.params.returnExtentOnly;
  1223. delete this.params.returnCountOnly;
  1224. },
  1225. _setGeometryParams: function (geometry) {
  1226. this.params.inSR = 4326;
  1227. var converted = _setGeometry(geometry);
  1228. this.params.geometry = converted.geometry;
  1229. this.params.geometryType = converted.geometryType;
  1230. }
  1231. });
  1232. function query (options) {
  1233. return new Query(options);
  1234. }
  1235. var Find = Task.extend({
  1236. setters: {
  1237. // method name > param name
  1238. 'contains': 'contains',
  1239. 'text': 'searchText',
  1240. 'fields': 'searchFields', // denote an array or single string
  1241. 'spatialReference': 'sr',
  1242. 'sr': 'sr',
  1243. 'layers': 'layers',
  1244. 'returnGeometry': 'returnGeometry',
  1245. 'maxAllowableOffset': 'maxAllowableOffset',
  1246. 'precision': 'geometryPrecision',
  1247. 'dynamicLayers': 'dynamicLayers',
  1248. 'returnZ': 'returnZ',
  1249. 'returnM': 'returnM',
  1250. 'gdbVersion': 'gdbVersion',
  1251. // skipped implementing this (for now) because the REST service implementation isnt consistent between operations
  1252. // 'transform': 'datumTransformations',
  1253. 'token': 'token'
  1254. },
  1255. path: 'find',
  1256. params: {
  1257. sr: 4326,
  1258. contains: true,
  1259. returnGeometry: true,
  1260. returnZ: true,
  1261. returnM: false
  1262. },
  1263. layerDefs: function (id, where) {
  1264. this.params.layerDefs = (this.params.layerDefs) ? this.params.layerDefs + ';' : '';
  1265. this.params.layerDefs += ([id, where]).join(':');
  1266. return this;
  1267. },
  1268. simplify: function (map, factor) {
  1269. var mapWidth = Math.abs(map.getBounds().getWest() - map.getBounds().getEast());
  1270. this.params.maxAllowableOffset = (mapWidth / map.getSize().y) * factor;
  1271. return this;
  1272. },
  1273. run: function (callback, context) {
  1274. return this.request(function (error, response) {
  1275. callback.call(context, error, (response && responseToFeatureCollection(response)), response);
  1276. }, context);
  1277. }
  1278. });
  1279. function find (options) {
  1280. return new Find(options);
  1281. }
  1282. var Identify = Task.extend({
  1283. path: 'identify',
  1284. between: function (start, end) {
  1285. this.params.time = [start.valueOf(), end.valueOf()];
  1286. return this;
  1287. }
  1288. });
  1289. function identify (options) {
  1290. return new Identify(options);
  1291. }
  1292. var IdentifyFeatures = Identify.extend({
  1293. setters: {
  1294. 'layers': 'layers',
  1295. 'precision': 'geometryPrecision',
  1296. 'tolerance': 'tolerance',
  1297. // skipped implementing this (for now) because the REST service implementation isnt consistent between operations.
  1298. // 'transform': 'datumTransformations'
  1299. 'returnGeometry': 'returnGeometry'
  1300. },
  1301. params: {
  1302. sr: 4326,
  1303. layers: 'all',
  1304. tolerance: 3,
  1305. returnGeometry: true
  1306. },
  1307. on: function (map) {
  1308. var extent = boundsToExtent(map.getBounds());
  1309. var size = map.getSize();
  1310. this.params.imageDisplay = [size.x, size.y, 96];
  1311. this.params.mapExtent = [extent.xmin, extent.ymin, extent.xmax, extent.ymax];
  1312. return this;
  1313. },
  1314. at: function (geometry) {
  1315. // cast lat, long pairs in raw array form manually
  1316. if (geometry.length === 2) {
  1317. geometry = leaflet.latLng(geometry);
  1318. }
  1319. this._setGeometryParams(geometry);
  1320. return this;
  1321. },
  1322. layerDef: function (id, where) {
  1323. this.params.layerDefs = (this.params.layerDefs) ? this.params.layerDefs + ';' : '';
  1324. this.params.layerDefs += ([id, where]).join(':');
  1325. return this;
  1326. },
  1327. simplify: function (map, factor) {
  1328. var mapWidth = Math.abs(map.getBounds().getWest() - map.getBounds().getEast());
  1329. this.params.maxAllowableOffset = (mapWidth / map.getSize().y) * factor;
  1330. return this;
  1331. },
  1332. run: function (callback, context) {
  1333. return this.request(function (error, response) {
  1334. // immediately invoke with an error
  1335. if (error) {
  1336. callback.call(context, error, undefined, response);
  1337. return;
  1338. // ok no error lets just assume we have features...
  1339. } else {
  1340. var featureCollection = responseToFeatureCollection(response);
  1341. response.results = response.results.reverse();
  1342. for (var i = 0; i < featureCollection.features.length; i++) {
  1343. var feature = featureCollection.features[i];
  1344. feature.layerId = response.results[i].layerId;
  1345. }
  1346. callback.call(context, undefined, featureCollection, response);
  1347. }
  1348. });
  1349. },
  1350. _setGeometryParams: function (geometry) {
  1351. var converted = _setGeometry(geometry);
  1352. this.params.geometry = converted.geometry;
  1353. this.params.geometryType = converted.geometryType;
  1354. }
  1355. });
  1356. function identifyFeatures (options) {
  1357. return new IdentifyFeatures(options);
  1358. }
  1359. var IdentifyImage = Identify.extend({
  1360. setters: {
  1361. 'setMosaicRule': 'mosaicRule',
  1362. 'setRenderingRule': 'renderingRule',
  1363. 'setPixelSize': 'pixelSize',
  1364. 'returnCatalogItems': 'returnCatalogItems',
  1365. 'returnGeometry': 'returnGeometry'
  1366. },
  1367. params: {
  1368. returnGeometry: false
  1369. },
  1370. at: function (latlng) {
  1371. latlng = leaflet.latLng(latlng);
  1372. this.params.geometry = JSON.stringify({
  1373. x: latlng.lng,
  1374. y: latlng.lat,
  1375. spatialReference: {
  1376. wkid: 4326
  1377. }
  1378. });
  1379. this.params.geometryType = 'esriGeometryPoint';
  1380. return this;
  1381. },
  1382. getMosaicRule: function () {
  1383. return this.params.mosaicRule;
  1384. },
  1385. getRenderingRule: function () {
  1386. return this.params.renderingRule;
  1387. },
  1388. getPixelSize: function () {
  1389. return this.params.pixelSize;
  1390. },
  1391. run: function (callback, context) {
  1392. return this.request(function (error, response) {
  1393. callback.call(context, error, (response && this._responseToGeoJSON(response)), response);
  1394. }, this);
  1395. },
  1396. // get pixel data and return as geoJSON point
  1397. // populate catalog items (if any)
  1398. // merging in any catalogItemVisibilities as a propery of each feature
  1399. _responseToGeoJSON: function (response) {
  1400. var location = response.location;
  1401. var catalogItems = response.catalogItems;
  1402. var catalogItemVisibilities = response.catalogItemVisibilities;
  1403. var geoJSON = {
  1404. 'pixel': {
  1405. 'type': 'Feature',
  1406. 'geometry': {
  1407. 'type': 'Point',
  1408. 'coordinates': [location.x, location.y]
  1409. },
  1410. 'crs': {
  1411. 'type': 'EPSG',
  1412. 'properties': {
  1413. 'code': location.spatialReference.wkid
  1414. }
  1415. },
  1416. 'properties': {
  1417. 'OBJECTID': response.objectId,
  1418. 'name': response.name,
  1419. 'value': response.value
  1420. },
  1421. 'id': response.objectId
  1422. }
  1423. };
  1424. if (response.properties && response.properties.Values) {
  1425. geoJSON.pixel.properties.values = response.properties.Values;
  1426. }
  1427. if (catalogItems && catalogItems.features) {
  1428. geoJSON.catalogItems = responseToFeatureCollection(catalogItems);
  1429. if (catalogItemVisibilities && catalogItemVisibilities.length === geoJSON.catalogItems.features.length) {
  1430. for (var i = catalogItemVisibilities.length - 1; i >= 0; i--) {
  1431. geoJSON.catalogItems.features[i].properties.catalogItemVisibility = catalogItemVisibilities[i];
  1432. }
  1433. }
  1434. }
  1435. return geoJSON;
  1436. }
  1437. });
  1438. function identifyImage (params) {
  1439. return new IdentifyImage(params);
  1440. }
  1441. var Service = leaflet.Evented.extend({
  1442. options: {
  1443. proxy: false,
  1444. useCors: cors,
  1445. timeout: 0
  1446. },
  1447. initialize: function (options) {
  1448. options = options || {};
  1449. this._requestQueue = [];
  1450. this._authenticating = false;
  1451. leaflet.Util.setOptions(this, options);
  1452. this.options.url = cleanUrl(this.options.url);
  1453. },
  1454. get: function (path, params, callback, context) {
  1455. return this._request('get', path, params, callback, context);
  1456. },
  1457. post: function (path, params, callback, context) {
  1458. return this._request('post', path, params, callback, context);
  1459. },
  1460. request: function (path, params, callback, context) {
  1461. return this._request('request', path, params, callback, context);
  1462. },
  1463. metadata: function (callback, context) {
  1464. return this._request('get', '', {}, callback, context);
  1465. },
  1466. authenticate: function (token) {
  1467. this._authenticating = false;
  1468. this.options.token = token;
  1469. this._runQueue();
  1470. return this;
  1471. },
  1472. getTimeout: function () {
  1473. return this.options.timeout;
  1474. },
  1475. setTimeout: function (timeout) {
  1476. this.options.timeout = timeout;
  1477. },
  1478. _request: function (method, path, params, callback, context) {
  1479. this.fire('requeststart', {
  1480. url: this.options.url + path,
  1481. params: params,
  1482. method: method
  1483. }, true);
  1484. var wrappedCallback = this._createServiceCallback(method, path, params, callback, context);
  1485. if (this.options.token) {
  1486. params.token = this.options.token;
  1487. }
  1488. if (this.options.requestParams) {
  1489. leaflet.Util.extend(params, this.options.requestParams);
  1490. }
  1491. if (this._authenticating) {
  1492. this._requestQueue.push([method, path, params, callback, context]);
  1493. return;
  1494. } else {
  1495. var url = (this.options.proxy) ? this.options.proxy + '?' + this.options.url + path : this.options.url + path;
  1496. if ((method === 'get' || method === 'request') && !this.options.useCors) {
  1497. return Request.get.JSONP(url, params, wrappedCallback, context);
  1498. } else {
  1499. return Request[method](url, params, wrappedCallback, context);
  1500. }
  1501. }
  1502. },
  1503. _createServiceCallback: function (method, path, params, callback, context) {
  1504. return leaflet.Util.bind(function (error, response) {
  1505. if (error && (error.code === 499 || error.code === 498)) {
  1506. this._authenticating = true;
  1507. this._requestQueue.push([method, path, params, callback, context]);
  1508. // fire an event for users to handle and re-authenticate
  1509. this.fire('authenticationrequired', {
  1510. authenticate: leaflet.Util.bind(this.authenticate, this)
  1511. }, true);
  1512. // if the user has access to a callback they can handle the auth error
  1513. error.authenticate = leaflet.Util.bind(this.authenticate, this);
  1514. }
  1515. callback.call(context, error, response);
  1516. if (error) {
  1517. this.fire('requesterror', {
  1518. url: this.options.url + path,
  1519. params: params,
  1520. message: error.message,
  1521. code: error.code,
  1522. method: method
  1523. }, true);
  1524. } else {
  1525. this.fire('requestsuccess', {
  1526. url: this.options.url + path,
  1527. params: params,
  1528. response: response,
  1529. method: method
  1530. }, true);
  1531. }
  1532. this.fire('requestend', {
  1533. url: this.options.url + path,
  1534. params: params,
  1535. method: method
  1536. }, true);
  1537. }, this);
  1538. },
  1539. _runQueue: function () {
  1540. for (var i = this._requestQueue.length - 1; i >= 0; i--) {
  1541. var request = this._requestQueue[i];
  1542. var method = request.shift();
  1543. this[method].apply(this, request);
  1544. }
  1545. this._requestQueue = [];
  1546. }
  1547. });
  1548. function service (options) {
  1549. options = getUrlParams(options);
  1550. return new Service(options);
  1551. }
  1552. var MapService = Service.extend({
  1553. identify: function () {
  1554. return identifyFeatures(this);
  1555. },
  1556. find: function () {
  1557. return find(this);
  1558. },
  1559. query: function () {
  1560. return query(this);
  1561. }
  1562. });
  1563. function mapService (options) {
  1564. return new MapService(options);
  1565. }
  1566. var ImageService = Service.extend({
  1567. query: function () {
  1568. return query(this);
  1569. },
  1570. identify: function () {
  1571. return identifyImage(this);
  1572. }
  1573. });
  1574. function imageService (options) {
  1575. return new ImageService(options);
  1576. }
  1577. var FeatureLayerService = Service.extend({
  1578. options: {
  1579. idAttribute: 'OBJECTID'
  1580. },
  1581. query: function () {
  1582. return query(this);
  1583. },
  1584. addFeature: function (feature, callback, context) {
  1585. this.addFeatures(feature, callback, context);
  1586. },
  1587. addFeatures: function (features, callback, context) {
  1588. var featuresArray = features.features ? features.features : [features];
  1589. for (var i = featuresArray.length - 1; i >= 0; i--) {
  1590. delete featuresArray[i].id;
  1591. }
  1592. features = geojsonToArcGIS(features);
  1593. features = featuresArray.length > 1 ? features : [features];
  1594. return this.post('addFeatures', {
  1595. features: features
  1596. }, function (error, response) {
  1597. // For compatibility reason with former addFeature function,
  1598. // we return the object in the array and not the array itself
  1599. var result = (response && response.addResults) ? response.addResults.length > 1 ? response.addResults : response.addResults[0] : undefined;
  1600. if (callback) {
  1601. callback.call(context, error || response.addResults[0].error, result);
  1602. }
  1603. }, context);
  1604. },
  1605. updateFeature: function (feature, callback, context) {
  1606. this.updateFeatures(feature, callback, context);
  1607. },
  1608. updateFeatures: function (features, callback, context) {
  1609. var featuresArray = features.features ? features.features : [features];
  1610. features = geojsonToArcGIS(features, this.options.idAttribute);
  1611. features = featuresArray.length > 1 ? features : [features];
  1612. return this.post('updateFeatures', {
  1613. features: features
  1614. }, function (error, response) {
  1615. // For compatibility reason with former updateFeature function,
  1616. // we return the object in the array and not the array itself
  1617. var result = (response && response.updateResults) ? response.updateResults.length > 1 ? response.updateResults : response.updateResults[0] : undefined;
  1618. if (callback) {
  1619. callback.call(context, error || response.updateResults[0].error, result);
  1620. }
  1621. }, context);
  1622. },
  1623. deleteFeature: function (id, callback, context) {
  1624. this.deleteFeatures(id, callback, context);
  1625. },
  1626. deleteFeatures: function (ids, callback, context) {
  1627. return this.post('deleteFeatures', {
  1628. objectIds: ids
  1629. }, function (error, response) {
  1630. // For compatibility reason with former deleteFeature function,
  1631. // we return the object in the array and not the array itself
  1632. var result = (response && response.deleteResults) ? response.deleteResults.length > 1 ? response.deleteResults : response.deleteResults[0] : undefined;
  1633. if (callback) {
  1634. callback.call(context, error || response.deleteResults[0].error, result);
  1635. }
  1636. }, context);
  1637. }
  1638. });
  1639. function featureLayerService (options) {
  1640. return new FeatureLayerService(options);
  1641. }
  1642. var tileProtocol = (window.location.protocol !== 'https:') ? 'http:' : 'https:';
  1643. var BasemapLayer = leaflet.TileLayer.extend({
  1644. statics: {
  1645. TILES: {
  1646. Streets: {
  1647. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',
  1648. options: {
  1649. minZoom: 1,
  1650. maxZoom: 19,
  1651. subdomains: ['server', 'services'],
  1652. attribution: 'USGS, NOAA',
  1653. attributionUrl: 'https://static.arcgis.com/attribution/World_Street_Map'
  1654. }
  1655. },
  1656. Topographic: {
  1657. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}',
  1658. options: {
  1659. minZoom: 1,
  1660. maxZoom: 19,
  1661. subdomains: ['server', 'services'],
  1662. attribution: 'USGS, NOAA',
  1663. attributionUrl: 'https://static.arcgis.com/attribution/World_Topo_Map'
  1664. }
  1665. },
  1666. Oceans: {
  1667. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Base/MapServer/tile/{z}/{y}/{x}',
  1668. options: {
  1669. minZoom: 1,
  1670. maxZoom: 16,
  1671. subdomains: ['server', 'services'],
  1672. attribution: 'USGS, NOAA',
  1673. attributionUrl: 'https://static.arcgis.com/attribution/Ocean_Basemap'
  1674. }
  1675. },
  1676. OceansLabels: {
  1677. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Reference/MapServer/tile/{z}/{y}/{x}',
  1678. options: {
  1679. minZoom: 1,
  1680. maxZoom: 16,
  1681. subdomains: ['server', 'services'],
  1682. pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
  1683. attribution: ''
  1684. }
  1685. },
  1686. NationalGeographic: {
  1687. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}',
  1688. options: {
  1689. minZoom: 1,
  1690. maxZoom: 16,
  1691. subdomains: ['server', 'services'],
  1692. attribution: 'National Geographic, DeLorme, HERE, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, increment P Corp.'
  1693. }
  1694. },
  1695. DarkGray: {
  1696. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}',
  1697. options: {
  1698. minZoom: 1,
  1699. maxZoom: 16,
  1700. subdomains: ['server', 'services'],
  1701. attribution: 'HERE, DeLorme, MapmyIndia, &copy; OpenStreetMap contributors'
  1702. }
  1703. },
  1704. DarkGrayLabels: {
  1705. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Reference/MapServer/tile/{z}/{y}/{x}',
  1706. options: {
  1707. minZoom: 1,
  1708. maxZoom: 16,
  1709. subdomains: ['server', 'services'],
  1710. pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
  1711. attribution: ''
  1712. }
  1713. },
  1714. Gray: {
  1715. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}',
  1716. options: {
  1717. minZoom: 1,
  1718. maxZoom: 16,
  1719. subdomains: ['server', 'services'],
  1720. attribution: 'HERE, DeLorme, MapmyIndia, &copy; OpenStreetMap contributors'
  1721. }
  1722. },
  1723. GrayLabels: {
  1724. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Reference/MapServer/tile/{z}/{y}/{x}',
  1725. options: {
  1726. minZoom: 1,
  1727. maxZoom: 16,
  1728. subdomains: ['server', 'services'],
  1729. pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
  1730. attribution: ''
  1731. }
  1732. },
  1733. Imagery: {
  1734. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
  1735. options: {
  1736. minZoom: 1,
  1737. maxZoom: 19,
  1738. subdomains: ['server', 'services'],
  1739. attribution: 'DigitalGlobe, GeoEye, i-cubed, USDA, USGS, AEX, Getmapping, Aerogrid, IGN, IGP, swisstopo, and the GIS User Community',
  1740. attributionUrl: 'https://static.arcgis.com/attribution/World_Imagery'
  1741. }
  1742. },
  1743. ImageryLabels: {
  1744. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}',
  1745. options: {
  1746. minZoom: 1,
  1747. maxZoom: 19,
  1748. subdomains: ['server', 'services'],
  1749. pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
  1750. attribution: ''
  1751. }
  1752. },
  1753. ImageryTransportation: {
  1754. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer/tile/{z}/{y}/{x}',
  1755. options: {
  1756. minZoom: 1,
  1757. maxZoom: 19,
  1758. subdomains: ['server', 'services'],
  1759. pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
  1760. attribution: ''
  1761. }
  1762. },
  1763. ShadedRelief: {
  1764. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}',
  1765. options: {
  1766. minZoom: 1,
  1767. maxZoom: 13,
  1768. subdomains: ['server', 'services'],
  1769. attribution: 'USGS'
  1770. }
  1771. },
  1772. ShadedReliefLabels: {
  1773. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places_Alternate/MapServer/tile/{z}/{y}/{x}',
  1774. options: {
  1775. minZoom: 1,
  1776. maxZoom: 12,
  1777. subdomains: ['server', 'services'],
  1778. pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
  1779. attribution: ''
  1780. }
  1781. },
  1782. Terrain: {
  1783. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/{z}/{y}/{x}',
  1784. options: {
  1785. minZoom: 1,
  1786. maxZoom: 13,
  1787. subdomains: ['server', 'services'],
  1788. attribution: 'USGS, NOAA'
  1789. }
  1790. },
  1791. TerrainLabels: {
  1792. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Reference_Overlay/MapServer/tile/{z}/{y}/{x}',
  1793. options: {
  1794. minZoom: 1,
  1795. maxZoom: 13,
  1796. subdomains: ['server', 'services'],
  1797. pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
  1798. attribution: ''
  1799. }
  1800. },
  1801. USATopo: {
  1802. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/USA_Topo_Maps/MapServer/tile/{z}/{y}/{x}',
  1803. options: {
  1804. minZoom: 1,
  1805. maxZoom: 15,
  1806. subdomains: ['server', 'services'],
  1807. attribution: 'USGS, National Geographic Society, i-cubed'
  1808. }
  1809. },
  1810. ImageryClarity: {
  1811. urlTemplate: tileProtocol + '//clarity.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
  1812. options: {
  1813. minZoom: 1,
  1814. maxZoom: 19,
  1815. attribution: 'Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community'
  1816. }
  1817. },
  1818. Physical: {
  1819. urlTemplate: tileProtocol + '//{s}.arcgisonline.com/arcgis/rest/services/World_Physical_Map/MapServer/tile/{z}/{y}/{x}',
  1820. options: {
  1821. minZoom: 1,
  1822. maxZoom: 8,
  1823. subdomains: ['server', 'services'],
  1824. attribution: 'U.S. National Park Service'
  1825. }
  1826. },
  1827. ImageryFirefly: {
  1828. urlTemplate: tileProtocol + '//fly.maptiles.arcgis.com/arcgis/rest/services/World_Imagery_Firefly/MapServer/tile/{z}/{y}/{x}',
  1829. options: {
  1830. minZoom: 1,
  1831. maxZoom: 19,
  1832. attribution: 'Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community',
  1833. attributionUrl: 'https://static.arcgis.com/attribution/World_Imagery'
  1834. }
  1835. }
  1836. }
  1837. },
  1838. initialize: function (key, options) {
  1839. var config;
  1840. // set the config variable with the appropriate config object
  1841. if (typeof key === 'object' && key.urlTemplate && key.options) {
  1842. config = key;
  1843. } else if (typeof key === 'string' && BasemapLayer.TILES[key]) {
  1844. config = BasemapLayer.TILES[key];
  1845. } else {
  1846. throw new Error('L.esri.BasemapLayer: Invalid parameter. Use one of "Streets", "Topographic", "Oceans", "OceansLabels", "NationalGeographic", "Physical", "Gray", "GrayLabels", "DarkGray", "DarkGrayLabels", "Imagery", "ImageryLabels", "ImageryTransportation", "ImageryClarity", "ImageryFirefly", ShadedRelief", "ShadedReliefLabels", "Terrain", "TerrainLabels" or "USATopo"');
  1847. }
  1848. // merge passed options into the config options
  1849. var tileOptions = leaflet.Util.extend(config.options, options);
  1850. leaflet.Util.setOptions(this, tileOptions);
  1851. // Deprecation notice:
  1852. if (!this.options.ignoreDeprecationWarning) {
  1853. console.warn('WARNING: L.esri.BasemapLayer uses data services that are in mature support and are not being updated. Please use L.esri.Vector.vectorBasemapLayer instead. More info: https://esriurl.com/esri-leaflet-basemap');
  1854. }
  1855. if (this.options.token && config.urlTemplate.indexOf('token=') === -1) {
  1856. config.urlTemplate += ('?token=' + this.options.token);
  1857. }
  1858. if (this.options.proxy) {
  1859. config.urlTemplate = this.options.proxy + '?' + config.urlTemplate;
  1860. }
  1861. // call the initialize method on L.TileLayer to set everything up
  1862. leaflet.TileLayer.prototype.initialize.call(this, config.urlTemplate, tileOptions);
  1863. },
  1864. onAdd: function (map) {
  1865. // include 'Powered by Esri' in map attribution
  1866. setEsriAttribution(map);
  1867. if (this.options.pane === 'esri-labels') {
  1868. this._initPane();
  1869. }
  1870. // some basemaps can supply dynamic attribution
  1871. if (this.options.attributionUrl) {
  1872. _getAttributionData((this.options.proxy ? this.options.proxy + '?' : '') + this.options.attributionUrl, map);
  1873. }
  1874. map.on('moveend', _updateMapAttribution);
  1875. leaflet.TileLayer.prototype.onAdd.call(this, map);
  1876. },
  1877. onRemove: function (map) {
  1878. removeEsriAttribution(map);
  1879. map.off('moveend', _updateMapAttribution);
  1880. leaflet.TileLayer.prototype.onRemove.call(this, map);
  1881. },
  1882. _initPane: function () {
  1883. if (!this._map.getPane(this.options.pane)) {
  1884. var pane = this._map.createPane(this.options.pane);
  1885. pane.style.pointerEvents = 'none';
  1886. pane.style.zIndex = 500;
  1887. }
  1888. },
  1889. getAttribution: function () {
  1890. if (this.options.attribution) {
  1891. var attribution = '<span class="esri-dynamic-attribution">' + this.options.attribution + '</span>';
  1892. }
  1893. return attribution;
  1894. }
  1895. });
  1896. function basemapLayer (key, options) {
  1897. return new BasemapLayer(key, options);
  1898. }
  1899. var TiledMapLayer = leaflet.TileLayer.extend({
  1900. options: {
  1901. zoomOffsetAllowance: 0.1,
  1902. errorTileUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEABAMAAACuXLVVAAAAA1BMVEUzNDVszlHHAAAAAXRSTlMAQObYZgAAAAlwSFlzAAAAAAAAAAAB6mUWpAAAADZJREFUeJztwQEBAAAAgiD/r25IQAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7waBAAABw08RwAAAAABJRU5ErkJggg=='
  1903. },
  1904. statics: {
  1905. MercatorZoomLevels: {
  1906. '0': 156543.03392799999,
  1907. '1': 78271.516963999893,
  1908. '2': 39135.758482000099,
  1909. '3': 19567.879240999901,
  1910. '4': 9783.9396204999593,
  1911. '5': 4891.9698102499797,
  1912. '6': 2445.9849051249898,
  1913. '7': 1222.9924525624899,
  1914. '8': 611.49622628138002,
  1915. '9': 305.74811314055802,
  1916. '10': 152.874056570411,
  1917. '11': 76.437028285073197,
  1918. '12': 38.218514142536598,
  1919. '13': 19.109257071268299,
  1920. '14': 9.5546285356341496,
  1921. '15': 4.7773142679493699,
  1922. '16': 2.38865713397468,
  1923. '17': 1.1943285668550501,
  1924. '18': 0.59716428355981699,
  1925. '19': 0.29858214164761698,
  1926. '20': 0.14929107082381,
  1927. '21': 0.07464553541191,
  1928. '22': 0.0373227677059525,
  1929. '23': 0.0186613838529763
  1930. }
  1931. },
  1932. initialize: function (options) {
  1933. options = leaflet.Util.setOptions(this, options);
  1934. // set the urls
  1935. options = getUrlParams(options);
  1936. this.tileUrl = (options.proxy ? options.proxy + '?' : '') + options.url + 'tile/{z}/{y}/{x}' + (options.requestParams && Object.keys(options.requestParams).length > 0 ? leaflet.Util.getParamString(options.requestParams) : '');
  1937. // Remove subdomain in url
  1938. // https://github.com/Esri/esri-leaflet/issues/991
  1939. if (options.url.indexOf('{s}') !== -1 && options.subdomains) {
  1940. options.url = options.url.replace('{s}', options.subdomains[0]);
  1941. }
  1942. this.service = mapService(options);
  1943. this.service.addEventParent(this);
  1944. var arcgisonline = new RegExp(/tiles.arcgis(online)?\.com/g);
  1945. if (arcgisonline.test(options.url)) {
  1946. this.tileUrl = this.tileUrl.replace('://tiles', '://tiles{s}');
  1947. options.subdomains = ['1', '2', '3', '4'];
  1948. }
  1949. if (this.options.token) {
  1950. this.tileUrl += ('?token=' + this.options.token);
  1951. }
  1952. // init layer by calling TileLayers initialize method
  1953. leaflet.TileLayer.prototype.initialize.call(this, this.tileUrl, options);
  1954. },
  1955. getTileUrl: function (tilePoint) {
  1956. var zoom = this._getZoomForUrl();
  1957. return leaflet.Util.template(this.tileUrl, leaflet.Util.extend({
  1958. s: this._getSubdomain(tilePoint),
  1959. x: tilePoint.x,
  1960. y: tilePoint.y,
  1961. // try lod map first, then just default to zoom level
  1962. z: (this._lodMap && this._lodMap[zoom]) ? this._lodMap[zoom] : zoom
  1963. }, this.options));
  1964. },
  1965. createTile: function (coords, done) {
  1966. var tile = document.createElement('img');
  1967. leaflet.DomEvent.on(tile, 'load', leaflet.Util.bind(this._tileOnLoad, this, done, tile));
  1968. leaflet.DomEvent.on(tile, 'error', leaflet.Util.bind(this._tileOnError, this, done, tile));
  1969. if (this.options.crossOrigin) {
  1970. tile.crossOrigin = '';
  1971. }
  1972. /*
  1973. Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
  1974. http://www.w3.org/TR/WCAG20-TECHS/H67
  1975. */
  1976. tile.alt = '';
  1977. // if there is no lod map or an lod map with a proper zoom load the tile
  1978. // otherwise wait for the lod map to become available
  1979. if (!this._lodMap || (this._lodMap && this._lodMap[this._getZoomForUrl()])) {
  1980. tile.src = this.getTileUrl(coords);
  1981. } else {
  1982. this.once('lodmap', function () {
  1983. tile.src = this.getTileUrl(coords);
  1984. }, this);
  1985. }
  1986. return tile;
  1987. },
  1988. onAdd: function (map) {
  1989. // include 'Powered by Esri' in map attribution
  1990. setEsriAttribution(map);
  1991. if (!this._lodMap) {
  1992. this.metadata(function (error, metadata) {
  1993. if (!error && metadata.spatialReference) {
  1994. var sr = metadata.spatialReference.latestWkid || metadata.spatialReference.wkid;
  1995. // display the copyright text from the service using leaflet's attribution control
  1996. if (!this.options.attribution && map.attributionControl && metadata.copyrightText) {
  1997. this.options.attribution = metadata.copyrightText;
  1998. map.attributionControl.addAttribution(this.getAttribution());
  1999. }
  2000. // if the service tiles were published in web mercator using conventional LODs but missing levels, we can try and remap them
  2001. if (map.options.crs === leaflet.CRS.EPSG3857 && (sr === 102100 || sr === 3857)) {
  2002. this._lodMap = {};
  2003. // create the zoom level data
  2004. var arcgisLODs = metadata.tileInfo.lods;
  2005. var correctResolutions = TiledMapLayer.MercatorZoomLevels;
  2006. for (var i = 0; i < arcgisLODs.length; i++) {
  2007. var arcgisLOD = arcgisLODs[i];
  2008. for (var ci in correctResolutions) {
  2009. var correctRes = correctResolutions[ci];
  2010. if (this._withinPercentage(arcgisLOD.resolution, correctRes, this.options.zoomOffsetAllowance)) {
  2011. this._lodMap[ci] = arcgisLOD.level;
  2012. break;
  2013. }
  2014. }
  2015. }
  2016. this.fire('lodmap');
  2017. } else if (map.options.crs && map.options.crs.code && (map.options.crs.code.indexOf(sr) > -1)) ; else {
  2018. // if the service was cached in a custom projection and an appropriate LOD hasn't been defined in the map, guide the developer to our Proj4 sample
  2019. warn('L.esri.TiledMapLayer is using a non-mercator spatial reference. Support may be available through Proj4Leaflet http://esri.github.io/esri-leaflet/examples/non-mercator-projection.html');
  2020. }
  2021. }
  2022. }, this);
  2023. }
  2024. leaflet.TileLayer.prototype.onAdd.call(this, map);
  2025. },
  2026. onRemove: function (map) {
  2027. removeEsriAttribution(map);
  2028. leaflet.TileLayer.prototype.onRemove.call(this, map);
  2029. },
  2030. metadata: function (callback, context) {
  2031. this.service.metadata(callback, context);
  2032. return this;
  2033. },
  2034. identify: function () {
  2035. return this.service.identify();
  2036. },
  2037. find: function () {
  2038. return this.service.find();
  2039. },
  2040. query: function () {
  2041. return this.service.query();
  2042. },
  2043. authenticate: function (token) {
  2044. var tokenQs = '?token=' + token;
  2045. this.tileUrl = (this.options.token) ? this.tileUrl.replace(/\?token=(.+)/g, tokenQs) : this.tileUrl + tokenQs;
  2046. this.options.token = token;
  2047. this.service.authenticate(token);
  2048. return this;
  2049. },
  2050. _withinPercentage: function (a, b, percentage) {
  2051. var diff = Math.abs((a / b) - 1);
  2052. return diff < percentage;
  2053. }
  2054. });
  2055. function tiledMapLayer (url, options) {
  2056. return new TiledMapLayer(url, options);
  2057. }
  2058. var Overlay = leaflet.ImageOverlay.extend({
  2059. onAdd: function (map) {
  2060. this._topLeft = map.getPixelBounds().min;
  2061. leaflet.ImageOverlay.prototype.onAdd.call(this, map);
  2062. },
  2063. _reset: function () {
  2064. if (this._map.options.crs === leaflet.CRS.EPSG3857) {
  2065. leaflet.ImageOverlay.prototype._reset.call(this);
  2066. } else {
  2067. leaflet.DomUtil.setPosition(this._image, this._topLeft.subtract(this._map.getPixelOrigin()));
  2068. }
  2069. }
  2070. });
  2071. var RasterLayer = leaflet.Layer.extend({
  2072. options: {
  2073. opacity: 1,
  2074. position: 'front',
  2075. f: 'image',
  2076. useCors: cors,
  2077. attribution: null,
  2078. interactive: false,
  2079. alt: ''
  2080. },
  2081. onAdd: function (map) {
  2082. // include 'Powered by Esri' in map attribution
  2083. setEsriAttribution(map);
  2084. if (this.options.zIndex) {
  2085. this.options.position = null;
  2086. }
  2087. this._update = leaflet.Util.throttle(this._update, this.options.updateInterval, this);
  2088. map.on('moveend', this._update, this);
  2089. // if we had an image loaded and it matches the
  2090. // current bounds show the image otherwise remove it
  2091. if (this._currentImage && this._currentImage._bounds.equals(this._map.getBounds())) {
  2092. map.addLayer(this._currentImage);
  2093. } else if (this._currentImage) {
  2094. this._map.removeLayer(this._currentImage);
  2095. this._currentImage = null;
  2096. }
  2097. this._update();
  2098. if (this._popup) {
  2099. this._map.on('click', this._getPopupData, this);
  2100. this._map.on('dblclick', this._resetPopupState, this);
  2101. }
  2102. // add copyright text listed in service metadata
  2103. this.metadata(function (err, metadata) {
  2104. if (!err && !this.options.attribution && map.attributionControl && metadata.copyrightText) {
  2105. this.options.attribution = metadata.copyrightText;
  2106. map.attributionControl.addAttribution(this.getAttribution());
  2107. }
  2108. }, this);
  2109. },
  2110. onRemove: function (map) {
  2111. removeEsriAttribution(map);
  2112. if (this._currentImage) {
  2113. this._map.removeLayer(this._currentImage);
  2114. }
  2115. if (this._popup) {
  2116. this._map.off('click', this._getPopupData, this);
  2117. this._map.off('dblclick', this._resetPopupState, this);
  2118. }
  2119. this._map.off('moveend', this._update, this);
  2120. },
  2121. bindPopup: function (fn, popupOptions) {
  2122. this._shouldRenderPopup = false;
  2123. this._lastClick = false;
  2124. this._popup = leaflet.popup(popupOptions);
  2125. this._popupFunction = fn;
  2126. if (this._map) {
  2127. this._map.on('click', this._getPopupData, this);
  2128. this._map.on('dblclick', this._resetPopupState, this);
  2129. }
  2130. return this;
  2131. },
  2132. unbindPopup: function () {
  2133. if (this._map) {
  2134. this._map.closePopup(this._popup);
  2135. this._map.off('click', this._getPopupData, this);
  2136. this._map.off('dblclick', this._resetPopupState, this);
  2137. }
  2138. this._popup = false;
  2139. return this;
  2140. },
  2141. bringToFront: function () {
  2142. this.options.position = 'front';
  2143. if (this._currentImage) {
  2144. this._currentImage.bringToFront();
  2145. this._setAutoZIndex(Math.max);
  2146. }
  2147. return this;
  2148. },
  2149. bringToBack: function () {
  2150. this.options.position = 'back';
  2151. if (this._currentImage) {
  2152. this._currentImage.bringToBack();
  2153. this._setAutoZIndex(Math.min);
  2154. }
  2155. return this;
  2156. },
  2157. setZIndex: function (value) {
  2158. this.options.zIndex = value;
  2159. if (this._currentImage) {
  2160. this._currentImage.setZIndex(value);
  2161. }
  2162. return this;
  2163. },
  2164. _setAutoZIndex: function (compare) {
  2165. // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
  2166. if (!this._currentImage) {
  2167. return;
  2168. }
  2169. var layers = this._currentImage.getPane().children;
  2170. var edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
  2171. for (var i = 0, len = layers.length, zIndex; i < len; i++) {
  2172. zIndex = layers[i].style.zIndex;
  2173. if (layers[i] !== this._currentImage._image && zIndex) {
  2174. edgeZIndex = compare(edgeZIndex, +zIndex);
  2175. }
  2176. }
  2177. if (isFinite(edgeZIndex)) {
  2178. this.options.zIndex = edgeZIndex + compare(-1, 1);
  2179. this.setZIndex(this.options.zIndex);
  2180. }
  2181. },
  2182. getAttribution: function () {
  2183. return this.options.attribution;
  2184. },
  2185. getOpacity: function () {
  2186. return this.options.opacity;
  2187. },
  2188. setOpacity: function (opacity) {
  2189. this.options.opacity = opacity;
  2190. if (this._currentImage) {
  2191. this._currentImage.setOpacity(opacity);
  2192. }
  2193. return this;
  2194. },
  2195. getTimeRange: function () {
  2196. return [this.options.from, this.options.to];
  2197. },
  2198. setTimeRange: function (from, to) {
  2199. this.options.from = from;
  2200. this.options.to = to;
  2201. this._update();
  2202. return this;
  2203. },
  2204. metadata: function (callback, context) {
  2205. this.service.metadata(callback, context);
  2206. return this;
  2207. },
  2208. authenticate: function (token) {
  2209. this.service.authenticate(token);
  2210. return this;
  2211. },
  2212. redraw: function () {
  2213. this._update();
  2214. },
  2215. _renderImage: function (url, bounds, contentType) {
  2216. if (this._map) {
  2217. // if no output directory has been specified for a service, MIME data will be returned
  2218. if (contentType) {
  2219. url = 'data:' + contentType + ';base64,' + url;
  2220. }
  2221. // if server returns an inappropriate response, abort.
  2222. if (!url) return;
  2223. // create a new image overlay and add it to the map
  2224. // to start loading the image
  2225. // opacity is 0 while the image is loading
  2226. var image = new Overlay(url, bounds, {
  2227. opacity: 0,
  2228. crossOrigin: this.options.withCredentials ? 'use-credentials' : this.options.useCors,
  2229. alt: this.options.alt,
  2230. pane: this.options.pane || this.getPane(),
  2231. interactive: this.options.interactive
  2232. }).addTo(this._map);
  2233. var onOverlayError = function () {
  2234. this._map.removeLayer(image);
  2235. this.fire('error');
  2236. image.off('load', onOverlayLoad, this);
  2237. };
  2238. var onOverlayLoad = function (e) {
  2239. image.off('error', onOverlayLoad, this);
  2240. if (this._map) {
  2241. var newImage = e.target;
  2242. var oldImage = this._currentImage;
  2243. // if the bounds of this image matches the bounds that
  2244. // _renderImage was called with and we have a map with the same bounds
  2245. // hide the old image if there is one and set the opacity
  2246. // of the new image otherwise remove the new image
  2247. if (newImage._bounds.equals(bounds) && newImage._bounds.equals(this._map.getBounds())) {
  2248. this._currentImage = newImage;
  2249. if (this.options.position === 'front') {
  2250. this.bringToFront();
  2251. } else if (this.options.position === 'back') {
  2252. this.bringToBack();
  2253. }
  2254. if (this.options.zIndex) {
  2255. this.setZIndex(this.options.zIndex);
  2256. }
  2257. if (this._map && this._currentImage._map) {
  2258. this._currentImage.setOpacity(this.options.opacity);
  2259. } else {
  2260. this._currentImage._map.removeLayer(this._currentImage);
  2261. }
  2262. if (oldImage && this._map) {
  2263. this._map.removeLayer(oldImage);
  2264. }
  2265. if (oldImage && oldImage._map) {
  2266. oldImage._map.removeLayer(oldImage);
  2267. }
  2268. } else {
  2269. this._map.removeLayer(newImage);
  2270. }
  2271. }
  2272. this.fire('load', {
  2273. bounds: bounds
  2274. });
  2275. };
  2276. // If loading the image fails
  2277. image.once('error', onOverlayError, this);
  2278. // once the image loads
  2279. image.once('load', onOverlayLoad, this);
  2280. }
  2281. },
  2282. _update: function () {
  2283. if (!this._map) {
  2284. return;
  2285. }
  2286. var zoom = this._map.getZoom();
  2287. var bounds = this._map.getBounds();
  2288. if (this._animatingZoom) {
  2289. return;
  2290. }
  2291. if (this._map._panTransition && this._map._panTransition._inProgress) {
  2292. return;
  2293. }
  2294. if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
  2295. if (this._currentImage) {
  2296. this._currentImage._map.removeLayer(this._currentImage);
  2297. this._currentImage = null;
  2298. }
  2299. return;
  2300. }
  2301. var params = this._buildExportParams();
  2302. leaflet.Util.extend(params, this.options.requestParams);
  2303. if (params) {
  2304. this._requestExport(params, bounds);
  2305. this.fire('loading', {
  2306. bounds: bounds
  2307. });
  2308. } else if (this._currentImage) {
  2309. this._currentImage._map.removeLayer(this._currentImage);
  2310. this._currentImage = null;
  2311. }
  2312. },
  2313. _renderPopup: function (latlng, error, results, response) {
  2314. latlng = leaflet.latLng(latlng);
  2315. if (this._shouldRenderPopup && this._lastClick.equals(latlng)) {
  2316. // add the popup to the map where the mouse was clicked at
  2317. var content = this._popupFunction(error, results, response);
  2318. if (content) {
  2319. this._popup.setLatLng(latlng).setContent(content).openOn(this._map);
  2320. }
  2321. }
  2322. },
  2323. _resetPopupState: function (e) {
  2324. this._shouldRenderPopup = false;
  2325. this._lastClick = e.latlng;
  2326. },
  2327. _calculateBbox: function () {
  2328. var pixelBounds = this._map.getPixelBounds();
  2329. var sw = this._map.unproject(pixelBounds.getBottomLeft());
  2330. var ne = this._map.unproject(pixelBounds.getTopRight());
  2331. var neProjected = this._map.options.crs.project(ne);
  2332. var swProjected = this._map.options.crs.project(sw);
  2333. // this ensures ne/sw are switched in polar maps where north/top bottom/south is inverted
  2334. var boundsProjected = leaflet.bounds(neProjected, swProjected);
  2335. return [boundsProjected.getBottomLeft().x, boundsProjected.getBottomLeft().y, boundsProjected.getTopRight().x, boundsProjected.getTopRight().y].join(',');
  2336. },
  2337. _calculateImageSize: function () {
  2338. // ensure that we don't ask ArcGIS Server for a taller image than we have actual map displaying within the div
  2339. var bounds = this._map.getPixelBounds();
  2340. var size = this._map.getSize();
  2341. var sw = this._map.unproject(bounds.getBottomLeft());
  2342. var ne = this._map.unproject(bounds.getTopRight());
  2343. var top = this._map.latLngToLayerPoint(ne).y;
  2344. var bottom = this._map.latLngToLayerPoint(sw).y;
  2345. if (top > 0 || bottom < size.y) {
  2346. size.y = bottom - top;
  2347. }
  2348. return size.x + ',' + size.y;
  2349. }
  2350. });
  2351. var ImageMapLayer = RasterLayer.extend({
  2352. options: {
  2353. updateInterval: 150,
  2354. format: 'jpgpng',
  2355. transparent: true,
  2356. f: 'image'
  2357. },
  2358. query: function () {
  2359. return this.service.query();
  2360. },
  2361. identify: function () {
  2362. return this.service.identify();
  2363. },
  2364. initialize: function (options) {
  2365. options = getUrlParams(options);
  2366. this.service = imageService(options);
  2367. this.service.addEventParent(this);
  2368. leaflet.Util.setOptions(this, options);
  2369. },
  2370. setPixelType: function (pixelType) {
  2371. this.options.pixelType = pixelType;
  2372. this._update();
  2373. return this;
  2374. },
  2375. getPixelType: function () {
  2376. return this.options.pixelType;
  2377. },
  2378. setBandIds: function (bandIds) {
  2379. if (leaflet.Util.isArray(bandIds)) {
  2380. this.options.bandIds = bandIds.join(',');
  2381. } else {
  2382. this.options.bandIds = bandIds.toString();
  2383. }
  2384. this._update();
  2385. return this;
  2386. },
  2387. getBandIds: function () {
  2388. return this.options.bandIds;
  2389. },
  2390. setNoData: function (noData, noDataInterpretation) {
  2391. if (leaflet.Util.isArray(noData)) {
  2392. this.options.noData = noData.join(',');
  2393. } else {
  2394. this.options.noData = noData.toString();
  2395. }
  2396. if (noDataInterpretation) {
  2397. this.options.noDataInterpretation = noDataInterpretation;
  2398. }
  2399. this._update();
  2400. return this;
  2401. },
  2402. getNoData: function () {
  2403. return this.options.noData;
  2404. },
  2405. getNoDataInterpretation: function () {
  2406. return this.options.noDataInterpretation;
  2407. },
  2408. setRenderingRule: function (renderingRule) {
  2409. this.options.renderingRule = renderingRule;
  2410. this._update();
  2411. },
  2412. getRenderingRule: function () {
  2413. return this.options.renderingRule;
  2414. },
  2415. setMosaicRule: function (mosaicRule) {
  2416. this.options.mosaicRule = mosaicRule;
  2417. this._update();
  2418. },
  2419. getMosaicRule: function () {
  2420. return this.options.mosaicRule;
  2421. },
  2422. _getPopupData: function (e) {
  2423. var callback = leaflet.Util.bind(function (error, results, response) {
  2424. if (error) { return; } // we really can't do anything here but authenticate or requesterror will fire
  2425. setTimeout(leaflet.Util.bind(function () {
  2426. this._renderPopup(e.latlng, error, results, response);
  2427. }, this), 300);
  2428. }, this);
  2429. var identifyRequest = this.identify().at(e.latlng);
  2430. // set mosaic rule for identify task if it is set for layer
  2431. if (this.options.mosaicRule) {
  2432. identifyRequest.setMosaicRule(this.options.mosaicRule);
  2433. // @TODO: force return catalog items too?
  2434. }
  2435. // @TODO: set rendering rule? Not sure,
  2436. // sometimes you want raw pixel values
  2437. // if (this.options.renderingRule) {
  2438. // identifyRequest.setRenderingRule(this.options.renderingRule);
  2439. // }
  2440. identifyRequest.run(callback);
  2441. // set the flags to show the popup
  2442. this._shouldRenderPopup = true;
  2443. this._lastClick = e.latlng;
  2444. },
  2445. _buildExportParams: function () {
  2446. var sr = parseInt(this._map.options.crs.code.split(':')[1], 10);
  2447. var params = {
  2448. bbox: this._calculateBbox(),
  2449. size: this._calculateImageSize(),
  2450. format: this.options.format,
  2451. transparent: this.options.transparent,
  2452. bboxSR: sr,
  2453. imageSR: sr
  2454. };
  2455. if (this.options.from && this.options.to) {
  2456. params.time = this.options.from.valueOf() + ',' + this.options.to.valueOf();
  2457. }
  2458. if (this.options.pixelType) {
  2459. params.pixelType = this.options.pixelType;
  2460. }
  2461. if (this.options.interpolation) {
  2462. params.interpolation = this.options.interpolation;
  2463. }
  2464. if (this.options.compressionQuality) {
  2465. params.compressionQuality = this.options.compressionQuality;
  2466. }
  2467. if (this.options.bandIds) {
  2468. params.bandIds = this.options.bandIds;
  2469. }
  2470. // 0 is falsy *and* a valid input parameter
  2471. if (this.options.noData === 0 || this.options.noData) {
  2472. params.noData = this.options.noData;
  2473. }
  2474. if (this.options.noDataInterpretation) {
  2475. params.noDataInterpretation = this.options.noDataInterpretation;
  2476. }
  2477. if (this.service.options.token) {
  2478. params.token = this.service.options.token;
  2479. }
  2480. if (this.options.renderingRule) {
  2481. params.renderingRule = JSON.stringify(this.options.renderingRule);
  2482. }
  2483. if (this.options.mosaicRule) {
  2484. params.mosaicRule = JSON.stringify(this.options.mosaicRule);
  2485. }
  2486. return params;
  2487. },
  2488. _requestExport: function (params, bounds) {
  2489. if (this.options.f === 'json') {
  2490. this.service.request('exportImage', params, function (error, response) {
  2491. if (error) { return; } // we really can't do anything here but authenticate or requesterror will fire
  2492. if (this.options.token) {
  2493. response.href += ('?token=' + this.options.token);
  2494. }
  2495. if (this.options.proxy) {
  2496. response.href = this.options.proxy + '?' + response.href;
  2497. }
  2498. this._renderImage(response.href, bounds);
  2499. }, this);
  2500. } else {
  2501. params.f = 'image';
  2502. var fullUrl = this.options.url + 'exportImage' + leaflet.Util.getParamString(params);
  2503. if (this.options.proxy) {
  2504. fullUrl = this.options.proxy + '?' + fullUrl;
  2505. }
  2506. this._renderImage(fullUrl, bounds);
  2507. }
  2508. }
  2509. });
  2510. function imageMapLayer (url, options) {
  2511. return new ImageMapLayer(url, options);
  2512. }
  2513. var DynamicMapLayer = RasterLayer.extend({
  2514. options: {
  2515. updateInterval: 150,
  2516. layers: false,
  2517. layerDefs: false,
  2518. timeOptions: false,
  2519. format: 'png32',
  2520. transparent: true,
  2521. f: 'json'
  2522. },
  2523. initialize: function (options) {
  2524. options = getUrlParams(options);
  2525. this.service = mapService(options);
  2526. this.service.addEventParent(this);
  2527. leaflet.Util.setOptions(this, options);
  2528. },
  2529. getDynamicLayers: function () {
  2530. return this.options.dynamicLayers;
  2531. },
  2532. setDynamicLayers: function (dynamicLayers) {
  2533. this.options.dynamicLayers = dynamicLayers;
  2534. this._update();
  2535. return this;
  2536. },
  2537. getLayers: function () {
  2538. return this.options.layers;
  2539. },
  2540. setLayers: function (layers) {
  2541. this.options.layers = layers;
  2542. this._update();
  2543. return this;
  2544. },
  2545. getLayerDefs: function () {
  2546. return this.options.layerDefs;
  2547. },
  2548. setLayerDefs: function (layerDefs) {
  2549. this.options.layerDefs = layerDefs;
  2550. this._update();
  2551. return this;
  2552. },
  2553. getTimeOptions: function () {
  2554. return this.options.timeOptions;
  2555. },
  2556. setTimeOptions: function (timeOptions) {
  2557. this.options.timeOptions = timeOptions;
  2558. this._update();
  2559. return this;
  2560. },
  2561. query: function () {
  2562. return this.service.query();
  2563. },
  2564. identify: function () {
  2565. return this.service.identify();
  2566. },
  2567. find: function () {
  2568. return this.service.find();
  2569. },
  2570. _getPopupData: function (e) {
  2571. var callback = leaflet.Util.bind(function (error, featureCollection, response) {
  2572. if (error) { return; } // we really can't do anything here but authenticate or requesterror will fire
  2573. setTimeout(leaflet.Util.bind(function () {
  2574. this._renderPopup(e.latlng, error, featureCollection, response);
  2575. }, this), 300);
  2576. }, this);
  2577. var identifyRequest;
  2578. if (this.options.popup) {
  2579. identifyRequest = this.options.popup.on(this._map).at(e.latlng);
  2580. } else {
  2581. identifyRequest = this.identify().on(this._map).at(e.latlng);
  2582. }
  2583. // remove extraneous vertices from response features if it has not already been done
  2584. identifyRequest.params.maxAllowableOffset ? true : identifyRequest.simplify(this._map, 0.5);
  2585. if (!(this.options.popup && this.options.popup.params && this.options.popup.params.layers)) {
  2586. if (this.options.layers) {
  2587. identifyRequest.layers('visible:' + this.options.layers.join(','));
  2588. } else {
  2589. identifyRequest.layers('visible');
  2590. }
  2591. }
  2592. // if present, pass layer ids and sql filters through to the identify task
  2593. if (this.options.layerDefs && typeof this.options.layerDefs !== 'string' && !identifyRequest.params.layerDefs) {
  2594. for (var id in this.options.layerDefs) {
  2595. if (this.options.layerDefs.hasOwnProperty(id)) {
  2596. identifyRequest.layerDef(id, this.options.layerDefs[id]);
  2597. }
  2598. }
  2599. }
  2600. identifyRequest.run(callback);
  2601. // set the flags to show the popup
  2602. this._shouldRenderPopup = true;
  2603. this._lastClick = e.latlng;
  2604. },
  2605. _buildExportParams: function () {
  2606. var sr = parseInt(this._map.options.crs.code.split(':')[1], 10);
  2607. var params = {
  2608. bbox: this._calculateBbox(),
  2609. size: this._calculateImageSize(),
  2610. dpi: 96,
  2611. format: this.options.format,
  2612. transparent: this.options.transparent,
  2613. bboxSR: sr,
  2614. imageSR: sr
  2615. };
  2616. if (this.options.dynamicLayers) {
  2617. params.dynamicLayers = this.options.dynamicLayers;
  2618. }
  2619. if (this.options.layers) {
  2620. if (this.options.layers.length === 0) {
  2621. return;
  2622. } else {
  2623. params.layers = 'show:' + this.options.layers.join(',');
  2624. }
  2625. }
  2626. if (this.options.layerDefs) {
  2627. params.layerDefs = typeof this.options.layerDefs === 'string' ? this.options.layerDefs : JSON.stringify(this.options.layerDefs);
  2628. }
  2629. if (this.options.timeOptions) {
  2630. params.timeOptions = JSON.stringify(this.options.timeOptions);
  2631. }
  2632. if (this.options.from && this.options.to) {
  2633. params.time = this.options.from.valueOf() + ',' + this.options.to.valueOf();
  2634. }
  2635. if (this.service.options.token) {
  2636. params.token = this.service.options.token;
  2637. }
  2638. if (this.options.proxy) {
  2639. params.proxy = this.options.proxy;
  2640. }
  2641. // use a timestamp to bust server cache
  2642. if (this.options.disableCache) {
  2643. params._ts = Date.now();
  2644. }
  2645. return params;
  2646. },
  2647. _requestExport: function (params, bounds) {
  2648. if (this.options.f === 'json') {
  2649. this.service.request('export', params, function (error, response) {
  2650. if (error) { return; } // we really can't do anything here but authenticate or requesterror will fire
  2651. if (this.options.token && response.href) {
  2652. response.href += ('?token=' + this.options.token);
  2653. }
  2654. if (this.options.proxy && response.href) {
  2655. response.href = this.options.proxy + '?' + response.href;
  2656. }
  2657. if (response.href) {
  2658. this._renderImage(response.href, bounds);
  2659. } else {
  2660. this._renderImage(response.imageData, bounds, response.contentType);
  2661. }
  2662. }, this);
  2663. } else {
  2664. params.f = 'image';
  2665. var fullUrl = this.options.url + 'export' + leaflet.Util.getParamString(params);
  2666. if (this.options.proxy) {
  2667. fullUrl = this.options.proxy + '?' + fullUrl;
  2668. }
  2669. this._renderImage(fullUrl, bounds);
  2670. }
  2671. }
  2672. });
  2673. function dynamicMapLayer (url, options) {
  2674. return new DynamicMapLayer(url, options);
  2675. }
  2676. var FeatureGrid = leaflet.Layer.extend({
  2677. // @section
  2678. // @aka GridLayer options
  2679. options: {
  2680. // @option cellSize: Number|Point = 256
  2681. // Width and height of cells in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
  2682. cellSize: 512,
  2683. // @option updateWhenIdle: Boolean = (depends)
  2684. // Load new cells only when panning ends.
  2685. // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
  2686. // `false` otherwise in order to display new cells _during_ panning, since it is easy to pan outside the
  2687. // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
  2688. updateWhenIdle: leaflet.Browser.mobile,
  2689. // @option updateInterval: Number = 150
  2690. // Cells will not update more than once every `updateInterval` milliseconds when panning.
  2691. updateInterval: 150,
  2692. // @option noWrap: Boolean = false
  2693. // Whether the layer is wrapped around the antimeridian. If `true`, the
  2694. // GridLayer will only be displayed once at low zoom levels. Has no
  2695. // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
  2696. // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
  2697. // cells outside the CRS limits.
  2698. noWrap: false,
  2699. // @option keepBuffer: Number = 1.5
  2700. // When panning the map, keep this many rows and columns of cells before unloading them.
  2701. keepBuffer: 1.5
  2702. },
  2703. initialize: function (options) {
  2704. leaflet.Util.setOptions(this, options);
  2705. },
  2706. onAdd: function (map) {
  2707. this._cells = {};
  2708. this._activeCells = {};
  2709. this._resetView();
  2710. this._update();
  2711. },
  2712. onRemove: function (map) {
  2713. this._removeAllCells();
  2714. this._cellZoom = undefined;
  2715. },
  2716. // @method isLoading: Boolean
  2717. // Returns `true` if any cell in the grid layer has not finished loading.
  2718. isLoading: function () {
  2719. return this._loading;
  2720. },
  2721. // @method redraw: this
  2722. // Causes the layer to clear all the cells and request them again.
  2723. redraw: function () {
  2724. if (this._map) {
  2725. this._removeAllCells();
  2726. this._update();
  2727. }
  2728. return this;
  2729. },
  2730. getEvents: function () {
  2731. var events = {
  2732. viewprereset: this._invalidateAll,
  2733. viewreset: this._resetView,
  2734. zoom: this._resetView,
  2735. moveend: this._onMoveEnd
  2736. };
  2737. if (!this.options.updateWhenIdle) {
  2738. // update cells on move, but not more often than once per given interval
  2739. if (!this._onMove) {
  2740. this._onMove = leaflet.Util.throttle(
  2741. this._onMoveEnd,
  2742. this.options.updateInterval,
  2743. this
  2744. );
  2745. }
  2746. events.move = this._onMove;
  2747. }
  2748. return events;
  2749. },
  2750. // @section Extension methods
  2751. // Layers extending `GridLayer` shall reimplement the following method.
  2752. // @method createCell(coords: Object, done?: Function): HTMLElement
  2753. // Called only internally, must be overridden by classes extending `GridLayer`.
  2754. // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
  2755. // is specified, it must be called when the cell has finished loading and drawing.
  2756. createCell: function () {
  2757. return document.createElement('div');
  2758. },
  2759. removeCell: function () {
  2760. return;
  2761. },
  2762. reuseCell: function () {
  2763. return;
  2764. },
  2765. cellLeave: function () {
  2766. return;
  2767. },
  2768. cellEnter: function () {
  2769. return;
  2770. },
  2771. // @section
  2772. // @method getCellSize: Point
  2773. // Normalizes the [cellSize option](#gridlayer-cellsize) into a point. Used by the `createCell()` method.
  2774. getCellSize: function () {
  2775. var s = this.options.cellSize;
  2776. return s instanceof leaflet.Point ? s : new leaflet.Point(s, s);
  2777. },
  2778. _pruneCells: function () {
  2779. if (!this._map) {
  2780. return;
  2781. }
  2782. var key, cell;
  2783. for (key in this._cells) {
  2784. cell = this._cells[key];
  2785. cell.retain = cell.current;
  2786. }
  2787. for (key in this._cells) {
  2788. cell = this._cells[key];
  2789. if (cell.current && !cell.active) {
  2790. var coords = cell.coords;
  2791. if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
  2792. this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
  2793. }
  2794. }
  2795. }
  2796. for (key in this._cells) {
  2797. if (!this._cells[key].retain) {
  2798. this._removeCell(key);
  2799. }
  2800. }
  2801. },
  2802. _removeAllCells: function () {
  2803. for (var key in this._cells) {
  2804. this._removeCell(key);
  2805. }
  2806. },
  2807. _invalidateAll: function () {
  2808. this._removeAllCells();
  2809. this._cellZoom = undefined;
  2810. },
  2811. _retainParent: function (x, y, z, minZoom) {
  2812. var x2 = Math.floor(x / 2);
  2813. var y2 = Math.floor(y / 2);
  2814. var z2 = z - 1;
  2815. var coords2 = new leaflet.Point(+x2, +y2);
  2816. coords2.z = +z2;
  2817. var key = this._cellCoordsToKey(coords2);
  2818. var cell = this._cells[key];
  2819. if (cell && cell.active) {
  2820. cell.retain = true;
  2821. return true;
  2822. } else if (cell && cell.loaded) {
  2823. cell.retain = true;
  2824. }
  2825. if (z2 > minZoom) {
  2826. return this._retainParent(x2, y2, z2, minZoom);
  2827. }
  2828. return false;
  2829. },
  2830. _retainChildren: function (x, y, z, maxZoom) {
  2831. for (var i = 2 * x; i < 2 * x + 2; i++) {
  2832. for (var j = 2 * y; j < 2 * y + 2; j++) {
  2833. var coords = new leaflet.Point(i, j);
  2834. coords.z = z + 1;
  2835. var key = this._cellCoordsToKey(coords);
  2836. var cell = this._cells[key];
  2837. if (cell && cell.active) {
  2838. cell.retain = true;
  2839. continue;
  2840. } else if (cell && cell.loaded) {
  2841. cell.retain = true;
  2842. }
  2843. if (z + 1 < maxZoom) {
  2844. this._retainChildren(i, j, z + 1, maxZoom);
  2845. }
  2846. }
  2847. }
  2848. },
  2849. _resetView: function (e) {
  2850. var animating = e && (e.pinch || e.flyTo);
  2851. if (animating) {
  2852. return;
  2853. }
  2854. this._setView(
  2855. this._map.getCenter(),
  2856. this._map.getZoom(),
  2857. animating,
  2858. animating
  2859. );
  2860. },
  2861. _setView: function (center, zoom, noPrune, noUpdate) {
  2862. var cellZoom = Math.round(zoom);
  2863. if (!noUpdate) {
  2864. this._cellZoom = cellZoom;
  2865. if (this._abortLoading) {
  2866. this._abortLoading();
  2867. }
  2868. this._resetGrid();
  2869. if (cellZoom !== undefined) {
  2870. this._update(center);
  2871. }
  2872. if (!noPrune) {
  2873. this._pruneCells();
  2874. }
  2875. // Flag to prevent _updateOpacity from pruning cells during
  2876. // a zoom anim or a pinch gesture
  2877. this._noPrune = !!noPrune;
  2878. }
  2879. },
  2880. _resetGrid: function () {
  2881. var map = this._map;
  2882. var crs = map.options.crs;
  2883. var cellSize = (this._cellSize = this.getCellSize());
  2884. var cellZoom = this._cellZoom;
  2885. var bounds = this._map.getPixelWorldBounds(this._cellZoom);
  2886. if (bounds) {
  2887. this._globalCellRange = this._pxBoundsToCellRange(bounds);
  2888. }
  2889. this._wrapX = crs.wrapLng &&
  2890. !this.options.noWrap && [
  2891. Math.floor(map.project([0, crs.wrapLng[0]], cellZoom).x / cellSize.x),
  2892. Math.ceil(map.project([0, crs.wrapLng[1]], cellZoom).x / cellSize.y)
  2893. ];
  2894. this._wrapY = crs.wrapLat &&
  2895. !this.options.noWrap && [
  2896. Math.floor(map.project([crs.wrapLat[0], 0], cellZoom).y / cellSize.x),
  2897. Math.ceil(map.project([crs.wrapLat[1], 0], cellZoom).y / cellSize.y)
  2898. ];
  2899. },
  2900. _onMoveEnd: function (e) {
  2901. var animating = e && (e.pinch || e.flyTo);
  2902. if (animating || !this._map || this._map._animatingZoom) {
  2903. return;
  2904. }
  2905. this._update();
  2906. },
  2907. _getCelldPixelBounds: function (center) {
  2908. var map = this._map;
  2909. var mapZoom = map._animatingZoom
  2910. ? Math.max(map._animateToZoom, map.getZoom())
  2911. : map.getZoom();
  2912. var scale = map.getZoomScale(mapZoom, this._cellZoom);
  2913. var pixelCenter = map.project(center, this._cellZoom).floor();
  2914. var halfSize = map.getSize().divideBy(scale * 2);
  2915. return new leaflet.Bounds(
  2916. pixelCenter.subtract(halfSize),
  2917. pixelCenter.add(halfSize)
  2918. );
  2919. },
  2920. // Private method to load cells in the grid's active zoom level according to map bounds
  2921. _update: function (center) {
  2922. var map = this._map;
  2923. if (!map) {
  2924. return;
  2925. }
  2926. var zoom = Math.round(map.getZoom());
  2927. if (center === undefined) {
  2928. center = map.getCenter();
  2929. }
  2930. var pixelBounds = this._getCelldPixelBounds(center);
  2931. var cellRange = this._pxBoundsToCellRange(pixelBounds);
  2932. var cellCenter = cellRange.getCenter();
  2933. var queue = [];
  2934. var margin = this.options.keepBuffer;
  2935. var noPruneRange = new leaflet.Bounds(
  2936. cellRange.getBottomLeft().subtract([margin, -margin]),
  2937. cellRange.getTopRight().add([margin, -margin])
  2938. );
  2939. // Sanity check: panic if the cell range contains Infinity somewhere.
  2940. if (
  2941. !(
  2942. isFinite(cellRange.min.x) &&
  2943. isFinite(cellRange.min.y) &&
  2944. isFinite(cellRange.max.x) &&
  2945. isFinite(cellRange.max.y)
  2946. )
  2947. ) {
  2948. throw new Error('Attempted to load an infinite number of cells');
  2949. }
  2950. for (var key in this._cells) {
  2951. var c = this._cells[key].coords;
  2952. if (
  2953. c.z !== this._cellZoom ||
  2954. !noPruneRange.contains(new leaflet.Point(c.x, c.y))
  2955. ) {
  2956. this._cells[key].current = false;
  2957. }
  2958. }
  2959. // _update just loads more cells. If the cell zoom level differs too much
  2960. // from the map's, let _setView reset levels and prune old cells.
  2961. if (Math.abs(zoom - this._cellZoom) > 1) {
  2962. this._setView(center, zoom);
  2963. return;
  2964. }
  2965. // create a queue of coordinates to load cells from
  2966. for (var j = cellRange.min.y; j <= cellRange.max.y; j++) {
  2967. for (var i = cellRange.min.x; i <= cellRange.max.x; i++) {
  2968. var coords = new leaflet.Point(i, j);
  2969. coords.z = this._cellZoom;
  2970. if (!this._isValidCell(coords)) {
  2971. continue;
  2972. }
  2973. var cell = this._cells[this._cellCoordsToKey(coords)];
  2974. if (cell) {
  2975. cell.current = true;
  2976. } else {
  2977. queue.push(coords);
  2978. }
  2979. }
  2980. }
  2981. // sort cell queue to load cells in order of their distance to center
  2982. queue.sort(function (a, b) {
  2983. return a.distanceTo(cellCenter) - b.distanceTo(cellCenter);
  2984. });
  2985. if (queue.length !== 0) {
  2986. // if it's the first batch of cells to load
  2987. if (!this._loading) {
  2988. this._loading = true;
  2989. }
  2990. for (i = 0; i < queue.length; i++) {
  2991. var _key = this._cellCoordsToKey(queue[i]);
  2992. var _coords = this._keyToCellCoords(_key);
  2993. if (this._activeCells[_coords]) {
  2994. this._reuseCell(queue[i]);
  2995. } else {
  2996. this._createCell(queue[i]);
  2997. }
  2998. }
  2999. }
  3000. },
  3001. _isValidCell: function (coords) {
  3002. var crs = this._map.options.crs;
  3003. if (!crs.infinite) {
  3004. // don't load cell if it's out of bounds and not wrapped
  3005. var bounds = this._globalCellRange;
  3006. if (
  3007. (!crs.wrapLng &&
  3008. (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
  3009. (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))
  3010. ) {
  3011. return false;
  3012. }
  3013. }
  3014. if (!this.options.bounds) {
  3015. return true;
  3016. }
  3017. // don't load cell if it doesn't intersect the bounds in options
  3018. var cellBounds = this._cellCoordsToBounds(coords);
  3019. return leaflet.toLatLngBounds(this.options.bounds).overlaps(cellBounds);
  3020. },
  3021. _keyToBounds: function (key) {
  3022. return this._cellCoordsToBounds(this._keyToCellCoords(key));
  3023. },
  3024. _cellCoordsToNwSe: function (coords) {
  3025. var map = this._map;
  3026. var cellSize = this.getCellSize();
  3027. var nwPoint = coords.scaleBy(cellSize);
  3028. var sePoint = nwPoint.add(cellSize);
  3029. var nw = map.unproject(nwPoint, coords.z);
  3030. var se = map.unproject(sePoint, coords.z);
  3031. return [nw, se];
  3032. },
  3033. // converts cell coordinates to its geographical bounds
  3034. _cellCoordsToBounds: function (coords) {
  3035. var bp = this._cellCoordsToNwSe(coords);
  3036. var bounds = new leaflet.LatLngBounds(bp[0], bp[1]);
  3037. if (!this.options.noWrap) {
  3038. bounds = this._map.wrapLatLngBounds(bounds);
  3039. }
  3040. return bounds;
  3041. },
  3042. // converts cell coordinates to key for the cell cache
  3043. _cellCoordsToKey: function (coords) {
  3044. return coords.x + ':' + coords.y + ':' + coords.z;
  3045. },
  3046. // converts cell cache key to coordinates
  3047. _keyToCellCoords: function (key) {
  3048. var k = key.split(':');
  3049. var coords = new leaflet.Point(+k[0], +k[1]);
  3050. coords.z = +k[2];
  3051. return coords;
  3052. },
  3053. _removeCell: function (key) {
  3054. var cell = this._cells[key];
  3055. if (!cell) {
  3056. return;
  3057. }
  3058. var coords = this._keyToCellCoords(key);
  3059. var wrappedCoords = this._wrapCoords(coords);
  3060. var cellBounds = this._cellCoordsToBounds(this._wrapCoords(coords));
  3061. cell.current = false;
  3062. delete this._cells[key];
  3063. this._activeCells[key] = cell;
  3064. this.cellLeave(cellBounds, wrappedCoords, key);
  3065. this.fire('cellleave', {
  3066. key: key,
  3067. coords: wrappedCoords,
  3068. bounds: cellBounds
  3069. });
  3070. },
  3071. _reuseCell: function (coords) {
  3072. var key = this._cellCoordsToKey(coords);
  3073. // save cell in cache
  3074. this._cells[key] = this._activeCells[key];
  3075. this._cells[key].current = true;
  3076. var wrappedCoords = this._wrapCoords(coords);
  3077. var cellBounds = this._cellCoordsToBounds(this._wrapCoords(coords));
  3078. this.cellEnter(cellBounds, wrappedCoords, key);
  3079. this.fire('cellenter', {
  3080. key: key,
  3081. coords: wrappedCoords,
  3082. bounds: cellBounds
  3083. });
  3084. },
  3085. _createCell: function (coords) {
  3086. var key = this._cellCoordsToKey(coords);
  3087. var wrappedCoords = this._wrapCoords(coords);
  3088. var cellBounds = this._cellCoordsToBounds(this._wrapCoords(coords));
  3089. this.createCell(cellBounds, wrappedCoords, key);
  3090. this.fire('cellcreate', {
  3091. key: key,
  3092. coords: wrappedCoords,
  3093. bounds: cellBounds
  3094. });
  3095. // save cell in cache
  3096. this._cells[key] = {
  3097. coords: coords,
  3098. current: true
  3099. };
  3100. leaflet.Util.requestAnimFrame(this._pruneCells, this);
  3101. },
  3102. _cellReady: function (coords, err, cell) {
  3103. var key = this._cellCoordsToKey(coords);
  3104. cell = this._cells[key];
  3105. if (!cell) {
  3106. return;
  3107. }
  3108. cell.loaded = +new Date();
  3109. cell.active = true;
  3110. },
  3111. _getCellPos: function (coords) {
  3112. return coords.scaleBy(this.getCellSize());
  3113. },
  3114. _wrapCoords: function (coords) {
  3115. var newCoords = new leaflet.Point(
  3116. this._wrapX ? leaflet.Util.wrapNum(coords.x, this._wrapX) : coords.x,
  3117. this._wrapY ? leaflet.Util.wrapNum(coords.y, this._wrapY) : coords.y
  3118. );
  3119. newCoords.z = coords.z;
  3120. return newCoords;
  3121. },
  3122. _pxBoundsToCellRange: function (bounds) {
  3123. var cellSize = this.getCellSize();
  3124. return new leaflet.Bounds(
  3125. bounds.min.unscaleBy(cellSize).floor(),
  3126. bounds.max.unscaleBy(cellSize).ceil().subtract([1, 1])
  3127. );
  3128. }
  3129. });
  3130. function BinarySearchIndex (values) {
  3131. this.values = [].concat(values || []);
  3132. }
  3133. BinarySearchIndex.prototype.query = function (value) {
  3134. var index = this.getIndex(value);
  3135. return this.values[index];
  3136. };
  3137. BinarySearchIndex.prototype.getIndex = function getIndex (value) {
  3138. if (this.dirty) {
  3139. this.sort();
  3140. }
  3141. var minIndex = 0;
  3142. var maxIndex = this.values.length - 1;
  3143. var currentIndex;
  3144. var currentElement;
  3145. while (minIndex <= maxIndex) {
  3146. currentIndex = (minIndex + maxIndex) / 2 | 0;
  3147. currentElement = this.values[Math.round(currentIndex)];
  3148. if (+currentElement.value < +value) {
  3149. minIndex = currentIndex + 1;
  3150. } else if (+currentElement.value > +value) {
  3151. maxIndex = currentIndex - 1;
  3152. } else {
  3153. return currentIndex;
  3154. }
  3155. }
  3156. return Math.abs(~maxIndex);
  3157. };
  3158. BinarySearchIndex.prototype.between = function between (start, end) {
  3159. var startIndex = this.getIndex(start);
  3160. var endIndex = this.getIndex(end);
  3161. if (startIndex === 0 && endIndex === 0) {
  3162. return [];
  3163. }
  3164. while (this.values[startIndex - 1] && this.values[startIndex - 1].value === start) {
  3165. startIndex--;
  3166. }
  3167. while (this.values[endIndex + 1] && this.values[endIndex + 1].value === end) {
  3168. endIndex++;
  3169. }
  3170. if (this.values[endIndex] && this.values[endIndex].value === end && this.values[endIndex + 1]) {
  3171. endIndex++;
  3172. }
  3173. return this.values.slice(startIndex, endIndex);
  3174. };
  3175. BinarySearchIndex.prototype.insert = function insert (item) {
  3176. this.values.splice(this.getIndex(item.value), 0, item);
  3177. return this;
  3178. };
  3179. BinarySearchIndex.prototype.bulkAdd = function bulkAdd (items, sort) {
  3180. this.values = this.values.concat([].concat(items || []));
  3181. if (sort) {
  3182. this.sort();
  3183. } else {
  3184. this.dirty = true;
  3185. }
  3186. return this;
  3187. };
  3188. BinarySearchIndex.prototype.sort = function sort () {
  3189. this.values.sort(function (a, b) {
  3190. return +b.value - +a.value;
  3191. }).reverse();
  3192. this.dirty = false;
  3193. return this;
  3194. };
  3195. var FeatureManager = FeatureGrid.extend({
  3196. /**
  3197. * Options
  3198. */
  3199. options: {
  3200. attribution: null,
  3201. where: '1=1',
  3202. fields: ['*'],
  3203. from: false,
  3204. to: false,
  3205. timeField: false,
  3206. timeFilterMode: 'server',
  3207. simplifyFactor: 0,
  3208. precision: 6,
  3209. fetchAllFeatures: false
  3210. },
  3211. /**
  3212. * Constructor
  3213. */
  3214. initialize: function (options) {
  3215. FeatureGrid.prototype.initialize.call(this, options);
  3216. options = getUrlParams(options);
  3217. options = leaflet.Util.setOptions(this, options);
  3218. this.service = featureLayerService(options);
  3219. this.service.addEventParent(this);
  3220. // use case insensitive regex to look for common fieldnames used for indexing
  3221. if (this.options.fields[0] !== '*') {
  3222. var oidCheck = false;
  3223. for (var i = 0; i < this.options.fields.length; i++) {
  3224. if (this.options.fields[i].match(/^(OBJECTID|FID|OID|ID)$/i)) {
  3225. oidCheck = true;
  3226. }
  3227. }
  3228. if (oidCheck === false) {
  3229. warn(
  3230. 'no known esriFieldTypeOID field detected in fields Array. Please add an attribute field containing unique IDs to ensure the layer can be drawn correctly.'
  3231. );
  3232. }
  3233. }
  3234. if (this.options.timeField.start && this.options.timeField.end) {
  3235. this._startTimeIndex = new BinarySearchIndex();
  3236. this._endTimeIndex = new BinarySearchIndex();
  3237. } else if (this.options.timeField) {
  3238. this._timeIndex = new BinarySearchIndex();
  3239. }
  3240. this._cache = {};
  3241. this._currentSnapshot = []; // cache of what layers should be active
  3242. this._activeRequests = 0;
  3243. },
  3244. /**
  3245. * Layer Interface
  3246. */
  3247. onAdd: function (map) {
  3248. // include 'Powered by Esri' in map attribution
  3249. setEsriAttribution(map);
  3250. this.service.metadata(function (err, metadata) {
  3251. if (!err) {
  3252. var supportedFormats = metadata.supportedQueryFormats;
  3253. // Check if someone has requested that we don't use geoJSON, even if it's available
  3254. var forceJsonFormat = false;
  3255. if (
  3256. this.service.options.isModern === false ||
  3257. this.options.fetchAllFeatures
  3258. ) {
  3259. forceJsonFormat = true;
  3260. }
  3261. // Unless we've been told otherwise, check to see whether service can emit GeoJSON natively
  3262. if (
  3263. !forceJsonFormat &&
  3264. supportedFormats &&
  3265. supportedFormats.indexOf('geoJSON') !== -1
  3266. ) {
  3267. this.service.options.isModern = true;
  3268. }
  3269. if (metadata.objectIdField) {
  3270. this.service.options.idAttribute = metadata.objectIdField;
  3271. }
  3272. // add copyright text listed in service metadata
  3273. if (
  3274. !this.options.attribution &&
  3275. map.attributionControl &&
  3276. metadata.copyrightText
  3277. ) {
  3278. this.options.attribution = metadata.copyrightText;
  3279. map.attributionControl.addAttribution(this.getAttribution());
  3280. }
  3281. }
  3282. }, this);
  3283. map.on('zoomend', this._handleZoomChange, this);
  3284. return FeatureGrid.prototype.onAdd.call(this, map);
  3285. },
  3286. onRemove: function (map) {
  3287. removeEsriAttribution(map);
  3288. map.off('zoomend', this._handleZoomChange, this);
  3289. return FeatureGrid.prototype.onRemove.call(this, map);
  3290. },
  3291. getAttribution: function () {
  3292. return this.options.attribution;
  3293. },
  3294. /**
  3295. * Feature Management
  3296. */
  3297. createCell: function (bounds, coords) {
  3298. // dont fetch features outside the scale range defined for the layer
  3299. if (this._visibleZoom()) {
  3300. this._requestFeatures(bounds, coords);
  3301. }
  3302. },
  3303. _requestFeatures: function (bounds, coords, callback, offset) {
  3304. this._activeRequests++;
  3305. // default param
  3306. offset = offset || 0;
  3307. var originalWhere = this.options.where;
  3308. // our first active request fires loading
  3309. if (this._activeRequests === 1) {
  3310. this.fire(
  3311. 'loading',
  3312. {
  3313. bounds: bounds
  3314. },
  3315. true
  3316. );
  3317. }
  3318. return this._buildQuery(bounds, offset).run(function (
  3319. error,
  3320. featureCollection,
  3321. response
  3322. ) {
  3323. if (response && response.exceededTransferLimit) {
  3324. this.fire('drawlimitexceeded');
  3325. }
  3326. // the where changed while this request was being run so don't it.
  3327. if (this.options.where !== originalWhere) {
  3328. return;
  3329. }
  3330. // no error, features
  3331. if (!error && featureCollection && featureCollection.features.length) {
  3332. // schedule adding features until the next animation frame
  3333. leaflet.Util.requestAnimFrame(
  3334. leaflet.Util.bind(function () {
  3335. this._addFeatures(featureCollection.features, coords);
  3336. this._postProcessFeatures(bounds);
  3337. }, this)
  3338. );
  3339. }
  3340. // no error, no features
  3341. if (!error && featureCollection && !featureCollection.features.length) {
  3342. this._postProcessFeatures(bounds);
  3343. }
  3344. if (error) {
  3345. this._postProcessFeatures(bounds);
  3346. }
  3347. if (callback) {
  3348. callback.call(this, error, featureCollection);
  3349. }
  3350. if (
  3351. response &&
  3352. (response.exceededTransferLimit ||
  3353. (response.properties && response.properties.exceededTransferLimit)) &&
  3354. this.options.fetchAllFeatures
  3355. ) {
  3356. this._requestFeatures(
  3357. bounds,
  3358. coords,
  3359. callback,
  3360. offset + featureCollection.features.length
  3361. );
  3362. }
  3363. },
  3364. this);
  3365. },
  3366. _postProcessFeatures: function (bounds) {
  3367. // deincrement the request counter now that we have processed features
  3368. this._activeRequests--;
  3369. // if there are no more active requests fire a load event for this view
  3370. if (this._activeRequests <= 0) {
  3371. this.fire('load', {
  3372. bounds: bounds
  3373. });
  3374. }
  3375. },
  3376. _cacheKey: function (coords) {
  3377. return coords.z + ':' + coords.x + ':' + coords.y;
  3378. },
  3379. _addFeatures: function (features, coords) {
  3380. // coords is optional - will be false if coming from addFeatures() function
  3381. if (coords) {
  3382. var key = this._cacheKey(coords);
  3383. this._cache[key] = this._cache[key] || [];
  3384. }
  3385. for (var i = features.length - 1; i >= 0; i--) {
  3386. var id = features[i].id;
  3387. if (this._currentSnapshot.indexOf(id) === -1) {
  3388. this._currentSnapshot.push(id);
  3389. }
  3390. if (typeof key !== 'undefined' && this._cache[key].indexOf(id) === -1) {
  3391. this._cache[key].push(id);
  3392. }
  3393. }
  3394. if (this.options.timeField) {
  3395. this._buildTimeIndexes(features);
  3396. }
  3397. this.createLayers(features);
  3398. },
  3399. _buildQuery: function (bounds, offset) {
  3400. var query = this.service
  3401. .query()
  3402. .intersects(bounds)
  3403. .where(this.options.where)
  3404. .fields(this.options.fields)
  3405. .precision(this.options.precision);
  3406. if (this.options.fetchAllFeatures && !isNaN(parseInt(offset))) {
  3407. query = query.offset(offset);
  3408. }
  3409. query.params['resultType'] = 'tile';
  3410. if (this.options.requestParams) {
  3411. leaflet.Util.extend(query.params, this.options.requestParams);
  3412. }
  3413. if (this.options.simplifyFactor) {
  3414. query.simplify(this._map, this.options.simplifyFactor);
  3415. }
  3416. if (
  3417. this.options.timeFilterMode === 'server' &&
  3418. this.options.from &&
  3419. this.options.to
  3420. ) {
  3421. query.between(this.options.from, this.options.to);
  3422. }
  3423. return query;
  3424. },
  3425. /**
  3426. * Where Methods
  3427. */
  3428. setWhere: function (where, callback, context) {
  3429. this.options.where = where && where.length ? where : '1=1';
  3430. var oldSnapshot = [];
  3431. var newSnapshot = [];
  3432. var pendingRequests = 0;
  3433. var requestError = null;
  3434. var requestCallback = leaflet.Util.bind(function (error, featureCollection) {
  3435. if (error) {
  3436. requestError = error;
  3437. }
  3438. if (featureCollection) {
  3439. for (var i = featureCollection.features.length - 1; i >= 0; i--) {
  3440. newSnapshot.push(featureCollection.features[i].id);
  3441. }
  3442. }
  3443. pendingRequests--;
  3444. if (
  3445. pendingRequests <= 0 &&
  3446. this._visibleZoom() &&
  3447. where === this.options.where // the where is still the same so use this one
  3448. ) {
  3449. this._currentSnapshot = newSnapshot;
  3450. // schedule adding features for the next animation frame
  3451. leaflet.Util.requestAnimFrame(
  3452. leaflet.Util.bind(function () {
  3453. this.removeLayers(oldSnapshot);
  3454. this.addLayers(newSnapshot);
  3455. if (callback) {
  3456. callback.call(context, requestError);
  3457. }
  3458. }, this)
  3459. );
  3460. }
  3461. }, this);
  3462. for (var i = this._currentSnapshot.length - 1; i >= 0; i--) {
  3463. oldSnapshot.push(this._currentSnapshot[i]);
  3464. }
  3465. this._cache = {};
  3466. for (var key in this._cells) {
  3467. pendingRequests++;
  3468. var coords = this._keyToCellCoords(key);
  3469. var bounds = this._cellCoordsToBounds(coords);
  3470. this._requestFeatures(bounds, coords, requestCallback);
  3471. }
  3472. return this;
  3473. },
  3474. getWhere: function () {
  3475. return this.options.where;
  3476. },
  3477. /**
  3478. * Time Range Methods
  3479. */
  3480. getTimeRange: function () {
  3481. return [this.options.from, this.options.to];
  3482. },
  3483. setTimeRange: function (from, to, callback, context) {
  3484. var oldFrom = this.options.from;
  3485. var oldTo = this.options.to;
  3486. var pendingRequests = 0;
  3487. var requestError = null;
  3488. var requestCallback = leaflet.Util.bind(function (error) {
  3489. if (error) {
  3490. requestError = error;
  3491. }
  3492. this._filterExistingFeatures(oldFrom, oldTo, from, to);
  3493. pendingRequests--;
  3494. if (callback && pendingRequests <= 0) {
  3495. callback.call(context, requestError);
  3496. }
  3497. }, this);
  3498. this.options.from = from;
  3499. this.options.to = to;
  3500. this._filterExistingFeatures(oldFrom, oldTo, from, to);
  3501. if (this.options.timeFilterMode === 'server') {
  3502. for (var key in this._cells) {
  3503. pendingRequests++;
  3504. var coords = this._keyToCellCoords(key);
  3505. var bounds = this._cellCoordsToBounds(coords);
  3506. this._requestFeatures(bounds, coords, requestCallback);
  3507. }
  3508. }
  3509. return this;
  3510. },
  3511. refresh: function () {
  3512. this.setWhere(this.options.where);
  3513. },
  3514. _filterExistingFeatures: function (oldFrom, oldTo, newFrom, newTo) {
  3515. var layersToRemove =
  3516. oldFrom && oldTo
  3517. ? this._getFeaturesInTimeRange(oldFrom, oldTo)
  3518. : this._currentSnapshot;
  3519. var layersToAdd = this._getFeaturesInTimeRange(newFrom, newTo);
  3520. if (layersToAdd.indexOf) {
  3521. for (var i = 0; i < layersToAdd.length; i++) {
  3522. var shouldRemoveLayer = layersToRemove.indexOf(layersToAdd[i]);
  3523. if (shouldRemoveLayer >= 0) {
  3524. layersToRemove.splice(shouldRemoveLayer, 1);
  3525. }
  3526. }
  3527. }
  3528. // schedule adding features until the next animation frame
  3529. leaflet.Util.requestAnimFrame(
  3530. leaflet.Util.bind(function () {
  3531. this.removeLayers(layersToRemove);
  3532. this.addLayers(layersToAdd);
  3533. }, this)
  3534. );
  3535. },
  3536. _getFeaturesInTimeRange: function (start, end) {
  3537. var ids = [];
  3538. var search;
  3539. if (this.options.timeField.start && this.options.timeField.end) {
  3540. var startTimes = this._startTimeIndex.between(start, end);
  3541. var endTimes = this._endTimeIndex.between(start, end);
  3542. search = startTimes.concat(endTimes);
  3543. } else if (this._timeIndex) {
  3544. search = this._timeIndex.between(start, end);
  3545. } else {
  3546. warn(
  3547. 'You must set timeField in the layer constructor in order to manipulate the start and end time filter.'
  3548. );
  3549. return [];
  3550. }
  3551. for (var i = search.length - 1; i >= 0; i--) {
  3552. ids.push(search[i].id);
  3553. }
  3554. return ids;
  3555. },
  3556. _buildTimeIndexes: function (geojson) {
  3557. var i;
  3558. var feature;
  3559. if (this.options.timeField.start && this.options.timeField.end) {
  3560. var startTimeEntries = [];
  3561. var endTimeEntries = [];
  3562. for (i = geojson.length - 1; i >= 0; i--) {
  3563. feature = geojson[i];
  3564. startTimeEntries.push({
  3565. id: feature.id,
  3566. value: new Date(feature.properties[this.options.timeField.start])
  3567. });
  3568. endTimeEntries.push({
  3569. id: feature.id,
  3570. value: new Date(feature.properties[this.options.timeField.end])
  3571. });
  3572. }
  3573. this._startTimeIndex.bulkAdd(startTimeEntries);
  3574. this._endTimeIndex.bulkAdd(endTimeEntries);
  3575. } else {
  3576. var timeEntries = [];
  3577. for (i = geojson.length - 1; i >= 0; i--) {
  3578. feature = geojson[i];
  3579. timeEntries.push({
  3580. id: feature.id,
  3581. value: new Date(feature.properties[this.options.timeField])
  3582. });
  3583. }
  3584. this._timeIndex.bulkAdd(timeEntries);
  3585. }
  3586. },
  3587. _featureWithinTimeRange: function (feature) {
  3588. if (!this.options.from || !this.options.to) {
  3589. return true;
  3590. }
  3591. var from = +this.options.from.valueOf();
  3592. var to = +this.options.to.valueOf();
  3593. if (typeof this.options.timeField === 'string') {
  3594. var date = +feature.properties[this.options.timeField];
  3595. return date >= from && date <= to;
  3596. }
  3597. if (this.options.timeField.start && this.options.timeField.end) {
  3598. var startDate = +feature.properties[this.options.timeField.start];
  3599. var endDate = +feature.properties[this.options.timeField.end];
  3600. return (
  3601. (startDate >= from && startDate <= to) ||
  3602. (endDate >= from && endDate <= to) ||
  3603. (startDate <= from && endDate >= to)
  3604. );
  3605. }
  3606. },
  3607. _visibleZoom: function () {
  3608. // check to see whether the current zoom level of the map is within the optional limit defined for the FeatureLayer
  3609. if (!this._map) {
  3610. return false;
  3611. }
  3612. var zoom = this._map.getZoom();
  3613. if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
  3614. return false;
  3615. } else {
  3616. return true;
  3617. }
  3618. },
  3619. _handleZoomChange: function () {
  3620. if (!this._visibleZoom()) {
  3621. // if we have moved outside the visible zoom range clear the current snapshot, no layers should be active
  3622. this.removeLayers(this._currentSnapshot);
  3623. this._currentSnapshot = [];
  3624. } else {
  3625. /*
  3626. for every cell in this._cells
  3627. 1. Get the cache key for the coords of the cell
  3628. 2. If this._cache[key] exists it will be an array of feature IDs.
  3629. 3. Call this.addLayers(this._cache[key]) to instruct the feature layer to add the layers back.
  3630. */
  3631. for (var i in this._cells) {
  3632. var coords = this._cells[i].coords;
  3633. var key = this._cacheKey(coords);
  3634. if (this._cache[key]) {
  3635. this.addLayers(this._cache[key]);
  3636. }
  3637. }
  3638. }
  3639. },
  3640. /**
  3641. * Service Methods
  3642. */
  3643. authenticate: function (token) {
  3644. this.service.authenticate(token);
  3645. return this;
  3646. },
  3647. metadata: function (callback, context) {
  3648. this.service.metadata(callback, context);
  3649. return this;
  3650. },
  3651. query: function () {
  3652. return this.service.query();
  3653. },
  3654. _getMetadata: function (callback) {
  3655. if (this._metadata) {
  3656. var error;
  3657. callback(error, this._metadata);
  3658. } else {
  3659. this.metadata(
  3660. leaflet.Util.bind(function (error, response) {
  3661. this._metadata = response;
  3662. callback(error, this._metadata);
  3663. }, this)
  3664. );
  3665. }
  3666. },
  3667. addFeature: function (feature, callback, context) {
  3668. this.addFeatures(feature, callback, context);
  3669. },
  3670. addFeatures: function (features, callback, context) {
  3671. this._getMetadata(
  3672. leaflet.Util.bind(function (error, metadata) {
  3673. if (error) {
  3674. if (callback) {
  3675. callback.call(this, error, null);
  3676. }
  3677. return;
  3678. }
  3679. // GeoJSON featureCollection or simple feature
  3680. var featuresArray = features.features ? features.features : [features];
  3681. this.service.addFeatures(
  3682. features,
  3683. leaflet.Util.bind(function (error, response) {
  3684. if (!error) {
  3685. for (var i = featuresArray.length - 1; i >= 0; i--) {
  3686. // assign ID from result to appropriate objectid field from service metadata
  3687. featuresArray[i].properties[metadata.objectIdField] =
  3688. featuresArray.length > 1
  3689. ? response[i].objectId
  3690. : response.objectId;
  3691. // we also need to update the geojson id for createLayers() to function
  3692. featuresArray[i].id =
  3693. featuresArray.length > 1
  3694. ? response[i].objectId
  3695. : response.objectId;
  3696. }
  3697. this._addFeatures(featuresArray);
  3698. }
  3699. if (callback) {
  3700. callback.call(context, error, response);
  3701. }
  3702. }, this)
  3703. );
  3704. }, this)
  3705. );
  3706. },
  3707. updateFeature: function (feature, callback, context) {
  3708. this.updateFeatures(feature, callback, context);
  3709. },
  3710. updateFeatures: function (features, callback, context) {
  3711. // GeoJSON featureCollection or simple feature
  3712. var featuresArray = features.features ? features.features : [features];
  3713. this.service.updateFeatures(
  3714. features,
  3715. function (error, response) {
  3716. if (!error) {
  3717. for (var i = featuresArray.length - 1; i >= 0; i--) {
  3718. this.removeLayers([featuresArray[i].id], true);
  3719. }
  3720. this._addFeatures(featuresArray);
  3721. }
  3722. if (callback) {
  3723. callback.call(context, error, response);
  3724. }
  3725. },
  3726. this
  3727. );
  3728. },
  3729. deleteFeature: function (id, callback, context) {
  3730. this.deleteFeatures(id, callback, context);
  3731. },
  3732. deleteFeatures: function (ids, callback, context) {
  3733. return this.service.deleteFeatures(
  3734. ids,
  3735. function (error, response) {
  3736. var responseArray = response.length ? response : [response];
  3737. if (!error && responseArray.length > 0) {
  3738. for (var i = responseArray.length - 1; i >= 0; i--) {
  3739. this.removeLayers([responseArray[i].objectId], true);
  3740. }
  3741. }
  3742. if (callback) {
  3743. callback.call(context, error, response);
  3744. }
  3745. },
  3746. this
  3747. );
  3748. }
  3749. });
  3750. var FeatureLayer = FeatureManager.extend({
  3751. options: {
  3752. cacheLayers: true
  3753. },
  3754. /**
  3755. * Constructor
  3756. */
  3757. initialize: function (options) {
  3758. if (options.apikey) {
  3759. options.token = options.apikey;
  3760. }
  3761. FeatureManager.prototype.initialize.call(this, options);
  3762. this._originalStyle = this.options.style;
  3763. this._layers = {};
  3764. },
  3765. /**
  3766. * Layer Interface
  3767. */
  3768. onRemove: function (map) {
  3769. for (var i in this._layers) {
  3770. map.removeLayer(this._layers[i]);
  3771. // trigger the event when the entire featureLayer is removed from the map
  3772. this.fire(
  3773. 'removefeature',
  3774. {
  3775. feature: this._layers[i].feature,
  3776. permanent: false
  3777. },
  3778. true
  3779. );
  3780. }
  3781. return FeatureManager.prototype.onRemove.call(this, map);
  3782. },
  3783. createNewLayer: function (geojson) {
  3784. var layer = leaflet.GeoJSON.geometryToLayer(geojson, this.options);
  3785. // trap for GeoJSON without geometry
  3786. if (layer) {
  3787. layer.defaultOptions = layer.options;
  3788. }
  3789. return layer;
  3790. },
  3791. _updateLayer: function (layer, geojson) {
  3792. // convert the geojson coordinates into a Leaflet LatLng array/nested arrays
  3793. // pass it to setLatLngs to update layer geometries
  3794. var latlngs = [];
  3795. var coordsToLatLng = this.options.coordsToLatLng || leaflet.GeoJSON.coordsToLatLng;
  3796. // copy new attributes, if present
  3797. if (geojson.properties) {
  3798. layer.feature.properties = geojson.properties;
  3799. }
  3800. switch (geojson.geometry.type) {
  3801. case 'Point':
  3802. latlngs = leaflet.GeoJSON.coordsToLatLng(geojson.geometry.coordinates);
  3803. layer.setLatLng(latlngs);
  3804. break;
  3805. case 'LineString':
  3806. latlngs = leaflet.GeoJSON.coordsToLatLngs(
  3807. geojson.geometry.coordinates,
  3808. 0,
  3809. coordsToLatLng
  3810. );
  3811. layer.setLatLngs(latlngs);
  3812. break;
  3813. case 'MultiLineString':
  3814. latlngs = leaflet.GeoJSON.coordsToLatLngs(
  3815. geojson.geometry.coordinates,
  3816. 1,
  3817. coordsToLatLng
  3818. );
  3819. layer.setLatLngs(latlngs);
  3820. break;
  3821. case 'Polygon':
  3822. latlngs = leaflet.GeoJSON.coordsToLatLngs(
  3823. geojson.geometry.coordinates,
  3824. 1,
  3825. coordsToLatLng
  3826. );
  3827. layer.setLatLngs(latlngs);
  3828. break;
  3829. case 'MultiPolygon':
  3830. latlngs = leaflet.GeoJSON.coordsToLatLngs(
  3831. geojson.geometry.coordinates,
  3832. 2,
  3833. coordsToLatLng
  3834. );
  3835. layer.setLatLngs(latlngs);
  3836. break;
  3837. }
  3838. },
  3839. /**
  3840. * Feature Management Methods
  3841. */
  3842. createLayers: function (features) {
  3843. for (var i = features.length - 1; i >= 0; i--) {
  3844. var geojson = features[i];
  3845. var layer = this._layers[geojson.id];
  3846. var newLayer;
  3847. if (
  3848. this._visibleZoom() &&
  3849. layer &&
  3850. !this._map.hasLayer(layer) &&
  3851. (!this.options.timeField || this._featureWithinTimeRange(geojson))
  3852. ) {
  3853. this._map.addLayer(layer);
  3854. this.fire(
  3855. 'addfeature',
  3856. {
  3857. feature: layer.feature
  3858. },
  3859. true
  3860. );
  3861. }
  3862. // update geometry if necessary
  3863. if (
  3864. layer &&
  3865. this.options.simplifyFactor > 0 &&
  3866. (layer.setLatLngs || layer.setLatLng)
  3867. ) {
  3868. this._updateLayer(layer, geojson);
  3869. }
  3870. if (!layer) {
  3871. newLayer = this.createNewLayer(geojson);
  3872. if (!newLayer) {
  3873. warn('invalid GeoJSON encountered');
  3874. } else {
  3875. newLayer.feature = geojson;
  3876. // bubble events from individual layers to the feature layer
  3877. newLayer.addEventParent(this);
  3878. if (this.options.onEachFeature) {
  3879. this.options.onEachFeature(newLayer.feature, newLayer);
  3880. }
  3881. // cache the layer
  3882. this._layers[newLayer.feature.id] = newLayer;
  3883. // style the layer
  3884. this.setFeatureStyle(newLayer.feature.id, this.options.style);
  3885. this.fire(
  3886. 'createfeature',
  3887. {
  3888. feature: newLayer.feature
  3889. },
  3890. true
  3891. );
  3892. // add the layer if the current zoom level is inside the range defined for the layer, it is within the current time bounds or our layer is not time enabled
  3893. if (
  3894. this._visibleZoom() &&
  3895. (!this.options.timeField ||
  3896. (this.options.timeField && this._featureWithinTimeRange(geojson)))
  3897. ) {
  3898. this._map.addLayer(newLayer);
  3899. }
  3900. }
  3901. }
  3902. }
  3903. },
  3904. addLayers: function (ids) {
  3905. for (var i = ids.length - 1; i >= 0; i--) {
  3906. var layer = this._layers[ids[i]];
  3907. if (
  3908. layer &&
  3909. (!this.options.timeField || this._featureWithinTimeRange(layer.feature))
  3910. ) {
  3911. this._map.addLayer(layer);
  3912. this.fire(
  3913. 'addfeature',
  3914. {
  3915. feature: layer.feature
  3916. },
  3917. true
  3918. );
  3919. }
  3920. }
  3921. },
  3922. removeLayers: function (ids, permanent) {
  3923. for (var i = ids.length - 1; i >= 0; i--) {
  3924. var id = ids[i];
  3925. var layer = this._layers[id];
  3926. if (layer) {
  3927. this.fire(
  3928. 'removefeature',
  3929. {
  3930. feature: layer.feature,
  3931. permanent: permanent
  3932. },
  3933. true
  3934. );
  3935. this._map.removeLayer(layer);
  3936. }
  3937. if (layer && permanent) {
  3938. delete this._layers[id];
  3939. }
  3940. }
  3941. },
  3942. cellEnter: function (bounds, coords) {
  3943. if (this._visibleZoom() && !this._zooming && this._map) {
  3944. leaflet.Util.requestAnimFrame(
  3945. leaflet.Util.bind(function () {
  3946. var cacheKey = this._cacheKey(coords);
  3947. var cellKey = this._cellCoordsToKey(coords);
  3948. var layers = this._cache[cacheKey];
  3949. if (this._activeCells[cellKey] && layers) {
  3950. this.addLayers(layers);
  3951. }
  3952. }, this)
  3953. );
  3954. }
  3955. },
  3956. cellLeave: function (bounds, coords) {
  3957. if (!this._zooming) {
  3958. leaflet.Util.requestAnimFrame(
  3959. leaflet.Util.bind(function () {
  3960. if (this._map) {
  3961. var cacheKey = this._cacheKey(coords);
  3962. var cellKey = this._cellCoordsToKey(coords);
  3963. var layers = this._cache[cacheKey];
  3964. var mapBounds = this._map.getBounds();
  3965. if (!this._activeCells[cellKey] && layers) {
  3966. var removable = true;
  3967. for (var i = 0; i < layers.length; i++) {
  3968. var layer = this._layers[layers[i]];
  3969. if (
  3970. layer &&
  3971. layer.getBounds &&
  3972. mapBounds.intersects(layer.getBounds())
  3973. ) {
  3974. removable = false;
  3975. }
  3976. }
  3977. if (removable) {
  3978. this.removeLayers(layers, !this.options.cacheLayers);
  3979. }
  3980. if (!this.options.cacheLayers && removable) {
  3981. delete this._cache[cacheKey];
  3982. delete this._cells[cellKey];
  3983. delete this._activeCells[cellKey];
  3984. }
  3985. }
  3986. }
  3987. }, this)
  3988. );
  3989. }
  3990. },
  3991. /**
  3992. * Styling Methods
  3993. */
  3994. resetStyle: function () {
  3995. this.options.style = this._originalStyle;
  3996. this.eachFeature(function (layer) {
  3997. this.resetFeatureStyle(layer.feature.id);
  3998. }, this);
  3999. return this;
  4000. },
  4001. setStyle: function (style) {
  4002. this.options.style = style;
  4003. this.eachFeature(function (layer) {
  4004. this.setFeatureStyle(layer.feature.id, style);
  4005. }, this);
  4006. return this;
  4007. },
  4008. resetFeatureStyle: function (id) {
  4009. var layer = this._layers[id];
  4010. var style = this._originalStyle || leaflet.Path.prototype.options;
  4011. if (layer) {
  4012. leaflet.Util.extend(layer.options, layer.defaultOptions);
  4013. this.setFeatureStyle(id, style);
  4014. }
  4015. return this;
  4016. },
  4017. setFeatureStyle: function (id, style) {
  4018. var layer = this._layers[id];
  4019. if (typeof style === 'function') {
  4020. style = style(layer.feature);
  4021. }
  4022. if (layer.setStyle) {
  4023. layer.setStyle(style);
  4024. }
  4025. return this;
  4026. },
  4027. /**
  4028. * Utility Methods
  4029. */
  4030. eachActiveFeature: function (fn, context) {
  4031. // figure out (roughly) which layers are in view
  4032. if (this._map) {
  4033. var activeBounds = this._map.getBounds();
  4034. for (var i in this._layers) {
  4035. if (this._currentSnapshot.indexOf(this._layers[i].feature.id) !== -1) {
  4036. // a simple point in poly test for point geometries
  4037. if (
  4038. typeof this._layers[i].getLatLng === 'function' &&
  4039. activeBounds.contains(this._layers[i].getLatLng())
  4040. ) {
  4041. fn.call(context, this._layers[i]);
  4042. } else if (
  4043. typeof this._layers[i].getBounds === 'function' &&
  4044. activeBounds.intersects(this._layers[i].getBounds())
  4045. ) {
  4046. // intersecting bounds check for polyline and polygon geometries
  4047. fn.call(context, this._layers[i]);
  4048. }
  4049. }
  4050. }
  4051. }
  4052. return this;
  4053. },
  4054. eachFeature: function (fn, context) {
  4055. for (var i in this._layers) {
  4056. fn.call(context, this._layers[i]);
  4057. }
  4058. return this;
  4059. },
  4060. getFeature: function (id) {
  4061. return this._layers[id];
  4062. },
  4063. bringToBack: function () {
  4064. this.eachFeature(function (layer) {
  4065. if (layer.bringToBack) {
  4066. layer.bringToBack();
  4067. }
  4068. });
  4069. },
  4070. bringToFront: function () {
  4071. this.eachFeature(function (layer) {
  4072. if (layer.bringToFront) {
  4073. layer.bringToFront();
  4074. }
  4075. });
  4076. },
  4077. redraw: function (id) {
  4078. if (id) {
  4079. this._redraw(id);
  4080. }
  4081. return this;
  4082. },
  4083. _redraw: function (id) {
  4084. var layer = this._layers[id];
  4085. var geojson = layer.feature;
  4086. // if this looks like a marker
  4087. if (layer && layer.setIcon && this.options.pointToLayer) {
  4088. // update custom symbology, if necessary
  4089. if (this.options.pointToLayer) {
  4090. var getIcon = this.options.pointToLayer(
  4091. geojson,
  4092. leaflet.latLng(
  4093. geojson.geometry.coordinates[1],
  4094. geojson.geometry.coordinates[0]
  4095. )
  4096. );
  4097. var updatedIcon = getIcon.options.icon;
  4098. layer.setIcon(updatedIcon);
  4099. }
  4100. }
  4101. // looks like a vector marker (circleMarker)
  4102. if (layer && layer.setStyle && this.options.pointToLayer) {
  4103. var getStyle = this.options.pointToLayer(
  4104. geojson,
  4105. leaflet.latLng(geojson.geometry.coordinates[1], geojson.geometry.coordinates[0])
  4106. );
  4107. var updatedStyle = getStyle.options;
  4108. this.setFeatureStyle(geojson.id, updatedStyle);
  4109. }
  4110. // looks like a path (polygon/polyline)
  4111. if (layer && layer.setStyle && this.options.style) {
  4112. this.resetStyle(geojson.id);
  4113. }
  4114. }
  4115. });
  4116. function featureLayer (options) {
  4117. return new FeatureLayer(options);
  4118. }
  4119. // export version
  4120. var version = packageInfo.version;
  4121. exports.BasemapLayer = BasemapLayer;
  4122. exports.DynamicMapLayer = DynamicMapLayer;
  4123. exports.FeatureLayer = FeatureLayer;
  4124. exports.FeatureLayerService = FeatureLayerService;
  4125. exports.FeatureManager = FeatureManager;
  4126. exports.Find = Find;
  4127. exports.Identify = Identify;
  4128. exports.IdentifyFeatures = IdentifyFeatures;
  4129. exports.IdentifyImage = IdentifyImage;
  4130. exports.ImageMapLayer = ImageMapLayer;
  4131. exports.ImageService = ImageService;
  4132. exports.MapService = MapService;
  4133. exports.Query = Query;
  4134. exports.RasterLayer = RasterLayer;
  4135. exports.Service = Service;
  4136. exports.Support = Support;
  4137. exports.Task = Task;
  4138. exports.TiledMapLayer = TiledMapLayer;
  4139. exports.Util = EsriUtil;
  4140. exports.VERSION = version;
  4141. exports.basemapLayer = basemapLayer;
  4142. exports.dynamicMapLayer = dynamicMapLayer;
  4143. exports.featureLayer = featureLayer;
  4144. exports.featureLayerService = featureLayerService;
  4145. exports.find = find;
  4146. exports.get = get;
  4147. exports.identify = identify;
  4148. exports.identifyFeatures = identifyFeatures;
  4149. exports.identifyImage = identifyImage;
  4150. exports.imageMapLayer = imageMapLayer;
  4151. exports.imageService = imageService;
  4152. exports.mapService = mapService;
  4153. exports.options = options;
  4154. exports.post = xmlHttpPost;
  4155. exports.query = query;
  4156. exports.request = request;
  4157. exports.service = service;
  4158. exports.task = task;
  4159. exports.tiledMapLayer = tiledMapLayer;
  4160. Object.defineProperty(exports, '__esModule', { value: true });
  4161. })));
  4162. //# sourceMappingURL=esri-leaflet-debug.js.map