KmlDataSource.js 119 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085
  1. import ArcType from "../Core/ArcType.js";
  2. import AssociativeArray from "../Core/AssociativeArray.js";
  3. import BoundingRectangle from "../Core/BoundingRectangle.js";
  4. import Cartesian2 from "../Core/Cartesian2.js";
  5. import Cartesian3 from "../Core/Cartesian3.js";
  6. import Cartographic from "../Core/Cartographic.js";
  7. import ClockRange from "../Core/ClockRange.js";
  8. import ClockStep from "../Core/ClockStep.js";
  9. import clone from "../Core/clone.js";
  10. import Color from "../Core/Color.js";
  11. import createGuid from "../Core/createGuid.js";
  12. import Credit from "../Core/Credit.js";
  13. import defaultValue from "../Core/defaultValue.js";
  14. import defined from "../Core/defined.js";
  15. import DeveloperError from "../Core/DeveloperError.js";
  16. import Ellipsoid from "../Core/Ellipsoid.js";
  17. import Event from "../Core/Event.js";
  18. import getExtensionFromUri from "../Core/getExtensionFromUri.js";
  19. import getFilenameFromUri from "../Core/getFilenameFromUri.js";
  20. import getTimestamp from "../Core/getTimestamp.js";
  21. import HeadingPitchRange from "../Core/HeadingPitchRange.js";
  22. import HeadingPitchRoll from "../Core/HeadingPitchRoll.js";
  23. import Iso8601 from "../Core/Iso8601.js";
  24. import JulianDate from "../Core/JulianDate.js";
  25. import CesiumMath from "../Core/Math.js";
  26. import NearFarScalar from "../Core/NearFarScalar.js";
  27. import objectToQuery from "../Core/objectToQuery.js";
  28. import oneTimeWarning from "../Core/oneTimeWarning.js";
  29. import PinBuilder from "../Core/PinBuilder.js";
  30. import PolygonHierarchy from "../Core/PolygonHierarchy.js";
  31. import queryToObject from "../Core/queryToObject.js";
  32. import Rectangle from "../Core/Rectangle.js";
  33. import Resource from "../Core/Resource.js";
  34. import RuntimeError from "../Core/RuntimeError.js";
  35. import TimeInterval from "../Core/TimeInterval.js";
  36. import TimeIntervalCollection from "../Core/TimeIntervalCollection.js";
  37. import HeightReference from "../Scene/HeightReference.js";
  38. import HorizontalOrigin from "../Scene/HorizontalOrigin.js";
  39. import LabelStyle from "../Scene/LabelStyle.js";
  40. import SceneMode from "../Scene/SceneMode.js";
  41. import Autolinker from "../ThirdParty/Autolinker.js";
  42. import Uri from "../ThirdParty/Uri.js";
  43. import when from "../ThirdParty/when.js";
  44. import zip from "../ThirdParty/zip.js";
  45. import BillboardGraphics from "./BillboardGraphics.js";
  46. import CompositePositionProperty from "./CompositePositionProperty.js";
  47. import DataSource from "./DataSource.js";
  48. import DataSourceClock from "./DataSourceClock.js";
  49. import Entity from "./Entity.js";
  50. import EntityCluster from "./EntityCluster.js";
  51. import EntityCollection from "./EntityCollection.js";
  52. import KmlCamera from "./KmlCamera.js";
  53. import KmlLookAt from "./KmlLookAt.js";
  54. import KmlTour from "./KmlTour.js";
  55. import KmlTourFlyTo from "./KmlTourFlyTo.js";
  56. import KmlTourWait from "./KmlTourWait.js";
  57. import LabelGraphics from "./LabelGraphics.js";
  58. import PathGraphics from "./PathGraphics.js";
  59. import PolygonGraphics from "./PolygonGraphics.js";
  60. import PolylineGraphics from "./PolylineGraphics.js";
  61. import PositionPropertyArray from "./PositionPropertyArray.js";
  62. import RectangleGraphics from "./RectangleGraphics.js";
  63. import ReferenceProperty from "./ReferenceProperty.js";
  64. import SampledPositionProperty from "./SampledPositionProperty.js";
  65. import ScaledPositionProperty from "./ScaledPositionProperty.js";
  66. import TimeIntervalCollectionProperty from "./TimeIntervalCollectionProperty.js";
  67. import WallGraphics from "./WallGraphics.js";
  68. //This is by no means an exhaustive list of MIME types.
  69. //The purpose of this list is to be able to accurately identify content embedded
  70. //in KMZ files. Eventually, we can make this configurable by the end user so they can add
  71. //there own content types if they have KMZ files that require it.
  72. var MimeTypes = {
  73. avi: "video/x-msvideo",
  74. bmp: "image/bmp",
  75. bz2: "application/x-bzip2",
  76. chm: "application/vnd.ms-htmlhelp",
  77. css: "text/css",
  78. csv: "text/csv",
  79. doc: "application/msword",
  80. dvi: "application/x-dvi",
  81. eps: "application/postscript",
  82. flv: "video/x-flv",
  83. gif: "image/gif",
  84. gz: "application/x-gzip",
  85. htm: "text/html",
  86. html: "text/html",
  87. ico: "image/vnd.microsoft.icon",
  88. jnlp: "application/x-java-jnlp-file",
  89. jpeg: "image/jpeg",
  90. jpg: "image/jpeg",
  91. m3u: "audio/x-mpegurl",
  92. m4v: "video/mp4",
  93. mathml: "application/mathml+xml",
  94. mid: "audio/midi",
  95. midi: "audio/midi",
  96. mov: "video/quicktime",
  97. mp3: "audio/mpeg",
  98. mp4: "video/mp4",
  99. mp4v: "video/mp4",
  100. mpeg: "video/mpeg",
  101. mpg: "video/mpeg",
  102. odp: "application/vnd.oasis.opendocument.presentation",
  103. ods: "application/vnd.oasis.opendocument.spreadsheet",
  104. odt: "application/vnd.oasis.opendocument.text",
  105. ogg: "application/ogg",
  106. pdf: "application/pdf",
  107. png: "image/png",
  108. pps: "application/vnd.ms-powerpoint",
  109. ppt: "application/vnd.ms-powerpoint",
  110. ps: "application/postscript",
  111. qt: "video/quicktime",
  112. rdf: "application/rdf+xml",
  113. rss: "application/rss+xml",
  114. rtf: "application/rtf",
  115. svg: "image/svg+xml",
  116. swf: "application/x-shockwave-flash",
  117. text: "text/plain",
  118. tif: "image/tiff",
  119. tiff: "image/tiff",
  120. txt: "text/plain",
  121. wav: "audio/x-wav",
  122. wma: "audio/x-ms-wma",
  123. wmv: "video/x-ms-wmv",
  124. xml: "application/xml",
  125. zip: "application/zip",
  126. detectFromFilename: function (filename) {
  127. var ext = filename.toLowerCase();
  128. ext = getExtensionFromUri(ext);
  129. return MimeTypes[ext];
  130. },
  131. };
  132. var parser;
  133. if (typeof DOMParser !== "undefined") {
  134. parser = new DOMParser();
  135. }
  136. var autolinker = new Autolinker({
  137. stripPrefix: false,
  138. email: false,
  139. replaceFn: function (match) {
  140. if (!match.protocolUrlMatch) {
  141. //Prevent matching of non-explicit urls.
  142. //i.e. foo.id won't match but http://foo.id will
  143. return false;
  144. }
  145. },
  146. });
  147. var BILLBOARD_SIZE = 32;
  148. var BILLBOARD_NEAR_DISTANCE = 2414016;
  149. var BILLBOARD_NEAR_RATIO = 1.0;
  150. var BILLBOARD_FAR_DISTANCE = 1.6093e7;
  151. var BILLBOARD_FAR_RATIO = 0.1;
  152. var kmlNamespaces = [
  153. null,
  154. undefined,
  155. "http://www.opengis.net/kml/2.2",
  156. "http://earth.google.com/kml/2.2",
  157. "http://earth.google.com/kml/2.1",
  158. "http://earth.google.com/kml/2.0",
  159. ];
  160. var gxNamespaces = ["http://www.google.com/kml/ext/2.2"];
  161. var atomNamespaces = ["http://www.w3.org/2005/Atom"];
  162. var namespaces = {
  163. kml: kmlNamespaces,
  164. gx: gxNamespaces,
  165. atom: atomNamespaces,
  166. kmlgx: kmlNamespaces.concat(gxNamespaces),
  167. };
  168. // Ensure Specs/Data/KML/unsupported.kml is kept up to date with these supported types
  169. var featureTypes = {
  170. Document: processDocument,
  171. Folder: processFolder,
  172. Placemark: processPlacemark,
  173. NetworkLink: processNetworkLink,
  174. GroundOverlay: processGroundOverlay,
  175. PhotoOverlay: processUnsupportedFeature,
  176. ScreenOverlay: processUnsupportedFeature,
  177. Tour: processTour,
  178. };
  179. function DeferredLoading(dataSource) {
  180. this._dataSource = dataSource;
  181. this._deferred = when.defer();
  182. this._stack = [];
  183. this._promises = [];
  184. this._timeoutSet = false;
  185. this._used = false;
  186. this._started = 0;
  187. this._timeThreshold = 1000; // Initial load is 1 second
  188. }
  189. Object.defineProperties(DeferredLoading.prototype, {
  190. dataSource: {
  191. get: function () {
  192. return this._dataSource;
  193. },
  194. },
  195. });
  196. DeferredLoading.prototype.addNodes = function (nodes, processingData) {
  197. this._stack.push({
  198. nodes: nodes,
  199. index: 0,
  200. processingData: processingData,
  201. });
  202. this._used = true;
  203. };
  204. DeferredLoading.prototype.addPromise = function (promise) {
  205. this._promises.push(promise);
  206. };
  207. DeferredLoading.prototype.wait = function () {
  208. // Case where we had a non-document/folder as the root
  209. var deferred = this._deferred;
  210. if (!this._used) {
  211. deferred.resolve();
  212. }
  213. return when.join(deferred.promise, when.all(this._promises));
  214. };
  215. DeferredLoading.prototype.process = function () {
  216. var isFirstCall = this._stack.length === 1;
  217. if (isFirstCall) {
  218. this._started = KmlDataSource._getTimestamp();
  219. }
  220. return this._process(isFirstCall);
  221. };
  222. DeferredLoading.prototype._giveUpTime = function () {
  223. if (this._timeoutSet) {
  224. // Timeout was already set so just return
  225. return;
  226. }
  227. this._timeoutSet = true;
  228. this._timeThreshold = 50; // After the first load lower threshold to 0.5 seconds
  229. var that = this;
  230. setTimeout(function () {
  231. that._timeoutSet = false;
  232. that._started = KmlDataSource._getTimestamp();
  233. that._process(true);
  234. }, 0);
  235. };
  236. DeferredLoading.prototype._nextNode = function () {
  237. var stack = this._stack;
  238. var top = stack[stack.length - 1];
  239. var index = top.index;
  240. var nodes = top.nodes;
  241. if (index === nodes.length) {
  242. return;
  243. }
  244. ++top.index;
  245. return nodes[index];
  246. };
  247. DeferredLoading.prototype._pop = function () {
  248. var stack = this._stack;
  249. stack.pop();
  250. // Return false if we are done
  251. if (stack.length === 0) {
  252. this._deferred.resolve();
  253. return false;
  254. }
  255. return true;
  256. };
  257. DeferredLoading.prototype._process = function (isFirstCall) {
  258. var dataSource = this.dataSource;
  259. var processingData = this._stack[this._stack.length - 1].processingData;
  260. var child = this._nextNode();
  261. while (defined(child)) {
  262. var featureProcessor = featureTypes[child.localName];
  263. if (
  264. defined(featureProcessor) &&
  265. (namespaces.kml.indexOf(child.namespaceURI) !== -1 ||
  266. namespaces.gx.indexOf(child.namespaceURI) !== -1)
  267. ) {
  268. featureProcessor(dataSource, child, processingData, this);
  269. // Give up time and continue loading later
  270. if (
  271. this._timeoutSet ||
  272. KmlDataSource._getTimestamp() > this._started + this._timeThreshold
  273. ) {
  274. this._giveUpTime();
  275. return;
  276. }
  277. }
  278. child = this._nextNode();
  279. }
  280. // If we are a recursive call from a subfolder, just return so the parent folder can continue processing
  281. // If we aren't then make another call to processNodes because there is stuff still left in the queue
  282. if (this._pop() && isFirstCall) {
  283. this._process(true);
  284. }
  285. };
  286. function isZipFile(blob) {
  287. var magicBlob = blob.slice(0, Math.min(4, blob.size));
  288. var deferred = when.defer();
  289. var reader = new FileReader();
  290. reader.addEventListener("load", function () {
  291. deferred.resolve(
  292. new DataView(reader.result).getUint32(0, false) === 0x504b0304
  293. );
  294. });
  295. reader.addEventListener("error", function () {
  296. deferred.reject(reader.error);
  297. });
  298. reader.readAsArrayBuffer(magicBlob);
  299. return deferred.promise;
  300. }
  301. function readBlobAsText(blob) {
  302. var deferred = when.defer();
  303. var reader = new FileReader();
  304. reader.addEventListener("load", function () {
  305. deferred.resolve(reader.result);
  306. });
  307. reader.addEventListener("error", function () {
  308. deferred.reject(reader.error);
  309. });
  310. reader.readAsText(blob);
  311. return deferred.promise;
  312. }
  313. function insertNamespaces(text) {
  314. var namespaceMap = {
  315. xsi: "http://www.w3.org/2001/XMLSchema-instance",
  316. };
  317. var firstPart, lastPart, reg, declaration;
  318. for (var key in namespaceMap) {
  319. if (namespaceMap.hasOwnProperty(key)) {
  320. reg = RegExp("[< ]" + key + ":");
  321. declaration = "xmlns:" + key + "=";
  322. if (reg.test(text) && text.indexOf(declaration) === -1) {
  323. if (!defined(firstPart)) {
  324. firstPart = text.substr(0, text.indexOf("<kml") + 4);
  325. lastPart = text.substr(firstPart.length);
  326. }
  327. firstPart += " " + declaration + '"' + namespaceMap[key] + '"';
  328. }
  329. }
  330. }
  331. if (defined(firstPart)) {
  332. text = firstPart + lastPart;
  333. }
  334. return text;
  335. }
  336. function removeDuplicateNamespaces(text) {
  337. var index = text.indexOf("xmlns:");
  338. var endDeclaration = text.indexOf(">", index);
  339. var namespace, startIndex, endIndex;
  340. while (index !== -1 && index < endDeclaration) {
  341. namespace = text.slice(index, text.indexOf('"', index));
  342. startIndex = index;
  343. index = text.indexOf(namespace, index + 1);
  344. if (index !== -1) {
  345. endIndex = text.indexOf('"', text.indexOf('"', index) + 1);
  346. text = text.slice(0, index - 1) + text.slice(endIndex + 1, text.length);
  347. index = text.indexOf("xmlns:", startIndex - 1);
  348. } else {
  349. index = text.indexOf("xmlns:", startIndex + 1);
  350. }
  351. }
  352. return text;
  353. }
  354. function loadXmlFromZip(entry, uriResolver, deferred) {
  355. entry.getData(new zip.TextWriter(), function (text) {
  356. text = insertNamespaces(text);
  357. text = removeDuplicateNamespaces(text);
  358. uriResolver.kml = parser.parseFromString(text, "application/xml");
  359. deferred.resolve();
  360. });
  361. }
  362. function loadDataUriFromZip(entry, uriResolver, deferred) {
  363. var mimeType = defaultValue(
  364. MimeTypes.detectFromFilename(entry.filename),
  365. "application/octet-stream"
  366. );
  367. entry.getData(new zip.Data64URIWriter(mimeType), function (dataUri) {
  368. uriResolver[entry.filename] = dataUri;
  369. deferred.resolve();
  370. });
  371. }
  372. function embedDataUris(div, elementType, attributeName, uriResolver) {
  373. var keys = uriResolver.keys;
  374. var baseUri = new Uri(".");
  375. var elements = div.querySelectorAll(elementType);
  376. for (var i = 0; i < elements.length; i++) {
  377. var element = elements[i];
  378. var value = element.getAttribute(attributeName);
  379. var uri = new Uri(value).resolve(baseUri).toString();
  380. var index = keys.indexOf(uri);
  381. if (index !== -1) {
  382. var key = keys[index];
  383. element.setAttribute(attributeName, uriResolver[key]);
  384. if (elementType === "a" && element.getAttribute("download") === null) {
  385. element.setAttribute("download", key);
  386. }
  387. }
  388. }
  389. }
  390. function applyBasePath(div, elementType, attributeName, sourceResource) {
  391. var elements = div.querySelectorAll(elementType);
  392. for (var i = 0; i < elements.length; i++) {
  393. var element = elements[i];
  394. var value = element.getAttribute(attributeName);
  395. var resource = resolveHref(value, sourceResource);
  396. element.setAttribute(attributeName, resource.url);
  397. }
  398. }
  399. // an optional context is passed to allow for some malformed kmls (those with multiple geometries with same ids) to still parse
  400. // correctly, as they do in Google Earth.
  401. function createEntity(node, entityCollection, context) {
  402. var id = queryStringAttribute(node, "id");
  403. id = defined(id) && id.length !== 0 ? id : createGuid();
  404. if (defined(context)) {
  405. id = context + id;
  406. }
  407. // If we have a duplicate ID just generate one.
  408. // This isn't valid KML but Google Earth handles this case.
  409. var entity = entityCollection.getById(id);
  410. if (defined(entity)) {
  411. id = createGuid();
  412. if (defined(context)) {
  413. id = context + id;
  414. }
  415. }
  416. entity = entityCollection.add(new Entity({ id: id }));
  417. if (!defined(entity.kml)) {
  418. entity.addProperty("kml");
  419. entity.kml = new KmlFeatureData();
  420. }
  421. return entity;
  422. }
  423. function isExtrudable(altitudeMode, gxAltitudeMode) {
  424. return (
  425. altitudeMode === "absolute" ||
  426. altitudeMode === "relativeToGround" ||
  427. gxAltitudeMode === "relativeToSeaFloor"
  428. );
  429. }
  430. function readCoordinate(value, ellipsoid) {
  431. //Google Earth treats empty or missing coordinates as 0.
  432. if (!defined(value)) {
  433. return Cartesian3.fromDegrees(0, 0, 0, ellipsoid);
  434. }
  435. var digits = value.match(/[^\s,\n]+/g);
  436. if (!defined(digits)) {
  437. return Cartesian3.fromDegrees(0, 0, 0, ellipsoid);
  438. }
  439. var longitude = parseFloat(digits[0]);
  440. var latitude = parseFloat(digits[1]);
  441. var height = parseFloat(digits[2]);
  442. longitude = isNaN(longitude) ? 0.0 : longitude;
  443. latitude = isNaN(latitude) ? 0.0 : latitude;
  444. height = isNaN(height) ? 0.0 : height;
  445. return Cartesian3.fromDegrees(longitude, latitude, height, ellipsoid);
  446. }
  447. function readCoordinates(element, ellipsoid) {
  448. if (!defined(element)) {
  449. return undefined;
  450. }
  451. var tuples = element.textContent.match(/[^\s\n]+/g);
  452. if (!defined(tuples)) {
  453. return undefined;
  454. }
  455. var length = tuples.length;
  456. var result = new Array(length);
  457. var resultIndex = 0;
  458. for (var i = 0; i < length; i++) {
  459. result[resultIndex++] = readCoordinate(tuples[i], ellipsoid);
  460. }
  461. return result;
  462. }
  463. function queryNumericAttribute(node, attributeName) {
  464. if (!defined(node)) {
  465. return undefined;
  466. }
  467. var value = node.getAttribute(attributeName);
  468. if (value !== null) {
  469. var result = parseFloat(value);
  470. return !isNaN(result) ? result : undefined;
  471. }
  472. return undefined;
  473. }
  474. function queryStringAttribute(node, attributeName) {
  475. if (!defined(node)) {
  476. return undefined;
  477. }
  478. var value = node.getAttribute(attributeName);
  479. return value !== null ? value : undefined;
  480. }
  481. function queryFirstNode(node, tagName, namespace) {
  482. if (!defined(node)) {
  483. return undefined;
  484. }
  485. var childNodes = node.childNodes;
  486. var length = childNodes.length;
  487. for (var q = 0; q < length; q++) {
  488. var child = childNodes[q];
  489. if (
  490. child.localName === tagName &&
  491. namespace.indexOf(child.namespaceURI) !== -1
  492. ) {
  493. return child;
  494. }
  495. }
  496. return undefined;
  497. }
  498. function queryNodes(node, tagName, namespace) {
  499. if (!defined(node)) {
  500. return undefined;
  501. }
  502. var result = [];
  503. var childNodes = node.getElementsByTagNameNS("*", tagName);
  504. var length = childNodes.length;
  505. for (var q = 0; q < length; q++) {
  506. var child = childNodes[q];
  507. if (
  508. child.localName === tagName &&
  509. namespace.indexOf(child.namespaceURI) !== -1
  510. ) {
  511. result.push(child);
  512. }
  513. }
  514. return result;
  515. }
  516. function queryChildNodes(node, tagName, namespace) {
  517. if (!defined(node)) {
  518. return [];
  519. }
  520. var result = [];
  521. var childNodes = node.childNodes;
  522. var length = childNodes.length;
  523. for (var q = 0; q < length; q++) {
  524. var child = childNodes[q];
  525. if (
  526. child.localName === tagName &&
  527. namespace.indexOf(child.namespaceURI) !== -1
  528. ) {
  529. result.push(child);
  530. }
  531. }
  532. return result;
  533. }
  534. function queryNumericValue(node, tagName, namespace) {
  535. var resultNode = queryFirstNode(node, tagName, namespace);
  536. if (defined(resultNode)) {
  537. var result = parseFloat(resultNode.textContent);
  538. return !isNaN(result) ? result : undefined;
  539. }
  540. return undefined;
  541. }
  542. function queryStringValue(node, tagName, namespace) {
  543. var result = queryFirstNode(node, tagName, namespace);
  544. if (defined(result)) {
  545. return result.textContent.trim();
  546. }
  547. return undefined;
  548. }
  549. function queryBooleanValue(node, tagName, namespace) {
  550. var result = queryFirstNode(node, tagName, namespace);
  551. if (defined(result)) {
  552. var value = result.textContent.trim();
  553. return value === "1" || /^true$/i.test(value);
  554. }
  555. return undefined;
  556. }
  557. function resolveHref(href, sourceResource, uriResolver) {
  558. if (!defined(href)) {
  559. return undefined;
  560. }
  561. var resource;
  562. if (defined(uriResolver)) {
  563. // To resolve issues with KML sources defined in Windows style paths.
  564. href = href.replace(/\\/g, "/");
  565. var blob = uriResolver[href];
  566. if (defined(blob)) {
  567. resource = new Resource({
  568. url: blob,
  569. });
  570. } else {
  571. // Needed for multiple levels of KML files in a KMZ
  572. var baseUri = new Uri(sourceResource.getUrlComponent());
  573. var uri = new Uri(href);
  574. blob = uriResolver[uri.resolve(baseUri)];
  575. if (defined(blob)) {
  576. resource = new Resource({
  577. url: blob,
  578. });
  579. }
  580. }
  581. }
  582. if (!defined(resource)) {
  583. resource = sourceResource.getDerivedResource({
  584. url: href,
  585. });
  586. }
  587. return resource;
  588. }
  589. var colorOptions = {
  590. maximumRed: undefined,
  591. red: undefined,
  592. maximumGreen: undefined,
  593. green: undefined,
  594. maximumBlue: undefined,
  595. blue: undefined,
  596. };
  597. function parseColorString(value, isRandom) {
  598. if (!defined(value) || /^\s*$/gm.test(value)) {
  599. return undefined;
  600. }
  601. if (value[0] === "#") {
  602. value = value.substring(1);
  603. }
  604. var alpha = parseInt(value.substring(0, 2), 16) / 255.0;
  605. var blue = parseInt(value.substring(2, 4), 16) / 255.0;
  606. var green = parseInt(value.substring(4, 6), 16) / 255.0;
  607. var red = parseInt(value.substring(6, 8), 16) / 255.0;
  608. if (!isRandom) {
  609. return new Color(red, green, blue, alpha);
  610. }
  611. if (red > 0) {
  612. colorOptions.maximumRed = red;
  613. colorOptions.red = undefined;
  614. } else {
  615. colorOptions.maximumRed = undefined;
  616. colorOptions.red = 0;
  617. }
  618. if (green > 0) {
  619. colorOptions.maximumGreen = green;
  620. colorOptions.green = undefined;
  621. } else {
  622. colorOptions.maximumGreen = undefined;
  623. colorOptions.green = 0;
  624. }
  625. if (blue > 0) {
  626. colorOptions.maximumBlue = blue;
  627. colorOptions.blue = undefined;
  628. } else {
  629. colorOptions.maximumBlue = undefined;
  630. colorOptions.blue = 0;
  631. }
  632. colorOptions.alpha = alpha;
  633. return Color.fromRandom(colorOptions);
  634. }
  635. function queryColorValue(node, tagName, namespace) {
  636. var value = queryStringValue(node, tagName, namespace);
  637. if (!defined(value)) {
  638. return undefined;
  639. }
  640. return parseColorString(
  641. value,
  642. queryStringValue(node, "colorMode", namespace) === "random"
  643. );
  644. }
  645. function processTimeStamp(featureNode) {
  646. var node = queryFirstNode(featureNode, "TimeStamp", namespaces.kmlgx);
  647. var whenString = queryStringValue(node, "when", namespaces.kmlgx);
  648. if (!defined(node) || !defined(whenString) || whenString.length === 0) {
  649. return undefined;
  650. }
  651. //According to the KML spec, a TimeStamp represents a "single moment in time"
  652. //However, since Cesium animates much differently than Google Earth, that doesn't
  653. //Make much sense here. Instead, we use the TimeStamp as the moment the feature
  654. //comes into existence. This works much better and gives a similar feel to
  655. //GE's experience.
  656. var when = JulianDate.fromIso8601(whenString);
  657. var result = new TimeIntervalCollection();
  658. result.addInterval(
  659. new TimeInterval({
  660. start: when,
  661. stop: Iso8601.MAXIMUM_VALUE,
  662. })
  663. );
  664. return result;
  665. }
  666. function processTimeSpan(featureNode) {
  667. var node = queryFirstNode(featureNode, "TimeSpan", namespaces.kmlgx);
  668. if (!defined(node)) {
  669. return undefined;
  670. }
  671. var result;
  672. var beginNode = queryFirstNode(node, "begin", namespaces.kmlgx);
  673. var beginDate = defined(beginNode)
  674. ? JulianDate.fromIso8601(beginNode.textContent)
  675. : undefined;
  676. var endNode = queryFirstNode(node, "end", namespaces.kmlgx);
  677. var endDate = defined(endNode)
  678. ? JulianDate.fromIso8601(endNode.textContent)
  679. : undefined;
  680. if (defined(beginDate) && defined(endDate)) {
  681. if (JulianDate.lessThan(endDate, beginDate)) {
  682. var tmp = beginDate;
  683. beginDate = endDate;
  684. endDate = tmp;
  685. }
  686. result = new TimeIntervalCollection();
  687. result.addInterval(
  688. new TimeInterval({
  689. start: beginDate,
  690. stop: endDate,
  691. })
  692. );
  693. } else if (defined(beginDate)) {
  694. result = new TimeIntervalCollection();
  695. result.addInterval(
  696. new TimeInterval({
  697. start: beginDate,
  698. stop: Iso8601.MAXIMUM_VALUE,
  699. })
  700. );
  701. } else if (defined(endDate)) {
  702. result = new TimeIntervalCollection();
  703. result.addInterval(
  704. new TimeInterval({
  705. start: Iso8601.MINIMUM_VALUE,
  706. stop: endDate,
  707. })
  708. );
  709. }
  710. return result;
  711. }
  712. function createDefaultBillboard() {
  713. var billboard = new BillboardGraphics();
  714. billboard.width = BILLBOARD_SIZE;
  715. billboard.height = BILLBOARD_SIZE;
  716. billboard.scaleByDistance = new NearFarScalar(
  717. BILLBOARD_NEAR_DISTANCE,
  718. BILLBOARD_NEAR_RATIO,
  719. BILLBOARD_FAR_DISTANCE,
  720. BILLBOARD_FAR_RATIO
  721. );
  722. billboard.pixelOffsetScaleByDistance = new NearFarScalar(
  723. BILLBOARD_NEAR_DISTANCE,
  724. BILLBOARD_NEAR_RATIO,
  725. BILLBOARD_FAR_DISTANCE,
  726. BILLBOARD_FAR_RATIO
  727. );
  728. return billboard;
  729. }
  730. function createDefaultPolygon() {
  731. var polygon = new PolygonGraphics();
  732. polygon.outline = true;
  733. polygon.outlineColor = Color.WHITE;
  734. return polygon;
  735. }
  736. function createDefaultLabel() {
  737. var label = new LabelGraphics();
  738. label.translucencyByDistance = new NearFarScalar(3000000, 1.0, 5000000, 0.0);
  739. label.pixelOffset = new Cartesian2(17, 0);
  740. label.horizontalOrigin = HorizontalOrigin.LEFT;
  741. label.font = "16px sans-serif";
  742. label.style = LabelStyle.FILL_AND_OUTLINE;
  743. return label;
  744. }
  745. function getIconHref(
  746. iconNode,
  747. dataSource,
  748. sourceResource,
  749. uriResolver,
  750. canRefresh
  751. ) {
  752. var href = queryStringValue(iconNode, "href", namespaces.kml);
  753. if (!defined(href) || href.length === 0) {
  754. return undefined;
  755. }
  756. if (href.indexOf("root://icons/palette-") === 0) {
  757. var palette = href.charAt(21);
  758. // Get the icon number
  759. var x = defaultValue(queryNumericValue(iconNode, "x", namespaces.gx), 0);
  760. var y = defaultValue(queryNumericValue(iconNode, "y", namespaces.gx), 0);
  761. x = Math.min(x / 32, 7);
  762. y = 7 - Math.min(y / 32, 7);
  763. var iconNum = 8 * y + x;
  764. href =
  765. "https://maps.google.com/mapfiles/kml/pal" +
  766. palette +
  767. "/icon" +
  768. iconNum +
  769. ".png";
  770. }
  771. var hrefResource = resolveHref(href, sourceResource, uriResolver);
  772. if (canRefresh) {
  773. var refreshMode = queryStringValue(iconNode, "refreshMode", namespaces.kml);
  774. var viewRefreshMode = queryStringValue(
  775. iconNode,
  776. "viewRefreshMode",
  777. namespaces.kml
  778. );
  779. if (refreshMode === "onInterval" || refreshMode === "onExpire") {
  780. oneTimeWarning(
  781. "kml-refreshMode-" + refreshMode,
  782. "KML - Unsupported Icon refreshMode: " + refreshMode
  783. );
  784. } else if (viewRefreshMode === "onStop" || viewRefreshMode === "onRegion") {
  785. oneTimeWarning(
  786. "kml-refreshMode-" + viewRefreshMode,
  787. "KML - Unsupported Icon viewRefreshMode: " + viewRefreshMode
  788. );
  789. }
  790. var viewBoundScale = defaultValue(
  791. queryStringValue(iconNode, "viewBoundScale", namespaces.kml),
  792. 1.0
  793. );
  794. var defaultViewFormat =
  795. viewRefreshMode === "onStop"
  796. ? "BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]"
  797. : "";
  798. var viewFormat = defaultValue(
  799. queryStringValue(iconNode, "viewFormat", namespaces.kml),
  800. defaultViewFormat
  801. );
  802. var httpQuery = queryStringValue(iconNode, "httpQuery", namespaces.kml);
  803. if (defined(viewFormat)) {
  804. hrefResource.setQueryParameters(queryToObject(cleanupString(viewFormat)));
  805. }
  806. if (defined(httpQuery)) {
  807. hrefResource.setQueryParameters(queryToObject(cleanupString(httpQuery)));
  808. }
  809. var ellipsoid = dataSource._ellipsoid;
  810. processNetworkLinkQueryString(
  811. hrefResource,
  812. dataSource._camera,
  813. dataSource._canvas,
  814. viewBoundScale,
  815. dataSource._lastCameraView.bbox,
  816. ellipsoid
  817. );
  818. return hrefResource;
  819. }
  820. return hrefResource;
  821. }
  822. function processBillboardIcon(
  823. dataSource,
  824. node,
  825. targetEntity,
  826. sourceResource,
  827. uriResolver
  828. ) {
  829. var scale = queryNumericValue(node, "scale", namespaces.kml);
  830. var heading = queryNumericValue(node, "heading", namespaces.kml);
  831. var color = queryColorValue(node, "color", namespaces.kml);
  832. var iconNode = queryFirstNode(node, "Icon", namespaces.kml);
  833. var icon = getIconHref(
  834. iconNode,
  835. dataSource,
  836. sourceResource,
  837. uriResolver,
  838. false
  839. );
  840. // If icon tags are present but blank, we do not want to show an icon
  841. if (defined(iconNode) && !defined(icon)) {
  842. icon = false;
  843. }
  844. var x = queryNumericValue(iconNode, "x", namespaces.gx);
  845. var y = queryNumericValue(iconNode, "y", namespaces.gx);
  846. var w = queryNumericValue(iconNode, "w", namespaces.gx);
  847. var h = queryNumericValue(iconNode, "h", namespaces.gx);
  848. var hotSpotNode = queryFirstNode(node, "hotSpot", namespaces.kml);
  849. var hotSpotX = queryNumericAttribute(hotSpotNode, "x");
  850. var hotSpotY = queryNumericAttribute(hotSpotNode, "y");
  851. var hotSpotXUnit = queryStringAttribute(hotSpotNode, "xunits");
  852. var hotSpotYUnit = queryStringAttribute(hotSpotNode, "yunits");
  853. var billboard = targetEntity.billboard;
  854. if (!defined(billboard)) {
  855. billboard = createDefaultBillboard();
  856. targetEntity.billboard = billboard;
  857. }
  858. billboard.image = icon;
  859. billboard.scale = scale;
  860. billboard.color = color;
  861. if (defined(x) || defined(y) || defined(w) || defined(h)) {
  862. billboard.imageSubRegion = new BoundingRectangle(x, y, w, h);
  863. }
  864. //GE treats a heading of zero as no heading
  865. //You can still point north using a 360 degree angle (or any multiple of 360)
  866. if (defined(heading) && heading !== 0) {
  867. billboard.rotation = CesiumMath.toRadians(-heading);
  868. billboard.alignedAxis = Cartesian3.UNIT_Z;
  869. }
  870. //Hotpot is the KML equivalent of pixel offset
  871. //The hotspot origin is the lower left, but we leave
  872. //our billboard origin at the center and simply
  873. //modify the pixel offset to take this into account
  874. scale = defaultValue(scale, 1.0);
  875. var xOffset;
  876. var yOffset;
  877. if (defined(hotSpotX)) {
  878. if (hotSpotXUnit === "pixels") {
  879. xOffset = -hotSpotX * scale;
  880. } else if (hotSpotXUnit === "insetPixels") {
  881. xOffset = (hotSpotX - BILLBOARD_SIZE) * scale;
  882. } else if (hotSpotXUnit === "fraction") {
  883. xOffset = -hotSpotX * BILLBOARD_SIZE * scale;
  884. }
  885. xOffset += BILLBOARD_SIZE * 0.5 * scale;
  886. }
  887. if (defined(hotSpotY)) {
  888. if (hotSpotYUnit === "pixels") {
  889. yOffset = hotSpotY * scale;
  890. } else if (hotSpotYUnit === "insetPixels") {
  891. yOffset = (-hotSpotY + BILLBOARD_SIZE) * scale;
  892. } else if (hotSpotYUnit === "fraction") {
  893. yOffset = hotSpotY * BILLBOARD_SIZE * scale;
  894. }
  895. yOffset -= BILLBOARD_SIZE * 0.5 * scale;
  896. }
  897. if (defined(xOffset) || defined(yOffset)) {
  898. billboard.pixelOffset = new Cartesian2(xOffset, yOffset);
  899. }
  900. }
  901. function applyStyle(
  902. dataSource,
  903. styleNode,
  904. targetEntity,
  905. sourceResource,
  906. uriResolver
  907. ) {
  908. for (var i = 0, len = styleNode.childNodes.length; i < len; i++) {
  909. var node = styleNode.childNodes.item(i);
  910. if (node.localName === "IconStyle") {
  911. processBillboardIcon(
  912. dataSource,
  913. node,
  914. targetEntity,
  915. sourceResource,
  916. uriResolver
  917. );
  918. } else if (node.localName === "LabelStyle") {
  919. var label = targetEntity.label;
  920. if (!defined(label)) {
  921. label = createDefaultLabel();
  922. targetEntity.label = label;
  923. }
  924. label.scale = defaultValue(
  925. queryNumericValue(node, "scale", namespaces.kml),
  926. label.scale
  927. );
  928. label.fillColor = defaultValue(
  929. queryColorValue(node, "color", namespaces.kml),
  930. label.fillColor
  931. );
  932. label.text = targetEntity.name;
  933. } else if (node.localName === "LineStyle") {
  934. var polyline = targetEntity.polyline;
  935. if (!defined(polyline)) {
  936. polyline = new PolylineGraphics();
  937. targetEntity.polyline = polyline;
  938. }
  939. polyline.width = queryNumericValue(node, "width", namespaces.kml);
  940. polyline.material = queryColorValue(node, "color", namespaces.kml);
  941. if (defined(queryColorValue(node, "outerColor", namespaces.gx))) {
  942. oneTimeWarning(
  943. "kml-gx:outerColor",
  944. "KML - gx:outerColor is not supported in a LineStyle"
  945. );
  946. }
  947. if (defined(queryNumericValue(node, "outerWidth", namespaces.gx))) {
  948. oneTimeWarning(
  949. "kml-gx:outerWidth",
  950. "KML - gx:outerWidth is not supported in a LineStyle"
  951. );
  952. }
  953. if (defined(queryNumericValue(node, "physicalWidth", namespaces.gx))) {
  954. oneTimeWarning(
  955. "kml-gx:physicalWidth",
  956. "KML - gx:physicalWidth is not supported in a LineStyle"
  957. );
  958. }
  959. if (defined(queryBooleanValue(node, "labelVisibility", namespaces.gx))) {
  960. oneTimeWarning(
  961. "kml-gx:labelVisibility",
  962. "KML - gx:labelVisibility is not supported in a LineStyle"
  963. );
  964. }
  965. } else if (node.localName === "PolyStyle") {
  966. var polygon = targetEntity.polygon;
  967. if (!defined(polygon)) {
  968. polygon = createDefaultPolygon();
  969. targetEntity.polygon = polygon;
  970. }
  971. polygon.material = defaultValue(
  972. queryColorValue(node, "color", namespaces.kml),
  973. polygon.material
  974. );
  975. polygon.fill = defaultValue(
  976. queryBooleanValue(node, "fill", namespaces.kml),
  977. polygon.fill
  978. );
  979. polygon.outline = defaultValue(
  980. queryBooleanValue(node, "outline", namespaces.kml),
  981. polygon.outline
  982. );
  983. } else if (node.localName === "BalloonStyle") {
  984. var bgColor = defaultValue(
  985. parseColorString(queryStringValue(node, "bgColor", namespaces.kml)),
  986. Color.WHITE
  987. );
  988. var textColor = defaultValue(
  989. parseColorString(queryStringValue(node, "textColor", namespaces.kml)),
  990. Color.BLACK
  991. );
  992. var text = queryStringValue(node, "text", namespaces.kml);
  993. //This is purely an internal property used in style processing,
  994. //it never ends up on the final entity.
  995. targetEntity.addProperty("balloonStyle");
  996. targetEntity.balloonStyle = {
  997. bgColor: bgColor,
  998. textColor: textColor,
  999. text: text,
  1000. };
  1001. } else if (node.localName === "ListStyle") {
  1002. var listItemType = queryStringValue(node, "listItemType", namespaces.kml);
  1003. if (listItemType === "radioFolder" || listItemType === "checkOffOnly") {
  1004. oneTimeWarning(
  1005. "kml-listStyle-" + listItemType,
  1006. "KML - Unsupported ListStyle with listItemType: " + listItemType
  1007. );
  1008. }
  1009. }
  1010. }
  1011. }
  1012. //Processes and merges any inline styles for the provided node into the provided entity.
  1013. function computeFinalStyle(
  1014. dataSource,
  1015. placeMark,
  1016. styleCollection,
  1017. sourceResource,
  1018. uriResolver
  1019. ) {
  1020. var result = new Entity();
  1021. var styleEntity;
  1022. //Google earth seems to always use the last inline Style/StyleMap only
  1023. var styleIndex = -1;
  1024. var childNodes = placeMark.childNodes;
  1025. var length = childNodes.length;
  1026. for (var q = 0; q < length; q++) {
  1027. var child = childNodes[q];
  1028. if (child.localName === "Style" || child.localName === "StyleMap") {
  1029. styleIndex = q;
  1030. }
  1031. }
  1032. if (styleIndex !== -1) {
  1033. var inlineStyleNode = childNodes[styleIndex];
  1034. if (inlineStyleNode.localName === "Style") {
  1035. applyStyle(
  1036. dataSource,
  1037. inlineStyleNode,
  1038. result,
  1039. sourceResource,
  1040. uriResolver
  1041. );
  1042. } else {
  1043. // StyleMap
  1044. var pairs = queryChildNodes(inlineStyleNode, "Pair", namespaces.kml);
  1045. for (var p = 0; p < pairs.length; p++) {
  1046. var pair = pairs[p];
  1047. var key = queryStringValue(pair, "key", namespaces.kml);
  1048. if (key === "normal") {
  1049. var styleUrl = queryStringValue(pair, "styleUrl", namespaces.kml);
  1050. if (defined(styleUrl)) {
  1051. styleEntity = styleCollection.getById(styleUrl);
  1052. if (!defined(styleEntity)) {
  1053. styleEntity = styleCollection.getById("#" + styleUrl);
  1054. }
  1055. if (defined(styleEntity)) {
  1056. result.merge(styleEntity);
  1057. }
  1058. } else {
  1059. var node = queryFirstNode(pair, "Style", namespaces.kml);
  1060. applyStyle(dataSource, node, result, sourceResource, uriResolver);
  1061. }
  1062. } else {
  1063. oneTimeWarning(
  1064. "kml-styleMap-" + key,
  1065. "KML - Unsupported StyleMap key: " + key
  1066. );
  1067. }
  1068. }
  1069. }
  1070. }
  1071. //Google earth seems to always use the first external style only.
  1072. var externalStyle = queryStringValue(placeMark, "styleUrl", namespaces.kml);
  1073. if (defined(externalStyle)) {
  1074. var id = externalStyle;
  1075. if (externalStyle[0] !== "#" && externalStyle.indexOf("#") !== -1) {
  1076. var tokens = externalStyle.split("#");
  1077. var uri = tokens[0];
  1078. var resource = sourceResource.getDerivedResource({
  1079. url: uri,
  1080. });
  1081. id = resource.getUrlComponent() + "#" + tokens[1];
  1082. }
  1083. styleEntity = styleCollection.getById(id);
  1084. if (!defined(styleEntity)) {
  1085. styleEntity = styleCollection.getById("#" + id);
  1086. }
  1087. if (defined(styleEntity)) {
  1088. result.merge(styleEntity);
  1089. }
  1090. }
  1091. return result;
  1092. }
  1093. //Asynchronously processes an external style file.
  1094. function processExternalStyles(dataSource, resource, styleCollection) {
  1095. return resource.fetchXML().then(function (styleKml) {
  1096. return processStyles(dataSource, styleKml, styleCollection, resource, true);
  1097. });
  1098. }
  1099. //Processes all shared and external styles and stores
  1100. //their id into the provided styleCollection.
  1101. //Returns an array of promises that will resolve when
  1102. //each style is loaded.
  1103. function processStyles(
  1104. dataSource,
  1105. kml,
  1106. styleCollection,
  1107. sourceResource,
  1108. isExternal,
  1109. uriResolver
  1110. ) {
  1111. var i;
  1112. var id;
  1113. var styleEntity;
  1114. var node;
  1115. var styleNodes = queryNodes(kml, "Style", namespaces.kml);
  1116. if (defined(styleNodes)) {
  1117. var styleNodesLength = styleNodes.length;
  1118. for (i = 0; i < styleNodesLength; i++) {
  1119. node = styleNodes[i];
  1120. id = queryStringAttribute(node, "id");
  1121. if (defined(id)) {
  1122. id = "#" + id;
  1123. if (isExternal && defined(sourceResource)) {
  1124. id = sourceResource.getUrlComponent() + id;
  1125. }
  1126. if (!defined(styleCollection.getById(id))) {
  1127. styleEntity = new Entity({
  1128. id: id,
  1129. });
  1130. styleCollection.add(styleEntity);
  1131. applyStyle(
  1132. dataSource,
  1133. node,
  1134. styleEntity,
  1135. sourceResource,
  1136. uriResolver
  1137. );
  1138. }
  1139. }
  1140. }
  1141. }
  1142. var styleMaps = queryNodes(kml, "StyleMap", namespaces.kml);
  1143. if (defined(styleMaps)) {
  1144. var styleMapsLength = styleMaps.length;
  1145. for (i = 0; i < styleMapsLength; i++) {
  1146. var styleMap = styleMaps[i];
  1147. id = queryStringAttribute(styleMap, "id");
  1148. if (defined(id)) {
  1149. var pairs = queryChildNodes(styleMap, "Pair", namespaces.kml);
  1150. for (var p = 0; p < pairs.length; p++) {
  1151. var pair = pairs[p];
  1152. var key = queryStringValue(pair, "key", namespaces.kml);
  1153. if (key === "normal") {
  1154. id = "#" + id;
  1155. if (isExternal && defined(sourceResource)) {
  1156. id = sourceResource.getUrlComponent() + id;
  1157. }
  1158. if (!defined(styleCollection.getById(id))) {
  1159. styleEntity = styleCollection.getOrCreateEntity(id);
  1160. var styleUrl = queryStringValue(pair, "styleUrl", namespaces.kml);
  1161. if (defined(styleUrl)) {
  1162. if (styleUrl[0] !== "#") {
  1163. styleUrl = "#" + styleUrl;
  1164. }
  1165. if (isExternal && defined(sourceResource)) {
  1166. styleUrl = sourceResource.getUrlComponent() + styleUrl;
  1167. }
  1168. var base = styleCollection.getById(styleUrl);
  1169. if (defined(base)) {
  1170. styleEntity.merge(base);
  1171. }
  1172. } else {
  1173. node = queryFirstNode(pair, "Style", namespaces.kml);
  1174. applyStyle(
  1175. dataSource,
  1176. node,
  1177. styleEntity,
  1178. sourceResource,
  1179. uriResolver
  1180. );
  1181. }
  1182. }
  1183. } else {
  1184. oneTimeWarning(
  1185. "kml-styleMap-" + key,
  1186. "KML - Unsupported StyleMap key: " + key
  1187. );
  1188. }
  1189. }
  1190. }
  1191. }
  1192. }
  1193. var promises = [];
  1194. var styleUrlNodes = kml.getElementsByTagName("styleUrl");
  1195. var styleUrlNodesLength = styleUrlNodes.length;
  1196. for (i = 0; i < styleUrlNodesLength; i++) {
  1197. var styleReference = styleUrlNodes[i].textContent;
  1198. if (styleReference[0] !== "#") {
  1199. //According to the spec, all local styles should start with a #
  1200. //and everything else is an external style that has a # seperating
  1201. //the URL of the document and the style. However, Google Earth
  1202. //also accepts styleUrls without a # as meaning a local style.
  1203. var tokens = styleReference.split("#");
  1204. if (tokens.length === 2) {
  1205. var uri = tokens[0];
  1206. var resource = sourceResource.getDerivedResource({
  1207. url: uri,
  1208. });
  1209. promises.push(
  1210. processExternalStyles(dataSource, resource, styleCollection)
  1211. );
  1212. }
  1213. }
  1214. }
  1215. return promises;
  1216. }
  1217. function createDropLine(entityCollection, entity, styleEntity) {
  1218. var entityPosition = new ReferenceProperty(entityCollection, entity.id, [
  1219. "position",
  1220. ]);
  1221. var surfacePosition = new ScaledPositionProperty(entity.position);
  1222. entity.polyline = defined(styleEntity.polyline)
  1223. ? styleEntity.polyline.clone()
  1224. : new PolylineGraphics();
  1225. entity.polyline.positions = new PositionPropertyArray([
  1226. entityPosition,
  1227. surfacePosition,
  1228. ]);
  1229. }
  1230. function heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode) {
  1231. if (
  1232. (!defined(altitudeMode) && !defined(gxAltitudeMode)) ||
  1233. altitudeMode === "clampToGround"
  1234. ) {
  1235. return HeightReference.CLAMP_TO_GROUND;
  1236. }
  1237. if (altitudeMode === "relativeToGround") {
  1238. return HeightReference.RELATIVE_TO_GROUND;
  1239. }
  1240. if (altitudeMode === "absolute") {
  1241. return HeightReference.NONE;
  1242. }
  1243. if (gxAltitudeMode === "clampToSeaFloor") {
  1244. oneTimeWarning(
  1245. "kml-gx:altitudeMode-clampToSeaFloor",
  1246. "KML - <gx:altitudeMode>:clampToSeaFloor is currently not supported, using <kml:altitudeMode>:clampToGround."
  1247. );
  1248. return HeightReference.CLAMP_TO_GROUND;
  1249. }
  1250. if (gxAltitudeMode === "relativeToSeaFloor") {
  1251. oneTimeWarning(
  1252. "kml-gx:altitudeMode-relativeToSeaFloor",
  1253. "KML - <gx:altitudeMode>:relativeToSeaFloor is currently not supported, using <kml:altitudeMode>:relativeToGround."
  1254. );
  1255. return HeightReference.RELATIVE_TO_GROUND;
  1256. }
  1257. if (defined(altitudeMode)) {
  1258. oneTimeWarning(
  1259. "kml-altitudeMode-unknown",
  1260. "KML - Unknown <kml:altitudeMode>:" +
  1261. altitudeMode +
  1262. ", using <kml:altitudeMode>:CLAMP_TO_GROUND."
  1263. );
  1264. } else {
  1265. oneTimeWarning(
  1266. "kml-gx:altitudeMode-unknown",
  1267. "KML - Unknown <gx:altitudeMode>:" +
  1268. gxAltitudeMode +
  1269. ", using <kml:altitudeMode>:CLAMP_TO_GROUND."
  1270. );
  1271. }
  1272. // Clamp to ground is the default
  1273. return HeightReference.CLAMP_TO_GROUND;
  1274. }
  1275. function createPositionPropertyFromAltitudeMode(
  1276. property,
  1277. altitudeMode,
  1278. gxAltitudeMode
  1279. ) {
  1280. if (
  1281. gxAltitudeMode === "relativeToSeaFloor" ||
  1282. altitudeMode === "absolute" ||
  1283. altitudeMode === "relativeToGround"
  1284. ) {
  1285. //Just return the ellipsoid referenced property until we support MSL
  1286. return property;
  1287. }
  1288. if (
  1289. (defined(altitudeMode) && altitudeMode !== "clampToGround") || //
  1290. (defined(gxAltitudeMode) && gxAltitudeMode !== "clampToSeaFloor")
  1291. ) {
  1292. oneTimeWarning(
  1293. "kml-altitudeMode-unknown",
  1294. "KML - Unknown altitudeMode: " +
  1295. defaultValue(altitudeMode, gxAltitudeMode)
  1296. );
  1297. }
  1298. // Clamp to ground is the default
  1299. return new ScaledPositionProperty(property);
  1300. }
  1301. function createPositionPropertyArrayFromAltitudeMode(
  1302. properties,
  1303. altitudeMode,
  1304. gxAltitudeMode,
  1305. ellipsoid
  1306. ) {
  1307. if (!defined(properties)) {
  1308. return undefined;
  1309. }
  1310. if (
  1311. gxAltitudeMode === "relativeToSeaFloor" ||
  1312. altitudeMode === "absolute" ||
  1313. altitudeMode === "relativeToGround"
  1314. ) {
  1315. //Just return the ellipsoid referenced property until we support MSL
  1316. return properties;
  1317. }
  1318. if (
  1319. (defined(altitudeMode) && altitudeMode !== "clampToGround") || //
  1320. (defined(gxAltitudeMode) && gxAltitudeMode !== "clampToSeaFloor")
  1321. ) {
  1322. oneTimeWarning(
  1323. "kml-altitudeMode-unknown",
  1324. "KML - Unknown altitudeMode: " +
  1325. defaultValue(altitudeMode, gxAltitudeMode)
  1326. );
  1327. }
  1328. // Clamp to ground is the default
  1329. var propertiesLength = properties.length;
  1330. for (var i = 0; i < propertiesLength; i++) {
  1331. var property = properties[i];
  1332. ellipsoid.scaleToGeodeticSurface(property, property);
  1333. }
  1334. return properties;
  1335. }
  1336. function processPositionGraphics(
  1337. dataSource,
  1338. entity,
  1339. styleEntity,
  1340. heightReference
  1341. ) {
  1342. var label = entity.label;
  1343. if (!defined(label)) {
  1344. label = defined(styleEntity.label)
  1345. ? styleEntity.label.clone()
  1346. : createDefaultLabel();
  1347. entity.label = label;
  1348. }
  1349. label.text = entity.name;
  1350. var billboard = entity.billboard;
  1351. if (!defined(billboard)) {
  1352. billboard = defined(styleEntity.billboard)
  1353. ? styleEntity.billboard.clone()
  1354. : createDefaultBillboard();
  1355. entity.billboard = billboard;
  1356. }
  1357. if (!defined(billboard.image)) {
  1358. billboard.image = dataSource._pinBuilder.fromColor(Color.YELLOW, 64);
  1359. // If there were empty <Icon> tags in the KML, then billboard.image was set to false above
  1360. // However, in this case, the false value would have been converted to a property afterwards
  1361. // Thus, we check if billboard.image is defined with value of false
  1362. } else if (!billboard.image.getValue()) {
  1363. billboard.image = undefined;
  1364. }
  1365. var scale = 1.0;
  1366. if (defined(billboard.scale)) {
  1367. scale = billboard.scale.getValue();
  1368. if (scale !== 0) {
  1369. label.pixelOffset = new Cartesian2(scale * 16 + 1, 0);
  1370. } else {
  1371. //Minor tweaks to better match Google Earth.
  1372. label.pixelOffset = undefined;
  1373. label.horizontalOrigin = undefined;
  1374. }
  1375. }
  1376. if (defined(heightReference) && dataSource._clampToGround) {
  1377. billboard.heightReference = heightReference;
  1378. label.heightReference = heightReference;
  1379. }
  1380. }
  1381. function processPathGraphics(entity, styleEntity) {
  1382. var path = entity.path;
  1383. if (!defined(path)) {
  1384. path = new PathGraphics();
  1385. path.leadTime = 0;
  1386. entity.path = path;
  1387. }
  1388. var polyline = styleEntity.polyline;
  1389. if (defined(polyline)) {
  1390. path.material = polyline.material;
  1391. path.width = polyline.width;
  1392. }
  1393. }
  1394. function processPoint(
  1395. dataSource,
  1396. entityCollection,
  1397. geometryNode,
  1398. entity,
  1399. styleEntity
  1400. ) {
  1401. var coordinatesString = queryStringValue(
  1402. geometryNode,
  1403. "coordinates",
  1404. namespaces.kml
  1405. );
  1406. var altitudeMode = queryStringValue(
  1407. geometryNode,
  1408. "altitudeMode",
  1409. namespaces.kml
  1410. );
  1411. var gxAltitudeMode = queryStringValue(
  1412. geometryNode,
  1413. "altitudeMode",
  1414. namespaces.gx
  1415. );
  1416. var extrude = queryBooleanValue(geometryNode, "extrude", namespaces.kml);
  1417. var ellipsoid = dataSource._ellipsoid;
  1418. var position = readCoordinate(coordinatesString, ellipsoid);
  1419. entity.position = position;
  1420. processPositionGraphics(
  1421. dataSource,
  1422. entity,
  1423. styleEntity,
  1424. heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode)
  1425. );
  1426. if (extrude && isExtrudable(altitudeMode, gxAltitudeMode)) {
  1427. createDropLine(entityCollection, entity, styleEntity);
  1428. }
  1429. return true;
  1430. }
  1431. function processLineStringOrLinearRing(
  1432. dataSource,
  1433. entityCollection,
  1434. geometryNode,
  1435. entity,
  1436. styleEntity
  1437. ) {
  1438. var coordinatesNode = queryFirstNode(
  1439. geometryNode,
  1440. "coordinates",
  1441. namespaces.kml
  1442. );
  1443. var altitudeMode = queryStringValue(
  1444. geometryNode,
  1445. "altitudeMode",
  1446. namespaces.kml
  1447. );
  1448. var gxAltitudeMode = queryStringValue(
  1449. geometryNode,
  1450. "altitudeMode",
  1451. namespaces.gx
  1452. );
  1453. var extrude = queryBooleanValue(geometryNode, "extrude", namespaces.kml);
  1454. var tessellate = queryBooleanValue(
  1455. geometryNode,
  1456. "tessellate",
  1457. namespaces.kml
  1458. );
  1459. var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);
  1460. var zIndex = queryNumericValue(geometryNode, "drawOrder", namespaces.gx);
  1461. var ellipsoid = dataSource._ellipsoid;
  1462. var coordinates = readCoordinates(coordinatesNode, ellipsoid);
  1463. var polyline = styleEntity.polyline;
  1464. if (canExtrude && extrude) {
  1465. var wall = new WallGraphics();
  1466. entity.wall = wall;
  1467. wall.positions = coordinates;
  1468. var polygon = styleEntity.polygon;
  1469. if (defined(polygon)) {
  1470. wall.fill = polygon.fill;
  1471. wall.material = polygon.material;
  1472. }
  1473. //Always outline walls so they show up in 2D.
  1474. wall.outline = true;
  1475. if (defined(polyline)) {
  1476. wall.outlineColor = defined(polyline.material)
  1477. ? polyline.material.color
  1478. : Color.WHITE;
  1479. wall.outlineWidth = polyline.width;
  1480. } else if (defined(polygon)) {
  1481. wall.outlineColor = defined(polygon.material)
  1482. ? polygon.material.color
  1483. : Color.WHITE;
  1484. }
  1485. } else if (dataSource._clampToGround && !canExtrude && tessellate) {
  1486. var polylineGraphics = new PolylineGraphics();
  1487. polylineGraphics.clampToGround = true;
  1488. entity.polyline = polylineGraphics;
  1489. polylineGraphics.positions = coordinates;
  1490. if (defined(polyline)) {
  1491. polylineGraphics.material = defined(polyline.material)
  1492. ? polyline.material.color.getValue(Iso8601.MINIMUM_VALUE)
  1493. : Color.WHITE;
  1494. polylineGraphics.width = defaultValue(polyline.width, 1.0);
  1495. } else {
  1496. polylineGraphics.material = Color.WHITE;
  1497. polylineGraphics.width = 1.0;
  1498. }
  1499. polylineGraphics.zIndex = zIndex;
  1500. } else {
  1501. if (defined(zIndex)) {
  1502. oneTimeWarning(
  1503. "kml-gx:drawOrder",
  1504. "KML - gx:drawOrder is not supported in LineStrings when clampToGround is false"
  1505. );
  1506. }
  1507. if (dataSource._clampToGround && !tessellate) {
  1508. oneTimeWarning(
  1509. "kml-line-tesselate",
  1510. "Ignoring clampToGround for KML lines without the tessellate flag."
  1511. );
  1512. }
  1513. polyline = defined(polyline) ? polyline.clone() : new PolylineGraphics();
  1514. entity.polyline = polyline;
  1515. polyline.positions = createPositionPropertyArrayFromAltitudeMode(
  1516. coordinates,
  1517. altitudeMode,
  1518. gxAltitudeMode,
  1519. ellipsoid
  1520. );
  1521. if (!tessellate || canExtrude) {
  1522. polyline.arcType = ArcType.NONE;
  1523. }
  1524. }
  1525. return true;
  1526. }
  1527. function processPolygon(
  1528. dataSource,
  1529. entityCollection,
  1530. geometryNode,
  1531. entity,
  1532. styleEntity
  1533. ) {
  1534. var outerBoundaryIsNode = queryFirstNode(
  1535. geometryNode,
  1536. "outerBoundaryIs",
  1537. namespaces.kml
  1538. );
  1539. var linearRingNode = queryFirstNode(
  1540. outerBoundaryIsNode,
  1541. "LinearRing",
  1542. namespaces.kml
  1543. );
  1544. var coordinatesNode = queryFirstNode(
  1545. linearRingNode,
  1546. "coordinates",
  1547. namespaces.kml
  1548. );
  1549. var ellipsoid = dataSource._ellipsoid;
  1550. var coordinates = readCoordinates(coordinatesNode, ellipsoid);
  1551. var extrude = queryBooleanValue(geometryNode, "extrude", namespaces.kml);
  1552. var altitudeMode = queryStringValue(
  1553. geometryNode,
  1554. "altitudeMode",
  1555. namespaces.kml
  1556. );
  1557. var gxAltitudeMode = queryStringValue(
  1558. geometryNode,
  1559. "altitudeMode",
  1560. namespaces.gx
  1561. );
  1562. var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);
  1563. var polygon = defined(styleEntity.polygon)
  1564. ? styleEntity.polygon.clone()
  1565. : createDefaultPolygon();
  1566. var polyline = styleEntity.polyline;
  1567. if (defined(polyline)) {
  1568. polygon.outlineColor = defined(polyline.material)
  1569. ? polyline.material.color
  1570. : Color.WHITE;
  1571. polygon.outlineWidth = polyline.width;
  1572. }
  1573. entity.polygon = polygon;
  1574. if (canExtrude) {
  1575. polygon.perPositionHeight = true;
  1576. polygon.extrudedHeight = extrude ? 0 : undefined;
  1577. } else if (!dataSource._clampToGround) {
  1578. polygon.height = 0;
  1579. }
  1580. if (defined(coordinates)) {
  1581. var hierarchy = new PolygonHierarchy(coordinates);
  1582. var innerBoundaryIsNodes = queryChildNodes(
  1583. geometryNode,
  1584. "innerBoundaryIs",
  1585. namespaces.kml
  1586. );
  1587. for (var j = 0; j < innerBoundaryIsNodes.length; j++) {
  1588. linearRingNode = queryChildNodes(
  1589. innerBoundaryIsNodes[j],
  1590. "LinearRing",
  1591. namespaces.kml
  1592. );
  1593. for (var k = 0; k < linearRingNode.length; k++) {
  1594. coordinatesNode = queryFirstNode(
  1595. linearRingNode[k],
  1596. "coordinates",
  1597. namespaces.kml
  1598. );
  1599. coordinates = readCoordinates(coordinatesNode, ellipsoid);
  1600. if (defined(coordinates)) {
  1601. hierarchy.holes.push(new PolygonHierarchy(coordinates));
  1602. }
  1603. }
  1604. }
  1605. polygon.hierarchy = hierarchy;
  1606. }
  1607. return true;
  1608. }
  1609. function processTrack(
  1610. dataSource,
  1611. entityCollection,
  1612. geometryNode,
  1613. entity,
  1614. styleEntity
  1615. ) {
  1616. var altitudeMode = queryStringValue(
  1617. geometryNode,
  1618. "altitudeMode",
  1619. namespaces.kml
  1620. );
  1621. var gxAltitudeMode = queryStringValue(
  1622. geometryNode,
  1623. "altitudeMode",
  1624. namespaces.gx
  1625. );
  1626. var coordNodes = queryChildNodes(geometryNode, "coord", namespaces.gx);
  1627. var angleNodes = queryChildNodes(geometryNode, "angles", namespaces.gx);
  1628. var timeNodes = queryChildNodes(geometryNode, "when", namespaces.kml);
  1629. var extrude = queryBooleanValue(geometryNode, "extrude", namespaces.kml);
  1630. var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);
  1631. var ellipsoid = dataSource._ellipsoid;
  1632. if (angleNodes.length > 0) {
  1633. oneTimeWarning(
  1634. "kml-gx:angles",
  1635. "KML - gx:angles are not supported in gx:Tracks"
  1636. );
  1637. }
  1638. var length = Math.min(coordNodes.length, timeNodes.length);
  1639. var coordinates = [];
  1640. var times = [];
  1641. for (var i = 0; i < length; i++) {
  1642. var position = readCoordinate(coordNodes[i].textContent, ellipsoid);
  1643. coordinates.push(position);
  1644. times.push(JulianDate.fromIso8601(timeNodes[i].textContent));
  1645. }
  1646. var property = new SampledPositionProperty();
  1647. property.addSamples(times, coordinates);
  1648. entity.position = property;
  1649. processPositionGraphics(
  1650. dataSource,
  1651. entity,
  1652. styleEntity,
  1653. heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode)
  1654. );
  1655. processPathGraphics(entity, styleEntity);
  1656. entity.availability = new TimeIntervalCollection();
  1657. if (timeNodes.length > 0) {
  1658. entity.availability.addInterval(
  1659. new TimeInterval({
  1660. start: times[0],
  1661. stop: times[times.length - 1],
  1662. })
  1663. );
  1664. }
  1665. if (canExtrude && extrude) {
  1666. createDropLine(entityCollection, entity, styleEntity);
  1667. }
  1668. return true;
  1669. }
  1670. function addToMultiTrack(
  1671. times,
  1672. positions,
  1673. composite,
  1674. availability,
  1675. dropShowProperty,
  1676. extrude,
  1677. altitudeMode,
  1678. gxAltitudeMode,
  1679. includeEndPoints
  1680. ) {
  1681. var start = times[0];
  1682. var stop = times[times.length - 1];
  1683. var data = new SampledPositionProperty();
  1684. data.addSamples(times, positions);
  1685. composite.intervals.addInterval(
  1686. new TimeInterval({
  1687. start: start,
  1688. stop: stop,
  1689. isStartIncluded: includeEndPoints,
  1690. isStopIncluded: includeEndPoints,
  1691. data: createPositionPropertyFromAltitudeMode(
  1692. data,
  1693. altitudeMode,
  1694. gxAltitudeMode
  1695. ),
  1696. })
  1697. );
  1698. availability.addInterval(
  1699. new TimeInterval({
  1700. start: start,
  1701. stop: stop,
  1702. isStartIncluded: includeEndPoints,
  1703. isStopIncluded: includeEndPoints,
  1704. })
  1705. );
  1706. dropShowProperty.intervals.addInterval(
  1707. new TimeInterval({
  1708. start: start,
  1709. stop: stop,
  1710. isStartIncluded: includeEndPoints,
  1711. isStopIncluded: includeEndPoints,
  1712. data: extrude,
  1713. })
  1714. );
  1715. }
  1716. function processMultiTrack(
  1717. dataSource,
  1718. entityCollection,
  1719. geometryNode,
  1720. entity,
  1721. styleEntity
  1722. ) {
  1723. // Multitrack options do not work in GE as detailed in the spec,
  1724. // rather than altitudeMode being at the MultiTrack level,
  1725. // GE just defers all settings to the underlying track.
  1726. var interpolate = queryBooleanValue(
  1727. geometryNode,
  1728. "interpolate",
  1729. namespaces.gx
  1730. );
  1731. var trackNodes = queryChildNodes(geometryNode, "Track", namespaces.gx);
  1732. var times;
  1733. var lastStop;
  1734. var lastStopPosition;
  1735. var needDropLine = false;
  1736. var dropShowProperty = new TimeIntervalCollectionProperty();
  1737. var availability = new TimeIntervalCollection();
  1738. var composite = new CompositePositionProperty();
  1739. var ellipsoid = dataSource._ellipsoid;
  1740. for (var i = 0, len = trackNodes.length; i < len; i++) {
  1741. var trackNode = trackNodes[i];
  1742. var timeNodes = queryChildNodes(trackNode, "when", namespaces.kml);
  1743. var coordNodes = queryChildNodes(trackNode, "coord", namespaces.gx);
  1744. var altitudeMode = queryStringValue(
  1745. trackNode,
  1746. "altitudeMode",
  1747. namespaces.kml
  1748. );
  1749. var gxAltitudeMode = queryStringValue(
  1750. trackNode,
  1751. "altitudeMode",
  1752. namespaces.gx
  1753. );
  1754. var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);
  1755. var extrude = queryBooleanValue(trackNode, "extrude", namespaces.kml);
  1756. var length = Math.min(coordNodes.length, timeNodes.length);
  1757. var positions = [];
  1758. times = [];
  1759. for (var x = 0; x < length; x++) {
  1760. var position = readCoordinate(coordNodes[x].textContent, ellipsoid);
  1761. positions.push(position);
  1762. times.push(JulianDate.fromIso8601(timeNodes[x].textContent));
  1763. }
  1764. if (interpolate) {
  1765. //If we are interpolating, then we need to fill in the end of
  1766. //the last track and the beginning of this one with a sampled
  1767. //property. From testing in Google Earth, this property
  1768. //is never extruded and always absolute.
  1769. if (defined(lastStop)) {
  1770. addToMultiTrack(
  1771. [lastStop, times[0]],
  1772. [lastStopPosition, positions[0]],
  1773. composite,
  1774. availability,
  1775. dropShowProperty,
  1776. false,
  1777. "absolute",
  1778. undefined,
  1779. false
  1780. );
  1781. }
  1782. lastStop = times[length - 1];
  1783. lastStopPosition = positions[positions.length - 1];
  1784. }
  1785. addToMultiTrack(
  1786. times,
  1787. positions,
  1788. composite,
  1789. availability,
  1790. dropShowProperty,
  1791. canExtrude && extrude,
  1792. altitudeMode,
  1793. gxAltitudeMode,
  1794. true
  1795. );
  1796. needDropLine = needDropLine || (canExtrude && extrude);
  1797. }
  1798. entity.availability = availability;
  1799. entity.position = composite;
  1800. processPositionGraphics(dataSource, entity, styleEntity);
  1801. processPathGraphics(entity, styleEntity);
  1802. if (needDropLine) {
  1803. createDropLine(entityCollection, entity, styleEntity);
  1804. entity.polyline.show = dropShowProperty;
  1805. }
  1806. return true;
  1807. }
  1808. var geometryTypes = {
  1809. Point: processPoint,
  1810. LineString: processLineStringOrLinearRing,
  1811. LinearRing: processLineStringOrLinearRing,
  1812. Polygon: processPolygon,
  1813. Track: processTrack,
  1814. MultiTrack: processMultiTrack,
  1815. MultiGeometry: processMultiGeometry,
  1816. Model: processUnsupportedGeometry,
  1817. };
  1818. function processMultiGeometry(
  1819. dataSource,
  1820. entityCollection,
  1821. geometryNode,
  1822. entity,
  1823. styleEntity,
  1824. context
  1825. ) {
  1826. var childNodes = geometryNode.childNodes;
  1827. var hasGeometry = false;
  1828. for (var i = 0, len = childNodes.length; i < len; i++) {
  1829. var childNode = childNodes.item(i);
  1830. var geometryProcessor = geometryTypes[childNode.localName];
  1831. if (defined(geometryProcessor)) {
  1832. var childEntity = createEntity(childNode, entityCollection, context);
  1833. childEntity.parent = entity;
  1834. childEntity.name = entity.name;
  1835. childEntity.availability = entity.availability;
  1836. childEntity.description = entity.description;
  1837. childEntity.kml = entity.kml;
  1838. if (
  1839. geometryProcessor(
  1840. dataSource,
  1841. entityCollection,
  1842. childNode,
  1843. childEntity,
  1844. styleEntity
  1845. )
  1846. ) {
  1847. hasGeometry = true;
  1848. }
  1849. }
  1850. }
  1851. return hasGeometry;
  1852. }
  1853. function processUnsupportedGeometry(
  1854. dataSource,
  1855. entityCollection,
  1856. geometryNode,
  1857. entity,
  1858. styleEntity
  1859. ) {
  1860. oneTimeWarning(
  1861. "kml-unsupportedGeometry",
  1862. "KML - Unsupported geometry: " + geometryNode.localName
  1863. );
  1864. return false;
  1865. }
  1866. function processExtendedData(node, entity) {
  1867. var extendedDataNode = queryFirstNode(node, "ExtendedData", namespaces.kml);
  1868. if (!defined(extendedDataNode)) {
  1869. return undefined;
  1870. }
  1871. if (defined(queryFirstNode(extendedDataNode, "SchemaData", namespaces.kml))) {
  1872. oneTimeWarning("kml-schemaData", "KML - SchemaData is unsupported");
  1873. }
  1874. if (defined(queryStringAttribute(extendedDataNode, "xmlns:prefix"))) {
  1875. oneTimeWarning(
  1876. "kml-extendedData",
  1877. "KML - ExtendedData with xmlns:prefix is unsupported"
  1878. );
  1879. }
  1880. var result = {};
  1881. var dataNodes = queryChildNodes(extendedDataNode, "Data", namespaces.kml);
  1882. if (defined(dataNodes)) {
  1883. var length = dataNodes.length;
  1884. for (var i = 0; i < length; i++) {
  1885. var dataNode = dataNodes[i];
  1886. var name = queryStringAttribute(dataNode, "name");
  1887. if (defined(name)) {
  1888. result[name] = {
  1889. displayName: queryStringValue(
  1890. dataNode,
  1891. "displayName",
  1892. namespaces.kml
  1893. ),
  1894. value: queryStringValue(dataNode, "value", namespaces.kml),
  1895. };
  1896. }
  1897. }
  1898. }
  1899. entity.kml.extendedData = result;
  1900. }
  1901. var scratchDiv;
  1902. if (typeof document !== "undefined") {
  1903. scratchDiv = document.createElement("div");
  1904. }
  1905. function processDescription(
  1906. node,
  1907. entity,
  1908. styleEntity,
  1909. uriResolver,
  1910. sourceResource
  1911. ) {
  1912. var i;
  1913. var key;
  1914. var keys;
  1915. var kmlData = entity.kml;
  1916. var extendedData = kmlData.extendedData;
  1917. var description = queryStringValue(node, "description", namespaces.kml);
  1918. var balloonStyle = defaultValue(
  1919. entity.balloonStyle,
  1920. styleEntity.balloonStyle
  1921. );
  1922. var background = Color.WHITE;
  1923. var foreground = Color.BLACK;
  1924. var text = description;
  1925. if (defined(balloonStyle)) {
  1926. background = defaultValue(balloonStyle.bgColor, Color.WHITE);
  1927. foreground = defaultValue(balloonStyle.textColor, Color.BLACK);
  1928. text = defaultValue(balloonStyle.text, description);
  1929. }
  1930. var value;
  1931. if (defined(text)) {
  1932. text = text.replace("$[name]", defaultValue(entity.name, ""));
  1933. text = text.replace("$[description]", defaultValue(description, ""));
  1934. text = text.replace("$[address]", defaultValue(kmlData.address, ""));
  1935. text = text.replace("$[Snippet]", defaultValue(kmlData.snippet, ""));
  1936. text = text.replace("$[id]", entity.id);
  1937. //While not explicitly defined by the OGC spec, in Google Earth
  1938. //The appearance of geDirections adds the directions to/from links
  1939. //We simply replace this string with nothing.
  1940. text = text.replace("$[geDirections]", "");
  1941. if (defined(extendedData)) {
  1942. var matches = text.match(/\$\[.+?\]/g);
  1943. if (matches !== null) {
  1944. for (i = 0; i < matches.length; i++) {
  1945. var token = matches[i];
  1946. var propertyName = token.substr(2, token.length - 3);
  1947. var isDisplayName = /\/displayName$/.test(propertyName);
  1948. propertyName = propertyName.replace(/\/displayName$/, "");
  1949. value = extendedData[propertyName];
  1950. if (defined(value)) {
  1951. value = isDisplayName ? value.displayName : value.value;
  1952. }
  1953. if (defined(value)) {
  1954. text = text.replace(token, defaultValue(value, ""));
  1955. }
  1956. }
  1957. }
  1958. }
  1959. } else if (defined(extendedData)) {
  1960. //If no description exists, build a table out of the extended data
  1961. keys = Object.keys(extendedData);
  1962. if (keys.length > 0) {
  1963. text =
  1964. '<table class="cesium-infoBox-defaultTable cesium-infoBox-defaultTable-lighter"><tbody>';
  1965. for (i = 0; i < keys.length; i++) {
  1966. key = keys[i];
  1967. value = extendedData[key];
  1968. text +=
  1969. "<tr><th>" +
  1970. defaultValue(value.displayName, key) +
  1971. "</th><td>" +
  1972. defaultValue(value.value, "") +
  1973. "</td></tr>";
  1974. }
  1975. text += "</tbody></table>";
  1976. }
  1977. }
  1978. if (!defined(text)) {
  1979. //No description
  1980. return;
  1981. }
  1982. //Turns non-explicit links into clickable links.
  1983. text = autolinker.link(text);
  1984. //Use a temporary div to manipulate the links
  1985. //so that they open in a new window.
  1986. scratchDiv.innerHTML = text;
  1987. var links = scratchDiv.querySelectorAll("a");
  1988. for (i = 0; i < links.length; i++) {
  1989. links[i].setAttribute("target", "_blank");
  1990. }
  1991. //Rewrite any KMZ embedded urls
  1992. if (defined(uriResolver) && uriResolver.keys.length > 1) {
  1993. embedDataUris(scratchDiv, "a", "href", uriResolver);
  1994. embedDataUris(scratchDiv, "img", "src", uriResolver);
  1995. }
  1996. //Make relative urls absolute using the sourceResource
  1997. applyBasePath(scratchDiv, "a", "href", sourceResource);
  1998. applyBasePath(scratchDiv, "img", "src", sourceResource);
  1999. var tmp = '<div class="cesium-infoBox-description-lighter" style="';
  2000. tmp += "overflow:auto;";
  2001. tmp += "word-wrap:break-word;";
  2002. tmp += "background-color:" + background.toCssColorString() + ";";
  2003. tmp += "color:" + foreground.toCssColorString() + ";";
  2004. tmp += '">';
  2005. tmp += scratchDiv.innerHTML + "</div>";
  2006. scratchDiv.innerHTML = "";
  2007. //Set the final HTML as the description.
  2008. entity.description = tmp;
  2009. }
  2010. function processFeature(dataSource, featureNode, processingData) {
  2011. var entityCollection = processingData.entityCollection;
  2012. var parent = processingData.parentEntity;
  2013. var sourceResource = processingData.sourceResource;
  2014. var uriResolver = processingData.uriResolver;
  2015. var entity = createEntity(
  2016. featureNode,
  2017. entityCollection,
  2018. processingData.context
  2019. );
  2020. var kmlData = entity.kml;
  2021. var styleEntity = computeFinalStyle(
  2022. dataSource,
  2023. featureNode,
  2024. processingData.styleCollection,
  2025. sourceResource,
  2026. uriResolver
  2027. );
  2028. var name = queryStringValue(featureNode, "name", namespaces.kml);
  2029. entity.name = name;
  2030. entity.parent = parent;
  2031. var availability = processTimeSpan(featureNode);
  2032. if (!defined(availability)) {
  2033. availability = processTimeStamp(featureNode);
  2034. }
  2035. entity.availability = availability;
  2036. mergeAvailabilityWithParent(entity);
  2037. // Per KML spec "A Feature is visible only if it and all its ancestors are visible."
  2038. function ancestryIsVisible(parentEntity) {
  2039. if (!parentEntity) {
  2040. return true;
  2041. }
  2042. return parentEntity.show && ancestryIsVisible(parentEntity.parent);
  2043. }
  2044. var visibility = queryBooleanValue(featureNode, "visibility", namespaces.kml);
  2045. entity.show = ancestryIsVisible(parent) && defaultValue(visibility, true);
  2046. //var open = queryBooleanValue(featureNode, 'open', namespaces.kml);
  2047. var authorNode = queryFirstNode(featureNode, "author", namespaces.atom);
  2048. var author = kmlData.author;
  2049. author.name = queryStringValue(authorNode, "name", namespaces.atom);
  2050. author.uri = queryStringValue(authorNode, "uri", namespaces.atom);
  2051. author.email = queryStringValue(authorNode, "email", namespaces.atom);
  2052. var linkNode = queryFirstNode(featureNode, "link", namespaces.atom);
  2053. var link = kmlData.link;
  2054. link.href = queryStringAttribute(linkNode, "href");
  2055. link.hreflang = queryStringAttribute(linkNode, "hreflang");
  2056. link.rel = queryStringAttribute(linkNode, "rel");
  2057. link.type = queryStringAttribute(linkNode, "type");
  2058. link.title = queryStringAttribute(linkNode, "title");
  2059. link.length = queryStringAttribute(linkNode, "length");
  2060. kmlData.address = queryStringValue(featureNode, "address", namespaces.kml);
  2061. kmlData.phoneNumber = queryStringValue(
  2062. featureNode,
  2063. "phoneNumber",
  2064. namespaces.kml
  2065. );
  2066. kmlData.snippet = queryStringValue(featureNode, "Snippet", namespaces.kml);
  2067. processExtendedData(featureNode, entity);
  2068. processDescription(
  2069. featureNode,
  2070. entity,
  2071. styleEntity,
  2072. uriResolver,
  2073. sourceResource
  2074. );
  2075. var ellipsoid = dataSource._ellipsoid;
  2076. processLookAt(featureNode, entity, ellipsoid);
  2077. processCamera(featureNode, entity, ellipsoid);
  2078. if (defined(queryFirstNode(featureNode, "Region", namespaces.kml))) {
  2079. oneTimeWarning("kml-region", "KML - Placemark Regions are unsupported");
  2080. }
  2081. return {
  2082. entity: entity,
  2083. styleEntity: styleEntity,
  2084. };
  2085. }
  2086. function processDocument(dataSource, node, processingData, deferredLoading) {
  2087. deferredLoading.addNodes(node.childNodes, processingData);
  2088. deferredLoading.process();
  2089. }
  2090. function processFolder(dataSource, node, processingData, deferredLoading) {
  2091. var r = processFeature(dataSource, node, processingData);
  2092. var newProcessingData = clone(processingData);
  2093. newProcessingData.parentEntity = r.entity;
  2094. processDocument(dataSource, node, newProcessingData, deferredLoading);
  2095. }
  2096. function processPlacemark(
  2097. dataSource,
  2098. placemark,
  2099. processingData,
  2100. deferredLoading
  2101. ) {
  2102. var r = processFeature(dataSource, placemark, processingData);
  2103. var entity = r.entity;
  2104. var styleEntity = r.styleEntity;
  2105. var hasGeometry = false;
  2106. var childNodes = placemark.childNodes;
  2107. for (var i = 0, len = childNodes.length; i < len && !hasGeometry; i++) {
  2108. var childNode = childNodes.item(i);
  2109. var geometryProcessor = geometryTypes[childNode.localName];
  2110. if (defined(geometryProcessor)) {
  2111. // pass the placemark entity id as a context for case of defining multiple child entities together to handle case
  2112. // where some malformed kmls reuse the same id across placemarks, which works in GE, but is not technically to spec.
  2113. geometryProcessor(
  2114. dataSource,
  2115. processingData.entityCollection,
  2116. childNode,
  2117. entity,
  2118. styleEntity,
  2119. entity.id
  2120. );
  2121. hasGeometry = true;
  2122. }
  2123. }
  2124. if (!hasGeometry) {
  2125. entity.merge(styleEntity);
  2126. processPositionGraphics(dataSource, entity, styleEntity);
  2127. }
  2128. }
  2129. var playlistNodeProcessors = {
  2130. FlyTo: processTourFlyTo,
  2131. Wait: processTourWait,
  2132. SoundCue: processTourUnsupportedNode,
  2133. AnimatedUpdate: processTourUnsupportedNode,
  2134. TourControl: processTourUnsupportedNode,
  2135. };
  2136. function processTour(dataSource, node, processingData, deferredLoading) {
  2137. var name = queryStringValue(node, "name", namespaces.kml);
  2138. var id = queryStringAttribute(node, "id");
  2139. var tour = new KmlTour(name, id);
  2140. var playlistNode = queryFirstNode(node, "Playlist", namespaces.gx);
  2141. if (playlistNode) {
  2142. var ellipsoid = dataSource._ellipsoid;
  2143. var childNodes = playlistNode.childNodes;
  2144. for (var i = 0; i < childNodes.length; i++) {
  2145. var entryNode = childNodes[i];
  2146. if (entryNode.localName) {
  2147. var playlistNodeProcessor = playlistNodeProcessors[entryNode.localName];
  2148. if (playlistNodeProcessor) {
  2149. playlistNodeProcessor(tour, entryNode, ellipsoid);
  2150. } else {
  2151. console.log(
  2152. "Unknown KML Tour playlist entry type " + entryNode.localName
  2153. );
  2154. }
  2155. }
  2156. }
  2157. }
  2158. if (!defined(dataSource.kmlTours)) {
  2159. dataSource.kmlTours = [];
  2160. }
  2161. dataSource.kmlTours.push(tour);
  2162. }
  2163. function processTourUnsupportedNode(tour, entryNode) {
  2164. oneTimeWarning("KML Tour unsupported node " + entryNode.localName);
  2165. }
  2166. function processTourWait(tour, entryNode) {
  2167. var duration = queryNumericValue(entryNode, "duration", namespaces.gx);
  2168. tour.addPlaylistEntry(new KmlTourWait(duration));
  2169. }
  2170. function processTourFlyTo(tour, entryNode, ellipsoid) {
  2171. var duration = queryNumericValue(entryNode, "duration", namespaces.gx);
  2172. var flyToMode = queryStringValue(entryNode, "flyToMode", namespaces.gx);
  2173. var t = { kml: {} };
  2174. processLookAt(entryNode, t, ellipsoid);
  2175. processCamera(entryNode, t, ellipsoid);
  2176. var view = t.kml.lookAt || t.kml.camera;
  2177. var flyto = new KmlTourFlyTo(duration, flyToMode, view);
  2178. tour.addPlaylistEntry(flyto);
  2179. }
  2180. function processCamera(featureNode, entity, ellipsoid) {
  2181. var camera = queryFirstNode(featureNode, "Camera", namespaces.kml);
  2182. if (defined(camera)) {
  2183. var lon = defaultValue(
  2184. queryNumericValue(camera, "longitude", namespaces.kml),
  2185. 0.0
  2186. );
  2187. var lat = defaultValue(
  2188. queryNumericValue(camera, "latitude", namespaces.kml),
  2189. 0.0
  2190. );
  2191. var altitude = defaultValue(
  2192. queryNumericValue(camera, "altitude", namespaces.kml),
  2193. 0.0
  2194. );
  2195. var heading = defaultValue(
  2196. queryNumericValue(camera, "heading", namespaces.kml),
  2197. 0.0
  2198. );
  2199. var tilt = defaultValue(
  2200. queryNumericValue(camera, "tilt", namespaces.kml),
  2201. 0.0
  2202. );
  2203. var roll = defaultValue(
  2204. queryNumericValue(camera, "roll", namespaces.kml),
  2205. 0.0
  2206. );
  2207. var position = Cartesian3.fromDegrees(lon, lat, altitude, ellipsoid);
  2208. var hpr = HeadingPitchRoll.fromDegrees(heading, tilt - 90.0, roll);
  2209. entity.kml.camera = new KmlCamera(position, hpr);
  2210. }
  2211. }
  2212. function processLookAt(featureNode, entity, ellipsoid) {
  2213. var lookAt = queryFirstNode(featureNode, "LookAt", namespaces.kml);
  2214. if (defined(lookAt)) {
  2215. var lon = defaultValue(
  2216. queryNumericValue(lookAt, "longitude", namespaces.kml),
  2217. 0.0
  2218. );
  2219. var lat = defaultValue(
  2220. queryNumericValue(lookAt, "latitude", namespaces.kml),
  2221. 0.0
  2222. );
  2223. var altitude = defaultValue(
  2224. queryNumericValue(lookAt, "altitude", namespaces.kml),
  2225. 0.0
  2226. );
  2227. var heading = queryNumericValue(lookAt, "heading", namespaces.kml);
  2228. var tilt = queryNumericValue(lookAt, "tilt", namespaces.kml);
  2229. var range = defaultValue(
  2230. queryNumericValue(lookAt, "range", namespaces.kml),
  2231. 0.0
  2232. );
  2233. tilt = CesiumMath.toRadians(defaultValue(tilt, 0.0));
  2234. heading = CesiumMath.toRadians(defaultValue(heading, 0.0));
  2235. var hpr = new HeadingPitchRange(
  2236. heading,
  2237. tilt - CesiumMath.PI_OVER_TWO,
  2238. range
  2239. );
  2240. var viewPoint = Cartesian3.fromDegrees(lon, lat, altitude, ellipsoid);
  2241. entity.kml.lookAt = new KmlLookAt(viewPoint, hpr);
  2242. }
  2243. }
  2244. function processGroundOverlay(
  2245. dataSource,
  2246. groundOverlay,
  2247. processingData,
  2248. deferredLoading
  2249. ) {
  2250. var r = processFeature(dataSource, groundOverlay, processingData);
  2251. var entity = r.entity;
  2252. var geometry;
  2253. var isLatLonQuad = false;
  2254. var ellipsoid = dataSource._ellipsoid;
  2255. var positions = readCoordinates(
  2256. queryFirstNode(groundOverlay, "LatLonQuad", namespaces.gx),
  2257. ellipsoid
  2258. );
  2259. var zIndex = queryNumericValue(groundOverlay, "drawOrder", namespaces.kml);
  2260. if (defined(positions)) {
  2261. geometry = createDefaultPolygon();
  2262. geometry.hierarchy = new PolygonHierarchy(positions);
  2263. geometry.zIndex = zIndex;
  2264. entity.polygon = geometry;
  2265. isLatLonQuad = true;
  2266. } else {
  2267. geometry = new RectangleGraphics();
  2268. geometry.zIndex = zIndex;
  2269. entity.rectangle = geometry;
  2270. var latLonBox = queryFirstNode(groundOverlay, "LatLonBox", namespaces.kml);
  2271. if (defined(latLonBox)) {
  2272. var west = queryNumericValue(latLonBox, "west", namespaces.kml);
  2273. var south = queryNumericValue(latLonBox, "south", namespaces.kml);
  2274. var east = queryNumericValue(latLonBox, "east", namespaces.kml);
  2275. var north = queryNumericValue(latLonBox, "north", namespaces.kml);
  2276. if (defined(west)) {
  2277. west = CesiumMath.negativePiToPi(CesiumMath.toRadians(west));
  2278. }
  2279. if (defined(south)) {
  2280. south = CesiumMath.clampToLatitudeRange(CesiumMath.toRadians(south));
  2281. }
  2282. if (defined(east)) {
  2283. east = CesiumMath.negativePiToPi(CesiumMath.toRadians(east));
  2284. }
  2285. if (defined(north)) {
  2286. north = CesiumMath.clampToLatitudeRange(CesiumMath.toRadians(north));
  2287. }
  2288. geometry.coordinates = new Rectangle(west, south, east, north);
  2289. var rotation = queryNumericValue(latLonBox, "rotation", namespaces.kml);
  2290. if (defined(rotation)) {
  2291. var rotationRadians = CesiumMath.toRadians(rotation);
  2292. geometry.rotation = rotationRadians;
  2293. geometry.stRotation = rotationRadians;
  2294. }
  2295. }
  2296. }
  2297. var iconNode = queryFirstNode(groundOverlay, "Icon", namespaces.kml);
  2298. var href = getIconHref(
  2299. iconNode,
  2300. dataSource,
  2301. processingData.sourceResource,
  2302. processingData.uriResolver,
  2303. true
  2304. );
  2305. if (defined(href)) {
  2306. if (isLatLonQuad) {
  2307. oneTimeWarning(
  2308. "kml-gx:LatLonQuad",
  2309. "KML - gx:LatLonQuad Icon does not support texture projection."
  2310. );
  2311. }
  2312. var x = queryNumericValue(iconNode, "x", namespaces.gx);
  2313. var y = queryNumericValue(iconNode, "y", namespaces.gx);
  2314. var w = queryNumericValue(iconNode, "w", namespaces.gx);
  2315. var h = queryNumericValue(iconNode, "h", namespaces.gx);
  2316. if (defined(x) || defined(y) || defined(w) || defined(h)) {
  2317. oneTimeWarning(
  2318. "kml-groundOverlay-xywh",
  2319. "KML - gx:x, gx:y, gx:w, gx:h aren't supported for GroundOverlays"
  2320. );
  2321. }
  2322. geometry.material = href;
  2323. geometry.material.color = queryColorValue(
  2324. groundOverlay,
  2325. "color",
  2326. namespaces.kml
  2327. );
  2328. geometry.material.transparent = true;
  2329. } else {
  2330. geometry.material = queryColorValue(groundOverlay, "color", namespaces.kml);
  2331. }
  2332. var altitudeMode = queryStringValue(
  2333. groundOverlay,
  2334. "altitudeMode",
  2335. namespaces.kml
  2336. );
  2337. if (defined(altitudeMode)) {
  2338. if (altitudeMode === "absolute") {
  2339. //Use height above ellipsoid until we support MSL.
  2340. geometry.height = queryNumericValue(
  2341. groundOverlay,
  2342. "altitude",
  2343. namespaces.kml
  2344. );
  2345. geometry.zIndex = undefined;
  2346. } else if (altitudeMode !== "clampToGround") {
  2347. oneTimeWarning(
  2348. "kml-altitudeMode-unknown",
  2349. "KML - Unknown altitudeMode: " + altitudeMode
  2350. );
  2351. }
  2352. // else just use the default of 0 until we support 'clampToGround'
  2353. } else {
  2354. altitudeMode = queryStringValue(
  2355. groundOverlay,
  2356. "altitudeMode",
  2357. namespaces.gx
  2358. );
  2359. if (altitudeMode === "relativeToSeaFloor") {
  2360. oneTimeWarning(
  2361. "kml-altitudeMode-relativeToSeaFloor",
  2362. "KML - altitudeMode relativeToSeaFloor is currently not supported, treating as absolute."
  2363. );
  2364. geometry.height = queryNumericValue(
  2365. groundOverlay,
  2366. "altitude",
  2367. namespaces.kml
  2368. );
  2369. geometry.zIndex = undefined;
  2370. } else if (altitudeMode === "clampToSeaFloor") {
  2371. oneTimeWarning(
  2372. "kml-altitudeMode-clampToSeaFloor",
  2373. "KML - altitudeMode clampToSeaFloor is currently not supported, treating as clampToGround."
  2374. );
  2375. } else if (defined(altitudeMode)) {
  2376. oneTimeWarning(
  2377. "kml-altitudeMode-unknown",
  2378. "KML - Unknown altitudeMode: " + altitudeMode
  2379. );
  2380. }
  2381. }
  2382. }
  2383. function processUnsupportedFeature(
  2384. dataSource,
  2385. node,
  2386. processingData,
  2387. deferredLoading
  2388. ) {
  2389. dataSource._unsupportedNode.raiseEvent(
  2390. dataSource,
  2391. processingData.parentEntity,
  2392. node,
  2393. processingData.entityCollection,
  2394. processingData.styleCollection,
  2395. processingData.sourceResource,
  2396. processingData.uriResolver
  2397. );
  2398. oneTimeWarning(
  2399. "kml-unsupportedFeature-" + node.nodeName,
  2400. "KML - Unsupported feature: " + node.nodeName
  2401. );
  2402. }
  2403. var RefreshMode = {
  2404. INTERVAL: 0,
  2405. EXPIRE: 1,
  2406. STOP: 2,
  2407. };
  2408. function cleanupString(s) {
  2409. if (!defined(s) || s.length === 0) {
  2410. return "";
  2411. }
  2412. var sFirst = s[0];
  2413. if (sFirst === "&" || sFirst === "?") {
  2414. s = s.substring(1);
  2415. }
  2416. return s;
  2417. }
  2418. var zeroRectangle = new Rectangle();
  2419. var scratchCartographic = new Cartographic();
  2420. var scratchCartesian2 = new Cartesian2();
  2421. var scratchCartesian3 = new Cartesian3();
  2422. function processNetworkLinkQueryString(
  2423. resource,
  2424. camera,
  2425. canvas,
  2426. viewBoundScale,
  2427. bbox,
  2428. ellipsoid
  2429. ) {
  2430. function fixLatitude(value) {
  2431. if (value < -CesiumMath.PI_OVER_TWO) {
  2432. return -CesiumMath.PI_OVER_TWO;
  2433. } else if (value > CesiumMath.PI_OVER_TWO) {
  2434. return CesiumMath.PI_OVER_TWO;
  2435. }
  2436. return value;
  2437. }
  2438. function fixLongitude(value) {
  2439. if (value > CesiumMath.PI) {
  2440. return value - CesiumMath.TWO_PI;
  2441. } else if (value < -CesiumMath.PI) {
  2442. return value + CesiumMath.TWO_PI;
  2443. }
  2444. return value;
  2445. }
  2446. var queryString = objectToQuery(resource.queryParameters);
  2447. // objectToQuery escapes [ and ], so fix that
  2448. queryString = queryString.replace(/%5B/g, "[").replace(/%5D/g, "]");
  2449. if (defined(camera) && camera._mode !== SceneMode.MORPHING) {
  2450. var centerCartesian;
  2451. var centerCartographic;
  2452. bbox = defaultValue(bbox, zeroRectangle);
  2453. if (defined(canvas)) {
  2454. scratchCartesian2.x = canvas.clientWidth * 0.5;
  2455. scratchCartesian2.y = canvas.clientHeight * 0.5;
  2456. centerCartesian = camera.pickEllipsoid(
  2457. scratchCartesian2,
  2458. ellipsoid,
  2459. scratchCartesian3
  2460. );
  2461. }
  2462. if (defined(centerCartesian)) {
  2463. centerCartographic = ellipsoid.cartesianToCartographic(
  2464. centerCartesian,
  2465. scratchCartographic
  2466. );
  2467. } else {
  2468. centerCartographic = Rectangle.center(bbox, scratchCartographic);
  2469. centerCartesian = ellipsoid.cartographicToCartesian(centerCartographic);
  2470. }
  2471. if (
  2472. defined(viewBoundScale) &&
  2473. !CesiumMath.equalsEpsilon(viewBoundScale, 1.0, CesiumMath.EPSILON9)
  2474. ) {
  2475. var newHalfWidth = bbox.width * viewBoundScale * 0.5;
  2476. var newHalfHeight = bbox.height * viewBoundScale * 0.5;
  2477. bbox = new Rectangle(
  2478. fixLongitude(centerCartographic.longitude - newHalfWidth),
  2479. fixLatitude(centerCartographic.latitude - newHalfHeight),
  2480. fixLongitude(centerCartographic.longitude + newHalfWidth),
  2481. fixLatitude(centerCartographic.latitude + newHalfHeight)
  2482. );
  2483. }
  2484. queryString = queryString.replace(
  2485. "[bboxWest]",
  2486. CesiumMath.toDegrees(bbox.west).toString()
  2487. );
  2488. queryString = queryString.replace(
  2489. "[bboxSouth]",
  2490. CesiumMath.toDegrees(bbox.south).toString()
  2491. );
  2492. queryString = queryString.replace(
  2493. "[bboxEast]",
  2494. CesiumMath.toDegrees(bbox.east).toString()
  2495. );
  2496. queryString = queryString.replace(
  2497. "[bboxNorth]",
  2498. CesiumMath.toDegrees(bbox.north).toString()
  2499. );
  2500. var lon = CesiumMath.toDegrees(centerCartographic.longitude).toString();
  2501. var lat = CesiumMath.toDegrees(centerCartographic.latitude).toString();
  2502. queryString = queryString.replace("[lookatLon]", lon);
  2503. queryString = queryString.replace("[lookatLat]", lat);
  2504. queryString = queryString.replace(
  2505. "[lookatTilt]",
  2506. CesiumMath.toDegrees(camera.pitch).toString()
  2507. );
  2508. queryString = queryString.replace(
  2509. "[lookatHeading]",
  2510. CesiumMath.toDegrees(camera.heading).toString()
  2511. );
  2512. queryString = queryString.replace(
  2513. "[lookatRange]",
  2514. Cartesian3.distance(camera.positionWC, centerCartesian)
  2515. );
  2516. queryString = queryString.replace("[lookatTerrainLon]", lon);
  2517. queryString = queryString.replace("[lookatTerrainLat]", lat);
  2518. queryString = queryString.replace(
  2519. "[lookatTerrainAlt]",
  2520. centerCartographic.height.toString()
  2521. );
  2522. ellipsoid.cartesianToCartographic(camera.positionWC, scratchCartographic);
  2523. queryString = queryString.replace(
  2524. "[cameraLon]",
  2525. CesiumMath.toDegrees(scratchCartographic.longitude).toString()
  2526. );
  2527. queryString = queryString.replace(
  2528. "[cameraLat]",
  2529. CesiumMath.toDegrees(scratchCartographic.latitude).toString()
  2530. );
  2531. queryString = queryString.replace(
  2532. "[cameraAlt]",
  2533. CesiumMath.toDegrees(scratchCartographic.height).toString()
  2534. );
  2535. var frustum = camera.frustum;
  2536. var aspectRatio = frustum.aspectRatio;
  2537. var horizFov = "";
  2538. var vertFov = "";
  2539. if (defined(aspectRatio)) {
  2540. var fov = CesiumMath.toDegrees(frustum.fov);
  2541. if (aspectRatio > 1.0) {
  2542. horizFov = fov;
  2543. vertFov = fov / aspectRatio;
  2544. } else {
  2545. vertFov = fov;
  2546. horizFov = fov * aspectRatio;
  2547. }
  2548. }
  2549. queryString = queryString.replace("[horizFov]", horizFov.toString());
  2550. queryString = queryString.replace("[vertFov]", vertFov.toString());
  2551. } else {
  2552. queryString = queryString.replace("[bboxWest]", "-180");
  2553. queryString = queryString.replace("[bboxSouth]", "-90");
  2554. queryString = queryString.replace("[bboxEast]", "180");
  2555. queryString = queryString.replace("[bboxNorth]", "90");
  2556. queryString = queryString.replace("[lookatLon]", "");
  2557. queryString = queryString.replace("[lookatLat]", "");
  2558. queryString = queryString.replace("[lookatRange]", "");
  2559. queryString = queryString.replace("[lookatTilt]", "");
  2560. queryString = queryString.replace("[lookatHeading]", "");
  2561. queryString = queryString.replace("[lookatTerrainLon]", "");
  2562. queryString = queryString.replace("[lookatTerrainLat]", "");
  2563. queryString = queryString.replace("[lookatTerrainAlt]", "");
  2564. queryString = queryString.replace("[cameraLon]", "");
  2565. queryString = queryString.replace("[cameraLat]", "");
  2566. queryString = queryString.replace("[cameraAlt]", "");
  2567. queryString = queryString.replace("[horizFov]", "");
  2568. queryString = queryString.replace("[vertFov]", "");
  2569. }
  2570. if (defined(canvas)) {
  2571. queryString = queryString.replace("[horizPixels]", canvas.clientWidth);
  2572. queryString = queryString.replace("[vertPixels]", canvas.clientHeight);
  2573. } else {
  2574. queryString = queryString.replace("[horizPixels]", "");
  2575. queryString = queryString.replace("[vertPixels]", "");
  2576. }
  2577. queryString = queryString.replace("[terrainEnabled]", "1");
  2578. queryString = queryString.replace("[clientVersion]", "1");
  2579. queryString = queryString.replace("[kmlVersion]", "2.2");
  2580. queryString = queryString.replace("[clientName]", "Cesium");
  2581. queryString = queryString.replace("[language]", "English");
  2582. resource.setQueryParameters(queryToObject(queryString));
  2583. }
  2584. function processNetworkLink(dataSource, node, processingData, deferredLoading) {
  2585. var r = processFeature(dataSource, node, processingData);
  2586. var networkEntity = r.entity;
  2587. var sourceResource = processingData.sourceResource;
  2588. var uriResolver = processingData.uriResolver;
  2589. var link = queryFirstNode(node, "Link", namespaces.kml);
  2590. if (!defined(link)) {
  2591. link = queryFirstNode(node, "Url", namespaces.kml);
  2592. }
  2593. if (defined(link)) {
  2594. var href = queryStringValue(link, "href", namespaces.kml);
  2595. var viewRefreshMode;
  2596. var viewBoundScale;
  2597. if (defined(href)) {
  2598. var newSourceUri = href;
  2599. href = resolveHref(href, sourceResource, processingData.uriResolver);
  2600. // We need to pass in the original path if resolveHref returns a data uri because the network link
  2601. // references a document in a KMZ archive
  2602. if (/^data:/.test(href.getUrlComponent())) {
  2603. // So if sourceUri isn't the kmz file, then its another kml in the archive, so resolve it
  2604. if (!/\.kmz/i.test(sourceResource.getUrlComponent())) {
  2605. newSourceUri = sourceResource.getDerivedResource({
  2606. url: newSourceUri,
  2607. });
  2608. }
  2609. } else {
  2610. newSourceUri = href.clone(); // Not a data uri so use the fully qualified uri
  2611. viewRefreshMode = queryStringValue(
  2612. link,
  2613. "viewRefreshMode",
  2614. namespaces.kml
  2615. );
  2616. viewBoundScale = defaultValue(
  2617. queryStringValue(link, "viewBoundScale", namespaces.kml),
  2618. 1.0
  2619. );
  2620. var defaultViewFormat =
  2621. viewRefreshMode === "onStop"
  2622. ? "BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]"
  2623. : "";
  2624. var viewFormat = defaultValue(
  2625. queryStringValue(link, "viewFormat", namespaces.kml),
  2626. defaultViewFormat
  2627. );
  2628. var httpQuery = queryStringValue(link, "httpQuery", namespaces.kml);
  2629. if (defined(viewFormat)) {
  2630. href.setQueryParameters(queryToObject(cleanupString(viewFormat)));
  2631. }
  2632. if (defined(httpQuery)) {
  2633. href.setQueryParameters(queryToObject(cleanupString(httpQuery)));
  2634. }
  2635. var ellipsoid = dataSource._ellipsoid;
  2636. processNetworkLinkQueryString(
  2637. href,
  2638. dataSource._camera,
  2639. dataSource._canvas,
  2640. viewBoundScale,
  2641. dataSource._lastCameraView.bbox,
  2642. ellipsoid
  2643. );
  2644. }
  2645. var options = {
  2646. sourceUri: newSourceUri,
  2647. uriResolver: uriResolver,
  2648. context: networkEntity.id,
  2649. };
  2650. var networkLinkCollection = new EntityCollection();
  2651. var promise = load(dataSource, networkLinkCollection, href, options)
  2652. .then(function (rootElement) {
  2653. var entities = dataSource._entityCollection;
  2654. var newEntities = networkLinkCollection.values;
  2655. entities.suspendEvents();
  2656. for (var i = 0; i < newEntities.length; i++) {
  2657. var newEntity = newEntities[i];
  2658. if (!defined(newEntity.parent)) {
  2659. newEntity.parent = networkEntity;
  2660. mergeAvailabilityWithParent(newEntity);
  2661. }
  2662. entities.add(newEntity);
  2663. }
  2664. entities.resumeEvents();
  2665. // Add network links to a list if we need they will need to be updated
  2666. var refreshMode = queryStringValue(
  2667. link,
  2668. "refreshMode",
  2669. namespaces.kml
  2670. );
  2671. var refreshInterval = defaultValue(
  2672. queryNumericValue(link, "refreshInterval", namespaces.kml),
  2673. 0
  2674. );
  2675. if (
  2676. (refreshMode === "onInterval" && refreshInterval > 0) ||
  2677. refreshMode === "onExpire" ||
  2678. viewRefreshMode === "onStop"
  2679. ) {
  2680. var networkLinkControl = queryFirstNode(
  2681. rootElement,
  2682. "NetworkLinkControl",
  2683. namespaces.kml
  2684. );
  2685. var hasNetworkLinkControl = defined(networkLinkControl);
  2686. var now = JulianDate.now();
  2687. var networkLinkInfo = {
  2688. id: createGuid(),
  2689. href: href,
  2690. cookie: {},
  2691. lastUpdated: now,
  2692. updating: false,
  2693. entity: networkEntity,
  2694. viewBoundScale: viewBoundScale,
  2695. needsUpdate: false,
  2696. cameraUpdateTime: now,
  2697. };
  2698. var minRefreshPeriod = 0;
  2699. if (hasNetworkLinkControl) {
  2700. networkLinkInfo.cookie = queryToObject(
  2701. defaultValue(
  2702. queryStringValue(
  2703. networkLinkControl,
  2704. "cookie",
  2705. namespaces.kml
  2706. ),
  2707. ""
  2708. )
  2709. );
  2710. minRefreshPeriod = defaultValue(
  2711. queryNumericValue(
  2712. networkLinkControl,
  2713. "minRefreshPeriod",
  2714. namespaces.kml
  2715. ),
  2716. 0
  2717. );
  2718. }
  2719. if (refreshMode === "onInterval") {
  2720. if (hasNetworkLinkControl) {
  2721. refreshInterval = Math.max(minRefreshPeriod, refreshInterval);
  2722. }
  2723. networkLinkInfo.refreshMode = RefreshMode.INTERVAL;
  2724. networkLinkInfo.time = refreshInterval;
  2725. } else if (refreshMode === "onExpire") {
  2726. var expires;
  2727. if (hasNetworkLinkControl) {
  2728. expires = queryStringValue(
  2729. networkLinkControl,
  2730. "expires",
  2731. namespaces.kml
  2732. );
  2733. }
  2734. if (defined(expires)) {
  2735. try {
  2736. var date = JulianDate.fromIso8601(expires);
  2737. var diff = JulianDate.secondsDifference(date, now);
  2738. if (diff > 0 && diff < minRefreshPeriod) {
  2739. JulianDate.addSeconds(now, minRefreshPeriod, date);
  2740. }
  2741. networkLinkInfo.refreshMode = RefreshMode.EXPIRE;
  2742. networkLinkInfo.time = date;
  2743. } catch (e) {
  2744. oneTimeWarning(
  2745. "kml-refreshMode-onInterval-onExpire",
  2746. "KML - NetworkLinkControl expires is not a valid date"
  2747. );
  2748. }
  2749. } else {
  2750. oneTimeWarning(
  2751. "kml-refreshMode-onExpire",
  2752. "KML - refreshMode of onExpire requires the NetworkLinkControl to have an expires element"
  2753. );
  2754. }
  2755. } else if (dataSource._camera) {
  2756. // Only allow onStop refreshes if we have a camera
  2757. networkLinkInfo.refreshMode = RefreshMode.STOP;
  2758. networkLinkInfo.time = defaultValue(
  2759. queryNumericValue(link, "viewRefreshTime", namespaces.kml),
  2760. 0
  2761. );
  2762. } else {
  2763. oneTimeWarning(
  2764. "kml-refrehMode-onStop-noCamera",
  2765. "A NetworkLink with viewRefreshMode=onStop requires a camera be passed in when creating the KmlDataSource"
  2766. );
  2767. }
  2768. if (defined(networkLinkInfo.refreshMode)) {
  2769. dataSource._networkLinks.set(networkLinkInfo.id, networkLinkInfo);
  2770. }
  2771. } else if (viewRefreshMode === "onRegion") {
  2772. oneTimeWarning(
  2773. "kml-refrehMode-onRegion",
  2774. "KML - Unsupported viewRefreshMode: onRegion"
  2775. );
  2776. }
  2777. })
  2778. .otherwise(function (error) {
  2779. oneTimeWarning("An error occured during loading " + href.url);
  2780. dataSource._error.raiseEvent(dataSource, error);
  2781. });
  2782. deferredLoading.addPromise(promise);
  2783. }
  2784. }
  2785. }
  2786. function processFeatureNode(dataSource, node, processingData, deferredLoading) {
  2787. var featureProcessor = featureTypes[node.localName];
  2788. if (defined(featureProcessor)) {
  2789. return featureProcessor(dataSource, node, processingData, deferredLoading);
  2790. }
  2791. return processUnsupportedFeature(
  2792. dataSource,
  2793. node,
  2794. processingData,
  2795. deferredLoading
  2796. );
  2797. }
  2798. function loadKml(
  2799. dataSource,
  2800. entityCollection,
  2801. kml,
  2802. sourceResource,
  2803. uriResolver,
  2804. context
  2805. ) {
  2806. entityCollection.removeAll();
  2807. var documentElement = kml.documentElement;
  2808. var document =
  2809. documentElement.localName === "Document"
  2810. ? documentElement
  2811. : queryFirstNode(documentElement, "Document", namespaces.kml);
  2812. var name = queryStringValue(document, "name", namespaces.kml);
  2813. if (!defined(name)) {
  2814. name = getFilenameFromUri(sourceResource.getUrlComponent());
  2815. }
  2816. // Only set the name from the root document
  2817. if (!defined(dataSource._name)) {
  2818. dataSource._name = name;
  2819. }
  2820. var deferredLoading = new KmlDataSource._DeferredLoading(dataSource);
  2821. var styleCollection = new EntityCollection(dataSource);
  2822. return when
  2823. .all(
  2824. processStyles(
  2825. dataSource,
  2826. kml,
  2827. styleCollection,
  2828. sourceResource,
  2829. false,
  2830. uriResolver
  2831. )
  2832. )
  2833. .then(function () {
  2834. var element = kml.documentElement;
  2835. if (element.localName === "kml") {
  2836. var childNodes = element.childNodes;
  2837. for (var i = 0; i < childNodes.length; i++) {
  2838. var tmp = childNodes[i];
  2839. if (defined(featureTypes[tmp.localName])) {
  2840. element = tmp;
  2841. break;
  2842. }
  2843. }
  2844. }
  2845. var processingData = {
  2846. parentEntity: undefined,
  2847. entityCollection: entityCollection,
  2848. styleCollection: styleCollection,
  2849. sourceResource: sourceResource,
  2850. uriResolver: uriResolver,
  2851. context: context,
  2852. };
  2853. entityCollection.suspendEvents();
  2854. processFeatureNode(dataSource, element, processingData, deferredLoading);
  2855. entityCollection.resumeEvents();
  2856. return deferredLoading.wait().then(function () {
  2857. return kml.documentElement;
  2858. });
  2859. });
  2860. }
  2861. function loadKmz(dataSource, entityCollection, blob, sourceResource) {
  2862. var deferred = when.defer();
  2863. zip.createReader(
  2864. new zip.BlobReader(blob),
  2865. function (reader) {
  2866. reader.getEntries(function (entries) {
  2867. var promises = [];
  2868. var uriResolver = {};
  2869. var docEntry;
  2870. var docDefer;
  2871. for (var i = 0; i < entries.length; i++) {
  2872. var entry = entries[i];
  2873. if (!entry.directory) {
  2874. var innerDefer = when.defer();
  2875. promises.push(innerDefer.promise);
  2876. if (/\.kml$/i.test(entry.filename)) {
  2877. // We use the first KML document we come across
  2878. // https://developers.google.com/kml/documentation/kmzarchives
  2879. // Unless we come across a .kml file at the root of the archive because GE does this
  2880. if (!defined(docEntry) || !/\//i.test(entry.filename)) {
  2881. if (defined(docEntry)) {
  2882. // We found one at the root so load the initial kml as a data uri
  2883. loadDataUriFromZip(docEntry, uriResolver, docDefer);
  2884. }
  2885. docEntry = entry;
  2886. docDefer = innerDefer;
  2887. } else {
  2888. // Wasn't the first kml and wasn't at the root
  2889. loadDataUriFromZip(entry, uriResolver, innerDefer);
  2890. }
  2891. } else {
  2892. loadDataUriFromZip(entry, uriResolver, innerDefer);
  2893. }
  2894. }
  2895. }
  2896. // Now load the root KML document
  2897. if (defined(docEntry)) {
  2898. loadXmlFromZip(docEntry, uriResolver, docDefer);
  2899. }
  2900. when
  2901. .all(promises)
  2902. .then(function () {
  2903. reader.close();
  2904. if (!defined(uriResolver.kml)) {
  2905. deferred.reject(
  2906. new RuntimeError("KMZ file does not contain a KML document.")
  2907. );
  2908. return;
  2909. }
  2910. uriResolver.keys = Object.keys(uriResolver);
  2911. return loadKml(
  2912. dataSource,
  2913. entityCollection,
  2914. uriResolver.kml,
  2915. sourceResource,
  2916. uriResolver
  2917. );
  2918. })
  2919. .then(deferred.resolve)
  2920. .otherwise(deferred.reject);
  2921. });
  2922. },
  2923. function (e) {
  2924. deferred.reject(e);
  2925. }
  2926. );
  2927. return deferred.promise;
  2928. }
  2929. function load(dataSource, entityCollection, data, options) {
  2930. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  2931. var sourceUri = options.sourceUri;
  2932. var uriResolver = options.uriResolver;
  2933. var context = options.context;
  2934. var promise = data;
  2935. if (typeof data === "string" || data instanceof Resource) {
  2936. data = Resource.createIfNeeded(data);
  2937. promise = data.fetchBlob();
  2938. sourceUri = defaultValue(sourceUri, data.clone());
  2939. // Add resource credits to our list of credits to display
  2940. var resourceCredits = dataSource._resourceCredits;
  2941. var credits = data.credits;
  2942. if (defined(credits)) {
  2943. var length = credits.length;
  2944. for (var i = 0; i < length; i++) {
  2945. resourceCredits.push(credits[i]);
  2946. }
  2947. }
  2948. } else {
  2949. sourceUri = defaultValue(sourceUri, Resource.DEFAULT.clone());
  2950. }
  2951. sourceUri = Resource.createIfNeeded(sourceUri);
  2952. return when(promise)
  2953. .then(function (dataToLoad) {
  2954. if (dataToLoad instanceof Blob) {
  2955. return isZipFile(dataToLoad).then(function (isZip) {
  2956. if (isZip) {
  2957. return loadKmz(dataSource, entityCollection, dataToLoad, sourceUri);
  2958. }
  2959. return readBlobAsText(dataToLoad).then(function (text) {
  2960. //There's no official way to validate if a parse was successful.
  2961. //The following check detects the error on various browsers.
  2962. //Insert missing namespaces
  2963. text = insertNamespaces(text);
  2964. //Remove Duplicate Namespaces
  2965. text = removeDuplicateNamespaces(text);
  2966. //IE raises an exception
  2967. var kml;
  2968. var error;
  2969. try {
  2970. kml = parser.parseFromString(text, "application/xml");
  2971. } catch (e) {
  2972. error = e.toString();
  2973. }
  2974. //The parse succeeds on Chrome and Firefox, but the error
  2975. //handling is different in each.
  2976. if (
  2977. defined(error) ||
  2978. kml.body ||
  2979. kml.documentElement.tagName === "parsererror"
  2980. ) {
  2981. //Firefox has error information as the firstChild nodeValue.
  2982. var msg = defined(error)
  2983. ? error
  2984. : kml.documentElement.firstChild.nodeValue;
  2985. //Chrome has it in the body text.
  2986. if (!msg) {
  2987. msg = kml.body.innerText;
  2988. }
  2989. //Return the error
  2990. throw new RuntimeError(msg);
  2991. }
  2992. return loadKml(
  2993. dataSource,
  2994. entityCollection,
  2995. kml,
  2996. sourceUri,
  2997. uriResolver,
  2998. context
  2999. );
  3000. });
  3001. });
  3002. }
  3003. return loadKml(
  3004. dataSource,
  3005. entityCollection,
  3006. dataToLoad,
  3007. sourceUri,
  3008. uriResolver,
  3009. context
  3010. );
  3011. })
  3012. .otherwise(function (error) {
  3013. dataSource._error.raiseEvent(dataSource, error);
  3014. console.log(error);
  3015. return when.reject(error);
  3016. });
  3017. }
  3018. /**
  3019. * @typedef {Object} KmlDataSource.LoadOptions
  3020. *
  3021. * Initialization options for the `load` method.
  3022. *
  3023. * @property {Camera} camera The camera that is used for viewRefreshModes and sending camera properties to network links.
  3024. * @property {HTMLCanvasElement} canvas The canvas that is used for sending viewer properties to network links.
  3025. * @property {String} [sourceUri] Overrides the url to use for resolving relative links and other KML network features.
  3026. * @property {Boolean} [clampToGround=false] true if we want the geometry features (Polygons, LineStrings and LinearRings) clamped to the ground.
  3027. * @property {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The global ellipsoid used for geographical calculations.
  3028. * @property {Credit|String} [credit] A credit for the data source, which is displayed on the canvas.
  3029. */
  3030. /**
  3031. * A {@link DataSource} which processes Keyhole Markup Language 2.2 (KML).
  3032. * <p>
  3033. * KML support in Cesium is incomplete, but a large amount of the standard,
  3034. * as well as Google's <code>gx</code> extension namespace, is supported. See Github issue
  3035. * {@link https://github.com/CesiumGS/cesium/issues/873|#873} for a
  3036. * detailed list of what is and isn't support. Cesium will also write information to the
  3037. * console when it encounters most unsupported features.
  3038. * </p>
  3039. * <p>
  3040. * Non visual feature data, such as <code>atom:author</code> and <code>ExtendedData</code>
  3041. * is exposed via an instance of {@link KmlFeatureData}, which is added to each {@link Entity}
  3042. * under the <code>kml</code> property.
  3043. * </p>
  3044. *
  3045. * @alias KmlDataSource
  3046. * @constructor
  3047. *
  3048. * @param {Object} options An object with the following properties:
  3049. * @param {Camera} options.camera The camera that is used for viewRefreshModes and sending camera properties to network links.
  3050. * @param {HTMLCanvasElement} options.canvas The canvas that is used for sending viewer properties to network links.
  3051. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The global ellipsoid used for geographical calculations.
  3052. * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
  3053. *
  3054. * @see {@link http://www.opengeospatial.org/standards/kml/|Open Geospatial Consortium KML Standard}
  3055. * @see {@link https://developers.google.com/kml/|Google KML Documentation}
  3056. *
  3057. * @demo {@link https://sandcastle.cesium.com/index.html?src=KML.html|Cesium Sandcastle KML Demo}
  3058. *
  3059. * @example
  3060. * var viewer = new Cesium.Viewer('cesiumContainer');
  3061. * viewer.dataSources.add(Cesium.KmlDataSource.load('../../SampleData/facilities.kmz',
  3062. * {
  3063. * camera: viewer.scene.camera,
  3064. * canvas: viewer.scene.canvas
  3065. * })
  3066. * );
  3067. */
  3068. function KmlDataSource(options) {
  3069. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  3070. var camera = options.camera;
  3071. var canvas = options.canvas;
  3072. //>>includeStart('debug', pragmas.debug);
  3073. if (!defined(camera)) {
  3074. throw new DeveloperError("options.camera is required.");
  3075. }
  3076. if (!defined(canvas)) {
  3077. throw new DeveloperError("options.canvas is required.");
  3078. }
  3079. //>>includeEnd('debug');
  3080. this._changed = new Event();
  3081. this._error = new Event();
  3082. this._loading = new Event();
  3083. this._refresh = new Event();
  3084. this._unsupportedNode = new Event();
  3085. this._clock = undefined;
  3086. this._entityCollection = new EntityCollection(this);
  3087. this._name = undefined;
  3088. this._isLoading = false;
  3089. this._pinBuilder = new PinBuilder();
  3090. this._networkLinks = new AssociativeArray();
  3091. this._entityCluster = new EntityCluster();
  3092. this._canvas = canvas;
  3093. this._camera = camera;
  3094. this._lastCameraView = {
  3095. position: defined(camera) ? Cartesian3.clone(camera.positionWC) : undefined,
  3096. direction: defined(camera)
  3097. ? Cartesian3.clone(camera.directionWC)
  3098. : undefined,
  3099. up: defined(camera) ? Cartesian3.clone(camera.upWC) : undefined,
  3100. bbox: defined(camera)
  3101. ? camera.computeViewRectangle()
  3102. : Rectangle.clone(Rectangle.MAX_VALUE),
  3103. };
  3104. this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
  3105. // User specified credit
  3106. var credit = options.credit;
  3107. if (typeof credit === "string") {
  3108. credit = new Credit(credit);
  3109. }
  3110. this._credit = credit;
  3111. // Create a list of Credit's from the resource that the user can't remove
  3112. this._resourceCredits = [];
  3113. }
  3114. /**
  3115. * Creates a Promise to a new instance loaded with the provided KML data.
  3116. *
  3117. * @param {Resource|String|Document|Blob} data A url, parsed KML document, or Blob containing binary KMZ data or a parsed KML document.
  3118. * @param {KmlDataSource.LoadOptions} [options] An object specifying configuration options
  3119. *
  3120. * @returns {Promise.<KmlDataSource>} A promise that will resolve to a new KmlDataSource instance once the KML is loaded.
  3121. */
  3122. KmlDataSource.load = function (data, options) {
  3123. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  3124. var dataSource = new KmlDataSource(options);
  3125. return dataSource.load(data, options);
  3126. };
  3127. Object.defineProperties(KmlDataSource.prototype, {
  3128. /**
  3129. * Gets or sets a human-readable name for this instance.
  3130. * This will be automatically be set to the KML document name on load.
  3131. * @memberof KmlDataSource.prototype
  3132. * @type {String}
  3133. */
  3134. name: {
  3135. get: function () {
  3136. return this._name;
  3137. },
  3138. set: function (value) {
  3139. if (this._name !== value) {
  3140. this._name = value;
  3141. this._changed.raiseEvent(this);
  3142. }
  3143. },
  3144. },
  3145. /**
  3146. * Gets the clock settings defined by the loaded KML. This represents the total
  3147. * availability interval for all time-dynamic data. If the KML does not contain
  3148. * time-dynamic data, this value is undefined.
  3149. * @memberof KmlDataSource.prototype
  3150. * @type {DataSourceClock}
  3151. */
  3152. clock: {
  3153. get: function () {
  3154. return this._clock;
  3155. },
  3156. },
  3157. /**
  3158. * Gets the collection of {@link Entity} instances.
  3159. * @memberof KmlDataSource.prototype
  3160. * @type {EntityCollection}
  3161. */
  3162. entities: {
  3163. get: function () {
  3164. return this._entityCollection;
  3165. },
  3166. },
  3167. /**
  3168. * Gets a value indicating if the data source is currently loading data.
  3169. * @memberof KmlDataSource.prototype
  3170. * @type {Boolean}
  3171. */
  3172. isLoading: {
  3173. get: function () {
  3174. return this._isLoading;
  3175. },
  3176. },
  3177. /**
  3178. * Gets an event that will be raised when the underlying data changes.
  3179. * @memberof KmlDataSource.prototype
  3180. * @type {Event}
  3181. */
  3182. changedEvent: {
  3183. get: function () {
  3184. return this._changed;
  3185. },
  3186. },
  3187. /**
  3188. * Gets an event that will be raised if an error is encountered during processing.
  3189. * @memberof KmlDataSource.prototype
  3190. * @type {Event}
  3191. */
  3192. errorEvent: {
  3193. get: function () {
  3194. return this._error;
  3195. },
  3196. },
  3197. /**
  3198. * Gets an event that will be raised when the data source either starts or stops loading.
  3199. * @memberof KmlDataSource.prototype
  3200. * @type {Event}
  3201. */
  3202. loadingEvent: {
  3203. get: function () {
  3204. return this._loading;
  3205. },
  3206. },
  3207. /**
  3208. * Gets an event that will be raised when the data source refreshes a network link.
  3209. * @memberof KmlDataSource.prototype
  3210. * @type {Event}
  3211. */
  3212. refreshEvent: {
  3213. get: function () {
  3214. return this._refresh;
  3215. },
  3216. },
  3217. /**
  3218. * Gets an event that will be raised when the data source finds an unsupported node type.
  3219. * @memberof KmlDataSource.prototype
  3220. * @type {Event}
  3221. */
  3222. unsupportedNodeEvent: {
  3223. get: function () {
  3224. return this._unsupportedNode;
  3225. },
  3226. },
  3227. /**
  3228. * Gets whether or not this data source should be displayed.
  3229. * @memberof KmlDataSource.prototype
  3230. * @type {Boolean}
  3231. */
  3232. show: {
  3233. get: function () {
  3234. return this._entityCollection.show;
  3235. },
  3236. set: function (value) {
  3237. this._entityCollection.show = value;
  3238. },
  3239. },
  3240. /**
  3241. * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources.
  3242. *
  3243. * @memberof KmlDataSource.prototype
  3244. * @type {EntityCluster}
  3245. */
  3246. clustering: {
  3247. get: function () {
  3248. return this._entityCluster;
  3249. },
  3250. set: function (value) {
  3251. //>>includeStart('debug', pragmas.debug);
  3252. if (!defined(value)) {
  3253. throw new DeveloperError("value must be defined.");
  3254. }
  3255. //>>includeEnd('debug');
  3256. this._entityCluster = value;
  3257. },
  3258. },
  3259. /**
  3260. * Gets the credit that will be displayed for the data source
  3261. * @memberof KmlDataSource.prototype
  3262. * @type {Credit}
  3263. */
  3264. credit: {
  3265. get: function () {
  3266. return this._credit;
  3267. },
  3268. },
  3269. });
  3270. /**
  3271. * Asynchronously loads the provided KML data, replacing any existing data.
  3272. *
  3273. * @param {Resource|String|Document|Blob} data A url, parsed KML document, or Blob containing binary KMZ data or a parsed KML document.
  3274. * @param {Object} [options] An object with the following properties:
  3275. * @param {Resource|String} [options.sourceUri] Overrides the url to use for resolving relative links and other KML network features.
  3276. * @param {Boolean} [options.clampToGround=false] true if we want the geometry features (Polygons, LineStrings and LinearRings) clamped to the ground. If true, lines will use corridors so use Entity.corridor instead of Entity.polyline.
  3277. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The global ellipsoid used for geographical calculations.
  3278. *
  3279. * @returns {Promise.<KmlDataSource>} A promise that will resolve to this instances once the KML is loaded.
  3280. */
  3281. KmlDataSource.prototype.load = function (data, options) {
  3282. //>>includeStart('debug', pragmas.debug);
  3283. if (!defined(data)) {
  3284. throw new DeveloperError("data is required.");
  3285. }
  3286. //>>includeEnd('debug');
  3287. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  3288. DataSource.setLoading(this, true);
  3289. var oldName = this._name;
  3290. this._name = undefined;
  3291. this._clampToGround = defaultValue(options.clampToGround, false);
  3292. var that = this;
  3293. return load(this, this._entityCollection, data, options)
  3294. .then(function () {
  3295. var clock;
  3296. var availability = that._entityCollection.computeAvailability();
  3297. var start = availability.start;
  3298. var stop = availability.stop;
  3299. var isMinStart = JulianDate.equals(start, Iso8601.MINIMUM_VALUE);
  3300. var isMaxStop = JulianDate.equals(stop, Iso8601.MAXIMUM_VALUE);
  3301. if (!isMinStart || !isMaxStop) {
  3302. var date;
  3303. //If start is min time just start at midnight this morning, local time
  3304. if (isMinStart) {
  3305. date = new Date();
  3306. date.setHours(0, 0, 0, 0);
  3307. start = JulianDate.fromDate(date);
  3308. }
  3309. //If stop is max value just stop at midnight tonight, local time
  3310. if (isMaxStop) {
  3311. date = new Date();
  3312. date.setHours(24, 0, 0, 0);
  3313. stop = JulianDate.fromDate(date);
  3314. }
  3315. clock = new DataSourceClock();
  3316. clock.startTime = start;
  3317. clock.stopTime = stop;
  3318. clock.currentTime = JulianDate.clone(start);
  3319. clock.clockRange = ClockRange.LOOP_STOP;
  3320. clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
  3321. clock.multiplier = Math.round(
  3322. Math.min(
  3323. Math.max(JulianDate.secondsDifference(stop, start) / 60, 1),
  3324. 3.15569e7
  3325. )
  3326. );
  3327. }
  3328. var changed = false;
  3329. if (clock !== that._clock) {
  3330. that._clock = clock;
  3331. changed = true;
  3332. }
  3333. if (oldName !== that._name) {
  3334. changed = true;
  3335. }
  3336. if (changed) {
  3337. that._changed.raiseEvent(that);
  3338. }
  3339. DataSource.setLoading(that, false);
  3340. return that;
  3341. })
  3342. .otherwise(function (error) {
  3343. DataSource.setLoading(that, false);
  3344. that._error.raiseEvent(that, error);
  3345. console.log(error);
  3346. return when.reject(error);
  3347. });
  3348. };
  3349. function mergeAvailabilityWithParent(child) {
  3350. var parent = child.parent;
  3351. if (defined(parent)) {
  3352. var parentAvailability = parent.availability;
  3353. if (defined(parentAvailability)) {
  3354. var childAvailability = child.availability;
  3355. if (defined(childAvailability)) {
  3356. childAvailability.intersect(parentAvailability);
  3357. } else {
  3358. child.availability = parentAvailability;
  3359. }
  3360. }
  3361. }
  3362. }
  3363. function getNetworkLinkUpdateCallback(
  3364. dataSource,
  3365. networkLink,
  3366. newEntityCollection,
  3367. networkLinks,
  3368. processedHref
  3369. ) {
  3370. return function (rootElement) {
  3371. if (!networkLinks.contains(networkLink.id)) {
  3372. // Got into the odd case where a parent network link was updated while a child
  3373. // network link update was in flight, so just throw it away.
  3374. return;
  3375. }
  3376. var remove = false;
  3377. var networkLinkControl = queryFirstNode(
  3378. rootElement,
  3379. "NetworkLinkControl",
  3380. namespaces.kml
  3381. );
  3382. var hasNetworkLinkControl = defined(networkLinkControl);
  3383. var minRefreshPeriod = 0;
  3384. if (hasNetworkLinkControl) {
  3385. if (
  3386. defined(queryFirstNode(networkLinkControl, "Update", namespaces.kml))
  3387. ) {
  3388. oneTimeWarning(
  3389. "kml-networkLinkControl-update",
  3390. "KML - NetworkLinkControl updates aren't supported."
  3391. );
  3392. networkLink.updating = false;
  3393. networkLinks.remove(networkLink.id);
  3394. return;
  3395. }
  3396. networkLink.cookie = queryToObject(
  3397. defaultValue(
  3398. queryStringValue(networkLinkControl, "cookie", namespaces.kml),
  3399. ""
  3400. )
  3401. );
  3402. minRefreshPeriod = defaultValue(
  3403. queryNumericValue(
  3404. networkLinkControl,
  3405. "minRefreshPeriod",
  3406. namespaces.kml
  3407. ),
  3408. 0
  3409. );
  3410. }
  3411. var now = JulianDate.now();
  3412. var refreshMode = networkLink.refreshMode;
  3413. if (refreshMode === RefreshMode.INTERVAL) {
  3414. if (defined(networkLinkControl)) {
  3415. networkLink.time = Math.max(minRefreshPeriod, networkLink.time);
  3416. }
  3417. } else if (refreshMode === RefreshMode.EXPIRE) {
  3418. var expires;
  3419. if (defined(networkLinkControl)) {
  3420. expires = queryStringValue(
  3421. networkLinkControl,
  3422. "expires",
  3423. namespaces.kml
  3424. );
  3425. }
  3426. if (defined(expires)) {
  3427. try {
  3428. var date = JulianDate.fromIso8601(expires);
  3429. var diff = JulianDate.secondsDifference(date, now);
  3430. if (diff > 0 && diff < minRefreshPeriod) {
  3431. JulianDate.addSeconds(now, minRefreshPeriod, date);
  3432. }
  3433. networkLink.time = date;
  3434. } catch (e) {
  3435. oneTimeWarning(
  3436. "kml-networkLinkControl-expires",
  3437. "KML - NetworkLinkControl expires is not a valid date"
  3438. );
  3439. remove = true;
  3440. }
  3441. } else {
  3442. oneTimeWarning(
  3443. "kml-refreshMode-onExpire",
  3444. "KML - refreshMode of onExpire requires the NetworkLinkControl to have an expires element"
  3445. );
  3446. remove = true;
  3447. }
  3448. }
  3449. var networkLinkEntity = networkLink.entity;
  3450. var entityCollection = dataSource._entityCollection;
  3451. var newEntities = newEntityCollection.values;
  3452. function removeChildren(entity) {
  3453. entityCollection.remove(entity);
  3454. var children = entity._children;
  3455. var count = children.length;
  3456. for (var i = 0; i < count; ++i) {
  3457. removeChildren(children[i]);
  3458. }
  3459. }
  3460. // Remove old entities
  3461. entityCollection.suspendEvents();
  3462. var entitiesCopy = entityCollection.values.slice();
  3463. var i;
  3464. for (i = 0; i < entitiesCopy.length; ++i) {
  3465. var entityToRemove = entitiesCopy[i];
  3466. if (entityToRemove.parent === networkLinkEntity) {
  3467. entityToRemove.parent = undefined;
  3468. removeChildren(entityToRemove);
  3469. }
  3470. }
  3471. entityCollection.resumeEvents();
  3472. // Add new entities
  3473. entityCollection.suspendEvents();
  3474. for (i = 0; i < newEntities.length; i++) {
  3475. var newEntity = newEntities[i];
  3476. if (!defined(newEntity.parent)) {
  3477. newEntity.parent = networkLinkEntity;
  3478. mergeAvailabilityWithParent(newEntity);
  3479. }
  3480. entityCollection.add(newEntity);
  3481. }
  3482. entityCollection.resumeEvents();
  3483. // No refresh information remove it, otherwise update lastUpdate time
  3484. if (remove) {
  3485. networkLinks.remove(networkLink.id);
  3486. } else {
  3487. networkLink.lastUpdated = now;
  3488. }
  3489. var availability = entityCollection.computeAvailability();
  3490. var start = availability.start;
  3491. var stop = availability.stop;
  3492. var isMinStart = JulianDate.equals(start, Iso8601.MINIMUM_VALUE);
  3493. var isMaxStop = JulianDate.equals(stop, Iso8601.MAXIMUM_VALUE);
  3494. if (!isMinStart || !isMaxStop) {
  3495. var clock = dataSource._clock;
  3496. if (clock.startTime !== start || clock.stopTime !== stop) {
  3497. clock.startTime = start;
  3498. clock.stopTime = stop;
  3499. dataSource._changed.raiseEvent(dataSource);
  3500. }
  3501. }
  3502. networkLink.updating = false;
  3503. networkLink.needsUpdate = false;
  3504. dataSource._refresh.raiseEvent(
  3505. dataSource,
  3506. processedHref.getUrlComponent(true)
  3507. );
  3508. };
  3509. }
  3510. var entitiesToIgnore = new AssociativeArray();
  3511. /**
  3512. * Updates any NetworkLink that require updating.
  3513. *
  3514. * @param {JulianDate} time The simulation time.
  3515. * @returns {Boolean} True if this data source is ready to be displayed at the provided time, false otherwise.
  3516. */
  3517. KmlDataSource.prototype.update = function (time) {
  3518. var networkLinks = this._networkLinks;
  3519. if (networkLinks.length === 0) {
  3520. return true;
  3521. }
  3522. var now = JulianDate.now();
  3523. var that = this;
  3524. entitiesToIgnore.removeAll();
  3525. function recurseIgnoreEntities(entity) {
  3526. var children = entity._children;
  3527. var count = children.length;
  3528. for (var i = 0; i < count; ++i) {
  3529. var child = children[i];
  3530. entitiesToIgnore.set(child.id, child);
  3531. recurseIgnoreEntities(child);
  3532. }
  3533. }
  3534. var cameraViewUpdate = false;
  3535. var lastCameraView = this._lastCameraView;
  3536. var camera = this._camera;
  3537. if (
  3538. defined(camera) &&
  3539. !(
  3540. camera.positionWC.equalsEpsilon(
  3541. lastCameraView.position,
  3542. CesiumMath.EPSILON7
  3543. ) &&
  3544. camera.directionWC.equalsEpsilon(
  3545. lastCameraView.direction,
  3546. CesiumMath.EPSILON7
  3547. ) &&
  3548. camera.upWC.equalsEpsilon(lastCameraView.up, CesiumMath.EPSILON7)
  3549. )
  3550. ) {
  3551. // Camera has changed so update the last view
  3552. lastCameraView.position = Cartesian3.clone(camera.positionWC);
  3553. lastCameraView.direction = Cartesian3.clone(camera.directionWC);
  3554. lastCameraView.up = Cartesian3.clone(camera.upWC);
  3555. lastCameraView.bbox = camera.computeViewRectangle();
  3556. cameraViewUpdate = true;
  3557. }
  3558. var newNetworkLinks = new AssociativeArray();
  3559. var changed = false;
  3560. networkLinks.values.forEach(function (networkLink) {
  3561. var entity = networkLink.entity;
  3562. if (entitiesToIgnore.contains(entity.id)) {
  3563. return;
  3564. }
  3565. if (!networkLink.updating) {
  3566. var doUpdate = false;
  3567. if (networkLink.refreshMode === RefreshMode.INTERVAL) {
  3568. if (
  3569. JulianDate.secondsDifference(now, networkLink.lastUpdated) >
  3570. networkLink.time
  3571. ) {
  3572. doUpdate = true;
  3573. }
  3574. } else if (networkLink.refreshMode === RefreshMode.EXPIRE) {
  3575. if (JulianDate.greaterThan(now, networkLink.time)) {
  3576. doUpdate = true;
  3577. }
  3578. } else if (networkLink.refreshMode === RefreshMode.STOP) {
  3579. if (cameraViewUpdate) {
  3580. networkLink.needsUpdate = true;
  3581. networkLink.cameraUpdateTime = now;
  3582. }
  3583. if (
  3584. networkLink.needsUpdate &&
  3585. JulianDate.secondsDifference(now, networkLink.cameraUpdateTime) >=
  3586. networkLink.time
  3587. ) {
  3588. doUpdate = true;
  3589. }
  3590. }
  3591. if (doUpdate) {
  3592. recurseIgnoreEntities(entity);
  3593. networkLink.updating = true;
  3594. var newEntityCollection = new EntityCollection();
  3595. var href = networkLink.href.clone();
  3596. href.setQueryParameters(networkLink.cookie);
  3597. var ellipsoid = defaultValue(that._ellipsoid, Ellipsoid.WGS84);
  3598. processNetworkLinkQueryString(
  3599. href,
  3600. that._camera,
  3601. that._canvas,
  3602. networkLink.viewBoundScale,
  3603. lastCameraView.bbox,
  3604. ellipsoid
  3605. );
  3606. load(that, newEntityCollection, href, { context: entity.id })
  3607. .then(
  3608. getNetworkLinkUpdateCallback(
  3609. that,
  3610. networkLink,
  3611. newEntityCollection,
  3612. newNetworkLinks,
  3613. href
  3614. )
  3615. )
  3616. .otherwise(function (error) {
  3617. var msg =
  3618. "NetworkLink " + networkLink.href + " refresh failed: " + error;
  3619. console.log(msg);
  3620. that._error.raiseEvent(that, msg);
  3621. });
  3622. changed = true;
  3623. }
  3624. }
  3625. newNetworkLinks.set(networkLink.id, networkLink);
  3626. });
  3627. if (changed) {
  3628. this._networkLinks = newNetworkLinks;
  3629. this._changed.raiseEvent(this);
  3630. }
  3631. return true;
  3632. };
  3633. /**
  3634. * Contains KML Feature data loaded into the <code>Entity.kml</code> property by {@link KmlDataSource}.
  3635. * @alias KmlFeatureData
  3636. * @constructor
  3637. */
  3638. function KmlFeatureData() {
  3639. /**
  3640. * @typedef KmlFeatureData.Author
  3641. * @type {Object}
  3642. * @property {String} name Gets the name.
  3643. * @property {String} uri Gets the URI.
  3644. * @property {Number} age Gets the email.
  3645. */
  3646. /**
  3647. * Gets the atom syndication format author field.
  3648. * @type {KmlFeatureData.Author}
  3649. */
  3650. this.author = {
  3651. name: undefined,
  3652. uri: undefined,
  3653. email: undefined,
  3654. };
  3655. /**
  3656. * @typedef KmlFeatureData.Link
  3657. * @type {Object}
  3658. * @property {String} href Gets the href.
  3659. * @property {String} hreflang Gets the language of the linked resource.
  3660. * @property {String} rel Gets the link relation.
  3661. * @property {String} type Gets the link type.
  3662. * @property {String} title Gets the link title.
  3663. * @property {String} length Gets the link length.
  3664. */
  3665. /**
  3666. * Gets the link.
  3667. * @type {KmlFeatureData.Link}
  3668. */
  3669. this.link = {
  3670. href: undefined,
  3671. hreflang: undefined,
  3672. rel: undefined,
  3673. type: undefined,
  3674. title: undefined,
  3675. length: undefined,
  3676. };
  3677. /**
  3678. * Gets the unstructured address field.
  3679. * @type {String}
  3680. */
  3681. this.address = undefined;
  3682. /**
  3683. * Gets the phone number.
  3684. * @type {String}
  3685. */
  3686. this.phoneNumber = undefined;
  3687. /**
  3688. * Gets the snippet.
  3689. * @type {String}
  3690. */
  3691. this.snippet = undefined;
  3692. /**
  3693. * Gets the extended data, parsed into a JSON object.
  3694. * Currently only the <code>Data</code> property is supported.
  3695. * <code>SchemaData</code> and custom data are ignored.
  3696. * @type {String}
  3697. */
  3698. this.extendedData = undefined;
  3699. }
  3700. // For testing
  3701. KmlDataSource._DeferredLoading = DeferredLoading;
  3702. KmlDataSource._getTimestamp = getTimestamp;
  3703. export default KmlDataSource;