commit a8bb35a3953bffbf1503fdd7f0a430f422a6554a Author: manetta Date: Wed Jan 20 16:02:19 2021 +0100 new standalone git repository for this Special Issue diff --git a/Announcement.txt b/Announcement.txt new file mode 100644 index 0000000..f864c3c --- /dev/null +++ b/Announcement.txt @@ -0,0 +1,18 @@ +The Network We (de)Served, (XPUB Special Issue #08) +Thursday, 04 April 2019 at Varia + +Dear guest, + +We traveled from home to home by bicycle, setting up homeservers. As friends and companions on this Infrastructour, we studied our routers over drinks served by our hosts. Where possible we installed our servers in our homes, in other cases we had to depend on another member of the group. While self-hosting together we questioned our understandings of networks, autonomy, online publishing and social infrastructures, where each of us departed from a different question. We would like to share our personal (yet interconnected) routes with you, tell you a story, present our web- and printed zines, and invite you to explore our homebrewed network. + +Date: Thursday, 04 April 2019 +Location: Gouwstraat 3, Rotterdam +Entrance: Free +Start: 19:00 + +https://issue.xpub.nl/08/ + +Contributors: Simon Browne, Tancredi Di Giovanni, Paloma García, Rita Graça, Artemis Gryllaki, Pedro Sá Couto, Biyi Wen, Bohye Woo, Roel Roscam Abbing, Manetta Berends, Lídia Pereira, André Castro, Aymeric Mansoux, Michael Murtaugh, Steve Rushton, Leslie Robbins. + +Brought to you by the Master of Arts in Fine Art and Design: Experimental Publishing (XPUB) of the Piet Zwart Institute, and Varia, Centre for Everyday Technology, Rotterdam, April 2019. + diff --git a/CNN/.footer.html b/CNN/.footer.html new file mode 100644 index 0000000..2d6d16f --- /dev/null +++ b/CNN/.footer.html @@ -0,0 +1,20 @@ +
+
+

Category Network Navigator (CNN)

+
+
+

The CNN is a tool to navigate through our researches.
+ Originally, the banner that you can see on the top of this page and in Introduction.html, was linking randomly to one of our research website. + Once inside the network, the banner was showing four categories linking our research websites by common topics treated:

+
    +
  • What is a Network?
  • +
  • Autonomy and its Contingencies
  • +
  • Social Networks
  • +
  • Network(ed) Publishing
  • +
  • Mapping Networks
  • +
+
+
+
+ + diff --git a/CNN/.header.html b/CNN/.header.html new file mode 100644 index 0000000..ac6d9b1 --- /dev/null +++ b/CNN/.header.html @@ -0,0 +1,63 @@ + + + + + + The Network We (de)Served - Special Issue #08 + + + + +
+ +
+ + + +
+

Index of/ The Network We (de)Served

+
+ + + + diff --git a/CNN/cnn.css b/CNN/cnn.css new file mode 100644 index 0000000..185d06c --- /dev/null +++ b/CNN/cnn.css @@ -0,0 +1,63 @@ +/* Push the body down with margin-top*/ +body { + margin-top: 40px; + /*background-color: pink;*/ +} + +/* CNN styling */ +#cnn{ + position: fixed; + width: calc(100% - 10px); + min-width: 420px; + height: 30px; + top:0; + left:0; + right: 0; + vertical-align: top; + padding: 5px; + font-size: 14px; + /*background-image: -webkit-gradient(linear, left top, left bottom, from(#02b38d), to(#07d2c4));*/ + background-image: -webkit-gradient(linear, left top, left bottom, from(yellow), to(gold)); +} +#cnn .button_container { + display: inline-block; + width: calc(50% - 20px); +} +button { + width:auto; + margin-right: 10px; +} +button:hover{ + cursor: pointer; +} +#sitemap button{ + position: absolute; + top:0; + right:0; + margin:5px; + padding:0; + color:blue; +} + +@media screen and (orientation: portrait){ + body { + margin-top: 80px; + /*background-color: yellow;*/ + } + #cnn { + width: calc(100% - 10px); + height: 65px; + font-size: 12px; + } + #cnn .button_container { + width: calc(100% - 100px); + } + button { + width: 100%; + margin-bottom: 5px; + } + #sitemap button { + width: 90px; + height: calc(100% - 12px); + } +} diff --git a/CNN/cnn.js b/CNN/cnn.js new file mode 100644 index 0000000..3ac438b --- /dev/null +++ b/CNN/cnn.js @@ -0,0 +1,91 @@ + const invocation = new XMLHttpRequest(); + const url = 'https://pad.xpub.nl/p/special_issue_8_cnn/export/txt'; + + function findCategories(data){ + // Return categories that this page is part of + // by doing a reversed dictionary lookup + var current_url = window.location.href; + var keys = Object.keys(data) + var categories = []; + for(var i=0; i 0 && categories.length < 2){ + //if we have only one category we add another one at random + var keys = Object.keys(data) + keys.splice(categories[1], 1) + categories.push(keys[Math.floor(Math.random() * keys.length)]) + } + + // Select a pseudo-randomised category for buttonA + var buttonA = categories[Math.floor(Math.random() * categories.length)]; + document.querySelectorAll('#buttonA button')[0].textContent = buttonA; + + // Select another category for buttonB + var indexA = categories.indexOf(buttonA); + categories.splice(indexA, 1); + var buttonB = categories[Math.floor(Math.random() * categories.length)]; + document.querySelectorAll('#buttonB button')[0].textContent = buttonB; + + // Insert a link for each button + // to another page of the same category + var linksA = data[buttonA]; + var linksB = data[buttonB]; + linksA.splice(window.location.href, 1); + linksB.splice(window.location.href, 1); + document.getElementById("buttonA").href = linksA[Math.floor(Math.random() * linksA.length)]; + document.getElementById("buttonB").href = linksB[Math.floor(Math.random() * linksB.length)]; + } + + function readEtherpadJSON(){ + if (invocation){ + invocation.open( "GET", url, true ); + invocation.onreadystatechange = function() { + console.log('ready!'); + if (invocation.readyState === 4 && invocation.status === 200) { + console.log('invocation:', invocation); + CategoryNetworkNavigator(invocation.responseText); + } + }; + invocation.send(); + } + return invocation; + } + + function insertCNN(cnn_template){ + document.body.innerHTML = cnn_template + document.body.innerHTML; + readEtherpadJSON(); + } + + var cnn_template = '\ + \ + \ + ' + + insertCNN(cnn_template); diff --git a/Images/.footer.html b/Images/.footer.html new file mode 100644 index 0000000..225cfa5 --- /dev/null +++ b/Images/.footer.html @@ -0,0 +1,23 @@ +
+
+

The Network We (de)Served, (XPUB Special Issue #08)
+ Thursday, 04 April 2019 at Varia

+ +
+

Dear guest,

+ +

We traveled from home to home by bicycle, setting up homeservers. As friends and companions on this Infrastructour, we studied our routers over drinks served by our hosts. Where possible we installed our servers in our homes, in other cases we had to depend on another member of the group. While self-hosting together we questioned our understandings of networks, autonomy, online publishing and social infrastructures, where each of us departed from a different question. We would like to share our personal (yet interconnected) routes with you, tell you a story, present our web- and printed zines, and invite you to explore our homebrewed network.

+ +

Date: Thursday, 04 April 2019
+ Location: Gouwstraat 3, Rotterdam
+ Entrance: Free
+ Start: 19:00

+ +

https://issue.xpub.nl/08/

+ +

Contributors: Simon Browne, Tancredi Di Giovanni, Paloma García, Rita Graça, Artemis Gryllaki, Pedro Så Couto, Biyi Wen, Bohye Woo, Roel Roscam Abbing, Manetta Berends, Lídia Pereira, André Castro, Aymeric Mansoux, Michael Murtaugh, Steve Rushton, Leslie Robbins.

+

Brought to you by the Master of Arts in Fine Art and Design: Experimental Publishing (XPUB) of the Piet Zwart Institute, and Varia, Centre for Everyday Technology, Rotterdam, April 2019.

+
+
+ + diff --git a/Images/.header.html b/Images/.header.html new file mode 100644 index 0000000..477a9fd --- /dev/null +++ b/Images/.header.html @@ -0,0 +1,50 @@ + + + + + + The Network We (de)Served - Special Issue #08 + + + +

Index of/ The Network We (de)Served

+
+ + + + diff --git a/Images/infrastructour/.footer.html b/Images/infrastructour/.footer.html new file mode 100644 index 0000000..225cfa5 --- /dev/null +++ b/Images/infrastructour/.footer.html @@ -0,0 +1,23 @@ +
+
+

The Network We (de)Served, (XPUB Special Issue #08)
+ Thursday, 04 April 2019 at Varia

+ +
+

Dear guest,

+ +

We traveled from home to home by bicycle, setting up homeservers. As friends and companions on this Infrastructour, we studied our routers over drinks served by our hosts. Where possible we installed our servers in our homes, in other cases we had to depend on another member of the group. While self-hosting together we questioned our understandings of networks, autonomy, online publishing and social infrastructures, where each of us departed from a different question. We would like to share our personal (yet interconnected) routes with you, tell you a story, present our web- and printed zines, and invite you to explore our homebrewed network.

+ +

Date: Thursday, 04 April 2019
+ Location: Gouwstraat 3, Rotterdam
+ Entrance: Free
+ Start: 19:00

+ +

https://issue.xpub.nl/08/

+ +

Contributors: Simon Browne, Tancredi Di Giovanni, Paloma García, Rita Graça, Artemis Gryllaki, Pedro Så Couto, Biyi Wen, Bohye Woo, Roel Roscam Abbing, Manetta Berends, Lídia Pereira, André Castro, Aymeric Mansoux, Michael Murtaugh, Steve Rushton, Leslie Robbins.

+

Brought to you by the Master of Arts in Fine Art and Design: Experimental Publishing (XPUB) of the Piet Zwart Institute, and Varia, Centre for Everyday Technology, Rotterdam, April 2019.

+
+
+ + diff --git a/Images/infrastructour/.header.html b/Images/infrastructour/.header.html new file mode 100644 index 0000000..477a9fd --- /dev/null +++ b/Images/infrastructour/.header.html @@ -0,0 +1,50 @@ + + + + + + The Network We (de)Served - Special Issue #08 + + + +

Index of/ The Network We (de)Served

+
+ + + + diff --git a/Images/infrastructour/INFRASTRUCTOUR-01.jpg b/Images/infrastructour/INFRASTRUCTOUR-01.jpg new file mode 100644 index 0000000..3dc017a Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-01.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-02.jpg b/Images/infrastructour/INFRASTRUCTOUR-02.jpg new file mode 100644 index 0000000..d7f9cb8 Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-02.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-03.jpg b/Images/infrastructour/INFRASTRUCTOUR-03.jpg new file mode 100644 index 0000000..4b4eb83 Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-03.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-04.jpg b/Images/infrastructour/INFRASTRUCTOUR-04.jpg new file mode 100644 index 0000000..34ca74b Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-04.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-05.jpg b/Images/infrastructour/INFRASTRUCTOUR-05.jpg new file mode 100644 index 0000000..804a0b4 Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-05.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-06.jpg b/Images/infrastructour/INFRASTRUCTOUR-06.jpg new file mode 100644 index 0000000..828e8c3 Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-06.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-07.jpg b/Images/infrastructour/INFRASTRUCTOUR-07.jpg new file mode 100644 index 0000000..3bbaa50 Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-07.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-09.jpg b/Images/infrastructour/INFRASTRUCTOUR-09.jpg new file mode 100644 index 0000000..7489689 Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-09.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-10.jpg b/Images/infrastructour/INFRASTRUCTOUR-10.jpg new file mode 100644 index 0000000..c21fe74 Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-10.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-11.jpg b/Images/infrastructour/INFRASTRUCTOUR-11.jpg new file mode 100644 index 0000000..9dac4cb Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-11.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-12.jpg b/Images/infrastructour/INFRASTRUCTOUR-12.jpg new file mode 100644 index 0000000..52a791d Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-12.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-13.jpg b/Images/infrastructour/INFRASTRUCTOUR-13.jpg new file mode 100644 index 0000000..b7b83ba Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-13.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-14.jpg b/Images/infrastructour/INFRASTRUCTOUR-14.jpg new file mode 100644 index 0000000..cecaaf6 Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-14.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-16.jpg b/Images/infrastructour/INFRASTRUCTOUR-16.jpg new file mode 100644 index 0000000..703116d Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-16.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-18.jpg b/Images/infrastructour/INFRASTRUCTOUR-18.jpg new file mode 100644 index 0000000..c264b9a Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-18.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-19.jpg b/Images/infrastructour/INFRASTRUCTOUR-19.jpg new file mode 100644 index 0000000..eb4b019 Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-19.jpg differ diff --git a/Images/infrastructour/INFRASTRUCTOUR-20.jpg b/Images/infrastructour/INFRASTRUCTOUR-20.jpg new file mode 100644 index 0000000..ecbf90c Binary files /dev/null and b/Images/infrastructour/INFRASTRUCTOUR-20.jpg differ diff --git a/Images/infrastructour/infrastructour_medium.gif b/Images/infrastructour/infrastructour_medium.gif new file mode 100644 index 0000000..d5957eb Binary files /dev/null and b/Images/infrastructour/infrastructour_medium.gif differ diff --git a/Introduction.html b/Introduction.html new file mode 100644 index 0000000..826fddd --- /dev/null +++ b/Introduction.html @@ -0,0 +1,92 @@ + + + + + + + The Network We (de)Served + + + + + + + + + +

The Network We (de)Served

+

Dear guest,

+

We traveled from home to home by bicycle, setting up homeservers. As friends and companions on this Infrastructour, we studied our routers over drinks served by our hosts. Where possible we installed our servers in our homes, in other cases we had to depend on another member of the group. While self-hosting together we questioned our understandings of networks, autonomy, online publishing and social infrastructures, where each of us departed from a different question. We would like to share our personal (yet interconnected) routes with you, tell you a story, present our web- and printed zines, and invite you to explore our homebrewed network.

+

Here are our accounts of this experience.

+

Artemis

+

My house was the starting point of the Infrastructour, and that made me the first host of the day. I have to admit I was quite nervous, waiting for the Xpub1 group to come to my place. I thought, if the first installation fails, it will bring bad luck to the group! Luckily, the router is located in the living room, so I was confident that there was space for everyone. I had to make sure that I had access to my router because this is the first essential thing for installing a web server. Surprisingly, the password had never changed from the default “1234”, so it was about time to change it
 Everyone started looking at my router’s interface. So many weird terms appeared: LAN, WAN, DHCP, Port Mapping, Port Forwarding and so on


+

Most of us felt awkward trying to understand how everything worked. Looking at each other with a question mark on our faces, we started discussing internet connections, the World Wide Web, nodes and paranodes of a network. After a series of questions and answers, and with each other’s help, we finally succeeded! My Raspberry Pi took on a new life and was ready to serve. It felt quite rewarding that we could finally continue the bike tour to the other houses for this installation journey. Our collective “suffering” while installing our servers and finally creating our small network helped me understand how difficult it is to “build” alternatives. Not only conceptually, but also materially. Even at an amateur and rudimentary level, it requires quite some dedication and time - and lots of discussions to educate each other.

+

In the end, why did we create a self-hosted network? Does what we publish through this network have a unique value, precisely because of our suffering on the very first day of its establishment? These questions came to my mind on that day and many more followed, most of which related to broader social aspects around digital networks. A lot of questions about privacy, inclusivity, safety and knowledge of technical tools, which I asked myself during this project, were maybe too big to answer directly. As a strategy, I thought it would be useful to gather them and write small texts expressing my thoughts. Trying to shape and share some of my ideas around these issues would hopefully help me find the “safe and cosy” online corner I was looking for. I wanted a place in which I could host my personal concerns about a frightening post-digital era.

+

Paloma

+

The Infrastructour recalls a time in which networks became more familiar to me. It took some time to understand what the meaning of having a server at home is. In the installation moment, I discovered new utilities of my router, my provider company, my server, etc. At this time, I felt that I started to have a better understanding of my network topology. We started the server installation process looking for the physical conditions that this small machine needed, such as direct connection with the router and power supply. Once we had solved these logistic issues we accessed the router (through the default password I had found some days before) via my laptop and we set the server configuration to make it work online. I also discovered that I was able to host another person’s server. Biyi’s server was hosted in my router for some weeks with a parallel structure to mine, so I was able to control the configuration of both our servers. I found myself in control of the conditions of the networks that others were in.

+

When we installed the server we configured the local and public IP (that we would identify later with a domain name) opening the proper ports to manage it. We had some problems originating in the lack of a user-friendly interface, and limited freedom provided by my Internet Service Provider. Once we managed to have a public IP for both of our servers we had created a public self-hosted node, so we were able to publish everything through the internet hosting it all ourselves.

+

At this moment I knew that I had the power to define my own rules and restrictions. I didn’t have any filter between me and my publication ambitions. I felt really responsible for the content that I could make public. We are used to utilizing external platforms to host our material. When we do that, we accept their rules and conditions but when we host it ourselves we are the ones establishing our own guidelines.

+

Our first networked activity was to build a ring between our servers. This action made me conscious of the real connection between the users of our network: Xpub1. Through this ring, we could navigate between our webpages, defining an interrelated connection among users.

+

In parallel, we started having a strong theoretical approach to network topology where I started to come across concepts like centralization, agency, link, virtual communities, bot, user, synergy and collaboration
 As an architect, I have worked with visual concepts that I’ve always found easier to assimilate and understand. So, I started to research networks that define the connections between users through visual material.

+

At this point, crowd-sourced and collaborative projects became a good mirror of graphic networks (and networks in general). There, one can find these concepts of centralization, rules and community-making having a real reflection in a graphic creation. In order to have an active experience of these types of networks, I thought of developing several drawing experiments with Xpub1, where the levels of agency, synergy, and centralization would change depending on who determines the users that are going to participate. Who establishes the rules, and how do the users interact? This experiment would help us understand which consequences influence the participant’s behavior if we transform the nature of their network, which is built on a specific project, and how these changes affect the visual collaboration.

+

Biyi

+

At the beginning of the Special Issue, we were asked to read an article by Brian Larkin, “The Politics and Poetics of Infrastructure”. The text approached the concept of infrastructure in a comprehensive and concrete way. Larkin described anthropological and ethnographical ways of interpreting infrastructure; amongst several anecdotes, some related to the description of goods and material provisions during Soviet and post-Soviet eras. This was particularly resonant with my daily experiences of China’s transition from a society organized in an infrastructure of planned, provisioned material flows, towards a society organized by the “invisible hand”. The introduction of this text in the beginning of the trimester expanded our interpretation of networks, in both its material and ideological aspects.

+

After the article paved the way for our expanded concept of infrastructure, we embarked on the Infrastructour. I was too late to subscribe to a new Internet provider upon moving into a new house, so I didn’t have a subscription until weeks later. Up to this time I was a “parasite”, hosted through Paloma’s router, as she had opened extra ports for me. Finally the router was delivered. A note in the box specified that I could only connect it after 18:00 on January 23rd to receive the earliest signal available on my street in Katendrecht. Located in the south of Rotterdam, it once was home to both the red light district and Chinatown. However, on the delivery day, there was no signal. Two KPN technicians were dispatched to fix the problem, despite my subscription being from Tele-2. They replaced the four-holed communications socket; problem solved! According to the setup menu, this particular four-holed connection is mostly found in houses built during the 1970s and 80s.

+

The following week I learned that, although Tele-2 was running the service, KPN covered the building and maintenance-specific parts of the infrastructure. During a brief repair, this inter-parasitic cohabitation of providers became visible to me as the customer.

+

The relationship between Tele-2 and KPN as two distinctive and connected ISPs triggerd my interest to explore network topology in practical reality. In my network publication, I used network as a writing strategy to recount narratives about institutions that present rigid and flexible qualities. I treat my writing content - public library, privately-held bookstores, printshops, and readers as nodes within a network, in context of a lived, social reality. The vivid narratives reveal network and nodes as entities elusive to define. Rather, it’s the inter-layering topological quality that defines their nature.

+

SIMON

+

On the way to my place a few of us stopped at Eudokiaplein to buy groceries for lunch. Although I already had things to make sandwiches at home, I picked up some hummus and corn chips for the group. When the rest of Xpub1 arrived I offered food, alongside what was becoming the repeated (now ritualistic) offer of “Coffee or tea?”. With a sandwich in one hand, I got down to the business of setting up my homeserver, as I configured my router through a browser.

+

Crammed between Roel and Biyi on a two-seater sofa, I opened ports in the firewall and forwarded them. The final test was to see if I could access the server from a remote terminal. However, this was proving impossible. I made a hotspot on my iPhone and connected my laptop to it 
 success! It turned out that I couldn’t access my server while connected to the local network; I had to fake going “outside” in order to get back “in”.

+

My homeserver is now in a temporary sublet, only about 10 minutes walk from where it was installed that day. I had to move it, along with the rest of my meagre possessions, after my housemate (the main tenant) gave notice, and the real estate agent said we all had to move out because the landlord wanted to sell the place. About a month after this I was sitting on the sofa in my pajamas, SSH-ing into the server, when I heard unfamiliar voices in the stairwell. The landlord had decided to relet the apartment and now a real estate agent was leading a parade of strangers through my living room in an unannounced house inspection. Since then, at share-house interviews I’ve had to ask if I could configure the router for my server; not a typical question, and one that raises a few eyebrows.

+

My research interests started with the Infrastructour and the visualisation task Artemis and I had of mapping dependencies. Together we tried to find a novel way to visualize the network that wasn’t just simply a generic method of connecting dots with straight lines. The term dependencies incorporates not only technical factors, such as physical access to a router, but also social dependencies, such as whether or not we had to ask anyone for permission to use the network. What we came up with in the end was a series of concentric circles; here, we imagined the dependencies almost like tiers on a wedding cake, or the wifi symbol multiplied for each section of our network.

+

Reading texts from the 1970s media activist group Radical Software revealed a connection between what we were looking at, and the radical approaches of people who were seeking to decentralize the broadcast television network. These included visualisations of alternative network topologies such as klein worms, weird shapes that could account for hidden complexities such as black holes, parts with other parts folded into themselves, or endlessly circulating links.

+

I’m interested in vision; not just what the eye sees, but also how it influences the way you think about things. Abstraction is useful in rendering complex ideas, but at the same time limits the sense of autonomy. I’m trying to understand networks and our place within them, and to visualise what seems hidden or invisible to me. I’ve collected readings and republished them online, taken photographs, written texts, and made sculptures and drawings, both by hand and by walking between our homeservers while GPS tracking myself. While walking I noticed other networks, represented by lines that were never straight - contrails in the sky, tram lines, and tyre tracks on bicycle paths throughout Rotterdam. The lines made by my GPS walking seemed to form knots - or nodes - where my location was obscured to satellites, somewhere up there, far above me.

+

TANCRE

+

So, we were riding our bikes to my place, the last one of the Infrastructour. Pedro and Rita couldn’t self-host their servers and their house is near mine. The idea was to set them up all together, connected to my router.

+

Once inside, it was easy to find my router; too easy. From the ground floor, exactly on the left side of the entrance, one can find the ethernet plug; from there I installed a long cable which runs along the side of the first flight of stairs, arriving at the first floor where it enters through the door of my apartment. The ethernet cable again sneaks snakily up the second flight of stairs arriving on the second floor, passing the corridor and entering into the main room where it finishes its two-floor journey to rest in the socket of my router. We all followed this cable until its end and we started the process of installing the servers. While Rita and I were using a Raspberry Pi as hardware for our server, Pedro was using another machine labelled TIZEN SUNXI ALLWINNER A20. He didn’t have the possibility to set it up through SSH and he needed a screen to connect his hardware via an HDMI cable, plus a mouse and a keyboard. Fortunately, I had all he needed and we were positive on the success of these last server configurations because my router was the same as Simon’s, so we already knew how to set it up and manage the port forwarding process. However, after a while we noticed Rita’s machine wasn’t recognised on the home page of the router (192.168.1.1), where you can check the connected devices and their respective IPs. The physical router went from being a nice looking and clean object, with a cable for the power and the snakey Ethernet cable, to resembling a nest of connected objects with multiple lights and colors. I had a power strip because I knew there was only one plug, and we needed another three to supply the power to our machines. My idea was to hide that nest in a box but actually it is still there. To find out what was the problem with Rita’s server we entered inside the tangled object with our eyes and our hands, and after we checked the connections we realized two of the five plugs for Ethernet cables (one was already taken by the two-storey long cable which enable the connection in the web) were labeled as TV while the other two where labeled as Internet, and that meant only two of the three servers could have been set.

+

Rita had to give up having her server hosted at my place and decided to set it up at Artemis’ instead. Apart from this problem related to the difference between routers, the day ended well. Some went home, others continued the tour in a pub with a beer, but this experience with technical problems and new techie terms was the beginning of an ongoing process in discovering the material and the digital aspects of a network that comprises hardware, software and physical dependencies. We started to understand how difficult it is to comprehend the complexity of it all. Even if the intention is to have something independent, we understood that independence requires a knowledge which is both technical and situated. In such circumstances one needs a teacher. There is not only the knowledge of how it works, but also the practical knowledge of how things work in particular circumstances. This was the starting point for my thoughts on autonomy and contingency. The idea of having your autonomy on the web – in our case through a server which is ‘ours’ – is always related to these particular conditions and to the fact that you need to operate hardware and software that will facilitate it
 one has to balance the two different worlds (autonomy vs contingency) and yet the notion of autonomy is based on both; knowing terms and physical structures, protocols and how to apply them by opening ports.

+

We can find a similarity between hosting a server and being hosts in our house; just as there are protocols in software there are protocols within homes, to open ports is to open doors. If one is only a client, one is homeless, or a guest in someone else’s house; on the other hand, if one has their own server, one becomes a host. Things that are normally separated come together. The distances collapse. This process is a passage from client to client-server.

+

PEDRO

+

I already knew we wouldn’t be going to my apartment during the Infrastructour. I guess that this might have been the main reason why I got a bit skeptical about this pursuit of online autonomy from the beginning. When I first came across this theme I had to immediately question myself. On the one hand, to what degree are we really autonomous while still relying on an internet provider? On the other hand, as I was not able to have a server hosted in my home, would the task be unaccomplishable even before it had begun?

+

A short contextualization on why I could not set my server up in the freshly renovated building where I live: I live in a 21-story building, where the internet is provided by one company to all the separate houses. I don’t have physical access to the main router and I only have a small access point installed in a reachable place. Even while calling the company in charge of this hardware I was not given the credentials to further explore, and neither did they have the time nor patience to explain to me how the system was put together. Long story short, I think that the community aspect of building this network was what made it possible to continue. I set my server in Tancredi’s place. His house was the last one on the tour, but from what I can recall, that didn’t even make our installation more successful or straightforward than any of the previous ones. We did not do it on the Infrastructour day with everyone there. I not only had different hardware from all the others but the software was also different. During the weekend, over beers and without time constraints, we finally did it: the server was online! I could not SSH into it directly but, in an ingenious way, we were able to create a bridge between both server and everything was sorted out. I could easily access it remotely. I think that this increased my autonomy as long as this small machine was still plugged in, and online. Practically however, I still relied on a second party.

+

From this point on, my main question became clear: what did it mean to be autonomous and what kind of examples were available which allowed me to see more directly how communities rely on self-hosting and how they manage it?

+

We were introduced to Mastodon, an online federated social network. I focused my research on this case study, where users can either be part of an instance or host their own. I researched within its universe to understand what kind of communities are present and why they moved there. Mastodon started being used by some because it provided a possible answer to issues of personal safety . We can take the example of marginalised communities (for instance LGBTQI+). Such groups used to rely on Twitter to discuss and share their points of view, but Twitter became filled with hate bots, trolls that skimmed through posts and at the first trigger word would react (in)discriminately. This made a few users leave Twitter, and Mastodon presented an alternative. When you create your own instance, you can state clear rules for what you imagine it to be. Some instances try to be open to everyone in order to provide a safe space. However, this also creates the necessity to draw clear lines between what one can and cannot do.

+

Bo

+

At the very beginning of the Special Issue #8, we had an Infrastructour, where we visited each other’s places in order to install our own server. I remember I was quite confused about what Roel and Manetta meant by installing the server, and how the procedure of installation would happen. I have to admit that I was quite clueless about networks, except for some basic knowledge on how to connect electronic devices to WIFI, or how to reboot a router.

+

Amongst the confusion, we had to choose a device for our own server in the meantime. There were single-board computers, such as several Raspberry Pi, a device that looked like a gameboy I had back when I was around 16 years old and a very old mini laptop. I had a lot of trouble deciding which device to choose. Although all of them seemed to work equally well, for some reason I was very serious about choosing the device that I would use for my server. Ridiculously enough, my laptop was broken at the time, and I was waiting for a new laptop to be delivered from Korea. Since we needed a screen to set up some configurations with the device immediately, I had no choice but to take the old mini laptop. Moreover, this meant that whenever I wanted to check if my device was working properly, I could do this conveniently. Because it has a screen, it’s possible to log in to the server on the mini-laptop directly. It was like killing two birds with one stone.

+

Even with a decent device, self-hosting my server at home proved to be difficult. The problem was that my building has a centralised network system which doesn’t give residents full access to routers in our homes. This meant that I couldn’t configure my router and I wouldn’t be able to install it at home, which made me very disappointed. Luckily, Artemis offered to let me “piggyback” on her router, which she now shares with other devices including hers, mine and Rita’s.

+

At this point, I began to question what dependency means within our network. Although we had planned to self-host our servers, sharing a router reduced my independence. To me, hosting your own personal server means that you should possess full control of it. However, I could already see some physical and social dependency problems. Some of these issues occurred not only from the limited accessibility of my router at home, but also from needing permission to use someone else’s router. For example, if Artemis doesn’t allow me to come over to her place to use my mini laptop with its convenient screen, then I have no physical access to it. What’s more, if she unplugs her router, or decides to move house and change the Internet service provider, technically my server will be “gone”.

+

Based on my experience, I started thinking about why I couldn’t do certain things, and how I managed to solve these problems. This led me to formulate research questions: What were the technical, physical and social limitations I had in installing my server? What does dependency mean in our network? How much are we dependent on our network? Who are we depending on within our network? There are different layers of dependencies in networks, so how do you define whether you’re independent, or not?

+

With Special Issue #8, I experienced self-hosting a network, documented dependency issues, and recorded my personal observations using an XMPP based publishing tool that I developed as a way to annotate these thoughts. The relationship between servers, XMPP, and the annotation tool are layered in interconnected dependencies, but with their own stories to tell.

+

RITA

+

Before going to everyone’s house, we had to make sure we had access to our router, with a username and password. In most cases, the service companies seemed to leave everything as it came (a very username:admin, password:1234 approach). However, that wasn’t my case. The building where I live is managed by one company, and somehow me having access to any credentials would compromise the safety of other residents. I cannot explain this argument very well, not only because I lack the IT knowledge, but also because when I contacted the company that maintains the routers they didn’t know what “host your own server” meant. It seems that being self-hosted is not an aspiration shared by most individuals.

+

This way, I had to rely on another person’s router (and goodwill) to host my own server. The first option was Tancredi’s place, but the lack of available network sockets was a constraint. I then became dependent on Artemis’ house. Biyi usually calls the people who rely on other people’s servers “the parasites”, which is quite a good analogy if we think how we are literally sucking resources from our host. Slowly we started to build our interdependencies inside the network.

+

Creating a network between ourselves, being nodes and sharing links with each other, seemed fascinating, but I wasn’t really sure what that actually meant, nor was I acquainted with the specific jargon. Exploring our routers was the first step to becoming autonomous by hosting ourselves and starting to build digital evidence of our network, but that wasn’t a linear process. All the difficulties and hardship grew on me and I started wondering if there was a future for decentralized, self-hosted networks. If we, as students of the subject, found it hard to manage all the specifications of setting up a server, how could this ever become a comfortable norm?

+

In this way, I also speculated on some inconveniences, such as what would happen if someone moved house, changed routers, became a dead node in the network, etc. The sustainability of our digital bond was the trigger point for my personal research.

+

I started to look at other networks, physical or digital, online and offline. I was searching for common paths they followed, how they dealt with some problems that we were also encountering and for how long these networks survived. Alongside this, I was prototyping some tools, small experiments to scrape links, images, sources, anything that could prove that our network had existed at some point in space and time. The documentation and anecdotes of other communities helped me understand better the focal point of my doubts: a digital network can indeed be fragile and ephemeral, but the cooperation aspect is important and I believe it can outlive the web version of a network.

+ + + + diff --git a/Research/.footer.html b/Research/.footer.html new file mode 100644 index 0000000..60c95cd --- /dev/null +++ b/Research/.footer.html @@ -0,0 +1,24 @@ + +
+
+

The Network We (de)Served, (XPUB Special Issue #08)
+ Thursday, 04 April 2019 at Varia

+ +
+

Dear guest,

+ +

We traveled from home to home by bicycle, setting up homeservers. As friends and companions on this Infrastructour, we studied our routers over drinks served by our hosts. Where possible we installed our servers in our homes, in other cases we had to depend on another member of the group. While self-hosting together we questioned our understandings of networks, autonomy, online publishing and social infrastructures, where each of us departed from a different question. We would like to share our personal (yet interconnected) routes with you, tell you a story, present our web- and printed zines, and invite you to explore our homebrewed network.

+ +

Date: Thursday, 04 April 2019
+ Location: Gouwstraat 3, Rotterdam
+ Entrance: Free
+ Start: 19:00

+ +

https://issue.xpub.nl/08/

+ +

Contributors: Simon Browne, Tancredi Di Giovanni, Paloma García, Rita Graça, Artemis Gryllaki, Pedro Så Couto, Biyi Wen, Bohye Woo, Roel Roscam Abbing, Manetta Berends, Lídia Pereira, André Castro, Aymeric Mansoux, Michael Murtaugh, Steve Rushton, Leslie Robbins.

+

Brought to you by the Master of Arts in Fine Art and Design: Experimental Publishing (XPUB) of the Piet Zwart Institute, and Varia, Centre for Everyday Technology, Rotterdam, April 2019.

+
+
+ + diff --git a/Research/.header.html b/Research/.header.html new file mode 100644 index 0000000..477a9fd --- /dev/null +++ b/Research/.header.html @@ -0,0 +1,50 @@ + + + + + + The Network We (de)Served - Special Issue #08 + + + +

Index of/ The Network We (de)Served

+
+ + + + diff --git a/Research/b-e-e-t.r-o-o-t.net/beetroot_to_ciao.html b/Research/b-e-e-t.r-o-o-t.net/beetroot_to_ciao.html new file mode 100644 index 0000000..e667af6 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/beetroot_to_ciao.html @@ -0,0 +1,76 @@ + + + + b-e-e-t.r-o-o-t.net to ciao.urca.tv, richfolks.club + + + + + +
+ +
+

The Seven Bridges of Königsberg

+ +

The Seven Bridges of Königsberg is a historical mathematical problem which laid the ground for graph theory, and prefigured topology. Königsberg, in former Prussia (now Kaliningrad, Russia), set on both sides of the Pregel River, included two large islands - Kneiphof and Lomse - which were connected to the mainland by a series of seven bridges. The problem was to devise a walk through the city crossing each of those bridges once and only once.

+ +

The negative solution came from Swiss mathematician and physicist Leonhard Euler, who pointed out that the choice of route inside each land mass was irrelevant; only the sequence of crossings mattered. Euler created a diagram in which each land mass was represented by a "vertex" or node, and each bridge became an "edge", or link between them. This allowed him to consider the problem in abstract terms, in the mathematical structure of a graph.

+ +

As only the connection information is relevant, the shape of the pictorial representations can be distorted in any way without changing the graph. For example, it does not matter if the links drawn are straight or curved, or whether a node is to the left or right of another.

+ +

Euler observed that, except at the beginning and end of the walk, if one enters a land mass by a bridge, one must leave a land mass by a bridge. In other words, at any time during the walk, the number of times one enters a non-terminal vertex equals the number of times one leaves it, meaning that the total number of bridges touching that land mass must be even, as half the crossings will be towards a land mass, and the other half away from it. However, all four of the land masses in the problem are touched by an odd number of bridges (one is touched by 5, the other three by 3). Since only two land masses can act as the beginning or end, it is impossible to cross each bridge only once during a walk.

+ +

Quipu

+ +

Quipu (also spelled khipu) or "talking knots", historically were cords with knots made in them as a way to record numbers, used by various ancient cultures of Andean South America.

+ + + + + + + +

Topology

+

Topology is a non-metric elastic geometry. It is concerned with transformations of shapes and properties such as nearness, inside and outside.

+ +

In mathematics, topology is concerned with the properties of space that are preserved under continuous deformations, such as stretching, crumpling and bending, but not tearing or gluing.

+
+
+
+
+

Unraveling knots

+

The drawings I've been making by tracking myself over GPS makes a kind of map; however it does not display scale, or landmarks, or street names. It doesn't show which way is north, south, east or west. What it does show is some kind of graphic representation of the path I took by following my nose.

+

After I return back to the studio at Wijnhaven 61, I save and export .gpx (GPS exchange) format, and then drop the files into geographic information system software which allows me to accurately position the paths, representing them as lines.

+

Later, I export a line to .svg (scalable vector graphics) format and start to zoom in on it using a vector graphics editor. When zoomed out, the line appears to be curved, jagged, definitely not straight. However, when zooming in there are many straight lines, and they only bend at anchor points where each snapshot is taken. The line becomes knotted at places, representing social interactions, financial transactions, places where I backtracked, and where the GPS signal was obscured within or deflected by buildings in the urban landscape.

+ + + + + + +

I begin to simplify the line, sliding a scale that removes anchor points and unravels the knot into a completely straight line. As I do this, I notice smaller knots that were not so visible at the scale I saw them at originally.

+ + + + + + +
+ + + + + + +
+ + + + + +
+
+
+ + + \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/beetroot_to_p_lions_es.html b/Research/b-e-e-t.r-o-o-t.net/beetroot_to_p_lions_es.html new file mode 100644 index 0000000..b53cea2 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/beetroot_to_p_lions_es.html @@ -0,0 +1,77 @@ + + + + b-e-e-t.r-o-o-t.net to p.lions.es + + + + + +
+ +
+

1867

+

A note from Peter Guthrie Tait scribbled on an envelope asks an unknown recipient "Can't you come on Monday the present at the performance? An elliptical hole gives the rings in a state of vibration!!!"

+ +

In a room, thick with smoke, Tait and William Thomson (Lord Kelvin) are conducting an experiment to test the German physicist Hermann von Helmholtz's theory, that closed vortex lines in a fluid remain stable forever. Tait is using a box that emits smoke made from a pungent mixture of ammonia solution, salt and sulfuric acid. He taps the back of his makeshift vortex cannon, and thick rings he later describes as looking "like solid rings of India rubber" waft from a hole drilled in its front. Thomson develops a theory that each smoke ring is structured around knots in the ether, a space-filling substance believed to transmit matter. Consequently, Tait begins to tabulate possible forms of mathematical knots.

+ +

Klein worms

+

Klein worms are forms based on the klein bottle, drawn by the artist Claude Ponsot. These drawings made their first appearance as illustrations for the article Cybernetic Guerilla Warfare, by Paul Ryan (not the politician) in Radical Software, Vol.1, Issue 3.

+ + + +

Knotworks

+

Knotworks are visualisations of the topology of our small, humble digital network of eight home servers. Each Knotwork drawing is of a mathematical knot with eight crossings, each crossing representing a node of the network. The points where the parts of the loop overlap can conceal parts contained, like the internal sections of klein worms. When unravelled, the knot is a continuous loop, and the links (edges) and the nodes (vertices) are the same.

+ + + +
+ + + + + +
+ + +

Mathematical knots

+

Mathematical knots, or knots which are studied in the field of knot theory, are based on the embedding of a circle within three-dimensional space. They are different from the usual idea of a knot, that is a string with free ends. Therefore, mathematical knots are (almost) always considered to be closed loops.

+ + +

Network of possibilities

+

I'm making visualisations of things that are not visible in order to better understand them myself. The methodology I'm adopting is one based on a mind-map network, where possible ways to reflect are connected to subsequent actions and outcomes that infinitely loop into each other. In this way, an idea becomes an action (drawing, walking, sculpting, writing), which becomes an outcome (hand-drawing, GPS drawing, clay model, narrative text, explicatory text, and so on), then can become a new idea from which to act again, etcetera etcetera...

+ + + + + + + +

Network topology

+

Network topology is the arrangement of the elements (links, nodes, etc.) of a communication network. Network topology can be used to define or describe the arrangement of various types of telecommunication networks, including command and control radio networks, industrial fieldbusses, and computer networks.

+ + +

There are two types of network topologies: physical and logical. Physical topology emphasizes the physical layout of the connected devices and nodes, while logical topology focuses on the pattern of data transfer between network nodes.

+ + + + + + + + + + + + + + + + + + +
+
+ + + \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/beetroot_to_wijnhaven.html b/Research/b-e-e-t.r-o-o-t.net/beetroot_to_wijnhaven.html new file mode 100644 index 0000000..9c4119a --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/beetroot_to_wijnhaven.html @@ -0,0 +1,56 @@ + + + + b-e-e-t.r-o-o-t.net to wijnhaven + + + + + +
+ +
+

Chemtrail conspiracy theory

+

The chemtrail conspiracy theory is an erroneous belief that the contrails of aircraft are being used for nefarious purposes, ranging from altering the weather to mind control. Chemtrail (a portmanteau of "chemical" and "contrail") sightings are often reported on the r/chemtrails sub-messageboard of reddit.com, a discussion website which claims to be "the front page of the internet".

+

On this sub-messageboard, under a section titled "Definitions & such" is the short description of a chemtrail as +

...a visible trail left in the sky by an aircraft and believed by some to consist of chemical or biological agents released as part of a covert operation.

+

Following this is a short text that encourages both believers and skeptics to adopt a tolerant attitude towards each other with a disclaimer:

+

+

All viewpoints are allowed here, but please be respectful. This is a subreddit, not a court of law. Please don't badger users into providing concrete evidence. Short of flying us all up into the atmosphere with an evidence bag, you probably won't get it.

+ +

Contingency

+

A contingency plan; a fall-back, a plan B, what to do if all else fails.

+

Contingencies are incidental to something else, dependent on chance, possible, conditional, but usually unnecessary.

+

A contingency is what something allows or doesn’t allow one to do, constrained by its form and use. For example, a pen is often used to write with, but also has the contingency of being used for other purposes, such as scratching one’s back.

+ +

Contrails

+

Contrails (a portmanteau of "condensation" and "trail") are the line-shaped clouds left behind by the engines of aircraft flying at low altitude. They are commonly composed of water in the form of ice crystals.

+
+
+
+
+
+
+ +

Temporary Autonomous Zone (TAZ)

+ +

THE CONCEPT OF THE TAZ arises first out of a critique of Revolution, and an appreciation of the Insurrection. The former labels the latter a failure; but for us uprising represents a far more interesting possibility, from the standard of a psychology of liberation, than all the "successful" revolutions of bourgeoisie, communists, fascists, etc.

+ + +

The second generating force behind the TAZ springs from the historical development I call "the closure of the map." The last bit of Earth unclaimed by any nation-state was eaten up in 1899. Ours is the first century without terra incognita, without a frontier. Nationality is the highest principle of world governance--not one speck of rock in the South Seas can be left open, not one remote valley, not even the Moon and planets. This is the apotheosis of "territorial gangsterism." Not one square inch of Earth goes unpoliced or untaxed...in theory.

+ +

The "map" is a political abstract grid, a gigantic con enforced by the carrot/stick conditioning of the "Expert" State, until for most of us the map becomes the territory--no longer "Turtle Island," but "the USA." And yet because the map is an abstraction it cannot cover Earth with 1:1 accuracy. Within the fractal complexities of actual geography the map can see only dimensional grids. Hidden enfolded immensities escape the measuring rod. The map is not accurate; the map cannot be accurate.

+ +

So--Revolution is closed, but insurgency is open. For the time being we concentrate our force on temporary "power surges," avoiding all entanglements with "permanent solutions."

+ + +

And--the map is closed, but the autonomous zone is open. Metaphorically it unfolds within the fractal dimensions invisible to the cartography of Control. And here we should introduce the concept of psychotopology (and -topography) as an alternative "science" to that of the State's surveying and mapmaking and "psychic imperialism." Only psychotopography can draw 1:1 maps of reality because only the human mind provides sufficient complexity to model the real. But a 1:1 map cannot "control" its territory because it is virtually identical with its territory. It can only be used to suggest, in a sense gesture towards, certain features. We are looking for "spaces" (geographic, social, cultural, imaginal) with potential to flower as autonomous zones--and we are looking for times in which these spaces are relatively open, either through neglect on the part of the State or because they have somehow escaped notice by the mapmakers, or for whatever reason. Psychotopology is the art of dowsing for potential TAZs.

+ +

— Hakim Bey, excerpt from 'The Psychotopology of Everyday Life', T.A.Z, The Temporary Autonomous Zone, 1991.

+ +
+ +
+ + + \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/beyond_the_internet_and_all_control_diagrams.html b/Research/b-e-e-t.r-o-o-t.net/beyond_the_internet_and_all_control_diagrams.html new file mode 100644 index 0000000..f5a3f74 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/beyond_the_internet_and_all_control_diagrams.html @@ -0,0 +1,91 @@ + + + + beyond the internet and all control diagrams + + + + + +
+
+

Beyond the Internet and All Control Diagrams
+ Simone Browne and Zach Blas, January 24, 2017

+ —
+ Conversation between Simone Browne and Zach Blas
+ First published at https://thenewinquiry.com/beyond-the-internet-and-all-control-diagrams/

+

In an exclusive conversation, surveillance scholar Simone Browne and artist Zach Blas critique various forms of “control diagrams” and imagine a new commons in the space between the Internet’s network nodes.

+

+

+ Contra-Internet Inversion Practice #3: Modeling Paranodal Space(2016) +
+
+

+
+

SIMONE BROWNE: The soundtrack for artist Zach Blas's “Contra-Internet Inversion Practice #3: Modeling Paranodal Space” comes from the corresponding title track to Joe Meek and The Blue Men’s 1960 concept album “I Hear a New World.” The album, Meek wrote, is a deliberately “strange record” meant to conjure up rockets, science fiction, moon landings before the first moon landing, as well as “whatever could be up there in outer space.”

+

Zach, there's a lot that you are questioning and putting forward in this piece: The disappearance of the Internet, the killability of the Internet, Paul Preciado’s queer concept of constrasexuality, dildotectronics, and a commons to come.

+

Tell me, Zach, what motivations went into creating the project? Why Meek and The Blue Men’s “I Hear a New World,” and how will the contra-Internet get us to the commons to come?

+

ZACH BLAS: Contra-Internet is a series of artworks and writings that I've been working on since 2014. Spanning video, sculpture, installation, and performance, the project confronts the Internet as an instrument for control, state oppression, and accelerated capitalism. Over the next several months, I will be focusing on a video centerpiece, which turns to alternative Internet infrastructures that activists are building around the world. In September 2017, Contra-Internet will premiere as a solo exhibition at Gasworks in London. That said, I began the Contra-Internet project with two questions to use as a basis for research and experimentation:

+

1) When and how did the Internet transition from a site of immense political potentiality to a premiere arena of control, surveillance, and hegemony?

+

2) Why is it so difficult to conceive of an alternative or outside to the Internet today?

+

It seems, on the one hand, that the Internet operates as a kind of totalized condition, constricting what is possible for communicating, gathering, and being together. Popular concepts like “post-Internet” propagate this sentiment: there can no longer be an outside to the Internet when it has already seeped into the very material fabric of contemporary existence. Prophecies of the Internet of things to come promise to secure this understanding of the Internet, as the world itself and the Internet become more and more indistinguishable. That said, it strikes me as queer to desire to fracture this Internet totality, and in fact, on the other hand, many people around the world are practically doing just that, by building infrastructural alternatives to the Internet. These include certain tools that activists, hackers, and artists around the world are building to avoid control and surveillance. Mesh-networking is a powerful example to consider, as it is a networking technology that can function autonomously with no reliance on “the Internet,” and has been used to constitute political alternatives in cities such as Detroit, New York, and Hong Kong.

+

A working definition of contra-Internet is the refusal of Internet totality, but this is not a simple outright refusal. Rather, it is a refusal of naturalizations, hegemonies, and normalizations of the Internet that have contributed to its transformation into a locus of policing and control. Supplementing this, contra-Internet is also the search for and constitution of Internet alternatives. I consider the examples of alternative infrastructure outlined above as crucial to the contra-Internet because they reveal that social movements no longer necessarily see the Internet as a political horizon;it is, rather, about finding something else. This is where the commons comes in for me, as a collective and open project of thinking, imagining, and building something other than “the Internet.” From an artistic perspective, I would like Contra-Internet to give a particularly queer consistency to this activity, and this must happen not only by documenting Internet alternatives but also by imagining beyond the network form itself.

+

+

+ Contra-Internet Inversion Practice #1: Constituting an Outside (Utopian Plagiarism) (2015) +
+
+

+
+

I have developed this idea through a reworking of Paul Preciado’s theory of the contrasexual, which takes a similar aim at sexuality. This move from the contrasexual to the contra-Internet could be described as “utopian plagiarism,” which Critical Art Ensemble theorized about in the early 1990s (and which I learned from one of the most important art mentors in my life, Ricardo Dominguez). At the beginnings of a recombinant Internet culture, Critical Art Ensemble imagined a networked mode of sharing and re-writings ideas;of remixing words and images to unearth alternate and minor meanings. This is exactly what the group did with one of their most well known concepts;electronic civil disobedience, created by taking Thoreau’s theory of civil disobedience and making it electronic. That said, it’s not that contra-Internet is a radical break with the contrasexual; rather, it extends that concept and (hopefully) makes something new comprehensible. While Preciado has been a starting point, I’ve been quite interested in developing the broader conceptual framework for Contra-Internet out of queer, feminist, and minoritarian thinkers and artists, be it J. K. Gibson-Graham’s post capitalist politics, Le Tigre’s “Get Off the Internet,” or Stefano Harney and Fred Moten’s undercommons. I believe their teachings in seeking alternatives to domination and control are of the utmost importance here.

+

This brings me to the video you mentioned. I am in the process of making a series of videos for the Contra-Internet project that take place within the technical, material, and visual confines of the computer;namely, the computer I use, a Mac laptop. These videos are kind of like performances, and they attempt to invert the logics, visualities, and software environments that condition and delimit the discursive, practical, and creative possibilities of the computer (again, this idea of inversion comes from Preciado’s “inversion practices” in his Manifiesto Contrasexual). The computer is a starting point for writing, imagining, and experimenting, so it felt crucial to literally start here. In the Contra-Internet Inversion Practice #3, I am exploring what going beyond the network form looks like.

+

To do this, I import a well-known distributed network diagram;the kind of network diagram often used to describe the Internet;into three-dimensional modeling software, and then I peel away and discard the network diagram, in order to liberate the space bound to this configuration. Several ideas led me to this point: I have been very taken by Preciado’s figuring of the dildo. For Preciado, the dildo is a diagrammatic form that can practically guide us to contrasexuality (the dildo is not a phallus or anything patriarchal for Preciado). My question here is what might the dildotectonics of the Internet be? What I mean by this is: if the dildo is an adequate form to unleash contrasexuality, what form might this be for the Internet? Certainly not the network form.

+
+ paranode-line +

Paul Baran’s 1964 distributed network diagram, with a paranode pointed out.

+
+

In a recent conversation with digital humanities scholar David Berry, media theorist Alexander Galloway proposes the idea of reticular pessimism, a criticism of the network as a dominant model for interpreting reality. Galloway suggests that today the network, through its dominance and ubiquity, forecloses the utopian. The reticular pessimist is unable to grasp the world and its potentialities as something other than a network. As an artist, this is a provocative claim to consider, as it evokes the beyond-or-other-than the network as something needed but not yet fully known. For me, this is where imagination must begin. Network theorist Ulises Ali Mejias offers the second step to this disavowal of the network form through his concept of the paranode, which he explains as the space that networks leave out or exclude. I have a hunch that the paranodal is one way to conceptualize the dildotectonics of the Internet. I am attracted to the concept of the paranode because it calls forth at least two militancies: the practical work of building infrastructural alternatives (which are often still network alternatives), and the intellectual or artistic task of making comprehensible and imaginable that which is beyond the network form.

+
+ Contra-Internet Inversion Practice #2: Social Media Exodus (Response) (2015) +
+
+

+
+

So finally, the 1960 song “I Hear a New World” by Joe Meek, which plays throughout Practice #3. It has always struck me as inextricably queer, before I even knew anything about Meek. Of course, there’s his biography: a gifted sound engineer and closeted gay man whose life ended in suicide and murder (he lived in England during a time when homosexuality was illegal, and he also killed the proprietor of his flat before he shot himself). The song;and the entire album it appears on;is an outer space fantasy, one that projects outer space, it can be inferred, as outside the confines of Meek’s difficult present on Earth. The music is futuristic, a bit kitschy, haunting, and filled with desire. It’s the anticipation and longing that really gets me in this song;the hearing a new world but not yet there, sensing something to come and not fully knowing what that might be; being attuned to another possibility. When the paranodal space is freed from the distributed network diagram, all of these sentiments and feelings of Meek’s music really coming rushing in for me. It is Meek’s haunting of the paranodal space that makes me hear a new (queer) world.

+

SIMONE BROWNE: I also want to talk about a different project of yours, Face Cages, as another moment of this queer consistency. But first a primer of sorts on biometric technology, which we can think of as code or a set of instructions employed to render the body, living or otherwise, as machine-readable information.

+

This information; abstracted from parts, pieces, and performances of the human body;is then put to work in the service of consumer products, military applications, gaming, identification documents, and a variety of other applications. Biometrics, as the computational representation of the physical, behavioral and, more recently, affective attributes of the human body, are of many types: the face, for example, and the variety of biometric modalities when it comes to measures of the human face, including retina scans, iris recognition, thermal face imaging that detects the supposedly unique emission of a face’s heat pattern, and the more commonly known facial recognition that measures the spacing between the eyes, nose bridge, and more.

+

Your video installation Face Cages is the structure of the algorithm rendered as a three-dimensional metal cage. Face Cages makes it so that facial recognition can’t be reconciled with its use to ban, expel, and account for certain humans’ bodies at borders, in prisons, or on kill lists. I see the metal work of the cages and I think of iron masks, copper fastenings, and metal collars used to torture, gag, muzzle, and restrict enslaved people when it came to breathing, speaking, eating, or escape.

+
+

Featuring Elle Mehrmand, micha cĂĄrdenas, Paul Mpagi Sepuya, and ZACH BLAS:

+

+

face-cage-2-elle-mehrmand_portrait-383x575

+

face-cage-3-micha-cardenas_portrait-383x575

+

face-cage-4-paul-mpagi-sepuya_portrait-383x575

+
+
+

This was especially so when I watched the four Face Cages performance videos. The masks restricted their breathing so that it was labored. Each inhalation seemed tense. Each exhalation the same. You call this installation an endurance performance, and for me it brings to mind Frantz Fanon’s “combat breathing” that he writes about in A Dying Colonialism. The suffocating corporeal effect of colonization is one felt not only at the site of occupied territory, but experienced physiologically on the body as what Fanon names “occupied breathing.” It is respiration as unfreedom.

+

The endurance performance work of Face Cages is that of four queer artists: you, micha cĂĄrdenas, Elle Mehrmand and Paul Mpagi Sepuya. This makes the endurance work of Face Cages a form of combat breathing in an anti-queer and transantagonistic world, where biometric technologies enable the growth of mass surveillance and incarceration. How do you see Face Cages and your other projects that critique surveillance technologies as part of an anti-colonial queer politics? As undoing, in some way, enduring and ongoing settler-colonial structures?

+

ZACH BLAS: The central question of Face Cages is how to intensify and dramatize the violence of biometrics. I chose to pursue this through the ways in which biometrics abstracts bodies. This can technically (and I would argue politically) be defined as capture. I particularly focused on the landmark plotting technique of facial analysis that generates bright, colorful, and minimal geometries over the face, as a seemingly perfect calculation of its specificities. I call this a biometric diagram, and it appears as something primarily aesthetic. Yet, I wanted to make explicit that this diagram is not just an aesthetic abstraction of body parts but also a diagram of control. Thus, I think of the biometric diagram in a Foucauldian sense, as a mapping of power. While Foucault once described the panopticon as a major diagram of power in the 19th century, I think biometrics is one of today’s major control diagrams. As such, this mode of abstraction has an explicit and unavoidable collusion with global surveillance and the prison-industrial complex.

+

I decided to recast the biometric diagram as a face cage, which I developed from the writings of feminist communications scholar Shoshana Amielle Magnet, particularly her phrase “a cage of information.” Biometrics as a cage of information highlights two key points: 1) criminalization, as biometrics are first and foremost a technology of policing, and 2) disembodiment, because biometrics promotes a conception of identity that can be digitally extracted from the surface of the body. Alternately, embodiment, as media theorist Kate Hayles has argued, is not algorithmic.

+

In Face Cages, I took four biometric diagrams of faces and transformed them into metal cages. Metal intensifies, makes heavy, the physicality of biometric abstraction, but I also used metal to evoke aspects of policing, such as handcuffs and prison bars. I worked with three other artists that currently embody those persons most vulnerable to biometric scrutiny, in terms of ethnicity, gender, nationality, and race. I produced face cages based on our biometric data, and when worn, they were remarkably ill-fitting, causing pain and discomfort, even though these cages should sit perfectly on the surfaces of our faces, following biometric logic.

+

We wore our face cages in endurance performances for a video camera;the prompt being to wear it until you can’t bear it any longer. These performances aim to dramatize the struggle between embodiment and biometric capture, as algorithmic abstraction becomes hyperphysical, felt, violent, painful, and endured over time. Your reference to Fanon’s occupied breathing is important here, because I made Face Cages with the hope that it would expose the long histories of colonialism, oppression, and violence inherent to biometric technologies. Here, I am reminded of another concept I’m doing much work around these days, which is feminist science and technology studies scholar Donna Haraway’s “informatics of domination.” In her “A Cyborg Manifesto,” Haraway renames white capitalist patriarchy as the informatics of domination, “the scary new networks” that are united by “a common move;the translation of the world into a problem of coding.” I think one of the goals of recent queer, feminist, anti-racist, and anti-colonial work that addresses technologies like biometrics (in critical writing and the arts) is to demonstrate how this informatic coding impacts, violates, and destroys minoritarian lives.

+

Another way to explain how biometric capture dominates is through its attempt to obliterate opacity. For a number of years, I have been taken with opacity as an anti-biometric concept that is particularly sensitive to minoritarian lives;I am even currently writing a book titled Informatic Opacity. I have developed such an idea from the writings of Édouard Glissant, whose claim that we must “clamor for the right to opacity for everyone” could be a slogan for biometric times. Opacity, for Glissant, is a vast and robust conception; it is at once ethical, political, aesthetic, and even ontological. Glissant gives us multiple definitions and tendencies to consider: “Opaqueness is a positive value to be opposed to any pseudo-humanist attempt to reduce us to the scale of some universal model” (how can this not bring to mind biometrics?); “that which protects the Diverse we call opacity”; and he even states that opacity is the very aesthetics of the Other. Glissant also distinguishes opacity from difference, in that opacity exceeds the terrain of identity and identification.

+

With a concept like “informatic opacity,” I am interested in addressing how current struggles for opacity must confront both humans and machines. I explored what informatic opacity might look like in an earlier artwork titled Facial Weaponization Suite, in which I produced masks in public workshops based on the aggregated facial data of participants. The resultant masks;a kind of collectivization of the face in data;could not be detected by biometric facial recognition technologies as human faces. Of course, there are other theoretical ways to think about opacity today: queerness as a mode of escape, feminist imperceptibility, black fugitivity, or your own term “dark sousveillance” all come to mind.

+

SIMONE BROWNE: Yes, there is a part in Glissant’s “For Opacity” from his Poetics of Relation where he argues that Western thought’s pathological demand for understanding is underwritten by hierarchies and a “requirement of transparency.” He writes that “in order to understand and thus accept you, I have to measure your solidity with the ideal scale providing me with grounds to make comparisons and, perhaps, judgments, I have to reduce.” He says we must do away with “the scale.”

+

There’s another part of Poetics of Relation that I want to cite here, as a way to close. It comes from its beginnings, “The Open Boat.” In it, Glissant speaks of metal balls and chains gone green and what the slave ship left in its wake. Importantly, he reminds us of why we must stay with poetry, with that which expresses our freedoms. So I like that you mentioned earlier about being attuned to another possibility. I think that’s what Glissant left us with: the places, the means and the ways to start looking for other possibilities.

+

ZACH BLAS: Thanks for highlighting Glissant’s critique of scale, which, as you indicate, can also refer to measure, classification, and categorization. I am currently at work on a few new art projects that continue to expand upon the political implications of algorithmic measure in capture technologies and security apparatuses.

+

I have returned to biometrics after recently moving to the United Kingdom. Before arriving in London, I had to apply for a “Biometric Residence Permit,” which is the U.K.’s official title for a work visa, and as part of this process I was required to attend a “biometric enrolment” appointment where facial and fingerprint data is gathered. When I received the online notification to attend my biometrics appointment in New York City, I was struck by the following statement:

+

“This is either to submit documentation and/or to enable us to collect your biometrics, unless you are bio-exempt.”

+

Unless you are bio-exempt. What a fantastical, impossible category! If you look through more official literature on biometrics from the U.K. Home Office, you will come to understand that children and amputees with one or no fingers are bio-exempt, but so are diplomats. If you look more closely at this material, you will also notice that Home Office alternates between the term bio-exempt and the phrase “exempt from control.” I am finding my way towards a work titled bio-exempt, in which I aim to pull out all the term’s various meanings, such as biopolitical control: who has the legal right to be exempt from their embodied self and who has the right to remain unmarked, not indexed.

+

In a second work titled The Prison-House, I am reimagining Fredric Jameson’s conception of “the prison-house of language” as the prison-house of capture. I want to ask something like: If the architectural diagrams of Jeremy Bentham’s panopticon broadly mapped power and discipline in a previous era, what might such architectural renderings and diagrams look like now? To do this, I am making a series of immersive installations that collapse secret interrogation rooms, torture chambers, and the world of machine vision into one another to dramatize a “prison-house” structure that is mobile, flexible, often invisible, and thoroughly informatic.

+

Lastly, and perhaps of a more utopian sentiment, I’m working on a video-essay and installation (and collective!) titled The Outside. The outside is an idea often evoked today by speculative realism, object-oriented ontology, and others focused on the nonhuman turn, united in their pursuit to get out of what philosopher Quentin Meillassoux describes as the correlationist trap, that is, understanding the world only through the human ability to grasp, know, and perceive it. The other side of correlationism, Meillassoux proclaims, is “the great outdoors.” Yet, there is a more minoritarian outside that has been operative and at work before the rise of these other intellectual endeavors, oriented towards undoing totalities, making alternatives, and combating domination. Consider “the black outdoors,” discussed by Fred Moten and Saidiya Hartman; the post-capitalist politics in J. K. Gibson-Graham’s writings, when they argue that there is an outside to capitalism; Donna Haraway’s insistence that “there might indeed be a feminist science”; or even George Michael’s sex-positive “Outside,” when he sings, “Let’s go outside.” This outside might return us to Glissant again; as a way of being outside of “the ideal scale,” which goes to your point Simone, about freedoms and relation.

+

+

+

George Michael – “Outside”

+
+ +
+
+ + + \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/biotopology.html b/Research/b-e-e-t.r-o-o-t.net/biotopology.html new file mode 100644 index 0000000..35335cd --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/biotopology.html @@ -0,0 +1,104 @@ + + + + biotopology + + + + + +
+
+

Biotopology 1972
+ Warren Brodey

+ —
+ First published in Radical Software, Volume 1, Issue no. 4, pp 4-7, 1970
+
+

The following are excerpts from

+

1) a manuscript/letter recently received from Warren Brodey on the topology of klein form systems and

+

2) a transcription of the audio portion of a two-hour video tape made by Andy Mann and Darcy Umstedter in which Warren relates klein form systems to bioptemes (biological optimizing systems) and contrasts these with mechy max (mechanical maximizing systems) which he thinks predominates in the mismanagement of the earth's ecology in ignorance or disregard of context [the extent to which all things (systems) are related].

+
+

TOPOLOGY is a non-metric elastic geometry. It is concerned with transformation of shapes and properties such as nearness, inside and outside. (Paul Ryan, Radical Software 3).

+

Compare the kind of space people are in who ask "Do you follow my line of reasoning?" and the space of those who ask, "Can you get into the space I am in?"

+

"Can you get into the space I am in" means asking the other people to loop through your style, your information arrangements, your habits, your epistemology, your language, and how you deal with the unanticipated.

+

Infolding: Imagine working through into depths with the help of a media that provides instantaneous feedback and thereby allows infolding with time, memory, energy, relation, no longer in the image of print. "Do you follow my line of reasoning?"

+

I am not a TV freak. I am a person engaged with a group in synthesizing actual plastic materials that use the ecothink in their working. The going is slow but the space is now clear in my head. We taped a discussion — each of us trying to catch what we thought had meaning, I might catch your face when you registered surprise at what your hands had just built. On the next infolding we would discuss what you expected and your surprise. We would use the TV to penetrate in depth the experience even as it happened and to penetrate the experience of the experience—the meta experience.

+

Paul talked about this in the last issue of Radical Software:

+ +
+

Taping something new with yourself is a part uncontained

+

To replay the tape for yourself is to contain it in your perceptual system

+

Taping yourself playing with the replay is to contain both on a new tape

+

To replay for oneself tape of self with tape of self is to contain that process in a new dimension

+

Parts left out of that process are parts uncontained

+

All of this is mapable on computer graphic terminals!

+
+

Infolding as it is described by many creators of Radical Software is really a radical, a powerful, a timely, and a materially significant happening. It takes us into a new space. Some of the readers, particularly Paul, would look at the tape we were making if this were an infolding session and show me my stubbornness in not seeing what they were telling me a year ago or more. But our group has been working in the same space with different media in hand — a responsive touch media instead of a visual one. Our child has asked for its launching. It is a frail being, almost unborn...

+

Now I would like you to take the trip into our space...

+

Do you anticipate enough value in this trip to sacrifice a sock of a stocking... for the sake of finding a way to stream through our new space? Do you? If you do peel off a stocking and move with me.

+

We can make a simple, soft klein bottle or klein form, and it will provide us with a simplex with which to synthesize complex structures which are "lively" — like living structures.

+
+ + + + + + +
+

First, cut the toe out of a stocking, stretch hose is better. Cut a slit near the knee; make it about the diameter of the toe. Fold the stocking over back on itself; put the toe in through the slit. Pull the toe free edge through (but not all the way through) til the free edge at the toe and thigh are adjacent. Now get a needle and thread. Sew the slit to the stocking coming through it. Sew the toe free edge to the thigh free edge. (See diagram of klein form)

+

Reach down the double tube. Your hand will go down the contained tube (what was the toe) through the slit to where it is uncontained and then around into the containing space between the toe and the thigh of the garment.

+

We are in very different territory.

+

In the past you started out with points; points went to lines; lines swept a surface in two dimensional forms. When you went to three dimensional forms the first form was a sphere, because that's the simplest; then from a sphere [you can make a hole in a sphere and stretch the sphere out (as far as topology is concerned, you're allowed to stretch everything)] you went to a donut; a donut to be a donut had to have a hole in the middle, and you could stretch it as much as you wanted but it still had a hole in it.

+

The klein form is different. There's no inside; there's no outside. Instead you have a contained tube and an uncontained tube, a contained hole and an uncontained hole from which you can make interlocking klein forms in a chain ... Any part of the form can touch, contact, communicate with, flow with any other part, and the parts, the whole, in time flow through each other in a way the donut and sphere cannot. We have a quality of continuousness in the form and at the same time intracontainment or infolding; we have intrinsic to the form identifiable relationships that are not diadic (inside, outside) but are always at least triadic (context). There is no central governance or cooperative communication. There is enormous variation — the basic structure is so informationally rich that no two systems are sufficiently similar to value a same "thing" at the same time — indeed there are no "things" except as special cases.

+

The beauty about the klein form is that for the first time you are not captured by spheres or donuts, You can talk about a jet of air that goes up through the part of the klein form that is in contact with the external environment (where it is uncontained) and then becomes contained within itself and continues. For the first time you have a form which allows you to talk about something contained within itself ... if I put my hand on my knee it forms a kind of hole where the "outside" is in complete contact with the arm and where the energy from my hand goes back through my body and alters what happens "outside" again as it passes from within my body down through my shoulder ... I start to have a loop which is partly uncontained that is, really senses that which is outside itself, and partly contained, that is, it senses itself wilhin itself. It is a form that begins to have the capacity to know about its own behavior as it behaves "outside," that is, in simple connection with the environment, and as it behaves "inside," as informational representation to the environment within itself.

+

Paul spoke of how the klein worm has a capacity for anticipation and we find that anticipation has meaning only if we are considering a time-form geometry, a + geometry of relations rather than things (no longer Newtonian geometry but an Einsteinian time-space form, a form that does not define time but is time that is + by definition) ... ("Taping something new with yourself is a part uncontained. To replay the tape for yourself is to contain it in your perceptual system ...")

+

When you model with a klein form you have to change your head around, because for the first time you can talk about time as influencing behavior. Consider the + klein forms as being able to breathe. Let us say it is made of material with local energy that allows it to expand and contract. Image waves of contraction flowing in this material. The part that loops out into the environment — the unanticipated context — recurs through itself comparing the return with the rhythmic response on adjacent recursions. It changes its waveform to better maintain its intentional behavior. It is permeated by context. It has no walls. Yet it uses its structural infolding for maintaining itself changing in a sufficiently regular way to find new relations.

+

In biological systems rhythms pass through themselves interfering, augmenting, amplifying by setting resonant rhythms going which soak up energy which would otherwise be lost to relevant work. Rhythms that are more intracontained will tend to null out rhythms that are not convergent or that cannot find energies at the time they are needed...

+

To put it another way: Let's say you have a colony of birds and this colon) of birds is in a mountain valley almost filling up I he mountain valley, and the birds behave in the colony in a particular way that allows them to propagate so there are many more birds. The colon) then becomes crowded, and individual birds start to behave in a crowded way; the colony is then changed. The way the colony changes influences the way the birds change. The way the birds change influences the way the colony changes, but the birds change and the colon) s change are not simple additions; the colony is not made up of a million birds, nor is a bird made up of a colony, because there now starts to be in time an interaction, an active dynamic interaction between the single unit and the mass unit. The dynamic is not simply dividing the mass into the units. All of our theory and governmentology has been that the individual is simply a member of the class called mass. Now, however, we start to move to what the interaction is between the individual and the mass in a way that takes in the context which is beyond either the individual or the mass, that is, that which is contained around that totality; so we have always a system of three at least. You always have a context.

+

In the past all of our logic in all of our theory, in all of our ways of thinking, has been bound up with systems of two, systems basically true and false. But we know now that there's no such thing as high holy eternal noon, the time when all things are pure, because things are always changing, because time always exists. The klein form helps you get your head into a space where time starts to exist and where (lungs are constantly in dynamic motion with a different kind of dynamic relationship than you get if you're talking about spheres. The concern used to be: how do you get the mass contained in the single member; how do you get the class contained in a member of the class. You could talk about how members made up the class but you could never talk about how the class made up the members; you were never able to talk about it with any geometric representation. But now people can talk about this in terms of triadic logic (the man who taught me what I know is Warren McCulloch. and Warren was searching for triadic logic in asking questions about things); that is, how do you set up a contexual logic so that your experiments aren't for the purpose of destroying context. Usually experiments are done so as to eliminate context ... Now, if you eliminate context you're then into what I call mechy max systems. Mechy max systems are mechanical maximizing systems which operate by Newtonian physics, which operate like a cluck with its clockworks. This is what Buckminster Fuller was talking about. There is for the clock a winder which is the energy source and there is the energv sync which is the fact that the hands of the clock go around; between the source and the sync are a number of levers of various sorts: wheels, ratchets, the great dumpers and the like, but the output never effects the input; there is always infinite source and infinite sync, infinite beginning and infinite end, and we find now that this is no longer a reasonable way to think. Now Bucky talks about spaceship earth and how man has to take it over, and I say bullshit, because man doesn't want to take anything over, because man is a part of the universe but he is not controller of the universe. Once you start to think that you must take it over it becomes like a Japanese garden. A Japanese garden is a garden that is arranged for man's purposes and basically has none of the mystery, none of the uncertainty ... (literally I have talked with people from NASA, people who are high up in government who think of our taking over the whole earth, artificial climate, artificial creation of environments ... of mechy max corning in, destroying the environment, and then recreating it ...

+

The thing that you learn when you start to play the game of building biological systems (what I call biological optimizing systems or bioptemes) is that there is a context which man has nothing to do with and is not in any way in control of. There's no way to recreate biological systems, because in the recreation you do what you did with hybrid corn; you make a better com except that all the corn is exactly the same as the next; if any disease comes along it wipes out everything. There's no flexibility; man-made ecology is of necessity a low variety system because it only contains that variety which man can conceive of. An ecological system is a high variety system ... We're making "toys" which help us to think about ecology. In these biological systems that we're trying to create, however, we don't have control of the total system — we don't have control of the tools that we've built. "They" have a life of their own which is insensitive to the life that forms around them; each one is different from the next and if some part doesn't work it doesn't stop operating.

+

However, in a mechy max system, which is a clockwork, if one wheel stops turning the whole thing, because it's like a simple chain, and there's a weakest link, stops. If you have a densely interconnected system within itself where all the parts are connected with all the other parts, then all ihese parts are less densely connected with that which is outside which is the context; no two systems, then, are alike, and if any part dies, which it will, inevitably (because in some ways you try to make them as improperly, as inaccurately, as sloppily as you're able} ... if any part dies then the thing just has a different way of going about its behaviors — it may not have the same behaviors, it may not have the same purposes, it may not achieve the same purposes, it may have different purposes ... but death has occured naturally and in one clump which leaves a hole, and that hole is taken up by the regeneration and evolution of other species which fill the hole.

+

In mechy max systems there are no holes because everything is as uniform as possible.

+

I started out as a physician and with mechy max biology, the biology of low information systems, the biology of vision: you see something, but you're not aware of the effect of your seeing; you smell something and you're not aware of the effect of your smelling; you hear something and you're not aware of the effect of your hearing - your hearing is not active (you're not aware of its activity though actually it is active), but with touch and the sensuous world you start to get into if you touch something, then you touch it, it touches you; you move it, it moves you; you change it, it changes you, and it's happening simultaneously. You are no longer in the world of weak interconnection — when you're into densely connected systems you're into everything that happens effecting everything else that happens; when you're talking about densely interconnected systems you're talking always about effect. ... In eastern philosophy you talk about breathing out as well as breathing in; in western philosophy you talk about breathing in — everything is in; everything is need, everything is desire. And effect, breathing out and the sense of breathing, the whole sense of rhythming is something that eastern philosophy brings us close to. Western philosophy is the world of things ...

+

In mechy max systems, low variety systems, you have as I said toys which operate like clockwork. There are carnivore mechy max's that eat people and eat animals — military machines of all sorts; and there are herbivore mechy max's — the tractors and the cranes and the giant earth movers which cat up all the greenery and spit out lines of sugar cane, of corn, fields of cultivated plants that are domesticated plants. You have a whole field of one kind like a whole group of people of one kind. The herbivores also stack up mud into houses and into new apartment buildings and they proliferate more mechy max within this; washing machines, heaters; the mechy max have gradually been taking over the people and we have what we call plastic people, mechy max people. Biological systems become like Newtonian machines. People become like Newtonian machines. Their logic is like that.

+

Now the way this happened mostly is by the omnivores: the omnivores eat the herbivores, eat the carnivores. The omnivores are mostly made out of paper, out of form: they are called Internal Revenue Service, Social Security, health insurance, health center, mental health center. They are places where people are conditioned to act in mechy max ways; they are places where plants are conditioned so they will all be exactly the same as each other. Simplification in the mechy max style occurs by reducing the information to as low a level as possible by reducing the consequences of the environment as much as possible. The clock is so set up that the metals all counterbalance each other so that the heat changes will not effect the movement of the wheels and is not context or environment sensitive in any respect, that is, to reduce context sensitive. Biological systems operate quite to the contrary. Whatever happens, they have within them the capacity to cope so the animal is not taught, or he is not genetically made up to deal with a particular streaming of water; he's brought up to cope in such away as to loop again the behavior of that which is outside himself, and go back and reconsider what was outside himself in terms of his behavior, and recycle his own behavior through himself altering it in such a way so as to maintain survival, or to evolve survival so as to relate to the external world.

+

Biological systems are not all made the same. People may seem in many ways more like each other than they are like monkeys or rabbits, but even person has entirely different characteristics from the next, except that these differences coalesce or converge each in its own recipe to mate people who are somewhat similar. Inherently though there are enormous differences between people. Some of that difference is not obvious. Some of the flexibility in any natural system is not apparent because it's not being used. It's stored, like with wild wheat. Wild wheat looks like wheat but all the different kinds of wild wheat have a different genetic structure, more different than wheat that s been carefully selected like the wheat we see in mechy max books — quality controlled. Everyone knows exactly what kind of wheat they're going to get. In real wild systems there is enormous flexibility because many different kinds of components mix in such a way that the mixture is convergent towards a product or towards a creature which is sort of naturally similar — the manifest behavior and rhythms and identity is similar, but what makes it up is different. The wildness is not used and is non-apparent, but if something happens to the environment the wild potential still allows changes to occur because the flexibility is there available. A kind of wild system has a capacity for maintaining itself that a domesticated system does not.

+

In the mechy max system you try to maximize particular behavior, simplistic behavior so as to accomplish the one simple purpose which may be for instance to scrape up earth; scraping up earth in such a way so as In destroy all of the green things; all of the worms and ants; the earth boring mechy max truck or scraping thing doesn't pay any attention to what it picks up It tries to plant but it always replants in such a way as to destroy the variety: a meadow is not like a grassy lawn. There were meadows, meadows had bushes, the bushes lived by trees, and all of these, each part, was related to all other parts, and if anything came along, a big wind came along, it might destroy some of the trees but the bushes and the small trees would grow up again and if some grass eating thing came alone well, there are other forms of grass, but now you build lawns ...

+

One cannot talk about genetics, Gregory Bateson's point, in terms of classes of animals and creatures, you can't talk about the genetics of deer or the evolution of deer you have to talk about the evolution or genetics of deer in relation to grass and the evolution of plants. You can't separate the evolution of one particular aspect of life from another because when you think biologically then the whole world becomes interconnected and everything effects everything else, and everything contains evervthing else, and even beyond the world if you want to be spiritual about it, so that all things are in contact with everything else.

+

We are trying to develop a language of becoming; not a language of explaining which is what science has done, but a language of describing becoming which is what ecology's about, and not even explaining becoming, since everyone has within them the sense of the whole world in all of its parts. Our intuitive sense of becoming can be very rich provided we give up the mythology of the mechy max.

+

We're developing systems now that operate by touch, so if you touch them you intervene in their loops. They are not paying attention to you. They're paying attention to that you've interfered with their usual mode of operation. To reestablish their mode of operations they have to behave in particular ways that allow them to continue to exist in their style which is very different from their sensing you. They don't sense you as you, as a plant doesn't sense a tree as a tree. It senses that it has more shade and it must grow in a different way to find its sun. The other plant, the tree, in a way presses upon it. it becomes environment to it just as we are environment to each other and for the first time we can now talk about humans as environments to the rest of the world, or humans as environments to animals — we don't think of ourselves as the center of the world anymore; we're just environment, and there are many environments.

+

Mechy max organizations are doomed at this point because thev're not capable of managing the high information level that people want and need in order to survive. We have to accept that we are continuous with biological systems and have never been otherwise. In biological systems control is explicit. The mechy max myth is government control of the people and the government is a set of forms (I'm not talking about human people — they lost control of the government); the government is a mechy max system like a great earth moving device that now moves people about like a big clock that has all sorts of ratchets and all the people have to fit into ratchet position; literally in government the positions you have are not related to the people — they're related to the positions in the forms and forms do not have power. People have power, so power to the people is a joke because the people already have the power, but they haven't exercised it ...

+

Fuller is trying to reprogram the mechy max system to make it work better and my statement goes this way — the system is self-destructing now and the myth that the mechy max have power must now be destructed rather quickly among people. It's this attitude, that the mechy max have ultimate power, that the big machines + have ultimate power, that has put us where we have been eating up all sorts of garbage, the machines put out in order to keep the system going ... so we eat chicklets ...

+

I went through the stores and through the city recently (I've been living and working in the country lately and getting along on very little money) and looked at the whole city in terms of the destruct that's going on because all the products that are made are really just a bi-product of talk — the mechy max omnivores is a paper system and its single purpose is tally; tally is money; money is just keeping tally; mechy max operates by keeping tally; the game has been how you maintain the tally as gross national product for example, population rate for example, interest rates for example — these are all tally forms, banking, insurance ... all parasitic operations are tally systems of the mechy max— the money sy stem. This is not wealth. Wealth is the capacity of any organism to obtain that which is necessary for its own survival, and more than that to obtain that which is necessary to optimize its evolution and to maintain a kind of evolutionary stability that allows everything the whole world over to continue to prosper in a way that's healthy ...

+

I'm not talking about getting rid of all mechy max, however; (man's controlling nature was perfectly fine as long as he didn't have too much influence; it is just that the proliferation of the mechy max has become so enormous that the destruct not only of the mechy max but of the total earth is now possible); we are talking about biological optimizing systems, A maximum is where you try and get more and more and more; it grows and grows and grows; the bigger it is the better it is. If you don't think of optimal size, schooling is to pour more and more into your head and you no longer think of optimal pouring into your head in relationship to experience. There are optimal positions where you would have some mechy max but they wouldn't have grown like a cancer. Cancers kill their host and after a while the cancer dies because the person who has the cancer dies. Well the met In max at this point, the industrial system, the tally svstem, is like cancer. It is now proceeding to kill its host which is the earth ...

+

Up until now we haven't had anything to take the place of the mechy max mythology. We haven't had a sense of living systems, biological systems, being a totality; that the earth is a biological system; that the rocks are biological systems; that they're alive; that everything is alive but there are some things that seem much less alive; those are the rocks, the air. We must talk about these as special cases of living things which man basically has very little connection with because they're so different from man and he hardly comprehends their aliveness just as we don't comprehend really the aliveness of crickets. We comprehend better the aliveness of mice because mice are more like us — thev re mammals: we don't comprehend reptiles; we don't comprehend birds as well as we do monkeys, because the metaphor of any biological system is itself, because it is self-referent and self-organizing ... We were talking alwut the klein form; about effects at a distance returning to be infolded. That is, any biological system makes noise — it does things which are sort of trial and error and which don't get anywhere; that are fairly random. Those things which are random by definition don't persist: those things which converge into a behavior help to maintain the particular "thing" that has been going through trial and error behavior. If these converge, then the resultant behavior persists and we don't call it random anymore. Randomness or noise is the trial and error of biological systems.

+

Mechy max people proceed by considering things in a modular form — houses are ticky tack all like each other — or in uniform form. That is, all the ocean is like all the rest of the ocean. It's possible to dump atomic waste into the ocean because you know it will be diluted by the total ocean — but this does not occur. Atomic waste that's been dumped moves around in clumps in the ocean. It maintains its integrity; it stays together. The fish are alive. They concentrate the mercury and the mercury goes up the food chain and gets concentrated. Atomic waste gets concentrated. The world is of clumps and all the chimps are different — clumps of people are just different kinds of people.

+

The idea of clumps is very important because part of the mechy max mythology is that things start off as uniform and then develop into highly differentiated sets. This is not so. Everything starts out as highly differentiated from the outset though there are holes, discontinuities, which may be invaded by one set or another. Life processes operate against things becoming uniform and operate towards things becoming more highly differentiated.

+

One of the most fascinating problems is what happens when there is no leadership. In our cells there is no leader, but mechy max thinks of genetics as a great leadership system (as if genetics operates separately from what happens in the womb — what the mother ate, what kind of life she was leading).

+

You must start out with the fact that there are clumps. (Only God could organize from zero with everything uniform — that was in the mind of the religious people who organized from zero ... it's interesting he organized in seven days, in rhythms.) ...

+

Let's say you have a group of people together who are not together because there is a leader, but are a leaderless group. After a while they'll organize so that they get jobs done and sometimes they'll organize without a leader; sometimes they'll have a leader for a particular function — sometimes for a day or a month; all of this is different depending on the different kinds of people who happen to be in that group, so there's a natural type of organization that happens among a group of people, but it's not uniform. The rules are not the same across many cultures. Each culture has its own style. You don't start with randomness. Randomness and infinity are mechy max terms. Randomness as a continuous state can only be created with great difficulty; it's a mathematical state which doesn't occur in nature at all. What happens in nature is you get things grouping together in clumps which behave over time in such a way as they may continue to exist as a group ...

+

... and these clumps can only come in contact with those things which are physically adjacent or that are informationally adjacent or rhythmically adjacent. If you have two systems which have similar rhythms and if the rhythms are slightly different they'll start to rhythm together ... to form simpler rhythms. There may + be many different kinds of instruments but the rhythms tend to group in clumps. If you think of our communication process then those things which have similar rhythms are able to speak to each other; those which are very different rhythms are not able to speak to each other. So there are different communications that occur between elements of a system which are of different rhythms ... There's a certain kind of self-organization that occurs with a rock group making music together, or with two people making love. You may start when you're making love a new rhythm, but whether it'll catch on depends on where your partner's at and whether it's a random rhythm that has meaning and catches other random rhythms. What may start out as noise — that which does not have meaning, that which is not information, that which does not produce change — because at that point you're in transition, may be a rhythm your partner picks up on and plays back, and plays back again until a new rhythm is organized. You've gone through the transition into a new rhythm. What was noise becomes information, because it did have effect, it was that change which produced an effect. Rhythms tend to organize so that that which is relatively random and meaningless drops out, and that which was meaningless may be the very thing that sets off the next transition.

+

I have moved finally into the space which I call eco-space. Eco-space is self-referencing such that the existence of time and space and size and materials and energy are all in constant rhythmic motion so there is no way to repeat behavior. Eco-space is triadic. Eco-space is recursive. It is not a place of beginnings and endings, of inputs and outputs discreet from each other. Eco-space is auto-correlating ... self-organizing ... I have moved into rhythms, ecological rhythms. The thing that's most constant when you're talking about nature and biology is rhythms and time things; that's where the most important information lies, information being denied by in large by science. In our kleinform sponge there can be many currents and rhythms looping themselves and each other, spreading and flowing like a meadow or forest or like the living sponge in the sea, or the sea as a sponge: a current of water moves swiftly between two coral heads; it hits a back flow and is turned back, like the stocking looping outside then across through the flow jetting intra-contained through its own streaming. It intervenes in its own becoming. Dive into the water and surface through the bubbles you made and dive again. Wind back through yourself a tape of yourself talking and behaving so that you can relate to yourself as you will be when you watch the tape, then infold again.

+ A topology that uses rhythms intermingling and flowing around and through each other would let us build walls secondarily, rather than as categorical dividers. TV networks do not have walls ... Swim in its currents, feel them, where the activity of the space changes abruptly, sediment — slower changing stuff — is laid + down. The slow rhythm — a "now" memory, infolds and gives context to faster events which in turn give the slow rhythm meaning.

+

Scuba swimming deep in the ocean one can feel the eddys and rhythms of fluid filling the holes which one would have called cells. Coral reefs grow in slow time — slow rhythms wearing volcanic rivulets into bridges of sponge, volcanic bubbles and the sea twisting and turning rhythms the sand into ripples — and these ripples and sand spits rhythm the sea and the growing of coral and the wearing of rock — and all these are rhythms. Swimming below one knows one's own rhythms and the rhythms of breathing and blood and that nothing is still. Putting one's face mask close to the ripples of sand one can watch the grains flowing. But to sense that flow of slow things like sand, or equipment or hard wired programming — the flow of these walls, we must change our rhythm and swim in their time and size grain. Ten year interval time; equipment distribution size.

+

Time lapse in 10 year intervals. Focus for large size objects. "Now" is a 10 year duration.

+

Infolded time lapse taping will show the rapid change of events ordinarily called unchangeable. Time taping can be tailored to find patterns. When I was with Bateson in Hawaii we both longed for a scries of time lapse shots of Honolulu showing the cancerously money producing developments destroying the cities' + survival environment. Month by month one can see the cancer growing. Day by day it is hidden. By changing time grain of the taping appropriately, complex rhythms are simplified. Then one can feel the repititiousness and code the kind of information/materials/energy flow that follows one to glue into our new biotopology conceptions.

+

But here I must leave off. If you have followed me into this space you may lead me through the enormous holes I see all around me filling them with energy/information/materials/time which as it resonates, converges or dies, or provides the surprises which may evolve the means of survival.

+

We must leave the old space. There is no life there.

+

A 1-hour tape from which the above transcription was made is available. See inside back cover for tape offering.

+

Special credit and thanks from Warren to Paul, Gregory Bateson, Avery Johnson, + Lita Osmundsen, Judy Johnson, Frank Gillette, Beryl and many others ...

+ —
+

See article by Avery Johnson entitled Infolding Paul Ryan.

+
+ + + + diff --git a/Research/b-e-e-t.r-o-o-t.net/buckminster_fuller_software.html b/Research/b-e-e-t.r-o-o-t.net/buckminster_fuller_software.html new file mode 100644 index 0000000..265dca3 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/buckminster_fuller_software.html @@ -0,0 +1,45 @@ + + + +software + + + + + +
+
+

SOFTWARE
+ R. Buckminster Fuller

+ —
+

Pirated transcription of interview videotaped by Raindance Corporation
+ First published in Radical Software, pg. 5, Vol.1, Issue 1, 1970

+
+ +
+

... and so we find what man's real function is, is sorting out his experience, developing what we call the normale, and being useful ... we hear people talk about technology as something very threatening, but we are technology, the universe is technology ... it's simply a matter of our understanding these things ... that nature has these beautiful exchanges ... and what's happened was this shortsighted - really scared - fear of man about whether he's going to survive ... he's been told there's nowhere nearly enough to go around ... therefore you've got to go out and look out for your side, look out for your family - he's got to hold this thing and make the short move ...

+ +

... so when our young world, like that young girl talking so superbly on earth day, eight year old kid, pure wisdom pouting out, her eyes could see as clear, when she said we ought not to throw away, we ought to reuse, and things like that ... that little girl was seeing that ... and so the net from all of our extraordinary earth day is that we have all of humanity catching on to things that need to be attended to when they were assuming yesterday someone else was attending to ... the fact that they were in such poverty ... they had so little time ... they had to work 12 hours or 14 hours a day ... my first job i really was working 18 hours a day ... you can't get anything done, you go home, i really didn't hardly have enough left to eat my supper before i fell down on the bed to sleep ... so i find man didn't even have time to think, nor did he have the vocabulary ... he didn't have the literacy ... the literacy did not come as much out of school as out of radio ... the people who had the radio jobs had good diction, good vocabulary necessary for it, so the kid could listen to a good vocabulary that papa didn't have ... and so we really proliferated the capability to communicate ... + and now that we know how to communicate, we know there are many nuances of information ... that little child, impressive beyond her wisdom was the beautiful resource of words that she had which came so spontaneously to her ... when i was a little kid all that kids would say was "i don't like it" or "wow" ... just make a noise because they didn't have the resources to express it ... the same wisdom ...

+ +

... i think the great beautiful thing that's happening in evolution here is that quite clearly we have gone through a great historical sequence of events ... from man as so ignorant and his hunger so great, his needs so great, he doesn't know how to satisfy them so he goes through starvation and he goes through pain and disease ... go back to the earliest pharoah time ... life was so bad that nobody thought of life as worthwhile in its own right ... therefore the only way you could explain your having such experience was getting yourself ready for afterlife ... so everybody thought about afterlife but the fact is part of the experience with so little to go around is that you could only think of the pharoah having an afterlife ... so the great economic drive, all the great ingenuity of the man who could see anything - artist, conceiver - was patronized by the afterlife of the pharoah ... then in getting ready for the afterlife of the pharoah you incidentally discover the levers ... (in order to take care of the pharoahs what are you going to do? ... you know there are thieves everywhere and he's going to need tools after his life so you've got to get all of these fine things under a great stone mountain so it couldn't be stolen and that's why you've got your pyramids ... ) so the Leonardo type, good-thinker, realizes the lever ... he gets an army of prisoners and they use their levers to move those stones around and build that mountain ... however, after the pharoah dies, the leonardo type dies, the people still remember about the lever ... they still remember that the leonardo type saw these people falling at the road ... they needed food, quite clearly, connected food, so there's the nile that would bring water into those side layers ... and we have fertilization ... when the pharoah dies and that thinker dies, the ditches are still there and the levers are still there, and the people remember there's an accumulation of technical capability so when another man comes along he adds to the inventory of tools ... what we may call the scaffolding to make ready for afterlife ... finally there's such accumulation of tools and capability and a little more know-how everywhere - advancement ... well, we may be able to take care of the afterlife of the nobles as well as the pharoahs ... then the tools increase some more, as they did then, and we say, well, we can take care of the afterlife of the middleclass ... and that is exactly where you come into roman and greek history - the individual family mausoleums ... finally there's got to be so much tooling around that we've a buddha and a christ and a muhammad coming around saying, you know, i think we can take care of the afterlife of everybody ... and so really the great christian era of 1500 is getting ready for the afterlife of everybody ... the great cathedrals, fantastic things, and you should see the real pathos of that little human being going in there ... the great joy that they're going to have afterlife ... suddenly there's so much tools accumulated here and the know-how keeps accumulating, and man knows a little bit more about nature and what it can do, and so he says, you know, we can take care of the afterlife of the king, as well as his living life, and still take care of everybody's afterlife ... that is what we call the beginning of the divine right of kings ... then the tools accumulate some more, and so now we can take care of the nobles in their present life, as well as the afterlife for everybody - the magna carta days ... then we have so much more proliferation of tools that we know we can take care of the afterlife of everybody, and the king, and the nobles, and the middle class ... that's the great victorian era right up to all the brownstones in new york here ... then suddenly the tools accumulated so much that henry ford said, you know, we can take care of the afterlife of everybody and we can take care of the living life of everybody ... that's the beginning of the new era, but at this point the leonardo artist-type says, up to now we were using our own hands to make end-products for the patron ... so in the victorian era you'll find the beautiful cabinet maker, and you'll find the beautiful shoemaker and tailor ... fantastic craftsmen everywhere ... but now he says, i can't make end-products for everybody ... there aren't enough artists to make end-products for everybody ... therefore, we'll have to have an entirely new kind of thing which is our industrial tools, our mass production ... and that's what is really come to all of humanity ... ... so what we've got to really come to now is developing awareness in that little child ...

+ + we've got to proliferate the right kind of information ... industrialization and technology is not something new ... you and i are technology, so superior to any we've ever devised ... that camera looks pretty crude along side of my eye, and my eye has always had its own light meter - it's got the whole works ... + and so i simply say, if you had that camera so it could also rebuild itself and keep itself going and improving itself for the next 70 years then you have something approximating the technology you and i really consist of ... technology's not new ... we've just been a little too crude at it ... our society's got to be sure not to let somebody mislead us ... not let our own ignorance mislead us into making the wrong moves ... + +

... in your picture of earth day, if the young people go out with a broom and start collecting, and if they went further than picking out the paper from it and the metal and said we're going to find out how to get those recirculated, then we're really getting somewhere ... each one of us is process ... we're not things ... and so it's fantastic - there's no scientist been asked to look at the plumbing ... the best flushing toilet you have is so inefficient that we use 65 volumes of water to get rid of one volume of human waste - but it is waste, and it's very, very valueable chemistry ... at the university of illinois way back in 1929 we found that the human excrement in one farm family has in it enough energy to run all the farm machinery ... so these are the things - i hope your young world first is getting aware, and then getting to be critical and picking out things ... and now we're really beginning to understand this need of a greater understanding of nature ...

+ +

... it's very important for me to tell you that the word failure is invented by man just like the word pollution ... it's a word of ignorance because nature can't fail ... nature knows exactly what she's doing ... but when man doesn't understand nature and thinks that this is the way nature behaves, and he tries to make it do this and that's not in her program then it frustrates him and he calls it a failure ... but nature doesn't intend to have anything go on for very long ... she's always transforming so she has a way of terminating, and when man wants her to go on beyond that termination point then he calls it failure, but it's not so ...

+ +

nature is intent on trying to make man a success despite himself, and despite his long, long history of his great ignorance where i'm trying to give you the way the breakthrough is occuring ... we're still assuming fallaciously there's not enough to go around ... you have to prove your right leave; you have to earn a living ... was the old statement ... the young world really feels now that's wrong ... that the information we can get to the moon and do all this is very important because i think it tells man he can do anything he needs to do and he can make man work ...

+ +

... he's got to learn that the space program is not something - (never mind that space stuff, let's get back on earth, let's be practical, let's be blaise about the moon shoot ... ) the fact is our earth is a little spaceship ... unless we catch on to the fact we are a space program ourselves and that we have just so much supply and we've got to learn how to run that big spaceship which we are onboard ... to send off little spaceships to find out exactly what we need to be able to keep human beings doing ... this is the only way we will ever find out about ecology ...

+ +

... on earth day i spoke at 4 universities ... i asked each one of the audiences of kids if they could tell me how much of the earth was necessary to support each life ... when you talk ecology that is a pattern of the science of the total process in life ... what's necessary to regenerate it ... each species is a relationship to the environment ... we're not really qualified to use the word ecology until we get into that ... but i'll tell you the way we'll find out is to send a man off into space ... get him outside where there's no air to be breathed; no water available; no foods ... what do we have to have on board to keep him out there for a year? ... we've literally found now that it is possible - there are two space program researches where we have teams of six men each, sealed up in cylinders (completely different operations, really quite remote from one another, the russians are doing one and the same thing too) ... those men are sealed for a year, and we give them preliminary equipment which you did learn by having scientists who are good ecologists and good chemists ... putting everything in there necessary, they hope to keep the men going ... they're connected by telephone (really very easy to talk in now - you have a window) ... but they are now operating six men for one year on 350 pounds of apparatus and the whole apparatus being able to put in an airplane suitcase ... that we could get everything you need to regenerate life ... there is entropy so the system in the end has to have something added but you're able to have it sufficiently so you only have to add but once a year ... this is really getting somewhere ... so we come back on earth - we have 350 pounds suitcase size; even at the most expensive mass production for $2 a pound; that's $700 and you do away with sewers, all the water supply lines; all you need is a milk bottle or so a year to add into the system ... on a rental basis per six men for $700 you're down to $200 a year capital cost; maybe $1 a year you've got the equipment, and you go on any mountain top and really start living the highest standard ... and this equipment when it gets first used by those men off in space due to the television relay system around the world you'll have possibly a billion people watching those six men all year round and you'll have every kid really catching on to this ... here would be the great educational system about what the chemistry changes really are ...

+ +

... at any rate i simply say we must be very careful ... and we must not cut off things simply because the wrong people, with short and selfish and non-thinking motives have used tools ... a pencil is a beautiful thing but you could literally jab it into a man's heart and it would kill him ... so don't say that a pencil is lethal ... we must not blame the universe ... it would be like saying the universe is used in the wrong way, therefore it's better we not have any universe ... if we accept universe at all, if we accept life, and really would like to have something best for it, then we've simply got to learn how to use our universe in the best way ... and the universe is technology, and it's always evoluting, it's always complex, it's not repeating, so we have to be catching on to our new technology and realize we really do have a machinery of mutual regeneration around the world which has been for the moment - it's so powerful, so confident - very highly exploitable by the ignorant man who happens to get to monopolize it ... but in itself it's getting out from under him ... because he has sovereign claims - well, look, you can't stop the radio waves from going out of the sovereign limits ...

+
+
+ + + diff --git a/Research/b-e-e-t.r-o-o-t.net/ciao_to_wijnhaven.html b/Research/b-e-e-t.r-o-o-t.net/ciao_to_wijnhaven.html new file mode 100644 index 0000000..0452a04 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/ciao_to_wijnhaven.html @@ -0,0 +1,37 @@ + + + + ciao.urca.tv, richfolks.club to wijnhaven + + + + + +
+ +
+ + + + + + +

Abstraction

+ +

To abstract is to pull or draw away rules and concepts in general from specific examples, first principles, literal signifiers, and other methods. So, abstraction becomes a conceptual process of creating super-categorical representatives for subordinate concepts, connecting related concepts as a group, field or category.

+ +

Abstraction layers

+ +

Computer scientists use abstraction layers to make conceptual models that can be re-used and applied widely without having to write code again. For example, using a computer program language allows source code to be translated into machine code which can be used on various different machines. This separates a framework (a categorical concept of writing a program) from specific implementation, thus making the abstraction a categorical concept of the solution.

+ + A layer is considered to be on top of another if it depends on it. Every layer can exist without the layers above it, and requires the layers below it to function. Frequently abstraction layers can be composed into a hierarchy of abstraction levels. The OSI model comprises seven abstraction layers. Each layer of the model encapsulates and addresses a different part of the needs of digital communications, thereby reducing the complexity of the associated engineering solutions. + +

Autonomy

+ +

Autonomy is the freedom to make an informed, uncoerced decision. Autonomous organisations, institutions and individuals have independence and the ability to self-govern.

+
+
+
+ + + \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/cybernetic_guerilla_warfare.html b/Research/b-e-e-t.r-o-o-t.net/cybernetic_guerilla_warfare.html new file mode 100644 index 0000000..552063a --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/cybernetic_guerilla_warfare.html @@ -0,0 +1,176 @@ + + + + Cybernetic Guerilla Warfare + + + + + +
+
+

Cybernetic Guerilla Warfare
Paul Ryan

+ —
+ +

First published in Radical Software, Vol.1, Issue 3, pp. 1-2, 1971 +
+ Klein worm illustrations by Claude Ponsot +

+ +
+

To fight a hundred times and win a hundred times is not the blessing of blessings. The blessing of blessings is to beat the other man's army without getting into the fight yourself.

+

The Art of War
+ — Sun Tzu

+
+ +

Part I
+ GUERILLA STRATEGY AND CYBERNETIC THEORY

+ +

Traditional guerilla activity such as bombing, snipings, and kidnappings complete with printed manifestos seems like so many ecologically risky short change feedback devices compared with the real possibilities of portable video, maverick data banks, acid metaprogramming, Cable TV, satellites, cybernetic craft industries, and alternate life styles. Yet the guerilla tradition is highly relevant in the current information environment. Guerilla warfare is by nature irregular and non-repetitive. Like information theory, it recognizes that redundancy can easily become reactionary and result in entropy and defeat. The juxtaposition of cybernetics and guerilla strategy suggests a way of moving that is a genuine alternative to the film scenario of NYC urban guerilla warfare "Ice". Using machine guns to round up people in an apartment house for a revolutionary teach-in is not what the information environment is about. All power does not proceed from the end of a gun.

+ +

We suffer the violence of the entropy of old forms; nuclear family, educational institutions, supermarketing, cities, the oil slick complex, etc., etc. They are running us down, running down on us and with us. How do we get out of the way? This ship of state continues to oscillate into runaway from its people and its planetary responsibilities, while efforts continue to seduce us into boarding this sinking ship; educational loans, fellowships, lowering the voting age. Where did Nixon come from anyway? How did that leftover from the old days of Elvis get to be Captain of our ship, Master of our fate?

+ +

How many Americans once horrified by thermonuclear war are now thinking the unthinkable in ecological terms with a certain spiteful glee of relief at the prospect of a white hell for all?

+ +
+

+ Psychedelic my ass: Children of A-Bomb. +
— Bob Lenox

+
+ +

Nobody with any wisdom is looking for a straight out fight. We have come to understand that in fighting you too easily become what you behold. Yet there is no way on this planet to get out of the way. Strategy and tactics need to be developed so the establishment in its entropy does not use up our budgets of flexibility. The efforts to enlist the young in the traditional parties by '72 will be gross. Relative to the establishment and its cultural automatons, we need to move from pure Weiner wise Augustinian Cybernetics into the realm of war game theory and practice in the information environment.

+ +

The most elegant piece of earth technology remains the human biocomputer; the most important data banks are in our brain cells. Inherent in cybernetic warfare is the absolute necessity of having the people participate as fully as possible. This can be done in an information environment by insisting on ways of feeding back for human enhancement rather than feeding off people for the sake of concentration of power through captial, psuedo mythologies or withheld information. The information economy that begins in a guerilla mode accepts, cultivates and depends on living thinking flesh for its success. People are not information coolies rickashawing around the perceptions of the privileged, the well paid, or the past. People can and do process information according to the uniqueness of their perceptual systems. Uniqueness is premium in a noospheric culture that thrives on high variety. Information is here understood as a difference that makes a difference. The difficulties of a negentropic or information culture are in our transformations: how do we manage transformation of differences without exploitation, jam or corruption that sucks power from people.

+ +

I am not talking about the cultivation of perceptual systems at the expense of emotional cadences. Faster is not always better. Doing it all ways sometimes means slowing down. Internal syncing of all facets is critical to the maintenance of a flexibiity and avoidance of a non-cybernetic "hang-up" and "drag".

+ +

The bulk of the work done on cybernetics from Weiner's guided missiles through the work at IBM and Bell Labs along with the various academic spin-offs has been big budget establishment supported and conditioned by the relation of those intellectuals to the powers that be distinctly non-cybernetic and unresponsive to people. The concept of entropy itself may be so conditioned. Witness the parallel between Weiner's theoretical statements about enclaves and the enclave theory of withdrawal from Vietnam. One of the grosses results of this situation is the preoccupation of the phone company and others with making "foolproof teminals" since many potential users are assumed to be fools who can only give the most dumb-dumb answers. So fools are created.

+ +

The Japanese, the people we dropped the A-Bomb on in '45, introduced the portable video system to this country in 1967, at a price low enough so that independent and semi-independent users could get their hands on it and begin to experiment. This experimentation, this experience, carries within it the logic of cybernetic guerilla warfare.

+ +

Warfare ... because having total control over the processing of video puts you in direct conflict with that system of perceptual imperialism called broadcast television that puts a terminal in your home and thereby controls your access to information. This situation of conflict also exists as a matter of fact between people using portable video for feedback and in situations such as schools that operate through withholding and controlling the flow of information.

+ +

Guerilla warfare ... because the portable video tool only enables you to fight on a small scale in an irregular way at this time. Running to the networks with portable video material seems rear view mirror at best, reactionary at worst. What is critical is to develop an infrastructure to cable in situations where feedback and relevant access routes can be set up as part of the process.

+ +

Cybernetic guerilla warfare ... because the tool of portable video is a cybernetic extension of man and because cybernetics is the only language of intelligence and power that is ecologically viable. Guerilla warfare as the Weathermen have been engaging in up to now and revolution as they have articulated it is simply play acting on the stage of history in an ahistoric context. Guerilla theatre, doing it for the hell of it on their stage doesn't make it either. We need to develop biologically viable information structures on a planetary scale. Nothing short of that will work. We move now inthis present information environment in a phase that finds its best analogue in those stages of human struggle called guerila warfare.

+ +

Yet this is not China in the 1930's. Though there is much to learn from Mao and traditional guerilla warfare this is not the same. Critically, for instance, in an economy that operates on the transformation of differences a hundred flowers must bloom from the beginning. In order to "win" in cybernetic guerilla warfare, differences must be cherished, not temporarily suppressed for the sake of "victory". A la McLuhan, war is education. Conflict defines differences. We need to know what not to be enough to internally calculate our own becoming earth-alive noosphere. The more we are able to internally process differences among us the more we will be able to process "spoils" of conflict with the entropic establishment-i.e., understanding the significant differences between us and them in such a way as to avoid processing what is dangerous and death producing. Learn what you can from the Egyptians, the exodus is cybernetic.

+ +

Traditional guerilla warfare is concerned with climate and weather. We must concern ourselves with decoding the information contours of the culture. How does power function here? How is this system of communications and control maintained? What information is habitually withheld and how? Ought it to be jammed? How do we jam it? How do we keep the action small enough so it is relevant to real people? How do we build up an indiginous data base? Where do we rove and strike next?

+ +

Traditional guerilla warfare is concerned with knowing the terrain. We must expand this to a full understanding of the ecological thresholds within which we move. We must know ourselves in a cybernetic way, know the enemy in a cybernetic way, and know the ecology so that we can take and take care of the planet intact.

+ +

The traditional concern is for good generals. What's desirable for us is adhoc heterarchies of power which have their logistics down. Cybernetics understands that power is distributed throughout the system. Relevant pathways shift and change with the conditions. The navy has developed war plans where the command is a fleet moves from ship to ship every fifteen minutes. It is near impossible to knock out the command vessel.

+ +
+

The traditional tricks of guerilla warfare are remarkably suited for cybernetic action in an information environment. To scan briefly:

+

Mixing "straight" moves with "freak" moves. Using straight moves to engage the enemy, freak moves to beat him and not letting the enemy know which is which.

+

Running away when it's just too heavy. Leave the enemy's strong places and seek the weak. Go where you can make a difference.

+

Shaping the enemy's forces and keeping our own unshaped, thereby beating the many with the few.

+

Faking the enemy out. Surprise attacks.

+
+ +

The business of deception in guerilla warfare is a turn off for most people in this relatively open culture. This is simply an area that need be better understood, if we are to be successful. People feel that concealing is unethical. Yet overexposure means underdevelopment. Many projects die of too much publicity. There is a sense in which we are information junkies feeding off each others unlived hopes. The media repeatedly stuns the growth of alternate culture in this country through saturation coverage. It is hard for an American to just keep his mouth shut and get something cooking. You are what you reveal. The start system renders impotent by overexposure and keeps others impotent through no exposure. Seeming different is more important that making a difference. Deception in guerilla tactics is an active way of avoiding control by an alien, alienating intelligence. When a policeman takes your name, he takes over. I know a guy who is inventing another identity for the computer. There is a virtue of mistrust and wisdom in knowing significantly more about yourself than you reveal. Love Thy label as thyself.

+ +
+

We retreat in space, but we advance in time. +
— Mao

+ +

Count the Cost. We need develop an information accounting system, a cultural calculus. +

+

Use the enemies supply. With portable video one can take the Amerikan mythology right off the air and use it as part of a new perceptual collage.

+

Be flexible. In cybernetics, flexibility, the maintenance of a good guessing way is critical. +

+

Patience. Cybernetics is inherently concerned with timing and time design. It is a protracted war. +

+

Do not repeat a tactic which has gained you victory, but shape your actions in an infinite variety. Water sets its flow according to the ground below; set your victories according to the enemy against you. War has no constant aspect as water has no constant shape.
— Sun Tzu

+
+ +

Part II
ATTEMPTING A CALCULUS OF INTENTION

+ +
+ +

Calculus of intention was a concept developed over many years by the cybernetic wizard, Warren McCulloch. He was in the business of brain circuits. McCulloch felt that dialogue breakdowns occured largely because we lacked a logic that could handle triadic relationships. Here is his description of the problem of the calculus of intention.

+ +

The relations we need are triadic, not diadic. Once you give me triadic relations, I can make N-adic relations; but out of diadic relations I can't go anywhere. I can build strings and I can build circles, and there it ends.

+ +

The great problem of the nervous system is the one concerning its core, the so-called reticular formation ... This reticular core is that things that decided whether you'd better run of whether you'd better fight, whether you should wait, whether you should sleep, whether you should make love. This is its business and it has never relinquished that business. It is a structure incredibly simple when you look at it, but the problem of organization of many components, each of which is a living thing, each of which in some sense, senses the world, each of which tells others what it has sensed, and somehow a couple million of these cells get themselves organised enough to commit the whole organism. We do not yet have any theory that is capable of handling such a structure.

+ +

Communication: Theory and Research ed Thayer, Lee, Thomas, Springfield, 1967. McCulloch's commentary on "Logical Structure of the Mind."

+ +
+

I have not made a thorough study of McCulloch. It would take years. I do not know if what follows satisfies that criterion he established for such a calculus. I have maintained a certain organization of ignorance relative to formal cybernetics and formal topology. In fact, what follow would not, it seems, satisfy the kind of discreteness, one-two-three, that McCulloch seemed to want. However, such discreteness may not be necessary.

+

My approach stems from work with McLuhan that preoccpied me with the problem of how to maintain congruence between our intentions and our extensions. McLuhan talked of orchestration of media and sense ratios. Neither cut it. Orchestras just aren't around and sense ratios or sensus communis is a medieval model, essentially a simile of meta touch. Gibson's book on the senses considered as percetual systems is richer in description of the process. It includes McLuhan's personal probing ability as an active part of the perceptual system.

+

While the following formulations may not in fact work as a calculus of intention I put them forth both because they have been exciting and useful for me and because the calculus itself seems a critical problem in terms of cybernetic guerilla warfare. Dialogue degenrates and moves to conflict without an understanding of mutual intent and non-intent. While it does not seem that we can work out such a common language of intent with the people pursuing the established entropic way of increasingly dedifferentiated ways of eating bullshit; it is critical we develop such a language with each other. The high variety of self organizing social systems we are working toward will be unable to co-cybernate re each other re the ecology without such a calculus of intent.

+

This calculus of intention is done in mathematical topology.Topology is a non-metric elastic geometry. It is concerned with transformations of shapes and properties such as nearness, inside and outside.

Topologists have been able to describe the birth of a baby in terms of topological necessity. There is a feeling among some topologists that while fixed math has failed to describe the world quantitatively, it may be able to describe the world qualitatively. Work is being done on topological description of verbs that seem commmon to all languages. Piaget felt that topology was close to the core of the way children think. Truck drivers have been found to be the people who are most able to learn new jobs. While driving truck for Ballantine one summer, it became apparent to me why. Hand an experienced driver a stack of delivery tickets and he could route in five minutes what would take you an hour. It was a recurring problem of mapping topologically how to get through this network in the shortest amount of time given one way streets etc.

+

I should say that my own topological explorations have a lot to do with a personal perspective system that never learned phonetics, can't spell or sing, and took to topology the way many people seem to take to music. The strangest explicit experience with toplogy I've had came via a painter friend, Claude Ponsot, whose handling of complex topological patterns on canvas convinced me that a non-verbal coherent graphic thing was possible. The following transformations on the klein bottle - klein worms, if you will - came in the context of working with Warren Brody on soft control systems using plastic membranes. Behind that are three years of experience infolding videotape. I checked these formulations with a Ph.D topologist. He had not seen them before, questioned whether they were strictly topological. As far as I know, they are original.

+ +
+ +
+ +
+ +
+ +
+ +
+ +

There are three specific areas where I think this topological calculus of invention can be of use: acid metaprogramming, a grammar of video infolding and perceptual sharing, and in soft control structures using plastic membranes.

+ +

Relative to acid metaprogramming I am not recommending LSD-25 to anyone nor am I endorsing Leary's approach. I am simply looking at some of the work John Lilly has done and suggesting that this calculus might be useful in that context. Both in Programming and MetaProgramming in the Human Biosphere and in Mind of the Dolphin Lilly uses the notion of interlock to describe communication between people and between species. In Programming and MetaProgramming he describes a personal experience with acid that in some way undercuts the metaphor of interlock, and to me suggests that the klein worms might be a better way to describe the process he calls "interlock". Here is Lilly's description of that experience he titles "the key is no key".

+ +
+

Mathematical transformations were next tried in the approach to the locked rooms. The concept of the key fitting into the lock and the necessity of finding the key were abandoned and the rooms were approached as "topological puzzles". In the multidimensional cognitional and visual space the rooms were now manipluated without the necessity of the key in the lock.

+ +

Using the transitional concept that the lock is a hole in the door through which one can exert an effort for a topological transformation, one could turn the room into another topological form other than a closed box. The room in effect was turned inside out through the hole, through the lock leaving the contents outside and the room now a collapsed balloon placed farther from the self metaprogrammer. Room after room was thus defined as turned inside out with the contents spewed forth for use by the self-metaprogrammer. Once this control "key" worked, it continued automatically to its own limits.

+ +

With this sort of an "intellectual crutch" as it were, entire new areas of basic beliefs were entered upon. Most of the rooms which before had appeared as strong rooms with big powerful walls, doors and locks now ended up as empty baloons. The greatly defended contents of the rooms in many cases turned out to be relatively trivial programs and episodes from childhood which had been over-generalized and over-valued by this particular human computer. The devaluation of the general purpose properties of the human biocomputer was one such room. In childhood the many episodes which led to the self-programmer not remaining general purpose but becoming more and more limited and "specialized" were entered upon. Several levels of the supra-self-metaprograms laid down in childhood were opened up.

+ +

The mathematical operation which took place in the computer was the movement of energies and masses of data from the supra-self-metaprogram down to the self metaprogrammatic level and below. At the same time there was the knowledge that programmatic materials had been moved from the "supra-self position" to the "under self-control position" at the programmatic level. These operations were all filed in meta-program storage under the title "the key is no key".

+ +

Programming and MetaProgramming, +
Lilly, pp. 42-43

+
+ +

Relative to video infolding it is near impossible to describe in words even using klein worm graphs what I'm talking about. The following will mean little to anyone except those who have had some experience of taping with themselves at different levels.

+ +
+

Taping something new with yourself is a part contained.

+ +

To replay the tape for yourself is to contain it in your perceptual system.

+ +

Taping yourself playing with the replay is to contain both on a new tape.

+ +

To replay for oneself tape of self with tape of self is to contain the process in a new dimension.

+ +

Parts left out of that process are parts uncontained.

+ +

All of this is mapable on computer graphic terminals.

+ +

At one level that of reality that is left off the tape is the part contained.

+ +

Raw tape replayed is part contained in the head.

+ +

If it is somebody else's tape you are watching you can to an extent share in this live perceptual system via the tape he took.

+ +

To watch another's edited tape is to share in the way he thinks about the relation between his various perceptions in a real time mode. This enters the realm of his intention.

+ +

If you are editing some of your tape along with tape somebody else shot and he is doing the same thing using some of your tape then it is possible to see how one's perceptions relate to another's intentions and vice versa.

+
+ +

Relative to sharing perceptual systems it is somewhat easier to talk about since there are parallels with photography and film.

+ +

The most explicit experience of this mode of perceptual sharing came in the early days of Raindance when Frank Gillette, Ira Schneider, Michael Shamberg and myself "shot" twelve rolls of tape on earth day. Both in replay that evening (we laughed our heads off digging each others tape while the old perceptual imperialist, Walter Cronkite, explained Earth Day for us) and in the edits that followed each of us got a good idea of how each saw and thought about the events vis-a-vis the others.

+ +

Relative to soft control systems using plastic membranes I am thinking mostly of the soft cybernetic work being done by Warren Brody, Avery Johnson and Bill Carrigan. The sense of the sacred and the transcendental that surrounds some of the inflatable subculture is to me a kind of pseudomythology. Conciousness might be better invested in designing self-referencing structures where awareness is imminent in the structure and its relation to the users; not by being invested in a religious way to a "special" structure that does not relate intelligently to the users.

+ +

A Klein Worm couch is a suggestion of a possible way of moving in that direction. It could be built of strong polyurethane, filled with air, perhaps by a constant flow from a pump. People might interrelate kinetically through the changes in the air pressure. Design of the actual couch could be arrived at experimentally by combinations and transformations of the structures described above.

+ +
+ +
+ + + diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/Times New Roman.ttf b/Research/b-e-e-t.r-o-o-t.net/fonts/Times New Roman.ttf new file mode 100644 index 0000000..d7969c3 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/Times New Roman.ttf differ diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new-webfont.ttf b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new-webfont.ttf new file mode 100644 index 0000000..441bf96 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new-webfont.ttf differ diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new-webfont.woff b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new-webfont.woff new file mode 100644 index 0000000..abc7f65 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new-webfont.woff differ diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new-webfont.woff2 b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new-webfont.woff2 new file mode 100644 index 0000000..8d28aec Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new-webfont.woff2 differ diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold-webfont.ttf b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold-webfont.ttf new file mode 100644 index 0000000..ce7e673 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold-webfont.ttf differ diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold-webfont.woff b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold-webfont.woff new file mode 100644 index 0000000..bb00091 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold-webfont.woff differ diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold-webfont.woff2 b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold-webfont.woff2 new file mode 100644 index 0000000..19e0f69 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold-webfont.woff2 differ diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold_italic-webfont.ttf b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold_italic-webfont.ttf new file mode 100644 index 0000000..2044d5a Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold_italic-webfont.ttf differ diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold_italic-webfont.woff b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold_italic-webfont.woff new file mode 100644 index 0000000..b40f669 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold_italic-webfont.woff differ diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold_italic-webfont.woff2 b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold_italic-webfont.woff2 new file mode 100644 index 0000000..1381c10 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_bold_italic-webfont.woff2 differ diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_italic-webfont.woff b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_italic-webfont.woff new file mode 100644 index 0000000..c39d8f0 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_italic-webfont.woff differ diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_italic-webfont.woff2 b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_italic-webfont.woff2 new file mode 100644 index 0000000..6fb27e4 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_italic-webfont.woff2 differ diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_italic.ttf b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_italic.ttf new file mode 100644 index 0000000..326d622 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/courier_new_italic.ttf differ diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/times_new_roman-webfont.woff b/Research/b-e-e-t.r-o-o-t.net/fonts/times_new_roman-webfont.woff new file mode 100755 index 0000000..731d63c Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/times_new_roman-webfont.woff differ diff --git a/Research/b-e-e-t.r-o-o-t.net/fonts/times_new_roman-webfont.woff2 b/Research/b-e-e-t.r-o-o-t.net/fonts/times_new_roman-webfont.woff2 new file mode 100755 index 0000000..81bb860 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/fonts/times_new_roman-webfont.woff2 differ diff --git a/Research/b-e-e-t.r-o-o-t.net/gps_drawings/beetroot_to_ciao.svg b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/beetroot_to_ciao.svg new file mode 100644 index 0000000..ac66782 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/beetroot_to_ciao.svg @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Research/b-e-e-t.r-o-o-t.net/gps_drawings/beetroot_to_p_lions_es.svg b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/beetroot_to_p_lions_es.svg new file mode 100644 index 0000000..840d8e4 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/beetroot_to_p_lions_es.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Research/b-e-e-t.r-o-o-t.net/gps_drawings/beetroot_to_wijnhaven.svg b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/beetroot_to_wijnhaven.svg new file mode 100644 index 0000000..acaa2aa --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/beetroot_to_wijnhaven.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Research/b-e-e-t.r-o-o-t.net/gps_drawings/beetroot_to_wijnhaven_01.svg b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/beetroot_to_wijnhaven_01.svg new file mode 100644 index 0000000..ffd1eb9 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/beetroot_to_wijnhaven_01.svg @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Research/b-e-e-t.r-o-o-t.net/gps_drawings/ciao_to_wijnhaven.svg b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/ciao_to_wijnhaven.svg new file mode 100644 index 0000000..38d65bb --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/ciao_to_wijnhaven.svg @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Research/b-e-e-t.r-o-o-t.net/gps_drawings/p_lions_es_to_wijnhaven.svg b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/p_lions_es_to_wijnhaven.svg new file mode 100644 index 0000000..7bd1c50 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/p_lions_es_to_wijnhaven.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Research/b-e-e-t.r-o-o-t.net/gps_drawings/p_lions_es_to_wijnhaven_01.svg b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/p_lions_es_to_wijnhaven_01.svg new file mode 100644 index 0000000..09b413d --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/p_lions_es_to_wijnhaven_01.svg @@ -0,0 +1,44 @@ + + + + + +Wijnhaven 61 +p.lions.es + diff --git a/Research/b-e-e-t.r-o-o-t.net/gps_drawings/please_to_foshan.svg b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/please_to_foshan.svg new file mode 100644 index 0000000..36030da --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/please_to_foshan.svg @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Research/b-e-e-t.r-o-o-t.net/gps_drawings/please_to_wijnhaven.svg b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/please_to_wijnhaven.svg new file mode 100644 index 0000000..ba4d3ad --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/please_to_wijnhaven.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Research/b-e-e-t.r-o-o-t.net/gps_drawings/wijnhaven_to_foshan.svg b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/wijnhaven_to_foshan.svg new file mode 100644 index 0000000..1a6c733 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/wijnhaven_to_foshan.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Research/b-e-e-t.r-o-o-t.net/gps_drawings/wijnhaven_to_foshan_01.svg b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/wijnhaven_to_foshan_01.svg new file mode 100644 index 0000000..38865d4 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/wijnhaven_to_foshan_01.svg @@ -0,0 +1,64 @@ + + + + + + +Artboard 3 + + + Wijnhaven 61 + + + foshan.1992.pw + + diff --git a/Research/b-e-e-t.r-o-o-t.net/gps_drawings/wijnhaven_to_foshan_02.svg b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/wijnhaven_to_foshan_02.svg new file mode 100644 index 0000000..fe8256f --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/gps_drawings/wijnhaven_to_foshan_02.svg @@ -0,0 +1 @@ +Artboard 3Wijnhaven 61foshan-1992.pw \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/img/4_inca_quipu_knots.jpg b/Research/b-e-e-t.r-o-o-t.net/img/4_inca_quipu_knots.jpg new file mode 100644 index 0000000..b52e023 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/4_inca_quipu_knots.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/4_inca_quipu_knots_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/4_inca_quipu_knots_640.jpg new file mode 100644 index 0000000..8ec7e56 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/4_inca_quipu_knots_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/4_knots_solid_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/4_knots_solid_640.jpg new file mode 100644 index 0000000..fa22a82 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/4_knots_solid_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_01.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_01.jpg new file mode 100644 index 0000000..be7c4ae Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_01.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_01_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_01_640.jpg new file mode 100644 index 0000000..19d6d9f Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_01_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_02.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_02.jpg new file mode 100644 index 0000000..e9c55d8 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_02.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_02_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_02_640.jpg new file mode 100644 index 0000000..8654b19 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_02_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_03.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_03.jpg new file mode 100644 index 0000000..f436d2d Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_03.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_03_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_03_640.jpg new file mode 100644 index 0000000..90c5467 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_03_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_04.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_04.jpg new file mode 100644 index 0000000..deb748a Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_04.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_04_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_04_640.jpg new file mode 100644 index 0000000..656c7b8 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knot_04_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knots_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knots_640.jpg new file mode 100644 index 0000000..594ea59 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knots_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knots_small_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knots_small_640.jpg new file mode 100644 index 0000000..5d0e15e Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Atomised_knots_small_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_09_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_09_640.jpg new file mode 100644 index 0000000..9e21c03 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_09_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_10_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_10_640.jpg new file mode 100644 index 0000000..161b844 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_10_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_11_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_11_640.jpg new file mode 100644 index 0000000..b3c7c65 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_11_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_12_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_12_640.jpg new file mode 100644 index 0000000..f478fa4 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_12_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_drawing_01_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_drawing_01_640.jpg new file mode 100644 index 0000000..1cc9088 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_drawing_01_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_drawing_02_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_drawing_02_640.jpg new file mode 100644 index 0000000..a01b96d Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_drawing_02_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_lines_01_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_lines_01_640.jpg new file mode 100644 index 0000000..d1bcddf Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knot_board_lines_01_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_01.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_01.jpg new file mode 100644 index 0000000..7c74a31 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_01.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_01_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_01_640.jpg new file mode 100644 index 0000000..aadde3d Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_01_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_02.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_02.jpg new file mode 100644 index 0000000..c1c7012 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_02.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_02_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_02_640.jpg new file mode 100644 index 0000000..8bc292f Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_02_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_03.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_03.jpg new file mode 100644 index 0000000..302c15f Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_03.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_03_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_03_640.jpg new file mode 100644 index 0000000..6a35c60 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_03_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_04.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_04.jpg new file mode 100644 index 0000000..f071edc Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_04.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_04_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_04_640.jpg new file mode 100644 index 0000000..6af0c88 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_04_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_05.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_05.jpg new file mode 100644 index 0000000..d148c6e Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_05.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_05_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_05_640.jpg new file mode 100644 index 0000000..3e8185d Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_05_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_06.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_06.jpg new file mode 100644 index 0000000..9a1437f Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_06.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_06_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_06_640.jpg new file mode 100644 index 0000000..538eeed Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_06_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_07.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_07.jpg new file mode 100644 index 0000000..b64e528 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_07.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_07_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_07_640.jpg new file mode 100644 index 0000000..edc0d7b Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_07_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_640.jpg new file mode 100644 index 0000000..5804ec6 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_01.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_01.jpg new file mode 100644 index 0000000..5f4f2a2 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_01.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_01_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_01_640.jpg new file mode 100644 index 0000000..dda2d78 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_01_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_02.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_02.jpg new file mode 100644 index 0000000..6bcd285 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_02.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_02_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_02_640.jpg new file mode 100644 index 0000000..349efee Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_02_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_03.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_03.jpg new file mode 100644 index 0000000..9ebca92 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_03.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_03_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_03_640.jpg new file mode 100644 index 0000000..c05d8b1 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_03_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_04.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_04.jpg new file mode 100644 index 0000000..4995907 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_04.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_04_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_04_640.jpg new file mode 100644 index 0000000..220e121 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Knotwork_wht_04_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Unknot.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Unknot.jpg new file mode 100644 index 0000000..d8a5a83 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Unknot.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Unknot.png b/Research/b-e-e-t.r-o-o-t.net/img/Unknot.png new file mode 100644 index 0000000..34059ba Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Unknot.png differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Unknot_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/Unknot_640.jpg new file mode 100644 index 0000000..96b9695 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Unknot_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/Unknot_640.png b/Research/b-e-e-t.r-o-o-t.net/img/Unknot_640.png new file mode 100644 index 0000000..bcc1398 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/Unknot_640.png differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/bus_topology_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/bus_topology_640.jpg new file mode 100644 index 0000000..42910f4 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/bus_topology_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/bus_wht_topology.jpg b/Research/b-e-e-t.r-o-o-t.net/img/bus_wht_topology.jpg new file mode 100644 index 0000000..72671a7 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/bus_wht_topology.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/bus_wht_topology_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/bus_wht_topology_640.jpg new file mode 100644 index 0000000..d01bf7d Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/bus_wht_topology_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_1.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_1.jpg new file mode 100644 index 0000000..f92e342 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_1.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_2.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_2.jpg new file mode 100644 index 0000000..430c385 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_2.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_3.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_3.jpg new file mode 100644 index 0000000..61a7971 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_3.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_4.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_4.jpg new file mode 100644 index 0000000..51ec0a6 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_4.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_5.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_5.jpg new file mode 100644 index 0000000..f8992b4 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_5.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_6.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_6.jpg new file mode 100644 index 0000000..7daf606 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_6.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_7.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_7.jpg new file mode 100644 index 0000000..389a937 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_7.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_8.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_8.jpg new file mode 100644 index 0000000..2997ee7 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_1_8.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_1.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_1.jpg new file mode 100644 index 0000000..ae91140 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_1.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_2.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_2.jpg new file mode 100644 index 0000000..e49e0eb Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_2.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_3.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_3.jpg new file mode 100644 index 0000000..47255c4 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_3.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_4.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_4.jpg new file mode 100644 index 0000000..4cc4532 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_4.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_5.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_5.jpg new file mode 100644 index 0000000..4af73ea Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_5.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_6.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_6.jpg new file mode 100644 index 0000000..38de7b5 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_6.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_7.jpg b/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_7.jpg new file mode 100644 index 0000000..a1dbece Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/contrail_2_7.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/erasmusbrug_smoking.jpg b/Research/b-e-e-t.r-o-o-t.net/img/erasmusbrug_smoking.jpg new file mode 100644 index 0000000..fbef36f Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/erasmusbrug_smoking.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/gps_trilateration_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/gps_trilateration_640.jpg new file mode 100644 index 0000000..667eef6 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/gps_trilateration_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/knot_scale_times_four_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/knot_scale_times_four_640.jpg new file mode 100644 index 0000000..a3a89ce Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/knot_scale_times_four_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/mandarin_peel_01.jpg b/Research/b-e-e-t.r-o-o-t.net/img/mandarin_peel_01.jpg new file mode 100644 index 0000000..5bd36e3 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/mandarin_peel_01.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/mandarin_peel_02.jpg b/Research/b-e-e-t.r-o-o-t.net/img/mandarin_peel_02.jpg new file mode 100644 index 0000000..bef50bd Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/mandarin_peel_02.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/mandarin_peel_03.jpg b/Research/b-e-e-t.r-o-o-t.net/img/mandarin_peel_03.jpg new file mode 100644 index 0000000..6ec3345 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/mandarin_peel_03.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/mandarin_whole.jpg b/Research/b-e-e-t.r-o-o-t.net/img/mandarin_whole.jpg new file mode 100644 index 0000000..743f7f1 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/mandarin_whole.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/mesh_topology_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/mesh_topology_640.jpg new file mode 100644 index 0000000..03c14dd Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/mesh_topology_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/mesh_wht_topology.jpg b/Research/b-e-e-t.r-o-o-t.net/img/mesh_wht_topology.jpg new file mode 100644 index 0000000..73dc329 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/mesh_wht_topology.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/mesh_wht_topology_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/mesh_wht_topology_640.jpg new file mode 100644 index 0000000..eb9f9a3 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/mesh_wht_topology_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/ring_topology_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/ring_topology_640.jpg new file mode 100644 index 0000000..b8eb6ab Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/ring_topology_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/ring_wht_topology.jpg b/Research/b-e-e-t.r-o-o-t.net/img/ring_wht_topology.jpg new file mode 100644 index 0000000..54fd8f3 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/ring_wht_topology.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/ring_wht_topology_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/ring_wht_topology_640.jpg new file mode 100644 index 0000000..9c29d5e Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/ring_wht_topology_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/star_topology_wht.jpg b/Research/b-e-e-t.r-o-o-t.net/img/star_topology_wht.jpg new file mode 100644 index 0000000..7eab73e Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/star_topology_wht.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/star_topology_wht_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/star_topology_wht_640.jpg new file mode 100644 index 0000000..cbca6b5 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/star_topology_wht_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/tram_lines.jpg b/Research/b-e-e-t.r-o-o-t.net/img/tram_lines.jpg new file mode 100644 index 0000000..e441afd Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/tram_lines.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/tyre_tracks.jpg b/Research/b-e-e-t.r-o-o-t.net/img/tyre_tracks.jpg new file mode 100644 index 0000000..5fc958c Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/tyre_tracks.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/unravelled_knot_01_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/unravelled_knot_01_640.jpg new file mode 100644 index 0000000..96838b2 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/unravelled_knot_01_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/unravelled_knot_02_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/unravelled_knot_02_640.jpg new file mode 100644 index 0000000..84eb8cf Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/unravelled_knot_02_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/img/unravelled_knot_03_640.jpg b/Research/b-e-e-t.r-o-o-t.net/img/unravelled_knot_03_640.jpg new file mode 100644 index 0000000..f21951b Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/img/unravelled_knot_03_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/index.html b/Research/b-e-e-t.r-o-o-t.net/index.html new file mode 100644 index 0000000..b7f2805 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/index.html @@ -0,0 +1,17 @@ + + + +From Networks to Knotworks + + + + + +
+
+

From Networks to Knotworks

+
+
+
+ + \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/mejias_liberation_technology.html b/Research/b-e-e-t.r-o-o-t.net/mejias_liberation_technology.html new file mode 100644 index 0000000..31e864b --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/mejias_liberation_technology.html @@ -0,0 +1,164 @@ + + + + liberation_technology_and_the_arab_spring + + + + + +
+ +
+

Liberation Technology and the Arab Spring: From Utopia to Atopia and Beyond
+ Ulises A. Mejias, SUNY Oswego +

+ —
+ First published in The Fibreculture Journal, Issue 20, 2012
+ +

Abstract: While the tendency in the West to refer to the Arab Spring movements as ‘Twitter Revolutions’ has passed, a liberal discourse of ‘liberation technology’ (information and communication technologies that empower grassroots movements) continues to influence our ideas about networked participation. Unfortunately, this utopian discourse tends to circumvent any discussion of the capitalist market structure in which these tools operate. In this paper, I suggest that liberation technologies may in fact increase opportunities for political participation, but that they simultaneously create certain kinds of inequalities. I end by proposing a theoretical framework for locating alternative practices of participation and liberation.

+

—1—
+ After some initial fascination with the concept, there now appears to be more skepticism than support for the idea that tools like Twitter and Facebook are single-handedly responsible for igniting the Arab Spring movements. As we witness the immense effort and human cost that has gone into uprisings in Algeria, Bahrain, Egypt, Iraq, Jordan, Kuwait, Lebanon, Libya, Mauritania, Morocco, Oman, Saudi Arabia, Sudan, Syria, Tunisia, Western Sahara and Yemen, we recognise that it takes much more than a social media platform to organise and sustain a grassroots protest movement. And yet, the neoliberal discourse behind the trope of a “Twitter Revolution” (a revolution enabled by “liberation technologies” which empower oppressed groups) continues to function — especially in Western media and academia — as a utopian discourse that conceals the role of communicative capitalism in undermining democracy. The meme of the Twitter Revolution may have come and gone, but the ideology that gave rise to it continues to colour our ideas about participation and democracy.

+

—2—
+ What follows are some observations about the manner in which discourses around liberation technology are used to imagine a utopian model of activism in which digitally networked communities are capable of changing their political realities through mediated participation facilitated by corporations. Specifically, I want to do three things: 1) to examine how the utopian discourse of liberation technology circumvents any discussion of the market structure of digital information and communication technologies; 2) to explore how this utopian discourse normalises the role of digital networks as platforms that increase participation while simultaneously increasing inequality, and 3) to propose responses to the utopian discourse of liberation technology that provide alternative imaginings of social participation. I should clarify that my objective is not to provide a detailed account of the unfolding of the Arab Spring movements or their continuing repercussions; rather, my goal is to describe how the assumptions behind the rhetoric of liberation technology correlate to the practice of civic disobedience, and to delineate a theoretical framework for understanding the contrast between the two. Hence, I do not believe my argument is limited to a North African or Middle Eastern context. Events since the Arab Spring such as the England riots in August of 2011, or the emergence of the Occupy movement in September (which happened after this text was originally submitted for publication, and are therefore not discussed in detail) serve to extend the validity and application of my argument.

+

—3—
+ It would be adequate to begin by expanding the constrictive parameters set forth by the concept of utopia. Here, I will take a page from McKenzie Wark (2007) and augment this idea with the concepts of heterotopia and atopia. While a utopia is a nowhere that exists in a theoretical realm, a heterotopia is an actual but different space, an elsewhere where exceptional conditions from those that usually apply exist. Thus, while a utopia can only exist in the imagination, a heterotopia is an “island” (such as a school, a prison, a stadium or a hospital) where people are allowed — or forced — to follow different social rules. Lastly, an atopia is similarly an alternative site with different social norms, except that in this case, the site can be located anywhere or everywhere; it is borderless. In the remainder of this paper, I will be sometimes alluding to how the discourses of digital networks, participatory media, and mobilisation inscribe social participation in the different topological planes of utopia, heterotopia and atopia. Although these concepts are not central to my argument per se, they will help me frame a critique of liberation technology.

+

—4—
+ There is, indeed, much utopianism around the discourse of social media and recent protest movements. Even before the so-called Twitter Revolution, we can point to a growing trend, particularly within mainstream and even alternative journalism, that suggested that protest movements all over the world were transformed by participatory media (examples include statements about the revolutionary impact of cell phones in the Philippines, YouTube in Iran, Facebook in Moldova, and so on). I am choosing to collect this particular brand of techno-utopianism under the rubric of “liberation technology,” not because this is a term that is readily recognisable in popular or academic discourses, but because of its rich semiotic meaning. One noticeable place where a definition of liberation technology is attempted is the Web site for the Program on Liberation Technology at Stanford University. There, we are informed that the goal of the program is to research ‘how information technology can be used to defend human rights, improve governance, empower the poor, promote economic development, and pursue a variety of other social goods’ (‘Program on Liberation Technology’).

+

—5—
+ These are worthy goals. But my first encounter with the term “liberation technology” made me think of a similarly sounding concept, and even now, typing those words in Wikipedia will cause the search algorithm to ask: ‘Did you mean liberation theology?’ At first glance, perhaps both movements share a certain ethos and idealism. But my critique of liberation technology centers on the fact that, whereas liberation theology sought to lend legitimacy to the struggle of the oppressed by questioning the hierarchical structure of the Catholic church from within, and suggesting that the church itself could be the source of injustice, liberation technology does not seem very interested in questioning the roles and structures of the institutions that own and control social media networks. Instead, liberation technology seems to posit a worldview whereby technologies that emerge in the context of capitalism (precisely at places like Stanford) can be used by those wishing to challenge capitalism itself.

+

—6—
+ As the history of global unrest intersects with the emerging affordances of information and communication technologies (ICT), no one can deny that these can — often in unforeseen ways — aid in the defence of human rights, improve governance, empower the poor, and so on. But that is not the point. The point is that while presenting these technologies as nothing less than the agents of liberation, a critique of the capitalist institutions and superstructures in which these technologies operate is obscured, and this critique is necessary for understanding the relationship between capitalism and ICT, as well as for opening up new frontiers of liberation.

+

—7—
+ It has already been convincingly argued that neoliberalism would not have been possible without ICT (cf. Robert Neubauer, 2011), to the extent that these technologies facilitated transnational flexible production and unrestricted capital flows, causing the erosion of organised labour and the promotion of an unregulated, privatised “free” market as the solution to all of society’s ills. But here I am more interested in the link between capitalism and communication as an act of participation in society. Jodi Dean’s concept of communicative capitalism is particularly relevant, since she defines it as ‘the materialisation of ideas of inclusion and participation in information, entertainment, and communication technologies in ways that capture resistance and intensify global capitalism’ (2009: 2). In communicative capitalism, everyone has the tools and opportunities to express an opinion. “Participation” in society is therefore identified first and foremost as the ability to communicate, to express one’s opinion, in particular about the — mostly commercial — choices that give individuals their identity. However, the overabundance of communication in a marketplace in which all statements compete for visibility results in an environment where political change becomes difficult (if all options are equally valid, how can one option be declared superior?). Thus, the more we communicate (through our participation in digital networks, for instance), the more resistance is obstructed, and the more the ideology of capitalism is reinforced. Communicative capitalism — to paraphrase Gilles Deleuze — doesn’t stop people from expressing themselves, but forces them to express themselves continuously (1997: 129).

+

—8—
+ Encouraging compulsive and continuous expression has turned out to be a profitable business model, as evidenced by the growth of the social media industry. Facebook, launched only in 2004, was adding on average 250,000 new members a day by 2007. Currently, it has over 845 million members (‘Facebook Company Info’), who store ‘more than 100 petabytes (100 quadrillion bytes) of photos and videos’ in the company’s servers (‘Facebook Infrastructure’). According to industry reports, the social networking market as a whole grew 87% from February 2006 to February 2007 (Britton and McGonegal, 2007: 80). Currently, the world spends over 110 billion minutes a month on social networks and blog sites, which equates to 22% of all time spent online (Nielsen Wire, 2010). Social media is driven by advertisements targeted to users based on the demographic data they provide, and the amount spent on advertising in social network services was $1.4 billion in 2008, with companies spending $305 and $850 million dollars to advertise their products on Facebook and MySpace, respectively (Eskelsen, Marcus and Ferree, 2009: 102-103). While the launch of new social media companies gives the impression of a competitive market, merger and acquisition trends suggest a move towards conglomeration that mirrors that of (and intersects with) traditional broadcast media. In a notable example, MySpace (which currently has over 185 million members) was acquired for US$580 million in 2005 by Rupert Murdoch’s News Corporation, one of the eight companies that dominate the global media market (although it was later sold again, once it lost its market share to Facebook).

+

—9—
+ In essence, communicative capitalism means that communication and social exchange take place not just in any environment, but in a privatised one. The neoliberal impulse to subsume all social communication and participation to market forces can only be achieved if the network is made the dominant episteme or model for organising social realities. This is accomplished by the application of what I call a nodocentric filter to social formations, which renders all human interaction in terms of network dynamics (not just any network, but a digital network with a profit-driven infrastructure). Under a nodocentric view, the goal is to assign to everything its place in the network. Nodocentrism is an epistemic stance where the distance between a node and something outside the network is, for all practical purposes, infinite (Mejias, 2010). Thus, to be anything other than a node is to be invisible, non-existent. The technologies of communicative capitalism are applied towards the creation of a pervasive or ubiquitous computing environment in which every thing and every utterance must be integrated or assimilated as a node in a digital network.

+

—10—
+ As a way to illustrate the concept of nodocentrism in broader terms, consider the example of search engine results, and how they point to documents, sites or objects that have been indexed in a database. What has not been indexed is not listed as a result, and it might as well not even exist in the universe of knowable things as far as the search engine is concerned. Nodocentrism is also at work in the creation of friend lists like the ones used in social networking programs. These lists are nodocentric because they depict a social network comprised of individuals available (or potentially available) to interact with, but they render invisible the individuals who are not on the list because they do not use the same program, or because they do not have an account with that service. The algorithms of digital networks operationalise decisions about what is included or not included on the list. I am not suggesting that nodocentrism provides a deficient or false image of the world; I am simply pointing out how it embodies a politics of network inclusion and exclusion.

+

—11—
+ Consider the example of social movements like the Arab Spring. The discourse of liberation technology presents these movements as the work of “wired” activists, although this portrayal excludes the work and participation of activists who are not computer literate, or simply not social media users. Social change is thus imagined as an outcome of information flows within a network, and activists are portrayed as nodes transmitting dissent to other nodes. In order for liberation to happen, everyone must be connected to the same digital networks. Change and resistance are conceived in nodocentric terms.

+

—12—
+ But privileging a networked view of activism in this way can also serve to obstruct any real critique of social media technologies, and to justify their use without the need to question their terms of use. The discourse of liberation technology accomplishes this by providing two different — although interdependent — versions of the affordances of these technologies: one for the homeland territory, and one for abroad. While communicative capitalism provides citizens at home no real opportunities for resistance (the majority are too occupied compulsively communicating), liberation technology presents a liberal and utopian narrative of the emancipating and empowering potential of technology in places not entirely corrupted by capitalism. In other words, change, while impossible “here,” is realised through liberation technology “over there,” in a heterotopian elsewhere (that in the case of the Arab Spring includes the Middle East and parts of Africa). This is a valuable manoeuvre for liberal sensitivities, because it redeems the technologies of communicative capitalism. Activists “over there” are using these tools not just to talk about commercial choices, but about things that really matter: the overthrow of injustice, the plight of the poor, etc. Liberation technology thus functions as a form of self-focused empathy in which an Other is imagined who is nothing more than a projection that validates our own desires, a user of the same technologies we are using — a user who applies these tools not for the frivolous ends of consumerism, but for the betterment of the world.

+

—13—
+ This would seem to imply that the discourse of liberation technology can only serve to arrest social change at home. If that were strictly the case, it would be difficult to account for the Wisconsin protests in early 2011, the emergence of the Occupy movements, or for that matter, any subsequent act of protest in the West that uses technology to mobilise people. The fact that these events continue to germinate and spread seems to demonstrate that it is only a matter of time before social movements influence each other in this age of global media, thus making it possible for liberation technologies to fulfil their true potential wherever the social and economic conditions that fuel social unrest are present, even at home.

+

—14—
What is interesting, however, is that coverage of post Arab Spring movements in the West has not really revolved around protesters’ use of social media, or it has only minimally. Participatory media being used at home for organising protests is apparently not that newsworthy, since it lacks the sensationalist and media-friendly orientalism of the Twitter Revolution stories. And as the use of participatory media in social movements has become normalised and generalised, there seems to be continued support for the belief that these corporate products have fundamentally shifted the balance of power between producers and consumers, and therefore between the owners of the means of production and the audience.

+

—15—
+ However, I would propose that the discourse of liberation technology conceals, in fact, how production on the new platform continues to exhibit a power imbalance. In theory, the internet (the ĂŒber liberation technology in the liberal worldview) brought about the end of communication monopolies with their one-to-many models of dissemination; now, in the age of user-generated content, we have communication that is many-to-many. Access to the tools of production and the channels of distribution has been greatly democratised — the theory goes — and monopolies have been replaced by a free market with perfect competition. Everyone has the opportunity to create content, and everyone has the opportunity to engage that content. While the equation of this continuous communication cycle with civic participation is precisely what the concept of communicative capitalism seeks to critique, we need to also question the utopian narrative that describes a seamless evolution from monopolies (one-to-many) to more democratised circuits of communication (many-to-many). Has the empowering of more voices fundamentally altered the market structure of participation?

+

—16—
+ To answer that question, we need perhaps to take a brief detour through the Hitler Finds Out meme. This phenomenon refers to a series of parody videos on YouTube that began to appear circa 2006 in reaction to a sequence from the German film Downfall (2004), which depicts the last days of Hitler towards the end of World War II. Users took a three minute clip from the film in which Hitler learns he is losing the war, and while leaving the original German soundtrack intact, provided new subtitles to make it appear as if the FĂŒhrer is ranting about something else (like being kicked out of Xbox Live, the subprime mortgage crisis, the cancellation of the TV show Ugly Betty, and so on). But when the company that produced the film began to receive complaints that the parodies were trivialising the war and the holocaust, they decided to pull the clips from YouTube, claiming that the videos constituted a violation of copyright. The creators of the parodies felt their Fair Use rights were violated, and responded by creating more videos. One of them, in which Hitler rants against the videos being removed from YouTube, contains an interesting moment. When a Nazi general suggests to Hitler that they simply upload the videos to another video hosting service, like DailyMotion or Vimeo, Hitler angrily responds that nobody uses those services, and that ‘YouTube is the de facto standard’ (Green, 2010).

+

—17—
+ The point of this anecdote is to highlight the fact that when people have a video they want seen by the largest audience, they will most likely use YouTube (even if it is a video critiquing YouTube). And when people want to join a social networking site, they will join Facebook. And when they want to participate in a micro-blogging community, they will choose Twitter. There are alternatives for each of these services in the marketplace, but the fact that these networks host the most users renders the competitors almost useless. Most individuals will not willingly opt to use a service with a lesser share of the market.

+

—18—
+ This brings us to an important realisation about the market structure of social media: one-to-many is not giving way to many-to-many without first going through many-to-one. In other words, in this age where everyone can be producer and not just a consumer, the communication monopoly has merely been replaced by the monopsony (in economic terms, a monopoly is a market structure characterised by the presence of a single seller, whereas a monopsony is characterised by the presence of a single buyer). We — the sellers — are legion, but the buyers of what we produce are few. What we sell is not a product assembled in a factory, but “content” generated through social interactions which we hand over to the only buyer in town, the Facebook’s and Google’s of the world.

+

—19—
+ That monopsony has become the dominant market structure of the Web is not accidental. The architectures of participation of social media are based on a model where profit margins are maximised the more users join the network (which is why access is free or extremely low cost), and the more demographic data those users provide so that advertising can be targeted at them. As the saying goes: if you are not paying for it, you are not the customer, you are the product being sold. Access to “free” social media services exist only because companies have figured out a way to monetize our participation.

+

—20—
+ My argument is that this exchange is not fair for a variety of reasons, and that while digital networks increase opportunities for participation, they simultaneously increase inequality. In other words, the technologies of communicative capitalism embody practices of social participation and inclusion, but as Dean (2009) suggests, they also perpetuate the ideology of capitalism and obstruct any resistance to it. The way in which they do so — the way in which they create inequality while increasing participation — is through strategies that include the commodification of social labour (bringing activities we used to perform outside the market into the market), the privatisation of social spaces (eradicating public spaces and replacing them with “enhanced” private spaces), and the surveillance of dissenters (through new methods of warrantless wiretapping and social network analysis).

+

—21—
+ In order to provide a clear picture of the impact of this inequality, we must consider not only arguments that show the immediate benefits of a particular technology, but broader arguments that contrast the increase of access and participation with more comprehensive societal indicators. For instance, a Pew Internet & American Life Project survey from July of 2010 (Smith, 2010) indicated that cell phone ownership in the United States was higher among Latinos and African Americans (87%) than among Whites (80%). This would seem to suggest some progress in terms of inclusion, and perhaps even economic opportunity. However, when we contrast this data with the fact that the median wealth of African Americans has decreased 77% since 2007 (‘Harper’s Index,’ 2010), it becomes apparent that access to certain technologies does not, by itself, translate into more equality. It might be helpful to speak of the inequality generated thorough participation via digital networks in the manner that Andre Gunder Frank (1967) spoke of underdevelopment: not as the result of being excluded from the economic systems of capitalism, but precisely as the result of being included in them.

+

—22—
+ That digital networks increase participation while increasing inequality is also evident in the case of the Arab Spring. While we have seen a diminishing in the compulsion to brand these acts of protest as Twitter Revolutions (as if corporate products, not people, deserve the credit), the discourse of liberation technology nonetheless implies that social media is partly responsible for igniting the uprisings — and in cases like Egypt, for their success. That these tools can and should be used for getting more people to participate in democratic movements is not what I am arguing against. Rather, I am interested in the larger consequences and implications for democracy of employing such tools, and I am proposing (along with people like Evgeny Morozov, 2011) that the use of social media by activists increases opportunities for participation and action, but it also makes it easier for governments and corporations to operate a repressive panopticon.

+

—23—
+ According to a report by the OpenNet Initiative, around 20 million users in the Middle East and North Africa have already experienced the blocking of online political content carried out with the help of Western technologies (Norman and York, 2010). To the extent that grassroots movements all over the world continue to rely on corporate liberation technologies to organise and mobilise, we can expect inequality (through participation) to take various forms.

+

—24—
+ First, there is the loss of privacy through surveillance. States can monitor activity within online social networks to identify dissenters and learn of (and obstruct) their plans. This is accomplished through deep-packet surveillance, filtering and blocking technologies, provided to repressive regimes like Iran, China, Burma and Egypt by companies like Cisco, Motorola, Boeing, Alcatel-Lucent, McAfee, Netsweeper, and Websense (York, 2011; Mayton, 2011). Recently, a group of Chinese citizens even filed a lawsuit against Cisco, claiming that the technology that allowed the government to set up the Great Firewall of China led to their arrest and torture (Abbott, 2011). That the US government pays lip service to the importance of a Free Internet (MacKinnon, 2011) and finances circumvention technologies (Glanz and Markoff, 2011) while supporting these companies through tax breaks and lax regulation is an unfortunate contradiction.

+

—25—
+ Second, inequality through participation can also be produced through the use of PSYOPs and propaganda. The US Army, for instance, is developing artificial intelligence agents that would populate social networking platforms and dispense pro-American propaganda (Fielding and Cobain, 2011). Dozens of these ‘sock puppets’ could be supervised by a single person, and their profiles and conduct would be indistinguishable from that of a real human being. A low-budget version of this strategy has already been put into action by the Syrian government, who apparently released an army of Twitter spambots to spread pro-regime opinions (York, 2011).

+

—26—
+ Loss of freedom of speech is another example of inequality through participation. Companies, unlike states, are not obliged to guarantee any human rights, and their Terms of Use give them carte blanche to curtail the speech of certain users. For instance, Facebook (one assumes under the direction of the British authorities) recently removed pages and accounts of various protesters belonging to the group UK Uncut just before the wedding of Prince William and Kate Middleton (Malik, 2011). UK Uncut is not a violent terrorist organisation, but a group that opposes cuts to public services and demands that companies like Vodafone pay their share of taxes.

+

—27—
+ Suspension of service is another issue to consider. States (in collaboration with corporations) can simply “switch off” internet and mobile phone services for whole regions, in order to terminate access to the resources activists have been relying on. Vodafone, for instance, complied with the Egyptian government’s directive to end cell phone service during the Revolution of 25 January (Shenker, 2011).

+

—28—
+ Inequality though participation will also be evident in new technologies that will facilitate the remote control of mobile devices without the user’s consent. Modern cell phones have, for some time, provided the authorities with the ability to use them as wiretapping devices without their owner’s knowledge, even when the power is off (McCullagh and Broache, 2006). And they can also be used to track individuals and report their locations. An indication of what else we can expect in the future is a patent, filed by Apple, that allows for authorities to remotely disable a phone’s camera (Mack, 2011). While this is intended to prevent illegal recording at concerts, museums, etc., we can imagine how effective it would be at protests.

+

—29—
+ The last example of inequality through participation I will briefly discuss is crowd-sourced identification. One reason why authorities may want, in fact, not to remotely disable phone cameras is because they can aid in the identification of activists. At the Vancouver riots of June 2011 (which had nothing to do with correcting social injustices, and everything to do with sports hooliganism), Facebook, Twitter and Tumblr users were enlisted in a crowd-sourcing attempt to identify miscreants using digital photos and videos posted by onlookers (Wong, 2011). Similar practices were employed by the Iranian government during the post-election riots of 2009. Websites like http://gerdab.ir were setup to allow regime sympathisers to identify protesters and report them to the authorities (Tehrani, 2009).

+

—30—
+ All of the practices described above confirm Morozov’s observation that social media can be used by both sides, not just the side we agree with, and that the sacrifices in privacy may not be worth the gains (2009). Which perhaps explains why, at least in the Gulf Countries, Facebook usage seems to be diminishing (Khatri, 2011). But as regimes — repressive as well as democratic — learn how to use social media to influence the popularity of certain viewpoints, monitor communication, and detect threats, it seems as if dissent will become possible only in the excluded, non-surveilled spaces of what is outside the network, away from the participation templates of the monopsony. It is to this possibility that I want to turn next.

+

—31—
+ A typical drawing of a network depicts a series of nodes connected by lines, representing the links. As a mental exercise, I want to call attention to the space between the nodes. This space surrounding the nodes is not blank, and we can even give it a name: the paranodal. Because of nodocentrism we tend to see only the nodes in a network, but the space between nodes is not empty, it is inhabited by multitudes of paranodes that simply do not conform to the organising logic of the network, and cannot be seen through the algorithms of the network. The paranodal is not a utopia — it is not nowhere, but somewhere (beyond the nodes). It is not a heterotopia, since it is not outside the network but within it as well. The paranodal is an atopia, because it constitutes a difference that is everywhere.

+

—32—
+ Broken Web links pointing to pages that no longer exist, or cached versions of pages no longer active are paranodal, because they represent phantom nodes. Signal obstructers such as RFID (Radio-frequency identification) blockers that prevent network devices from being found are examples of technologies that create paranodality. Public spaces without surveillance cameras are paranodal spaces. Pirate radio operators are paranodal, because they function without network registration. Any kind of space where signal reception cannot be established is paranodal. Digital viruses and parasites that obstruct the operations of a network are also examples of paranodal technologies. Obsolete technology is paranodal, because its usage is no longer required to operate the network. Digital noise and glitches are paranodal, because they interfere with the flow of data in the network. Paranodality is a lost information packet in the internet. Populations in a dataset that are excluded or discriminated against by an algorithm become paranodal. Punk or rouge nodes — nodes who belong to a network only in order to destroy it — are paranodal. The activist who does not use liberation technologies is also an example of a paranode. This does not mean that paranodes are completely off the grid and outside all networks; for instance, someone not on Facebook might still be on email, which means she is a paranode in relation to the former but a participant in relation to the latter. Thus, the concept of the paranodal can help us describe network exclusions as well as allegiances.

+

—33—
+ The reason paranodes are important in our discussion of liberation technology, monopsonies, and protest movements is because these peripheries represent the only sites from which to disidentify from the network. The paranodal, to paraphrase Jacques RanciĂšre, is the part of those who have no part (1999), and it is the means by which things that are not nodes can claim difference from the network as a whole, refusing to identify with it.

+

—34—
+ While the study of resistance movements as networks continues and will continue to be useful, a framework for opposing the nodocentric ordering of these movements into privatised templates for participation is necessary. As activists continue to demonstrate to liberation technologists, the struggle must go on after the internet and other digital networks are shut off — if the fight can’t continue without Facebook and Twitter, then it is doomed. This means that the struggle is in part against those who own and control the privatised networks of participation (and can thus switch them off). Consequently, we have to turn to the paranodal for the emergence of corresponding models of activism. Since the peripheries represent the only sites from which to unthink the network, it is in the paranodal where new strategies will emerge: strategies of obstruction, interference, and disassembly of privatised networks; strategies of leaking information or circulating misinformation in networks; and strategies of intensification: transforming action that begins in one kind of network into resistance and engagement with alternative forms of networks.

+

—35—
+ As we realise that many-to-many communication is becoming impossible without a for-profit many-to-one infrastructure, we must abandon the utopian fantasy that liberation technology, as currently envisioned, can increase democratic participation. Participation managed by monopsony can only increase inequality. In response, paranodality must provide an atopian way to challenge the network by serving as a method for thinking and acting outside the monopsony. As networks have become not just metaphors for describing sociality, but templates that organise and shape social realities, we must question our investment in corporate technologies as the agents of liberation.

+
+

Biographical Note

+

Ulises A. Mejias is assistant professor in the Communication Studies Department at SUNY Oswego. His research interests include network studies, critical theory, philosophy and social studies of technology, and political economy of new media. His book on critical network theory is scheduled for publication in 2012 by University of Minnesota Press. For more information, see http://ulisesmejias.com

+
+ +

References

+ +

Abbott, Ryan. 'Torture Victims Say Cisco Systems Helped China Hound and Surveil', Courthouse News Service, 10 June (2011)

+ +

Britton, Daniel B., and McGonegal, Stephen. The Digital Economy Fact Book, Ninth Edition (Washington DC: The Progress & Freedom Foundation, 2007).

+ +

Dean, Jodi. Democracy and Other Neoliberal Fantasies: Communicative Capitalism and Left Politics (Durham, NC: Duke University Press Books, 2009).

+ +

Deleuze, Gilles. Negotiations 1972-1990 (New York: Columbia University Press, 1997).

+ +

Eskelsen, Grant, Marcus, Adam, and Ferree, W. Kenneth. The Digital Economy Fact Book, Tenth Edition (Washington DC: The Progress & Freedom Foundation, 2009).

+ +

Facebook Company Info

+ +

Facebook Infrastructure

+ +

Fielding, Nick, and Cobain, Ian. 'Revealed: US spy operation that manipulates social media', The Guardian. 17 March (2011)

+ +

Frank, Andre Gunder. Capitalism and Underdevelopment in Latin America: Historical Studies of Chile and Brazil (Monthly Review Press, 1967).

+ +

Glanz, James, and Markoff, John. 'U.S. Underwrites Internet Detour Around Censors Abroad', The New York Times, 12 June (2011), sec. World

+ +

Green, Zacqary Adam. 'Hitler reacts to the Hitler parodies being removed from YouTube', April 20 (2010)

+ +

‘Harper’s Index.’ Harper’s Magazine, August 2010.

+ +

Hirschbiegel, Oliver. Downfall (Der Untergang) (Germany, 2004).

+ +

Khatri, Shabina. ‘Facebook usage falls in GCC, including in Qatar, Saudi Arabia’, Doha News, May (2011)

+ +

Mack, Eric. ‘Apple patent suggests infrared sensors for iPhone’, CNet News, 2 June (2011)

+ +

MacKinnon, Rebecca. ‘Internet Freedom’ in the Age of Assange, Foreign Policy, 17 February (2011).

+

Malik, Shiv. 'Facebook accused of removing activists’ pages', The Guardian, 29 April (2011)

+ +

Mayton, Joseph. 'US company may have helped Egypt spy on citizens', IT News Africa, 10 February (2011)

+ +

McCullagh, Declan, and Broache, Anne. 'FBI taps cell phone mic as eavesdropping tool', Cnet News, 1 December (2006)

+ +

Mejias, Ulises A. ‘The limits of networks as models for organizing the social,’ New Media & Society 12.4 (2010): 603-617.

+ +

Morozov, Evgeny. 'Testimony to the US Commission on Security and Cooperation in Europe', Washington DC, 22 October 22 (2009)

+ +

——. The Net Delusion: The Dark Side of Internet Freedom (PublicAffairs, 2011).

+ +

Neubauer, Robert. 'Neoliberalism in the Information Age, or Vice Versa? Global Citizenship, Technology, and Hegemonic Ideology', Triple C 9.2 (2011): 190-225

+ +

Norman, Helmi, and York, Jillian C. 'West Censoring East: The Use of Western Technologies by Middle East Censors, 2010-2011', OpenNet Initiative (2010)

+ +

Program on Liberation Technology, Stanford University

+ +

RanciĂšre, Jacques. Disagreement: Politics and Philosophy (Minneapolis: University of Minnesota Press, 1999).

+ +

Shenker, Jack. 'Fury over advert claiming Egypt revolution as Vodafone’s', The Guardian, 3 June (2011)

+ +

Smith, Aaron. 'Mobile Access 2010', Pew Internet and American Life Project, 7 July (2010)

+ +

'Social Networks/Blogs Now Account for One in Every Four and a Half Minutes Online', Nielsen Wire, 15 June (2010)

+ +

Tehrani, Hamid. 'Iranian officials ‘crowd-source’ protester identities', Global Voices, 27 June 27 (2009)

+ +

Wong, Queenie. 'Social media play big role in riot probe', The Seattle Times, 16 June (2011)

+ +

York, Jillian C. 'Syria’s Twitter spambots', The Guardian, 21 April (2011)

+ +

———. 'This Week in Internet Censorship', Electronic Frontier Foundation, 15 June (2011)

+
+
+ + + \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/p_lions_es_to_wijnhaven.html b/Research/b-e-e-t.r-o-o-t.net/p_lions_es_to_wijnhaven.html new file mode 100644 index 0000000..b8221e8 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/p_lions_es_to_wijnhaven.html @@ -0,0 +1,75 @@ + + + + p.lions.es_to_wijnhaven + + + + + +
+
+ +
+ + + + + + +
+ + + + + + +
+
+

Knotboard

+

The knotboard is a piece of wood, pre-drilled with a grid of holes. Accompanying this is an assortment of pre-made polymer clay knotted links. These links can be put into the holes in multiple configurations. By doing this, one can play with the structures of various network topologies. This is a hands-on technique for meditation on networks and a departure point for other forms of representation, including writing, drawing, and walking.

+ + + + + + +
+ + + + + + +
+ + + + + + +
+ + + + + + +

Node

+ +

In telecommunications networks, a node (Latin nodus "knot") is either a point at which data is redistributed, or a communication endpoint.

+ +

Nodocentrism

+ +

Nodocentrism is an epistemological bias in which the nodes of a network take primary focus over the connections. In his book Off the Network: Disrupting the Digital World, media scholar Ulises AlĂ­ MejĂ­as outlines his theories of nodocentrism as the exclusionary network logic that cannot render anything except nodes, and in opposition, paranodality, the peripheral space, both inside and outside the network, which makes disidentification possible. Together, these concepts prepare the ground for decolonizing the internet by reframing ways of belonging to, and differentiating the self and the collective from, the network.

+ +

Paranodal

+ +

According to Ulises Alí Mejías, the paranodal is "the space between the nodes", and further; "This space surrounding the nodes is not blank". In his essay Liberation Technology and the Arab Spring: From Utopia to Atopia and Beyond, Mejías describes the paranodal as such; "it is inhabited by multitudes of paranodes that simply do not conform to the organising logic of the network, and cannot be seen through the algorithms of the network. The paranodal is not a utopia—it is not nowhere, but somewhere (beyond the nodes). It is not a heterotopia, since it is not outside the network but within it as well. The paranodal is an atopia, because it constitutes a difference that is everywhere."

+ +
+
+
+ + + \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/please_to_foshan.html b/Research/b-e-e-t.r-o-o-t.net/please_to_foshan.html new file mode 100644 index 0000000..c5657d7 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/please_to_foshan.html @@ -0,0 +1,70 @@ + + + + please.undo.undo.it to foshan-1992.pw + + + + + +
+ +
+
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+

The AEther

+

In early modern physics, the luminiferous aether (or ether) was believed to be an invisible space-filling substance or field that was a transmission medium for electromagnetic or gravitational forces.

+ +

Tait conjectures

+

Peter Guthrie Tait (1837-1901) was a Scottish mathematical physicist, whose investigations in knot theory contributed to the field of topology as a mathematical discipline. His tabulations of knots with ten crossings, which became known as the Tait conjectures, arose out of experiments he conducted with William Thomspon (Lord Kelvin) in 1867 at the University of Edinburgh.

+ +

Klein bottle

+

If you like a drink, then a Klein bottle is not a recommended receptacle. It may look vaguely like a bottle, but it doesn't enclose any volume, which means that it can't actually hold any liquid. Whatever you pour "in" will just come back out again as the Klein bottle is an example of a non-orientable surface. It has no "inside" and no "outside", instead, just a side.

+ + + +
+ +
+

Knots

+

A knot is an entanglement, an intentional complication in cordage.

+ +

Knot Theory

+

Knot theory is a field of mathematics that studies the topology of knots. In mathematical language, a knot is the embedding of a circle in 3-dimensional Euclidean space, R3.

+ +

Unknot

+

The unknot, or torus, is the first type of mathematical knot listed in knot theory. Intuitively, the unknot is a closed loop of rope without a knot in it.

+ + + +
+ +
+
+
+ + + \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/please_to_wijnhaven.html b/Research/b-e-e-t.r-o-o-t.net/please_to_wijnhaven.html new file mode 100644 index 0000000..da5fe58 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/please_to_wijnhaven.html @@ -0,0 +1,45 @@ + + + + please.undo.undo.it, nothat.bad.mn, sweetandsour.chickenkiller.com to wijnhaven + + + + + +
+ +
+

Dependencies

+

Dependencies may be technical, such as software that depends on other software in which to run successfully, or social, as in depending on others in order to achieve a goal. + +

Ethernet

+

Ethernet is a family of computer networking technologies working over WAN (Wide Area Network), LAN (Local Area Network) and MAN (Metropolitan Area Networks). The Internet Protocol is commonly carried out over ethernet, so it has become one of the key technologies that make up the Internet.

+ +

Ethernet was developed at Xerox PARC (Palo Alto Research Center) between 1973-1974. The idea was first documented in a memo written by Robert Metcalfe, who named it after the luminiferous aether, a substance that was once thought to exist as an "omnipresent, completely-passive medium for the propagation of electromagnetic waves."

+ +

Graphology

+

While topology is the study of forms that are preserved under deformations, such as stretching, crumpling or bending (but not tearing or gluing), graphology is the study of diagrams that represent these forms in a 2-dimensional space. Often 3-dimensional topologies can be collapsed into 2-dimension graphology studies.

+ + + + + + + + + +

GPS

+ +

GPS (Global Positioning Service) is a satellite radionavigation system owned by the United States government, and operated by their air force. The system uses a process of trilateration, whereby at least three satellites are needed to determine position.

+ +

Paranodal activity

+ +

As I walked from server to server, I began to notice lines. Contrails in the sky, tyre marks on bicycle lanes, overhead tram lines and their corresponding tracks...

+
+ +
+
+ + + \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/readings.css b/Research/b-e-e-t.r-o-o-t.net/readings.css new file mode 100644 index 0000000..606d555 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/readings.css @@ -0,0 +1,119 @@ +@font-face { + font-family: Courier_New; + src: url('fonts/courier_new-webfont.woff'); + font-weight: normal; + font-style: normal; + text-rendering: optimizeLegibility; +} + +body { + font-family: Courier_New; + font-size: 16px; + line-height: 120%; + position: relative; +} + +main { + margin: 12px 12px auto 12px; +} + +b { + font-family: Courier_New; + text-transform: uppercase; + font-weight: 400; +} + +h1 { + font-family: "Helvetica", "Arial", sans-serif; + font-size: 21px; + line-height: 120%; + text-align: left; + font-weight: normal; +} + +h2 { + font-family: Courier_New; + font-size: 16px; + line-height: 120%; + text-align: left; + text-transform: uppercase; + font-weight: normal; +} + +.text { + display: block; + margin-right: auto; + max-width: 640px; +} + +img { + display: block; + margin-left: auto; + margin-right: auto; + max-width: 100%; + height: auto; +} + + +/*text formatting*/ +/*.bold { + font: 'arial'; + display: inline; + text-rendering: optimizeLegibility; + text-rendering: optimizeSpeed; + letter-spacing: -0.4px; +}*/ + +.indent { + display: block; + padding-left: 2em; + padding-right: 2em; + margin-right: auto; +} + +.drawing { + mix-blend-mode: multiply; +} + + +a:link { + color: blue; + text-decoration-line: none; +} + +a:visited { + color: blue; +} + +a:hover { + color: blue; + text-decoration-line: underline; +} + +a:active { + color: blue; +} + +a.outOfNetworkLink { + color:red; +} + + +.list { + text-decoration: none; +} + +#line { + position: absolute; + /*z-index: 2;*/ +} + +#photo { + position: absolute; + margin-top: 200px; + /*z-index: 1;*/ +} + +#description { + position: absolute; +} \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/c_g_w_01.png b/Research/b-e-e-t.r-o-o-t.net/readings_images/c_g_w_01.png new file mode 100644 index 0000000..4d01cba Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/c_g_w_01.png differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/c_g_w_02.png b/Research/b-e-e-t.r-o-o-t.net/readings_images/c_g_w_02.png new file mode 100644 index 0000000..e6af638 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/c_g_w_02.png differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/c_g_w_03.png b/Research/b-e-e-t.r-o-o-t.net/readings_images/c_g_w_03.png new file mode 100644 index 0000000..f6a3f4f Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/c_g_w_03.png differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/face-cage-1-zach-blas_portrait-383x575-533x800.jpg b/Research/b-e-e-t.r-o-o-t.net/readings_images/face-cage-1-zach-blas_portrait-383x575-533x800.jpg new file mode 100644 index 0000000..f18f668 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/face-cage-1-zach-blas_portrait-383x575-533x800.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/face-cage-2-elle-mehrmand_portrait-383x575-533x800.jpg b/Research/b-e-e-t.r-o-o-t.net/readings_images/face-cage-2-elle-mehrmand_portrait-383x575-533x800.jpg new file mode 100644 index 0000000..5153bbd Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/face-cage-2-elle-mehrmand_portrait-383x575-533x800.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/face-cage-3-micha-cardenas_portrait-383x575-533x800.jpg b/Research/b-e-e-t.r-o-o-t.net/readings_images/face-cage-3-micha-cardenas_portrait-383x575-533x800.jpg new file mode 100644 index 0000000..b01ec9b Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/face-cage-3-micha-cardenas_portrait-383x575-533x800.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/face-cage-4-paul-mpagi-sepuya_portrait-383x575-533x800.jpg b/Research/b-e-e-t.r-o-o-t.net/readings_images/face-cage-4-paul-mpagi-sepuya_portrait-383x575-533x800.jpg new file mode 100644 index 0000000..37d02e6 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/face-cage-4-paul-mpagi-sepuya_portrait-383x575-533x800.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-01-baran-on-distributed-communications-1964.png b/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-01-baran-on-distributed-communications-1964.png new file mode 100644 index 0000000..88fdef2 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-01-baran-on-distributed-communications-1964.png differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-02-ponsot-klein-worms-1971.png b/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-02-ponsot-klein-worms-1971.png new file mode 100644 index 0000000..b46da20 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-02-ponsot-klein-worms-1971.png differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-03-antfarm-TruckstopNetwork-recto-1971.png b/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-03-antfarm-TruckstopNetwork-recto-1971.png new file mode 100644 index 0000000..5862817 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-03-antfarm-TruckstopNetwork-recto-1971.png differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-04-SRI-internetwork-diagram-1977.png b/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-04-SRI-internetwork-diagram-1977.png new file mode 100644 index 0000000..c924b07 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-04-SRI-internetwork-diagram-1977.png differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-05-antfarm-MediaVan-mobile-vt-studio-1971.png b/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-05-antfarm-MediaVan-mobile-vt-studio-1971.png new file mode 100644 index 0000000..18473f5 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/hu-05-antfarm-MediaVan-mobile-vt-studio-1971.png differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_form.jpg b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_form.jpg new file mode 100644 index 0000000..bd43bbd Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_form.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_form.png b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_form.png new file mode 100644 index 0000000..083da44 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_form.png differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_form_640.jpg b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_form_640.jpg new file mode 100644 index 0000000..62f25f2 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_form_640.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_01.jpg b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_01.jpg new file mode 100644 index 0000000..924e40a Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_01.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_02.jpg b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_02.jpg new file mode 100644 index 0000000..2a4ab0e Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_02.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_03.jpg b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_03.jpg new file mode 100644 index 0000000..164b92f Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_03.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_04.jpg b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_04.jpg new file mode 100644 index 0000000..17354b1 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_04.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_05.jpg b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_05.jpg new file mode 100644 index 0000000..59b1c1e Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_05.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_06.jpg b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_06.jpg new file mode 100644 index 0000000..b8dd03d Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/klein_worm_06.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/paranode-line.jpg b/Research/b-e-e-t.r-o-o-t.net/readings_images/paranode-line.jpg new file mode 100644 index 0000000..0ff6849 Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/paranode-line.jpg differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_images/software.png b/Research/b-e-e-t.r-o-o-t.net/readings_images/software.png new file mode 100644 index 0000000..cb2928e Binary files /dev/null and b/Research/b-e-e-t.r-o-o-t.net/readings_images/software.png differ diff --git a/Research/b-e-e-t.r-o-o-t.net/readings_index.html b/Research/b-e-e-t.r-o-o-t.net/readings_index.html new file mode 100644 index 0000000..d068624 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/readings_index.html @@ -0,0 +1,31 @@ + + + +Readings + + + + + +
+
+

Beyond the Internet and All Control Diagrams
+ Simone Browne and Zach Blas

+

Biotopology 1972
+ Warren Brodey

+

Cybernetic Guerilla Warfare
+ Paul Ryan

+

Liberation Technology and the Arab Spring: From Utopia to Atopia and Beyond
+ Ulises A. Mejias

+

SOFTWARE
+ R. Buckmister Fuller

+

The Psychotopology of Everyday Life
+ Hakim Bey

+

Theory of the DĂ©rive
+ Guy Debord

+

Truckstops on the Information Superhighway: Ant Farm, SRI, and the Cloud
+ Tung-Hui Hu

+
+
+ + \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/style.css b/Research/b-e-e-t.r-o-o-t.net/style.css new file mode 100644 index 0000000..6621125 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/style.css @@ -0,0 +1,155 @@ +@font-face { + font-family: Courier_New; + src: url('fonts/courier_new-webfont.woff'); + font-weight: normal; + font-style: normal; + text-rendering: optimizeLegibility; +} + +@font-face { + font-family: Courier_New_bold; + src: url('fonts/courier_new_bold-webfont.woff'); + font-weight: normal; + font-style: normal; + text-rendering: optimizeLegibility; +} + +@font-face { + font-family: Times_New_Roman; + src: url('fonts/Times New Roman.ttf'); + font-weight: normal; + font-style: normal; + text-rendering: optimizeLegibility; +} + +body { + font-family: 'Courier New', 'Courier', monospace; + font-size: 16px; + line-height: 120%; + position: relative; +} + +main { + margin: 12px 12px auto 12px; +} + +b { +/* font-family: 'Courier New';*/ + font-family: 'courier_newbold' 'Courier New', monospace; + font-weight: 600; +} + +h1 { + text-decoration: underline; + line-height: 120%; + text-align: left; + font-weight: normal; +} + +h2 { + font-size: inherit; + line-height: 120%; + text-align: left; + text-decoration: underline; + font-weight: normal; +} + +div { +/* display: block; + float: left; + margin-right: auto;*/ +/* max-width: 720px;*/ +} + +img { + display: block; + margin-left: auto; + margin-right: auto; + max-width: 100%; + height: auto; +} + +.indent { + display: block; + padding-left: 40px; +} + +.text { + position: absolute; + display: block; + float: left; + margin-right: auto; + max-width: 640px; +} + +.drawing { + mix-blend-mode: multiply; +} + +.fullImage { + max-width: 66%; + display: block; +} + +#left { + text-align: left; +} + +#center { + text-align: center; +} + +#right { + text-align: right; +} + +a:link { + color: blue; + text-decoration: none; +} + +a:visited { + color: blue; +} + +a:hover { +/* text-decoration-line: underline;*/ +} + +a:active { +/* color: blue;*/ +} + +/*how to style this???*/ +a.outOfNetworkLink { + color:red; + text-decoration-line: +} + +.list { + text-decoration: none; +} + +#line { + position: absolute; + z-index: -1; +} + +#ciao_line { + position: inherit; + z-index: inherit; + left: 0px; + top: 0; + height: 3000px; +} + +#photo { + position: absolute; + margin-top: 200px; + /*z-index: 1;*/ +} + +#description { + position: absolute; +} + diff --git a/Research/b-e-e-t.r-o-o-t.net/the_psychotopology_of_everyday_life.html b/Research/b-e-e-t.r-o-o-t.net/the_psychotopology_of_everyday_life.html new file mode 100644 index 0000000..1c47478 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/the_psychotopology_of_everyday_life.html @@ -0,0 +1,41 @@ + + + +the psychotopology of everyday life + + + + + +
+
+

The Psychotopology of Everyday Life
+ Hakim Bey

+ —
+

Excerpt from T.A.Z., + The Temporary Autonomous Zone, Autonomedia, 1991
+ Full text available at theanarchistlibrary.org

+
+

THE CONCEPT OF THE TAZ arises first out of a critique of Revolution, and an appreciation of the Insurrection. The former labels the latter a failure; but for us uprising represents a far more interesting possibility, from the standard of a psychology of liberation, than all the "successful" revolutions of bourgeoisie, communists, fascists, etc.

+

The second generating force behind the TAZ springs from the historical development I call "the closure of the map." The last bit of Earth unclaimed by any nation-state was eaten up in 1899. Ours is the first century without terra incognita, without a frontier. Nationality is the highest principle of world governance — not one speck of rock in the South Seas can be left open, not one remote valley, not even the Moon and planets. This is the apotheosis of "territorial gangsterism." Not one square inch of Earth goes unpoliced or untaxed...in theory.

+

The "map" is a political abstract grid, a gigantic con enforced by the carrot/stick conditioning of the "Expert" State, until for most of us the map becomes the territory — no longer "Turtle Island," but "the USA." And yet because the map is an abstraction it cannot cover Earth with 1:1 accuracy. Within the fractal complexities of actual geography the map can see only dimensional grids. Hidden enfolded immensities escape the measuring rod. The map is not accurate; the map cannot be accurate.

+

So — Revolution is closed, but insurgency is open. For the time being we concentrate our force on temporary "power surges," avoiding all entanglements with "permanent solutions." + And — the map is closed, but the autonomous zone is open. Metaphorically it unfolds within the fractal dimensions invisible to the cartography of Control. And here we should introduce the concept of psychotopology (and -topography) as an alternative "science" to that of the State's surveying and mapmaking and "psychic imperialism." Only psychotopography can draw 1:1 maps of reality because only the human mind provides sufficient complexity to model the real. But a 1:1 map cannot "control" its territory because it is virtually identical with its territory. It can only be used to suggest, in a sense gesture towards, certain features. We are looking for "spaces" (geographic, social, cultural, imaginal) with potential to flower as autonomous zones — and we are looking for times in which these spaces are relatively open, either through neglect on the part of the State or because they have somehow escaped notice by the mapmakers, or for whatever reason. Psychotopology is the art of dowsing for potential TAZs.

+

The closures of Revolution and of the map, however, are only the negative sources of the TAZ; much remains to be said of its positive inspirations. Reaction alone cannot provide the energy needed to "manifest" a TAZ. An uprising must be for something as well.

+

1. First, we can speak of a natural anthropology of the TAZ. The nuclear family is the base unit of consensus society, but not of the TAZ. ("Families! — how I hate them! the misers of love!" — Gide) The nuclear family, with its attendant "oedipal miseries," appears to have been a Neolithic invention, a response to the "agricultural revolution" with its imposed scarcity and its imposed hierarchy. The Paleolithic model is at once more primal and more radical: the band. The typical hunter/gatherer nomadic or semi- nomadic band consists of about 50 people. Within larger tribal societies the band-structure is fulfilled by clans within the tribe, or by sodalities such as initiatic or secret societies, hunt or war societies, gender societies, "children's republics," and so on. If the nuclear family is produced by scarcity (and results in miserliness), the band is produced by abundance — and results in prodigality. The family is closed, by genetics, by the male's possession of women and children, by the hierarchic totality of agricultural/industrial society. The band is open — not to everyone, of course, but to the affinity group, the initiates sworn to a bond of love. The band is not part of a larger hierarchy, but rather part of a horizontal pattern of custom, extended kinship, contract and alliance, spiritual affinities, etc. (American Indian society preserves certain aspects of this structure even now.)

+

In our own post-Spectacular Society of Simulation many forces are working — largely invisibly — to phase out the nuclear family and bring back the band. Breakdowns in the structure of Work resonate in the shattered "stability" of the unit-home and unit-family. One's "band" nowadays includes friends, ex- spouses and lovers, people met at different jobs and pow-wows, affinity groups, special interest networks, mail networks, etc. The nuclear family becomes more and more obviously a trap, a cultural sinkhole, a neurotic secret implosion of split atoms — and the obvious counter-strategy emerges spontaneously in the almost unconscious rediscovery of the more archaic and yet more post-industrial possibility of the band.

+

2. The TAZ as festival. Stephen Pearl Andrews once offered, as an image of anarchist society, the dinner party, in which all structure of authority dissolves in conviviality and celebration (see Appendix C). Here we might also invoke Fourier and his concept of the senses as the basis of social becoming — "touch-rut" and "gastrosophy," and his paean to the neglected implications of smell and taste. The ancient concepts of jubilee and saturnalia originate in an intuition that certain events lie outside the scope of "profane time," the measuring-rod of the State and of History. These holidays literally occupied gaps in the calendar — intercalary intervals. By the Middle Ages, nearly a third of the year was given over to holidays. Perhaps the riots against calendar reform had less to do with the "eleven lost days" than with a sense that imperial science was conspiring to close up these gaps in the calendar where the people's freedoms had accumulated — a coup d'etat, a mapping of the year, a seizure of time itself, turning the organic cosmos into a clockwork universe. The death of the festival.

+

Participants in insurrection invariably note its festive aspects, even in the midst of armed struggle, danger, and risk. The uprising is like a saturnalia which has slipped loose (or been forced to vanish) from its intercalary interval and is now at liberty to pop up anywhere or when. Freed of time and place, it nevertheless possesses a nose for the ripeness of events, and an affinity for the genius loci; the science of psychotopology indicates "flows of forces" and "spots of power" (to borrow occultist metaphors) which localize the TAZ spatio-temporally, or at least help to define its relation to moment and locale.

+

The media invite us to "come celebrate the moments of your life" with the spurious unification of commodity and spectacle, the famous non-event of pure representation. In response to this obscenity we have, on the one hand, the spectrum of refusal (chronicled by the Situationists, John Zerzan, Bob Black et al.) — and on the other hand, the emergence of a festal culture removed and even hidden from the would-be managers of our leisure. "Fight for the right to party" is in fact not a parody of the radical struggle but a new manifestation of it, appropriate to an age which offers TVs and telephones as ways to "reach out and touch" other human beings, ways to "Be There!"

+

Pearl Andrews was right: the dinner party is already "the seed of the new society taking shape within the shell of the old" (IWW Preamble). The sixties-style "tribal gathering," the forest conclave of eco-saboteurs, the idyllic Beltane of the neo-pagans, anarchist conferences, gay faery circles...Harlem rent parties of the twenties, nightclubs, banquets, old-time libertarian picnics — we should realize that all these are already "liberated zones" of a sort, or at least potential TAZs. Whether open only to a few friends, like a dinner party, or to thousands of celebrants, like a Be-In, the party is always "open" because it is not "ordered"; it may be planned, but unless it "happens" it's a failure. The element of spontaneity is crucial.

+

The essence of the party: face-to-face, a group of humans synergize their efforts to realize mutual desires, whether for good food and cheer, dance, conversation, the arts of life; perhaps even for erotic pleasure, or to create a communal artwork, or to attain the very transport of bliss — in short, a "union of egoists" (as Stirner put it) in its simplest form — or else, in Kropotkin's terms, a basic biological drive to "mutual aid." (Here we should also mention Bataille's "economy of excess" and his theory of potlatch culture.)

+

3. Vital in shaping TAZ reality is the concept of psychic nomadism (or as we jokingly call it, "rootless cosmopolitanism"). Aspects of this phenomenon have been discussed by Deleuze and Guattari in Nomadology and the War Machine, by Lyotard in Driftworks and by various authors in the "Oasis" issue of Semiotext(e). We use the term "psychic nomadism" here rather than "urban nomadism," "nomadology," "driftwork," etc., simply in order to garner all these concepts into a single loose complex, to be studied in light of the coming- into-being of the TAZ. "The death of God," in some ways a de- centering of the entire "European" project, opened a multi-perspectived post- ideological worldview able to move "rootlessly" from philosophy to tribal myth, from natural science to Taoism — able to see for the first time through eyes like some golden insect's, each facet giving a view of an entirely other world. + But this vision was attained at the expense of inhabiting an epoch where speed and "commodity fetishism" have created a tyrannical false unity which tends to blur all cultural diversity and individuality, so that "one place is as good as another." This paradox creates "gypsies," psychic travellers driven by desire or curiosity, wanderers with shallow loyalties (in fact disloyal to the "European Project" which has lost all its charm and vitality), not tied down to any particular time and place, in search of diversity and adventure...This description covers not only the X-class artists and intellectuals but also migrant laborers, refugees, the "homeless," tourists, the RV and mobile-home culture — also people who "travel" via the Net, but may never leave their own rooms (or those like Thoreau who "have travelled much — in Concord"); and finally it includes "everybody," all of us, living through our automobiles, our vacations, our TVs, books, movies, telephones, changing jobs, changing "lifestyles," religions, diets, etc., etc.

+

Psychic nomadism as a tactic, what Deleuze & Guattari metaphorically call "the war machine," shifts the paradox from a passive to an active and perhaps even "violent" mode. "God"'s last throes and deathbed rattles have been going on for such a long time — in the form of Capitalism, Fascism, and Communism, for example — that there's still a lot of "creative destruction" to be carried out by post-Bakuninist post-Nietzschean commandos or apaches (literally "enemies") of the old Consensus. These nomads practice the razzia, they are corsairs, they are viruses; they have both need and desire for TAZs, camps of black tents under the desert stars, interzones, hidden fortified oases along secret caravan routes, "liberated" bits of jungle and bad-land, no-go areas, black markets, and underground bazaars.

+

These nomads chart their courses by strange stars, which might be luminous clusters of data in cyberspace, or perhaps hallucinations. Lay down a map of the land; over that, set a map of political change; over that, a map of the Net, especially the counter-Net with its emphasis on clandestine information-flow and logistics — and finally, over all, the 1:1 map of the creative imagination, aesthetics, values. The resultant grid comes to life, animated by unexpected eddies and surges of energy, coagulations of light, secret tunnels, surprises.

+
+
+ + + + diff --git a/Research/b-e-e-t.r-o-o-t.net/theory_of_the_derive.html b/Research/b-e-e-t.r-o-o-t.net/theory_of_the_derive.html new file mode 100644 index 0000000..da95745 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/theory_of_the_derive.html @@ -0,0 +1,55 @@ + + + +theory of the derive + + + + + +
+
+
+

Theory of the DĂ©rive
+ Guy Debord

+ —
+

“ThĂ©orie de la dĂ©rive” was published in Internationale Situationniste #2 (Paris, December 1958). A slightly different version was first published in the Belgian surrealist journal Les LĂšvres Nues #9 (November 1956) along with accounts of two dĂ©rives. This translation by Ken Knabb is from the Situationist International Anthology (Revised and Expanded Edition, 2006). No copyright.

+
+

One of the basic situationist practices is the dérive,(1) a technique of rapid passage through varied ambiences. Dérives involve playful-constructive behavior and awareness of psychogeographical effects, and are thus quite different from the classic notions of journey or stroll.

+

In a dérive one or more persons during a certain period drop their relations, their work and leisure activities, and all their other usual motives for movement and action, and let themselves be drawn by the attractions of the terrain and the encounters they find there. Chance is a less important factor in this activity than one might think: from a dérive point of view cities have psychogeographical contours, with constant currents, fixed points and vortexes that strongly discourage entry into or exit from certain zones.

+

But the dérive includes both this letting-go and its necessary contradiction: the domination of psychogeographical variations by the knowledge and calculation of their possibilities. In this latter regard, ecological science, despite the narrow social space to which it limits itself, provides psychogeography with abundant data.

+

The ecological analysis of the absolute or relative character of fissures in the urban network, of the role of microclimates, of distinct neighborhoods with no relation to administrative boundaries, and above all of the dominating action of centers of attraction, must be utilized and completed by psychogeographical methods. The objective passional terrain of the dérive must be defined in accordance both with its own logic and with its relations with social morphology.

+

In his study Paris et l'agglomération parisienne (BibliothÚque de Sociologie Contemporaine, P.U.F., 1952) Chombart de Lauwe notes that "an urban neighborhood is determined not only by geographical and economic factors, but also by the image that its inhabitants and those of other neighborhoods have of it." In the same work, in order to illustrate "the narrowness of the real Paris in which each individual lives ... within a geographical area whose radius is extremely small," he diagrams all the movements made in the space of one year by a student living in the 16th Arrondissement. Her itinerary forms a small triangle with no significant deviations, the three apexes of which are the School of Political Sciences, her residence and that of her piano teacher.

+

Such data — examples of a modern poetry capable of provoking sharp emotional reactions (in this particular case, outrage at the fact that anyone's life can be so pathetically limited) — or even Burgess's theory of Chicago's social activities as being distributed in distinct concentric zones, will undoubtedly prove useful in developing dĂ©rives.

+

If chance plays an important role in dérives this is because the methodology of psychogeographical observation is still in its infancy. But the action of chance is naturally conservative and in a new setting tends to reduce everything to habit or to an alternation between a limited number of variants. Progress means breaking through fields where chance holds sway by creating new conditions more favorable to our purposes. We can say, then, that the randomness of a dérive is fundamentally different from that of the stroll, but also that the first psychogeographical attractions discovered by dérivers may tend to fixate them around new habitual axes, to which they will constantly be drawn back.

+

An insufficient awareness of the limitations of chance, and of its inevitably reactionary effects, condemned to a dismal failure the famous aimless wandering attempted in 1923 by four surrealists, beginning from a town chosen by lot: Wandering in open country is naturally depressing, and the interventions of chance are poorer there than anywhere else. But this mindlessness is pushed much further by a certain Pierre Vendryes (in MĂ©dium, May 1954), who thinks he can relate this anecdote to various probability experiments, on the ground that they all supposedly involve the same sort of antideterminist liberation. He gives as an example the random distribution of tadpoles in a circular aquarium, adding, significantly, "It is necessary, of course, that such a population be subject to no external guiding influence." From that perspective, the tadpoles could be considered more spontaneously liberated than the surrealists, since they have the advantage of being "as stripped as possible of intelligence, sociability and sexuality," and are thus "truly independent from one another."

+

At the opposite pole from such imbecilities, the primarily urban character of the dérive, in its element in the great industrially transformed cities that are such rich centers of possibilities and meanings, could be expressed in Marx's phrase: "Men can see nothing around them that is not their own image; everything speaks to them of themselves. Their very landscape is alive."

+

One can dérive alone, but all indications are that the most fruitful numerical arrangement consists of several small groups of two or three people who have reached the same level of awareness, since cross-checking these different groups' impressions makes it possible to arrive at more objective conclusions. It is preferable for the composition of these groups to change from one dérive to another. With more than four or five participants, the specifically dérive character rapidly diminishes, and in any case it is impossible for there to be more than ten or twelve people without the dérive fragmenting into several simultaneous dérives. The practice of such subdivision is in fact of great interest, but the difficulties it entails have so far prevented it from being organized on a sufficient scale.

+

The average duration of a dérive is one day, considered as the time between two periods of sleep. The starting and ending times have no necessary relation to the solar day, but it should be noted that the last hours of the night are generally unsuitable for dérives.

+

But this duration is merely a statistical average. For one thing, a dérive rarely occurs in its pure form: it is difficult for the participants to avoid setting aside an hour or two at the beginning or end of the day for taking care of banal tasks; and toward the end of the day fatigue tends to encourage such an abandonment. But more importantly, a dérive often takes place within a deliberately limited period of a few hours, or even fortuitously during fairly brief moments; or it may last for several days without interruption. In spite of the cessations imposed by the need for sleep, certain dérives of a sufficient intensity have been sustained for three or four days, or even longer. It is true that in the case of a series of dérives over a rather long period of time it is almost impossible to determine precisely when the state of mind peculiar to one dérive gives way to that of another. One sequence of dérives was pursued without notable interruption for around two months. Such an experience gives rise to new objective conditions of behavior that bring about the disappearance of a good number of the old ones.(2)

+

The influence of weather on dérives, although real, is a significant factor only in the case of prolonged rains, which make them virtually impossible. But storms or other types of precipitation are rather favorable for dérives.

+

The spatial field of a dérive may be precisely delimited or vague, depending on whether the goal is to study a terrain or to emotionally disorient oneself. It should not be forgotten that these two aspects of dérives overlap in so many ways that it is impossible to isolate one of them in a pure state. But the use of taxis, for example, can provide a clear enough dividing line: If in the course of a dérive one takes a taxi, either to get to a specific destination or simply to move, say, twenty minutes to the west, one is concerned primarily with personal disorientation. If, on the other hand, one sticks to the direct exploration of a particular terrain, one is concentrating primarily on research for a psychogeographical urbanism.

+

In every case the spatial field depends first of all on the point of departure — the residence of the solo dĂ©river or the meeting place selected by a group. The maximum area of this spatial field does not extend beyond the entirety of a large city and its suburbs. At its minimum it can be limited to a small self-contained ambience: a single neighborhood or even a single block of houses if it's interesting enough (the extreme case being a static-dĂ©rive of an entire day within the Saint-Lazare train station).

+

The exploration of a fixed spatial field entails establishing bases and calculating directions of penetration. It is here that the study of maps comes in — ordinary ones as well as ecological and psychogeographical ones — along with their correction and improvement. It should go without saying that we are not at all interested in any mere exoticism that may arise from the fact that one is exploring a neighborhood for the first time. Besides its unimportance, this aspect of the problem is completely subjective and soon fades away.

+

In the "possible rendezvous," on the other hand, the element of exploration + is minimal in comparison with that of behavioral disorientation. The subject is invited to come alone to a certain place at a specified time. He is freed from the bothersome obligations of the ordinary rendezvous since there is no one to wait for. But since this "possible rendezvous" has brought him without warning to a place he may or may not know, he observes the surroundings. It may be that the same spot has been specified for a "possible rendezvous" for someone else whose identity he has no way of knowing. Since he may never even have seen the other person before, he will be encouraged to start up conversations with various passersby. He may meet no one, or he may even by + chance meet the person who has arranged the "possible rendezvous." In any case, particularly if the time and place have been well chosen, his use of time will take an unexpected turn. He may even telephone someone else who doesn't know where the first "possible rendezvous" has taken him, in order to ask for another one to be specified. One can see the virtually unlimited resources of this pastime.

+

Our rather anarchic lifestyle and even certain amusements considered dubious that have always been enjoyed among our entourage — slipping by night into houses undergoing demolition, hitchhiking nonstop and without destination through Paris during a transportation strike in the name of adding to the confusion, wandering in subterranean catacombs forbidden to the public, etc. — are expressions of a more general sensibility which is no different from that of the dĂ©rive. Written descriptions can be no more than passwords to this great game.

+

The lessons drawn from dérives enable us to draft the first surveys of the psychogeographical articulations of a modern city. Beyond the discovery of unities of ambience, of their main components and their spatial localization, one comes to perceive their principal axes of passage, their exits and their defenses. One arrives at the central hypothesis of the existence of psychogeographical pivotal points. One measures the distances that actually separate two regions of a city, distances that may have little relation with the physical distance between them. With the aid of old maps, aerial photographs and experimental dérives, one can draw up hitherto lacking maps of influences, maps whose inevitable imprecision at this early stage is no worse than that of the earliest navigational charts. The only difference is that it is no longer a matter of precisely delineating stable continents, but of changing architecture and urbanism.

+

Today the different unities of atmosphere and of dwellings are not precisely marked off, but are surrounded by more or less extended bordering regions. The + most general change that dérive experiences lead to proposing is the constant diminution of these border regions, up to the point of their complete suppression.

+

Within architecture itself, the taste for dériving tends to promote all sorts of new forms of labyrinths made possible by modern techniques of construction. Thus in March 1955 the press reported the construction in New York of a building in which one can see the first signs of an opportunity to dérive inside an apartment:

+
+

"The apartments of the helicoidal building will be shaped like slices of cake. One will be able to enlarge or reduce them by shifting movable partitions. The half-floor gradations avoid limiting the number of rooms, since the tenant can request the use of the adjacent section on either upper or lower levels. With this setup three four-room apartments can be transformed into one twelve-room apartment in less than six hours."

+
+

(To be continued.)

+

GUY DEBORD
+ 1958

+

[TRANSLATOR'S NOTES]

+

1.dérive: literally "drift" or "drifting". Like détournement, this term has usually been anglicized as both a noun and a verb.

+

2. "The dérive (with its flow of acts, its gestures, its strolls, its encounters) was to the totality exactly what psychoanalysis (in the best sense) is to language. Let yourself go with the flow of words, says the psychoanalyst. He listens, until the moment when he rejects or modifies (one could say detourns) a word, an expression or a definition. The dérive is certainly a technique, almost a therapeutic one. But just as analysis unaccompanied with anything else is almost always contraindicated, so continual dériving is dangerous to the extent that the individual, having gone too far (not without bases, but...) without defenses, is threatened with explosion, dissolution, dissociation, disintegration. And thence the relapse into what is termed 'ordinary life,' that is to say, in reality, into 'petrified life.' In this regard I now repudiate my Formulary's propaganda for a continuous dérive. It could be continuous like the poker game in Las Vegas, but only for a certain period, limited to a weekend for some people, to a week as a good average; a month is really pushing it. In 1953-1954 we dérived for three or four months straight. That's the extreme limit. It's a miracle it didn't kill us" (Ivan Chtcheglov, excerpt from a 1963 letter to MichÚle Bernstein and Guy Debord, reprinted inInternationale Situationniste #9, p.38).

+
+
+ + + \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/truckstops_on_the_information_superhighway.html b/Research/b-e-e-t.r-o-o-t.net/truckstops_on_the_information_superhighway.html new file mode 100644 index 0000000..2b73d4c --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/truckstops_on_the_information_superhighway.html @@ -0,0 +1,106 @@ + + + +truckstops on the information superhighway + + + + + +
+
+

Truckstops on the Information Superhighway: Ant Farm, SRI, and the Cloud
+ Tung-Hui Hu, Assistant Professor, English, University of Michigan

+ —
+

First published at http://median.newmediacaucus.org/art-infrastructures-hardware/truckstops-on-the-information-superhighway-ant-farm-sri-and-the-cloud/

+
+

To start keeping track of network glitches — to watch, for example, all Chinese network traffic routed through a 1,700 square foot house in suburban Cheyenne, Wyoming on January 21, 2014 — is to quickly realize that the Internet’s shape is far goofier than we can imagine. The Internet bears little resemblance to the elegant mesh with which RAND Corporation scientist Paul Baran depicted a distributed network in 1964. (fig. 1c). Filled with complex vortices and firewalls and black holes, the Internet might be depicted more accurately as a series of topological contortions (fig. 2)

+ +
+

Fig. 1. On Distributed Networks, 1964, Paul Baran, diagram for RAND research memoranda 3420, © RAND Corporation. (Used with permission.)

+
+ +
+

Fig. 2. Klein Worms, 1971, Claude Ponsot, illustration for Paul Ryan, “Cybernetic Guerilla Warfare,” in Radical Software 1:3.

+
+

Why is it, then, that digital media scholars persist in picturing the Internet as a distributed, grid or mesh-shaped network? In part, this is because Baran’s diagrams have been misinterpreted as a historical narrative: a move from what Paul Edwards has termed the “closed-world” discourse of the 1950s and early 1960s to decentralized and, later, distributed computer networks. [1] As evidence, these scholars cite the progression from the centralized, command-and-control structures of Air Force computer rooms to ARPAnet and the Internet. [2] Though this is a misreading of Baran’s article, it remains a seductive mythology because it explains the dispersion of power through the formal qualities of the computer networks that supposedly enable it. This model serves digital studies well because it allows the Internet to stand for network culture as a whole, and uses the technology as a proxy for larger societal changes. But what happens if you take the digital out of the equation?

+

This essay returns to the moment that this shift away from centralized networks is said to occur to tell an alternate story. It is certainly true that in the 1960s and 1970s, a group of engineers from California were actively trying to find an alternative to centralized networks. Not all of these engineers were working for RAND or other military-funded laboratories, however; many of them were artists. [3] And for them, as for the rest of the country, the networks they were designing did not necessarily involve digital data. Instead, at that moment, television was the centralized system that needed to be subverted or at least radically redesigned. Network television — famously described as a “wasteland” — was a monolithic schedule of programming pumped out by NBC, CBS, ABC, and, until it folded, DuMont: national broadcasters which homogeneized the flow of information. The studios broadcast to the homes; information flow was a one-way arrow — at least until a 1969 Federal Communications Commission decision allowing community access television (CATV), better known as cable. Television delivered the network. But video and cable had the potential to hijack it.

+

In 1970, the same year that computer scientist John McCarthy asked if home computer networks could lure TV viewers away from the tube with alternative sources of information, an artist group called Raindance Corporation proposed a “Center for Decentralized Television.” [4] A playful parody of the RAND Corporation’s 1964 design for a decentralized digital network, its name suggested its paradoxically centralizing tendencies. Formed in response to news that RAND Corporation had begun to study cable networks (or, as one contributor speculated, was developing mind-control techniques), the video collective wrote: “We believe culture needs new information structures, not just improved content pumped through existing ones,” and their unrealized “Center” would have served as a re-granting agency for video artists. [5] An early issue of the collective’s newsletter, Radical Software, suggests the thrill of imagining new information structures: the typography of Frank Gilette’s piece, “Loop-de-Loop,” depicts arrows twisted to form loops that lead nowhere. Claude Ponsot illustrates an article about the structure of cybernetics and guerilla tactics with whimsical mathematical diagrams that I invoked earlier. Dubbed “Klein worms,” after the topologically impossible Klein bottle, we are still within the ballpark of Baran’s network diagrams, but just barely. (Figure fig. 2)

+

These earlier moments of reconfiguring the network structure hold uncanny parallels to present-day digital networks. The first page of Radical Software’s first issue is an excerpt from Gene Youngblood’s book, The Videosphere; a later advertisement summarizes his book as a description of a “single unified system, a ‘decentralized feedback communication network’” that would unite five different mediums: cable TV, portable video, storage networks, “time-shared computer utilities,” and “the domestic satellite system.” Youngblood’s videosphere is often understood metaphorically, as a reiteration of Marshall McLuhan, but here Youngblood turns his attention to specific networks: the FCC’s decision to allow MCI (then called Microwave Communications Inc.) to compete with AT&T by renting CATV circuits; a “‘quasi-laser’ broadcasting system
 [that] transmits up to 15 miles,” a technology pioneered by MCI that will anticipate fiber-optic cable; the US Defense Department satellites, along with Soviet and the commercial Comsat networks. Youngblood’s union of heterogeneous networks is eerily similar to the union of satellite, land, and radio networks that was dubbed, five years later, the Internet. Add in storage networks and “time-shared computer utilities,” and you have cloud computing – the system that allows computing resources to be accessed seamlessly from cell phones, tablets, and computers.

+

Excited by the potential of this new technology, the late 1960s and early 1970s became a test bed for questions that would preoccupy computer scientists and artists alike: If you could design a two-way, ‘feedback network,’ could you even out the structures of power and create a more participatory media environment? And if you could change the media, would its viewers see differently? These are large questions, but ones which have inevitably lost their potency over time, because so many of these structures have come into fruition: viewers feed back images and videos to television shows all the time, as with citizen-generated videos that regularly air on CNN, and YouTube has become an even more eclectic repository for images than cable has ever been. We take distributed networks, and their properties, such as two-way interaction, for granted; the rhetoric of the artists is too utopian to be taken as more than a product of its time. And as David Joselit reminds us, while video and cable may be a “cautionary tale regarding the Internet’s claims as a site for radical democracy,” it is an embarrassing lesson to learn – particularly given how quickly cable, like the Internet, became commercialized and assimilated into the system of power it once claimed to subvert. [6]

+

These artistic attempts to critique and reconfigure the network of television at the same time as ARPAnet and the Internet suggest that a larger, generalizable discourse about networks was at play at this time that was not limited to computer technology. Essayist Joan Didion aptly summed up the massive social upheaval in the late 1960s by invoking Yeats: “The center was not holding.” [7] Despite a smoothly functioning marketplace and a high GNP, the gravitational pull of these mechanisms no longer seemed enough. Normative culture seemed torn by what Didion described as a sense of unease. And that decentralized networks were created in response — whether as alternatives to the centralized system of information distribution, or for upholding the center’s survivability by dispersing its power – does not strike me as a coincidence. [8]

+

I would like to open a window onto this larger discourse by examining one of Radical Software’s collaborators, the San Francisco-based collective Ant Farm (Chip Lord, Doug Michaels, Hudson Marquez, and Curtis Schreier). Ant Farm’s proposal for a media distribution structure called a “Truckstop Network” allow us to see how fertile the ground was for alternate network structures. The caveat is that my necessarily abbreviated consideration of a single Ant Farm project misses not only the rest of their work, but also contemporaneous examples from the rich history of video; for interested readers, I direct them to references that take up this subject in more depth. [9]

+

With this caveat in mind, let us move to 1970, when a modified Chevrolet van with a clear plastic bubble and a distinctive antenna hit the road. Serving as Ant Farm’s temporary home for a year, it contained a TV window, a videotape setup, silver roof-mounted speaker domes, and a dashboard-mounted camera, all hardware “reminiscent of a B-52.” [10] It was quickly named the Media Van, and became an integral part of what they eventually dubbed a “Truckstop Network.” Ant Farm bought several of the new Portapaks and went on tour, stopping at several colleges, shooting video of “dancing chickens, an okra farmer, a ground-breaking in Scottsdale, aspiring pop singer Johnny Romeo belting out a ballad in the Yale School of Architecture
” [11] If the television network refused to broadcast these video images, the Media Van would bring it directly to the audience’s door.

+

This van drove off during a moment of transition for highway culture. Through the 1960s, Jonathan Crary argues, the automobile and the television worked hand-in-hand in popular culture to conceal the growing complexity of capitalist representation. A highway route had an effect much like television, acting as a sort of TV channel that seemed to enable a driver/viewer’s autonomy by giving him or her the power to choose — even as it cloaked the mechanism of capital behind it. [12] In the 1970s, Crary continues, television “began to be grafted onto other networks
 the screens of home computer and word processor,” and the computer’s window replacing the car’s window as the predominant space of the virtual. [13] Though the ideal of car culture had begun to sour — a matter brought to a head by the 1973 oil crisis – it was precisely the highway’s identification with Cold War surplus, rusted roadside attractions, and its lack of newness that made it fertile ground for artistic reappropriation. [14]

+

Thus Truckstop Network, was more than a road trip tour; it was also a statement about mobility itself. Standing at the hinge between auto window and computer window, it proposed a countrywide network of truckstops for “media nomads.” Placed just off the highway, each truckstop would offer an array of services for those living on the road: housing, electricity, and water; truck repair and a communal kitchen; but also communications services – computers and video equipment – seen, “like food and gas, as nutrients necessary for survival.” [15]

+ +
+

Fig. 3. Truckstop Network Placemat (recto), 1971, Ant Farm, offset printing on paper (2-sided); 17 x 11 in.; University of California, Berkeley Art Museum and Pacific Film Archive. Photo: Benjamin Blackwell. © Ant Farm. (Used with permission.)

+
+

Indeed, the computer aspect was essential to this plan: not only would it link all the truckstops, or “nodes,” in Ant Farm’s parlance, into a nationwide “communication network,” but it would also direct the visitor to the services available at other truckstops – a wood-working shop, or astrology lessons, for example. [16] Truckers could be sent to other nodes via several highway directions; a placemat passed out to audiences on the Ant Farm tour maps several of these cross-country routes. (fig. 3) On the flip side of the placemat, a star identifies potential Cold War surplus sites that could be reused as nodes, an act of reappropriating what Mark Wasiuta describes as the nation’s “expanding computerized military network and its underground command centers.” [17] A sketch for one of these sites, identified as a former desert missile silo near Wendato (likely Wendover, Utah), contains plans to transform layers of the silo into various layers for maintaining software (film/video) and hardware (auto/bus), all wired via a solar dish to its nervous system/core. [18]

+

For Ant Farm, the interconnections turned each node into a “physically fragmented city” of media. Distributed across the country in places where “land is cheap and codes are lax in between the cities,” – one thinks of the arid wheat field in Amarillo, Texas, where they executed their most famous piece, Cadillac Ranch, or the California deserts where they set up inflatable structures – the Truckstop nodes would be connected by the simplest yet most robust piece of Cold War infrastructure, the interstate highway. And by placing the nodes at the side of the highway, it was possible to build an existence where the journey was the destination, and where the motion of the network was the point of the network. Cars traveling between the nodes thus became packets; remaining in constant motion, each packet would not stop at one node for long before traveling to another node. In other words, packet-switching. [19] Without a centralized node (although at one point Ant Farm envisioned a central computer to direct traffic), the network would constantly move information from point to point while avoiding the concentration of information in any one place. Moreover, the nodes were cheap, inflatable, and flexible. In effect, Ant Farm had envisioned an anarchic, distributed network for mobile living.

+

We may be tempted to dismiss this plan for “mobile living” as so much New Age artist cant. But Truckstop Network articulated an idea of mobility that would soon profoundly shape cloud computing. For the first Internet protocol was not developed through ARPAnet, as one might expect, and as most network historians claim, but through the physical act of driving on the open road. With its fixed nodes, fixed links, and bunker-sized computers, ARPAnet was the quintessential embodiment of “closed-world” infrastructure. Instead, military researchers envisioned soldiers going mobile. Though there is no evidence that researchers at the Stanford Research Institute (SRI) saw any of Ant Farm’s media productions, they nonetheless shared a similar vision: media would need to produced and consumed on the road.

+

For SRI’s engineers, this meant retrofitting a ‘bread truck’ style van to test the difficulty of broadcasting and receiving network signals on the move. They wanted to see if, for instance, their packet radio connection would remain intact if the van went under a highway overpass. [20] (Packet radio is an early version of today’s cellular networks.) Rigged on the inside with a DEC LSI-11 computer and two packet radio transmitters, the SRI van ran its first successful test in August 1976, six years after Ant Farm’s own media van. The test was of a protocol that would bridge the aerial network — the Packet Radio Network, or PRnet – with the ground-based ARPAnet. It was the first time two disparate computer networks were bridged, and as a result, it is considered the first inter-network, or Internet, transmission.

+ +
+

Fig. 4. Diagram of first two-network Internet transmission, 1977, SRI International. Originally published in “Progress Report on Packet Radio Experimental Network,” September 1977. © SRI International, Inc. Used with permission.

+
+ +
+

Fig. 5. Media Van: mobile vt studio, 1971, Ant Farm; ink, stamp marks in black ink, sticker, and collage elements on paper; 11 x 17 in.; University of California, Berkeley Art Museum and Pacific Film Archive. Photograph: Benjamin Blackwell. © Ant Farm. Used with permission.

+
+

In this inaugural test, the van is clearly visible in the right side of the network diagram, connected to two clouds labeled PR NET and ARPA NET. (fig. 4) What is perhaps missing from the diagram is the texture of the setting, of the van’s driver – protocol engineer Jim Mathis – trucking down Northern California’s Bayshore Freeway, and the van’s final stop, which was chosen because it was a “‘hostile environment’ – in keeping with relevance to military application”:

+
+

This was the parking lot of Ross[o]tti’s biker bar in Palo Alto, still well in reach of the repeater units at Mt. Umunum and Mission Ridge – and with good supply of local bikers who gave the appearance of hostility after the requisite number of beers. [21]

+
+

There is an improvisatory aspect to SRI’s van test. The inter-network they built was by definition an “amalgam of wire and radio networks”; it was a way of allowing a highly mobile, even ethereal network – packet radio – to tap into a pre-existing, fixed network infrastructure. [22] The van also reveals a third infrastructure that is only implicit: the highways where the researchers in what is now known as Silicon Valley circulated to test their van, which also delighted the bikers and video freaks with whom they mingled. A few miles down the street from Rossotti’s, you could buy a catalog containing Ant Farm’s latest inflatable architecture projects or video schematics from the “Whole Earth Truck Store.” The first node on the inter-media network was a truck stop, or, in the case of SRI, a biker bar.

+

The two media vans soon went into storage, SRI’s to a forgotten back lot, Ant Farm’s to a bunker in Marin County, California. But the inter-networking protocol tested in 1976, TCP, would cement the growth of what would be christened the “Internet” in 1983, and the networks’ shapes would resemble the possibilities – the freedom of the road, a constantly moving, physically fragmented existence – once offered by the highway. No matter that American highway culture itself had gone into a decline. The potentialities that the highway once represented – the idea of the highway without the highway itself, simultaneously decentralized and yet an infrastructure from the Cold War – remained.

+

The “information superhighway” articulated a new kind of lifestyle, where media processors could go mobile, feeding information (often in the form of video) back into the cloud. Yet the shift from the media of the van to digital media was not difficult to envision. In “Truckstop Fantasy Number One,” Ant Farm had even mused that “EVENTUALLY WE WILL ABANDON PHYSICAL MOVEMENT FOR TELEPATHIC/CYBERNETIC MOVEMENT (TELEVISION) AND OUR NETWORK WILL ADAPT TO THE CHANGE.” [23] For Ant Farm, computer links were merely one of many forms of communication, and the specific medium (telepathy or television!) was somewhat beside the point. In the bottom of their network diagram for Truckstop, Ant Farm asks: “How many ways do you communicate/inter truckstop.” [24] And then they list “linear” mediums, such as the mail, next to “electronic” mediums (radio and telegraph and computer) and land and aerial transportation mediums (cars, trucks, blimps). A single anomalous dotted line in a mesh network appears to indicate, of all things, a telegraph line.

+

The inspiration for Truckstop was as much the new technology of the Sony Portapak as the well-worn technology of the postal service. As Chip Lord recalls, “[b]efore we went on the road, we were doing mail art and we tapped into this network of people doing mail art.” [25] Kris Paulsen has additionally uncovered a buried history of guerilla television within its lo-fi distribution network: videographers swapped half-inch videotapes by advertisements and mail order. [26] The point is that cloud computing is always an amalgam – a “network of networks – that can only come into existence when it is not tied to a specific technology. This is why there are multiple clouds in the SRI diagram, and even some internal debate at SRI on how many networks – two or three – are needed before the project can officially be termed an “inter-network.”

+

To think about digital networks, I am arguing, one must first think about the network in the absence of individual technologies. This is what I have tried to do with the example of the two media vans. In the late 1960s and early 1970s, the rhetoric behind the creation of new information structures was certainly overblown; we ignore the utopianism of their claims because they are so sweeping that they are hard to take seriously (Youngblood’s videosphere that envisioned an “Intermedia network” that will unite all media). But we dismiss their rhetoric at our own risk. Strip away the technological layer – the artists’ concern with television, for example – and we see something very similar to what we have now: the digital cloud as a place where all media seem to converge; the cloud as an enabler of supposedly decentralized networked communities. [27] The universalist fantasy of the cloud remains as ubiquitous now as it was forty years ago.

+

There is a second reason why I have brought the vans into the story. If we only imagine the network as a product of the military, working with their contractors, to “invent” ARPA and the Internet, then the network that we take away is a deeply paranoid one — a vision of nuclear strikes and distributed tanks. There is a hole in that narrative. By their own admission, the engineers at SRI were trying to convince the military that their own interests in packet radio could eventually have a military application. Inside the van were several other projects, including a computer program for encoding speech run by the “Network Speech Compression and Network Skiing Club,” that reflected a more utopian heritage within SRI of using computers to augment human capabilities. Yet the story they told to the military is the one that is inevitably retold by computer historians.

+

Precisely because many of the claims in the late 1960s and early 1970s are strange — precisely because they are unexplainable – is grounds for why we should embrace them. SRI used a Mickey Mouse phone inside their van to test phone service over the packet network; this research in digital speech resulted in the decidedly un-military Speak & Spell toy for children. Meanwhile, Ant Farm sketched an ink diagram of Television America, its primetime audience re-imagined as a slice of prime – prime meat, that is. In their specificity, in their improvisatory strangeness, they rub against the grain of universalism. A dancing chicken broadcast from the Media Van undercuts any sort of sweeping claims for a new Media America. By their very refusal to be assimilated into useful categories for Internet history, they stake out a space for the autonomy of their production. In contrast to understanding network culture as a paranoid world system, one that encompasses all networks, these weird and unexplainable moments offer the potential for an alternate, reparative reading. [28]

+

It is unknown whether the video freaks and the network engineers in Portola Valley rubbed shoulders over a beer at Rossotti’s. But there was a rich relationship between the counterculture and computer scientists of the San Francisco Bay Area. Theodore Roszak and John Markoff have identified a shared interest in political dissent, communalist, and consciousness-expanding practices by members of the counterculture and computer researchers living in San Francisco and the Stanford area, respectively. [29] And as Fred Turner has shown, Stewart Brand served as a key hinge between the two worlds, acting as a cameraman during Douglas Engelbart’s 1968 demonstration of personal computing, and a publisher of the seminal Whole Earth Catalog – a kind of World Wide Web in print that indirectly led to the establishment of the Berkeley Homebrew Computer Club. [30]

+

These histories, however, typically trace inventors and researchers within or on the peripheries of computer science. As I have tried to show, network culture properly resides in a vibrant debate – one that preceded the 1960s, and continues to this day – about the proper configuration between media and power. Computer scientists were a part of this debate, but were not the only ones to weigh in. Years before ARPAnet’s existence, sociologists, urban planners, government bureaucrats, privacy advocates, epidemiologists, computer scientists, and, of course, the aforementioned artists, were keenly aware of the centralizing tendencies of networks. Would the computer network become a “natural monopoly,” like all of their predecessor utilities, asked Baran in a 1966 Congressional hearing, and if so, what steps should we take to set it on the right track?

+

The answer to Baran’s question had already begun to percolate in the fierce debates over television. Only five years earlier, Newton Minow, the incoming FCC commissioner, warned about television’s monopoly over its viewers in his famous “wasteland” speech by describing the flatness of television: “You will see a procession of game shows, formula comedies about totally unbelievable families, blood and thunder, mayhem, violence, sadism, murder, western bad men, western good men, private eyes, gangsters, more violence, and cartoons
 And most of all, boredom.” [31] This distaste builds to the commissioner’s larger point: “I am deeply concerned with concentration of power in the hands of the networks.” The network was then, as it is now, a potent manifestation of aesthetic questions. Aesthetic — which is to say political.

+
+
+

References

+

1. Paul Edwards, The Closed World: Computers and the Politics of Discourse in Cold War America (Cambridge: MIT Press, 1997).

+

2. For more on this myth, which Kazys Varnelis calls the “foundation myth for the Internet,” see Kazys Varnelis, “The Centripetal City: Telecommunications, the Internet, and the Shaping of the Modern Urban Environment,” Cabinet Magazine 17, Spring 2004/2005 and Kazys Varnelis, “Conclusion: The Rise of Network Culture,” in Networked Publics, ed. Kazys Varnelis (Cambridge: MIT Press, 2008).

+

3. What is not in dispute is the complex accumulation of technological innovations, such as Donald Evans’s packet-switching technology and Paul Baran’s writings on link interference at RAND Corporation, that set the stage for a precursor of the Internet, named ARPAnet, after the Department of Defense’s Advanced Research Projects Agency, first deployed in 1969, and the Internet itself, around 1977.

+

4. John McCarthy, “The Home Information Terminal,” Man and Computer, Proceedings of the International Conference, Bordeaux, 1970, 48-57, Basel: Karger, 1972, reprinted at http://www-formal.stanford.edu/jmc/hoter2.pdf, June 1, 2000 (accessed 10/2013).

+

5. Raindance Corporation, no title, in Radical Software 1:1, “The Alternate Television Movement” (1970), 19.

+

6. David Joselit, “Tale of the Tape,” Artforum, May 2002, 196.

+

7. Joan Didion, “Slouching Towards Bethlehem,” originally published in Saturday Evening Post 240:19, September 23, 1967, 25-94.

+

8. The advent of new media in the late 1960s and early 1970s was felt primarily as the advent of news media — for instance, as new reportage from the 1972 Democratic and Republican National Conventions by the amateur group TVTV (Top Value Television), wielding the new handheld videorecorders named Portapaks. We tend to lose sight of this because a scholarly focus on the specificity of the network’s mediums (its wires or logics or apparatus) has led to its inevitable separation from the network’s media, the sense of mass or communications media. A contemporary scholar studying the newspaper industry may have little to say to a contemporary scholar studying network protocols. But at one point, the two senses of the word were inseparable. In the words of German media theorist Hans Magnus Enzensberger, writing in 1970, “the media are making possible mass participation in a social and socialized productive process, the practical means of which are in the hands of the masses themselves
 In its present form, equipment like television or film does not serve communication but prevents it.” Hans Magnus Enzensberger, “Constituents of a Theory of the Media (1970)”, in Video Culture, ed. John Hanhardt (Rochester, NY: Visual Studies Workshop Press, 1986), 98.

+

9. Examples might include artist Dan Graham’s “feed-forward” cable network (ca. 1972); Austin Community Television (ACTV, 1972-), which fed directly into the cable’s “head-end,” or distribution center; Stan VanDerBeek’s live performance/call-in piece for WGBH-Boston, Violence Sonata (1970); or the Videofreex pirate TV station in the Catskills, Lanesville TV (1972-1977), that attempted to hack or reconfigure the shape of the network system. For further reading, in addition to scholarly studies such as David Joselit’s Feedback: Television Against Democracy (Cambridge: MIT Press, 2007), Deirdre Boyle, Subject to Change: Guerrilla Television Revisited (New York: Oxford University Press, 1996), there are numerous books by the video collectives themselves, including Michael Shamberg and the Raindance Corporation, Guerrilla Television (New York: Henry Holt & Co., Inc., 1971) and Videofreex, Spaghetti City Video Manual (New York: Praeger, 1973).

+

10. Ant Farm, “Media Van, Ant Farm Video,” 1970, photocopy, three-hole punched, two pages, in Living Archive 7: Ant Farm, ed. Felicity D. Scott (Barcelona: Actar Publishing, 2008), 224.

+

11. Steve Seid, “Tunneling through the Wasteland: Ant Farm Video,” in Ant Farm 1968-1978, ed. Constance M. Lewallen (Berkeley: University of California Press, 2004), 25.

+

12. Jonathan Crary, “Eclipse of the Spectacle,” in Art After Modernism: Rethinking Representation, ed. Brian Wallis (New York: New Museum, 1984), 289.

+

13. Crary, “Eclipse of the Spectacle,” 290.

+

14. Kirsten Olds, Networked Collectivities: North American Artists’ Groups, 1968–1978 (Ph.D. diss., University of Michigan, 2009), 125.

+

15. Scott, Living Archive 7: Ant Farm, 99.

+

16. Ant Farm, “Truckstop Network,” in Scott, 226-227; also see Scott, 104.

+

17. Mark Wasiuta, “Ant Farm Underground.” Cabinet 30, Summer 2008.

+

18. This reference occurs in Ant Farm’s drawing “3D Truckstop” and is likely a misspelled version of Wendover, Utah, as there is a reference to art being faster if it were situated in neighboring Bonneville Salt Flats, home of the Bonneville Speedway. There is no town named Wendato.

+

19. Thanks to Finn Brunton for suggesting this idea.

+

20. Don Nielsen, “The SRI Van and Computer Internetworking,” Core 3:1 (February 2002), a publication of the Computer History Museum, 2-7. Cellular phones use a modified form of packet radio technology to send data. Indeed, some phones continue to use the acronym GPRS, General Packet Radio Service, for this technology.

+

21. David Retz, “ARPANET, as I Recall,” April 2010, accessed April 15, 2014, http://comware.us/Content/internetrecollections

+

22. Nielsen, “The SRI Van and Computer Internetworking,” 3.

+

23. Ant Farm, “Truck stop fantasy one,” 1971, in Scott, 174.

+

24. Ant Farm, “Put Energy into a System You Can Belive In,” 1971, in Scott, 176.

+

25. Chip Lord and Curtis Schreier, “Media Van – Ant Farm Interview by Jimmy Stamp,” in Floater Magazine 2, “System False,” January 2009, accessed April 15, 2014, http://www.floatermagazine.com/issue02/pdfs/Media_Van.pdf

+

26. Kris Paulsen, “Half-Inch Revolution: The Guerilla Video Tape Network,” in Amodern 2, “Network Archaeology,” October 2013, accessed April 15, 2014, http://amodern.net/article/half-inch-revolution/

+

27. For instance, Friedrich Kittler writes that “a digital base will erase the very concept of medium,” an idea echoed and even expanded by other new media scholars, such as Lev Manovich, subscribing to what might be termed the “convergence hypothesis.” Kittler, Gramophone, Film, Typewriter. Stanford: Stanford UP, 1999. 1. +

28. My reference to “reparative reading” is from Eve Kosofsky Sedgwick, “Paranoid Reading and Reparative Reading, Or, You’re So Paranoid, You Probably Think This Essay Is About You.” In Touching Feeling: Affect, Pedagogy, Performativity (Durham, NC: Duke University Press, 2003), 123-151.

+

29. Theodore Roszak, From Satori to Silicon Valley: San Francisco and the American Counterculture (San Francisco: Don’t Call it Frisco Press, 1986); John Markoff, What the Dormouse Said: How the Sixties Counterculture Shaped the Personal Computer Industry (New York: Penguin, 2006).

+

30. Fred Turner, From Counterculture to Cyberculture: Stewart Brand, the Whole Earth Network, and the Rise of Digital Utopianism (Chicago: University of Chicago Press, 2008).

+

31. Newton Minow, “Television and the Public Interest,” speech delivered 9 May 1961, National Association of Broadcasters, Washington, DC.

+
+

Bio

+

Tung-Hui Hu is the author of Cloud: A Pre-History, forthcoming from MIT Press in 2015, from which this essay is taken, as well as three books of poetry, most recently Greenhouses, Lighthouses (Copper Canyon Press, 2013). Recent publications include articles on real time (in Discourse: Journal for Theoretical Studies in Media and Culture) and digital poetics (in Acts + Encounters, UCSC Poetry and Politics imprint). He teaches at the University of Michigan, where he co-organizes the Digital Environments Cluster.

+
+ + + \ No newline at end of file diff --git a/Research/b-e-e-t.r-o-o-t.net/wijnhaven_to_foshan.html b/Research/b-e-e-t.r-o-o-t.net/wijnhaven_to_foshan.html new file mode 100644 index 0000000..2b13637 --- /dev/null +++ b/Research/b-e-e-t.r-o-o-t.net/wijnhaven_to_foshan.html @@ -0,0 +1,45 @@ + + + + wijnhaven_to_foshan + + + + + +
+ +
+

Maps

+

"The map is a help provided to the imagination through the eyes"
+ —Henri Abraham Chatelain

+ +

A map is not the territory it represents, but, if correct, it has a similar structure to the territory, which accounts for its usefulness.
+ — Alfred Korzybski, Science and Sanity, p.58.
+ + +

Map/territory

+

A map at a 1:1 scale would be of no use to a traveller wishing to simply get from A to B in the easiest possible way. A 1:1 map is the territory, and is therefore redundant.

+ +

Therefore, without some degree of abstraction (beginning with scale), maps would be useless. A map shows not only locations, but also concepts; marking what is public or private property is a way of mapping the world, and determining the difference between travel and trespass.

+ +

Taking a line for a walk

+

I'm making drawings by walking and tracking myself over GPS using an app on my phone. I walk door-to-door between the geographical locations of our network. The app I'm using displays my path as a jagged line, alongside information about the distance, altitude, speed, pace and time elapsed. When I reach my destination I save the walk, export it as a .gpx file to my computer, and then load it into software for plotting geospatial information. In the graphic interface of this software the track points are connected with a series of lines that link them together into a route.

+

This is a visualisation of my movements, abstracted into a line for quick and easy representation. Ask someone to draw a route from A to B and they'll probably draw a similar series of lines, bending where you should make a left or right turn. The most direct route is a completely straight line (as the crow flies) but this is hardly useful to the average pedestrian. Utility here is predicated by a delicate balance between a certain level of detail, and a certain level of abstraction.

+
+ + + + + + +

Somewhere above me, satellites trilaterate my position and decide where I am on the globe. They take snapshots and these are intermittently uploaded to a remote server - I don't know where exactly - which serves this data to the software on my phone.

+

As I walk from homeserver to homeserver, I'm relying on a mental mind-map of Rotterdam, one formed over the past 7 months that I've been here. If I follow my nose, I can usually end up in the general vicinity of where I'm supposed to be. Eventually I need to use an actual map to pinpoint my targeted destination, but for the most part I enjoy the increasingly rare occurence of being lost for a moment.

+

If I walk enough routes, this will eventually form a map of Rotterdam, though one reduced down to just simple lines against a blank background. These represent areas that are accessible on foot, most likely the streets and footpaths.

+

The line here meanders slightly, perhaps this is where I crossed the street? I'm still in the habit of walking on the left side of the road, following the direction that traffic moves in Australia. Sometimes I notice that oncoming pedestrians can't always tell which way I'm going to pass them on the pavement; even on foot we still follow the flow of traffic.

+

If you know Rotterdam, perhaps you can identify what the straightest part of this path represents: the Erasmusbrug. It's easy to guess why; the bridge is a high traffic area, and it's not so easy to wander off the path here, or to cross the lanes of traffic going over it. There are few other ways to cross the Maas River apart from going over a bridge. I suppose it could be crossed by boat, and there is also the subterranean Maastunnel further up the river. Or perhaps you might be brave enough to swim. But any which way one crosses the river (and assuming your GPS tracking device doesn't end up in the drink), the path represented by tracking software would reveal itself as straight lines between the snapshots of your location. On this particular day the bridge was raised; trams stopped mid-way, and their drivers stood outside, smoking in a cluster. Impatient joggers ran on the spot, the rest of us huddled while we waited for the bridge to lower again.

+

+
+ + + \ No newline at end of file diff --git a/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-01.jpg b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-01.jpg new file mode 100644 index 0000000..41f3f48 Binary files /dev/null and b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-01.jpg differ diff --git a/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-02.jpg b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-02.jpg new file mode 100644 index 0000000..25e7311 Binary files /dev/null and b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-02.jpg differ diff --git a/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-03.jpg b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-03.jpg new file mode 100644 index 0000000..bb28276 Binary files /dev/null and b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-03.jpg differ diff --git a/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-04.jpg b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-04.jpg new file mode 100644 index 0000000..bef405f Binary files /dev/null and b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-04.jpg differ diff --git a/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-05.jpg b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-05.jpg new file mode 100644 index 0000000..deb0551 Binary files /dev/null and b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-05.jpg differ diff --git a/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-06.jpg b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-06.jpg new file mode 100644 index 0000000..7881039 Binary files /dev/null and b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-06.jpg differ diff --git a/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-07.jpg b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-07.jpg new file mode 100644 index 0000000..846544b Binary files /dev/null and b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-07.jpg differ diff --git a/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-08.jpg b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-08.jpg new file mode 100644 index 0000000..8a097ad Binary files /dev/null and b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-08.jpg differ diff --git a/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-09.jpg b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-09.jpg new file mode 100644 index 0000000..5663dea Binary files /dev/null and b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-09.jpg differ diff --git a/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-10.jpg b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-10.jpg new file mode 100644 index 0000000..4b1c347 Binary files /dev/null and b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-10.jpg differ diff --git a/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-11.jpg b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-11.jpg new file mode 100644 index 0000000..3ae6d50 Binary files /dev/null and b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-11.jpg differ diff --git a/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-12.jpg b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-12.jpg new file mode 100644 index 0000000..115c8fa Binary files /dev/null and b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-12.jpg differ diff --git a/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-13.jpg b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-13.jpg new file mode 100644 index 0000000..3e11e18 Binary files /dev/null and b/Research/ciao.urca.tv/img/abstr/Abstractions_permutations-13.jpg differ diff --git a/Research/ciao.urca.tv/img/thek1.jpg b/Research/ciao.urca.tv/img/thek1.jpg new file mode 100644 index 0000000..9c8856f Binary files /dev/null and b/Research/ciao.urca.tv/img/thek1.jpg differ diff --git a/Research/ciao.urca.tv/img/thek2.jpg b/Research/ciao.urca.tv/img/thek2.jpg new file mode 100644 index 0000000..528c1b1 Binary files /dev/null and b/Research/ciao.urca.tv/img/thek2.jpg differ diff --git a/Research/ciao.urca.tv/index.html b/Research/ciao.urca.tv/index.html new file mode 100644 index 0000000..e87ac00 --- /dev/null +++ b/Research/ciao.urca.tv/index.html @@ -0,0 +1,53 @@ + + + + + + + + + map/map/map/map/map + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + − +
+
+
+
+
+
Special Issue 8
+
+
+
+ + + + + + + + diff --git a/Research/ciao.urca.tv/leaf_elements.js b/Research/ciao.urca.tv/leaf_elements.js new file mode 100644 index 0000000..0d5cba2 --- /dev/null +++ b/Research/ciao.urca.tv/leaf_elements.js @@ -0,0 +1,323 @@ +var mainX = 40, + mainY = 220; + +//IDEOLOGY +var theme1 = L.divIcon({ className:"themes", html:'
IDEOLOGY
' }); + var stheme1 = L.divIcon({ className:"sthemes", html:'
CALIFORNIAN IDEOLOGY
' }); + var pdfIcon5 = L.divIcon({ className:"pdf", html:'' }); + var barca = L.divIcon({ className:"subsub", html:'
Barbrook and Cameron
' }); + var citgabo = L.divIcon({ className:"subsub", html:'
"Not to lie about the future is impossible and
one can lie about it at will" - Naum Gabo
' }); + var pdfIcon6 = L.divIcon({ className:"pdf", html:'' }); + var ftu = L.divIcon({ className:"subsub", html:'
Fred Turner
' }); + var countcu = L.divIcon({ className:"subsub", html:'
counter-culture
' }); + var cybcu = L.divIcon({ className:"subsub", html:'
cyber-culture
' }); + + + var stheme2 = L.divIcon({ className:"sthemes", html:'
MARXISM
' }); + var pdfIcon1 = L.divIcon({ className:"pdf", html:'' }); + var citrul = L.divIcon({ className:"subsub", html:'
"The ideas of the ruling class
are in very epoch the ruling ideas.."
' }); + var classes = L.divIcon({ className:"subsub", html:'
classes
' }); + var mareg = L.divIcon({ className:"subsub", html:'
Marx and Engels
' }); + var pdfIcon2 = L.divIcon({ className:"pdf", html:'' }); + var gram = L.divIcon({ className:"subsub", html:'
Antonio Gramsci
' }); + var pris = L.divIcon({ className:"subsub", html:'
Prison Notebooks
' }); + var subalt = L.divIcon({ className:"subsub", html:'
subaltern
' }); + + + var stheme3 = L.divIcon({ className:"sthemes", html:'
CULTURAL STUDIES
' }); + var pdfIcon3 = L.divIcon({ className:"pdf", html:'' }); + var stuha = L.divIcon({ className:"subsub", html:'
Stuart Hall
' }); + var comthe = L.divIcon({ className:"subsub", html:'
communication theory
' }); + var encod = L.divIcon({ className:"subsub", html:'
encoding
' }); + var decod = L.divIcon({ className:"subsub", html:'
decoding
' }); + var tv = L.divIcon({ className:"subsub", html:'
television
' }); + var pdfIcon4 = L.divIcon({ className:"pdf", html:'' }); + var dihe = L.divIcon({ className:"subsub", html:'
Dick Hebdige
' }); + var subcu = L.divIcon({ className:"subsub", html:'
subculture
' }); + var style = L.divIcon({ className:"subsub", html:'
style
' }); + var punk = L.divIcon({ className:"subsub", html:'
punk
' }); + + + var stheme4 = L.divIcon({ className:"sthemes", html:'
MEDIA THEORY
' }); + var pdfIcon8 = L.divIcon({ className:"pdf", html:'' }); + var marme = L.divIcon({ className:"subsub", html:'
Marshall McLuhan
' }); + var undme = L.divIcon({ className:"subsub", html:'
understanding media
' }); + var medme = L.divIcon({ className:"subsub", html:'
"the medium is the message"
' }); + var pdfIcon9 = L.divIcon({ className:"pdf", html:'' }); + var duke = L.divIcon({ className:"subsub", html:'
Durham and Kellnere
' }); + var keywo = L.divIcon({ className:"subsub", html:'
keywords
' }); + + + + var stheme5 = L.divIcon({ className:"sthemes", html:'
HEGEMONY
' }); + var pdfIcon7 = L.divIcon({ className:"pdf", html:'' }); + var chant = L.divIcon({ className:"subsub", html:'
Chantel Mouffe
' }); + var agon = L.divIcon({ className:"subsub", html:'
agonism
' }); + var artt = L.divIcon({ className:"subsub", html:'
art
' }) + var pspace = L.divIcon({ className:"subsub", html:'
public space
' }) + + + var video2 = L.divIcon({ className:"vid", html:'' }); + var slav = L.divIcon({ className:"subsub", html:'
Zlazloj Zlizlek
' }); + var andso = L.divIcon({ className:"subsub", html:'
"and so on and so on.."
' }); + var theyli = L.divIcon({ className:"subsub", html:'
They Live
' }); + + + + +var idX = mainX+100, + idY = mainY+100; +L.marker(new L.LatLng(idX, idY), {icon: theme1}).addTo(map); + L.marker(new L.LatLng(idX+25, idY+15), {icon: stheme1}).addTo(map); + L.marker(new L.LatLng(idX+35, idY+10), {icon: pdfIcon5}).addTo(map); + L.marker(new L.LatLng(idX+40, idY+15), {icon: barca}).addTo(map); + L.marker(new L.LatLng(idX+35, idY+30), {icon: citgabo}).addTo(map); + L.marker(new L.LatLng(idX+20, idY+35), {icon: pdfIcon6}).addTo(map); + L.marker(new L.LatLng(idX+13, idY+25), {icon: ftu}).addTo(map); + L.marker(new L.LatLng(idX+15, idY+55), {icon: countcu}).addTo(map); + L.marker(new L.LatLng(idX+10, idY+40), {icon: cybcu}).addTo(map); + + L.marker(new L.LatLng(idX-4, idY+43), {icon: stheme2}).addTo(map); + L.marker(new L.LatLng(idX-15, idY+50), {icon: pdfIcon1}).addTo(map); + L.marker(new L.LatLng(idX-25, idY+60), {icon: citrul}).addTo(map); + L.marker(new L.LatLng(idX-3, idY+33), {icon: classes}).addTo(map); + L.marker(new L.LatLng(idX-12, idY+39), {icon: mareg}).addTo(map); + L.marker(new L.LatLng(idX+3, idY+60), {icon: pdfIcon2}).addTo(map); + L.marker(new L.LatLng(idX+3, idY+45), {icon: gram}).addTo(map); + L.marker(new L.LatLng(idX+8, idY+65), {icon: pris}).addTo(map); + L.marker(new L.LatLng(idX-5, idY+70), {icon: subalt}).addTo(map); + + L.marker(new L.LatLng(idX-17, idY-28), {icon: stheme3}).addTo(map); + L.marker(new L.LatLng(idX-23, idY-10), {icon: pdfIcon3}).addTo(map); + L.marker(new L.LatLng(idX-33, idY-3), {icon: stuha}).addTo(map); + L.marker(new L.LatLng(idX-13, idY-10), {icon: comthe}).addTo(map); + L.marker(new L.LatLng(idX-23, idY-5), {icon: encod}).addTo(map); + L.marker(new L.LatLng(idX-30, idY-20), {icon: decod}).addTo(map); + L.marker(new L.LatLng(idX-40, idY-15), {icon: tv}).addTo(map); + L.marker(new L.LatLng(idX-19,idY-45), {icon: pdfIcon4}).addTo(map); + L.marker(new L.LatLng(idX-10,idY-53), {icon: dihe}).addTo(map); + L.marker(new L.LatLng(idX-27,idY-35), {icon: subcu}).addTo(map); + L.marker(new L.LatLng(idX-35,idY-45), {icon: style}).addTo(map); + L.marker(new L.LatLng(idX-25,idY-60), {icon: punk}).addTo(map); + + L.marker(new L.LatLng(idX+10, idY-45), {icon: stheme4}).addTo(map); + L.marker(new L.LatLng(idX+25, idY-50), {icon: pdfIcon8}).addTo(map); + L.marker(new L.LatLng(idX+30, idY-60), {icon: marme}).addTo(map); + L.marker(new L.LatLng(idX+25, idY-40), {icon: undme}).addTo(map); + L.marker(new L.LatLng(idX+10, idY-75), {icon: medme}).addTo(map); + L.marker(new L.LatLng(idX, idY-30), {icon: pdfIcon9}).addTo(map); + L.marker(new L.LatLng(idX, idY-45), {icon: duke}).addTo(map); + L.marker(new L.LatLng(idX-10, idY-20), {icon: keywo}).addTo(map); + + L.marker(new L.LatLng(idX-30, idY+20), {icon: stheme5}).addTo(map); + L.marker(new L.LatLng(idX-20, idY+25), {icon: pdfIcon7}).addTo(map); + L.marker(new L.LatLng(idX-15, idY+15), {icon: chant}).addTo(map); + L.marker(new L.LatLng(idX-23, idY+40), {icon: agon}).addTo(map); + L.marker(new L.LatLng(idX-40, idY+25), {icon: artt}).addTo(map); + L.marker(new L.LatLng(idX-20, idY+10), {icon: pspace}).addTo(map); + + + L.marker(new L.LatLng(idX+24, idY-17), {icon: video2}).addTo(map); + L.marker(new L.LatLng(idX+39, idY-20), {icon: slav}).addTo(map); + L.marker(new L.LatLng(idX+6, idY+12), {icon: andso}).addTo(map); + L.marker(new L.LatLng(idX+1, idY-17), {icon: theyli}).addTo(map); + +var ontmyth = L.divIcon({ className:"themes", html:'
ONTOLOGY AND MYTHOLOGY
' }); +var testo7 = L.divIcon({ className:"text", html:"

Infrastructures are one of the most hiding aspects of the architectures of
our society. Their hidden/hiding nature create a magical aura around them. A double
disappearance, from the outside and in the inside, which in part reflect the
debate around the 'black-box' and transforming the concept of infrastructure in
the unknown object hidden inside the box which is a black-box in itself.
Autonomy becomes the illusion made possible by a second layer (or infinite
layering) posed on a physical object, by the tangled complexity of a circuit or
network which forces us to reinvent those concepts in mythological ways.

The server we set up was just that: a black mythological object capable of
hiding worlds, both its physical content, a raspberry pi, and its software.
At the same time, with its mass of tangled cables, it is an object to hide in a
corner of a house. I still forget that have it there, attached to my modem, and
at the same time every day I connect to it through my computer.

"}); +L.marker(new L.LatLng(idX+150, idY-80), {icon: testo7}).addTo(map); +L.marker(new L.LatLng(idX+155, idY-85), {icon: ontmyth}).addTo(map); + +var countid = L.divIcon({ className:"themes", html:'
COUNTER-IDEOLOGY
' }); +var testo8 = L.divIcon({ className:"text", html:"

Experiencing music is an extremely intimate act because the sense of distance,
typical of visual art, is collapsed in an immediate sense of embodiment.
Thinking about the relation between autonomy and contingency as an interplay
where the subject is posed in a 'liminal' zone opens a new perspective.
The subject, embodying the music, has the power to set their own experience
deciding where to put their boundaries. Subjectivity becomes the meter to
define freedom, against a pre-determinate thought and a 0-1 solution.

In the computer world subjectivity works in the same way embodying the whole
conception of the hardware-software dualism and reflecting its own condition of
'being a mind inside a body' in the software. The power of autonomy and
contingency as an interplay enhancing freedom becomes a way to re-read the
meaning of setting up a server and being autonomous in the web. A way to shape
our subjectivity in a world that reflects our inner being and gives us the
possibility to create a parallel place where our intimacy is projected,
represented and owned.

"}); +L.marker(new L.LatLng(idX+30, idY+140), {icon: testo8}).addTo(map); +L.marker(new L.LatLng(idX+35, idY+135), {icon: countid}).addTo(map); + + +//MAPPING +var theme8 = L.divIcon({ className:"themes", html:'
MAPPING AND ABSTRACTIONS
' }); + var mappa = L.divIcon({ html:'' }); + var mappa2 = L.divIcon({ html:''}); + var vis1 = L.divIcon({ className:"vis", html:'' }); + var vis2 = L.divIcon({ className:"vis", html:'' }); + var vis3 = L.divIcon({ className:"vis", html:'' }); + var vis5 = L.divIcon({ className:"visb", html:'' }); + var vis6 = L.divIcon({ className:"visb", html:'' }); + var vis7 = L.divIcon({ className:"visb", html:'' }); + var vis8 = L.divIcon({ className:"visb", html:'' }); + var vis9 = L.divIcon({ className:"visb", html:'' }); + var vis10 = L.divIcon({ className:"visb", html:'' }); + var vis11 = L.divIcon({ className:"visb", html:'' }); + var vis13 = L.divIcon({ className:"visb", html:'' }); + +var mapX = mainX-120, + mapY = mainY+150; +L.marker(new L.LatLng(mapX, mapY-40), {icon: theme8}).addTo(map); + L.marker(new L.LatLng(mapX+45, mapY-30), {icon: mappa}).addTo(map); + L.marker(new L.LatLng(mapX+45, mapY+10), {icon: mappa2}).addTo(map); + +var visX = mapX-24, + visY = mapY-100; + L.marker(new L.LatLng(visX, visY+8), {icon: vis1}).addTo(map); + L.marker(new L.LatLng(visX-23, visY+8), {icon: vis2}).addTo(map); + L.marker(new L.LatLng(visX-46, visY+8), {icon: vis3}).addTo(map); + L.marker(new L.LatLng(visX-17, visY+33), {icon: vis5}).addTo(map); + L.marker(new L.LatLng(visX, visY+80), {icon: vis6}).addTo(map); + L.marker(new L.LatLng(visX-23, visY+80), {icon: vis7}).addTo(map); + L.marker(new L.LatLng(visX-46, visY+80), {icon: vis8}).addTo(map); + L.marker(new L.LatLng(visX-18, visY+105), {icon: vis9}).addTo(map); + L.marker(new L.LatLng(visX-2, visY+150), {icon: vis10}).addTo(map); + L.marker(new L.LatLng(visX-32, visY+150), {icon: vis11}).addTo(map); + L.marker(new L.LatLng(visX-18, visY+190), {icon: vis13}).addTo(map); + +var theme9 = L.divIcon({ className:"themes", html:'
FROM MAPS TO ABSTRACTIONS
' }); +var testo9 = L.divIcon({ className:"text", html:"

Experiencing music is an extremely intimate act because the sense of distance,
typical of visual art, is collapsed in an immediate sense of embodiment.
Thinking about the relation between autonomy and contingency as an interplay
where the subject is posed in a 'liminal' zone opens a new perspective.
The subject, embodying the music, has the power to set their own experience
deciding where to put their boundaries. Subjectivity becomes the meter to
define freedom, against a pre-determinate thought and a 0-1 solution.

In the computer world subjectivity works in the same way embodying the whole
conception of the hardware-software dualism and reflecting its own condition of
'being a mind inside a body' in the software. The power of autonomy and
contingency as an interplay enhancing freedom becomes a way to re-read the
meaning of setting up a server and being autonomous in the web. A way to shape
our subjectivity in a world that reflects our inner being and gives us the
possibility to create a parallel place where our intimacy is projected,
represented and owned.

"}); +L.marker(new L.LatLng(visX+100, visY+300), {icon: testo9}).addTo(map); +L.marker(new L.LatLng(visX+105, visY+295), {icon: theme9}).addTo(map); + + +//AUTONOMY AND CONTINGENCY +var autX = mainX + 10, + autY = mainY - 510; +var theme2 = L.divIcon({ className:"themes", html:'
AUTONOMY AND CONTINGENCY
' }); + var theme2_music = L.divIcon({ className:"sthemes", html:'
MUSIC
' }); + var pdf_kramer = L.divIcon({ className:"pdf", html:'' }); + var lawkr = L.divIcon({ className:"subsub", html:'
Lawrence Kramer
' }); + var musmin = L.divIcon({ className:"subsub", html:'
musical meaning
' }); + var clas = L.divIcon({ className:"subsub", html:'
classical
' }); + var nois = L.divIcon({ className:"subsub", html:'
noise
' }); + var moder = L.divIcon({ className:"subsub", html:'
modern
' }); + var regfe = L.divIcon({ className:"subsub", html:'
noiserr +
regenerative feedback
' }); + var shostak = L.divIcon({ className:"vid", html:'' }); + var beth = L.divIcon({ className:"vid", html:'' }); + var noise = L.divIcon({ className:"vid", html:'' }); + var theme2_infrastr = L.divIcon({ className:"sthemes", html:'
INFRASTRUCTURES
' }); + var pdf_larkin = L.divIcon({ className:"pdf", html:'' }); + var brila = L.divIcon({ className:"subsub", html:'
Brian Larkin
' }); + var polipoet = L.divIcon({ className:"subsub", html:'
politics and poetics
' }); + var actnet = L.divIcon({ className:"subsub", html:'
actor-network theory
' }); + var img_thek1 = L.divIcon({ className:"visb", html:'' }); + var img_thek2 = L.divIcon({ className:"visb", html:'' }); + var paute = L.divIcon({ className:"subsub", html:'
Paul Thek
' }); + var lco = L.divIcon({ className:"subsub", html:'
L-column
' }); + var tecre = L.divIcon({ className:"subsub", html:'
technological reliquaries
' }); + var theme2_taz = L.divIcon({ className:"sthemes", html:'
TEMPORARY
AUTONOMOUS
ZONE
' }); + var pdf_taz = L.divIcon({ className:"pdf", html:'' }); + var hacbe = L.divIcon({ className:"subsub", html:'
Hackim Bey
' }); + var mapp = L.divIcon({ className:"subsub", html:'
map
' }); + var ster = L.divIcon({ className:"subsub", html:'
"Sterling - islands in the net"
' }); + var disap = L.divIcon({ className:"subsub", html:'
disappearence
' }); + var theme2_postaut = L.divIcon({ className:"sthemes", html:'
POST-AUTONOMIA
' }); + +L.marker(new L.LatLng(autX, autY), {icon: theme2}).addTo(map); + L.marker(new L.LatLng(autX+25, autY), {icon: theme2_music}).addTo(map); + L.marker(new L.LatLng(autX+15, autY-10), {icon: pdf_kramer}).addTo(map); + L.marker(new L.LatLng(autX+10, autY+5), {icon: lawkr}).addTo(map); + L.marker(new L.LatLng(autX+5, autY-20), {icon: musmin}).addTo(map); + L.marker(new L.LatLng(autX+50, autY+20), {icon: clas}).addTo(map); + L.marker(new L.LatLng(autX+20, autY-60), {icon: nois}).addTo(map); + L.marker(new L.LatLng(autX+60, autY-30), {icon: moder}).addTo(map); + L.marker(new L.LatLng(autX+10, autY-55), {icon: regfe}).addTo(map); + L.marker(new L.LatLng(autX+55, autY-40), {icon: shostak}).addTo(map); + L.marker(new L.LatLng(autX+55, autY-10), {icon: beth}).addTo(map); + L.marker(new L.LatLng(autX+30, autY-40), {icon: noise}).addTo(map); + + L.marker(new L.LatLng(autX+15, autY+33), {icon: theme2_infrastr}).addTo(map); + L.marker(new L.LatLng(autX+25, autY+30), {icon: pdf_larkin}).addTo(map); + L.marker(new L.LatLng(autX+30, autY+30), {icon: brila}).addTo(map); + L.marker(new L.LatLng(autX+25, autY+40), {icon: polipoet}).addTo(map); + L.marker(new L.LatLng(autX+7, autY+36), {icon: actnet}).addTo(map); + + L.marker(new L.LatLng(autX+25, autY+70), {icon: img_thek1}).addTo(map); + L.marker(new L.LatLng(autX+25, autY+110), {icon: img_thek2}).addTo(map); + L.marker(new L.LatLng(autX+25, autY+150), {icon: paute}).addTo(map); + L.marker(new L.LatLng(autX+30, autY+70), {icon: lco}).addTo(map); + L.marker(new L.LatLng(autX-5, autY+110), {icon: tecre}).addTo(map); + + + L.marker(new L.LatLng(autX-20, autY+10), {icon: theme2_taz}).addTo(map); + L.marker(new L.LatLng(autX-20, autY), {icon: pdf_taz}).addTo(map); + L.marker(new L.LatLng(autX-15, autY-5), {icon: hacbe}).addTo(map); + L.marker(new L.LatLng(autX-30, autY-8), {icon: mapp}).addTo(map); + L.marker(new L.LatLng(autX-40, autY+15), {icon: ster}).addTo(map); + L.marker(new L.LatLng(autX-25, autY+40), {icon: disap}).addTo(map); + + L.marker(new L.LatLng(autX-10, autY-45), {icon: theme2_postaut}).addTo(map); + + +var theme7 = L.divIcon({ className:"themes", html:'
INFRASTRUCTOUR
' }); +var testo = L.divIcon({ className:"text", html:"

So, we were riding our bikes to my place, the last one of the Infrastructour.
Pedro and Rita couldn't self-host their servers and their house is near mine.
The idea was to set them up all together, connected to my router.

Once inside, it was easy to find my router; too easy. From the ground floor,
exactly on the left side of the entrance, one can find the ethernet plug; from
there I installed a long cable which runs along the side of the first flight of
stairs, arriving at the first floor where it enters through the door of my
apartment. The ethernet cable again sneaks snakily up the second flight of
stairs arriving on the second floor, passing the corridor and entering into the
main room where it finishes its two-floor journey to rest in the socket of my
router. We all followed this cable until its end and we started the process of
installing the servers. While Rita and I were using a Raspberry Pi as hardware
for our server, Pedro was using another machine labelled TIZEN SUNXI ALLWINNER
A20. He didn't have the possibility to set it up through SSH and he needed a
screen to connect his hardware via an HDMI cable, plus a mouse and a keyboard.
Fortunately, I had all he needed and we were positive on the success of these
last server configurations because my router was the same as Simon's, so we
already knew how to set it up and manage the port forwarding process. However,
after a while we noticed Rita's machine wasn't recognised on the home page of
the router (192.168.1.1), where you can check the connected devices and their
respective IPs. The physical router went from being a nice looking and clean
object, with a cable for the power and the snakey Ethernet cable, to resembling
a nest of connected objects with multiple lights and colors. I had a power
strip because I knew there was only one plug, and we needed another three to
supply the power to our machines. My idea was to hide that nest in a box but
actually it is still there. To find out what was the problem with Rita's server
we entered inside the tangled object with our eyes and our hands, and after we
checked the connections we realized two of the five plugs for Ethernet cables
(one was already taken by the two-storey long cable which enable the connection
in the web) were labeled as TV while the other two where labeled as Internet,
and that meant only two of the three servers could have been set.

Rita had to give up having her server hosted at my place and decided to set it
up at Artemis' instead. Apart from this problem related to the difference
between routers, the day ended well. Some went home, others continued the tour
in a pub with a beer, but this experience with technical problems and new
techie terms was the beginning of an ongoing process in discovering the
material and the digital aspects of a network that comprises hardware, software
and physical dependencies. We started to understand how difficult it is to
comprehend the complexity of it all. Even if the intention is to have something
independent, we understood that independence requires a knowledge which is both
technical and situated. In such circumstances one needs a teacher. There is not
only the knowledge of how it works, but also the practical knowledge of how
things work in particular circumstances. This was the starting point for my
thoughts on autonomy and contingency. The idea of having your autonomy on the
web – in our case through a server which is 'ours' – is always related to these
particular conditions and to the fact that you need to operate hardware and
software that will facilitate it... one has to balance the two different worlds
(autonomy vs contingency) and yet the notion of autonomy is based on both;
knowing terms and physical structures, protocols and how to apply them by
opening ports.

We can find a similarity between hosting a server and being hosts in our house;
just as there are protocols in software there are protocols within homes, to
open ports is to open doors. If one is only a client, one is homeless, or a
guest in someone else's house; on the other hand, if one has their own server,
one becomes a host. Things that are normally separated come together.
The distances collapse. This process is a passage from client to client-server.

"}); +L.marker(new L.LatLng(mainX+80, mainY-250), {icon: testo}).addTo(map); +L.marker(new L.LatLng(mainX+85, mainY-255), {icon: theme7}).addTo(map); + +var theme5 = L.divIcon({ className:"themes", html:'
MAGICAL INFRASTRUCTURES
' }); +var testo3 = L.divIcon({ className:"text", html:"

Infrastructures are one of the most hiding aspects of the architectures of
our society. Their hidden/hiding nature create a magical aura around them. A double
disappearance, from the outside and in the inside, which in part reflect the
debate around the 'black-box' and transforming the concept of infrastructure in
the unknown object hidden inside the box which is a black-box in itself.
Autonomy becomes the illusion made possible by a second layer (or infinite
layering) posed on a physical object, by the tangled complexity of a circuit or
network which forces us to reinvent those concepts in mythological ways.

The server we set up was just that: a black mythological object capable of
hiding worlds, both its physical content, a raspberry pi, and its software.
At the same time, with its mass of tangled cables, it is an object to hide in a
corner of a house. I still forget that have it there, attached to my modem, and
at the same time every day I connect to it through my computer.

"}); +L.marker(new L.LatLng(mainX+230, mainY-550), {icon: testo3}).addTo(map); +L.marker(new L.LatLng(mainX+235, mainY-555), {icon: theme5}).addTo(map); + +var theme6 = L.divIcon({ className:"themes", html:'
MEANING IN MUSIC
' }); +var testo4 = L.divIcon({ className:"text", html:"

Experiencing music is an extremely intimate act because the sense of distance,
typical of visual art, is collapsed in an immediate sense of embodiment.
Thinking about the relation between autonomy and contingency as an interplay
where the subject is posed in a 'liminal' zone opens a new perspective.
The subject, embodying the music, has the power to set their own experience
deciding where to put their boundaries. Subjectivity becomes the meter to
define freedom, against a pre-determinate thought and a 0-1 solution.

In the computer world subjectivity works in the same way embodying the whole
conception of the hardware-software dualism and reflecting its own condition of
'being a mind inside a body' in the software. The power of autonomy and
contingency as an interplay enhancing freedom becomes a way to re-read the
meaning of setting up a server and being autonomous in the web. A way to shape
our subjectivity in a world that reflects our inner being and gives us the
possibility to create a parallel place where our intimacy is projected,
represented and owned.

"}); +L.marker(new L.LatLng(mainX+140, mainY-800), {icon: testo4}).addTo(map); +L.marker(new L.LatLng(mainX+145, mainY-805), {icon: theme6}).addTo(map); + + + + + +// SERVER +var servX = mainX-200, + servY = mainY-400; +var serverthem = L.divIcon({ className:"themes", html:'
SERVER
' }); +var boatr = L.divIcon({ className:"vid", html:'' }); +var embod = L.divIcon({ className:"vid", html:'' }); + var radnet = L.divIcon({ className:"subsub", html:'
Radical Networks 2018
' }); + var atrans = L.divIcon({ className:"subsub", html:'
a transmaterial body
' }); + var boattr = L.divIcon({ className:"subsub", html:'
boattr
' }); + var techdict = L.divIcon({ className:"sthemes", html:'
technical dictionary
' }); + +L.marker(new L.LatLng(servX, servY), {icon: serverthem}).addTo(map); +L.marker(new L.LatLng(servX+30, servY-40), {icon: boatr}).addTo(map); +L.marker(new L.LatLng(servX+30, servY-10), {icon: embod}).addTo(map); +L.marker(new L.LatLng(servX+40, servY), {icon: radnet}).addTo(map); +L.marker(new L.LatLng(servX, servY-45), {icon: atrans}).addTo(map); +L.marker(new L.LatLng(servX+40, servY-60), {icon: boattr}).addTo(map); +L.marker(new L.LatLng(servX-30, servY-10), {icon: techdict}).addTo(map); + +var theme3 = L.divIcon({ className:"themes", html:'
SETTING UP A SERVER
' }); +var testo1 = L.divIcon({ className:"text", html:"

Setting up a server is a quick and smart way to gain a certain level of
autonomy in the web. Quick because it gives you the possibility to own the
place in which your projects are hosted. Smart because it opens the doors to
acquire knowledges on how the web works. This DYI attitude reflects the hacker
mindset of being independent and autonomous questioning the role of mainstream
and mass-media circuits, while revealing a practical alternative. Learning
computer networks is not only a technical exercise but actually can be
considered as a political act questioning the role of internet in our society
and the ideologies involved in develop the computer itself. It shows how the
building blocks of the web and computer networks are based on physical objects
disrupting the idea of internet as a nowhere cloud in the air.

However setting up a server require technical skills in both hardware and
software's side plus a knowledge of how the web works. Without those expertise
it is very difficult to proceed alone. Sharing this knowledge and skills, and
make a community to provide it, become an essential point in building autonomy,
but an autonomy built on dependencies.

"}); +L.marker(new L.LatLng(servX+50, servY-250), {icon: testo1}).addTo(map); +L.marker(new L.LatLng(servX+55, servY-255), {icon: theme3}).addTo(map); + +var theme4 = L.divIcon({ className:"themes", html:'
SERVER/BODY/HOME
' }); +var testo2 = L.divIcon({ className:"text", html:"

When your home becomes your server, you realize that hosting people in real
life is the same as allowing people to enter inside your server. Open the door
becomes to open your HTTP ports, and rotocols are a series of allowed
behaviours, and of course, as the owner, you can do whatever you want in your
house. When someone is weaving in internet, is connecting as a client to
someone else’s house, but if you own a server you are both a client and a host.
The relation existing between you and your home allows to have a fixed place,
gaining an higher level of autonomy and the possibility to invite people
at yours.

But a server is not always an object in a fixed space and more than one person
needed a moving one. Who for the necessity to live on a boat, who to question
the relation between a person and his/her body, showing how a server can move
with you. Those examples are fascinating not only as they question the standard
fixed location of the server, but also because they are strictly related to an
inquiry on the meaning of home, as a moving house, and subjectivity, as an
embodied one.

"}); +L.marker(new L.LatLng(servX-60, servY-60), {icon: testo2}).addTo(map); +L.marker(new L.LatLng(servX-55, servY-65), {icon: theme4}).addTo(map); + +var intro = L.divIcon({ className:"themes", html:'
INTRODUCTION
' }); +var textintro = L.divIcon({ className:"text", html:"

The Internet is not any more the wild wide web meant to improve our lives and
increase democratic ideas but has become a worldwide weapon of control and
exploitation. Nowadays the hegemonic centralization is ruling on several layers.
When surfing was a free flow of motion, a matter of following rings made of links
and explore unknown islands, the selection of Google search based on sponsored and
indexed lands, replaced a shared equality with a mainstream selection of contents
making possible the rise of a top class web. Where web's communication was an
astonishing step in the cyberspace capable to connect the globe in shared-time and
an experience to shape through communities, Facebook and its empire of
communication systems is not only killing the value of the communal experience,
enclosing users in a prison called profile, but also stealing information to those
users by presenting them as second class data called metadata. Stolen data which
are sold and used strategically to program behaviour while being hidden to the
public and the academic world researches.
This narrative of the transformation of the web, from a place of freedom to one of
control, resumed in the nowadays role of centralizing corporations and the raise
of a metadata society, is a debate existing before internet itslef and reflecting
the more general issues of the computer world and the machine. This short and
oversimplification actually is far well wider and radiates its querying from
technical to philosophical aspects, from social to existential experiences,
without never forgetting the political view.

How can we escape from systematic control?

How can we build our ideal experience and media?

This way of thinking is intrinsic to the Experimental Publishing course at Piet
Zwart Institute, and the way of exploring solutions and alternatives floats from a
pure interest in experimentation to a more politically involved counter-reaction.
The hacker attitude become the way, and how computers and their software has been
shaped, with the struggles and fights of the early pioneers, became the lens
through which depict today's issues and examples of how to acquire the skills to
reinvent a vanishing future.
Actually the alternative dwells in the potential of the computer itself, a power
that has been hidden during building the blocks of its structure. As Ted Nelsons
points in all his career, the process of building technologies is a political one
made of fights and interests, and not a 'natural' process. What we
see of a computer, its hardware and software, are products of individuals which
developed projects based on certain ideologies. I'll talk later about ideology
but basically this means two things: on the one hand everyone can challenge the
existing structures of power because they are not the only one possible (rather
being usually mediated by a capitalist interest in profit and exclusion). On the other
hand ideally everyone, as individuals capable of critical thought, can build their
own world against the actual shared feeling of impossibility to react, freezing
thought in a constant nihilism and resumed both in Mark Fisher's life experience
and thought, with his book 'Capitalist Realism' (2011) and his recent suicide in
2017.
The economical interest behind the computer's world putted the inexpert user as
meter of judgment and means of profit imposing a well looking interface while
hiding and closing the potential of this media in a black box. This political
process of programmed disappearance has facilitate the immediate usage but raised
the standard computer's interface as the only way possible, banning the knowledge
of the code and erasing the idea of being able to develop another way.
What hacker mindset helps us to see, is not a mythological world fighting between
good or bad behaviours, usually idealized in the meaning of the hacker itself
within a science-fiction narrative made of white and black hats, but a world made
of physical structures and infrastructures made by people and their communities
sharing knowledges. Experiencing the 'develop your way' is intended as an
experimentation free from profit, an interest in challenge yourself from a point
of view where the possibilities are open and everything thinkable becomes
interesting. Behind this view there is a deep and disrupting idea embodying the
power to react in the enthusiasm in experimentation, capable to breaks the black
walls of the box, and finding an object which contains something inherently
different from the Pandora's box.

What is the content of this box?

The possibility to create a world not yet written and within reach. The exploding
actuality of the dreamed post-mass-media era where the hegemonic power comes from
the disruption of illusions and not vice-versa.
A reality that is being shaped in the Experimental Publishing course at Piet Zwart
Institute and surely, if not as a full program, in many other approaches within
communities and in the academic world.

"}); +L.marker(new L.LatLng(servX+150, servY-530), {icon: textintro}).addTo(map); +L.marker(new L.LatLng(servX+155, servY-535), {icon: intro}).addTo(map); + + +var nuX = mainX + 80, + nuY = mainY - 650; + +var emptsp = L.divIcon({ className:"themes", html:'
EMPTY SPACE AND NODES
' }); +var textemptsp = L.divIcon({ className:"text", html:"

Experiencing music is an extremely intimate act because the sense of distance,
typical of visual art, is collapsed in an immediate sense of embodiment.
Thinking about the relation between autonomy and contingency as an interplay
where the subject is posed in a 'liminal' zone opens a new perspective.
The subject, embodying the music, has the power to set their own experience
deciding where to put their boundaries. Subjectivity becomes the meter to
define freedom, against a pre-determinate thought and a 0-1 solution.

In the computer world subjectivity works in the same way embodying the whole
conception of the hardware-software dualism and reflecting its own condition of
'being a mind inside a body' in the software. The power of autonomy and
contingency as an interplay enhancing freedom becomes a way to re-read the
meaning of setting up a server and being autonomous in the web. A way to shape
our subjectivity in a world that reflects our inner being and gives us the
possibility to create a parallel place where our intimacy is projected,
represented and owned.

"}); +L.marker(new L.LatLng(nuX-380, nuY+480), {icon: emptsp}).addTo(map); +L.marker(new L.LatLng(nuX-385, nuY+485), {icon: textemptsp}).addTo(map); + +var scalay = L.divIcon({ className:"themes", html:'
SCALE AND LAYERS
' }); +var textscalay = L.divIcon({ className:"text", html:"

Experiencing music is an extremely intimate act because the sense of distance,
typical of visual art, is collapsed in an immediate sense of embodiment.
Thinking about the relation between autonomy and contingency as an interplay
where the subject is posed in a 'liminal' zone opens a new perspective.
The subject, embodying the music, has the power to set their own experience
deciding where to put their boundaries. Subjectivity becomes the meter to
define freedom, against a pre-determinate thought and a 0-1 solution.

In the computer world subjectivity works in the same way embodying the whole
conception of the hardware-software dualism and reflecting its own condition of
'being a mind inside a body' in the software. The power of autonomy and
contingency as an interplay enhancing freedom becomes a way to re-read the
meaning of setting up a server and being autonomous in the web. A way to shape
our subjectivity in a world that reflects our inner being and gives us the
possibility to create a parallel place where our intimacy is projected,
represented and owned.

"}); +L.marker(new L.LatLng(nuX-350, nuY+800), {icon: scalay}).addTo(map); +L.marker(new L.LatLng(nuX-355, nuY+805), {icon: textscalay}).addTo(map); + +var linkpara = L.divIcon({ className:"themes", html:'
LINKS AND PARANODES
' }); +var textlinkpara = L.divIcon({ className:"text", html:"

Experiencing music is an extremely intimate act because the sense of distance,
typical of visual art, is collapsed in an immediate sense of embodiment.
Thinking about the relation between autonomy and contingency as an interplay
where the subject is posed in a 'liminal' zone opens a new perspective.
The subject, embodying the music, has the power to set their own experience
deciding where to put their boundaries. Subjectivity becomes the meter to
define freedom, against a pre-determinate thought and a 0-1 solution.

In the computer world subjectivity works in the same way embodying the whole
conception of the hardware-software dualism and reflecting its own condition of
'being a mind inside a body' in the software. The power of autonomy and
contingency as an interplay enhancing freedom becomes a way to re-read the
meaning of setting up a server and being autonomous in the web. A way to shape
our subjectivity in a world that reflects our inner being and gives us the
possibility to create a parallel place where our intimacy is projected,
represented and owned.

"}); +L.marker(new L.LatLng(nuX+200, nuY+350), {icon: linkpara}).addTo(map); +L.marker(new L.LatLng(nuX+195, nuY+355), {icon: textlinkpara}).addTo(map); + + + + + + diff --git a/Research/ciao.urca.tv/leaf_structure.js b/Research/ciao.urca.tv/leaf_structure.js new file mode 100644 index 0000000..4d0903e --- /dev/null +++ b/Research/ciao.urca.tv/leaf_structure.js @@ -0,0 +1,391 @@ +//map settings +var map = L.map('map', { crs: L.CRS.Simple }); +map.fitBounds( [[0,0], [1000,1000]] ); +map.setView( [0, 0], 0); + +var hash = new L.Hash(map); + +// var popup = L.popup(); + +// function onMapClick(e) { +// popup +// .setLatLng(e.latlng) +// .setContent(e.latlng.toString()) +// .openOn(map); +// } + +// map.on('click', onMapClick); + +//control size for each zoom's layer +var ss = document.getElementsByClassName("subsub"); +var texts = document.getElementsByClassName("text"); +var fonts = document.getElementsByClassName("themes"); +var sfonts = document.getElementsByClassName("sthemes"); +var pdfs = document.getElementsByName("pdf") +var videos = document.getElementsByName("yt"); +var vis = document.getElementsByName("vis"); +var visb = document.getElementsByName("visb"); +var xmap = document.getElementsByName("xpubmap"); + +function layer1() { + for (var i = 0; i < texts.length; i++) { //text + var element = texts[i]; + element.style.fontSize = "3px"; + } + + for (var i = 0; i < fonts.length; i++) { //themes + var element = fonts[i]; + element.style.fontSize = "5px"; + } + + for (var i = 0; i < sfonts.length; i++) { //subthemes + var element = sfonts[i]; + element.style.fontSize = "3px"; + } + + for (var i = 0; i < ss.length; i++) { //subsub + var element = ss[i]; + element.style.fontSize = "1px"; + } + + for (var i = 0; i < videos.length; i++) { //videos + var element = videos[i]; + element.width = "20"; + element.height = "15"; + } + + for (var i = 0; i < pdfs.length; i++) { //pdfs + var element = pdfs[i]; + element.width = "7"; + element.height = "9"; + } + + for (var i = 0; i < vis.length; i++) { //pdfs + var element = vis[i]; + element.width = "15"; + element.height = "11"; + } + + for (var i = 0; i < visb.length; i++) { //pdfs + var element = visb[i]; + element.width = "28"; + element.height = "20"; + } + + for (var i = 0; i < xmap.length; i++) { //videos + var element = xmap[i]; + element.width = "30"; + element.height = "25"; + } + +} + +function layer2() { + for (var i = 0; i < texts.length; i++) { //text + var element = texts[i]; + element.style.fontSize = "5px"; + } + + for (var i = 0; i < fonts.length; i++) { //themes + var element = fonts[i]; + element.style.fontSize = "13px"; + } + + for (var i = 0; i < sfonts.length; i++) { //subthemes + var element = sfonts[i]; + element.style.fontSize = "6px"; + } + + for (var i = 0; i < ss.length; i++) { //subsub + var element = ss[i]; + element.style.fontSize = "2px"; + } + + for (var i = 0; i < videos.length; i++) { //videos + var element = videos[i]; + element.width = "40"; + element.height = "30"; + } + + for (var i = 0; i < pdfs.length; i++) { //pdfs + var element = pdfs[i]; + element.width = "10"; + element.height = "14"; + } + + for (var i = 0; i < vis.length; i++) { //img small + var element = vis[i]; + element.width = "28"; + element.height = "20"; + } + + for (var i = 0; i < visb.length; i++) { //img big + var element = visb[i]; + element.width = "56"; + element.height = "40"; + } + + for (var i = 0; i < xmap.length; i++) { //maps + var element = xmap[i]; + element.width = "60"; + element.height = "55"; + } + +} + +function layer3() { + for (var i = 0; i < texts.length; i++) { //text + var element = texts[i]; + element.style.fontSize = "11px"; + } + + for (var i = 0; i < fonts.length; i++) { //themes + var element = fonts[i]; + element.style.fontSize = "20px"; + } + + for (var i = 0; i < sfonts.length; i++) { //subthemes + var element = sfonts[i]; + element.style.fontSize = "13px"; + } + + for (var i = 0; i < ss.length; i++) { //subsub + var element = ss[i]; + element.style.fontSize = "7px"; + } + + for (var i = 0; i < videos.length; i++) { //videos + var element = videos[i]; + element.width = "100"; + element.height = "80"; + } + + for (var i = 0; i < pdfs.length; i++) { //pdfs + var element = pdfs[i]; + element.width = "20"; + element.height = "24"; + } + for (var i = 0; i < vis.length; i++) { //img 1 + var element = vis[i]; + element.width = "56"; + element.height = "40"; + } + for (var i = 0; i < visb.length; i++) { //img 2 + var element = visb[i]; + element.width = "111"; + element.height = "82"; + } + + for (var i = 0; i < xmap.length; i++) { //map + var element = xmap[i]; + element.width = "150"; + element.height = "140"; + } +} + +function layer4() { + for (var i = 0; i < texts.length; i++) { //text + var element = texts[i]; + element.style.fontSize = "17px"; + } + + for (var i = 0; i < fonts.length; i++) { //themes + var element = fonts[i]; + element.style.fontSize = "30px"; + } + + for (var i = 0; i < sfonts.length; i++) { //subthemes + var element = sfonts[i]; + element.style.fontSize = "16px"; + } + + for (var i = 0; i < ss.length; i++) { //subsub + var element = ss[i]; + element.style.fontSize = "8px"; + } + + + for (var i = 0; i < videos.length; i++) { //videos + var element = videos[i]; + element.width = "210"; + element.height = "160"; + } + for (var i = 0; i < pdfs.length; i++) { //pdfs + var element = pdfs[i]; + element.width = "40"; + element.height = "50"; + } + for (var i = 0; i < vis.length; i++) { //img 1 + var element = vis[i]; + element.width = "111"; + element.height = "82"; + } + for (var i = 0; i < visb.length; i++) { //img 2 + var element = visb[i]; + element.width = "297"; + element.height = "210"; + } + + for (var i = 0; i < xmap.length; i++) { //map + var element = xmap[i]; + element.width = "250"; + element.height = "240"; + } +} +function layer5() { + for (var i = 0; i < fonts.length; i++) { //themes + var element = fonts[i]; + element.style.fontSize = "25px"; + } + + for (var i = 0; i < sfonts.length; i++) { //subthemes + var element = sfonts[i]; + element.style.fontSize = "25px"; + } + + for (var i = 0; i < ss.length; i++) { //subsub + var element = ss[i]; + element.style.fontSize = "15px"; + } + + for (var i = 0; i < videos.length; i++) { //videos + var element = videos[i]; + element.width = "360"; + element.height = "300"; + } + for (var i = 0; i < pdfs.length; i++) { //pdfs + var element = pdfs[i]; + element.width = "58"; + element.height = "70"; + } + for (var i = 0; i < vis.length; i++) { //img 1 + var element = vis[i]; + element.width = "297"; + element.height = "210"; + } + + for (var i = 0; i < visb.length; i++) { //img 2 + var element = visb[i]; + element.width = "600"; + element.height = "424"; + } + + for (var i = 0; i < xmap.length; i++) { //map + var element = xmap[i]; + element.width = "600"; + element.height = "590"; + } + +} + +function layer6() { + for (var i = 0; i < texts.length; i++) { //text + var element = texts[i]; + element.style.fontSize = "35px"; + } + + for (var i = 0; i < fonts.length; i++) { //themes + var element = fonts[i]; + element.style.fontSize = "90px"; + } + + for (var i = 0; i < sfonts.length; i++) { //subthemes + var element = sfonts[i]; + element.style.fontSize = "45px"; + } + + for (var i = 0; i < ss.length; i++) { //subsub + var element = ss[i]; + element.style.fontSize = "20px"; + } + for (var i = 0; i < videos.length; i++) { //videos + var element = videos[i]; + element.width = "460"; + element.height = "400"; + } + for (var i = 0; i < pdfs.length; i++) { //pdfs + var element = pdfs[i]; + element.width = "76"; + element.height = "90"; + } + for (var i = 0; i < vis.length; i++) { //img 1 + var element = vis[i]; + element.width = "600"; + element.height = "424"; + } + + for (var i = 0; i < visb.length; i++) { //img 2 + var element = visb[i]; + element.width = "1131"; + element.height = "800"; + } + + for (var i = 0; i < xmap.length; i++) { //map + var element = xmap[i]; + element.width = "1100"; + element.height = "1000"; + } + +} + +function layer7() { + for (var i = 0; i < texts.length; i++) { //text + var element = texts[i]; + element.style.fontSize = "45px"; + } + + for (var i = 0; i < fonts.length; i++) { //themes + var element = fonts[i]; + element.style.fontSize = "90px"; + } + + for (var i = 0; i < sfonts.length; i++) { //subthemes + var element = sfonts[i]; + element.style.fontSize = "45px"; + } + + for (var i = 0; i < ss.length; i++) { //subsub + var element = ss[i]; + element.style.fontSize = "30px"; + } + + for (var i = 0; i < videos.length; i++) { //videos + var element = videos[i]; + element.width = "700"; + element.height = "600"; + } + for (var i = 0; i < pdfs.length; i++) { //pdfs + var element = pdfs[i]; + element.width = "76"; + element.height = "90"; + } + for (var i = 0; i < vis.length; i++) { //img 1 + var element = vis[i]; + element.width = "1131"; + element.height = "800"; + } + + for (var i = 0; i < visb.length; i++) { //img 2 + var element = visb[i]; + element.width = "1400"; + element.height = "990"; + } +} + +map.on('zoomend', function(ev){ + if (map.getZoom() == 0) { + layer1(); + } else if (map.getZoom() == 1) { + layer2(); + } else if (map.getZoom() == 2) { + layer3(); + } else if (map.getZoom() == 3) { + layer4(); + } else if (map.getZoom() == 4){ + layer5(); + } else if (map.getZoom() == 5){ + layer6(); + } else { + layer7(); + } +}); \ No newline at end of file diff --git a/Research/ciao.urca.tv/leaflet-hash/LICENSE.md b/Research/ciao.urca.tv/leaflet-hash/LICENSE.md new file mode 100644 index 0000000..a46a450 --- /dev/null +++ b/Research/ciao.urca.tv/leaflet-hash/LICENSE.md @@ -0,0 +1,7 @@ +Copyright (c) 2013 Michael Lawrence Evans + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Research/ciao.urca.tv/leaflet-hash/README.md b/Research/ciao.urca.tv/leaflet-hash/README.md new file mode 100644 index 0000000..596d01e --- /dev/null +++ b/Research/ciao.urca.tv/leaflet-hash/README.md @@ -0,0 +1,41 @@ +# Leaflet-hash + +Leaflet-hash lets you to add dynamic URL hashes to web pages with Leaflet maps. You can easily +link users to specific map views. + +![Leaflet-hash](https://github.com/mlevans/leaflet-hash/raw/master/screenshots/screenshot.png) + +### Demo +You can view a demo of leaflet-hash at [mlevans.github.io/leaflet-hash/map.html](http://mlevans.github.io/leaflet-hash/map.html). + +### Getting started + +1. Prepare a basic leaflet map. You can find instructions on [Leaflet's quick-start guide](http://leaflet.cloudmade.com/examples/quick-start.html). + +2. Include [leaflet-hash.js](https://github.com/mlevans/leaflet-hash/blob/master/leaflet-hash.js). + +3. Once you have initialized the map (an instance of [L.Map](http://leaflet.cloudmade.com/reference.html#map-usage)), add the following code: + + ```javascript + // Assuming your map instance is in a variable called map + var hash = new L.Hash(map); + ``` + +### Author +[@mlevans](http://github.com/mlevans) + +### Contributors +[@calvinmetcalf](http://github.com/calvinmetcalf) + +[@jfirebaugh](http://github.com/jfirebaugh) + +[@rsudekum](http://github.com/rsudekum) + +[@tmcw](http://github.com/tmcw) + +[@yohanboniface](http://github.com/yohanboniface) + + +### License + +MIT License. See [LICENSE](https://github.com/mlevans/leaflet-hash/blob/master/LICENSE.md) for details. diff --git a/Research/ciao.urca.tv/leaflet-hash/leaflet-hash.js b/Research/ciao.urca.tv/leaflet-hash/leaflet-hash.js new file mode 100644 index 0000000..70a1007 --- /dev/null +++ b/Research/ciao.urca.tv/leaflet-hash/leaflet-hash.js @@ -0,0 +1,162 @@ +(function(window) { + var HAS_HASHCHANGE = (function() { + var doc_mode = window.documentMode; + return ('onhashchange' in window) && + (doc_mode === undefined || doc_mode > 7); + })(); + + L.Hash = function(map) { + this.onHashChange = L.Util.bind(this.onHashChange, this); + + if (map) { + this.init(map); + } + }; + + L.Hash.parseHash = function(hash) { + if(hash.indexOf('#') === 0) { + hash = hash.substr(1); + } + var args = hash.split("/"); + if (args.length == 3) { + var zoom = parseInt(args[0], 10), + lat = parseFloat(args[1]), + lon = parseFloat(args[2]); + if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) { + return false; + } else { + return { + center: new L.LatLng(lat, lon), + zoom: zoom + }; + } + } else { + return false; + } + }; + + L.Hash.formatHash = function(map) { + var center = map.getCenter(), + zoom = map.getZoom(), + precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); + + return "#" + [zoom, + center.lat.toFixed(precision), + center.lng.toFixed(precision) + ].join("/"); + }, + + L.Hash.prototype = { + map: null, + lastHash: null, + + parseHash: L.Hash.parseHash, + formatHash: L.Hash.formatHash, + + init: function(map) { + this.map = map; + + // reset the hash + this.lastHash = null; + this.onHashChange(); + + if (!this.isListening) { + this.startListening(); + } + }, + + removeFrom: function(map) { + if (this.changeTimeout) { + clearTimeout(this.changeTimeout); + } + + if (this.isListening) { + this.stopListening(); + } + + this.map = null; + }, + + onMapMove: function() { + // bail if we're moving the map (updating from a hash), + // or if the map is not yet loaded + + if (this.movingMap || !this.map._loaded) { + return false; + } + + var hash = this.formatHash(this.map); + if (this.lastHash != hash) { + location.replace(hash); + this.lastHash = hash; + } + }, + + movingMap: false, + update: function() { + var hash = location.hash; + if (hash === this.lastHash) { + return; + } + var parsed = this.parseHash(hash); + if (parsed) { + this.movingMap = true; + + this.map.setView(parsed.center, parsed.zoom); + + this.movingMap = false; + } else { + this.onMapMove(this.map); + } + }, + + // defer hash change updates every 100ms + changeDefer: 100, + changeTimeout: null, + onHashChange: function() { + // throttle calls to update() so that they only happen every + // `changeDefer` ms + if (!this.changeTimeout) { + var that = this; + this.changeTimeout = setTimeout(function() { + that.update(); + that.changeTimeout = null; + }, this.changeDefer); + } + }, + + isListening: false, + hashChangeInterval: null, + startListening: function() { + this.map.on("moveend", this.onMapMove, this); + + if (HAS_HASHCHANGE) { + L.DomEvent.addListener(window, "hashchange", this.onHashChange); + } else { + clearInterval(this.hashChangeInterval); + this.hashChangeInterval = setInterval(this.onHashChange, 50); + } + this.isListening = true; + }, + + stopListening: function() { + this.map.off("moveend", this.onMapMove, this); + + if (HAS_HASHCHANGE) { + L.DomEvent.removeListener(window, "hashchange", this.onHashChange); + } else { + clearInterval(this.hashChangeInterval); + } + this.isListening = false; + } + }; + L.hash = function(map) { + return new L.Hash(map); + }; + L.Map.prototype.addHash = function() { + this._hash = L.hash(this); + }; + L.Map.prototype.removeHash = function() { + this._hash.removeFrom(); + }; +})(window); diff --git a/Research/ciao.urca.tv/leaflet-hash/lib/leaflet-src.js b/Research/ciao.urca.tv/leaflet-hash/lib/leaflet-src.js new file mode 100644 index 0000000..c5b4c6d --- /dev/null +++ b/Research/ciao.urca.tv/leaflet-hash/lib/leaflet-src.js @@ -0,0 +1,8339 @@ +/* + Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com + (c) 2010-2013, Vladimir Agafonkin, CloudMade +*/ +(function (window, document, undefined) {/* + * The L namespace contains all Leaflet classes and functions. + * This code allows you to handle any possible namespace conflicts. + */ + +var L, originalL; + +if (typeof exports !== undefined + '') { + L = exports; +} else { + originalL = window.L; + L = {}; + + L.noConflict = function () { + window.L = originalL; + return this; + }; + + window.L = L; +} + +L.version = '0.5.1'; + + +/* + * L.Util contains various utility functions used throughout Leaflet code. + */ + +L.Util = { + extend: function (dest) { // (Object[, Object, ...]) -> + var sources = Array.prototype.slice.call(arguments, 1), + i, j, len, src; + + for (j = 0, len = sources.length; j < len; j++) { + src = sources[j] || {}; + for (i in src) { + if (src.hasOwnProperty(i)) { + dest[i] = src[i]; + } + } + } + return dest; + }, + + bind: function (fn, obj) { // (Function, Object) -> Function + var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null; + return function () { + return fn.apply(obj, args || arguments); + }; + }, + + stamp: (function () { + var lastId = 0, key = '_leaflet_id'; + return function (/*Object*/ obj) { + obj[key] = obj[key] || ++lastId; + return obj[key]; + }; + }()), + + limitExecByInterval: function (fn, time, context) { + var lock, execOnUnlock; + + return function wrapperFn() { + var args = arguments; + + if (lock) { + execOnUnlock = true; + return; + } + + lock = true; + + setTimeout(function () { + lock = false; + + if (execOnUnlock) { + wrapperFn.apply(context, args); + execOnUnlock = false; + } + }, time); + + fn.apply(context, args); + }; + }, + + falseFn: function () { + return false; + }, + + formatNum: function (num, digits) { + var pow = Math.pow(10, digits || 5); + return Math.round(num * pow) / pow; + }, + + splitWords: function (str) { + return str.replace(/^\s+|\s+$/g, '').split(/\s+/); + }, + + setOptions: function (obj, options) { + obj.options = L.extend({}, obj.options, options); + return obj.options; + }, + + getParamString: function (obj, existingUrl) { + var params = []; + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + params.push(i + '=' + obj[i]); + } + } + return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); + }, + + template: function (str, data) { + return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) { + var value = data[key]; + if (!data.hasOwnProperty(key)) { + throw new Error('No value provided for variable ' + str); + } + return value; + }); + }, + + isArray: function (obj) { + return (Object.prototype.toString.call(obj) === '[object Array]'); + }, + + emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' +}; + +(function () { + + // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ + + function getPrefixed(name) { + var i, fn, + prefixes = ['webkit', 'moz', 'o', 'ms']; + + for (i = 0; i < prefixes.length && !fn; i++) { + fn = window[prefixes[i] + name]; + } + + return fn; + } + + var lastTime = 0; + + function timeoutDefer(fn) { + var time = +new Date(), + timeToCall = Math.max(0, 16 - (time - lastTime)); + + lastTime = time + timeToCall; + return window.setTimeout(fn, timeToCall); + } + + var requestFn = window.requestAnimationFrame || + getPrefixed('RequestAnimationFrame') || timeoutDefer; + + var cancelFn = window.cancelAnimationFrame || + getPrefixed('CancelAnimationFrame') || + getPrefixed('CancelRequestAnimationFrame') || + function (id) { window.clearTimeout(id); }; + + + L.Util.requestAnimFrame = function (fn, context, immediate, element) { + fn = L.bind(fn, context); + + if (immediate && requestFn === timeoutDefer) { + fn(); + } else { + return requestFn.call(window, fn, element); + } + }; + + L.Util.cancelAnimFrame = function (id) { + if (id) { + cancelFn.call(window, id); + } + }; + +}()); + +// shortcuts for most used utility functions +L.extend = L.Util.extend; +L.bind = L.Util.bind; +L.stamp = L.Util.stamp; +L.setOptions = L.Util.setOptions; + + +/* + * L.Class powers the OOP facilities of the library. + * Thanks to John Resig and Dean Edwards for inspiration! + */ + +L.Class = function () {}; + +L.Class.extend = function (props) { + + // extended class with the new prototype + var NewClass = function () { + + // call the constructor + if (this.initialize) { + this.initialize.apply(this, arguments); + } + + // call all constructor hooks + if (this._initHooks) { + this.callInitHooks(); + } + }; + + // instantiate class without calling constructor + var F = function () {}; + F.prototype = this.prototype; + + var proto = new F(); + proto.constructor = NewClass; + + NewClass.prototype = proto; + + //inherit parent's statics + for (var i in this) { + if (this.hasOwnProperty(i) && i !== 'prototype') { + NewClass[i] = this[i]; + } + } + + // mix static properties into the class + if (props.statics) { + L.extend(NewClass, props.statics); + delete props.statics; + } + + // mix includes into the prototype + if (props.includes) { + L.Util.extend.apply(null, [proto].concat(props.includes)); + delete props.includes; + } + + // merge options + if (props.options && proto.options) { + props.options = L.extend({}, proto.options, props.options); + } + + // mix given properties into the prototype + L.extend(proto, props); + + proto._initHooks = []; + + var parent = this; + // add method for calling all hooks + proto.callInitHooks = function () { + + if (this._initHooksCalled) { return; } + + if (parent.prototype.callInitHooks) { + parent.prototype.callInitHooks.call(this); + } + + this._initHooksCalled = true; + + for (var i = 0, len = proto._initHooks.length; i < len; i++) { + proto._initHooks[i].call(this); + } + }; + + return NewClass; +}; + + +// method for adding properties to prototype +L.Class.include = function (props) { + L.extend(this.prototype, props); +}; + +// merge new default options to the Class +L.Class.mergeOptions = function (options) { + L.extend(this.prototype.options, options); +}; + +// add a constructor hook +L.Class.addInitHook = function (fn) { // (Function) || (String, args...) + var args = Array.prototype.slice.call(arguments, 1); + + var init = typeof fn === 'function' ? fn : function () { + this[fn].apply(this, args); + }; + + this.prototype._initHooks = this.prototype._initHooks || []; + this.prototype._initHooks.push(init); +}; + + +/* + * L.Mixin.Events is used to add custom events functionality to Leaflet classes. + */ + +var key = '_leaflet_events'; + +L.Mixin = {}; + +L.Mixin.Events = { + + addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object]) + var events = this[key] = this[key] || {}, + type, i, len; + + // Types can be a map of types/handlers + if (typeof types === 'object') { + for (type in types) { + if (types.hasOwnProperty(type)) { + this.addEventListener(type, types[type], fn); + } + } + + return this; + } + + types = L.Util.splitWords(types); + + for (i = 0, len = types.length; i < len; i++) { + events[types[i]] = events[types[i]] || []; + events[types[i]].push({ + action: fn, + context: context || this + }); + } + + return this; + }, + + hasEventListeners: function (type) { // (String) -> Boolean + return (key in this) && (type in this[key]) && (this[key][type].length > 0); + }, + + removeEventListener: function (types, fn, context) { // (String[, Function, Object]) or (Object[, Object]) + var events = this[key], + type, i, len, listeners, j; + + if (typeof types === 'object') { + for (type in types) { + if (types.hasOwnProperty(type)) { + this.removeEventListener(type, types[type], fn); + } + } + + return this; + } + + types = L.Util.splitWords(types); + + for (i = 0, len = types.length; i < len; i++) { + + if (this.hasEventListeners(types[i])) { + listeners = events[types[i]]; + + for (j = listeners.length - 1; j >= 0; j--) { + if ( + (!fn || listeners[j].action === fn) && + (!context || (listeners[j].context === context)) + ) { + listeners.splice(j, 1); + } + } + } + } + + return this; + }, + + fireEvent: function (type, data) { // (String[, Object]) + if (!this.hasEventListeners(type)) { + return this; + } + + var event = L.extend({ + type: type, + target: this + }, data); + + var listeners = this[key][type].slice(); + + for (var i = 0, len = listeners.length; i < len; i++) { + listeners[i].action.call(listeners[i].context || this, event); + } + + return this; + } +}; + +L.Mixin.Events.on = L.Mixin.Events.addEventListener; +L.Mixin.Events.off = L.Mixin.Events.removeEventListener; +L.Mixin.Events.fire = L.Mixin.Events.fireEvent; + + +/* + * L.Browser handles different browser and feature detections for internal Leaflet use. + */ + +(function () { + + var ie = !!window.ActiveXObject, + ie6 = ie && !window.XMLHttpRequest, + ie7 = ie && !document.querySelector, + + // terrible browser detection to work around Safari / iOS / Android browser bugs + ua = navigator.userAgent.toLowerCase(), + webkit = ua.indexOf('webkit') !== -1, + chrome = ua.indexOf('chrome') !== -1, + android = ua.indexOf('android') !== -1, + android23 = ua.search('android [23]') !== -1, + + mobile = typeof orientation !== undefined + '', + msTouch = window.navigator && window.navigator.msPointerEnabled && + window.navigator.msMaxTouchPoints, + retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) || + ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') && + window.matchMedia('(min-resolution:144dpi)').matches), + + doc = document.documentElement, + ie3d = ie && ('transition' in doc.style), + webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()), + gecko3d = 'MozPerspective' in doc.style, + opera3d = 'OTransition' in doc.style, + any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d); + + + var touch = !window.L_NO_TOUCH && (function () { + + var startName = 'ontouchstart'; + + // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.MsTouch) or WebKit, etc. + if (msTouch || (startName in doc)) { + return true; + } + + // Firefox/Gecko + var div = document.createElement('div'), + supported = false; + + if (!div.setAttribute) { + return false; + } + div.setAttribute(startName, 'return;'); + + if (typeof div[startName] === 'function') { + supported = true; + } + + div.removeAttribute(startName); + div = null; + + return supported; + }()); + + + L.Browser = { + ie: ie, + ie6: ie6, + ie7: ie7, + webkit: webkit, + + android: android, + android23: android23, + + chrome: chrome, + + ie3d: ie3d, + webkit3d: webkit3d, + gecko3d: gecko3d, + opera3d: opera3d, + any3d: any3d, + + mobile: mobile, + mobileWebkit: mobile && webkit, + mobileWebkit3d: mobile && webkit3d, + mobileOpera: mobile && window.opera, + + touch: touch, + msTouch: msTouch, + + retina: retina + }; + +}()); + + +/* + * L.Point represents a point with x and y coordinates. + */ + +L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) { + this.x = (round ? Math.round(x) : x); + this.y = (round ? Math.round(y) : y); +}; + +L.Point.prototype = { + + clone: function () { + return new L.Point(this.x, this.y); + }, + + // non-destructive, returns a new point + add: function (point) { + return this.clone()._add(L.point(point)); + }, + + // destructive, used directly for performance in situations where it's safe to modify existing point + _add: function (point) { + this.x += point.x; + this.y += point.y; + return this; + }, + + subtract: function (point) { + return this.clone()._subtract(L.point(point)); + }, + + _subtract: function (point) { + this.x -= point.x; + this.y -= point.y; + return this; + }, + + divideBy: function (num) { + return this.clone()._divideBy(num); + }, + + _divideBy: function (num) { + this.x /= num; + this.y /= num; + return this; + }, + + multiplyBy: function (num) { + return this.clone()._multiplyBy(num); + }, + + _multiplyBy: function (num) { + this.x *= num; + this.y *= num; + return this; + }, + + round: function () { + return this.clone()._round(); + }, + + _round: function () { + this.x = Math.round(this.x); + this.y = Math.round(this.y); + return this; + }, + + floor: function () { + return this.clone()._floor(); + }, + + _floor: function () { + this.x = Math.floor(this.x); + this.y = Math.floor(this.y); + return this; + }, + + distanceTo: function (point) { + point = L.point(point); + + var x = point.x - this.x, + y = point.y - this.y; + + return Math.sqrt(x * x + y * y); + }, + + equals: function (point) { + return point.x === this.x && + point.y === this.y; + }, + + toString: function () { + return 'Point(' + + L.Util.formatNum(this.x) + ', ' + + L.Util.formatNum(this.y) + ')'; + } +}; + +L.point = function (x, y, round) { + if (x instanceof L.Point) { + return x; + } + if (L.Util.isArray(x)) { + return new L.Point(x[0], x[1]); + } + if (isNaN(x)) { + return x; + } + return new L.Point(x, y, round); +}; + + +/* + * L.Bounds represents a rectangular area on the screen in pixel coordinates. + */ + +L.Bounds = function (a, b) { //(Point, Point) or Point[] + if (!a) { return; } + + var points = b ? [a, b] : a; + + for (var i = 0, len = points.length; i < len; i++) { + this.extend(points[i]); + } +}; + +L.Bounds.prototype = { + // extend the bounds to contain the given point + extend: function (point) { // (Point) + point = L.point(point); + + if (!this.min && !this.max) { + this.min = point.clone(); + this.max = point.clone(); + } else { + this.min.x = Math.min(point.x, this.min.x); + this.max.x = Math.max(point.x, this.max.x); + this.min.y = Math.min(point.y, this.min.y); + this.max.y = Math.max(point.y, this.max.y); + } + return this; + }, + + getCenter: function (round) { // (Boolean) -> Point + return new L.Point( + (this.min.x + this.max.x) / 2, + (this.min.y + this.max.y) / 2, round); + }, + + getBottomLeft: function () { // -> Point + return new L.Point(this.min.x, this.max.y); + }, + + getTopRight: function () { // -> Point + return new L.Point(this.max.x, this.min.y); + }, + + getSize: function () { + return this.max.subtract(this.min); + }, + + contains: function (obj) { // (Bounds) or (Point) -> Boolean + var min, max; + + if (typeof obj[0] === 'number' || obj instanceof L.Point) { + obj = L.point(obj); + } else { + obj = L.bounds(obj); + } + + if (obj instanceof L.Bounds) { + min = obj.min; + max = obj.max; + } else { + min = max = obj; + } + + return (min.x >= this.min.x) && + (max.x <= this.max.x) && + (min.y >= this.min.y) && + (max.y <= this.max.y); + }, + + intersects: function (bounds) { // (Bounds) -> Boolean + bounds = L.bounds(bounds); + + var min = this.min, + max = this.max, + min2 = bounds.min, + max2 = bounds.max, + xIntersects = (max2.x >= min.x) && (min2.x <= max.x), + yIntersects = (max2.y >= min.y) && (min2.y <= max.y); + + return xIntersects && yIntersects; + }, + + isValid: function () { + return !!(this.min && this.max); + } +}; + +L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[]) + if (!a || a instanceof L.Bounds) { + return a; + } + return new L.Bounds(a, b); +}; + + +/* + * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix. + */ + +L.Transformation = function (a, b, c, d) { + this._a = a; + this._b = b; + this._c = c; + this._d = d; +}; + +L.Transformation.prototype = { + transform: function (point, scale) { // (Point, Number) -> Point + return this._transform(point.clone(), scale); + }, + + // destructive transform (faster) + _transform: function (point, scale) { + scale = scale || 1; + point.x = scale * (this._a * point.x + this._b); + point.y = scale * (this._c * point.y + this._d); + return point; + }, + + untransform: function (point, scale) { + scale = scale || 1; + return new L.Point( + (point.x / scale - this._b) / this._a, + (point.y / scale - this._d) / this._c); + } +}; + + +/* + * L.DomUtil contains various utility functions for working with DOM. + */ + +L.DomUtil = { + get: function (id) { + return (typeof id === 'string' ? document.getElementById(id) : id); + }, + + getStyle: function (el, style) { + + var value = el.style[style]; + + if (!value && el.currentStyle) { + value = el.currentStyle[style]; + } + + if ((!value || value === 'auto') && document.defaultView) { + var css = document.defaultView.getComputedStyle(el, null); + value = css ? css[style] : null; + } + + return value === 'auto' ? null : value; + }, + + getViewportOffset: function (element) { + + var top = 0, + left = 0, + el = element, + docBody = document.body, + pos, + ie7 = L.Browser.ie7; + + do { + top += el.offsetTop || 0; + left += el.offsetLeft || 0; + + //add borders + top += parseInt(L.DomUtil.getStyle(el, "borderTopWidth"), 10) || 0; + left += parseInt(L.DomUtil.getStyle(el, "borderLeftWidth"), 10) || 0; + + pos = L.DomUtil.getStyle(el, 'position'); + + if (el.offsetParent === docBody && pos === 'absolute') { break; } + + if (pos === 'fixed') { + top += docBody.scrollTop || 0; + left += docBody.scrollLeft || 0; + break; + } + el = el.offsetParent; + + } while (el); + + el = element; + + do { + if (el === docBody) { break; } + + top -= el.scrollTop || 0; + left -= el.scrollLeft || 0; + + // webkit (and ie <= 7) handles RTL scrollLeft different to everyone else + // https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js + if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) { + left += el.scrollWidth - el.clientWidth; + + // ie7 shows the scrollbar by default and provides clientWidth counting it, so we + // need to add it back in if it is visible; scrollbar is on the left as we are RTL + if (ie7 && L.DomUtil.getStyle(el, 'overflow-y') !== 'hidden' && + L.DomUtil.getStyle(el, 'overflow') !== 'hidden') { + left += 17; + } + } + + el = el.parentNode; + } while (el); + + return new L.Point(left, top); + }, + + documentIsLtr: function () { + if (!L.DomUtil._docIsLtrCached) { + L.DomUtil._docIsLtrCached = true; + L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === "ltr"; + } + return L.DomUtil._docIsLtr; + }, + + create: function (tagName, className, container) { + + var el = document.createElement(tagName); + el.className = className; + + if (container) { + container.appendChild(el); + } + + return el; + }, + + disableTextSelection: function () { + if (document.selection && document.selection.empty) { + document.selection.empty(); + } + if (!this._onselectstart) { + this._onselectstart = document.onselectstart || null; + document.onselectstart = L.Util.falseFn; + } + }, + + enableTextSelection: function () { + if (document.onselectstart === L.Util.falseFn) { + document.onselectstart = this._onselectstart; + this._onselectstart = null; + } + }, + + hasClass: function (el, name) { + return (el.className.length > 0) && + new RegExp("(^|\\s)" + name + "(\\s|$)").test(el.className); + }, + + addClass: function (el, name) { + if (!L.DomUtil.hasClass(el, name)) { + el.className += (el.className ? ' ' : '') + name; + } + }, + + removeClass: function (el, name) { + + function replaceFn(w, match) { + if (match === name) { return ''; } + return w; + } + + el.className = el.className + .replace(/(\S+)\s*/g, replaceFn) + .replace(/(^\s+|\s+$)/, ''); + }, + + setOpacity: function (el, value) { + + if ('opacity' in el.style) { + el.style.opacity = value; + + } else if ('filter' in el.style) { + + var filter = false, + filterName = 'DXImageTransform.Microsoft.Alpha'; + + // filters collection throws an error if we try to retrieve a filter that doesn't exist + try { filter = el.filters.item(filterName); } catch (e) {} + + value = Math.round(value * 100); + + if (filter) { + filter.Enabled = (value !== 100); + filter.Opacity = value; + } else { + el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; + } + } + }, + + testProp: function (props) { + + var style = document.documentElement.style; + + for (var i = 0; i < props.length; i++) { + if (props[i] in style) { + return props[i]; + } + } + return false; + }, + + getTranslateString: function (point) { + // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate + // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care + // (same speed either way), Opera 12 doesn't support translate3d + + var is3d = L.Browser.webkit3d, + open = 'translate' + (is3d ? '3d' : '') + '(', + close = (is3d ? ',0' : '') + ')'; + + return open + point.x + 'px,' + point.y + 'px' + close; + }, + + getScaleString: function (scale, origin) { + + var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))), + scaleStr = ' scale(' + scale + ') '; + + return preTranslateStr + scaleStr; + }, + + setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean]) + + el._leaflet_pos = point; + + if (!disable3D && L.Browser.any3d) { + el.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(point); + + // workaround for Android 2/3 stability (https://github.com/CloudMade/Leaflet/issues/69) + if (L.Browser.mobileWebkit3d) { + el.style.WebkitBackfaceVisibility = 'hidden'; + } + } else { + el.style.left = point.x + 'px'; + el.style.top = point.y + 'px'; + } + }, + + getPosition: function (el) { + // this method is only used for elements previously positioned using setPosition, + // so it's safe to cache the position for performance + return el._leaflet_pos; + } +}; + + +// prefix style property names + +L.DomUtil.TRANSFORM = L.DomUtil.testProp( + ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); + +// webkitTransition comes first because some browser versions that drop vendor prefix don't do +// the same for the transitionend event, in particular the Android 4.1 stock browser + +L.DomUtil.TRANSITION = L.DomUtil.testProp( + ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); + +L.DomUtil.TRANSITION_END = + L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ? + L.DomUtil.TRANSITION + 'End' : 'transitionend'; + + +/* + * L.LatLng represents a geographical point with latitude and longitude coordinates. + */ + +L.LatLng = function (rawLat, rawLng) { // (Number, Number) + var lat = parseFloat(rawLat), + lng = parseFloat(rawLng); + + if (isNaN(lat) || isNaN(lng)) { + throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')'); + } + + this.lat = lat; + this.lng = lng; +}; + +L.extend(L.LatLng, { + DEG_TO_RAD: Math.PI / 180, + RAD_TO_DEG: 180 / Math.PI, + MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check +}); + +L.LatLng.prototype = { + equals: function (obj) { // (LatLng) -> Boolean + if (!obj) { return false; } + + obj = L.latLng(obj); + + var margin = Math.max( + Math.abs(this.lat - obj.lat), + Math.abs(this.lng - obj.lng)); + + return margin <= L.LatLng.MAX_MARGIN; + }, + + toString: function (precision) { // (Number) -> String + return 'LatLng(' + + L.Util.formatNum(this.lat, precision) + ', ' + + L.Util.formatNum(this.lng, precision) + ')'; + }, + + // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula + // TODO move to projection code, LatLng shouldn't know about Earth + distanceTo: function (other) { // (LatLng) -> Number + other = L.latLng(other); + + var R = 6378137, // earth radius in meters + d2r = L.LatLng.DEG_TO_RAD, + dLat = (other.lat - this.lat) * d2r, + dLon = (other.lng - this.lng) * d2r, + lat1 = this.lat * d2r, + lat2 = other.lat * d2r, + sin1 = Math.sin(dLat / 2), + sin2 = Math.sin(dLon / 2); + + var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2); + + return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + }, + + wrap: function (a, b) { // (Number, Number) -> LatLng + var lng = this.lng; + + a = a || -180; + b = b || 180; + + lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a); + + return new L.LatLng(this.lat, lng); + } +}; + +L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number) + if (a instanceof L.LatLng) { + return a; + } + if (L.Util.isArray(a)) { + return new L.LatLng(a[0], a[1]); + } + if (isNaN(a)) { + return a; + } + return new L.LatLng(a, b); +}; + + + +/* + * L.LatLngBounds represents a rectangular area on the map in geographical coordinates. + */ + +L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[]) + if (!southWest) { return; } + + var latlngs = northEast ? [southWest, northEast] : southWest; + + for (var i = 0, len = latlngs.length; i < len; i++) { + this.extend(latlngs[i]); + } +}; + +L.LatLngBounds.prototype = { + // extend the bounds to contain the given point or bounds + extend: function (obj) { // (LatLng) or (LatLngBounds) + if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || obj instanceof L.LatLng) { + obj = L.latLng(obj); + } else { + obj = L.latLngBounds(obj); + } + + if (obj instanceof L.LatLng) { + if (!this._southWest && !this._northEast) { + this._southWest = new L.LatLng(obj.lat, obj.lng); + this._northEast = new L.LatLng(obj.lat, obj.lng); + } else { + this._southWest.lat = Math.min(obj.lat, this._southWest.lat); + this._southWest.lng = Math.min(obj.lng, this._southWest.lng); + + this._northEast.lat = Math.max(obj.lat, this._northEast.lat); + this._northEast.lng = Math.max(obj.lng, this._northEast.lng); + } + } else if (obj instanceof L.LatLngBounds) { + this.extend(obj._southWest); + this.extend(obj._northEast); + } + return this; + }, + + // extend the bounds by a percentage + pad: function (bufferRatio) { // (Number) -> LatLngBounds + var sw = this._southWest, + ne = this._northEast, + heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, + widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; + + return new L.LatLngBounds( + new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), + new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); + }, + + getCenter: function () { // -> LatLng + return new L.LatLng( + (this._southWest.lat + this._northEast.lat) / 2, + (this._southWest.lng + this._northEast.lng) / 2); + }, + + getSouthWest: function () { + return this._southWest; + }, + + getNorthEast: function () { + return this._northEast; + }, + + getNorthWest: function () { + return new L.LatLng(this._northEast.lat, this._southWest.lng); + }, + + getSouthEast: function () { + return new L.LatLng(this._southWest.lat, this._northEast.lng); + }, + + contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean + if (typeof obj[0] === 'number' || obj instanceof L.LatLng) { + obj = L.latLng(obj); + } else { + obj = L.latLngBounds(obj); + } + + var sw = this._southWest, + ne = this._northEast, + sw2, ne2; + + if (obj instanceof L.LatLngBounds) { + sw2 = obj.getSouthWest(); + ne2 = obj.getNorthEast(); + } else { + sw2 = ne2 = obj; + } + + return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && + (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); + }, + + intersects: function (bounds) { // (LatLngBounds) + bounds = L.latLngBounds(bounds); + + var sw = this._southWest, + ne = this._northEast, + sw2 = bounds.getSouthWest(), + ne2 = bounds.getNorthEast(), + + latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), + lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); + + return latIntersects && lngIntersects; + }, + + toBBoxString: function () { + var sw = this._southWest, + ne = this._northEast; + + return [sw.lng, sw.lat, ne.lng, ne.lat].join(','); + }, + + equals: function (bounds) { // (LatLngBounds) + if (!bounds) { return false; } + + bounds = L.latLngBounds(bounds); + + return this._southWest.equals(bounds.getSouthWest()) && + this._northEast.equals(bounds.getNorthEast()); + }, + + isValid: function () { + return !!(this._southWest && this._northEast); + } +}; + +//TODO International date line? + +L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng) + if (!a || a instanceof L.LatLngBounds) { + return a; + } + return new L.LatLngBounds(a, b); +}; + + +/* + * L.Projection contains various geographical projections used by CRS classes. + */ + +L.Projection = {}; + + +/* + * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default. + */ + +L.Projection.SphericalMercator = { + MAX_LATITUDE: 85.0511287798, + + project: function (latlng) { // (LatLng) -> Point + var d = L.LatLng.DEG_TO_RAD, + max = this.MAX_LATITUDE, + lat = Math.max(Math.min(max, latlng.lat), -max), + x = latlng.lng * d, + y = lat * d; + + y = Math.log(Math.tan((Math.PI / 4) + (y / 2))); + + return new L.Point(x, y); + }, + + unproject: function (point) { // (Point, Boolean) -> LatLng + var d = L.LatLng.RAD_TO_DEG, + lng = point.x * d, + lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d; + + return new L.LatLng(lat, lng); + } +}; + + +/* + * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple. + */ + +L.Projection.LonLat = { + project: function (latlng) { + return new L.Point(latlng.lng, latlng.lat); + }, + + unproject: function (point) { + return new L.LatLng(point.y, point.x); + } +}; + + +/* + * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet. + */ + +L.CRS = { + latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point + var projectedPoint = this.projection.project(latlng), + scale = this.scale(zoom); + + return this.transformation._transform(projectedPoint, scale); + }, + + pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng + var scale = this.scale(zoom), + untransformedPoint = this.transformation.untransform(point, scale); + + return this.projection.unproject(untransformedPoint); + }, + + project: function (latlng) { + return this.projection.project(latlng); + }, + + scale: function (zoom) { + return 256 * Math.pow(2, zoom); + } +}; + + +/* + * A simple CRS that can be used for flat non-Earth maps like panoramas or game maps. + */ + +L.CRS.Simple = L.extend({}, L.CRS, { + projection: L.Projection.LonLat, + transformation: new L.Transformation(1, 0, -1, 0), + + scale: function (zoom) { + return Math.pow(2, zoom); + } +}); + + +/* + * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping + * and is used by Leaflet by default. + */ + +L.CRS.EPSG3857 = L.extend({}, L.CRS, { + code: 'EPSG:3857', + + projection: L.Projection.SphericalMercator, + transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5), + + project: function (latlng) { // (LatLng) -> Point + var projectedPoint = this.projection.project(latlng), + earthRadius = 6378137; + return projectedPoint.multiplyBy(earthRadius); + } +}); + +L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, { + code: 'EPSG:900913' +}); + + +/* + * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists. + */ + +L.CRS.EPSG4326 = L.extend({}, L.CRS, { + code: 'EPSG:4326', + + projection: L.Projection.LonLat, + transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5) +}); + + +/* + * L.Map is the central class of the API - it is used to create a map. + */ + +L.Map = L.Class.extend({ + + includes: L.Mixin.Events, + + options: { + crs: L.CRS.EPSG3857, + + /* + center: LatLng, + zoom: Number, + layers: Array, + */ + + fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23, + trackResize: true, + markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d + }, + + initialize: function (id, options) { // (HTMLElement or String, Object) + options = L.setOptions(this, options); + + this._initContainer(id); + this._initLayout(); + this.callInitHooks(); + this._initEvents(); + + if (options.maxBounds) { + this.setMaxBounds(options.maxBounds); + } + + if (options.center && options.zoom !== undefined) { + this.setView(L.latLng(options.center), options.zoom, true); + } + + this._initLayers(options.layers); + }, + + + // public methods that modify map state + + // replaced by animation-powered implementation in Map.PanAnimation.js + setView: function (center, zoom) { + this._resetView(L.latLng(center), this._limitZoom(zoom)); + return this; + }, + + setZoom: function (zoom) { // (Number) + return this.setView(this.getCenter(), zoom); + }, + + zoomIn: function (delta) { + return this.setZoom(this._zoom + (delta || 1)); + }, + + zoomOut: function (delta) { + return this.setZoom(this._zoom - (delta || 1)); + }, + + fitBounds: function (bounds) { // (LatLngBounds) + var zoom = this.getBoundsZoom(bounds); + return this.setView(L.latLngBounds(bounds).getCenter(), zoom); + }, + + fitWorld: function () { + var sw = new L.LatLng(-60, -170), + ne = new L.LatLng(85, 179); + + return this.fitBounds(new L.LatLngBounds(sw, ne)); + }, + + panTo: function (center) { // (LatLng) + return this.setView(center, this._zoom); + }, + + panBy: function (offset) { // (Point) + // replaced with animated panBy in Map.Animation.js + this.fire('movestart'); + + this._rawPanBy(L.point(offset)); + + this.fire('move'); + return this.fire('moveend'); + }, + + setMaxBounds: function (bounds) { + bounds = L.latLngBounds(bounds); + + this.options.maxBounds = bounds; + + if (!bounds) { + this._boundsMinZoom = null; + return this; + } + + var minZoom = this.getBoundsZoom(bounds, true); + + this._boundsMinZoom = minZoom; + + if (this._loaded) { + if (this._zoom < minZoom) { + this.setView(bounds.getCenter(), minZoom); + } else { + this.panInsideBounds(bounds); + } + } + + return this; + }, + + panInsideBounds: function (bounds) { + bounds = L.latLngBounds(bounds); + + var viewBounds = this.getBounds(), + viewSw = this.project(viewBounds.getSouthWest()), + viewNe = this.project(viewBounds.getNorthEast()), + sw = this.project(bounds.getSouthWest()), + ne = this.project(bounds.getNorthEast()), + dx = 0, + dy = 0; + + if (viewNe.y < ne.y) { // north + dy = ne.y - viewNe.y; + } + if (viewNe.x > ne.x) { // east + dx = ne.x - viewNe.x; + } + if (viewSw.y > sw.y) { // south + dy = sw.y - viewSw.y; + } + if (viewSw.x < sw.x) { // west + dx = sw.x - viewSw.x; + } + + return this.panBy(new L.Point(dx, dy, true)); + }, + + addLayer: function (layer) { + // TODO method is too big, refactor + + var id = L.stamp(layer); + + if (this._layers[id]) { return this; } + + this._layers[id] = layer; + + // TODO getMaxZoom, getMinZoom in ILayer (instead of options) + if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) { + this._zoomBoundLayers[id] = layer; + this._updateZoomLevels(); + } + + // TODO looks ugly, refactor!!! + if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { + this._tileLayersNum++; + this._tileLayersToLoad++; + layer.on('load', this._onTileLayerLoad, this); + } + + this.whenReady(function () { + layer.onAdd(this); + this.fire('layeradd', {layer: layer}); + }, this); + + return this; + }, + + removeLayer: function (layer) { + var id = L.stamp(layer); + + if (!this._layers[id]) { return; } + + layer.onRemove(this); + + delete this._layers[id]; + if (this._zoomBoundLayers[id]) { + delete this._zoomBoundLayers[id]; + this._updateZoomLevels(); + } + + // TODO looks ugly, refactor + if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { + this._tileLayersNum--; + this._tileLayersToLoad--; + layer.off('load', this._onTileLayerLoad, this); + } + + return this.fire('layerremove', {layer: layer}); + }, + + hasLayer: function (layer) { + var id = L.stamp(layer); + return this._layers.hasOwnProperty(id); + }, + + invalidateSize: function (animate) { + var oldSize = this.getSize(); + + this._sizeChanged = true; + + if (this.options.maxBounds) { + this.setMaxBounds(this.options.maxBounds); + } + + if (!this._loaded) { return this; } + + var offset = oldSize._subtract(this.getSize())._divideBy(2)._round(); + + if (animate === true) { + this.panBy(offset); + } else { + this._rawPanBy(offset); + + this.fire('move'); + + clearTimeout(this._sizeTimer); + this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200); + } + return this; + }, + + // TODO handler.addTo + addHandler: function (name, HandlerClass) { + if (!HandlerClass) { return; } + + this[name] = new HandlerClass(this); + + if (this.options[name]) { + this[name].enable(); + } + + return this; + }, + + + // public methods for getting map state + + getCenter: function () { // (Boolean) -> LatLng + return this.layerPointToLatLng(this._getCenterLayerPoint()); + }, + + getZoom: function () { + return this._zoom; + }, + + getBounds: function () { + var bounds = this.getPixelBounds(), + sw = this.unproject(bounds.getBottomLeft()), + ne = this.unproject(bounds.getTopRight()); + + return new L.LatLngBounds(sw, ne); + }, + + getMinZoom: function () { + var z1 = this.options.minZoom || 0, + z2 = this._layersMinZoom || 0, + z3 = this._boundsMinZoom || 0; + + return Math.max(z1, z2, z3); + }, + + getMaxZoom: function () { + var z1 = this.options.maxZoom === undefined ? Infinity : this.options.maxZoom, + z2 = this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom; + + return Math.min(z1, z2); + }, + + getBoundsZoom: function (bounds, inside) { // (LatLngBounds, Boolean) -> Number + bounds = L.latLngBounds(bounds); + + var size = this.getSize(), + zoom = this.options.minZoom || 0, + maxZoom = this.getMaxZoom(), + ne = bounds.getNorthEast(), + sw = bounds.getSouthWest(), + boundsSize, + nePoint, + swPoint, + zoomNotFound = true; + + if (inside) { + zoom--; + } + + do { + zoom++; + nePoint = this.project(ne, zoom); + swPoint = this.project(sw, zoom); + + boundsSize = new L.Point( + Math.abs(nePoint.x - swPoint.x), + Math.abs(swPoint.y - nePoint.y)); + + if (!inside) { + zoomNotFound = boundsSize.x <= size.x && boundsSize.y <= size.y; + } else { + zoomNotFound = boundsSize.x < size.x || boundsSize.y < size.y; + } + } while (zoomNotFound && zoom <= maxZoom); + + if (zoomNotFound && inside) { + return null; + } + + return inside ? zoom : zoom - 1; + }, + + getSize: function () { + if (!this._size || this._sizeChanged) { + this._size = new L.Point( + this._container.clientWidth, + this._container.clientHeight); + + this._sizeChanged = false; + } + return this._size.clone(); + }, + + getPixelBounds: function () { + var topLeftPoint = this._getTopLeftPoint(); + return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); + }, + + getPixelOrigin: function () { + return this._initialTopLeftPoint; + }, + + getPanes: function () { + return this._panes; + }, + + getContainer: function () { + return this._container; + }, + + + // TODO replace with universal implementation after refactoring projections + + getZoomScale: function (toZoom) { + var crs = this.options.crs; + return crs.scale(toZoom) / crs.scale(this._zoom); + }, + + getScaleZoom: function (scale) { + return this._zoom + (Math.log(scale) / Math.LN2); + }, + + + // conversion methods + + project: function (latlng, zoom) { // (LatLng[, Number]) -> Point + zoom = zoom === undefined ? this._zoom : zoom; + return this.options.crs.latLngToPoint(L.latLng(latlng), zoom); + }, + + unproject: function (point, zoom) { // (Point[, Number]) -> LatLng + zoom = zoom === undefined ? this._zoom : zoom; + return this.options.crs.pointToLatLng(L.point(point), zoom); + }, + + layerPointToLatLng: function (point) { // (Point) + var projectedPoint = L.point(point).add(this._initialTopLeftPoint); + return this.unproject(projectedPoint); + }, + + latLngToLayerPoint: function (latlng) { // (LatLng) + var projectedPoint = this.project(L.latLng(latlng))._round(); + return projectedPoint._subtract(this._initialTopLeftPoint); + }, + + containerPointToLayerPoint: function (point) { // (Point) + return L.point(point).subtract(this._getMapPanePos()); + }, + + layerPointToContainerPoint: function (point) { // (Point) + return L.point(point).add(this._getMapPanePos()); + }, + + containerPointToLatLng: function (point) { + var layerPoint = this.containerPointToLayerPoint(L.point(point)); + return this.layerPointToLatLng(layerPoint); + }, + + latLngToContainerPoint: function (latlng) { + return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng))); + }, + + mouseEventToContainerPoint: function (e) { // (MouseEvent) + return L.DomEvent.getMousePosition(e, this._container); + }, + + mouseEventToLayerPoint: function (e) { // (MouseEvent) + return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); + }, + + mouseEventToLatLng: function (e) { // (MouseEvent) + return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); + }, + + + // map initialization methods + + _initContainer: function (id) { + var container = this._container = L.DomUtil.get(id); + + if (container._leaflet) { + throw new Error("Map container is already initialized."); + } + + container._leaflet = true; + }, + + _initLayout: function () { + var container = this._container; + + L.DomUtil.addClass(container, 'leaflet-container'); + + if (L.Browser.touch) { + L.DomUtil.addClass(container, 'leaflet-touch'); + } + + if (this.options.fadeAnimation) { + L.DomUtil.addClass(container, 'leaflet-fade-anim'); + } + + var position = L.DomUtil.getStyle(container, 'position'); + + if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { + container.style.position = 'relative'; + } + + this._initPanes(); + + if (this._initControlPos) { + this._initControlPos(); + } + }, + + _initPanes: function () { + var panes = this._panes = {}; + + this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container); + + this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane); + panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane); + panes.shadowPane = this._createPane('leaflet-shadow-pane'); + panes.overlayPane = this._createPane('leaflet-overlay-pane'); + panes.markerPane = this._createPane('leaflet-marker-pane'); + panes.popupPane = this._createPane('leaflet-popup-pane'); + + var zoomHide = ' leaflet-zoom-hide'; + + if (!this.options.markerZoomAnimation) { + L.DomUtil.addClass(panes.markerPane, zoomHide); + L.DomUtil.addClass(panes.shadowPane, zoomHide); + L.DomUtil.addClass(panes.popupPane, zoomHide); + } + }, + + _createPane: function (className, container) { + return L.DomUtil.create('div', className, container || this._panes.objectsPane); + }, + + _initLayers: function (layers) { + layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : []; + + this._layers = {}; + this._zoomBoundLayers = {}; + this._tileLayersNum = 0; + + var i, len; + + for (i = 0, len = layers.length; i < len; i++) { + this.addLayer(layers[i]); + } + }, + + + // private methods that modify map state + + _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) { + + var zoomChanged = (this._zoom !== zoom); + + if (!afterZoomAnim) { + this.fire('movestart'); + + if (zoomChanged) { + this.fire('zoomstart'); + } + } + + this._zoom = zoom; + + this._initialTopLeftPoint = this._getNewTopLeftPoint(center); + + if (!preserveMapOffset) { + L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); + } else { + this._initialTopLeftPoint._add(this._getMapPanePos()); + } + + this._tileLayersToLoad = this._tileLayersNum; + + var loading = !this._loaded; + this._loaded = true; + + this.fire('viewreset', {hard: !preserveMapOffset}); + + this.fire('move'); + + if (zoomChanged || afterZoomAnim) { + this.fire('zoomend'); + } + + this.fire('moveend', {hard: !preserveMapOffset}); + + if (loading) { + this.fire('load'); + } + }, + + _rawPanBy: function (offset) { + L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); + }, + + _updateZoomLevels: function () { + var i, + minZoom = Infinity, + maxZoom = -Infinity; + + for (i in this._zoomBoundLayers) { + if (this._zoomBoundLayers.hasOwnProperty(i)) { + var layer = this._zoomBoundLayers[i]; + if (!isNaN(layer.options.minZoom)) { + minZoom = Math.min(minZoom, layer.options.minZoom); + } + if (!isNaN(layer.options.maxZoom)) { + maxZoom = Math.max(maxZoom, layer.options.maxZoom); + } + } + } + + if (i === undefined) { // we have no tilelayers + this._layersMaxZoom = this._layersMinZoom = undefined; + } else { + this._layersMaxZoom = maxZoom; + this._layersMinZoom = minZoom; + } + }, + + // map events + + _initEvents: function () { + if (!L.DomEvent) { return; } + + L.DomEvent.on(this._container, 'click', this._onMouseClick, this); + + var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter', + 'mouseleave', 'mousemove', 'contextmenu'], + i, len; + + for (i = 0, len = events.length; i < len; i++) { + L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this); + } + + if (this.options.trackResize) { + L.DomEvent.on(window, 'resize', this._onResize, this); + } + }, + + _onResize: function () { + L.Util.cancelAnimFrame(this._resizeRequest); + this._resizeRequest = L.Util.requestAnimFrame( + this.invalidateSize, this, false, this._container); + }, + + _onMouseClick: function (e) { + if (!this._loaded || (this.dragging && this.dragging.moved())) { return; } + + this.fire('preclick'); + this._fireMouseEvent(e); + }, + + _fireMouseEvent: function (e) { + if (!this._loaded) { return; } + + var type = e.type; + + type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type)); + + if (!this.hasEventListeners(type)) { return; } + + if (type === 'contextmenu') { + L.DomEvent.preventDefault(e); + } + + var containerPoint = this.mouseEventToContainerPoint(e), + layerPoint = this.containerPointToLayerPoint(containerPoint), + latlng = this.layerPointToLatLng(layerPoint); + + this.fire(type, { + latlng: latlng, + layerPoint: layerPoint, + containerPoint: containerPoint, + originalEvent: e + }); + }, + + _onTileLayerLoad: function () { + // TODO super-ugly, refactor!!! + // clear scaled tiles after all new tiles are loaded (for performance) + this._tileLayersToLoad--; + if (this._tileLayersNum && !this._tileLayersToLoad && this._tileBg) { + clearTimeout(this._clearTileBgTimer); + this._clearTileBgTimer = setTimeout(L.bind(this._clearTileBg, this), 500); + } + }, + + whenReady: function (callback, context) { + if (this._loaded) { + callback.call(context || this, this); + } else { + this.on('load', callback, context); + } + return this; + }, + + + // private methods for getting map state + + _getMapPanePos: function () { + return L.DomUtil.getPosition(this._mapPane); + }, + + _getTopLeftPoint: function () { + if (!this._loaded) { + throw new Error('Set map center and zoom first.'); + } + + return this._initialTopLeftPoint.subtract(this._getMapPanePos()); + }, + + _getNewTopLeftPoint: function (center, zoom) { + var viewHalf = this.getSize()._divideBy(2); + // TODO round on display, not calculation to increase precision? + return this.project(center, zoom)._subtract(viewHalf)._round(); + }, + + _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) { + var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos()); + return this.project(latlng, newZoom)._subtract(topLeft); + }, + + _getCenterLayerPoint: function () { + return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); + }, + + _getCenterOffset: function (center) { + return this.latLngToLayerPoint(center).subtract(this._getCenterLayerPoint()); + }, + + _limitZoom: function (zoom) { + var min = this.getMinZoom(), + max = this.getMaxZoom(); + + return Math.max(min, Math.min(max, zoom)); + } +}); + +L.map = function (id, options) { + return new L.Map(id, options); +}; + + +/* + * Mercator projection that takes into account that the Earth is not a perfect sphere. + * Less popular than spherical mercator; used by projections like EPSG:3395. + */ + +L.Projection.Mercator = { + MAX_LATITUDE: 85.0840591556, + + R_MINOR: 6356752.3142, + R_MAJOR: 6378137, + + project: function (latlng) { // (LatLng) -> Point + var d = L.LatLng.DEG_TO_RAD, + max = this.MAX_LATITUDE, + lat = Math.max(Math.min(max, latlng.lat), -max), + r = this.R_MAJOR, + r2 = this.R_MINOR, + x = latlng.lng * d * r, + y = lat * d, + tmp = r2 / r, + eccent = Math.sqrt(1.0 - tmp * tmp), + con = eccent * Math.sin(y); + + con = Math.pow((1 - con) / (1 + con), eccent * 0.5); + + var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con; + y = -r2 * Math.log(ts); + + return new L.Point(x, y); + }, + + unproject: function (point) { // (Point, Boolean) -> LatLng + var d = L.LatLng.RAD_TO_DEG, + r = this.R_MAJOR, + r2 = this.R_MINOR, + lng = point.x * d / r, + tmp = r2 / r, + eccent = Math.sqrt(1 - (tmp * tmp)), + ts = Math.exp(- point.y / r2), + phi = (Math.PI / 2) - 2 * Math.atan(ts), + numIter = 15, + tol = 1e-7, + i = numIter, + dphi = 0.1, + con; + + while ((Math.abs(dphi) > tol) && (--i > 0)) { + con = eccent * Math.sin(phi); + dphi = (Math.PI / 2) - 2 * Math.atan(ts * + Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi; + phi += dphi; + } + + return new L.LatLng(phi * d, lng); + } +}; + + + +L.CRS.EPSG3395 = L.extend({}, L.CRS, { + code: 'EPSG:3395', + + projection: L.Projection.Mercator, + + transformation: (function () { + var m = L.Projection.Mercator, + r = m.R_MAJOR, + r2 = m.R_MINOR; + + return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5); + }()) +}); + + +/* + * L.TileLayer is used for standard xyz-numbered tile layers. + */ + +L.TileLayer = L.Class.extend({ + includes: L.Mixin.Events, + + options: { + minZoom: 0, + maxZoom: 18, + tileSize: 256, + subdomains: 'abc', + errorTileUrl: '', + attribution: '', + zoomOffset: 0, + opacity: 1, + /* (undefined works too) + zIndex: null, + tms: false, + continuousWorld: false, + noWrap: false, + zoomReverse: false, + detectRetina: false, + reuseTiles: false, + */ + unloadInvisibleTiles: L.Browser.mobile, + updateWhenIdle: L.Browser.mobile + }, + + initialize: function (url, options) { + options = L.setOptions(this, options); + + // detecting retina displays, adjusting tileSize and zoom levels + if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) { + + options.tileSize = Math.floor(options.tileSize / 2); + options.zoomOffset++; + + if (options.minZoom > 0) { + options.minZoom--; + } + this.options.maxZoom--; + } + + this._url = url; + + var subdomains = this.options.subdomains; + + if (typeof subdomains === 'string') { + this.options.subdomains = subdomains.split(''); + } + }, + + onAdd: function (map) { + this._map = map; + + // create a container div for tiles + this._initContainer(); + + // create an image to clone for tiles + this._createTileProto(); + + // set up events + map.on({ + 'viewreset': this._resetCallback, + 'moveend': this._update + }, this); + + if (!this.options.updateWhenIdle) { + this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this); + map.on('move', this._limitedUpdate, this); + } + + this._reset(); + this._update(); + }, + + addTo: function (map) { + map.addLayer(this); + return this; + }, + + onRemove: function (map) { + this._container.parentNode.removeChild(this._container); + + map.off({ + 'viewreset': this._resetCallback, + 'moveend': this._update + }, this); + + if (!this.options.updateWhenIdle) { + map.off('move', this._limitedUpdate, this); + } + + this._container = null; + this._map = null; + }, + + bringToFront: function () { + var pane = this._map._panes.tilePane; + + if (this._container) { + pane.appendChild(this._container); + this._setAutoZIndex(pane, Math.max); + } + + return this; + }, + + bringToBack: function () { + var pane = this._map._panes.tilePane; + + if (this._container) { + pane.insertBefore(this._container, pane.firstChild); + this._setAutoZIndex(pane, Math.min); + } + + return this; + }, + + getAttribution: function () { + return this.options.attribution; + }, + + setOpacity: function (opacity) { + this.options.opacity = opacity; + + if (this._map) { + this._updateOpacity(); + } + + return this; + }, + + setZIndex: function (zIndex) { + this.options.zIndex = zIndex; + this._updateZIndex(); + + return this; + }, + + setUrl: function (url, noRedraw) { + this._url = url; + + if (!noRedraw) { + this.redraw(); + } + + return this; + }, + + redraw: function () { + if (this._map) { + this._map._panes.tilePane.empty = false; + this._reset(true); + this._update(); + } + return this; + }, + + _updateZIndex: function () { + if (this._container && this.options.zIndex !== undefined) { + this._container.style.zIndex = this.options.zIndex; + } + }, + + _setAutoZIndex: function (pane, compare) { + + var layers = pane.children, + edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min + zIndex, i, len; + + for (i = 0, len = layers.length; i < len; i++) { + + if (layers[i] !== this._container) { + zIndex = parseInt(layers[i].style.zIndex, 10); + + if (!isNaN(zIndex)) { + edgeZIndex = compare(edgeZIndex, zIndex); + } + } + } + + this.options.zIndex = this._container.style.zIndex = + (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1); + }, + + _updateOpacity: function () { + L.DomUtil.setOpacity(this._container, this.options.opacity); + + // stupid webkit hack to force redrawing of tiles + var i, + tiles = this._tiles; + + if (L.Browser.webkit) { + for (i in tiles) { + if (tiles.hasOwnProperty(i)) { + tiles[i].style.webkitTransform += ' translate(0,0)'; + } + } + } + }, + + _initContainer: function () { + var tilePane = this._map._panes.tilePane; + + if (!this._container || tilePane.empty) { + this._container = L.DomUtil.create('div', 'leaflet-layer'); + + this._updateZIndex(); + + tilePane.appendChild(this._container); + + if (this.options.opacity < 1) { + this._updateOpacity(); + } + } + }, + + _resetCallback: function (e) { + this._reset(e.hard); + }, + + _reset: function (clearOldContainer) { + var tiles = this._tiles; + + for (var key in tiles) { + if (tiles.hasOwnProperty(key)) { + this.fire('tileunload', {tile: tiles[key]}); + } + } + + this._tiles = {}; + this._tilesToLoad = 0; + + if (this.options.reuseTiles) { + this._unusedTiles = []; + } + + if (clearOldContainer && this._container) { + this._container.innerHTML = ""; + } + + this._initContainer(); + }, + + _update: function () { + + if (!this._map) { return; } + + var bounds = this._map.getPixelBounds(), + zoom = this._map.getZoom(), + tileSize = this.options.tileSize; + + if (zoom > this.options.maxZoom || zoom < this.options.minZoom) { + return; + } + + var nwTilePoint = new L.Point( + Math.floor(bounds.min.x / tileSize), + Math.floor(bounds.min.y / tileSize)), + + seTilePoint = new L.Point( + Math.floor(bounds.max.x / tileSize), + Math.floor(bounds.max.y / tileSize)), + + tileBounds = new L.Bounds(nwTilePoint, seTilePoint); + + this._addTilesFromCenterOut(tileBounds); + + if (this.options.unloadInvisibleTiles || this.options.reuseTiles) { + this._removeOtherTiles(tileBounds); + } + }, + + _addTilesFromCenterOut: function (bounds) { + var queue = [], + center = bounds.getCenter(); + + var j, i, point; + + for (j = bounds.min.y; j <= bounds.max.y; j++) { + for (i = bounds.min.x; i <= bounds.max.x; i++) { + point = new L.Point(i, j); + + if (this._tileShouldBeLoaded(point)) { + queue.push(point); + } + } + } + + var tilesToLoad = queue.length; + + if (tilesToLoad === 0) { return; } + + // load tiles in order of their distance to center + queue.sort(function (a, b) { + return a.distanceTo(center) - b.distanceTo(center); + }); + + var fragment = document.createDocumentFragment(); + + // if its the first batch of tiles to load + if (!this._tilesToLoad) { + this.fire('loading'); + } + + this._tilesToLoad += tilesToLoad; + + for (i = 0; i < tilesToLoad; i++) { + this._addTile(queue[i], fragment); + } + + this._container.appendChild(fragment); + }, + + _tileShouldBeLoaded: function (tilePoint) { + if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) { + return false; // already loaded + } + + if (!this.options.continuousWorld) { + var limit = this._getWrapTileNum(); + + if (this.options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit) || + tilePoint.y < 0 || tilePoint.y >= limit) { + return false; // exceeds world bounds + } + } + + return true; + }, + + _removeOtherTiles: function (bounds) { + var kArr, x, y, key; + + for (key in this._tiles) { + if (this._tiles.hasOwnProperty(key)) { + kArr = key.split(':'); + x = parseInt(kArr[0], 10); + y = parseInt(kArr[1], 10); + + // remove tile if it's out of bounds + if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) { + this._removeTile(key); + } + } + } + }, + + _removeTile: function (key) { + var tile = this._tiles[key]; + + this.fire("tileunload", {tile: tile, url: tile.src}); + + if (this.options.reuseTiles) { + L.DomUtil.removeClass(tile, 'leaflet-tile-loaded'); + this._unusedTiles.push(tile); + + } else if (tile.parentNode === this._container) { + this._container.removeChild(tile); + } + + // for https://github.com/CloudMade/Leaflet/issues/137 + if (!L.Browser.android) { + tile.src = L.Util.emptyImageUrl; + } + + delete this._tiles[key]; + }, + + _addTile: function (tilePoint, container) { + var tilePos = this._getTilePos(tilePoint); + + // get unused tile - or create a new tile + var tile = this._getTile(); + + /* + Chrome 20 layouts much faster with top/left (verify with timeline, frames) + Android 4 browser has display issues with top/left and requires transform instead + Android 3 browser not tested + Android 2 browser requires top/left or tiles disappear on load or first drag + (reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866 + (other browsers don't currently care) - see debug/hacks/jitter.html for an example + */ + L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome || L.Browser.android23); + + this._tiles[tilePoint.x + ':' + tilePoint.y] = tile; + + this._loadTile(tile, tilePoint); + + if (tile.parentNode !== this._container) { + container.appendChild(tile); + } + }, + + _getZoomForUrl: function () { + + var options = this.options, + zoom = this._map.getZoom(); + + if (options.zoomReverse) { + zoom = options.maxZoom - zoom; + } + + return zoom + options.zoomOffset; + }, + + _getTilePos: function (tilePoint) { + var origin = this._map.getPixelOrigin(), + tileSize = this.options.tileSize; + + return tilePoint.multiplyBy(tileSize).subtract(origin); + }, + + // image-specific code (override to implement e.g. Canvas or SVG tile layer) + + getTileUrl: function (tilePoint) { + this._adjustTilePoint(tilePoint); + + return L.Util.template(this._url, L.extend({ + s: this._getSubdomain(tilePoint), + z: this._getZoomForUrl(), + x: tilePoint.x, + y: tilePoint.y + }, this.options)); + }, + + _getWrapTileNum: function () { + // TODO refactor, limit is not valid for non-standard projections + return Math.pow(2, this._getZoomForUrl()); + }, + + _adjustTilePoint: function (tilePoint) { + + var limit = this._getWrapTileNum(); + + // wrap tile coordinates + if (!this.options.continuousWorld && !this.options.noWrap) { + tilePoint.x = ((tilePoint.x % limit) + limit) % limit; + } + + if (this.options.tms) { + tilePoint.y = limit - tilePoint.y - 1; + } + }, + + _getSubdomain: function (tilePoint) { + var index = (tilePoint.x + tilePoint.y) % this.options.subdomains.length; + return this.options.subdomains[index]; + }, + + _createTileProto: function () { + var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile'); + img.style.width = img.style.height = this.options.tileSize + 'px'; + img.galleryimg = 'no'; + }, + + _getTile: function () { + if (this.options.reuseTiles && this._unusedTiles.length > 0) { + var tile = this._unusedTiles.pop(); + this._resetTile(tile); + return tile; + } + return this._createTile(); + }, + + // Override if data stored on a tile needs to be cleaned up before reuse + _resetTile: function (/*tile*/) {}, + + _createTile: function () { + var tile = this._tileImg.cloneNode(false); + tile.onselectstart = tile.onmousemove = L.Util.falseFn; + return tile; + }, + + _loadTile: function (tile, tilePoint) { + tile._layer = this; + tile.onload = this._tileOnLoad; + tile.onerror = this._tileOnError; + + tile.src = this.getTileUrl(tilePoint); + }, + + _tileLoaded: function () { + this._tilesToLoad--; + if (!this._tilesToLoad) { + this.fire('load'); + } + }, + + _tileOnLoad: function () { + var layer = this._layer; + + //Only if we are loading an actual image + if (this.src !== L.Util.emptyImageUrl) { + L.DomUtil.addClass(this, 'leaflet-tile-loaded'); + + layer.fire('tileload', { + tile: this, + url: this.src + }); + } + + layer._tileLoaded(); + }, + + _tileOnError: function () { + var layer = this._layer; + + layer.fire('tileerror', { + tile: this, + url: this.src + }); + + var newUrl = layer.options.errorTileUrl; + if (newUrl) { + this.src = newUrl; + } + + layer._tileLoaded(); + } +}); + +L.tileLayer = function (url, options) { + return new L.TileLayer(url, options); +}; + + +/* + * L.TileLayer.WMS is used for putting WMS tile layers on the map. + */ + +L.TileLayer.WMS = L.TileLayer.extend({ + + defaultWmsParams: { + service: 'WMS', + request: 'GetMap', + version: '1.1.1', + layers: '', + styles: '', + format: 'image/jpeg', + transparent: false + }, + + initialize: function (url, options) { // (String, Object) + + this._url = url; + + var wmsParams = L.extend({}, this.defaultWmsParams); + + if (options.detectRetina && L.Browser.retina) { + wmsParams.width = wmsParams.height = this.options.tileSize * 2; + } else { + wmsParams.width = wmsParams.height = this.options.tileSize; + } + + for (var i in options) { + // all keys that are not TileLayer options go to WMS params + if (!this.options.hasOwnProperty(i)) { + wmsParams[i] = options[i]; + } + } + + this.wmsParams = wmsParams; + + L.setOptions(this, options); + }, + + onAdd: function (map) { + + var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs'; + this.wmsParams[projectionKey] = map.options.crs.code; + + L.TileLayer.prototype.onAdd.call(this, map); + }, + + getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String + + this._adjustTilePoint(tilePoint); + + var map = this._map, + crs = map.options.crs, + tileSize = this.options.tileSize, + + nwPoint = tilePoint.multiplyBy(tileSize), + sePoint = nwPoint.add(new L.Point(tileSize, tileSize)), + + nw = crs.project(map.unproject(nwPoint, zoom)), + se = crs.project(map.unproject(sePoint, zoom)), + + bbox = [nw.x, se.y, se.x, nw.y].join(','), + + url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)}); + + return url + L.Util.getParamString(this.wmsParams, url) + "&bbox=" + bbox; + }, + + setParams: function (params, noRedraw) { + + L.extend(this.wmsParams, params); + + if (!noRedraw) { + this.redraw(); + } + + return this; + } +}); + +L.tileLayer.wms = function (url, options) { + return new L.TileLayer.WMS(url, options); +}; + + +/* + * L.TileLayer.Canvas is a class that you can use as a base for creating + * dynamically drawn Canvas-based tile layers. + */ + +L.TileLayer.Canvas = L.TileLayer.extend({ + options: { + async: false + }, + + initialize: function (options) { + L.setOptions(this, options); + }, + + redraw: function () { + var tiles = this._tiles; + + for (var i in tiles) { + if (tiles.hasOwnProperty(i)) { + this._redrawTile(tiles[i]); + } + } + }, + + _redrawTile: function (tile) { + this.drawTile(tile, tile._tilePoint, this._map._zoom); + }, + + _createTileProto: function () { + var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile'); + proto.width = proto.height = this.options.tileSize; + }, + + _createTile: function () { + var tile = this._canvasProto.cloneNode(false); + tile.onselectstart = tile.onmousemove = L.Util.falseFn; + return tile; + }, + + _loadTile: function (tile, tilePoint) { + tile._layer = this; + tile._tilePoint = tilePoint; + + this._redrawTile(tile); + + if (!this.options.async) { + this.tileDrawn(tile); + } + }, + + drawTile: function (/*tile, tilePoint*/) { + // override with rendering code + }, + + tileDrawn: function (tile) { + this._tileOnLoad.call(tile); + } +}); + + +L.tileLayer.canvas = function (options) { + return new L.TileLayer.Canvas(options); +}; + + +/* + * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds). + */ + +L.ImageOverlay = L.Class.extend({ + includes: L.Mixin.Events, + + options: { + opacity: 1 + }, + + initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) + this._url = url; + this._bounds = L.latLngBounds(bounds); + + L.setOptions(this, options); + }, + + onAdd: function (map) { + this._map = map; + + if (!this._image) { + this._initImage(); + } + + map._panes.overlayPane.appendChild(this._image); + + map.on('viewreset', this._reset, this); + + if (map.options.zoomAnimation && L.Browser.any3d) { + map.on('zoomanim', this._animateZoom, this); + } + + this._reset(); + }, + + onRemove: function (map) { + map.getPanes().overlayPane.removeChild(this._image); + + map.off('viewreset', this._reset, this); + + if (map.options.zoomAnimation) { + map.off('zoomanim', this._animateZoom, this); + } + }, + + addTo: function (map) { + map.addLayer(this); + return this; + }, + + setOpacity: function (opacity) { + this.options.opacity = opacity; + this._updateOpacity(); + return this; + }, + + // TODO remove bringToFront/bringToBack duplication from TileLayer/Path + bringToFront: function () { + if (this._image) { + this._map._panes.overlayPane.appendChild(this._image); + } + return this; + }, + + bringToBack: function () { + var pane = this._map._panes.overlayPane; + if (this._image) { + pane.insertBefore(this._image, pane.firstChild); + } + return this; + }, + + _initImage: function () { + this._image = L.DomUtil.create('img', 'leaflet-image-layer'); + + if (this._map.options.zoomAnimation && L.Browser.any3d) { + L.DomUtil.addClass(this._image, 'leaflet-zoom-animated'); + } else { + L.DomUtil.addClass(this._image, 'leaflet-zoom-hide'); + } + + this._updateOpacity(); + + //TODO createImage util method to remove duplication + L.extend(this._image, { + galleryimg: 'no', + onselectstart: L.Util.falseFn, + onmousemove: L.Util.falseFn, + onload: L.bind(this._onImageLoad, this), + src: this._url + }); + }, + + _animateZoom: function (e) { + var map = this._map, + image = this._image, + scale = map.getZoomScale(e.zoom), + nw = this._bounds.getNorthWest(), + se = this._bounds.getSouthEast(), + + topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center), + size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft), + origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale))); + + image.style[L.DomUtil.TRANSFORM] = + L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') '; + }, + + _reset: function () { + var image = this._image, + topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()), + size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft); + + L.DomUtil.setPosition(image, topLeft); + + image.style.width = size.x + 'px'; + image.style.height = size.y + 'px'; + }, + + _onImageLoad: function () { + this.fire('load'); + }, + + _updateOpacity: function () { + L.DomUtil.setOpacity(this._image, this.options.opacity); + } +}); + +L.imageOverlay = function (url, bounds, options) { + return new L.ImageOverlay(url, bounds, options); +}; + + +/* + * L.Icon is an image-based icon class that you can use with L.Marker for custom markers. + */ + +L.Icon = L.Class.extend({ + options: { + /* + iconUrl: (String) (required) + iconRetinaUrl: (String) (optional, used for retina devices if detected) + iconSize: (Point) (can be set through CSS) + iconAnchor: (Point) (centered by default, can be set in CSS with negative margins) + popupAnchor: (Point) (if not specified, popup opens in the anchor point) + shadowUrl: (Point) (no shadow by default) + shadowRetinaUrl: (String) (optional, used for retina devices if detected) + shadowSize: (Point) + shadowAnchor: (Point) + */ + className: '' + }, + + initialize: function (options) { + L.setOptions(this, options); + }, + + createIcon: function () { + return this._createIcon('icon'); + }, + + createShadow: function () { + return this._createIcon('shadow'); + }, + + _createIcon: function (name) { + var src = this._getIconUrl(name); + + if (!src) { + if (name === 'icon') { + throw new Error("iconUrl not set in Icon options (see the docs)."); + } + return null; + } + + var img = this._createImg(src); + this._setIconStyles(img, name); + + return img; + }, + + _setIconStyles: function (img, name) { + var options = this.options, + size = L.point(options[name + 'Size']), + anchor; + + if (name === 'shadow') { + anchor = L.point(options.shadowAnchor || options.iconAnchor); + } else { + anchor = L.point(options.iconAnchor); + } + + if (!anchor && size) { + anchor = size.divideBy(2, true); + } + + img.className = 'leaflet-marker-' + name + ' ' + options.className; + + if (anchor) { + img.style.marginLeft = (-anchor.x) + 'px'; + img.style.marginTop = (-anchor.y) + 'px'; + } + + if (size) { + img.style.width = size.x + 'px'; + img.style.height = size.y + 'px'; + } + }, + + _createImg: function (src) { + var el; + + if (!L.Browser.ie6) { + el = document.createElement('img'); + el.src = src; + } else { + el = document.createElement('div'); + el.style.filter = + 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")'; + } + return el; + }, + + _getIconUrl: function (name) { + if (L.Browser.retina && this.options[name + 'RetinaUrl']) { + return this.options[name + 'RetinaUrl']; + } + return this.options[name + 'Url']; + } +}); + +L.icon = function (options) { + return new L.Icon(options); +}; + + +/* + * L.Icon.Default is the blue marker icon used by default in Leaflet. + */ + +L.Icon.Default = L.Icon.extend({ + + options: { + iconSize: new L.Point(25, 41), + iconAnchor: new L.Point(12, 41), + popupAnchor: new L.Point(1, -34), + + shadowSize: new L.Point(41, 41) + }, + + _getIconUrl: function (name) { + var key = name + 'Url'; + + if (this.options[key]) { + return this.options[key]; + } + + if (L.Browser.retina && name === 'icon') { + name += '@2x'; + } + + var path = L.Icon.Default.imagePath; + + if (!path) { + throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually."); + } + + return path + '/marker-' + name + '.png'; + } +}); + +L.Icon.Default.imagePath = (function () { + var scripts = document.getElementsByTagName('script'), + leafletRe = /\/?leaflet[\-\._]?([\w\-\._]*)\.js\??/; + + var i, len, src, matches; + + for (i = 0, len = scripts.length; i < len; i++) { + src = scripts[i].src; + matches = src.match(leafletRe); + + if (matches) { + return src.split(leafletRe)[0] + '/images'; + } + } +}()); + + +/* + * L.Marker is used to display clickable/draggable icons on the map. + */ + +L.Marker = L.Class.extend({ + + includes: L.Mixin.Events, + + options: { + icon: new L.Icon.Default(), + title: '', + clickable: true, + draggable: false, + zIndexOffset: 0, + opacity: 1, + riseOnHover: false, + riseOffset: 250 + }, + + initialize: function (latlng, options) { + L.setOptions(this, options); + this._latlng = L.latLng(latlng); + }, + + onAdd: function (map) { + this._map = map; + + map.on('viewreset', this.update, this); + + this._initIcon(); + this.update(); + + if (map.options.zoomAnimation && map.options.markerZoomAnimation) { + map.on('zoomanim', this._animateZoom, this); + } + }, + + addTo: function (map) { + map.addLayer(this); + return this; + }, + + onRemove: function (map) { + this._removeIcon(); + + this.fire('remove'); + + map.off({ + 'viewreset': this.update, + 'zoomanim': this._animateZoom + }, this); + + this._map = null; + }, + + getLatLng: function () { + return this._latlng; + }, + + setLatLng: function (latlng) { + this._latlng = L.latLng(latlng); + + this.update(); + + return this.fire('move', { latlng: this._latlng }); + }, + + setZIndexOffset: function (offset) { + this.options.zIndexOffset = offset; + this.update(); + + return this; + }, + + setIcon: function (icon) { + if (this._map) { + this._removeIcon(); + } + + this.options.icon = icon; + + if (this._map) { + this._initIcon(); + this.update(); + } + + return this; + }, + + update: function () { + if (this._icon) { + var pos = this._map.latLngToLayerPoint(this._latlng).round(); + this._setPos(pos); + } + + return this; + }, + + _initIcon: function () { + var options = this.options, + map = this._map, + animation = (map.options.zoomAnimation && map.options.markerZoomAnimation), + classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide', + needOpacityUpdate = false; + + if (!this._icon) { + this._icon = options.icon.createIcon(); + + if (options.title) { + this._icon.title = options.title; + } + + this._initInteraction(); + needOpacityUpdate = (this.options.opacity < 1); + + L.DomUtil.addClass(this._icon, classToAdd); + + if (options.riseOnHover) { + L.DomEvent + .on(this._icon, 'mouseover', this._bringToFront, this) + .on(this._icon, 'mouseout', this._resetZIndex, this); + } + } + + if (!this._shadow) { + this._shadow = options.icon.createShadow(); + + if (this._shadow) { + L.DomUtil.addClass(this._shadow, classToAdd); + needOpacityUpdate = (this.options.opacity < 1); + } + } + + if (needOpacityUpdate) { + this._updateOpacity(); + } + + var panes = this._map._panes; + + panes.markerPane.appendChild(this._icon); + + if (this._shadow) { + panes.shadowPane.appendChild(this._shadow); + } + }, + + _removeIcon: function () { + var panes = this._map._panes; + + if (this.options.riseOnHover) { + L.DomEvent + .off(this._icon, 'mouseover', this._bringToFront) + .off(this._icon, 'mouseout', this._resetZIndex); + } + + panes.markerPane.removeChild(this._icon); + + if (this._shadow) { + panes.shadowPane.removeChild(this._shadow); + } + + this._icon = this._shadow = null; + }, + + _setPos: function (pos) { + L.DomUtil.setPosition(this._icon, pos); + + if (this._shadow) { + L.DomUtil.setPosition(this._shadow, pos); + } + + this._zIndex = pos.y + this.options.zIndexOffset; + + this._resetZIndex(); + }, + + _updateZIndex: function (offset) { + this._icon.style.zIndex = this._zIndex + offset; + }, + + _animateZoom: function (opt) { + var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center); + + this._setPos(pos); + }, + + _initInteraction: function () { + + if (!this.options.clickable) { return; } + + // TODO refactor into something shared with Map/Path/etc. to DRY it up + + var icon = this._icon, + events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu']; + + L.DomUtil.addClass(icon, 'leaflet-clickable'); + L.DomEvent.on(icon, 'click', this._onMouseClick, this); + + for (var i = 0; i < events.length; i++) { + L.DomEvent.on(icon, events[i], this._fireMouseEvent, this); + } + + if (L.Handler.MarkerDrag) { + this.dragging = new L.Handler.MarkerDrag(this); + + if (this.options.draggable) { + this.dragging.enable(); + } + } + }, + + _onMouseClick: function (e) { + var wasDragged = this.dragging && this.dragging.moved(); + + if (this.hasEventListeners(e.type) || wasDragged) { + L.DomEvent.stopPropagation(e); + } + + if (wasDragged) { return; } + + if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; } + + this.fire(e.type, { + originalEvent: e + }); + }, + + _fireMouseEvent: function (e) { + + this.fire(e.type, { + originalEvent: e + }); + + // TODO proper custom event propagation + // this line will always be called if marker is in a FeatureGroup + if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) { + L.DomEvent.preventDefault(e); + } + if (e.type !== 'mousedown') { + L.DomEvent.stopPropagation(e); + } + }, + + setOpacity: function (opacity) { + this.options.opacity = opacity; + if (this._map) { + this._updateOpacity(); + } + }, + + _updateOpacity: function () { + L.DomUtil.setOpacity(this._icon, this.options.opacity); + if (this._shadow) { + L.DomUtil.setOpacity(this._shadow, this.options.opacity); + } + }, + + _bringToFront: function () { + this._updateZIndex(this.options.riseOffset); + }, + + _resetZIndex: function () { + this._updateZIndex(0); + } +}); + +L.marker = function (latlng, options) { + return new L.Marker(latlng, options); +}; + + +/* + * L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon) + * to use with L.Marker. + */ + +L.DivIcon = L.Icon.extend({ + options: { + iconSize: new L.Point(12, 12), // also can be set through CSS + /* + iconAnchor: (Point) + popupAnchor: (Point) + html: (String) + bgPos: (Point) + */ + className: 'leaflet-div-icon' + }, + + createIcon: function () { + var div = document.createElement('div'), + options = this.options; + + if (options.html) { + div.innerHTML = options.html; + } + + if (options.bgPos) { + div.style.backgroundPosition = + (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px'; + } + + this._setIconStyles(div, 'icon'); + return div; + }, + + createShadow: function () { + return null; + } +}); + +L.divIcon = function (options) { + return new L.DivIcon(options); +}; + + +/* + * L.Popup is used for displaying popups on the map. + */ + +L.Map.mergeOptions({ + closePopupOnClick: true +}); + +L.Popup = L.Class.extend({ + includes: L.Mixin.Events, + + options: { + minWidth: 50, + maxWidth: 300, + maxHeight: null, + autoPan: true, + closeButton: true, + offset: new L.Point(0, 6), + autoPanPadding: new L.Point(5, 5), + className: '', + zoomAnimation: true + }, + + initialize: function (options, source) { + L.setOptions(this, options); + + this._source = source; + this._animated = L.Browser.any3d && this.options.zoomAnimation; + }, + + onAdd: function (map) { + this._map = map; + + if (!this._container) { + this._initLayout(); + } + this._updateContent(); + + var animFade = map.options.fadeAnimation; + + if (animFade) { + L.DomUtil.setOpacity(this._container, 0); + } + map._panes.popupPane.appendChild(this._container); + + map.on('viewreset', this._updatePosition, this); + + if (this._animated) { + map.on('zoomanim', this._zoomAnimation, this); + } + + if (map.options.closePopupOnClick) { + map.on('preclick', this._close, this); + } + + this._update(); + + if (animFade) { + L.DomUtil.setOpacity(this._container, 1); + } + }, + + addTo: function (map) { + map.addLayer(this); + return this; + }, + + openOn: function (map) { + map.openPopup(this); + return this; + }, + + onRemove: function (map) { + map._panes.popupPane.removeChild(this._container); + + L.Util.falseFn(this._container.offsetWidth); // force reflow + + map.off({ + viewreset: this._updatePosition, + preclick: this._close, + zoomanim: this._zoomAnimation + }, this); + + if (map.options.fadeAnimation) { + L.DomUtil.setOpacity(this._container, 0); + } + + this._map = null; + }, + + setLatLng: function (latlng) { + this._latlng = L.latLng(latlng); + this._update(); + return this; + }, + + setContent: function (content) { + this._content = content; + this._update(); + return this; + }, + + _close: function () { + var map = this._map; + + if (map) { + map._popup = null; + + map + .removeLayer(this) + .fire('popupclose', {popup: this}); + } + }, + + _initLayout: function () { + var prefix = 'leaflet-popup', + containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' + + (this._animated ? 'animated' : 'hide'), + container = this._container = L.DomUtil.create('div', containerClass), + closeButton; + + if (this.options.closeButton) { + closeButton = this._closeButton = + L.DomUtil.create('a', prefix + '-close-button', container); + closeButton.href = '#close'; + closeButton.innerHTML = '×'; + + L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this); + } + + var wrapper = this._wrapper = + L.DomUtil.create('div', prefix + '-content-wrapper', container); + L.DomEvent.disableClickPropagation(wrapper); + + this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper); + L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation); + + this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container); + this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer); + }, + + _update: function () { + if (!this._map) { return; } + + this._container.style.visibility = 'hidden'; + + this._updateContent(); + this._updateLayout(); + this._updatePosition(); + + this._container.style.visibility = ''; + + this._adjustPan(); + }, + + _updateContent: function () { + if (!this._content) { return; } + + if (typeof this._content === 'string') { + this._contentNode.innerHTML = this._content; + } else { + while (this._contentNode.hasChildNodes()) { + this._contentNode.removeChild(this._contentNode.firstChild); + } + this._contentNode.appendChild(this._content); + } + this.fire('contentupdate'); + }, + + _updateLayout: function () { + var container = this._contentNode, + style = container.style; + + style.width = ''; + style.whiteSpace = 'nowrap'; + + var width = container.offsetWidth; + width = Math.min(width, this.options.maxWidth); + width = Math.max(width, this.options.minWidth); + + style.width = (width + 1) + 'px'; + style.whiteSpace = ''; + + style.height = ''; + + var height = container.offsetHeight, + maxHeight = this.options.maxHeight, + scrolledClass = 'leaflet-popup-scrolled'; + + if (maxHeight && height > maxHeight) { + style.height = maxHeight + 'px'; + L.DomUtil.addClass(container, scrolledClass); + } else { + L.DomUtil.removeClass(container, scrolledClass); + } + + this._containerWidth = this._container.offsetWidth; + }, + + _updatePosition: function () { + if (!this._map) { return; } + + var pos = this._map.latLngToLayerPoint(this._latlng), + animated = this._animated, + offset = this.options.offset; + + if (animated) { + L.DomUtil.setPosition(this._container, pos); + } + + this._containerBottom = -offset.y - (animated ? 0 : pos.y); + this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x); + + //Bottom position the popup in case the height of the popup changes (images loading etc) + this._container.style.bottom = this._containerBottom + 'px'; + this._container.style.left = this._containerLeft + 'px'; + }, + + _zoomAnimation: function (opt) { + var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center); + + L.DomUtil.setPosition(this._container, pos); + }, + + _adjustPan: function () { + if (!this.options.autoPan) { return; } + + var map = this._map, + containerHeight = this._container.offsetHeight, + containerWidth = this._containerWidth, + + layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom); + + if (this._animated) { + layerPos._add(L.DomUtil.getPosition(this._container)); + } + + var containerPos = map.layerPointToContainerPoint(layerPos), + padding = this.options.autoPanPadding, + size = map.getSize(), + dx = 0, + dy = 0; + + if (containerPos.x < 0) { + dx = containerPos.x - padding.x; + } + if (containerPos.x + containerWidth > size.x) { + dx = containerPos.x + containerWidth - size.x + padding.x; + } + if (containerPos.y < 0) { + dy = containerPos.y - padding.y; + } + if (containerPos.y + containerHeight > size.y) { + dy = containerPos.y + containerHeight - size.y + padding.y; + } + + if (dx || dy) { + map.panBy(new L.Point(dx, dy)); + } + }, + + _onCloseButtonClick: function (e) { + this._close(); + L.DomEvent.stop(e); + } +}); + +L.popup = function (options, source) { + return new L.Popup(options, source); +}; + + +/* + * Popup extension to L.Marker, adding popup-related methods. + */ + +L.Marker.include({ + openPopup: function () { + if (this._popup && this._map) { + this._popup.setLatLng(this._latlng); + this._map.openPopup(this._popup); + } + + return this; + }, + + closePopup: function () { + if (this._popup) { + this._popup._close(); + } + return this; + }, + + bindPopup: function (content, options) { + var anchor = L.point(this.options.icon.options.popupAnchor) || new L.Point(0, 0); + + anchor = anchor.add(L.Popup.prototype.options.offset); + + if (options && options.offset) { + anchor = anchor.add(options.offset); + } + + options = L.extend({offset: anchor}, options); + + if (!this._popup) { + this + .on('click', this.openPopup, this) + .on('remove', this.closePopup, this) + .on('move', this._movePopup, this); + } + + this._popup = new L.Popup(options, this) + .setContent(content); + + return this; + }, + + unbindPopup: function () { + if (this._popup) { + this._popup = null; + this + .off('click', this.openPopup) + .off('remove', this.closePopup) + .off('move', this._movePopup); + } + return this; + }, + + _movePopup: function (e) { + this._popup.setLatLng(e.latlng); + } +}); + + +/* + * Adds popup-related methods to L.Map. + */ + +L.Map.include({ + openPopup: function (popup) { + this.closePopup(); + + this._popup = popup; + + return this + .addLayer(popup) + .fire('popupopen', {popup: this._popup}); + }, + + closePopup: function () { + if (this._popup) { + this._popup._close(); + } + return this; + } +}); + + +/* + * L.LayerGroup is a class to combine several layers into one so that + * you can manipulate the group (e.g. add/remove it) as one layer. + */ + +L.LayerGroup = L.Class.extend({ + initialize: function (layers) { + this._layers = {}; + + var i, len; + + if (layers) { + for (i = 0, len = layers.length; i < len; i++) { + this.addLayer(layers[i]); + } + } + }, + + addLayer: function (layer) { + var id = L.stamp(layer); + + this._layers[id] = layer; + + if (this._map) { + this._map.addLayer(layer); + } + + return this; + }, + + removeLayer: function (layer) { + var id = L.stamp(layer); + + delete this._layers[id]; + + if (this._map) { + this._map.removeLayer(layer); + } + + return this; + }, + + clearLayers: function () { + this.eachLayer(this.removeLayer, this); + return this; + }, + + invoke: function (methodName) { + var args = Array.prototype.slice.call(arguments, 1), + i, layer; + + for (i in this._layers) { + if (this._layers.hasOwnProperty(i)) { + layer = this._layers[i]; + + if (layer[methodName]) { + layer[methodName].apply(layer, args); + } + } + } + + return this; + }, + + onAdd: function (map) { + this._map = map; + this.eachLayer(map.addLayer, map); + }, + + onRemove: function (map) { + this.eachLayer(map.removeLayer, map); + this._map = null; + }, + + addTo: function (map) { + map.addLayer(this); + return this; + }, + + eachLayer: function (method, context) { + for (var i in this._layers) { + if (this._layers.hasOwnProperty(i)) { + method.call(context, this._layers[i]); + } + } + }, + + setZIndex: function (zIndex) { + return this.invoke('setZIndex', zIndex); + } +}); + +L.layerGroup = function (layers) { + return new L.LayerGroup(layers); +}; + + +/* + * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods + * shared between a group of interactive layers (like vectors or markers). + */ + +L.FeatureGroup = L.LayerGroup.extend({ + includes: L.Mixin.Events, + + statics: { + EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu' + }, + + addLayer: function (layer) { + if (this._layers[L.stamp(layer)]) { + return this; + } + + layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this); + + L.LayerGroup.prototype.addLayer.call(this, layer); + + if (this._popupContent && layer.bindPopup) { + layer.bindPopup(this._popupContent, this._popupOptions); + } + + return this.fire('layeradd', {layer: layer}); + }, + + removeLayer: function (layer) { + layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this); + + L.LayerGroup.prototype.removeLayer.call(this, layer); + + + if (this._popupContent) { + this.invoke('unbindPopup'); + } + + return this.fire('layerremove', {layer: layer}); + }, + + bindPopup: function (content, options) { + this._popupContent = content; + this._popupOptions = options; + return this.invoke('bindPopup', content, options); + }, + + setStyle: function (style) { + return this.invoke('setStyle', style); + }, + + bringToFront: function () { + return this.invoke('bringToFront'); + }, + + bringToBack: function () { + return this.invoke('bringToBack'); + }, + + getBounds: function () { + var bounds = new L.LatLngBounds(); + + this.eachLayer(function (layer) { + bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds()); + }); + + return bounds; + }, + + _propagateEvent: function (e) { + e.layer = e.target; + e.target = this; + + this.fire(e.type, e); + } +}); + +L.featureGroup = function (layers) { + return new L.FeatureGroup(layers); +}; + + +/* + * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc. + */ + +L.Path = L.Class.extend({ + includes: [L.Mixin.Events], + + statics: { + // how much to extend the clip area around the map view + // (relative to its size, e.g. 0.5 is half the screen in each direction) + // set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is) + CLIP_PADDING: L.Browser.mobile ? + Math.max(0, Math.min(0.5, + (1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2)) : 0.5 + }, + + options: { + stroke: true, + color: '#0033ff', + dashArray: null, + weight: 5, + opacity: 0.5, + + fill: false, + fillColor: null, //same as color by default + fillOpacity: 0.2, + + clickable: true + }, + + initialize: function (options) { + L.setOptions(this, options); + }, + + onAdd: function (map) { + this._map = map; + + if (!this._container) { + this._initElements(); + this._initEvents(); + } + + this.projectLatlngs(); + this._updatePath(); + + if (this._container) { + this._map._pathRoot.appendChild(this._container); + } + + this.fire('add'); + + map.on({ + 'viewreset': this.projectLatlngs, + 'moveend': this._updatePath + }, this); + }, + + addTo: function (map) { + map.addLayer(this); + return this; + }, + + onRemove: function (map) { + map._pathRoot.removeChild(this._container); + + // Need to fire remove event before we set _map to null as the event hooks might need the object + this.fire('remove'); + this._map = null; + + if (L.Browser.vml) { + this._container = null; + this._stroke = null; + this._fill = null; + } + + map.off({ + 'viewreset': this.projectLatlngs, + 'moveend': this._updatePath + }, this); + }, + + projectLatlngs: function () { + // do all projection stuff here + }, + + setStyle: function (style) { + L.setOptions(this, style); + + if (this._container) { + this._updateStyle(); + } + + return this; + }, + + redraw: function () { + if (this._map) { + this.projectLatlngs(); + this._updatePath(); + } + return this; + } +}); + +L.Map.include({ + _updatePathViewport: function () { + var p = L.Path.CLIP_PADDING, + size = this.getSize(), + panePos = L.DomUtil.getPosition(this._mapPane), + min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()), + max = min.add(size.multiplyBy(1 + p * 2)._round()); + + this._pathViewport = new L.Bounds(min, max); + } +}); + + +/* + * Extends L.Path with SVG-specific rendering code. + */ + +L.Path.SVG_NS = 'http://www.w3.org/2000/svg'; + +L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect); + +L.Path = L.Path.extend({ + statics: { + SVG: L.Browser.svg + }, + + bringToFront: function () { + var root = this._map._pathRoot, + path = this._container; + + if (path && root.lastChild !== path) { + root.appendChild(path); + } + return this; + }, + + bringToBack: function () { + var root = this._map._pathRoot, + path = this._container, + first = root.firstChild; + + if (path && first !== path) { + root.insertBefore(path, first); + } + return this; + }, + + getPathString: function () { + // form path string here + }, + + _createElement: function (name) { + return document.createElementNS(L.Path.SVG_NS, name); + }, + + _initElements: function () { + this._map._initPathRoot(); + this._initPath(); + this._initStyle(); + }, + + _initPath: function () { + this._container = this._createElement('g'); + + this._path = this._createElement('path'); + this._container.appendChild(this._path); + }, + + _initStyle: function () { + if (this.options.stroke) { + this._path.setAttribute('stroke-linejoin', 'round'); + this._path.setAttribute('stroke-linecap', 'round'); + } + if (this.options.fill) { + this._path.setAttribute('fill-rule', 'evenodd'); + } + this._updateStyle(); + }, + + _updateStyle: function () { + if (this.options.stroke) { + this._path.setAttribute('stroke', this.options.color); + this._path.setAttribute('stroke-opacity', this.options.opacity); + this._path.setAttribute('stroke-width', this.options.weight); + if (this.options.dashArray) { + this._path.setAttribute('stroke-dasharray', this.options.dashArray); + } else { + this._path.removeAttribute('stroke-dasharray'); + } + } else { + this._path.setAttribute('stroke', 'none'); + } + if (this.options.fill) { + this._path.setAttribute('fill', this.options.fillColor || this.options.color); + this._path.setAttribute('fill-opacity', this.options.fillOpacity); + } else { + this._path.setAttribute('fill', 'none'); + } + }, + + _updatePath: function () { + var str = this.getPathString(); + if (!str) { + // fix webkit empty string parsing bug + str = 'M0 0'; + } + this._path.setAttribute('d', str); + }, + + // TODO remove duplication with L.Map + _initEvents: function () { + if (this.options.clickable) { + if (L.Browser.svg || !L.Browser.vml) { + this._path.setAttribute('class', 'leaflet-clickable'); + } + + L.DomEvent.on(this._container, 'click', this._onMouseClick, this); + + var events = ['dblclick', 'mousedown', 'mouseover', + 'mouseout', 'mousemove', 'contextmenu']; + for (var i = 0; i < events.length; i++) { + L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this); + } + } + }, + + _onMouseClick: function (e) { + if (this._map.dragging && this._map.dragging.moved()) { return; } + + this._fireMouseEvent(e); + }, + + _fireMouseEvent: function (e) { + if (!this.hasEventListeners(e.type)) { return; } + + var map = this._map, + containerPoint = map.mouseEventToContainerPoint(e), + layerPoint = map.containerPointToLayerPoint(containerPoint), + latlng = map.layerPointToLatLng(layerPoint); + + this.fire(e.type, { + latlng: latlng, + layerPoint: layerPoint, + containerPoint: containerPoint, + originalEvent: e + }); + + if (e.type === 'contextmenu') { + L.DomEvent.preventDefault(e); + } + if (e.type !== 'mousemove') { + L.DomEvent.stopPropagation(e); + } + } +}); + +L.Map.include({ + _initPathRoot: function () { + if (!this._pathRoot) { + this._pathRoot = L.Path.prototype._createElement('svg'); + this._panes.overlayPane.appendChild(this._pathRoot); + + if (this.options.zoomAnimation && L.Browser.any3d) { + this._pathRoot.setAttribute('class', ' leaflet-zoom-animated'); + + this.on({ + 'zoomanim': this._animatePathZoom, + 'zoomend': this._endPathZoom + }); + } else { + this._pathRoot.setAttribute('class', ' leaflet-zoom-hide'); + } + + this.on('moveend', this._updateSvgViewport); + this._updateSvgViewport(); + } + }, + + _animatePathZoom: function (e) { + var scale = this.getZoomScale(e.zoom), + offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min); + + this._pathRoot.style[L.DomUtil.TRANSFORM] = + L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') '; + + this._pathZooming = true; + }, + + _endPathZoom: function () { + this._pathZooming = false; + }, + + _updateSvgViewport: function () { + + if (this._pathZooming) { + // Do not update SVGs while a zoom animation is going on otherwise the animation will break. + // When the zoom animation ends we will be updated again anyway + // This fixes the case where you do a momentum move and zoom while the move is still ongoing. + return; + } + + this._updatePathViewport(); + + var vp = this._pathViewport, + min = vp.min, + max = vp.max, + width = max.x - min.x, + height = max.y - min.y, + root = this._pathRoot, + pane = this._panes.overlayPane; + + // Hack to make flicker on drag end on mobile webkit less irritating + if (L.Browser.mobileWebkit) { + pane.removeChild(root); + } + + L.DomUtil.setPosition(root, min); + root.setAttribute('width', width); + root.setAttribute('height', height); + root.setAttribute('viewBox', [min.x, min.y, width, height].join(' ')); + + if (L.Browser.mobileWebkit) { + pane.appendChild(root); + } + } +}); + + +/* + * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods. + */ + +L.Path.include({ + + bindPopup: function (content, options) { + + if (!this._popup || options) { + this._popup = new L.Popup(options, this); + } + + this._popup.setContent(content); + + if (!this._popupHandlersAdded) { + this + .on('click', this._openPopup, this) + .on('remove', this.closePopup, this); + + this._popupHandlersAdded = true; + } + + return this; + }, + + unbindPopup: function () { + if (this._popup) { + this._popup = null; + this + .off('click', this._openPopup) + .off('remove', this.closePopup); + + this._popupHandlersAdded = false; + } + return this; + }, + + openPopup: function (latlng) { + + if (this._popup) { + // open the popup from one of the path's points if not specified + latlng = latlng || this._latlng || + this._latlngs[Math.floor(this._latlngs.length / 2)]; + + this._openPopup({latlng: latlng}); + } + + return this; + }, + + closePopup: function () { + if (this._popup) { + this._popup._close(); + } + return this; + }, + + _openPopup: function (e) { + this._popup.setLatLng(e.latlng); + this._map.openPopup(this._popup); + } +}); + + +/* + * Vector rendering for IE6-8 through VML. + * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! + */ + +L.Browser.vml = !L.Browser.svg && (function () { + try { + var div = document.createElement('div'); + div.innerHTML = ''; + + var shape = div.firstChild; + shape.style.behavior = 'url(#default#VML)'; + + return shape && (typeof shape.adj === 'object'); + + } catch (e) { + return false; + } +}()); + +L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({ + statics: { + VML: true, + CLIP_PADDING: 0.02 + }, + + _createElement: (function () { + try { + document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); + return function (name) { + return document.createElement(''); + }; + } catch (e) { + return function (name) { + return document.createElement( + '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); + }; + } + }()), + + _initPath: function () { + var container = this._container = this._createElement('shape'); + L.DomUtil.addClass(container, 'leaflet-vml-shape'); + if (this.options.clickable) { + L.DomUtil.addClass(container, 'leaflet-clickable'); + } + container.coordsize = '1 1'; + + this._path = this._createElement('path'); + container.appendChild(this._path); + + this._map._pathRoot.appendChild(container); + }, + + _initStyle: function () { + this._updateStyle(); + }, + + _updateStyle: function () { + var stroke = this._stroke, + fill = this._fill, + options = this.options, + container = this._container; + + container.stroked = options.stroke; + container.filled = options.fill; + + if (options.stroke) { + if (!stroke) { + stroke = this._stroke = this._createElement('stroke'); + stroke.endcap = 'round'; + container.appendChild(stroke); + } + stroke.weight = options.weight + 'px'; + stroke.color = options.color; + stroke.opacity = options.opacity; + + if (options.dashArray) { + stroke.dashStyle = options.dashArray instanceof Array ? + options.dashArray.join(' ') : + options.dashArray.replace(/ *, */g, ' '); + } else { + stroke.dashStyle = ''; + } + + } else if (stroke) { + container.removeChild(stroke); + this._stroke = null; + } + + if (options.fill) { + if (!fill) { + fill = this._fill = this._createElement('fill'); + container.appendChild(fill); + } + fill.color = options.fillColor || options.color; + fill.opacity = options.fillOpacity; + + } else if (fill) { + container.removeChild(fill); + this._fill = null; + } + }, + + _updatePath: function () { + var style = this._container.style; + + style.display = 'none'; + this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug + style.display = ''; + } +}); + +L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : { + _initPathRoot: function () { + if (this._pathRoot) { return; } + + var root = this._pathRoot = document.createElement('div'); + root.className = 'leaflet-vml-container'; + this._panes.overlayPane.appendChild(root); + + this.on('moveend', this._updatePathViewport); + this._updatePathViewport(); + } +}); + + +/* + * Vector rendering for all browsers that support canvas. + */ + +L.Browser.canvas = (function () { + return !!document.createElement('canvas').getContext; +}()); + +L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({ + statics: { + //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value + CANVAS: true, + SVG: false + }, + + redraw: function () { + if (this._map) { + this.projectLatlngs(); + this._requestUpdate(); + } + return this; + }, + + setStyle: function (style) { + L.setOptions(this, style); + + if (this._map) { + this._updateStyle(); + this._requestUpdate(); + } + return this; + }, + + onRemove: function (map) { + map + .off('viewreset', this.projectLatlngs, this) + .off('moveend', this._updatePath, this); + + if (this.options.clickable) { + this._map.off('click', this._onClick, this); + } + + this._requestUpdate(); + + this._map = null; + }, + + _requestUpdate: function () { + if (this._map && !L.Path._updateRequest) { + L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map); + } + }, + + _fireMapMoveEnd: function () { + L.Path._updateRequest = null; + this.fire('moveend'); + }, + + _initElements: function () { + this._map._initPathRoot(); + this._ctx = this._map._canvasCtx; + }, + + _updateStyle: function () { + var options = this.options; + + if (options.stroke) { + this._ctx.lineWidth = options.weight; + this._ctx.strokeStyle = options.color; + } + if (options.fill) { + this._ctx.fillStyle = options.fillColor || options.color; + } + }, + + _drawPath: function () { + var i, j, len, len2, point, drawMethod; + + this._ctx.beginPath(); + + for (i = 0, len = this._parts.length; i < len; i++) { + for (j = 0, len2 = this._parts[i].length; j < len2; j++) { + point = this._parts[i][j]; + drawMethod = (j === 0 ? 'move' : 'line') + 'To'; + + this._ctx[drawMethod](point.x, point.y); + } + // TODO refactor ugly hack + if (this instanceof L.Polygon) { + this._ctx.closePath(); + } + } + }, + + _checkIfEmpty: function () { + return !this._parts.length; + }, + + _updatePath: function () { + if (this._checkIfEmpty()) { return; } + + var ctx = this._ctx, + options = this.options; + + this._drawPath(); + ctx.save(); + this._updateStyle(); + + if (options.fill) { + ctx.globalAlpha = options.fillOpacity; + ctx.fill(); + } + + if (options.stroke) { + ctx.globalAlpha = options.opacity; + ctx.stroke(); + } + + ctx.restore(); + + // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature + }, + + _initEvents: function () { + if (this.options.clickable) { + // TODO hand cursor + // TODO mouseover, mouseout, dblclick + this._map.on('click', this._onClick, this); + } + }, + + _onClick: function (e) { + if (this._containsPoint(e.layerPoint)) { + this.fire('click', { + latlng: e.latlng, + layerPoint: e.layerPoint, + containerPoint: e.containerPoint, + originalEvent: e + }); + } + } +}); + +L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : { + _initPathRoot: function () { + var root = this._pathRoot, + ctx; + + if (!root) { + root = this._pathRoot = document.createElement("canvas"); + root.style.position = 'absolute'; + ctx = this._canvasCtx = root.getContext('2d'); + + ctx.lineCap = "round"; + ctx.lineJoin = "round"; + + this._panes.overlayPane.appendChild(root); + + if (this.options.zoomAnimation) { + this._pathRoot.className = 'leaflet-zoom-animated'; + this.on('zoomanim', this._animatePathZoom); + this.on('zoomend', this._endPathZoom); + } + this.on('moveend', this._updateCanvasViewport); + this._updateCanvasViewport(); + } + }, + + _updateCanvasViewport: function () { + // don't redraw while zooming. See _updateSvgViewport for more details + if (this._pathZooming) { return; } + this._updatePathViewport(); + + var vp = this._pathViewport, + min = vp.min, + size = vp.max.subtract(min), + root = this._pathRoot; + + //TODO check if this works properly on mobile webkit + L.DomUtil.setPosition(root, min); + root.width = size.x; + root.height = size.y; + root.getContext('2d').translate(-min.x, -min.y); + } +}); + + +/* + * L.LineUtil contains different utility functions for line segments + * and polylines (clipping, simplification, distances, etc.) + */ + +/*jshint bitwise:false */ // allow bitwise oprations for this file + +L.LineUtil = { + + // Simplify polyline with vertex reduction and Douglas-Peucker simplification. + // Improves rendering performance dramatically by lessening the number of points to draw. + + simplify: function (/*Point[]*/ points, /*Number*/ tolerance) { + if (!tolerance || !points.length) { + return points.slice(); + } + + var sqTolerance = tolerance * tolerance; + + // stage 1: vertex reduction + points = this._reducePoints(points, sqTolerance); + + // stage 2: Douglas-Peucker simplification + points = this._simplifyDP(points, sqTolerance); + + return points; + }, + + // distance from a point to a segment between two points + pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { + return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true)); + }, + + closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { + return this._sqClosestPointOnSegment(p, p1, p2); + }, + + // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm + _simplifyDP: function (points, sqTolerance) { + + var len = points.length, + ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, + markers = new ArrayConstructor(len); + + markers[0] = markers[len - 1] = 1; + + this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1); + + var i, + newPoints = []; + + for (i = 0; i < len; i++) { + if (markers[i]) { + newPoints.push(points[i]); + } + } + + return newPoints; + }, + + _simplifyDPStep: function (points, markers, sqTolerance, first, last) { + + var maxSqDist = 0, + index, i, sqDist; + + for (i = first + 1; i <= last - 1; i++) { + sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true); + + if (sqDist > maxSqDist) { + index = i; + maxSqDist = sqDist; + } + } + + if (maxSqDist > sqTolerance) { + markers[index] = 1; + + this._simplifyDPStep(points, markers, sqTolerance, first, index); + this._simplifyDPStep(points, markers, sqTolerance, index, last); + } + }, + + // reduce points that are too close to each other to a single point + _reducePoints: function (points, sqTolerance) { + var reducedPoints = [points[0]]; + + for (var i = 1, prev = 0, len = points.length; i < len; i++) { + if (this._sqDist(points[i], points[prev]) > sqTolerance) { + reducedPoints.push(points[i]); + prev = i; + } + } + if (prev < len - 1) { + reducedPoints.push(points[len - 1]); + } + return reducedPoints; + }, + + // Cohen-Sutherland line clipping algorithm. + // Used to avoid rendering parts of a polyline that are not currently visible. + + clipSegment: function (a, b, bounds, useLastCode) { + var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds), + codeB = this._getBitCode(b, bounds), + + codeOut, p, newCode; + + // save 2nd code to avoid calculating it on the next segment + this._lastCode = codeB; + + while (true) { + // if a,b is inside the clip window (trivial accept) + if (!(codeA | codeB)) { + return [a, b]; + // if a,b is outside the clip window (trivial reject) + } else if (codeA & codeB) { + return false; + // other cases + } else { + codeOut = codeA || codeB, + p = this._getEdgeIntersection(a, b, codeOut, bounds), + newCode = this._getBitCode(p, bounds); + + if (codeOut === codeA) { + a = p; + codeA = newCode; + } else { + b = p; + codeB = newCode; + } + } + } + }, + + _getEdgeIntersection: function (a, b, code, bounds) { + var dx = b.x - a.x, + dy = b.y - a.y, + min = bounds.min, + max = bounds.max; + + if (code & 8) { // top + return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y); + } else if (code & 4) { // bottom + return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y); + } else if (code & 2) { // right + return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx); + } else if (code & 1) { // left + return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx); + } + }, + + _getBitCode: function (/*Point*/ p, bounds) { + var code = 0; + + if (p.x < bounds.min.x) { // left + code |= 1; + } else if (p.x > bounds.max.x) { // right + code |= 2; + } + if (p.y < bounds.min.y) { // bottom + code |= 4; + } else if (p.y > bounds.max.y) { // top + code |= 8; + } + + return code; + }, + + // square distance (to avoid unnecessary Math.sqrt calls) + _sqDist: function (p1, p2) { + var dx = p2.x - p1.x, + dy = p2.y - p1.y; + return dx * dx + dy * dy; + }, + + // return closest point on segment or distance to that point + _sqClosestPointOnSegment: function (p, p1, p2, sqDist) { + var x = p1.x, + y = p1.y, + dx = p2.x - x, + dy = p2.y - y, + dot = dx * dx + dy * dy, + t; + + if (dot > 0) { + t = ((p.x - x) * dx + (p.y - y) * dy) / dot; + + if (t > 1) { + x = p2.x; + y = p2.y; + } else if (t > 0) { + x += dx * t; + y += dy * t; + } + } + + dx = p.x - x; + dy = p.y - y; + + return sqDist ? dx * dx + dy * dy : new L.Point(x, y); + } +}; + + +/* + * L.Polygon is used to display polylines on a map. + */ + +L.Polyline = L.Path.extend({ + initialize: function (latlngs, options) { + L.Path.prototype.initialize.call(this, options); + + this._latlngs = this._convertLatLngs(latlngs); + }, + + options: { + // how much to simplify the polyline on each zoom level + // more = better performance and smoother look, less = more accurate + smoothFactor: 1.0, + noClip: false + }, + + projectLatlngs: function () { + this._originalPoints = []; + + for (var i = 0, len = this._latlngs.length; i < len; i++) { + this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]); + } + }, + + getPathString: function () { + for (var i = 0, len = this._parts.length, str = ''; i < len; i++) { + str += this._getPathPartStr(this._parts[i]); + } + return str; + }, + + getLatLngs: function () { + return this._latlngs; + }, + + setLatLngs: function (latlngs) { + this._latlngs = this._convertLatLngs(latlngs); + return this.redraw(); + }, + + addLatLng: function (latlng) { + this._latlngs.push(L.latLng(latlng)); + return this.redraw(); + }, + + spliceLatLngs: function () { // (Number index, Number howMany) + var removed = [].splice.apply(this._latlngs, arguments); + this._convertLatLngs(this._latlngs); + this.redraw(); + return removed; + }, + + closestLayerPoint: function (p) { + var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null; + + for (var j = 0, jLen = parts.length; j < jLen; j++) { + var points = parts[j]; + for (var i = 1, len = points.length; i < len; i++) { + p1 = points[i - 1]; + p2 = points[i]; + var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true); + if (sqDist < minDistance) { + minDistance = sqDist; + minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2); + } + } + } + if (minPoint) { + minPoint.distance = Math.sqrt(minDistance); + } + return minPoint; + }, + + getBounds: function () { + var bounds = new L.LatLngBounds(), + latLngs = this.getLatLngs(), + i, len; + + for (i = 0, len = latLngs.length; i < len; i++) { + bounds.extend(latLngs[i]); + } + + return bounds; + }, + + _convertLatLngs: function (latlngs) { + var i, len; + for (i = 0, len = latlngs.length; i < len; i++) { + if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') { + return; + } + latlngs[i] = L.latLng(latlngs[i]); + } + return latlngs; + }, + + _initEvents: function () { + L.Path.prototype._initEvents.call(this); + }, + + _getPathPartStr: function (points) { + var round = L.Path.VML; + + for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) { + p = points[j]; + if (round) { + p._round(); + } + str += (j ? 'L' : 'M') + p.x + ' ' + p.y; + } + return str; + }, + + _clipPoints: function () { + var points = this._originalPoints, + len = points.length, + i, k, segment; + + if (this.options.noClip) { + this._parts = [points]; + return; + } + + this._parts = []; + + var parts = this._parts, + vp = this._map._pathViewport, + lu = L.LineUtil; + + for (i = 0, k = 0; i < len - 1; i++) { + segment = lu.clipSegment(points[i], points[i + 1], vp, i); + if (!segment) { + continue; + } + + parts[k] = parts[k] || []; + parts[k].push(segment[0]); + + // if segment goes out of screen, or it's the last one, it's the end of the line part + if ((segment[1] !== points[i + 1]) || (i === len - 2)) { + parts[k].push(segment[1]); + k++; + } + } + }, + + // simplify each clipped part of the polyline + _simplifyPoints: function () { + var parts = this._parts, + lu = L.LineUtil; + + for (var i = 0, len = parts.length; i < len; i++) { + parts[i] = lu.simplify(parts[i], this.options.smoothFactor); + } + }, + + _updatePath: function () { + if (!this._map) { return; } + + this._clipPoints(); + this._simplifyPoints(); + + L.Path.prototype._updatePath.call(this); + } +}); + +L.polyline = function (latlngs, options) { + return new L.Polyline(latlngs, options); +}; + + +/* + * L.PolyUtil contains utility functions for polygons (clipping, etc.). + */ + +/*jshint bitwise:false */ // allow bitwise operations here + +L.PolyUtil = {}; + +/* + * Sutherland-Hodgeman polygon clipping algorithm. + * Used to avoid rendering parts of a polygon that are not currently visible. + */ +L.PolyUtil.clipPolygon = function (points, bounds) { + var clippedPoints, + edges = [1, 4, 2, 8], + i, j, k, + a, b, + len, edge, p, + lu = L.LineUtil; + + for (i = 0, len = points.length; i < len; i++) { + points[i]._code = lu._getBitCode(points[i], bounds); + } + + // for each edge (left, bottom, right, top) + for (k = 0; k < 4; k++) { + edge = edges[k]; + clippedPoints = []; + + for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { + a = points[i]; + b = points[j]; + + // if a is inside the clip window + if (!(a._code & edge)) { + // if b is outside the clip window (a->b goes out of screen) + if (b._code & edge) { + p = lu._getEdgeIntersection(b, a, edge, bounds); + p._code = lu._getBitCode(p, bounds); + clippedPoints.push(p); + } + clippedPoints.push(a); + + // else if b is inside the clip window (a->b enters the screen) + } else if (!(b._code & edge)) { + p = lu._getEdgeIntersection(b, a, edge, bounds); + p._code = lu._getBitCode(p, bounds); + clippedPoints.push(p); + } + } + points = clippedPoints; + } + + return points; +}; + + +/* + * L.Polygon is used to display polygons on a map. + */ + +L.Polygon = L.Polyline.extend({ + options: { + fill: true + }, + + initialize: function (latlngs, options) { + L.Polyline.prototype.initialize.call(this, latlngs, options); + + if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) { + this._latlngs = this._convertLatLngs(latlngs[0]); + this._holes = latlngs.slice(1); + } + }, + + projectLatlngs: function () { + L.Polyline.prototype.projectLatlngs.call(this); + + // project polygon holes points + // TODO move this logic to Polyline to get rid of duplication + this._holePoints = []; + + if (!this._holes) { return; } + + var i, j, len, len2; + + for (i = 0, len = this._holes.length; i < len; i++) { + this._holePoints[i] = []; + + for (j = 0, len2 = this._holes[i].length; j < len2; j++) { + this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]); + } + } + }, + + _clipPoints: function () { + var points = this._originalPoints, + newParts = []; + + this._parts = [points].concat(this._holePoints); + + if (this.options.noClip) { return; } + + for (var i = 0, len = this._parts.length; i < len; i++) { + var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport); + if (clipped.length) { + newParts.push(clipped); + } + } + + this._parts = newParts; + }, + + _getPathPartStr: function (points) { + var str = L.Polyline.prototype._getPathPartStr.call(this, points); + return str + (L.Browser.svg ? 'z' : 'x'); + } +}); + +L.polygon = function (latlngs, options) { + return new L.Polygon(latlngs, options); +}; + + +/* + * Contains L.MultiPolyline and L.MultiPolygon layers. + */ + +(function () { + function createMulti(Klass) { + + return L.FeatureGroup.extend({ + + initialize: function (latlngs, options) { + this._layers = {}; + this._options = options; + this.setLatLngs(latlngs); + }, + + setLatLngs: function (latlngs) { + var i = 0, + len = latlngs.length; + + this.eachLayer(function (layer) { + if (i < len) { + layer.setLatLngs(latlngs[i++]); + } else { + this.removeLayer(layer); + } + }, this); + + while (i < len) { + this.addLayer(new Klass(latlngs[i++], this._options)); + } + + return this; + } + }); + } + + L.MultiPolyline = createMulti(L.Polyline); + L.MultiPolygon = createMulti(L.Polygon); + + L.multiPolyline = function (latlngs, options) { + return new L.MultiPolyline(latlngs, options); + }; + + L.multiPolygon = function (latlngs, options) { + return new L.MultiPolygon(latlngs, options); + }; +}()); + + +/* + * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object. + */ + +L.Rectangle = L.Polygon.extend({ + initialize: function (latLngBounds, options) { + L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); + }, + + setBounds: function (latLngBounds) { + this.setLatLngs(this._boundsToLatLngs(latLngBounds)); + }, + + _boundsToLatLngs: function (latLngBounds) { + latLngBounds = L.latLngBounds(latLngBounds); + return [ + latLngBounds.getSouthWest(), + latLngBounds.getNorthWest(), + latLngBounds.getNorthEast(), + latLngBounds.getSouthEast() + ]; + } +}); + +L.rectangle = function (latLngBounds, options) { + return new L.Rectangle(latLngBounds, options); +}; + + +/* + * L.Circle is a circle overlay (with a certain radius in meters). + */ + +L.Circle = L.Path.extend({ + initialize: function (latlng, radius, options) { + L.Path.prototype.initialize.call(this, options); + + this._latlng = L.latLng(latlng); + this._mRadius = radius; + }, + + options: { + fill: true + }, + + setLatLng: function (latlng) { + this._latlng = L.latLng(latlng); + return this.redraw(); + }, + + setRadius: function (radius) { + this._mRadius = radius; + return this.redraw(); + }, + + projectLatlngs: function () { + var lngRadius = this._getLngRadius(), + latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius), + point2 = this._map.latLngToLayerPoint(latlng2); + + this._point = this._map.latLngToLayerPoint(this._latlng); + this._radius = Math.max(Math.round(this._point.x - point2.x), 1); + }, + + getBounds: function () { + var lngRadius = this._getLngRadius(), + latRadius = (this._mRadius / 40075017) * 360, + latlng = this._latlng, + sw = new L.LatLng(latlng.lat - latRadius, latlng.lng - lngRadius), + ne = new L.LatLng(latlng.lat + latRadius, latlng.lng + lngRadius); + + return new L.LatLngBounds(sw, ne); + }, + + getLatLng: function () { + return this._latlng; + }, + + getPathString: function () { + var p = this._point, + r = this._radius; + + if (this._checkIfEmpty()) { + return ''; + } + + if (L.Browser.svg) { + return "M" + p.x + "," + (p.y - r) + + "A" + r + "," + r + ",0,1,1," + + (p.x - 0.1) + "," + (p.y - r) + " z"; + } else { + p._round(); + r = Math.round(r); + return "AL " + p.x + "," + p.y + " " + r + "," + r + " 0," + (65535 * 360); + } + }, + + getRadius: function () { + return this._mRadius; + }, + + // TODO Earth hardcoded, move into projection code! + + _getLatRadius: function () { + return (this._mRadius / 40075017) * 360; + }, + + _getLngRadius: function () { + return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat); + }, + + _checkIfEmpty: function () { + if (!this._map) { + return false; + } + var vp = this._map._pathViewport, + r = this._radius, + p = this._point; + + return p.x - r > vp.max.x || p.y - r > vp.max.y || + p.x + r < vp.min.x || p.y + r < vp.min.y; + } +}); + +L.circle = function (latlng, radius, options) { + return new L.Circle(latlng, radius, options); +}; + + +/* + * L.CircleMarker is a circle overlay with a permanent pixel radius. + */ + +L.CircleMarker = L.Circle.extend({ + options: { + radius: 10, + weight: 2 + }, + + initialize: function (latlng, options) { + L.Circle.prototype.initialize.call(this, latlng, null, options); + this._radius = this.options.radius; + }, + + projectLatlngs: function () { + this._point = this._map.latLngToLayerPoint(this._latlng); + }, + + _updateStyle : function () { + L.Circle.prototype._updateStyle.call(this); + this.setRadius(this.options.radius); + }, + + setRadius: function (radius) { + this.options.radius = this._radius = radius; + return this.redraw(); + } +}); + +L.circleMarker = function (latlng, options) { + return new L.CircleMarker(latlng, options); +}; + + +/* + * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines. + */ + +L.Polyline.include(!L.Path.CANVAS ? {} : { + _containsPoint: function (p, closed) { + var i, j, k, len, len2, dist, part, + w = this.options.weight / 2; + + if (L.Browser.touch) { + w += 10; // polyline click tolerance on touch devices + } + + for (i = 0, len = this._parts.length; i < len; i++) { + part = this._parts[i]; + for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { + if (!closed && (j === 0)) { + continue; + } + + dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]); + + if (dist <= w) { + return true; + } + } + } + return false; + } +}); + + +/* + * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons. + */ + +L.Polygon.include(!L.Path.CANVAS ? {} : { + _containsPoint: function (p) { + var inside = false, + part, p1, p2, + i, j, k, + len, len2; + + // TODO optimization: check if within bounds first + + if (L.Polyline.prototype._containsPoint.call(this, p, true)) { + // click on polygon border + return true; + } + + // ray casting algorithm for detecting if point is in polygon + + for (i = 0, len = this._parts.length; i < len; i++) { + part = this._parts[i]; + + for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { + p1 = part[j]; + p2 = part[k]; + + if (((p1.y > p.y) !== (p2.y > p.y)) && + (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) { + inside = !inside; + } + } + } + + return inside; + } +}); + + +/* + * Extends L.Circle with Canvas-specific code. + */ + +L.Circle.include(!L.Path.CANVAS ? {} : { + _drawPath: function () { + var p = this._point; + this._ctx.beginPath(); + this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false); + }, + + _containsPoint: function (p) { + var center = this._point, + w2 = this.options.stroke ? this.options.weight / 2 : 0; + + return (p.distanceTo(center) <= this._radius + w2); + } +}); + + +/* + * L.GeoJSON turns any GeoJSON data into a Leaflet layer. + */ + +L.GeoJSON = L.FeatureGroup.extend({ + + initialize: function (geojson, options) { + L.setOptions(this, options); + + this._layers = {}; + + if (geojson) { + this.addData(geojson); + } + }, + + addData: function (geojson) { + var features = L.Util.isArray(geojson) ? geojson : geojson.features, + i, len; + + if (features) { + for (i = 0, len = features.length; i < len; i++) { + // Only add this if geometry or geometries are set and not null + if (features[i].geometries || features[i].geometry || features[i].features) { + this.addData(features[i]); + } + } + return this; + } + + var options = this.options; + + if (options.filter && !options.filter(geojson)) { return; } + + var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer); + layer.feature = geojson; + + layer.defaultOptions = layer.options; + this.resetStyle(layer); + + if (options.onEachFeature) { + options.onEachFeature(geojson, layer); + } + + return this.addLayer(layer); + }, + + resetStyle: function (layer) { + var style = this.options.style; + if (style) { + // reset any custom styles + L.Util.extend(layer.options, layer.defaultOptions); + + this._setLayerStyle(layer, style); + } + }, + + setStyle: function (style) { + this.eachLayer(function (layer) { + this._setLayerStyle(layer, style); + }, this); + }, + + _setLayerStyle: function (layer, style) { + if (typeof style === 'function') { + style = style(layer.feature); + } + if (layer.setStyle) { + layer.setStyle(style); + } + } +}); + +L.extend(L.GeoJSON, { + geometryToLayer: function (geojson, pointToLayer) { + var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, + coords = geometry.coordinates, + layers = [], + latlng, latlngs, i, len, layer; + + switch (geometry.type) { + case 'Point': + latlng = this.coordsToLatLng(coords); + return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng); + + case 'MultiPoint': + for (i = 0, len = coords.length; i < len; i++) { + latlng = this.coordsToLatLng(coords[i]); + layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng); + layers.push(layer); + } + return new L.FeatureGroup(layers); + + case 'LineString': + latlngs = this.coordsToLatLngs(coords); + return new L.Polyline(latlngs); + + case 'Polygon': + latlngs = this.coordsToLatLngs(coords, 1); + return new L.Polygon(latlngs); + + case 'MultiLineString': + latlngs = this.coordsToLatLngs(coords, 1); + return new L.MultiPolyline(latlngs); + + case 'MultiPolygon': + latlngs = this.coordsToLatLngs(coords, 2); + return new L.MultiPolygon(latlngs); + + case 'GeometryCollection': + for (i = 0, len = geometry.geometries.length; i < len; i++) { + layer = this.geometryToLayer({ + geometry: geometry.geometries[i], + type: 'Feature', + properties: geojson.properties + }, pointToLayer); + layers.push(layer); + } + return new L.FeatureGroup(layers); + + default: + throw new Error('Invalid GeoJSON object.'); + } + }, + + coordsToLatLng: function (coords, reverse) { // (Array, Boolean) -> LatLng + var lat = parseFloat(coords[reverse ? 0 : 1]), + lng = parseFloat(coords[reverse ? 1 : 0]); + + return new L.LatLng(lat, lng); + }, + + coordsToLatLngs: function (coords, levelsDeep, reverse) { // (Array, Number, Boolean) -> Array + var latlng, + latlngs = [], + i, len; + + for (i = 0, len = coords.length; i < len; i++) { + latlng = levelsDeep ? + this.coordsToLatLngs(coords[i], levelsDeep - 1, reverse) : + this.coordsToLatLng(coords[i], reverse); + + latlngs.push(latlng); + } + + return latlngs; + } +}); + +L.geoJson = function (geojson, options) { + return new L.GeoJSON(geojson, options); +}; + + +/* + * L.DomEvent contains functions for working with DOM events. + */ + +L.DomEvent = { + /* inspired by John Resig, Dean Edwards and YUI addEvent implementations */ + addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object]) + + var id = L.stamp(fn), + key = '_leaflet_' + type + id, + handler, originalHandler, newType; + + if (obj[key]) { return this; } + + handler = function (e) { + return fn.call(context || obj, e || L.DomEvent._getEvent()); + }; + + if (L.Browser.msTouch && type.indexOf('touch') === 0) { + return this.addMsTouchListener(obj, type, handler, id); + } + if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) { + this.addDoubleTapListener(obj, handler, id); + } + + if ('addEventListener' in obj) { + + if (type === 'mousewheel') { + obj.addEventListener('DOMMouseScroll', handler, false); + obj.addEventListener(type, handler, false); + + } else if ((type === 'mouseenter') || (type === 'mouseleave')) { + + originalHandler = handler; + newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout'); + + handler = function (e) { + if (!L.DomEvent._checkMouse(obj, e)) { return; } + return originalHandler(e); + }; + + obj.addEventListener(newType, handler, false); + + } else { + obj.addEventListener(type, handler, false); + } + + } else if ('attachEvent' in obj) { + obj.attachEvent("on" + type, handler); + } + + obj[key] = handler; + + return this; + }, + + removeListener: function (obj, type, fn) { // (HTMLElement, String, Function) + + var id = L.stamp(fn), + key = '_leaflet_' + type + id, + handler = obj[key]; + + if (!handler) { return; } + + if (L.Browser.msTouch && type.indexOf('touch') === 0) { + this.removeMsTouchListener(obj, type, id); + } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) { + this.removeDoubleTapListener(obj, id); + + } else if ('removeEventListener' in obj) { + + if (type === 'mousewheel') { + obj.removeEventListener('DOMMouseScroll', handler, false); + obj.removeEventListener(type, handler, false); + + } else if ((type === 'mouseenter') || (type === 'mouseleave')) { + obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false); + } else { + obj.removeEventListener(type, handler, false); + } + } else if ('detachEvent' in obj) { + obj.detachEvent("on" + type, handler); + } + + obj[key] = null; + + return this; + }, + + stopPropagation: function (e) { + + if (e.stopPropagation) { + e.stopPropagation(); + } else { + e.cancelBubble = true; + } + return this; + }, + + disableClickPropagation: function (el) { + + var stop = L.DomEvent.stopPropagation; + + for (var i = L.Draggable.START.length - 1; i >= 0; i--) { + L.DomEvent.addListener(el, L.Draggable.START[i], stop); + } + + return L.DomEvent + .addListener(el, 'click', stop) + .addListener(el, 'dblclick', stop); + }, + + preventDefault: function (e) { + + if (e.preventDefault) { + e.preventDefault(); + } else { + e.returnValue = false; + } + return this; + }, + + stop: function (e) { + return L.DomEvent.preventDefault(e).stopPropagation(e); + }, + + getMousePosition: function (e, container) { + + var body = document.body, + docEl = document.documentElement, + x = e.pageX ? e.pageX : e.clientX + body.scrollLeft + docEl.scrollLeft, + y = e.pageY ? e.pageY : e.clientY + body.scrollTop + docEl.scrollTop, + pos = new L.Point(x, y); + + return (container ? pos._subtract(L.DomUtil.getViewportOffset(container)) : pos); + }, + + getWheelDelta: function (e) { + + var delta = 0; + + if (e.wheelDelta) { + delta = e.wheelDelta / 120; + } + if (e.detail) { + delta = -e.detail / 3; + } + return delta; + }, + + // check if element really left/entered the event target (for mouseenter/mouseleave) + _checkMouse: function (el, e) { + + var related = e.relatedTarget; + + if (!related) { return true; } + + try { + while (related && (related !== el)) { + related = related.parentNode; + } + } catch (err) { + return false; + } + return (related !== el); + }, + + _getEvent: function () { // evil magic for IE + /*jshint noarg:false */ + var e = window.event; + if (!e) { + var caller = arguments.callee.caller; + while (caller) { + e = caller['arguments'][0]; + if (e && window.Event === e.constructor) { + break; + } + caller = caller.caller; + } + } + return e; + } +}; + +L.DomEvent.on = L.DomEvent.addListener; +L.DomEvent.off = L.DomEvent.removeListener; + + +/* + * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too. + */ + +L.Draggable = L.Class.extend({ + includes: L.Mixin.Events, + + statics: { + START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'], + END: { + mousedown: 'mouseup', + touchstart: 'touchend', + MSPointerDown: 'touchend' + }, + MOVE: { + mousedown: 'mousemove', + touchstart: 'touchmove', + MSPointerDown: 'touchmove' + }, + TAP_TOLERANCE: 15 + }, + + initialize: function (element, dragStartTarget, longPress) { + this._element = element; + this._dragStartTarget = dragStartTarget || element; + this._longPress = longPress && !L.Browser.msTouch; + }, + + enable: function () { + if (this._enabled) { return; } + + for (var i = L.Draggable.START.length - 1; i >= 0; i--) { + L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this); + } + this._enabled = true; + }, + + disable: function () { + if (!this._enabled) { return; } + + for (var i = L.Draggable.START.length - 1; i >= 0; i--) { + L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this); + } + this._enabled = false; + this._moved = false; + }, + + _onDown: function (e) { + if ((!L.Browser.touch && e.shiftKey) || + ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } + + L.DomEvent.preventDefault(e); + L.DomEvent.stopPropagation(e); + + if (L.Draggable._disabled) { return; } + + this._simulateClick = true; + + if (e.touches && e.touches.length > 1) { + this._simulateClick = false; + clearTimeout(this._longPressTimeout); + return; + } + + var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), + el = first.target; + + if (L.Browser.touch && el.tagName.toLowerCase() === 'a') { + L.DomUtil.addClass(el, 'leaflet-active'); + } + + this._moved = false; + if (this._moving) { return; } + + this._startPoint = new L.Point(first.clientX, first.clientY); + this._startPos = this._newPos = L.DomUtil.getPosition(this._element); + + //Touch contextmenu event emulation + if (e.touches && e.touches.length === 1 && L.Browser.touch && this._longPress) { + this._longPressTimeout = setTimeout(L.bind(function () { + var dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0; + + if (dist < L.Draggable.TAP_TOLERANCE) { + this._simulateClick = false; + this._onUp(); + this._simulateEvent('contextmenu', first); + } + }, this), 1000); + } + + L.DomEvent.on(document, L.Draggable.MOVE[e.type], this._onMove, this); + L.DomEvent.on(document, L.Draggable.END[e.type], this._onUp, this); + }, + + _onMove: function (e) { + if (e.touches && e.touches.length > 1) { return; } + + var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), + newPoint = new L.Point(first.clientX, first.clientY), + diffVec = newPoint.subtract(this._startPoint); + + if (!diffVec.x && !diffVec.y) { return; } + + L.DomEvent.preventDefault(e); + + if (!this._moved) { + this.fire('dragstart'); + this._moved = true; + + this._startPos = L.DomUtil.getPosition(this._element).subtract(diffVec); + + if (!L.Browser.touch) { + L.DomUtil.disableTextSelection(); + this._setMovingCursor(); + } + } + + this._newPos = this._startPos.add(diffVec); + this._moving = true; + + L.Util.cancelAnimFrame(this._animRequest); + this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget); + }, + + _updatePosition: function () { + this.fire('predrag'); + L.DomUtil.setPosition(this._element, this._newPos); + this.fire('drag'); + }, + + _onUp: function (e) { + var simulateClickTouch; + clearTimeout(this._longPressTimeout); + if (this._simulateClick && e.changedTouches) { + var first = e.changedTouches[0], + el = first.target, + dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0; + + if (el.tagName.toLowerCase() === 'a') { + L.DomUtil.removeClass(el, 'leaflet-active'); + } + + if (dist < L.Draggable.TAP_TOLERANCE) { + simulateClickTouch = first; + } + } + + if (!L.Browser.touch) { + L.DomUtil.enableTextSelection(); + this._restoreCursor(); + } + + for (var i in L.Draggable.MOVE) { + if (L.Draggable.MOVE.hasOwnProperty(i)) { + L.DomEvent.off(document, L.Draggable.MOVE[i], this._onMove); + L.DomEvent.off(document, L.Draggable.END[i], this._onUp); + } + } + + if (this._moved) { + // ensure drag is not fired after dragend + L.Util.cancelAnimFrame(this._animRequest); + + this.fire('dragend'); + } + this._moving = false; + + if (simulateClickTouch) { + this._moved = false; + this._simulateEvent('click', simulateClickTouch); + } + }, + + _setMovingCursor: function () { + L.DomUtil.addClass(document.body, 'leaflet-dragging'); + }, + + _restoreCursor: function () { + L.DomUtil.removeClass(document.body, 'leaflet-dragging'); + }, + + _simulateEvent: function (type, e) { + var simulatedEvent = document.createEvent('MouseEvents'); + + simulatedEvent.initMouseEvent( + type, true, true, window, 1, + e.screenX, e.screenY, + e.clientX, e.clientY, + false, false, false, false, 0, null); + + e.target.dispatchEvent(simulatedEvent); + } +}); + + +/* + L.Handler is a base class for handler classes that are used internally to inject + interaction features like dragging to classes like Map and Marker. +*/ + +L.Handler = L.Class.extend({ + initialize: function (map) { + this._map = map; + }, + + enable: function () { + if (this._enabled) { return; } + + this._enabled = true; + this.addHooks(); + }, + + disable: function () { + if (!this._enabled) { return; } + + this._enabled = false; + this.removeHooks(); + }, + + enabled: function () { + return !!this._enabled; + } +}); + + +/* + * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. + */ + +L.Map.mergeOptions({ + dragging: true, + + inertia: !L.Browser.android23, + inertiaDeceleration: 3400, // px/s^2 + inertiaMaxSpeed: Infinity, // px/s + inertiaThreshold: L.Browser.touch ? 32 : 18, // ms + easeLinearity: 0.25, + + longPress: true, + + // TODO refactor, move to CRS + worldCopyJump: false +}); + +L.Map.Drag = L.Handler.extend({ + addHooks: function () { + if (!this._draggable) { + var map = this._map; + + this._draggable = new L.Draggable(map._mapPane, map._container, map.options.longPress); + + this._draggable.on({ + 'dragstart': this._onDragStart, + 'drag': this._onDrag, + 'dragend': this._onDragEnd + }, this); + + if (map.options.worldCopyJump) { + this._draggable.on('predrag', this._onPreDrag, this); + map.on('viewreset', this._onViewReset, this); + } + } + this._draggable.enable(); + }, + + removeHooks: function () { + this._draggable.disable(); + }, + + moved: function () { + return this._draggable && this._draggable._moved; + }, + + _onDragStart: function () { + var map = this._map; + + if (map._panAnim) { + map._panAnim.stop(); + } + + map + .fire('movestart') + .fire('dragstart'); + + if (map.options.inertia) { + this._positions = []; + this._times = []; + } + }, + + _onDrag: function () { + if (this._map.options.inertia) { + var time = this._lastTime = +new Date(), + pos = this._lastPos = this._draggable._newPos; + + this._positions.push(pos); + this._times.push(time); + + if (time - this._times[0] > 200) { + this._positions.shift(); + this._times.shift(); + } + } + + this._map + .fire('move') + .fire('drag'); + }, + + _onViewReset: function () { + // TODO fix hardcoded Earth values + var pxCenter = this._map.getSize()._divideBy(2), + pxWorldCenter = this._map.latLngToLayerPoint(new L.LatLng(0, 0)); + + this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x; + this._worldWidth = this._map.project(new L.LatLng(0, 180)).x; + }, + + _onPreDrag: function () { + // TODO refactor to be able to adjust map pane position after zoom + var worldWidth = this._worldWidth, + halfWidth = Math.round(worldWidth / 2), + dx = this._initialWorldOffset, + x = this._draggable._newPos.x, + newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, + newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, + newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2; + + this._draggable._newPos.x = newX; + }, + + _onDragEnd: function () { + var map = this._map, + options = map.options, + delay = +new Date() - this._lastTime, + + noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0]; + + if (noInertia) { + map.fire('moveend'); + + } else { + + var direction = this._lastPos.subtract(this._positions[0]), + duration = (this._lastTime + delay - this._times[0]) / 1000, + ease = options.easeLinearity, + + speedVector = direction.multiplyBy(ease / duration), + speed = speedVector.distanceTo(new L.Point(0, 0)), + + limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), + limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), + + decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), + offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); + + L.Util.requestAnimFrame(function () { + map.panBy(offset, decelerationDuration, ease); + }); + } + + map.fire('dragend'); + + if (options.maxBounds) { + // TODO predrag validation instead of animation + L.Util.requestAnimFrame(this._panInsideMaxBounds, map, true, map._container); + } + }, + + _panInsideMaxBounds: function () { + this.panInsideBounds(this.options.maxBounds); + } +}); + +L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag); + + +/* + * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. + */ + +L.Map.mergeOptions({ + doubleClickZoom: true +}); + +L.Map.DoubleClickZoom = L.Handler.extend({ + addHooks: function () { + this._map.on('dblclick', this._onDoubleClick); + }, + + removeHooks: function () { + this._map.off('dblclick', this._onDoubleClick); + }, + + _onDoubleClick: function (e) { + this.setView(e.latlng, this._zoom + 1); + } +}); + +L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom); + + +/* + * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. + */ + +L.Map.mergeOptions({ + scrollWheelZoom: true +}); + +L.Map.ScrollWheelZoom = L.Handler.extend({ + addHooks: function () { + L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this); + this._delta = 0; + }, + + removeHooks: function () { + L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll); + }, + + _onWheelScroll: function (e) { + var delta = L.DomEvent.getWheelDelta(e); + + this._delta += delta; + this._lastMousePos = this._map.mouseEventToContainerPoint(e); + + if (!this._startTime) { + this._startTime = +new Date(); + } + + var left = Math.max(40 - (+new Date() - this._startTime), 0); + + clearTimeout(this._timer); + this._timer = setTimeout(L.bind(this._performZoom, this), left); + + L.DomEvent.preventDefault(e); + L.DomEvent.stopPropagation(e); + }, + + _performZoom: function () { + var map = this._map, + delta = this._delta, + zoom = map.getZoom(); + + delta = delta > 0 ? Math.ceil(delta) : Math.round(delta); + delta = Math.max(Math.min(delta, 4), -4); + delta = map._limitZoom(zoom + delta) - zoom; + + this._delta = 0; + + this._startTime = null; + + if (!delta) { return; } + + var newZoom = zoom + delta, + newCenter = this._getCenterForScrollWheelZoom(newZoom); + + map.setView(newCenter, newZoom); + }, + + _getCenterForScrollWheelZoom: function (newZoom) { + var map = this._map, + scale = map.getZoomScale(newZoom), + viewHalf = map.getSize()._divideBy(2), + centerOffset = this._lastMousePos._subtract(viewHalf)._multiplyBy(1 - 1 / scale), + newCenterPoint = map._getTopLeftPoint()._add(viewHalf)._add(centerOffset); + + return map.unproject(newCenterPoint); + } +}); + +L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom); + + +/* + * Extends the event handling code with double tap support for mobile browsers. + */ + +L.extend(L.DomEvent, { + + _touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart', + _touchend: L.Browser.msTouch ? 'MSPointerUp' : 'touchend', + + // inspired by Zepto touch code by Thomas Fuchs + addDoubleTapListener: function (obj, handler, id) { + var last, + doubleTap = false, + delay = 250, + touch, + pre = '_leaflet_', + touchstart = this._touchstart, + touchend = this._touchend, + trackedTouches = []; + + function onTouchStart(e) { + var count; + if (L.Browser.msTouch) { + trackedTouches.push(e.pointerId); + count = trackedTouches.length; + } else { + count = e.touches.length; + } + if (count > 1) { + return; + } + + var now = Date.now(), + delta = now - (last || now); + + touch = e.touches ? e.touches[0] : e; + doubleTap = (delta > 0 && delta <= delay); + last = now; + } + + function onTouchEnd(e) { + /*jshint forin:false */ + if (L.Browser.msTouch) { + var idx = trackedTouches.indexOf(e.pointerId); + if (idx === -1) { + return; + } + trackedTouches.splice(idx, 1); + } + + if (doubleTap) { + if (L.Browser.msTouch) { + //Work around .type being readonly with MSPointer* events + var newTouch = { }, + prop; + + for (var i in touch) { + prop = touch[i]; + if (typeof prop === 'function') { + newTouch[i] = prop.bind(touch); + } else { + newTouch[i] = prop; + } + } + touch = newTouch; + } + touch.type = 'dblclick'; + handler(touch); + last = null; + } + } + obj[pre + touchstart + id] = onTouchStart; + obj[pre + touchend + id] = onTouchEnd; + + //On msTouch we need to listen on the document otherwise a drag starting on the map and moving off screen will not come through to us + // so we will lose track of how many touches are ongoing + var endElement = L.Browser.msTouch ? document.documentElement : obj; + + obj.addEventListener(touchstart, onTouchStart, false); + endElement.addEventListener(touchend, onTouchEnd, false); + if (L.Browser.msTouch) { + endElement.addEventListener('MSPointerCancel', onTouchEnd, false); + } + return this; + }, + + removeDoubleTapListener: function (obj, id) { + var pre = '_leaflet_'; + obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false); + (L.Browser.msTouch ? document.documentElement : obj).removeEventListener(this._touchend, obj[pre + this._touchend + id], false); + if (L.Browser.msTouch) { + document.documentElement.removeEventListener('MSPointerCancel', obj[pre + this._touchend + id], false); + } + return this; + } +}); + + +/* + * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. + */ + +L.extend(L.DomEvent, { + + _msTouches: [], + _msDocumentListener: false, + + // Provides a touch events wrapper for msPointer events. + // Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019 + + addMsTouchListener: function (obj, type, handler, id) { + + switch (type) { + case 'touchstart': + return this.addMsTouchListenerStart(obj, type, handler, id); + case 'touchend': + return this.addMsTouchListenerEnd(obj, type, handler, id); + case 'touchmove': + return this.addMsTouchListenerMove(obj, type, handler, id); + default: + throw 'Unknown touch event type'; + } + }, + + addMsTouchListenerStart: function (obj, type, handler, id) { + var pre = '_leaflet_', + touches = this._msTouches; + + var cb = function (e) { + + var alreadyInArray = false; + for (var i = 0; i < touches.length; i++) { + if (touches[i].pointerId === e.pointerId) { + alreadyInArray = true; + break; + } + } + if (!alreadyInArray) { + touches.push(e); + } + + e.touches = touches.slice(); + e.changedTouches = [e]; + + handler(e); + }; + + obj[pre + 'touchstart' + id] = cb; + obj.addEventListener('MSPointerDown', cb, false); + + // need to also listen for end events to keep the _msTouches list accurate + // this needs to be on the body and never go away + if (!this._msDocumentListener) { + var internalCb = function (e) { + for (var i = 0; i < touches.length; i++) { + if (touches[i].pointerId === e.pointerId) { + touches.splice(i, 1); + break; + } + } + }; + //We listen on the documentElement as any drags that end by moving the touch off the screen get fired there + document.documentElement.addEventListener('MSPointerUp', internalCb, false); + document.documentElement.addEventListener('MSPointerCancel', internalCb, false); + + this._msDocumentListener = true; + } + + return this; + }, + + addMsTouchListenerMove: function (obj, type, handler, id) { + var pre = '_leaflet_', + touches = this._msTouches; + + function cb(e) { + + // don't fire touch moves when mouse isn't down + if (e.pointerType === e.MSPOINTER_TYPE_MOUSE && e.buttons === 0) { return; } + + for (var i = 0; i < touches.length; i++) { + if (touches[i].pointerId === e.pointerId) { + touches[i] = e; + break; + } + } + + e.touches = touches.slice(); + e.changedTouches = [e]; + + handler(e); + } + + obj[pre + 'touchmove' + id] = cb; + obj.addEventListener('MSPointerMove', cb, false); + + return this; + }, + + addMsTouchListenerEnd: function (obj, type, handler, id) { + var pre = '_leaflet_', + touches = this._msTouches; + + var cb = function (e) { + for (var i = 0; i < touches.length; i++) { + if (touches[i].pointerId === e.pointerId) { + touches.splice(i, 1); + break; + } + } + + e.touches = touches.slice(); + e.changedTouches = [e]; + + handler(e); + }; + + obj[pre + 'touchend' + id] = cb; + obj.addEventListener('MSPointerUp', cb, false); + obj.addEventListener('MSPointerCancel', cb, false); + + return this; + }, + + removeMsTouchListener: function (obj, type, id) { + var pre = '_leaflet_', + cb = obj[pre + type + id]; + + switch (type) { + case 'touchstart': + obj.removeEventListener('MSPointerDown', cb, false); + break; + case 'touchmove': + obj.removeEventListener('MSPointerMove', cb, false); + break; + case 'touchend': + obj.removeEventListener('MSPointerUp', cb, false); + obj.removeEventListener('MSPointerCancel', cb, false); + break; + } + + return this; + } +}); + + +/* + * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers. + */ + +L.Map.mergeOptions({ + touchZoom: L.Browser.touch && !L.Browser.android23 +}); + +L.Map.TouchZoom = L.Handler.extend({ + addHooks: function () { + L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this); + }, + + removeHooks: function () { + L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this); + }, + + _onTouchStart: function (e) { + var map = this._map; + + if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } + + var p1 = map.mouseEventToLayerPoint(e.touches[0]), + p2 = map.mouseEventToLayerPoint(e.touches[1]), + viewCenter = map._getCenterLayerPoint(); + + this._startCenter = p1.add(p2)._divideBy(2); + this._startDist = p1.distanceTo(p2); + + this._moved = false; + this._zooming = true; + + this._centerOffset = viewCenter.subtract(this._startCenter); + + if (map._panAnim) { + map._panAnim.stop(); + } + + L.DomEvent + .on(document, 'touchmove', this._onTouchMove, this) + .on(document, 'touchend', this._onTouchEnd, this); + + L.DomEvent.preventDefault(e); + }, + + _onTouchMove: function (e) { + if (!e.touches || e.touches.length !== 2) { return; } + + var map = this._map; + + var p1 = map.mouseEventToLayerPoint(e.touches[0]), + p2 = map.mouseEventToLayerPoint(e.touches[1]); + + this._scale = p1.distanceTo(p2) / this._startDist; + this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter); + + if (this._scale === 1) { return; } + + if (!this._moved) { + L.DomUtil.addClass(map._mapPane, 'leaflet-zoom-anim leaflet-touching'); + + map + .fire('movestart') + .fire('zoomstart') + ._prepareTileBg(); + + this._moved = true; + } + + L.Util.cancelAnimFrame(this._animRequest); + this._animRequest = L.Util.requestAnimFrame( + this._updateOnMove, this, true, this._map._container); + + L.DomEvent.preventDefault(e); + }, + + _updateOnMove: function () { + var map = this._map, + origin = this._getScaleOrigin(), + center = map.layerPointToLatLng(origin); + + map.fire('zoomanim', { + center: center, + zoom: map.getScaleZoom(this._scale) + }); + + // Used 2 translates instead of transform-origin because of a very strange bug - + // it didn't count the origin on the first touch-zoom but worked correctly afterwards + + map._tileBg.style[L.DomUtil.TRANSFORM] = + L.DomUtil.getTranslateString(this._delta) + ' ' + + L.DomUtil.getScaleString(this._scale, this._startCenter); + }, + + _onTouchEnd: function () { + if (!this._moved || !this._zooming) { return; } + + var map = this._map; + + this._zooming = false; + L.DomUtil.removeClass(map._mapPane, 'leaflet-touching'); + + L.DomEvent + .off(document, 'touchmove', this._onTouchMove) + .off(document, 'touchend', this._onTouchEnd); + + var origin = this._getScaleOrigin(), + center = map.layerPointToLatLng(origin), + + oldZoom = map.getZoom(), + floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom, + roundZoomDelta = (floatZoomDelta > 0 ? + Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)), + + zoom = map._limitZoom(oldZoom + roundZoomDelta); + + map.fire('zoomanim', { + center: center, + zoom: zoom + }); + + map._runAnimation(center, zoom, map.getZoomScale(zoom) / this._scale, origin, true); + }, + + _getScaleOrigin: function () { + var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale); + return this._startCenter.add(centerOffset); + } +}); + +L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom); + + +/* + * L.Handler.ShiftDragZoom is used to add shift-drag zoom interaction to the map + * (zoom to a selected bounding box), enabled by default. + */ + +L.Map.mergeOptions({ + boxZoom: true +}); + +L.Map.BoxZoom = L.Handler.extend({ + initialize: function (map) { + this._map = map; + this._container = map._container; + this._pane = map._panes.overlayPane; + }, + + addHooks: function () { + L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this); + }, + + removeHooks: function () { + L.DomEvent.off(this._container, 'mousedown', this._onMouseDown); + }, + + _onMouseDown: function (e) { + if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } + + L.DomUtil.disableTextSelection(); + + this._startLayerPoint = this._map.mouseEventToLayerPoint(e); + + this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane); + L.DomUtil.setPosition(this._box, this._startLayerPoint); + + //TODO refactor: move cursor to styles + this._container.style.cursor = 'crosshair'; + + L.DomEvent + .on(document, 'mousemove', this._onMouseMove, this) + .on(document, 'mouseup', this._onMouseUp, this) + .preventDefault(e); + + this._map.fire("boxzoomstart"); + }, + + _onMouseMove: function (e) { + var startPoint = this._startLayerPoint, + box = this._box, + + layerPoint = this._map.mouseEventToLayerPoint(e), + offset = layerPoint.subtract(startPoint), + + newPos = new L.Point( + Math.min(layerPoint.x, startPoint.x), + Math.min(layerPoint.y, startPoint.y)); + + L.DomUtil.setPosition(box, newPos); + + // TODO refactor: remove hardcoded 4 pixels + box.style.width = (Math.max(0, Math.abs(offset.x) - 4)) + 'px'; + box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px'; + }, + + _onMouseUp: function (e) { + this._pane.removeChild(this._box); + this._container.style.cursor = ''; + + L.DomUtil.enableTextSelection(); + + L.DomEvent + .off(document, 'mousemove', this._onMouseMove) + .off(document, 'mouseup', this._onMouseUp); + + var map = this._map, + layerPoint = map.mouseEventToLayerPoint(e); + + if (this._startLayerPoint.equals(layerPoint)) { return; } + + var bounds = new L.LatLngBounds( + map.layerPointToLatLng(this._startLayerPoint), + map.layerPointToLatLng(layerPoint)); + + map.fitBounds(bounds); + + map.fire("boxzoomend", { + boxZoomBounds: bounds + }); + } +}); + +L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom); + + +/* + * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. + */ + +L.Map.mergeOptions({ + keyboard: true, + keyboardPanOffset: 80, + keyboardZoomOffset: 1 +}); + +L.Map.Keyboard = L.Handler.extend({ + + keyCodes: { + left: [37], + right: [39], + down: [40], + up: [38], + zoomIn: [187, 107, 61], + zoomOut: [189, 109, 173] + }, + + initialize: function (map) { + this._map = map; + + this._setPanOffset(map.options.keyboardPanOffset); + this._setZoomOffset(map.options.keyboardZoomOffset); + }, + + addHooks: function () { + var container = this._map._container; + + // make the container focusable by tabbing + if (container.tabIndex === -1) { + container.tabIndex = "0"; + } + + L.DomEvent + .on(container, 'focus', this._onFocus, this) + .on(container, 'blur', this._onBlur, this) + .on(container, 'mousedown', this._onMouseDown, this); + + this._map + .on('focus', this._addHooks, this) + .on('blur', this._removeHooks, this); + }, + + removeHooks: function () { + this._removeHooks(); + + var container = this._map._container; + + L.DomEvent + .off(container, 'focus', this._onFocus, this) + .off(container, 'blur', this._onBlur, this) + .off(container, 'mousedown', this._onMouseDown, this); + + this._map + .off('focus', this._addHooks, this) + .off('blur', this._removeHooks, this); + }, + + _onMouseDown: function () { + if (!this._focused) { + this._map._container.focus(); + } + }, + + _onFocus: function () { + this._focused = true; + this._map.fire('focus'); + }, + + _onBlur: function () { + this._focused = false; + this._map.fire('blur'); + }, + + _setPanOffset: function (pan) { + var keys = this._panKeys = {}, + codes = this.keyCodes, + i, len; + + for (i = 0, len = codes.left.length; i < len; i++) { + keys[codes.left[i]] = [-1 * pan, 0]; + } + for (i = 0, len = codes.right.length; i < len; i++) { + keys[codes.right[i]] = [pan, 0]; + } + for (i = 0, len = codes.down.length; i < len; i++) { + keys[codes.down[i]] = [0, pan]; + } + for (i = 0, len = codes.up.length; i < len; i++) { + keys[codes.up[i]] = [0, -1 * pan]; + } + }, + + _setZoomOffset: function (zoom) { + var keys = this._zoomKeys = {}, + codes = this.keyCodes, + i, len; + + for (i = 0, len = codes.zoomIn.length; i < len; i++) { + keys[codes.zoomIn[i]] = zoom; + } + for (i = 0, len = codes.zoomOut.length; i < len; i++) { + keys[codes.zoomOut[i]] = -zoom; + } + }, + + _addHooks: function () { + L.DomEvent.on(document, 'keydown', this._onKeyDown, this); + }, + + _removeHooks: function () { + L.DomEvent.off(document, 'keydown', this._onKeyDown, this); + }, + + _onKeyDown: function (e) { + var key = e.keyCode, + map = this._map; + + if (this._panKeys.hasOwnProperty(key)) { + map.panBy(this._panKeys[key]); + + if (map.options.maxBounds) { + map.panInsideBounds(map.options.maxBounds); + } + + } else if (this._zoomKeys.hasOwnProperty(key)) { + map.setZoom(map.getZoom() + this._zoomKeys[key]); + + } else { + return; + } + + L.DomEvent.stop(e); + } +}); + +L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard); + + +/* + * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. + */ + +L.Handler.MarkerDrag = L.Handler.extend({ + initialize: function (marker) { + this._marker = marker; + }, + + addHooks: function () { + var icon = this._marker._icon; + if (!this._draggable) { + this._draggable = new L.Draggable(icon, icon) + .on('dragstart', this._onDragStart, this) + .on('drag', this._onDrag, this) + .on('dragend', this._onDragEnd, this); + } + this._draggable.enable(); + }, + + removeHooks: function () { + this._draggable.disable(); + }, + + moved: function () { + return this._draggable && this._draggable._moved; + }, + + _onDragStart: function () { + this._marker + .closePopup() + .fire('movestart') + .fire('dragstart'); + }, + + _onDrag: function () { + var marker = this._marker, + shadow = marker._shadow, + iconPos = L.DomUtil.getPosition(marker._icon), + latlng = marker._map.layerPointToLatLng(iconPos); + + // update shadow position + if (shadow) { + L.DomUtil.setPosition(shadow, iconPos); + } + + marker._latlng = latlng; + + marker + .fire('move', {latlng: latlng}) + .fire('drag'); + }, + + _onDragEnd: function () { + this._marker + .fire('moveend') + .fire('dragend'); + } +}); + + +/* + * L.Handler.PolyEdit is an editing handler for polylines and polygons. + */ + +L.Handler.PolyEdit = L.Handler.extend({ + options: { + icon: new L.DivIcon({ + iconSize: new L.Point(8, 8), + className: 'leaflet-div-icon leaflet-editing-icon' + }) + }, + + initialize: function (poly, options) { + this._poly = poly; + L.setOptions(this, options); + }, + + addHooks: function () { + if (this._poly._map) { + if (!this._markerGroup) { + this._initMarkers(); + } + this._poly._map.addLayer(this._markerGroup); + } + }, + + removeHooks: function () { + if (this._poly._map) { + this._poly._map.removeLayer(this._markerGroup); + delete this._markerGroup; + delete this._markers; + } + }, + + updateMarkers: function () { + this._markerGroup.clearLayers(); + this._initMarkers(); + }, + + _initMarkers: function () { + if (!this._markerGroup) { + this._markerGroup = new L.LayerGroup(); + } + this._markers = []; + + var latlngs = this._poly._latlngs, + i, j, len, marker; + + // TODO refactor holes implementation in Polygon to support it here + + for (i = 0, len = latlngs.length; i < len; i++) { + + marker = this._createMarker(latlngs[i], i); + marker.on('click', this._onMarkerClick, this); + this._markers.push(marker); + } + + var markerLeft, markerRight; + + for (i = 0, j = len - 1; i < len; j = i++) { + if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) { + continue; + } + + markerLeft = this._markers[j]; + markerRight = this._markers[i]; + + this._createMiddleMarker(markerLeft, markerRight); + this._updatePrevNext(markerLeft, markerRight); + } + }, + + _createMarker: function (latlng, index) { + var marker = new L.Marker(latlng, { + draggable: true, + icon: this.options.icon + }); + + marker._origLatLng = latlng; + marker._index = index; + + marker.on('drag', this._onMarkerDrag, this); + marker.on('dragend', this._fireEdit, this); + + this._markerGroup.addLayer(marker); + + return marker; + }, + + _fireEdit: function () { + this._poly.fire('edit'); + }, + + _onMarkerDrag: function (e) { + var marker = e.target; + + L.extend(marker._origLatLng, marker._latlng); + + if (marker._middleLeft) { + marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker)); + } + if (marker._middleRight) { + marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next)); + } + + this._poly.redraw(); + }, + + _onMarkerClick: function (e) { + // we want to remove the marker on click, but if latlng count < 3, polyline would be invalid + if (this._poly._latlngs.length < 3) { return; } + + var marker = e.target, + i = marker._index; + + // remove the marker + this._markerGroup.removeLayer(marker); + this._markers.splice(i, 1); + this._poly.spliceLatLngs(i, 1); + this._updateIndexes(i, -1); + + // update prev/next links of adjacent markers + this._updatePrevNext(marker._prev, marker._next); + + // remove ghost markers near the removed marker + if (marker._middleLeft) { + this._markerGroup.removeLayer(marker._middleLeft); + } + if (marker._middleRight) { + this._markerGroup.removeLayer(marker._middleRight); + } + + // create a ghost marker in place of the removed one + if (marker._prev && marker._next) { + this._createMiddleMarker(marker._prev, marker._next); + + } else if (!marker._prev) { + marker._next._middleLeft = null; + + } else if (!marker._next) { + marker._prev._middleRight = null; + } + + this._poly.fire('edit'); + }, + + _updateIndexes: function (index, delta) { + this._markerGroup.eachLayer(function (marker) { + if (marker._index > index) { + marker._index += delta; + } + }); + }, + + _createMiddleMarker: function (marker1, marker2) { + var latlng = this._getMiddleLatLng(marker1, marker2), + marker = this._createMarker(latlng), + onClick, + onDragStart, + onDragEnd; + + marker.setOpacity(0.6); + + marker1._middleRight = marker2._middleLeft = marker; + + onDragStart = function () { + var i = marker2._index; + + marker._index = i; + + marker + .off('click', onClick) + .on('click', this._onMarkerClick, this); + + latlng.lat = marker.getLatLng().lat; + latlng.lng = marker.getLatLng().lng; + this._poly.spliceLatLngs(i, 0, latlng); + this._markers.splice(i, 0, marker); + + marker.setOpacity(1); + + this._updateIndexes(i, 1); + marker2._index++; + this._updatePrevNext(marker1, marker); + this._updatePrevNext(marker, marker2); + }; + + onDragEnd = function () { + marker.off('dragstart', onDragStart, this); + marker.off('dragend', onDragEnd, this); + + this._createMiddleMarker(marker1, marker); + this._createMiddleMarker(marker, marker2); + }; + + onClick = function () { + onDragStart.call(this); + onDragEnd.call(this); + this._poly.fire('edit'); + }; + + marker + .on('click', onClick, this) + .on('dragstart', onDragStart, this) + .on('dragend', onDragEnd, this); + + this._markerGroup.addLayer(marker); + }, + + _updatePrevNext: function (marker1, marker2) { + if (marker1) { + marker1._next = marker2; + } + if (marker2) { + marker2._prev = marker1; + } + }, + + _getMiddleLatLng: function (marker1, marker2) { + var map = this._poly._map, + p1 = map.latLngToLayerPoint(marker1.getLatLng()), + p2 = map.latLngToLayerPoint(marker2.getLatLng()); + + return map.layerPointToLatLng(p1._add(p2)._divideBy(2)); + } +}); + +L.Polyline.addInitHook(function () { + + if (L.Handler.PolyEdit) { + this.editing = new L.Handler.PolyEdit(this); + + if (this.options.editable) { + this.editing.enable(); + } + } + + this.on('add', function () { + if (this.editing && this.editing.enabled()) { + this.editing.addHooks(); + } + }); + + this.on('remove', function () { + if (this.editing && this.editing.enabled()) { + this.editing.removeHooks(); + } + }); +}); + + +/* + * L.Control is a base class for implementing map controls. Handles positioning. + * All other controls extend from this class. + */ + +L.Control = L.Class.extend({ + options: { + position: 'topright' + }, + + initialize: function (options) { + L.setOptions(this, options); + }, + + getPosition: function () { + return this.options.position; + }, + + setPosition: function (position) { + var map = this._map; + + if (map) { + map.removeControl(this); + } + + this.options.position = position; + + if (map) { + map.addControl(this); + } + + return this; + }, + + addTo: function (map) { + this._map = map; + + var container = this._container = this.onAdd(map), + pos = this.getPosition(), + corner = map._controlCorners[pos]; + + L.DomUtil.addClass(container, 'leaflet-control'); + + if (pos.indexOf('bottom') !== -1) { + corner.insertBefore(container, corner.firstChild); + } else { + corner.appendChild(container); + } + + return this; + }, + + removeFrom: function (map) { + var pos = this.getPosition(), + corner = map._controlCorners[pos]; + + corner.removeChild(this._container); + this._map = null; + + if (this.onRemove) { + this.onRemove(map); + } + + return this; + } +}); + +L.control = function (options) { + return new L.Control(options); +}; + + +/* + * Adds control-related methods to L.Map. + */ + +L.Map.include({ + addControl: function (control) { + control.addTo(this); + return this; + }, + + removeControl: function (control) { + control.removeFrom(this); + return this; + }, + + _initControlPos: function () { + var corners = this._controlCorners = {}, + l = 'leaflet-', + container = this._controlContainer = + L.DomUtil.create('div', l + 'control-container', this._container); + + function createCorner(vSide, hSide) { + var className = l + vSide + ' ' + l + hSide; + + corners[vSide + hSide] = L.DomUtil.create('div', className, container); + } + + createCorner('top', 'left'); + createCorner('top', 'right'); + createCorner('bottom', 'left'); + createCorner('bottom', 'right'); + } +}); + + +/* + * L.Control.Zoom is used for the default zoom buttons on the map. + */ + +L.Control.Zoom = L.Control.extend({ + options: { + position: 'topleft' + }, + + onAdd: function (map) { + var zoomName = 'leaflet-control-zoom', + barName = 'leaflet-bar', + partName = barName + '-part', + container = L.DomUtil.create('div', zoomName + ' ' + barName); + + this._map = map; + + this._zoomInButton = this._createButton('+', 'Zoom in', + zoomName + '-in ' + + partName + ' ' + + partName + '-top', + container, this._zoomIn, this); + + this._zoomOutButton = this._createButton('-', 'Zoom out', + zoomName + '-out ' + + partName + ' ' + + partName + '-bottom', + container, this._zoomOut, this); + + map.on('zoomend', this._updateDisabled, this); + + return container; + }, + + onRemove: function (map) { + map.off('zoomend', this._updateDisabled, this); + }, + + _zoomIn: function (e) { + this._map.zoomIn(e.shiftKey ? 3 : 1); + }, + + _zoomOut: function (e) { + this._map.zoomOut(e.shiftKey ? 3 : 1); + }, + + _createButton: function (html, title, className, container, fn, context) { + var link = L.DomUtil.create('a', className, container); + link.innerHTML = html; + link.href = '#'; + link.title = title; + + var stop = L.DomEvent.stopPropagation; + + L.DomEvent + .on(link, 'click', stop) + .on(link, 'mousedown', stop) + .on(link, 'dblclick', stop) + .on(link, 'click', L.DomEvent.preventDefault) + .on(link, 'click', fn, context); + + return link; + }, + + _updateDisabled: function () { + var map = this._map, + className = 'leaflet-control-zoom-disabled'; + + L.DomUtil.removeClass(this._zoomInButton, className); + L.DomUtil.removeClass(this._zoomOutButton, className); + + if (map._zoom === map.getMinZoom()) { + L.DomUtil.addClass(this._zoomOutButton, className); + } + if (map._zoom === map.getMaxZoom()) { + L.DomUtil.addClass(this._zoomInButton, className); + } + } +}); + +L.Map.mergeOptions({ + zoomControl: true +}); + +L.Map.addInitHook(function () { + if (this.options.zoomControl) { + this.zoomControl = new L.Control.Zoom(); + this.addControl(this.zoomControl); + } +}); + +L.control.zoom = function (options) { + return new L.Control.Zoom(options); +}; + + + +/* + * L.Control.Attribution is used for displaying attribution on the map (added by default). + */ + +L.Control.Attribution = L.Control.extend({ + options: { + position: 'bottomright', + prefix: 'Powered by Leaflet' + }, + + initialize: function (options) { + L.setOptions(this, options); + + this._attributions = {}; + }, + + onAdd: function (map) { + this._container = L.DomUtil.create('div', 'leaflet-control-attribution'); + L.DomEvent.disableClickPropagation(this._container); + + map + .on('layeradd', this._onLayerAdd, this) + .on('layerremove', this._onLayerRemove, this); + + this._update(); + + return this._container; + }, + + onRemove: function (map) { + map + .off('layeradd', this._onLayerAdd) + .off('layerremove', this._onLayerRemove); + + }, + + setPrefix: function (prefix) { + this.options.prefix = prefix; + this._update(); + return this; + }, + + addAttribution: function (text) { + if (!text) { return; } + + if (!this._attributions[text]) { + this._attributions[text] = 0; + } + this._attributions[text]++; + + this._update(); + + return this; + }, + + removeAttribution: function (text) { + if (!text) { return; } + + this._attributions[text]--; + this._update(); + + return this; + }, + + _update: function () { + if (!this._map) { return; } + + var attribs = []; + + for (var i in this._attributions) { + if (this._attributions.hasOwnProperty(i) && this._attributions[i]) { + attribs.push(i); + } + } + + var prefixAndAttribs = []; + + if (this.options.prefix) { + prefixAndAttribs.push(this.options.prefix); + } + if (attribs.length) { + prefixAndAttribs.push(attribs.join(', ')); + } + + this._container.innerHTML = prefixAndAttribs.join(' — '); + }, + + _onLayerAdd: function (e) { + if (e.layer.getAttribution) { + this.addAttribution(e.layer.getAttribution()); + } + }, + + _onLayerRemove: function (e) { + if (e.layer.getAttribution) { + this.removeAttribution(e.layer.getAttribution()); + } + } +}); + +L.Map.mergeOptions({ + attributionControl: true +}); + +L.Map.addInitHook(function () { + if (this.options.attributionControl) { + this.attributionControl = (new L.Control.Attribution()).addTo(this); + } +}); + +L.control.attribution = function (options) { + return new L.Control.Attribution(options); +}; + + +/* + * L.Control.Scale is used for displaying metric/imperial scale on the map. + */ + +L.Control.Scale = L.Control.extend({ + options: { + position: 'bottomleft', + maxWidth: 100, + metric: true, + imperial: true, + updateWhenIdle: false + }, + + onAdd: function (map) { + this._map = map; + + var className = 'leaflet-control-scale', + container = L.DomUtil.create('div', className), + options = this.options; + + this._addScales(options, className, container); + + map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); + map.whenReady(this._update, this); + + return container; + }, + + onRemove: function (map) { + map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); + }, + + _addScales: function (options, className, container) { + if (options.metric) { + this._mScale = L.DomUtil.create('div', className + '-line', container); + } + if (options.imperial) { + this._iScale = L.DomUtil.create('div', className + '-line', container); + } + }, + + _update: function () { + var bounds = this._map.getBounds(), + centerLat = bounds.getCenter().lat, + halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180), + dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180, + + size = this._map.getSize(), + options = this.options, + maxMeters = 0; + + if (size.x > 0) { + maxMeters = dist * (options.maxWidth / size.x); + } + + this._updateScales(options, maxMeters); + }, + + _updateScales: function (options, maxMeters) { + if (options.metric && maxMeters) { + this._updateMetric(maxMeters); + } + + if (options.imperial && maxMeters) { + this._updateImperial(maxMeters); + } + }, + + _updateMetric: function (maxMeters) { + var meters = this._getRoundNum(maxMeters); + + this._mScale.style.width = this._getScaleWidth(meters / maxMeters) + 'px'; + this._mScale.innerHTML = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; + }, + + _updateImperial: function (maxMeters) { + var maxFeet = maxMeters * 3.2808399, + scale = this._iScale, + maxMiles, miles, feet; + + if (maxFeet > 5280) { + maxMiles = maxFeet / 5280; + miles = this._getRoundNum(maxMiles); + + scale.style.width = this._getScaleWidth(miles / maxMiles) + 'px'; + scale.innerHTML = miles + ' mi'; + + } else { + feet = this._getRoundNum(maxFeet); + + scale.style.width = this._getScaleWidth(feet / maxFeet) + 'px'; + scale.innerHTML = feet + ' ft'; + } + }, + + _getScaleWidth: function (ratio) { + return Math.round(this.options.maxWidth * ratio) - 10; + }, + + _getRoundNum: function (num) { + var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), + d = num / pow10; + + d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1; + + return pow10 * d; + } +}); + +L.control.scale = function (options) { + return new L.Control.Scale(options); +}; + + +/* + * L.Control.Layers is a control to allow users to switch between different layers on the map. + */ + +L.Control.Layers = L.Control.extend({ + options: { + collapsed: true, + position: 'topright', + autoZIndex: true + }, + + initialize: function (baseLayers, overlays, options) { + L.setOptions(this, options); + + this._layers = {}; + this._lastZIndex = 0; + this._handlingClick = false; + + for (var i in baseLayers) { + if (baseLayers.hasOwnProperty(i)) { + this._addLayer(baseLayers[i], i); + } + } + + for (i in overlays) { + if (overlays.hasOwnProperty(i)) { + this._addLayer(overlays[i], i, true); + } + } + }, + + onAdd: function (map) { + this._initLayout(); + this._update(); + + map + .on('layeradd', this._onLayerChange, this) + .on('layerremove', this._onLayerChange, this); + + return this._container; + }, + + onRemove: function (map) { + map + .off('layeradd', this._onLayerChange) + .off('layerremove', this._onLayerChange); + }, + + addBaseLayer: function (layer, name) { + this._addLayer(layer, name); + this._update(); + return this; + }, + + addOverlay: function (layer, name) { + this._addLayer(layer, name, true); + this._update(); + return this; + }, + + removeLayer: function (layer) { + var id = L.stamp(layer); + delete this._layers[id]; + this._update(); + return this; + }, + + _initLayout: function () { + var className = 'leaflet-control-layers', + container = this._container = L.DomUtil.create('div', className); + + if (!L.Browser.touch) { + L.DomEvent.disableClickPropagation(container); + L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation); + } else { + L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation); + } + + var form = this._form = L.DomUtil.create('form', className + '-list'); + + if (this.options.collapsed) { + L.DomEvent + .on(container, 'mouseover', this._expand, this) + .on(container, 'mouseout', this._collapse, this); + + var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container); + link.href = '#'; + link.title = 'Layers'; + + if (L.Browser.touch) { + L.DomEvent + .on(link, 'click', L.DomEvent.stopPropagation) + .on(link, 'click', L.DomEvent.preventDefault) + .on(link, 'click', this._expand, this); + } + else { + L.DomEvent.on(link, 'focus', this._expand, this); + } + + this._map.on('movestart', this._collapse, this); + // TODO keyboard accessibility + } else { + this._expand(); + } + + this._baseLayersList = L.DomUtil.create('div', className + '-base', form); + this._separator = L.DomUtil.create('div', className + '-separator', form); + this._overlaysList = L.DomUtil.create('div', className + '-overlays', form); + + container.appendChild(form); + }, + + _addLayer: function (layer, name, overlay) { + var id = L.stamp(layer); + + this._layers[id] = { + layer: layer, + name: name, + overlay: overlay + }; + + if (this.options.autoZIndex && layer.setZIndex) { + this._lastZIndex++; + layer.setZIndex(this._lastZIndex); + } + }, + + _update: function () { + if (!this._container) { + return; + } + + this._baseLayersList.innerHTML = ''; + this._overlaysList.innerHTML = ''; + + var baseLayersPresent = false, + overlaysPresent = false; + + for (var i in this._layers) { + if (this._layers.hasOwnProperty(i)) { + var obj = this._layers[i]; + this._addItem(obj); + overlaysPresent = overlaysPresent || obj.overlay; + baseLayersPresent = baseLayersPresent || !obj.overlay; + } + } + + this._separator.style.display = (overlaysPresent && baseLayersPresent ? '' : 'none'); + }, + + _onLayerChange: function (e) { + var id = L.stamp(e.layer); + + if (this._layers[id] && !this._handlingClick) { + this._update(); + } + }, + + // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) + _createRadioElement: function (name, checked) { + + var radioHtml = ' 0.5 && + this._getLoadedTilesPercentage(tilePane) < 0.5) { + + tilePane.style.visibility = 'hidden'; + tilePane.empty = true; + this._stopLoadingImages(tilePane); + return; + } + + if (!tileBg) { + tileBg = this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane); + tileBg.style.zIndex = 1; + } + + // prepare the background pane to become the main tile pane + tileBg.style[L.DomUtil.TRANSFORM] = ''; + tileBg.style.visibility = 'hidden'; + + // tells tile layers to reinitialize their containers + tileBg.empty = true; //new FG + tilePane.empty = false; //new BG + + //Switch out the current layer to be the new bg layer (And vice-versa) + this._tilePane = this._panes.tilePane = tileBg; + var newTileBg = this._tileBg = tilePane; + + L.DomUtil.addClass(newTileBg, 'leaflet-zoom-animated'); + + this._stopLoadingImages(newTileBg); + }, + + _getLoadedTilesPercentage: function (container) { + var tiles = container.getElementsByTagName('img'), + i, len, count = 0; + + for (i = 0, len = tiles.length; i < len; i++) { + if (tiles[i].complete) { + count++; + } + } + return count / len; + }, + + // stops loading all tiles in the background layer + _stopLoadingImages: function (container) { + var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')), + i, len, tile; + + for (i = 0, len = tiles.length; i < len; i++) { + tile = tiles[i]; + + if (!tile.complete) { + tile.onload = L.Util.falseFn; + tile.onerror = L.Util.falseFn; + tile.src = L.Util.emptyImageUrl; + + tile.parentNode.removeChild(tile); + } + } + }, + + _onZoomTransitionEnd: function () { + this._restoreTileFront(); + + L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim'); + L.Util.falseFn(this._tileBg.offsetWidth); // force reflow + this._animatingZoom = false; + this._resetView(this._animateToCenter, this._animateToZoom, true, true); + + if (L.Draggable) { + L.Draggable._disabled = false; + } + }, + + _restoreTileFront: function () { + this._tilePane.innerHTML = ''; + this._tilePane.style.visibility = ''; + this._tilePane.style.zIndex = 2; + this._tileBg.style.zIndex = 1; + }, + + _clearTileBg: function () { + if (!this._animatingZoom && !this.touchZoom._zooming) { + this._tileBg.innerHTML = ''; + } + } +}); + + +/* + * Provides L.Map with convenient shortcuts for using browser geolocation features. + */ + +L.Map.include({ + _defaultLocateOptions: { + watch: false, + setView: false, + maxZoom: Infinity, + timeout: 10000, + maximumAge: 0, + enableHighAccuracy: false + }, + + locate: function (/*Object*/ options) { + + options = this._locationOptions = L.extend(this._defaultLocateOptions, options); + + if (!navigator.geolocation) { + this._handleGeolocationError({ + code: 0, + message: "Geolocation not supported." + }); + return this; + } + + var onResponse = L.bind(this._handleGeolocationResponse, this), + onError = L.bind(this._handleGeolocationError, this); + + if (options.watch) { + this._locationWatchId = + navigator.geolocation.watchPosition(onResponse, onError, options); + } else { + navigator.geolocation.getCurrentPosition(onResponse, onError, options); + } + return this; + }, + + stopLocate: function () { + if (navigator.geolocation) { + navigator.geolocation.clearWatch(this._locationWatchId); + } + return this; + }, + + _handleGeolocationError: function (error) { + var c = error.code, + message = error.message || + (c === 1 ? "permission denied" : + (c === 2 ? "position unavailable" : "timeout")); + + if (this._locationOptions.setView && !this._loaded) { + this.fitWorld(); + } + + this.fire('locationerror', { + code: c, + message: "Geolocation error: " + message + "." + }); + }, + + _handleGeolocationResponse: function (pos) { + var latAccuracy = 180 * pos.coords.accuracy / 4e7, + lngAccuracy = latAccuracy * 2, + + lat = pos.coords.latitude, + lng = pos.coords.longitude, + latlng = new L.LatLng(lat, lng), + + sw = new L.LatLng(lat - latAccuracy, lng - lngAccuracy), + ne = new L.LatLng(lat + latAccuracy, lng + lngAccuracy), + bounds = new L.LatLngBounds(sw, ne), + + options = this._locationOptions; + + if (options.setView) { + var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom); + this.setView(latlng, zoom); + } + + this.fire('locationfound', { + latlng: latlng, + bounds: bounds, + accuracy: pos.coords.accuracy + }); + } +}); + + +}(this, document)); \ No newline at end of file diff --git a/Research/ciao.urca.tv/leaflet-hash/package.json b/Research/ciao.urca.tv/leaflet-hash/package.json new file mode 100644 index 0000000..c684839 --- /dev/null +++ b/Research/ciao.urca.tv/leaflet-hash/package.json @@ -0,0 +1,20 @@ +{ + "author": "Michael Lawrence Evans", + "name": "leaflet-hash", + "description": "linkable location hashes for leaflet", + "version": "0.2.1", + "homepage": "https://github.com/mlevans/leaflet-hash", + "repository": { + "type": "git", + "url": "https://github.com/mlevans/leaflet-hash.git" + }, + "devDependencies": { + "mocha": "~1.8", + "expect.js": "~0.2.0" + }, + "main": "leaflet-hash.js", + "optionalDependencies": {}, + "engines": { + "node": "*" + } +} diff --git a/Research/ciao.urca.tv/leaflet-hash/screenshots/screenshot.png b/Research/ciao.urca.tv/leaflet-hash/screenshots/screenshot.png new file mode 100644 index 0000000..500d00c Binary files /dev/null and b/Research/ciao.urca.tv/leaflet-hash/screenshots/screenshot.png differ diff --git a/Research/ciao.urca.tv/leaflet-hash/test/index.html b/Research/ciao.urca.tv/leaflet-hash/test/index.html new file mode 100644 index 0000000..c4b1dcd --- /dev/null +++ b/Research/ciao.urca.tv/leaflet-hash/test/index.html @@ -0,0 +1,24 @@ + + + + Mocha Tests + + + +
+ + + + + + + + + + + + + + diff --git a/Research/ciao.urca.tv/leaflet-hash/test/spec/hash.js b/Research/ciao.urca.tv/leaflet-hash/test/spec/hash.js new file mode 100644 index 0000000..4360fd9 --- /dev/null +++ b/Research/ciao.urca.tv/leaflet-hash/test/spec/hash.js @@ -0,0 +1,68 @@ +describe("L.Hash", function() { + + var map; + + mocha.ignoreLeaks(); + + beforeEach(function() { + map = new L.Map(document.createElement('div')); + }); + + it('sets a hash when the map is moved', function() { + var hash = L.hash(map); + map.setView([51.505, -0.09], 13); + expect(location.hash).to.be('#13/51.5050/-0.0900'); + }); + + it('uses a hash set initially on the page', function(done) { + location.hash = '#13/10/40'; + var hash = L.hash(map); + window.setTimeout(function() { + expect(Math.round(map.getCenter().lat)).to.be(10); + expect(Math.round(map.getCenter().lng)).to.be(40); + done(); + }, 200); + }); + + it('responds to a hash change after an initial hash is set', function(done) { + map.setView([51.505, -0.09], 13); + location.hash = '#13/20/40'; + var hash = L.hash(map); + window.setTimeout(function() { + expect(Math.round(map.getCenter().lat)).to.be(20); + expect(Math.round(map.getCenter().lng)).to.be(40); + done(); + }, 200); + }); + + it('does not acknowledge a junk hash', function(done) { + var hash = L.hash(map); + map.setView([51, 2], 13); + location.hash = '#foo'; + window.setTimeout(function() { + expect(Math.round(map.getCenter().lat)).to.eql(51); + expect(Math.round(map.getCenter().lng)).to.eql(2); + done(); + }, 200); + }); + + it('unbinds events when removed', function() { + location.hash = ''; + var hash = L.hash(map); + map.removeControl(hash); + map.setView([51.505, -0.09], 13); + expect(location.hash).to.be(''); + }); + + it('parses a hash', function() { + var parsed = L.Hash.parseHash('#13/20/40'); + expect(parsed.zoom).to.be(13); + expect(parsed.center).to.be.a(L.LatLng); + expect(parsed.center).to.eql({lat: 20, lng: 40}); + }); + + it('formats a hash', function() { + map.setView([51, 2], 13); + expect(L.Hash.formatHash(map)).to.be('#13/51.0000/2.0000'); + }); +}); diff --git a/Research/ciao.urca.tv/leaflet/images/layers-2x.png b/Research/ciao.urca.tv/leaflet/images/layers-2x.png new file mode 100644 index 0000000..200c333 Binary files /dev/null and b/Research/ciao.urca.tv/leaflet/images/layers-2x.png differ diff --git a/Research/ciao.urca.tv/leaflet/images/layers.png b/Research/ciao.urca.tv/leaflet/images/layers.png new file mode 100644 index 0000000..1a72e57 Binary files /dev/null and b/Research/ciao.urca.tv/leaflet/images/layers.png differ diff --git a/Research/ciao.urca.tv/leaflet/images/marker-icon-2x.png b/Research/ciao.urca.tv/leaflet/images/marker-icon-2x.png new file mode 100644 index 0000000..88f9e50 Binary files /dev/null and b/Research/ciao.urca.tv/leaflet/images/marker-icon-2x.png differ diff --git a/Research/ciao.urca.tv/leaflet/images/marker-icon.png b/Research/ciao.urca.tv/leaflet/images/marker-icon.png new file mode 100644 index 0000000..950edf2 Binary files /dev/null and b/Research/ciao.urca.tv/leaflet/images/marker-icon.png differ diff --git a/Research/ciao.urca.tv/leaflet/images/marker-shadow.png b/Research/ciao.urca.tv/leaflet/images/marker-shadow.png new file mode 100644 index 0000000..9fd2979 Binary files /dev/null and b/Research/ciao.urca.tv/leaflet/images/marker-shadow.png differ diff --git a/Research/ciao.urca.tv/leaflet/leaflet-src.esm.js b/Research/ciao.urca.tv/leaflet/leaflet-src.esm.js new file mode 100644 index 0000000..f8b161b --- /dev/null +++ b/Research/ciao.urca.tv/leaflet/leaflet-src.esm.js @@ -0,0 +1,13838 @@ +/* @preserve + * Leaflet 1.4.0+Detached: 3337f36d2a2d2b33946779057619b31f674ff5dc.3337f36, a JS library for interactive maps. http://leafletjs.com + * (c) 2010-2018 Vladimir Agafonkin, (c) 2010-2011 CloudMade + */ + +var version = "1.4.0+HEAD.3337f36"; + +/* + * @namespace Util + * + * Various utility functions, used by Leaflet internally. + */ + +var freeze = Object.freeze; +Object.freeze = function (obj) { return obj; }; + +// @function extend(dest: Object, src?: Object): Object +// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. +function extend(dest) { + var i, j, len, src; + + for (j = 1, len = arguments.length; j < len; j++) { + src = arguments[j]; + for (i in src) { + dest[i] = src[i]; + } + } + return dest; +} + +// @function create(proto: Object, properties?: Object): Object +// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) +var create = Object.create || (function () { + function F() {} + return function (proto) { + F.prototype = proto; + return new F(); + }; +})(); + +// @function bind(fn: Function, 
): Function +// Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). +// Has a `L.bind()` shortcut. +function bind(fn, obj) { + var slice = Array.prototype.slice; + + if (fn.bind) { + return fn.bind.apply(fn, slice.call(arguments, 1)); + } + + var args = slice.call(arguments, 2); + + return function () { + return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); + }; +} + +// @property lastId: Number +// Last unique ID used by [`stamp()`](#util-stamp) +var lastId = 0; + +// @function stamp(obj: Object): Number +// Returns the unique ID of an object, assigning it one if it doesn't have it. +function stamp(obj) { + /*eslint-disable */ + obj._leaflet_id = obj._leaflet_id || ++lastId; + return obj._leaflet_id; + /* eslint-enable */ +} + +// @function throttle(fn: Function, time: Number, context: Object): Function +// Returns a function which executes function `fn` with the given scope `context` +// (so that the `this` keyword refers to `context` inside `fn`'s code). The function +// `fn` will be called no more than one time per given amount of `time`. The arguments +// received by the bound function will be any arguments passed when binding the +// function, followed by any arguments passed when invoking the bound function. +// Has an `L.throttle` shortcut. +function throttle(fn, time, context) { + var lock, args, wrapperFn, later; + + later = function () { + // reset lock and call if queued + lock = false; + if (args) { + wrapperFn.apply(context, args); + args = false; + } + }; + + wrapperFn = function () { + if (lock) { + // called too soon, queue to call later + args = arguments; + + } else { + // call and lock until later + fn.apply(context, arguments); + setTimeout(later, time); + lock = true; + } + }; + + return wrapperFn; +} + +// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number +// Returns the number `num` modulo `range` in such a way so it lies within +// `range[0]` and `range[1]`. The returned value will be always smaller than +// `range[1]` unless `includeMax` is set to `true`. +function wrapNum(x, range, includeMax) { + var max = range[1], + min = range[0], + d = max - min; + return x === max && includeMax ? x : ((x - min) % d + d) % d + min; +} + +// @function falseFn(): Function +// Returns a function which always returns `false`. +function falseFn() { return false; } + +// @function formatNum(num: Number, digits?: Number): Number +// Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default. +function formatNum(num, digits) { + var pow = Math.pow(10, (digits === undefined ? 6 : digits)); + return Math.round(num * pow) / pow; +} + +// @function trim(str: String): String +// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) +function trim(str) { + return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); +} + +// @function splitWords(str: String): String[] +// Trims and splits the string on whitespace and returns the array of parts. +function splitWords(str) { + return trim(str).split(/\s+/); +} + +// @function setOptions(obj: Object, options: Object): Object +// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut. +function setOptions(obj, options) { + if (!obj.hasOwnProperty('options')) { + obj.options = obj.options ? create(obj.options) : {}; + } + for (var i in options) { + obj.options[i] = options[i]; + } + return obj.options; +} + +// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String +// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}` +// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will +// be appended at the end. If `uppercase` is `true`, the parameter names will +// be uppercased (e.g. `'?A=foo&B=bar'`) +function getParamString(obj, existingUrl, uppercase) { + var params = []; + for (var i in obj) { + params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); + } + return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); +} + +var templateRe = /\{ *([\w_-]+) *\}/g; + +// @function template(str: String, data: Object): String +// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'` +// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string +// `('Hello foo, bar')`. You can also specify functions instead of strings for +// data values — they will be evaluated passing `data` as an argument. +function template(str, data) { + return str.replace(templateRe, function (str, key) { + var value = data[key]; + + if (value === undefined) { + throw new Error('No value provided for variable ' + str); + + } else if (typeof value === 'function') { + value = value(data); + } + return value; + }); +} + +// @function isArray(obj): Boolean +// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) +var isArray = Array.isArray || function (obj) { + return (Object.prototype.toString.call(obj) === '[object Array]'); +}; + +// @function indexOf(array: Array, el: Object): Number +// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) +function indexOf(array, el) { + for (var i = 0; i < array.length; i++) { + if (array[i] === el) { return i; } + } + return -1; +} + +// @property emptyImageUrl: String +// Data URI string containing a base64-encoded empty GIF image. +// Used as a hack to free memory from unused images on WebKit-powered +// mobile devices (by setting image `src` to this string). +var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; + +// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ + +function getPrefixed(name) { + return window['webkit' + name] || window['moz' + name] || window['ms' + name]; +} + +var lastTime = 0; + +// fallback for IE 7-8 +function timeoutDefer(fn) { + var time = +new Date(), + timeToCall = Math.max(0, 16 - (time - lastTime)); + + lastTime = time + timeToCall; + return window.setTimeout(fn, timeToCall); +} + +var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer; +var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || + getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; + +// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number +// Schedules `fn` to be executed when the browser repaints. `fn` is bound to +// `context` if given. When `immediate` is set, `fn` is called immediately if +// the browser doesn't have native support for +// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame), +// otherwise it's delayed. Returns a request ID that can be used to cancel the request. +function requestAnimFrame(fn, context, immediate) { + if (immediate && requestFn === timeoutDefer) { + fn.call(context); + } else { + return requestFn.call(window, bind(fn, context)); + } +} + +// @function cancelAnimFrame(id: Number): undefined +// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame). +function cancelAnimFrame(id) { + if (id) { + cancelFn.call(window, id); + } +} + + +var Util = (Object.freeze || Object)({ + freeze: freeze, + extend: extend, + create: create, + bind: bind, + lastId: lastId, + stamp: stamp, + throttle: throttle, + wrapNum: wrapNum, + falseFn: falseFn, + formatNum: formatNum, + trim: trim, + splitWords: splitWords, + setOptions: setOptions, + getParamString: getParamString, + template: template, + isArray: isArray, + indexOf: indexOf, + emptyImageUrl: emptyImageUrl, + requestFn: requestFn, + cancelFn: cancelFn, + requestAnimFrame: requestAnimFrame, + cancelAnimFrame: cancelAnimFrame +}); + +// @class Class +// @aka L.Class + +// @section +// @uninheritable + +// Thanks to John Resig and Dean Edwards for inspiration! + +function Class() {} + +Class.extend = function (props) { + + // @function extend(props: Object): Function + // [Extends the current class](#class-inheritance) given the properties to be included. + // Returns a Javascript function that is a class constructor (to be called with `new`). + var NewClass = function () { + + // call the constructor + if (this.initialize) { + this.initialize.apply(this, arguments); + } + + // call all constructor hooks + this.callInitHooks(); + }; + + var parentProto = NewClass.__super__ = this.prototype; + + var proto = create(parentProto); + proto.constructor = NewClass; + + NewClass.prototype = proto; + + // inherit parent's statics + for (var i in this) { + if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') { + NewClass[i] = this[i]; + } + } + + // mix static properties into the class + if (props.statics) { + extend(NewClass, props.statics); + delete props.statics; + } + + // mix includes into the prototype + if (props.includes) { + checkDeprecatedMixinEvents(props.includes); + extend.apply(null, [proto].concat(props.includes)); + delete props.includes; + } + + // merge options + if (proto.options) { + props.options = extend(create(proto.options), props.options); + } + + // mix given properties into the prototype + extend(proto, props); + + proto._initHooks = []; + + // add method for calling all hooks + proto.callInitHooks = function () { + + if (this._initHooksCalled) { return; } + + if (parentProto.callInitHooks) { + parentProto.callInitHooks.call(this); + } + + this._initHooksCalled = true; + + for (var i = 0, len = proto._initHooks.length; i < len; i++) { + proto._initHooks[i].call(this); + } + }; + + return NewClass; +}; + + +// @function include(properties: Object): this +// [Includes a mixin](#class-includes) into the current class. +Class.include = function (props) { + extend(this.prototype, props); + return this; +}; + +// @function mergeOptions(options: Object): this +// [Merges `options`](#class-options) into the defaults of the class. +Class.mergeOptions = function (options) { + extend(this.prototype.options, options); + return this; +}; + +// @function addInitHook(fn: Function): this +// Adds a [constructor hook](#class-constructor-hooks) to the class. +Class.addInitHook = function (fn) { // (Function) || (String, args...) + var args = Array.prototype.slice.call(arguments, 1); + + var init = typeof fn === 'function' ? fn : function () { + this[fn].apply(this, args); + }; + + this.prototype._initHooks = this.prototype._initHooks || []; + this.prototype._initHooks.push(init); + return this; +}; + +function checkDeprecatedMixinEvents(includes) { + if (typeof L === 'undefined' || !L || !L.Mixin) { return; } + + includes = isArray(includes) ? includes : [includes]; + + for (var i = 0; i < includes.length; i++) { + if (includes[i] === L.Mixin.Events) { + console.warn('Deprecated include of L.Mixin.Events: ' + + 'this property will be removed in future releases, ' + + 'please inherit from L.Evented instead.', new Error().stack); + } + } +} + +/* + * @class Evented + * @aka L.Evented + * @inherits Class + * + * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event). + * + * @example + * + * ```js + * map.on('click', function(e) { + * alert(e.latlng); + * } ); + * ``` + * + * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function: + * + * ```js + * function onClick(e) { ... } + * + * map.on('click', onClick); + * map.off('click', onClick); + * ``` + */ + +var Events = { + /* @method on(type: String, fn: Function, context?: Object): this + * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`). + * + * @alternative + * @method on(eventMap: Object): this + * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` + */ + on: function (types, fn, context) { + + // types can be a map of types/handlers + if (typeof types === 'object') { + for (var type in types) { + // we don't process space-separated events here for performance; + // it's a hot path since Layer uses the on(obj) syntax + this._on(type, types[type], fn); + } + + } else { + // types can be a string of space-separated words + types = splitWords(types); + + for (var i = 0, len = types.length; i < len; i++) { + this._on(types[i], fn, context); + } + } + + return this; + }, + + /* @method off(type: String, fn?: Function, context?: Object): this + * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener. + * + * @alternative + * @method off(eventMap: Object): this + * Removes a set of type/listener pairs. + * + * @alternative + * @method off: this + * Removes all listeners to all events on the object. + */ + off: function (types, fn, context) { + + if (!types) { + // clear all listeners if called without arguments + delete this._events; + + } else if (typeof types === 'object') { + for (var type in types) { + this._off(type, types[type], fn); + } + + } else { + types = splitWords(types); + + for (var i = 0, len = types.length; i < len; i++) { + this._off(types[i], fn, context); + } + } + + return this; + }, + + // attach listener (without syntactic sugar now) + _on: function (type, fn, context) { + this._events = this._events || {}; + + /* get/init listeners for type */ + var typeListeners = this._events[type]; + if (!typeListeners) { + typeListeners = []; + this._events[type] = typeListeners; + } + + if (context === this) { + // Less memory footprint. + context = undefined; + } + var newListener = {fn: fn, ctx: context}, + listeners = typeListeners; + + // check if fn already there + for (var i = 0, len = listeners.length; i < len; i++) { + if (listeners[i].fn === fn && listeners[i].ctx === context) { + return; + } + } + + listeners.push(newListener); + }, + + _off: function (type, fn, context) { + var listeners, + i, + len; + + if (!this._events) { return; } + + listeners = this._events[type]; + + if (!listeners) { + return; + } + + if (!fn) { + // Set all removed listeners to noop so they are not called if remove happens in fire + for (i = 0, len = listeners.length; i < len; i++) { + listeners[i].fn = falseFn; + } + // clear all listeners for a type if function isn't specified + delete this._events[type]; + return; + } + + if (context === this) { + context = undefined; + } + + if (listeners) { + + // find fn and remove it + for (i = 0, len = listeners.length; i < len; i++) { + var l = listeners[i]; + if (l.ctx !== context) { continue; } + if (l.fn === fn) { + + // set the removed listener to noop so that's not called if remove happens in fire + l.fn = falseFn; + + if (this._firingCount) { + /* copy array in case events are being fired */ + this._events[type] = listeners = listeners.slice(); + } + listeners.splice(i, 1); + + return; + } + } + } + }, + + // @method fire(type: String, data?: Object, propagate?: Boolean): this + // Fires an event of the specified type. You can optionally provide an data + // object — the first argument of the listener function will contain its + // properties. The event can optionally be propagated to event parents. + fire: function (type, data, propagate) { + if (!this.listens(type, propagate)) { return this; } + + var event = extend({}, data, { + type: type, + target: this, + sourceTarget: data && data.sourceTarget || this + }); + + if (this._events) { + var listeners = this._events[type]; + + if (listeners) { + this._firingCount = (this._firingCount + 1) || 1; + for (var i = 0, len = listeners.length; i < len; i++) { + var l = listeners[i]; + l.fn.call(l.ctx || this, event); + } + + this._firingCount--; + } + } + + if (propagate) { + // propagate the event to parents (set with addEventParent) + this._propagateEvent(event); + } + + return this; + }, + + // @method listens(type: String): Boolean + // Returns `true` if a particular event type has any listeners attached to it. + listens: function (type, propagate) { + var listeners = this._events && this._events[type]; + if (listeners && listeners.length) { return true; } + + if (propagate) { + // also check parents for listeners if event propagates + for (var id in this._eventParents) { + if (this._eventParents[id].listens(type, propagate)) { return true; } + } + } + return false; + }, + + // @method once(
): this + // Behaves as [`on(
)`](#evented-on), except the listener will only get fired once and then removed. + once: function (types, fn, context) { + + if (typeof types === 'object') { + for (var type in types) { + this.once(type, types[type], fn); + } + return this; + } + + var handler = bind(function () { + this + .off(types, fn, context) + .off(types, handler, context); + }, this); + + // add a listener that's executed once and removed after that + return this + .on(types, fn, context) + .on(types, handler, context); + }, + + // @method addEventParent(obj: Evented): this + // Adds an event parent - an `Evented` that will receive propagated events + addEventParent: function (obj) { + this._eventParents = this._eventParents || {}; + this._eventParents[stamp(obj)] = obj; + return this; + }, + + // @method removeEventParent(obj: Evented): this + // Removes an event parent, so it will stop receiving propagated events + removeEventParent: function (obj) { + if (this._eventParents) { + delete this._eventParents[stamp(obj)]; + } + return this; + }, + + _propagateEvent: function (e) { + for (var id in this._eventParents) { + this._eventParents[id].fire(e.type, extend({ + layer: e.target, + propagatedFrom: e.target + }, e), true); + } + } +}; + +// aliases; we should ditch those eventually + +// @method addEventListener(
): this +// Alias to [`on(
)`](#evented-on) +Events.addEventListener = Events.on; + +// @method removeEventListener(
): this +// Alias to [`off(
)`](#evented-off) + +// @method clearAllEventListeners(
): this +// Alias to [`off()`](#evented-off) +Events.removeEventListener = Events.clearAllEventListeners = Events.off; + +// @method addOneTimeEventListener(
): this +// Alias to [`once(
)`](#evented-once) +Events.addOneTimeEventListener = Events.once; + +// @method fireEvent(
): this +// Alias to [`fire(
)`](#evented-fire) +Events.fireEvent = Events.fire; + +// @method hasEventListeners(
): Boolean +// Alias to [`listens(
)`](#evented-listens) +Events.hasEventListeners = Events.listens; + +var Evented = Class.extend(Events); + +/* + * @class Point + * @aka L.Point + * + * Represents a point with `x` and `y` coordinates in pixels. + * + * @example + * + * ```js + * var point = L.point(200, 300); + * ``` + * + * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent: + * + * ```js + * map.panBy([200, 300]); + * map.panBy(L.point(200, 300)); + * ``` + * + * Note that `Point` does not inherit from Leafet's `Class` object, + * which means new classes can't inherit from it, and new methods + * can't be added to it with the `include` function. + */ + +function Point(x, y, round) { + // @property x: Number; The `x` coordinate of the point + this.x = (round ? Math.round(x) : x); + // @property y: Number; The `y` coordinate of the point + this.y = (round ? Math.round(y) : y); +} + +var trunc = Math.trunc || function (v) { + return v > 0 ? Math.floor(v) : Math.ceil(v); +}; + +Point.prototype = { + + // @method clone(): Point + // Returns a copy of the current point. + clone: function () { + return new Point(this.x, this.y); + }, + + // @method add(otherPoint: Point): Point + // Returns the result of addition of the current and the given points. + add: function (point) { + // non-destructive, returns a new point + return this.clone()._add(toPoint(point)); + }, + + _add: function (point) { + // destructive, used directly for performance in situations where it's safe to modify existing point + this.x += point.x; + this.y += point.y; + return this; + }, + + // @method subtract(otherPoint: Point): Point + // Returns the result of subtraction of the given point from the current. + subtract: function (point) { + return this.clone()._subtract(toPoint(point)); + }, + + _subtract: function (point) { + this.x -= point.x; + this.y -= point.y; + return this; + }, + + // @method divideBy(num: Number): Point + // Returns the result of division of the current point by the given number. + divideBy: function (num) { + return this.clone()._divideBy(num); + }, + + _divideBy: function (num) { + this.x /= num; + this.y /= num; + return this; + }, + + // @method multiplyBy(num: Number): Point + // Returns the result of multiplication of the current point by the given number. + multiplyBy: function (num) { + return this.clone()._multiplyBy(num); + }, + + _multiplyBy: function (num) { + this.x *= num; + this.y *= num; + return this; + }, + + // @method scaleBy(scale: Point): Point + // Multiply each coordinate of the current point by each coordinate of + // `scale`. In linear algebra terms, multiply the point by the + // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) + // defined by `scale`. + scaleBy: function (point) { + return new Point(this.x * point.x, this.y * point.y); + }, + + // @method unscaleBy(scale: Point): Point + // Inverse of `scaleBy`. Divide each coordinate of the current point by + // each coordinate of `scale`. + unscaleBy: function (point) { + return new Point(this.x / point.x, this.y / point.y); + }, + + // @method round(): Point + // Returns a copy of the current point with rounded coordinates. + round: function () { + return this.clone()._round(); + }, + + _round: function () { + this.x = Math.round(this.x); + this.y = Math.round(this.y); + return this; + }, + + // @method floor(): Point + // Returns a copy of the current point with floored coordinates (rounded down). + floor: function () { + return this.clone()._floor(); + }, + + _floor: function () { + this.x = Math.floor(this.x); + this.y = Math.floor(this.y); + return this; + }, + + // @method ceil(): Point + // Returns a copy of the current point with ceiled coordinates (rounded up). + ceil: function () { + return this.clone()._ceil(); + }, + + _ceil: function () { + this.x = Math.ceil(this.x); + this.y = Math.ceil(this.y); + return this; + }, + + // @method trunc(): Point + // Returns a copy of the current point with truncated coordinates (rounded towards zero). + trunc: function () { + return this.clone()._trunc(); + }, + + _trunc: function () { + this.x = trunc(this.x); + this.y = trunc(this.y); + return this; + }, + + // @method distanceTo(otherPoint: Point): Number + // Returns the cartesian distance between the current and the given points. + distanceTo: function (point) { + point = toPoint(point); + + var x = point.x - this.x, + y = point.y - this.y; + + return Math.sqrt(x * x + y * y); + }, + + // @method equals(otherPoint: Point): Boolean + // Returns `true` if the given point has the same coordinates. + equals: function (point) { + point = toPoint(point); + + return point.x === this.x && + point.y === this.y; + }, + + // @method contains(otherPoint: Point): Boolean + // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values). + contains: function (point) { + point = toPoint(point); + + return Math.abs(point.x) <= Math.abs(this.x) && + Math.abs(point.y) <= Math.abs(this.y); + }, + + // @method toString(): String + // Returns a string representation of the point for debugging purposes. + toString: function () { + return 'Point(' + + formatNum(this.x) + ', ' + + formatNum(this.y) + ')'; + } +}; + +// @factory L.point(x: Number, y: Number, round?: Boolean) +// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values. + +// @alternative +// @factory L.point(coords: Number[]) +// Expects an array of the form `[x, y]` instead. + +// @alternative +// @factory L.point(coords: Object) +// Expects a plain object of the form `{x: Number, y: Number}` instead. +function toPoint(x, y, round) { + if (x instanceof Point) { + return x; + } + if (isArray(x)) { + return new Point(x[0], x[1]); + } + if (x === undefined || x === null) { + return x; + } + if (typeof x === 'object' && 'x' in x && 'y' in x) { + return new Point(x.x, x.y); + } + return new Point(x, y, round); +} + +/* + * @class Bounds + * @aka L.Bounds + * + * Represents a rectangular area in pixel coordinates. + * + * @example + * + * ```js + * var p1 = L.point(10, 10), + * p2 = L.point(40, 60), + * bounds = L.bounds(p1, p2); + * ``` + * + * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this: + * + * ```js + * otherBounds.intersects([[10, 10], [40, 60]]); + * ``` + * + * Note that `Bounds` does not inherit from Leafet's `Class` object, + * which means new classes can't inherit from it, and new methods + * can't be added to it with the `include` function. + */ + +function Bounds(a, b) { + if (!a) { return; } + + var points = b ? [a, b] : a; + + for (var i = 0, len = points.length; i < len; i++) { + this.extend(points[i]); + } +} + +Bounds.prototype = { + // @method extend(point: Point): this + // Extends the bounds to contain the given point. + extend: function (point) { // (Point) + point = toPoint(point); + + // @property min: Point + // The top left corner of the rectangle. + // @property max: Point + // The bottom right corner of the rectangle. + if (!this.min && !this.max) { + this.min = point.clone(); + this.max = point.clone(); + } else { + this.min.x = Math.min(point.x, this.min.x); + this.max.x = Math.max(point.x, this.max.x); + this.min.y = Math.min(point.y, this.min.y); + this.max.y = Math.max(point.y, this.max.y); + } + return this; + }, + + // @method getCenter(round?: Boolean): Point + // Returns the center point of the bounds. + getCenter: function (round) { + return new Point( + (this.min.x + this.max.x) / 2, + (this.min.y + this.max.y) / 2, round); + }, + + // @method getBottomLeft(): Point + // Returns the bottom-left point of the bounds. + getBottomLeft: function () { + return new Point(this.min.x, this.max.y); + }, + + // @method getTopRight(): Point + // Returns the top-right point of the bounds. + getTopRight: function () { // -> Point + return new Point(this.max.x, this.min.y); + }, + + // @method getTopLeft(): Point + // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)). + getTopLeft: function () { + return this.min; // left, top + }, + + // @method getBottomRight(): Point + // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)). + getBottomRight: function () { + return this.max; // right, bottom + }, + + // @method getSize(): Point + // Returns the size of the given bounds + getSize: function () { + return this.max.subtract(this.min); + }, + + // @method contains(otherBounds: Bounds): Boolean + // Returns `true` if the rectangle contains the given one. + // @alternative + // @method contains(point: Point): Boolean + // Returns `true` if the rectangle contains the given point. + contains: function (obj) { + var min, max; + + if (typeof obj[0] === 'number' || obj instanceof Point) { + obj = toPoint(obj); + } else { + obj = toBounds(obj); + } + + if (obj instanceof Bounds) { + min = obj.min; + max = obj.max; + } else { + min = max = obj; + } + + return (min.x >= this.min.x) && + (max.x <= this.max.x) && + (min.y >= this.min.y) && + (max.y <= this.max.y); + }, + + // @method intersects(otherBounds: Bounds): Boolean + // Returns `true` if the rectangle intersects the given bounds. Two bounds + // intersect if they have at least one point in common. + intersects: function (bounds) { // (Bounds) -> Boolean + bounds = toBounds(bounds); + + var min = this.min, + max = this.max, + min2 = bounds.min, + max2 = bounds.max, + xIntersects = (max2.x >= min.x) && (min2.x <= max.x), + yIntersects = (max2.y >= min.y) && (min2.y <= max.y); + + return xIntersects && yIntersects; + }, + + // @method overlaps(otherBounds: Bounds): Boolean + // Returns `true` if the rectangle overlaps the given bounds. Two bounds + // overlap if their intersection is an area. + overlaps: function (bounds) { // (Bounds) -> Boolean + bounds = toBounds(bounds); + + var min = this.min, + max = this.max, + min2 = bounds.min, + max2 = bounds.max, + xOverlaps = (max2.x > min.x) && (min2.x < max.x), + yOverlaps = (max2.y > min.y) && (min2.y < max.y); + + return xOverlaps && yOverlaps; + }, + + isValid: function () { + return !!(this.min && this.max); + } +}; + + +// @factory L.bounds(corner1: Point, corner2: Point) +// Creates a Bounds object from two corners coordinate pairs. +// @alternative +// @factory L.bounds(points: Point[]) +// Creates a Bounds object from the given array of points. +function toBounds(a, b) { + if (!a || a instanceof Bounds) { + return a; + } + return new Bounds(a, b); +} + +/* + * @class LatLngBounds + * @aka L.LatLngBounds + * + * Represents a rectangular geographical area on a map. + * + * @example + * + * ```js + * var corner1 = L.latLng(40.712, -74.227), + * corner2 = L.latLng(40.774, -74.125), + * bounds = L.latLngBounds(corner1, corner2); + * ``` + * + * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this: + * + * ```js + * map.fitBounds([ + * [40.712, -74.227], + * [40.774, -74.125] + * ]); + * ``` + * + * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range. + * + * Note that `LatLngBounds` does not inherit from Leafet's `Class` object, + * which means new classes can't inherit from it, and new methods + * can't be added to it with the `include` function. + */ + +function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) + if (!corner1) { return; } + + var latlngs = corner2 ? [corner1, corner2] : corner1; + + for (var i = 0, len = latlngs.length; i < len; i++) { + this.extend(latlngs[i]); + } +} + +LatLngBounds.prototype = { + + // @method extend(latlng: LatLng): this + // Extend the bounds to contain the given point + + // @alternative + // @method extend(otherBounds: LatLngBounds): this + // Extend the bounds to contain the given bounds + extend: function (obj) { + var sw = this._southWest, + ne = this._northEast, + sw2, ne2; + + if (obj instanceof LatLng) { + sw2 = obj; + ne2 = obj; + + } else if (obj instanceof LatLngBounds) { + sw2 = obj._southWest; + ne2 = obj._northEast; + + if (!sw2 || !ne2) { return this; } + + } else { + return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this; + } + + if (!sw && !ne) { + this._southWest = new LatLng(sw2.lat, sw2.lng); + this._northEast = new LatLng(ne2.lat, ne2.lng); + } else { + sw.lat = Math.min(sw2.lat, sw.lat); + sw.lng = Math.min(sw2.lng, sw.lng); + ne.lat = Math.max(ne2.lat, ne.lat); + ne.lng = Math.max(ne2.lng, ne.lng); + } + + return this; + }, + + // @method pad(bufferRatio: Number): LatLngBounds + // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. + // For example, a ratio of 0.5 extends the bounds by 50% in each direction. + // Negative values will retract the bounds. + pad: function (bufferRatio) { + var sw = this._southWest, + ne = this._northEast, + heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, + widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; + + return new LatLngBounds( + new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), + new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); + }, + + // @method getCenter(): LatLng + // Returns the center point of the bounds. + getCenter: function () { + return new LatLng( + (this._southWest.lat + this._northEast.lat) / 2, + (this._southWest.lng + this._northEast.lng) / 2); + }, + + // @method getSouthWest(): LatLng + // Returns the south-west point of the bounds. + getSouthWest: function () { + return this._southWest; + }, + + // @method getNorthEast(): LatLng + // Returns the north-east point of the bounds. + getNorthEast: function () { + return this._northEast; + }, + + // @method getNorthWest(): LatLng + // Returns the north-west point of the bounds. + getNorthWest: function () { + return new LatLng(this.getNorth(), this.getWest()); + }, + + // @method getSouthEast(): LatLng + // Returns the south-east point of the bounds. + getSouthEast: function () { + return new LatLng(this.getSouth(), this.getEast()); + }, + + // @method getWest(): Number + // Returns the west longitude of the bounds + getWest: function () { + return this._southWest.lng; + }, + + // @method getSouth(): Number + // Returns the south latitude of the bounds + getSouth: function () { + return this._southWest.lat; + }, + + // @method getEast(): Number + // Returns the east longitude of the bounds + getEast: function () { + return this._northEast.lng; + }, + + // @method getNorth(): Number + // Returns the north latitude of the bounds + getNorth: function () { + return this._northEast.lat; + }, + + // @method contains(otherBounds: LatLngBounds): Boolean + // Returns `true` if the rectangle contains the given one. + + // @alternative + // @method contains (latlng: LatLng): Boolean + // Returns `true` if the rectangle contains the given point. + contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean + if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) { + obj = toLatLng(obj); + } else { + obj = toLatLngBounds(obj); + } + + var sw = this._southWest, + ne = this._northEast, + sw2, ne2; + + if (obj instanceof LatLngBounds) { + sw2 = obj.getSouthWest(); + ne2 = obj.getNorthEast(); + } else { + sw2 = ne2 = obj; + } + + return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && + (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); + }, + + // @method intersects(otherBounds: LatLngBounds): Boolean + // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. + intersects: function (bounds) { + bounds = toLatLngBounds(bounds); + + var sw = this._southWest, + ne = this._northEast, + sw2 = bounds.getSouthWest(), + ne2 = bounds.getNorthEast(), + + latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), + lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); + + return latIntersects && lngIntersects; + }, + + // @method overlaps(otherBounds: Bounds): Boolean + // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. + overlaps: function (bounds) { + bounds = toLatLngBounds(bounds); + + var sw = this._southWest, + ne = this._northEast, + sw2 = bounds.getSouthWest(), + ne2 = bounds.getNorthEast(), + + latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), + lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); + + return latOverlaps && lngOverlaps; + }, + + // @method toBBoxString(): String + // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data. + toBBoxString: function () { + return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); + }, + + // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean + // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number. + equals: function (bounds, maxMargin) { + if (!bounds) { return false; } + + bounds = toLatLngBounds(bounds); + + return this._southWest.equals(bounds.getSouthWest(), maxMargin) && + this._northEast.equals(bounds.getNorthEast(), maxMargin); + }, + + // @method isValid(): Boolean + // Returns `true` if the bounds are properly initialized. + isValid: function () { + return !!(this._southWest && this._northEast); + } +}; + +// TODO International date line? + +// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng) +// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle. + +// @alternative +// @factory L.latLngBounds(latlngs: LatLng[]) +// Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds). +function toLatLngBounds(a, b) { + if (a instanceof LatLngBounds) { + return a; + } + return new LatLngBounds(a, b); +} + +/* @class LatLng + * @aka L.LatLng + * + * Represents a geographical point with a certain latitude and longitude. + * + * @example + * + * ``` + * var latlng = L.latLng(50.5, 30.5); + * ``` + * + * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent: + * + * ``` + * map.panTo([50, 30]); + * map.panTo({lon: 30, lat: 50}); + * map.panTo({lat: 50, lng: 30}); + * map.panTo(L.latLng(50, 30)); + * ``` + * + * Note that `LatLng` does not inherit from Leaflet's `Class` object, + * which means new classes can't inherit from it, and new methods + * can't be added to it with the `include` function. + */ + +function LatLng(lat, lng, alt) { + if (isNaN(lat) || isNaN(lng)) { + throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); + } + + // @property lat: Number + // Latitude in degrees + this.lat = +lat; + + // @property lng: Number + // Longitude in degrees + this.lng = +lng; + + // @property alt: Number + // Altitude in meters (optional) + if (alt !== undefined) { + this.alt = +alt; + } +} + +LatLng.prototype = { + // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean + // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number. + equals: function (obj, maxMargin) { + if (!obj) { return false; } + + obj = toLatLng(obj); + + var margin = Math.max( + Math.abs(this.lat - obj.lat), + Math.abs(this.lng - obj.lng)); + + return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); + }, + + // @method toString(): String + // Returns a string representation of the point (for debugging purposes). + toString: function (precision) { + return 'LatLng(' + + formatNum(this.lat, precision) + ', ' + + formatNum(this.lng, precision) + ')'; + }, + + // @method distanceTo(otherLatLng: LatLng): Number + // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines). + distanceTo: function (other) { + return Earth.distance(this, toLatLng(other)); + }, + + // @method wrap(): LatLng + // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. + wrap: function () { + return Earth.wrapLatLng(this); + }, + + // @method toBounds(sizeInMeters: Number): LatLngBounds + // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`. + toBounds: function (sizeInMeters) { + var latAccuracy = 180 * sizeInMeters / 40075017, + lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); + + return toLatLngBounds( + [this.lat - latAccuracy, this.lng - lngAccuracy], + [this.lat + latAccuracy, this.lng + lngAccuracy]); + }, + + clone: function () { + return new LatLng(this.lat, this.lng, this.alt); + } +}; + + + +// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng +// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude). + +// @alternative +// @factory L.latLng(coords: Array): LatLng +// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead. + +// @alternative +// @factory L.latLng(coords: Object): LatLng +// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead. + +function toLatLng(a, b, c) { + if (a instanceof LatLng) { + return a; + } + if (isArray(a) && typeof a[0] !== 'object') { + if (a.length === 3) { + return new LatLng(a[0], a[1], a[2]); + } + if (a.length === 2) { + return new LatLng(a[0], a[1]); + } + return null; + } + if (a === undefined || a === null) { + return a; + } + if (typeof a === 'object' && 'lat' in a) { + return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); + } + if (b === undefined) { + return null; + } + return new LatLng(a, b, c); +} + +/* + * @namespace CRS + * @crs L.CRS.Base + * Object that defines coordinate reference systems for projecting + * geographical points into pixel (screen) coordinates and back (and to + * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See + * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system). + * + * Leaflet defines the most usual CRSs by default. If you want to use a + * CRS not defined by default, take a look at the + * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin. + * + * Note that the CRS instances do not inherit from Leafet's `Class` object, + * and can't be instantiated. Also, new classes can't inherit from them, + * and methods can't be added to them with the `include` function. + */ + +var CRS = { + // @method latLngToPoint(latlng: LatLng, zoom: Number): Point + // Projects geographical coordinates into pixel coordinates for a given zoom. + latLngToPoint: function (latlng, zoom) { + var projectedPoint = this.projection.project(latlng), + scale = this.scale(zoom); + + return this.transformation._transform(projectedPoint, scale); + }, + + // @method pointToLatLng(point: Point, zoom: Number): LatLng + // The inverse of `latLngToPoint`. Projects pixel coordinates on a given + // zoom into geographical coordinates. + pointToLatLng: function (point, zoom) { + var scale = this.scale(zoom), + untransformedPoint = this.transformation.untransform(point, scale); + + return this.projection.unproject(untransformedPoint); + }, + + // @method project(latlng: LatLng): Point + // Projects geographical coordinates into coordinates in units accepted for + // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services). + project: function (latlng) { + return this.projection.project(latlng); + }, + + // @method unproject(point: Point): LatLng + // Given a projected coordinate returns the corresponding LatLng. + // The inverse of `project`. + unproject: function (point) { + return this.projection.unproject(point); + }, + + // @method scale(zoom: Number): Number + // Returns the scale used when transforming projected coordinates into + // pixel coordinates for a particular zoom. For example, it returns + // `256 * 2^zoom` for Mercator-based CRS. + scale: function (zoom) { + return 256 * Math.pow(2, zoom); + }, + + // @method zoom(scale: Number): Number + // Inverse of `scale()`, returns the zoom level corresponding to a scale + // factor of `scale`. + zoom: function (scale) { + return Math.log(scale / 256) / Math.LN2; + }, + + // @method getProjectedBounds(zoom: Number): Bounds + // Returns the projection's bounds scaled and transformed for the provided `zoom`. + getProjectedBounds: function (zoom) { + if (this.infinite) { return null; } + + var b = this.projection.bounds, + s = this.scale(zoom), + min = this.transformation.transform(b.min, s), + max = this.transformation.transform(b.max, s); + + return new Bounds(min, max); + }, + + // @method distance(latlng1: LatLng, latlng2: LatLng): Number + // Returns the distance between two geographical coordinates. + + // @property code: String + // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`) + // + // @property wrapLng: Number[] + // An array of two numbers defining whether the longitude (horizontal) coordinate + // axis wraps around a given range and how. Defaults to `[-180, 180]` in most + // geographical CRSs. If `undefined`, the longitude axis does not wrap around. + // + // @property wrapLat: Number[] + // Like `wrapLng`, but for the latitude (vertical) axis. + + // wrapLng: [min, max], + // wrapLat: [min, max], + + // @property infinite: Boolean + // If true, the coordinate space will be unbounded (infinite in both axes) + infinite: false, + + // @method wrapLatLng(latlng: LatLng): LatLng + // Returns a `LatLng` where lat and lng has been wrapped according to the + // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds. + wrapLatLng: function (latlng) { + var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, + lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, + alt = latlng.alt; + + return new LatLng(lat, lng, alt); + }, + + // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds + // Returns a `LatLngBounds` with the same size as the given one, ensuring + // that its center is within the CRS's bounds. + // Only accepts actual `L.LatLngBounds` instances, not arrays. + wrapLatLngBounds: function (bounds) { + var center = bounds.getCenter(), + newCenter = this.wrapLatLng(center), + latShift = center.lat - newCenter.lat, + lngShift = center.lng - newCenter.lng; + + if (latShift === 0 && lngShift === 0) { + return bounds; + } + + var sw = bounds.getSouthWest(), + ne = bounds.getNorthEast(), + newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift), + newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift); + + return new LatLngBounds(newSw, newNe); + } +}; + +/* + * @namespace CRS + * @crs L.CRS.Earth + * + * Serves as the base for CRS that are global such that they cover the earth. + * Can only be used as the base for other CRS and cannot be used directly, + * since it does not have a `code`, `projection` or `transformation`. `distance()` returns + * meters. + */ + +var Earth = extend({}, CRS, { + wrapLng: [-180, 180], + + // Mean Earth Radius, as recommended for use by + // the International Union of Geodesy and Geophysics, + // see http://rosettacode.org/wiki/Haversine_formula + R: 6371000, + + // distance between two geographical points using spherical law of cosines approximation + distance: function (latlng1, latlng2) { + var rad = Math.PI / 180, + lat1 = latlng1.lat * rad, + lat2 = latlng2.lat * rad, + sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2), + sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2), + a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon, + c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return this.R * c; + } +}); + +/* + * @namespace Projection + * @projection L.Projection.SphericalMercator + * + * Spherical Mercator projection — the most common projection for online maps, + * used by almost all free and commercial tile providers. Assumes that Earth is + * a sphere. Used by the `EPSG:3857` CRS. + */ + +var SphericalMercator = { + + R: 6378137, + MAX_LATITUDE: 85.0511287798, + + project: function (latlng) { + var d = Math.PI / 180, + max = this.MAX_LATITUDE, + lat = Math.max(Math.min(max, latlng.lat), -max), + sin = Math.sin(lat * d); + + return new Point( + this.R * latlng.lng * d, + this.R * Math.log((1 + sin) / (1 - sin)) / 2); + }, + + unproject: function (point) { + var d = 180 / Math.PI; + + return new LatLng( + (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, + point.x * d / this.R); + }, + + bounds: (function () { + var d = 6378137 * Math.PI; + return new Bounds([-d, -d], [d, d]); + })() +}; + +/* + * @class Transformation + * @aka L.Transformation + * + * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d` + * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing + * the reverse. Used by Leaflet in its projections code. + * + * @example + * + * ```js + * var transformation = L.transformation(2, 5, -1, 10), + * p = L.point(1, 2), + * p2 = transformation.transform(p), // L.point(7, 8) + * p3 = transformation.untransform(p2); // L.point(1, 2) + * ``` + */ + + +// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) +// Creates a `Transformation` object with the given coefficients. +function Transformation(a, b, c, d) { + if (isArray(a)) { + // use array properties + this._a = a[0]; + this._b = a[1]; + this._c = a[2]; + this._d = a[3]; + return; + } + this._a = a; + this._b = b; + this._c = c; + this._d = d; +} + +Transformation.prototype = { + // @method transform(point: Point, scale?: Number): Point + // Returns a transformed point, optionally multiplied by the given scale. + // Only accepts actual `L.Point` instances, not arrays. + transform: function (point, scale) { // (Point, Number) -> Point + return this._transform(point.clone(), scale); + }, + + // destructive transform (faster) + _transform: function (point, scale) { + scale = scale || 1; + point.x = scale * (this._a * point.x + this._b); + point.y = scale * (this._c * point.y + this._d); + return point; + }, + + // @method untransform(point: Point, scale?: Number): Point + // Returns the reverse transformation of the given point, optionally divided + // by the given scale. Only accepts actual `L.Point` instances, not arrays. + untransform: function (point, scale) { + scale = scale || 1; + return new Point( + (point.x / scale - this._b) / this._a, + (point.y / scale - this._d) / this._c); + } +}; + +// factory L.transformation(a: Number, b: Number, c: Number, d: Number) + +// @factory L.transformation(a: Number, b: Number, c: Number, d: Number) +// Instantiates a Transformation object with the given coefficients. + +// @alternative +// @factory L.transformation(coefficients: Array): Transformation +// Expects an coefficients array of the form +// `[a: Number, b: Number, c: Number, d: Number]`. + +function toTransformation(a, b, c, d) { + return new Transformation(a, b, c, d); +} + +/* + * @namespace CRS + * @crs L.CRS.EPSG3857 + * + * The most common CRS for online maps, used by almost all free and commercial + * tile providers. Uses Spherical Mercator projection. Set in by default in + * Map's `crs` option. + */ + +var EPSG3857 = extend({}, Earth, { + code: 'EPSG:3857', + projection: SphericalMercator, + + transformation: (function () { + var scale = 0.5 / (Math.PI * SphericalMercator.R); + return toTransformation(scale, 0.5, -scale, 0.5); + }()) +}); + +var EPSG900913 = extend({}, EPSG3857, { + code: 'EPSG:900913' +}); + +// @namespace SVG; @section +// There are several static functions which can be called without instantiating L.SVG: + +// @function create(name: String): SVGElement +// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), +// corresponding to the class name passed. For example, using 'line' will return +// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). +function svgCreate(name) { + return document.createElementNS('http://www.w3.org/2000/svg', name); +} + +// @function pointsToPath(rings: Point[], closed: Boolean): String +// Generates a SVG path string for multiple rings, with each ring turning +// into "M..L..L.." instructions +function pointsToPath(rings, closed) { + var str = '', + i, j, len, len2, points, p; + + for (i = 0, len = rings.length; i < len; i++) { + points = rings[i]; + + for (j = 0, len2 = points.length; j < len2; j++) { + p = points[j]; + str += (j ? 'L' : 'M') + p.x + ' ' + p.y; + } + + // closes the ring for polygons; "x" is VML syntax + str += closed ? (svg ? 'z' : 'x') : ''; + } + + // SVG complains about empty path strings + return str || 'M0 0'; +} + +/* + * @namespace Browser + * @aka L.Browser + * + * A namespace with static properties for browser/feature detection used by Leaflet internally. + * + * @example + * + * ```js + * if (L.Browser.ielt9) { + * alert('Upgrade your browser, dude!'); + * } + * ``` + */ + +var style$1 = document.documentElement.style; + +// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge). +var ie = 'ActiveXObject' in window; + +// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9. +var ielt9 = ie && !document.addEventListener; + +// @property edge: Boolean; `true` for the Edge web browser. +var edge = 'msLaunchUri' in navigator && !('documentMode' in document); + +// @property webkit: Boolean; +// `true` for webkit-based browsers like Chrome and Safari (including mobile versions). +var webkit = userAgentContains('webkit'); + +// @property android: Boolean +// `true` for any browser running on an Android platform. +var android = userAgentContains('android'); + +// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3. +var android23 = userAgentContains('android 2') || userAgentContains('android 3'); + +/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */ +var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit +// @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome) +var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window); + +// @property opera: Boolean; `true` for the Opera browser +var opera = !!window.opera; + +// @property chrome: Boolean; `true` for the Chrome browser. +var chrome = userAgentContains('chrome'); + +// @property gecko: Boolean; `true` for gecko-based browsers like Firefox. +var gecko = userAgentContains('gecko') && !webkit && !opera && !ie; + +// @property safari: Boolean; `true` for the Safari browser. +var safari = !chrome && userAgentContains('safari'); + +var phantom = userAgentContains('phantom'); + +// @property opera12: Boolean +// `true` for the Opera browser supporting CSS transforms (version 12 or later). +var opera12 = 'OTransition' in style$1; + +// @property win: Boolean; `true` when the browser is running in a Windows platform +var win = navigator.platform.indexOf('Win') === 0; + +// @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms. +var ie3d = ie && ('transition' in style$1); + +// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms. +var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23; + +// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms. +var gecko3d = 'MozPerspective' in style$1; + +// @property any3d: Boolean +// `true` for all browsers supporting CSS transforms. +var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom; + +// @property mobile: Boolean; `true` for all browsers running in a mobile device. +var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile'); + +// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device. +var mobileWebkit = mobile && webkit; + +// @property mobileWebkit3d: Boolean +// `true` for all webkit-based browsers in a mobile device supporting CSS transforms. +var mobileWebkit3d = mobile && webkit3d; + +// @property msPointer: Boolean +// `true` for browsers implementing the Microsoft touch events model (notably IE10). +var msPointer = !window.PointerEvent && window.MSPointerEvent; + +// @property pointer: Boolean +// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx). +var pointer = !!(window.PointerEvent || msPointer); + +// @property touch: Boolean +// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). +// This does not necessarily mean that the browser is running in a computer with +// a touchscreen, it only means that the browser is capable of understanding +// touch events. +var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window || + (window.DocumentTouch && document instanceof window.DocumentTouch)); + +// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device. +var mobileOpera = mobile && opera; + +// @property mobileGecko: Boolean +// `true` for gecko-based browsers running in a mobile device. +var mobileGecko = mobile && gecko; + +// @property retina: Boolean +// `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%. +var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1; + + +// @property canvas: Boolean +// `true` when the browser supports [``](https://developer.mozilla.org/docs/Web/API/Canvas_API). +var canvas = (function () { + return !!document.createElement('canvas').getContext; +}()); + +// @property svg: Boolean +// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). +var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect); + +// @property vml: Boolean +// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). +var vml = !svg && (function () { + try { + var div = document.createElement('div'); + div.innerHTML = ''; + + var shape = div.firstChild; + shape.style.behavior = 'url(#default#VML)'; + + return shape && (typeof shape.adj === 'object'); + + } catch (e) { + return false; + } +}()); + + +function userAgentContains(str) { + return navigator.userAgent.toLowerCase().indexOf(str) >= 0; +} + + +var Browser = (Object.freeze || Object)({ + ie: ie, + ielt9: ielt9, + edge: edge, + webkit: webkit, + android: android, + android23: android23, + androidStock: androidStock, + opera: opera, + chrome: chrome, + gecko: gecko, + safari: safari, + phantom: phantom, + opera12: opera12, + win: win, + ie3d: ie3d, + webkit3d: webkit3d, + gecko3d: gecko3d, + any3d: any3d, + mobile: mobile, + mobileWebkit: mobileWebkit, + mobileWebkit3d: mobileWebkit3d, + msPointer: msPointer, + pointer: pointer, + touch: touch, + mobileOpera: mobileOpera, + mobileGecko: mobileGecko, + retina: retina, + canvas: canvas, + svg: svg, + vml: vml +}); + +/* + * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. + */ + + +var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown'; +var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove'; +var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup'; +var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel'; +var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION']; + +var _pointers = {}; +var _pointerDocListener = false; + +// DomEvent.DoubleTap needs to know about this +var _pointersCount = 0; + +// Provides a touch events wrapper for (ms)pointer events. +// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 + +function addPointerListener(obj, type, handler, id) { + if (type === 'touchstart') { + _addPointerStart(obj, handler, id); + + } else if (type === 'touchmove') { + _addPointerMove(obj, handler, id); + + } else if (type === 'touchend') { + _addPointerEnd(obj, handler, id); + } + + return this; +} + +function removePointerListener(obj, type, id) { + var handler = obj['_leaflet_' + type + id]; + + if (type === 'touchstart') { + obj.removeEventListener(POINTER_DOWN, handler, false); + + } else if (type === 'touchmove') { + obj.removeEventListener(POINTER_MOVE, handler, false); + + } else if (type === 'touchend') { + obj.removeEventListener(POINTER_UP, handler, false); + obj.removeEventListener(POINTER_CANCEL, handler, false); + } + + return this; +} + +function _addPointerStart(obj, handler, id) { + var onDown = bind(function (e) { + if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) { + // In IE11, some touch events needs to fire for form controls, or + // the controls will stop working. We keep a whitelist of tag names that + // need these events. For other target tags, we prevent default on the event. + if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) { + preventDefault(e); + } else { + return; + } + } + + _handlePointer(e, handler); + }); + + obj['_leaflet_touchstart' + id] = onDown; + obj.addEventListener(POINTER_DOWN, onDown, false); + + // need to keep track of what pointers and how many are active to provide e.touches emulation + if (!_pointerDocListener) { + // we listen documentElement as any drags that end by moving the touch off the screen get fired there + document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true); + document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true); + document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true); + document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true); + + _pointerDocListener = true; + } +} + +function _globalPointerDown(e) { + _pointers[e.pointerId] = e; + _pointersCount++; +} + +function _globalPointerMove(e) { + if (_pointers[e.pointerId]) { + _pointers[e.pointerId] = e; + } +} + +function _globalPointerUp(e) { + delete _pointers[e.pointerId]; + _pointersCount--; +} + +function _handlePointer(e, handler) { + e.touches = []; + for (var i in _pointers) { + e.touches.push(_pointers[i]); + } + e.changedTouches = [e]; + + handler(e); +} + +function _addPointerMove(obj, handler, id) { + var onMove = function (e) { + // don't fire touch moves when mouse isn't down + if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; } + + _handlePointer(e, handler); + }; + + obj['_leaflet_touchmove' + id] = onMove; + obj.addEventListener(POINTER_MOVE, onMove, false); +} + +function _addPointerEnd(obj, handler, id) { + var onUp = function (e) { + _handlePointer(e, handler); + }; + + obj['_leaflet_touchend' + id] = onUp; + obj.addEventListener(POINTER_UP, onUp, false); + obj.addEventListener(POINTER_CANCEL, onUp, false); +} + +/* + * Extends the event handling code with double tap support for mobile browsers. + */ + +var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart'; +var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend'; +var _pre = '_leaflet_'; + +// inspired by Zepto touch code by Thomas Fuchs +function addDoubleTapListener(obj, handler, id) { + var last, touch$$1, + doubleTap = false, + delay = 250; + + function onTouchStart(e) { + var count; + + if (pointer) { + if ((!edge) || e.pointerType === 'mouse') { return; } + count = _pointersCount; + } else { + count = e.touches.length; + } + + if (count > 1) { return; } + + var now = Date.now(), + delta = now - (last || now); + + touch$$1 = e.touches ? e.touches[0] : e; + doubleTap = (delta > 0 && delta <= delay); + last = now; + } + + function onTouchEnd(e) { + if (doubleTap && !touch$$1.cancelBubble) { + if (pointer) { + if ((!edge) || e.pointerType === 'mouse') { return; } + // work around .type being readonly with MSPointer* events + var newTouch = {}, + prop, i; + + for (i in touch$$1) { + prop = touch$$1[i]; + newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop; + } + touch$$1 = newTouch; + } + touch$$1.type = 'dblclick'; + handler(touch$$1); + last = null; + } + } + + obj[_pre + _touchstart + id] = onTouchStart; + obj[_pre + _touchend + id] = onTouchEnd; + obj[_pre + 'dblclick' + id] = handler; + + obj.addEventListener(_touchstart, onTouchStart, false); + obj.addEventListener(_touchend, onTouchEnd, false); + + // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse), + // the browser doesn't fire touchend/pointerup events but does fire + // native dblclicks. See #4127. + // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180. + obj.addEventListener('dblclick', handler, false); + + return this; +} + +function removeDoubleTapListener(obj, id) { + var touchstart = obj[_pre + _touchstart + id], + touchend = obj[_pre + _touchend + id], + dblclick = obj[_pre + 'dblclick' + id]; + + obj.removeEventListener(_touchstart, touchstart, false); + obj.removeEventListener(_touchend, touchend, false); + if (!edge) { + obj.removeEventListener('dblclick', dblclick, false); + } + + return this; +} + +/* + * @namespace DomUtil + * + * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) + * tree, used by Leaflet internally. + * + * Most functions expecting or returning a `HTMLElement` also work for + * SVG elements. The only difference is that classes refer to CSS classes + * in HTML and SVG classes in SVG. + */ + + +// @property TRANSFORM: String +// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit). +var TRANSFORM = testProp( + ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']); + +// webkitTransition comes first because some browser versions that drop vendor prefix don't do +// the same for the transitionend event, in particular the Android 4.1 stock browser + +// @property TRANSITION: String +// Vendor-prefixed transition style name. +var TRANSITION = testProp( + ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); + +// @property TRANSITION_END: String +// Vendor-prefixed transitionend event name. +var TRANSITION_END = + TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend'; + + +// @function get(id: String|HTMLElement): HTMLElement +// Returns an element given its DOM id, or returns the element itself +// if it was passed directly. +function get(id) { + return typeof id === 'string' ? document.getElementById(id) : id; +} + +// @function getStyle(el: HTMLElement, styleAttrib: String): String +// Returns the value for a certain style attribute on an element, +// including computed values or values set through CSS. +function getStyle(el, style) { + var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); + + if ((!value || value === 'auto') && document.defaultView) { + var css = document.defaultView.getComputedStyle(el, null); + value = css ? css[style] : null; + } + return value === 'auto' ? null : value; +} + +// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement +// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. +function create$1(tagName, className, container) { + var el = document.createElement(tagName); + el.className = className || ''; + + if (container) { + container.appendChild(el); + } + return el; +} + +// @function remove(el: HTMLElement) +// Removes `el` from its parent element +function remove(el) { + var parent = el.parentNode; + if (parent) { + parent.removeChild(el); + } +} + +// @function empty(el: HTMLElement) +// Removes all of `el`'s children elements from `el` +function empty(el) { + while (el.firstChild) { + el.removeChild(el.firstChild); + } +} + +// @function toFront(el: HTMLElement) +// Makes `el` the last child of its parent, so it renders in front of the other children. +function toFront(el) { + var parent = el.parentNode; + if (parent && parent.lastChild !== el) { + parent.appendChild(el); + } +} + +// @function toBack(el: HTMLElement) +// Makes `el` the first child of its parent, so it renders behind the other children. +function toBack(el) { + var parent = el.parentNode; + if (parent && parent.firstChild !== el) { + parent.insertBefore(el, parent.firstChild); + } +} + +// @function hasClass(el: HTMLElement, name: String): Boolean +// Returns `true` if the element's class attribute contains `name`. +function hasClass(el, name) { + if (el.classList !== undefined) { + return el.classList.contains(name); + } + var className = getClass(el); + return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); +} + +// @function addClass(el: HTMLElement, name: String) +// Adds `name` to the element's class attribute. +function addClass(el, name) { + if (el.classList !== undefined) { + var classes = splitWords(name); + for (var i = 0, len = classes.length; i < len; i++) { + el.classList.add(classes[i]); + } + } else if (!hasClass(el, name)) { + var className = getClass(el); + setClass(el, (className ? className + ' ' : '') + name); + } +} + +// @function removeClass(el: HTMLElement, name: String) +// Removes `name` from the element's class attribute. +function removeClass(el, name) { + if (el.classList !== undefined) { + el.classList.remove(name); + } else { + setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' '))); + } +} + +// @function setClass(el: HTMLElement, name: String) +// Sets the element's class. +function setClass(el, name) { + if (el.className.baseVal === undefined) { + el.className = name; + } else { + // in case of SVG element + el.className.baseVal = name; + } +} + +// @function getClass(el: HTMLElement): String +// Returns the element's class. +function getClass(el) { + // Check if the element is an SVGElementInstance and use the correspondingElement instead + // (Required for linked SVG elements in IE11.) + if (el.correspondingElement) { + el = el.correspondingElement; + } + return el.className.baseVal === undefined ? el.className : el.className.baseVal; +} + +// @function setOpacity(el: HTMLElement, opacity: Number) +// Set the opacity of an element (including old IE support). +// `opacity` must be a number from `0` to `1`. +function setOpacity(el, value) { + if ('opacity' in el.style) { + el.style.opacity = value; + } else if ('filter' in el.style) { + _setOpacityIE(el, value); + } +} + +function _setOpacityIE(el, value) { + var filter = false, + filterName = 'DXImageTransform.Microsoft.Alpha'; + + // filters collection throws an error if we try to retrieve a filter that doesn't exist + try { + filter = el.filters.item(filterName); + } catch (e) { + // don't set opacity to 1 if we haven't already set an opacity, + // it isn't needed and breaks transparent pngs. + if (value === 1) { return; } + } + + value = Math.round(value * 100); + + if (filter) { + filter.Enabled = (value !== 100); + filter.Opacity = value; + } else { + el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; + } +} + +// @function testProp(props: String[]): String|false +// Goes through the array of style names and returns the first name +// that is a valid style name for an element. If no such name is found, +// it returns false. Useful for vendor-prefixed styles like `transform`. +function testProp(props) { + var style = document.documentElement.style; + + for (var i = 0; i < props.length; i++) { + if (props[i] in style) { + return props[i]; + } + } + return false; +} + +// @function setTransform(el: HTMLElement, offset: Point, scale?: Number) +// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels +// and optionally scaled by `scale`. Does not have an effect if the +// browser doesn't support 3D CSS transforms. +function setTransform(el, offset, scale) { + var pos = offset || new Point(0, 0); + + el.style[TRANSFORM] = + (ie3d ? + 'translate(' + pos.x + 'px,' + pos.y + 'px)' : + 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + + (scale ? ' scale(' + scale + ')' : ''); +} + +// @function setPosition(el: HTMLElement, position: Point) +// Sets the position of `el` to coordinates specified by `position`, +// using CSS translate or top/left positioning depending on the browser +// (used by Leaflet internally to position its layers). +function setPosition(el, point) { + + /*eslint-disable */ + el._leaflet_pos = point; + /* eslint-enable */ + + if (any3d) { + setTransform(el, point); + } else { + el.style.left = point.x + 'px'; + el.style.top = point.y + 'px'; + } +} + +// @function getPosition(el: HTMLElement): Point +// Returns the coordinates of an element previously positioned with setPosition. +function getPosition(el) { + // this method is only used for elements previously positioned using setPosition, + // so it's safe to cache the position for performance + + return el._leaflet_pos || new Point(0, 0); +} + +// @function disableTextSelection() +// Prevents the user from generating `selectstart` DOM events, usually generated +// when the user drags the mouse through a page with text. Used internally +// by Leaflet to override the behaviour of any click-and-drag interaction on +// the map. Affects drag interactions on the whole document. + +// @function enableTextSelection() +// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). +var disableTextSelection; +var enableTextSelection; +var _userSelect; +if ('onselectstart' in document) { + disableTextSelection = function () { + on(window, 'selectstart', preventDefault); + }; + enableTextSelection = function () { + off(window, 'selectstart', preventDefault); + }; +} else { + var userSelectProperty = testProp( + ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); + + disableTextSelection = function () { + if (userSelectProperty) { + var style = document.documentElement.style; + _userSelect = style[userSelectProperty]; + style[userSelectProperty] = 'none'; + } + }; + enableTextSelection = function () { + if (userSelectProperty) { + document.documentElement.style[userSelectProperty] = _userSelect; + _userSelect = undefined; + } + }; +} + +// @function disableImageDrag() +// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but +// for `dragstart` DOM events, usually generated when the user drags an image. +function disableImageDrag() { + on(window, 'dragstart', preventDefault); +} + +// @function enableImageDrag() +// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). +function enableImageDrag() { + off(window, 'dragstart', preventDefault); +} + +var _outlineElement; +var _outlineStyle; +// @function preventOutline(el: HTMLElement) +// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) +// of the element `el` invisible. Used internally by Leaflet to prevent +// focusable elements from displaying an outline when the user performs a +// drag interaction on them. +function preventOutline(element) { + while (element.tabIndex === -1) { + element = element.parentNode; + } + if (!element.style) { return; } + restoreOutline(); + _outlineElement = element; + _outlineStyle = element.style.outline; + element.style.outline = 'none'; + on(window, 'keydown', restoreOutline); +} + +// @function restoreOutline() +// Cancels the effects of a previous [`L.DomUtil.preventOutline`](). +function restoreOutline() { + if (!_outlineElement) { return; } + _outlineElement.style.outline = _outlineStyle; + _outlineElement = undefined; + _outlineStyle = undefined; + off(window, 'keydown', restoreOutline); +} + +// @function getSizedParentNode(el: HTMLElement): HTMLElement +// Finds the closest parent node which size (width and height) is not null. +function getSizedParentNode(element) { + do { + element = element.parentNode; + } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body); + return element; +} + +// @function getScale(el: HTMLElement): Object +// Computes the CSS scale currently applied on the element. +// Returns an object with `x` and `y` members as horizontal and vertical scales respectively, +// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect). +function getScale(element) { + var rect = element.getBoundingClientRect(); // Read-only in old browsers. + + return { + x: rect.width / element.offsetWidth || 1, + y: rect.height / element.offsetHeight || 1, + boundingClientRect: rect + }; +} + + +var DomUtil = (Object.freeze || Object)({ + TRANSFORM: TRANSFORM, + TRANSITION: TRANSITION, + TRANSITION_END: TRANSITION_END, + get: get, + getStyle: getStyle, + create: create$1, + remove: remove, + empty: empty, + toFront: toFront, + toBack: toBack, + hasClass: hasClass, + addClass: addClass, + removeClass: removeClass, + setClass: setClass, + getClass: getClass, + setOpacity: setOpacity, + testProp: testProp, + setTransform: setTransform, + setPosition: setPosition, + getPosition: getPosition, + disableTextSelection: disableTextSelection, + enableTextSelection: enableTextSelection, + disableImageDrag: disableImageDrag, + enableImageDrag: enableImageDrag, + preventOutline: preventOutline, + restoreOutline: restoreOutline, + getSizedParentNode: getSizedParentNode, + getScale: getScale +}); + +/* + * @namespace DomEvent + * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. + */ + +// Inspired by John Resig, Dean Edwards and YUI addEvent implementations. + +// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this +// Adds a listener function (`fn`) to a particular DOM event type of the +// element `el`. You can optionally specify the context of the listener +// (object the `this` keyword will point to). You can also pass several +// space-separated types (e.g. `'click dblclick'`). + +// @alternative +// @function on(el: HTMLElement, eventMap: Object, context?: Object): this +// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` +function on(obj, types, fn, context) { + + if (typeof types === 'object') { + for (var type in types) { + addOne(obj, type, types[type], fn); + } + } else { + types = splitWords(types); + + for (var i = 0, len = types.length; i < len; i++) { + addOne(obj, types[i], fn, context); + } + } + + return this; +} + +var eventsKey = '_leaflet_events'; + +// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this +// Removes a previously added listener function. +// Note that if you passed a custom context to on, you must pass the same +// context to `off` in order to remove the listener. + +// @alternative +// @function off(el: HTMLElement, eventMap: Object, context?: Object): this +// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` +function off(obj, types, fn, context) { + + if (typeof types === 'object') { + for (var type in types) { + removeOne(obj, type, types[type], fn); + } + } else if (types) { + types = splitWords(types); + + for (var i = 0, len = types.length; i < len; i++) { + removeOne(obj, types[i], fn, context); + } + } else { + for (var j in obj[eventsKey]) { + removeOne(obj, j, obj[eventsKey][j]); + } + delete obj[eventsKey]; + } + + return this; +} + +function addOne(obj, type, fn, context) { + var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''); + + if (obj[eventsKey] && obj[eventsKey][id]) { return this; } + + var handler = function (e) { + return fn.call(context || obj, e || window.event); + }; + + var originalHandler = handler; + + if (pointer && type.indexOf('touch') === 0) { + // Needs DomEvent.Pointer.js + addPointerListener(obj, type, handler, id); + + } else if (touch && (type === 'dblclick') && addDoubleTapListener && + !(pointer && chrome)) { + // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener + // See #5180 + addDoubleTapListener(obj, handler, id); + + } else if ('addEventListener' in obj) { + + if (type === 'mousewheel') { + obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); + + } else if ((type === 'mouseenter') || (type === 'mouseleave')) { + handler = function (e) { + e = e || window.event; + if (isExternalTarget(obj, e)) { + originalHandler(e); + } + }; + obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false); + + } else { + if (type === 'click' && android) { + handler = function (e) { + filterClick(e, originalHandler); + }; + } + obj.addEventListener(type, handler, false); + } + + } else if ('attachEvent' in obj) { + obj.attachEvent('on' + type, handler); + } + + obj[eventsKey] = obj[eventsKey] || {}; + obj[eventsKey][id] = handler; +} + +function removeOne(obj, type, fn, context) { + + var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''), + handler = obj[eventsKey] && obj[eventsKey][id]; + + if (!handler) { return this; } + + if (pointer && type.indexOf('touch') === 0) { + removePointerListener(obj, type, id); + + } else if (touch && (type === 'dblclick') && removeDoubleTapListener && + !(pointer && chrome)) { + removeDoubleTapListener(obj, id); + + } else if ('removeEventListener' in obj) { + + if (type === 'mousewheel') { + obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); + + } else { + obj.removeEventListener( + type === 'mouseenter' ? 'mouseover' : + type === 'mouseleave' ? 'mouseout' : type, handler, false); + } + + } else if ('detachEvent' in obj) { + obj.detachEvent('on' + type, handler); + } + + obj[eventsKey][id] = null; +} + +// @function stopPropagation(ev: DOMEvent): this +// Stop the given event from propagation to parent elements. Used inside the listener functions: +// ```js +// L.DomEvent.on(div, 'click', function (ev) { +// L.DomEvent.stopPropagation(ev); +// }); +// ``` +function stopPropagation(e) { + + if (e.stopPropagation) { + e.stopPropagation(); + } else if (e.originalEvent) { // In case of Leaflet event. + e.originalEvent._stopped = true; + } else { + e.cancelBubble = true; + } + skipped(e); + + return this; +} + +// @function disableScrollPropagation(el: HTMLElement): this +// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants). +function disableScrollPropagation(el) { + addOne(el, 'mousewheel', stopPropagation); + return this; +} + +// @function disableClickPropagation(el: HTMLElement): this +// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`, +// `'mousedown'` and `'touchstart'` events (plus browser variants). +function disableClickPropagation(el) { + on(el, 'mousedown touchstart dblclick', stopPropagation); + addOne(el, 'click', fakeStop); + return this; +} + +// @function preventDefault(ev: DOMEvent): this +// Prevents the default action of the DOM Event `ev` from happening (such as +// following a link in the href of the a element, or doing a POST request +// with page reload when a `
` is submitted). +// Use it inside listener functions. +function preventDefault(e) { + if (e.preventDefault) { + e.preventDefault(); + } else { + e.returnValue = false; + } + return this; +} + +// @function stop(ev: DOMEvent): this +// Does `stopPropagation` and `preventDefault` at the same time. +function stop(e) { + preventDefault(e); + stopPropagation(e); + return this; +} + +// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point +// Gets normalized mouse position from a DOM event relative to the +// `container` (border excluded) or to the whole page if not specified. +function getMousePosition(e, container) { + if (!container) { + return new Point(e.clientX, e.clientY); + } + + var scale = getScale(container), + offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y) + + return new Point( + // offset.left/top values are in page scale (like clientX/Y), + // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies). + (e.clientX - offset.left) / scale.x - container.clientLeft, + (e.clientY - offset.top) / scale.y - container.clientTop + ); +} + +// Chrome on Win scrolls double the pixels as in other platforms (see #4538), +// and Firefox scrolls device pixels, not CSS pixels +var wheelPxFactor = + (win && chrome) ? 2 * window.devicePixelRatio : + gecko ? window.devicePixelRatio : 1; + +// @function getWheelDelta(ev: DOMEvent): Number +// Gets normalized wheel delta from a mousewheel DOM event, in vertical +// pixels scrolled (negative if scrolling down). +// Events from pointing devices without precise scrolling are mapped to +// a best guess of 60 pixels. +function getWheelDelta(e) { + return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta + (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels + (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines + (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages + (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events + e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels + (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines + e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages + 0; +} + +var skipEvents = {}; + +function fakeStop(e) { + // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e) + skipEvents[e.type] = true; +} + +function skipped(e) { + var events = skipEvents[e.type]; + // reset when checking, as it's only used in map container and propagates outside of the map + skipEvents[e.type] = false; + return events; +} + +// check if element really left/entered the event target (for mouseenter/mouseleave) +function isExternalTarget(el, e) { + + var related = e.relatedTarget; + + if (!related) { return true; } + + try { + while (related && (related !== el)) { + related = related.parentNode; + } + } catch (err) { + return false; + } + return (related !== el); +} + +var lastClick; + +// this is a horrible workaround for a bug in Android where a single touch triggers two click events +function filterClick(e, handler) { + var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)), + elapsed = lastClick && (timeStamp - lastClick); + + // are they closer together than 500ms yet more than 100ms? + // Android typically triggers them ~300ms apart while multiple listeners + // on the same event should be triggered far faster; + // or check if click is simulated on the element, and if it is, reject any non-simulated events + + if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { + stop(e); + return; + } + lastClick = timeStamp; + + handler(e); +} + + + + +var DomEvent = (Object.freeze || Object)({ + on: on, + off: off, + stopPropagation: stopPropagation, + disableScrollPropagation: disableScrollPropagation, + disableClickPropagation: disableClickPropagation, + preventDefault: preventDefault, + stop: stop, + getMousePosition: getMousePosition, + getWheelDelta: getWheelDelta, + fakeStop: fakeStop, + skipped: skipped, + isExternalTarget: isExternalTarget, + addListener: on, + removeListener: off +}); + +/* + * @class PosAnimation + * @aka L.PosAnimation + * @inherits Evented + * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9. + * + * @example + * ```js + * var fx = new L.PosAnimation(); + * fx.run(el, [300, 500], 0.5); + * ``` + * + * @constructor L.PosAnimation() + * Creates a `PosAnimation` object. + * + */ + +var PosAnimation = Evented.extend({ + + // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number) + // Run an animation of a given element to a new position, optionally setting + // duration in seconds (`0.25` by default) and easing linearity factor (3rd + // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1), + // `0.5` by default). + run: function (el, newPos, duration, easeLinearity) { + this.stop(); + + this._el = el; + this._inProgress = true; + this._duration = duration || 0.25; + this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); + + this._startPos = getPosition(el); + this._offset = newPos.subtract(this._startPos); + this._startTime = +new Date(); + + // @event start: Event + // Fired when the animation starts + this.fire('start'); + + this._animate(); + }, + + // @method stop() + // Stops the animation (if currently running). + stop: function () { + if (!this._inProgress) { return; } + + this._step(true); + this._complete(); + }, + + _animate: function () { + // animation loop + this._animId = requestAnimFrame(this._animate, this); + this._step(); + }, + + _step: function (round) { + var elapsed = (+new Date()) - this._startTime, + duration = this._duration * 1000; + + if (elapsed < duration) { + this._runFrame(this._easeOut(elapsed / duration), round); + } else { + this._runFrame(1); + this._complete(); + } + }, + + _runFrame: function (progress, round) { + var pos = this._startPos.add(this._offset.multiplyBy(progress)); + if (round) { + pos._round(); + } + setPosition(this._el, pos); + + // @event step: Event + // Fired continuously during the animation. + this.fire('step'); + }, + + _complete: function () { + cancelAnimFrame(this._animId); + + this._inProgress = false; + // @event end: Event + // Fired when the animation ends. + this.fire('end'); + }, + + _easeOut: function (t) { + return 1 - Math.pow(1 - t, this._easeOutPower); + } +}); + +/* + * @class Map + * @aka L.Map + * @inherits Evented + * + * The central class of the API — it is used to create a map on a page and manipulate it. + * + * @example + * + * ```js + * // initialize the map on the "map" div with a given center and zoom + * var map = L.map('map', { + * center: [51.505, -0.09], + * zoom: 13 + * }); + * ``` + * + */ + +var Map = Evented.extend({ + + options: { + // @section Map State Options + // @option crs: CRS = L.CRS.EPSG3857 + // The [Coordinate Reference System](#crs) to use. Don't change this if you're not + // sure what it means. + crs: EPSG3857, + + // @option center: LatLng = undefined + // Initial geographic center of the map + center: undefined, + + // @option zoom: Number = undefined + // Initial map zoom level + zoom: undefined, + + // @option minZoom: Number = * + // Minimum zoom level of the map. + // If not specified and at least one `GridLayer` or `TileLayer` is in the map, + // the lowest of their `minZoom` options will be used instead. + minZoom: undefined, + + // @option maxZoom: Number = * + // Maximum zoom level of the map. + // If not specified and at least one `GridLayer` or `TileLayer` is in the map, + // the highest of their `maxZoom` options will be used instead. + maxZoom: undefined, + + // @option layers: Layer[] = [] + // Array of layers that will be added to the map initially + layers: [], + + // @option maxBounds: LatLngBounds = null + // When this option is set, the map restricts the view to the given + // geographical bounds, bouncing the user back if the user tries to pan + // outside the view. To set the restriction dynamically, use + // [`setMaxBounds`](#map-setmaxbounds) method. + maxBounds: undefined, + + // @option renderer: Renderer = * + // The default method for drawing vector layers on the map. `L.SVG` + // or `L.Canvas` by default depending on browser support. + renderer: undefined, + + + // @section Animation Options + // @option zoomAnimation: Boolean = true + // Whether the map zoom animation is enabled. By default it's enabled + // in all browsers that support CSS3 Transitions except Android. + zoomAnimation: true, + + // @option zoomAnimationThreshold: Number = 4 + // Won't animate zoom if the zoom difference exceeds this value. + zoomAnimationThreshold: 4, + + // @option fadeAnimation: Boolean = true + // Whether the tile fade animation is enabled. By default it's enabled + // in all browsers that support CSS3 Transitions except Android. + fadeAnimation: true, + + // @option markerZoomAnimation: Boolean = true + // Whether markers animate their zoom with the zoom animation, if disabled + // they will disappear for the length of the animation. By default it's + // enabled in all browsers that support CSS3 Transitions except Android. + markerZoomAnimation: true, + + // @option transform3DLimit: Number = 2^23 + // Defines the maximum size of a CSS translation transform. The default + // value should not be changed unless a web browser positions layers in + // the wrong place after doing a large `panBy`. + transform3DLimit: 8388608, // Precision limit of a 32-bit float + + // @section Interaction Options + // @option zoomSnap: Number = 1 + // Forces the map's zoom level to always be a multiple of this, particularly + // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom. + // By default, the zoom level snaps to the nearest integer; lower values + // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0` + // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom. + zoomSnap: 1, + + // @option zoomDelta: Number = 1 + // Controls how much the map's zoom level will change after a + // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+` + // or `-` on the keyboard, or using the [zoom controls](#control-zoom). + // Values smaller than `1` (e.g. `0.5`) allow for greater granularity. + zoomDelta: 1, + + // @option trackResize: Boolean = true + // Whether the map automatically handles browser window resize to update itself. + trackResize: true + }, + + initialize: function (id, options) { // (HTMLElement or String, Object) + options = setOptions(this, options); + + // Make sure to assign internal flags at the beginning, + // to avoid inconsistent state in some edge cases. + this._handlers = []; + this._layers = {}; + this._zoomBoundLayers = {}; + this._sizeChanged = true; + + this._initContainer(id); + this._initLayout(); + + // hack for https://github.com/Leaflet/Leaflet/issues/1980 + this._onResize = bind(this._onResize, this); + + this._initEvents(); + + if (options.maxBounds) { + this.setMaxBounds(options.maxBounds); + } + + if (options.zoom !== undefined) { + this._zoom = this._limitZoom(options.zoom); + } + + if (options.center && options.zoom !== undefined) { + this.setView(toLatLng(options.center), options.zoom, {reset: true}); + } + + this.callInitHooks(); + + // don't animate on browsers without hardware-accelerated transitions or old Android/Opera + this._zoomAnimated = TRANSITION && any3d && !mobileOpera && + this.options.zoomAnimation; + + // zoom transitions run with the same duration for all layers, so if one of transitionend events + // happens after starting zoom animation (propagating to the map pane), we know that it ended globally + if (this._zoomAnimated) { + this._createAnimProxy(); + on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this); + } + + this._addLayers(this.options.layers); + }, + + + // @section Methods for modifying map state + + // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this + // Sets the view of the map (geographical center and zoom) with the given + // animation options. + setView: function (center, zoom, options) { + + zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); + center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds); + options = options || {}; + + this._stop(); + + if (this._loaded && !options.reset && options !== true) { + + if (options.animate !== undefined) { + options.zoom = extend({animate: options.animate}, options.zoom); + options.pan = extend({animate: options.animate, duration: options.duration}, options.pan); + } + + // try animating pan or zoom + var moved = (this._zoom !== zoom) ? + this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : + this._tryAnimatedPan(center, options.pan); + + if (moved) { + // prevent resize handler call, the view will refresh after animation anyway + clearTimeout(this._sizeTimer); + return this; + } + } + + // animation didn't start, just reset the map view + this._resetView(center, zoom); + + return this; + }, + + // @method setZoom(zoom: Number, options?: Zoom/pan options): this + // Sets the zoom of the map. + setZoom: function (zoom, options) { + if (!this._loaded) { + this._zoom = zoom; + return this; + } + return this.setView(this.getCenter(), zoom, {zoom: options}); + }, + + // @method zoomIn(delta?: Number, options?: Zoom options): this + // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). + zoomIn: function (delta, options) { + delta = delta || (any3d ? this.options.zoomDelta : 1); + return this.setZoom(this._zoom + delta, options); + }, + + // @method zoomOut(delta?: Number, options?: Zoom options): this + // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). + zoomOut: function (delta, options) { + delta = delta || (any3d ? this.options.zoomDelta : 1); + return this.setZoom(this._zoom - delta, options); + }, + + // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this + // Zooms the map while keeping a specified geographical point on the map + // stationary (e.g. used internally for scroll zoom and double-click zoom). + // @alternative + // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this + // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary. + setZoomAround: function (latlng, zoom, options) { + var scale = this.getZoomScale(zoom), + viewHalf = this.getSize().divideBy(2), + containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng), + + centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), + newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); + + return this.setView(newCenter, zoom, {zoom: options}); + }, + + _getBoundsCenterZoom: function (bounds, options) { + + options = options || {}; + bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds); + + var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), + paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), + + zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); + + zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom; + + if (zoom === Infinity) { + return { + center: bounds.getCenter(), + zoom: zoom + }; + } + + var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), + + swPoint = this.project(bounds.getSouthWest(), zoom), + nePoint = this.project(bounds.getNorthEast(), zoom), + center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); + + return { + center: center, + zoom: zoom + }; + }, + + // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this + // Sets a map view that contains the given geographical bounds with the + // maximum zoom level possible. + fitBounds: function (bounds, options) { + + bounds = toLatLngBounds(bounds); + + if (!bounds.isValid()) { + throw new Error('Bounds are not valid.'); + } + + var target = this._getBoundsCenterZoom(bounds, options); + return this.setView(target.center, target.zoom, options); + }, + + // @method fitWorld(options?: fitBounds options): this + // Sets a map view that mostly contains the whole world with the maximum + // zoom level possible. + fitWorld: function (options) { + return this.fitBounds([[-90, -180], [90, 180]], options); + }, + + // @method panTo(latlng: LatLng, options?: Pan options): this + // Pans the map to a given center. + panTo: function (center, options) { // (LatLng) + return this.setView(center, this._zoom, {pan: options}); + }, + + // @method panBy(offset: Point, options?: Pan options): this + // Pans the map by a given number of pixels (animated). + panBy: function (offset, options) { + offset = toPoint(offset).round(); + options = options || {}; + + if (!offset.x && !offset.y) { + return this.fire('moveend'); + } + // If we pan too far, Chrome gets issues with tiles + // and makes them disappear or appear in the wrong place (slightly offset) #2602 + if (options.animate !== true && !this.getSize().contains(offset)) { + this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom()); + return this; + } + + if (!this._panAnim) { + this._panAnim = new PosAnimation(); + + this._panAnim.on({ + 'step': this._onPanTransitionStep, + 'end': this._onPanTransitionEnd + }, this); + } + + // don't fire movestart if animating inertia + if (!options.noMoveStart) { + this.fire('movestart'); + } + + // animate pan unless animate: false specified + if (options.animate !== false) { + addClass(this._mapPane, 'leaflet-pan-anim'); + + var newPos = this._getMapPanePos().subtract(offset).round(); + this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); + } else { + this._rawPanBy(offset); + this.fire('move').fire('moveend'); + } + + return this; + }, + + // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this + // Sets the view of the map (geographical center and zoom) performing a smooth + // pan-zoom animation. + flyTo: function (targetCenter, targetZoom, options) { + + options = options || {}; + if (options.animate === false || !any3d) { + return this.setView(targetCenter, targetZoom, options); + } + + this._stop(); + + var from = this.project(this.getCenter()), + to = this.project(targetCenter), + size = this.getSize(), + startZoom = this._zoom; + + targetCenter = toLatLng(targetCenter); + targetZoom = targetZoom === undefined ? startZoom : targetZoom; + + var w0 = Math.max(size.x, size.y), + w1 = w0 * this.getZoomScale(startZoom, targetZoom), + u1 = (to.distanceTo(from)) || 1, + rho = 1.42, + rho2 = rho * rho; + + function r(i) { + var s1 = i ? -1 : 1, + s2 = i ? w1 : w0, + t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1, + b1 = 2 * s2 * rho2 * u1, + b = t1 / b1, + sq = Math.sqrt(b * b + 1) - b; + + // workaround for floating point precision bug when sq = 0, log = -Infinite, + // thus triggering an infinite loop in flyTo + var log = sq < 0.000000001 ? -18 : Math.log(sq); + + return log; + } + + function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; } + function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; } + function tanh(n) { return sinh(n) / cosh(n); } + + var r0 = r(0); + + function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); } + function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; } + + function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); } + + var start = Date.now(), + S = (r(1) - r0) / rho, + duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8; + + function frame() { + var t = (Date.now() - start) / duration, + s = easeOut(t) * S; + + if (t <= 1) { + this._flyToFrame = requestAnimFrame(frame, this); + + this._move( + this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), + this.getScaleZoom(w0 / w(s), startZoom), + {flyTo: true}); + + } else { + this + ._move(targetCenter, targetZoom) + ._moveEnd(true); + } + } + + this._moveStart(true, options.noMoveStart); + + frame.call(this); + return this; + }, + + // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this + // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto), + // but takes a bounds parameter like [`fitBounds`](#map-fitbounds). + flyToBounds: function (bounds, options) { + var target = this._getBoundsCenterZoom(bounds, options); + return this.flyTo(target.center, target.zoom, options); + }, + + // @method setMaxBounds(bounds: Bounds): this + // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). + setMaxBounds: function (bounds) { + bounds = toLatLngBounds(bounds); + + if (!bounds.isValid()) { + this.options.maxBounds = null; + return this.off('moveend', this._panInsideMaxBounds); + } else if (this.options.maxBounds) { + this.off('moveend', this._panInsideMaxBounds); + } + + this.options.maxBounds = bounds; + + if (this._loaded) { + this._panInsideMaxBounds(); + } + + return this.on('moveend', this._panInsideMaxBounds); + }, + + // @method setMinZoom(zoom: Number): this + // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option). + setMinZoom: function (zoom) { + var oldZoom = this.options.minZoom; + this.options.minZoom = zoom; + + if (this._loaded && oldZoom !== zoom) { + this.fire('zoomlevelschange'); + + if (this.getZoom() < this.options.minZoom) { + return this.setZoom(zoom); + } + } + + return this; + }, + + // @method setMaxZoom(zoom: Number): this + // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option). + setMaxZoom: function (zoom) { + var oldZoom = this.options.maxZoom; + this.options.maxZoom = zoom; + + if (this._loaded && oldZoom !== zoom) { + this.fire('zoomlevelschange'); + + if (this.getZoom() > this.options.maxZoom) { + return this.setZoom(zoom); + } + } + + return this; + }, + + // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this + // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any. + panInsideBounds: function (bounds, options) { + this._enforcingBounds = true; + var center = this.getCenter(), + newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds)); + + if (!center.equals(newCenter)) { + this.panTo(newCenter, options); + } + + this._enforcingBounds = false; + return this; + }, + + // @method panInside(latlng: LatLng, options?: options): this + // Pans the map the minimum amount to make the `latlng` visible. Use + // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit + // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds). + // If `latlng` is already within the (optionally padded) display bounds, + // the map will not be panned. + panInside: function (latlng, options) { + options = options || {}; + + var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), + paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), + center = this.getCenter(), + pixelCenter = this.project(center), + pixelPoint = this.project(latlng), + pixelBounds = this.getPixelBounds(), + halfPixelBounds = pixelBounds.getSize().divideBy(2), + paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]); + + if (!paddedBounds.contains(pixelPoint)) { + this._enforcingBounds = true; + var diff = pixelCenter.subtract(pixelPoint), + newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y); + + if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) { + newCenter.x = pixelCenter.x - diff.x; + if (diff.x > 0) { + newCenter.x += halfPixelBounds.x - paddingTL.x; + } else { + newCenter.x -= halfPixelBounds.x - paddingBR.x; + } + } + if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) { + newCenter.y = pixelCenter.y - diff.y; + if (diff.y > 0) { + newCenter.y += halfPixelBounds.y - paddingTL.y; + } else { + newCenter.y -= halfPixelBounds.y - paddingBR.y; + } + } + this.panTo(this.unproject(newCenter), options); + this._enforcingBounds = false; + } + return this; + }, + + // @method invalidateSize(options: Zoom/pan options): this + // Checks if the map container size changed and updates the map if so — + // call it after you've changed the map size dynamically, also animating + // pan by default. If `options.pan` is `false`, panning will not occur. + // If `options.debounceMoveend` is `true`, it will delay `moveend` event so + // that it doesn't happen often even if the method is called many + // times in a row. + + // @alternative + // @method invalidateSize(animate: Boolean): this + // Checks if the map container size changed and updates the map if so — + // call it after you've changed the map size dynamically, also animating + // pan by default. + invalidateSize: function (options) { + if (!this._loaded) { return this; } + + options = extend({ + animate: false, + pan: true + }, options === true ? {animate: true} : options); + + var oldSize = this.getSize(); + this._sizeChanged = true; + this._lastCenter = null; + + var newSize = this.getSize(), + oldCenter = oldSize.divideBy(2).round(), + newCenter = newSize.divideBy(2).round(), + offset = oldCenter.subtract(newCenter); + + if (!offset.x && !offset.y) { return this; } + + if (options.animate && options.pan) { + this.panBy(offset); + + } else { + if (options.pan) { + this._rawPanBy(offset); + } + + this.fire('move'); + + if (options.debounceMoveend) { + clearTimeout(this._sizeTimer); + this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200); + } else { + this.fire('moveend'); + } + } + + // @section Map state change events + // @event resize: ResizeEvent + // Fired when the map is resized. + return this.fire('resize', { + oldSize: oldSize, + newSize: newSize + }); + }, + + // @section Methods for modifying map state + // @method stop(): this + // Stops the currently running `panTo` or `flyTo` animation, if any. + stop: function () { + this.setZoom(this._limitZoom(this._zoom)); + if (!this.options.zoomSnap) { + this.fire('viewreset'); + } + return this._stop(); + }, + + // @section Geolocation methods + // @method locate(options?: Locate options): this + // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound) + // event with location data on success or a [`locationerror`](#map-locationerror) event on failure, + // and optionally sets the map view to the user's location with respect to + // detection accuracy (or to the world view if geolocation failed). + // Note that, if your page doesn't use HTTPS, this method will fail in + // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins)) + // See `Locate options` for more details. + locate: function (options) { + + options = this._locateOptions = extend({ + timeout: 10000, + watch: false + // setView: false + // maxZoom: + // maximumAge: 0 + // enableHighAccuracy: false + }, options); + + if (!('geolocation' in navigator)) { + this._handleGeolocationError({ + code: 0, + message: 'Geolocation not supported.' + }); + return this; + } + + var onResponse = bind(this._handleGeolocationResponse, this), + onError = bind(this._handleGeolocationError, this); + + if (options.watch) { + this._locationWatchId = + navigator.geolocation.watchPosition(onResponse, onError, options); + } else { + navigator.geolocation.getCurrentPosition(onResponse, onError, options); + } + return this; + }, + + // @method stopLocate(): this + // Stops watching location previously initiated by `map.locate({watch: true})` + // and aborts resetting the map view if map.locate was called with + // `{setView: true}`. + stopLocate: function () { + if (navigator.geolocation && navigator.geolocation.clearWatch) { + navigator.geolocation.clearWatch(this._locationWatchId); + } + if (this._locateOptions) { + this._locateOptions.setView = false; + } + return this; + }, + + _handleGeolocationError: function (error) { + var c = error.code, + message = error.message || + (c === 1 ? 'permission denied' : + (c === 2 ? 'position unavailable' : 'timeout')); + + if (this._locateOptions.setView && !this._loaded) { + this.fitWorld(); + } + + // @section Location events + // @event locationerror: ErrorEvent + // Fired when geolocation (using the [`locate`](#map-locate) method) failed. + this.fire('locationerror', { + code: c, + message: 'Geolocation error: ' + message + '.' + }); + }, + + _handleGeolocationResponse: function (pos) { + var lat = pos.coords.latitude, + lng = pos.coords.longitude, + latlng = new LatLng(lat, lng), + bounds = latlng.toBounds(pos.coords.accuracy * 2), + options = this._locateOptions; + + if (options.setView) { + var zoom = this.getBoundsZoom(bounds); + this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom); + } + + var data = { + latlng: latlng, + bounds: bounds, + timestamp: pos.timestamp + }; + + for (var i in pos.coords) { + if (typeof pos.coords[i] === 'number') { + data[i] = pos.coords[i]; + } + } + + // @event locationfound: LocationEvent + // Fired when geolocation (using the [`locate`](#map-locate) method) + // went successfully. + this.fire('locationfound', data); + }, + + // TODO Appropriate docs section? + // @section Other Methods + // @method addHandler(name: String, HandlerClass: Function): this + // Adds a new `Handler` to the map, given its name and constructor function. + addHandler: function (name, HandlerClass) { + if (!HandlerClass) { return this; } + + var handler = this[name] = new HandlerClass(this); + + this._handlers.push(handler); + + if (this.options[name]) { + handler.enable(); + } + + return this; + }, + + // @method remove(): this + // Destroys the map and clears all related event listeners. + remove: function () { + + this._initEvents(true); + + if (this._containerId !== this._container._leaflet_id) { + throw new Error('Map container is being reused by another instance'); + } + + try { + // throws error in IE6-8 + delete this._container._leaflet_id; + delete this._containerId; + } catch (e) { + /*eslint-disable */ + this._container._leaflet_id = undefined; + /* eslint-enable */ + this._containerId = undefined; + } + + if (this._locationWatchId !== undefined) { + this.stopLocate(); + } + + this._stop(); + + remove(this._mapPane); + + if (this._clearControlPos) { + this._clearControlPos(); + } + if (this._resizeRequest) { + cancelAnimFrame(this._resizeRequest); + this._resizeRequest = null; + } + + this._clearHandlers(); + + if (this._loaded) { + // @section Map state change events + // @event unload: Event + // Fired when the map is destroyed with [remove](#map-remove) method. + this.fire('unload'); + } + + var i; + for (i in this._layers) { + this._layers[i].remove(); + } + for (i in this._panes) { + remove(this._panes[i]); + } + + this._layers = []; + this._panes = []; + delete this._mapPane; + delete this._renderer; + + return this; + }, + + // @section Other Methods + // @method createPane(name: String, container?: HTMLElement): HTMLElement + // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already, + // then returns it. The pane is created as a child of `container`, or + // as a child of the main map pane if not set. + createPane: function (name, container) { + var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), + pane = create$1('div', className, container || this._mapPane); + + if (name) { + this._panes[name] = pane; + } + return pane; + }, + + // @section Methods for Getting Map State + + // @method getCenter(): LatLng + // Returns the geographical center of the map view + getCenter: function () { + this._checkIfLoaded(); + + if (this._lastCenter && !this._moved()) { + return this._lastCenter; + } + return this.layerPointToLatLng(this._getCenterLayerPoint()); + }, + + // @method getZoom(): Number + // Returns the current zoom level of the map view + getZoom: function () { + return this._zoom; + }, + + // @method getBounds(): LatLngBounds + // Returns the geographical bounds visible in the current map view + getBounds: function () { + var bounds = this.getPixelBounds(), + sw = this.unproject(bounds.getBottomLeft()), + ne = this.unproject(bounds.getTopRight()); + + return new LatLngBounds(sw, ne); + }, + + // @method getMinZoom(): Number + // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default. + getMinZoom: function () { + return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom; + }, + + // @method getMaxZoom(): Number + // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers). + getMaxZoom: function () { + return this.options.maxZoom === undefined ? + (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : + this.options.maxZoom; + }, + + // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number + // Returns the maximum zoom level on which the given bounds fit to the map + // view in its entirety. If `inside` (optional) is set to `true`, the method + // instead returns the minimum zoom level on which the map view fits into + // the given bounds in its entirety. + getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number + bounds = toLatLngBounds(bounds); + padding = toPoint(padding || [0, 0]); + + var zoom = this.getZoom() || 0, + min = this.getMinZoom(), + max = this.getMaxZoom(), + nw = bounds.getNorthWest(), + se = bounds.getSouthEast(), + size = this.getSize().subtract(padding), + boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), + snap = any3d ? this.options.zoomSnap : 1, + scalex = size.x / boundsSize.x, + scaley = size.y / boundsSize.y, + scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley); + + zoom = this.getScaleZoom(scale, zoom); + + if (snap) { + zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level + zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap; + } + + return Math.max(min, Math.min(max, zoom)); + }, + + // @method getSize(): Point + // Returns the current size of the map container (in pixels). + getSize: function () { + if (!this._size || this._sizeChanged) { + this._size = new Point( + this._container.clientWidth || 0, + this._container.clientHeight || 0); + + this._sizeChanged = false; + } + return this._size.clone(); + }, + + // @method getPixelBounds(): Bounds + // Returns the bounds of the current map view in projected pixel + // coordinates (sometimes useful in layer and overlay implementations). + getPixelBounds: function (center, zoom) { + var topLeftPoint = this._getTopLeftPoint(center, zoom); + return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); + }, + + // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to + // the map pane? "left point of the map layer" can be confusing, specially + // since there can be negative offsets. + // @method getPixelOrigin(): Point + // Returns the projected pixel coordinates of the top left point of + // the map layer (useful in custom layer and overlay implementations). + getPixelOrigin: function () { + this._checkIfLoaded(); + return this._pixelOrigin; + }, + + // @method getPixelWorldBounds(zoom?: Number): Bounds + // Returns the world's bounds in pixel coordinates for zoom level `zoom`. + // If `zoom` is omitted, the map's current zoom level is used. + getPixelWorldBounds: function (zoom) { + return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom); + }, + + // @section Other Methods + + // @method getPane(pane: String|HTMLElement): HTMLElement + // Returns a [map pane](#map-pane), given its name or its HTML element (its identity). + getPane: function (pane) { + return typeof pane === 'string' ? this._panes[pane] : pane; + }, + + // @method getPanes(): Object + // Returns a plain object containing the names of all [panes](#map-pane) as keys and + // the panes as values. + getPanes: function () { + return this._panes; + }, + + // @method getContainer: HTMLElement + // Returns the HTML element that contains the map. + getContainer: function () { + return this._container; + }, + + + // @section Conversion Methods + + // @method getZoomScale(toZoom: Number, fromZoom: Number): Number + // Returns the scale factor to be applied to a map transition from zoom level + // `fromZoom` to `toZoom`. Used internally to help with zoom animations. + getZoomScale: function (toZoom, fromZoom) { + // TODO replace with universal implementation after refactoring projections + var crs = this.options.crs; + fromZoom = fromZoom === undefined ? this._zoom : fromZoom; + return crs.scale(toZoom) / crs.scale(fromZoom); + }, + + // @method getScaleZoom(scale: Number, fromZoom: Number): Number + // Returns the zoom level that the map would end up at, if it is at `fromZoom` + // level and everything is scaled by a factor of `scale`. Inverse of + // [`getZoomScale`](#map-getZoomScale). + getScaleZoom: function (scale, fromZoom) { + var crs = this.options.crs; + fromZoom = fromZoom === undefined ? this._zoom : fromZoom; + var zoom = crs.zoom(scale * crs.scale(fromZoom)); + return isNaN(zoom) ? Infinity : zoom; + }, + + // @method project(latlng: LatLng, zoom: Number): Point + // Projects a geographical coordinate `LatLng` according to the projection + // of the map's CRS, then scales it according to `zoom` and the CRS's + // `Transformation`. The result is pixel coordinate relative to + // the CRS origin. + project: function (latlng, zoom) { + zoom = zoom === undefined ? this._zoom : zoom; + return this.options.crs.latLngToPoint(toLatLng(latlng), zoom); + }, + + // @method unproject(point: Point, zoom: Number): LatLng + // Inverse of [`project`](#map-project). + unproject: function (point, zoom) { + zoom = zoom === undefined ? this._zoom : zoom; + return this.options.crs.pointToLatLng(toPoint(point), zoom); + }, + + // @method layerPointToLatLng(point: Point): LatLng + // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), + // returns the corresponding geographical coordinate (for the current zoom level). + layerPointToLatLng: function (point) { + var projectedPoint = toPoint(point).add(this.getPixelOrigin()); + return this.unproject(projectedPoint); + }, + + // @method latLngToLayerPoint(latlng: LatLng): Point + // Given a geographical coordinate, returns the corresponding pixel coordinate + // relative to the [origin pixel](#map-getpixelorigin). + latLngToLayerPoint: function (latlng) { + var projectedPoint = this.project(toLatLng(latlng))._round(); + return projectedPoint._subtract(this.getPixelOrigin()); + }, + + // @method wrapLatLng(latlng: LatLng): LatLng + // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the + // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the + // CRS's bounds. + // By default this means longitude is wrapped around the dateline so its + // value is between -180 and +180 degrees. + wrapLatLng: function (latlng) { + return this.options.crs.wrapLatLng(toLatLng(latlng)); + }, + + // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds + // Returns a `LatLngBounds` with the same size as the given one, ensuring that + // its center is within the CRS's bounds. + // By default this means the center longitude is wrapped around the dateline so its + // value is between -180 and +180 degrees, and the majority of the bounds + // overlaps the CRS's bounds. + wrapLatLngBounds: function (latlng) { + return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng)); + }, + + // @method distance(latlng1: LatLng, latlng2: LatLng): Number + // Returns the distance between two geographical coordinates according to + // the map's CRS. By default this measures distance in meters. + distance: function (latlng1, latlng2) { + return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2)); + }, + + // @method containerPointToLayerPoint(point: Point): Point + // Given a pixel coordinate relative to the map container, returns the corresponding + // pixel coordinate relative to the [origin pixel](#map-getpixelorigin). + containerPointToLayerPoint: function (point) { // (Point) + return toPoint(point).subtract(this._getMapPanePos()); + }, + + // @method layerPointToContainerPoint(point: Point): Point + // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), + // returns the corresponding pixel coordinate relative to the map container. + layerPointToContainerPoint: function (point) { // (Point) + return toPoint(point).add(this._getMapPanePos()); + }, + + // @method containerPointToLatLng(point: Point): LatLng + // Given a pixel coordinate relative to the map container, returns + // the corresponding geographical coordinate (for the current zoom level). + containerPointToLatLng: function (point) { + var layerPoint = this.containerPointToLayerPoint(toPoint(point)); + return this.layerPointToLatLng(layerPoint); + }, + + // @method latLngToContainerPoint(latlng: LatLng): Point + // Given a geographical coordinate, returns the corresponding pixel coordinate + // relative to the map container. + latLngToContainerPoint: function (latlng) { + return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng))); + }, + + // @method mouseEventToContainerPoint(ev: MouseEvent): Point + // Given a MouseEvent object, returns the pixel coordinate relative to the + // map container where the event took place. + mouseEventToContainerPoint: function (e) { + return getMousePosition(e, this._container); + }, + + // @method mouseEventToLayerPoint(ev: MouseEvent): Point + // Given a MouseEvent object, returns the pixel coordinate relative to + // the [origin pixel](#map-getpixelorigin) where the event took place. + mouseEventToLayerPoint: function (e) { + return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); + }, + + // @method mouseEventToLatLng(ev: MouseEvent): LatLng + // Given a MouseEvent object, returns geographical coordinate where the + // event took place. + mouseEventToLatLng: function (e) { // (MouseEvent) + return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); + }, + + + // map initialization methods + + _initContainer: function (id) { + var container = this._container = get(id); + + if (!container) { + throw new Error('Map container not found.'); + } else if (container._leaflet_id) { + throw new Error('Map container is already initialized.'); + } + + on(container, 'scroll', this._onScroll, this); + this._containerId = stamp(container); + }, + + _initLayout: function () { + var container = this._container; + + this._fadeAnimated = this.options.fadeAnimation && any3d; + + addClass(container, 'leaflet-container' + + (touch ? ' leaflet-touch' : '') + + (retina ? ' leaflet-retina' : '') + + (ielt9 ? ' leaflet-oldie' : '') + + (safari ? ' leaflet-safari' : '') + + (this._fadeAnimated ? ' leaflet-fade-anim' : '')); + + var position = getStyle(container, 'position'); + + if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { + container.style.position = 'relative'; + } + + this._initPanes(); + + if (this._initControlPos) { + this._initControlPos(); + } + }, + + _initPanes: function () { + var panes = this._panes = {}; + this._paneRenderers = {}; + + // @section + // + // Panes are DOM elements used to control the ordering of layers on the map. You + // can access panes with [`map.getPane`](#map-getpane) or + // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the + // [`map.createPane`](#map-createpane) method. + // + // Every map has the following default panes that differ only in zIndex. + // + // @pane mapPane: HTMLElement = 'auto' + // Pane that contains all other map panes + + this._mapPane = this.createPane('mapPane', this._container); + setPosition(this._mapPane, new Point(0, 0)); + + // @pane tilePane: HTMLElement = 200 + // Pane for `GridLayer`s and `TileLayer`s + this.createPane('tilePane'); + // @pane overlayPane: HTMLElement = 400 + // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s + this.createPane('shadowPane'); + // @pane shadowPane: HTMLElement = 500 + // Pane for overlay shadows (e.g. `Marker` shadows) + this.createPane('overlayPane'); + // @pane markerPane: HTMLElement = 600 + // Pane for `Icon`s of `Marker`s + this.createPane('markerPane'); + // @pane tooltipPane: HTMLElement = 650 + // Pane for `Tooltip`s. + this.createPane('tooltipPane'); + // @pane popupPane: HTMLElement = 700 + // Pane for `Popup`s. + this.createPane('popupPane'); + + if (!this.options.markerZoomAnimation) { + addClass(panes.markerPane, 'leaflet-zoom-hide'); + addClass(panes.shadowPane, 'leaflet-zoom-hide'); + } + }, + + + // private methods that modify map state + + // @section Map state change events + _resetView: function (center, zoom) { + setPosition(this._mapPane, new Point(0, 0)); + + var loading = !this._loaded; + this._loaded = true; + zoom = this._limitZoom(zoom); + + this.fire('viewprereset'); + + var zoomChanged = this._zoom !== zoom; + this + ._moveStart(zoomChanged, false) + ._move(center, zoom) + ._moveEnd(zoomChanged); + + // @event viewreset: Event + // Fired when the map needs to redraw its content (this usually happens + // on map zoom or load). Very useful for creating custom overlays. + this.fire('viewreset'); + + // @event load: Event + // Fired when the map is initialized (when its center and zoom are set + // for the first time). + if (loading) { + this.fire('load'); + } + }, + + _moveStart: function (zoomChanged, noMoveStart) { + // @event zoomstart: Event + // Fired when the map zoom is about to change (e.g. before zoom animation). + // @event movestart: Event + // Fired when the view of the map starts changing (e.g. user starts dragging the map). + if (zoomChanged) { + this.fire('zoomstart'); + } + if (!noMoveStart) { + this.fire('movestart'); + } + return this; + }, + + _move: function (center, zoom, data) { + if (zoom === undefined) { + zoom = this._zoom; + } + var zoomChanged = this._zoom !== zoom; + + this._zoom = zoom; + this._lastCenter = center; + this._pixelOrigin = this._getNewPixelOrigin(center); + + // @event zoom: Event + // Fired repeatedly during any change in zoom level, including zoom + // and fly animations. + if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530 + this.fire('zoom', data); + } + + // @event move: Event + // Fired repeatedly during any movement of the map, including pan and + // fly animations. + return this.fire('move', data); + }, + + _moveEnd: function (zoomChanged) { + // @event zoomend: Event + // Fired when the map has changed, after any animations. + if (zoomChanged) { + this.fire('zoomend'); + } + + // @event moveend: Event + // Fired when the center of the map stops changing (e.g. user stopped + // dragging the map). + return this.fire('moveend'); + }, + + _stop: function () { + cancelAnimFrame(this._flyToFrame); + if (this._panAnim) { + this._panAnim.stop(); + } + return this; + }, + + _rawPanBy: function (offset) { + setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); + }, + + _getZoomSpan: function () { + return this.getMaxZoom() - this.getMinZoom(); + }, + + _panInsideMaxBounds: function () { + if (!this._enforcingBounds) { + this.panInsideBounds(this.options.maxBounds); + } + }, + + _checkIfLoaded: function () { + if (!this._loaded) { + throw new Error('Set map center and zoom first.'); + } + }, + + // DOM event handling + + // @section Interaction events + _initEvents: function (remove$$1) { + this._targets = {}; + this._targets[stamp(this._container)] = this; + + var onOff = remove$$1 ? off : on; + + // @event click: MouseEvent + // Fired when the user clicks (or taps) the map. + // @event dblclick: MouseEvent + // Fired when the user double-clicks (or double-taps) the map. + // @event mousedown: MouseEvent + // Fired when the user pushes the mouse button on the map. + // @event mouseup: MouseEvent + // Fired when the user releases the mouse button on the map. + // @event mouseover: MouseEvent + // Fired when the mouse enters the map. + // @event mouseout: MouseEvent + // Fired when the mouse leaves the map. + // @event mousemove: MouseEvent + // Fired while the mouse moves over the map. + // @event contextmenu: MouseEvent + // Fired when the user pushes the right mouse button on the map, prevents + // default browser context menu from showing if there are listeners on + // this event. Also fired on mobile when the user holds a single touch + // for a second (also called long press). + // @event keypress: KeyboardEvent + // Fired when the user presses a key from the keyboard while the map is focused. + onOff(this._container, 'click dblclick mousedown mouseup ' + + 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this); + + if (this.options.trackResize) { + onOff(window, 'resize', this._onResize, this); + } + + if (any3d && this.options.transform3DLimit) { + (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd); + } + }, + + _onResize: function () { + cancelAnimFrame(this._resizeRequest); + this._resizeRequest = requestAnimFrame( + function () { this.invalidateSize({debounceMoveend: true}); }, this); + }, + + _onScroll: function () { + this._container.scrollTop = 0; + this._container.scrollLeft = 0; + }, + + _onMoveEnd: function () { + var pos = this._getMapPanePos(); + if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) { + // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have + // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/ + this._resetView(this.getCenter(), this.getZoom()); + } + }, + + _findEventTargets: function (e, type) { + var targets = [], + target, + isHover = type === 'mouseout' || type === 'mouseover', + src = e.target || e.srcElement, + dragging = false; + + while (src) { + target = this._targets[stamp(src)]; + if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) { + // Prevent firing click after you just dragged an object. + dragging = true; + break; + } + if (target && target.listens(type, true)) { + if (isHover && !isExternalTarget(src, e)) { break; } + targets.push(target); + if (isHover) { break; } + } + if (src === this._container) { break; } + src = src.parentNode; + } + if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) { + targets = [this]; + } + return targets; + }, + + _handleDOMEvent: function (e) { + if (!this._loaded || skipped(e)) { return; } + + var type = e.type; + + if (type === 'mousedown' || type === 'keypress') { + // prevents outline when clicking on keyboard-focusable element + preventOutline(e.target || e.srcElement); + } + + this._fireDOMEvent(e, type); + }, + + _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'], + + _fireDOMEvent: function (e, type, targets) { + + if (e.type === 'click') { + // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups). + // @event preclick: MouseEvent + // Fired before mouse click on the map (sometimes useful when you + // want something to happen on click before any existing click + // handlers start running). + var synth = extend({}, e); + synth.type = 'preclick'; + this._fireDOMEvent(synth, synth.type, targets); + } + + if (e._stopped) { return; } + + // Find the layer the event is propagating from and its parents. + targets = (targets || []).concat(this._findEventTargets(e, type)); + + if (!targets.length) { return; } + + var target = targets[0]; + if (type === 'contextmenu' && target.listens(type, true)) { + preventDefault(e); + } + + var data = { + originalEvent: e + }; + + if (e.type !== 'keypress') { + var isMarker = target.getLatLng && (!target._radius || target._radius <= 10); + data.containerPoint = isMarker ? + this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); + data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); + data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint); + } + + for (var i = 0; i < targets.length; i++) { + targets[i].fire(type, data, true); + if (data.originalEvent._stopped || + (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; } + } + }, + + _draggableMoved: function (obj) { + obj = obj.dragging && obj.dragging.enabled() ? obj : this; + return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()); + }, + + _clearHandlers: function () { + for (var i = 0, len = this._handlers.length; i < len; i++) { + this._handlers[i].disable(); + } + }, + + // @section Other Methods + + // @method whenReady(fn: Function, context?: Object): this + // Runs the given function `fn` when the map gets initialized with + // a view (center and zoom) and at least one layer, or immediately + // if it's already initialized, optionally passing a function context. + whenReady: function (callback, context) { + if (this._loaded) { + callback.call(context || this, {target: this}); + } else { + this.on('load', callback, context); + } + return this; + }, + + + // private methods for getting map state + + _getMapPanePos: function () { + return getPosition(this._mapPane) || new Point(0, 0); + }, + + _moved: function () { + var pos = this._getMapPanePos(); + return pos && !pos.equals([0, 0]); + }, + + _getTopLeftPoint: function (center, zoom) { + var pixelOrigin = center && zoom !== undefined ? + this._getNewPixelOrigin(center, zoom) : + this.getPixelOrigin(); + return pixelOrigin.subtract(this._getMapPanePos()); + }, + + _getNewPixelOrigin: function (center, zoom) { + var viewHalf = this.getSize()._divideBy(2); + return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round(); + }, + + _latLngToNewLayerPoint: function (latlng, zoom, center) { + var topLeft = this._getNewPixelOrigin(center, zoom); + return this.project(latlng, zoom)._subtract(topLeft); + }, + + _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) { + var topLeft = this._getNewPixelOrigin(center, zoom); + return toBounds([ + this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft), + this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft), + this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft), + this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft) + ]); + }, + + // layer point of the current center + _getCenterLayerPoint: function () { + return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); + }, + + // offset of the specified place to the current center in pixels + _getCenterOffset: function (latlng) { + return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); + }, + + // adjust center for view to get inside bounds + _limitCenter: function (center, zoom, bounds) { + + if (!bounds) { return center; } + + var centerPoint = this.project(center, zoom), + viewHalf = this.getSize().divideBy(2), + viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), + offset = this._getBoundsOffset(viewBounds, bounds, zoom); + + // If offset is less than a pixel, ignore. + // This prevents unstable projections from getting into + // an infinite loop of tiny offsets. + if (offset.round().equals([0, 0])) { + return center; + } + + return this.unproject(centerPoint.add(offset), zoom); + }, + + // adjust offset for view to get inside bounds + _limitOffset: function (offset, bounds) { + if (!bounds) { return offset; } + + var viewBounds = this.getPixelBounds(), + newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); + + return offset.add(this._getBoundsOffset(newBounds, bounds)); + }, + + // returns offset needed for pxBounds to get inside maxBounds at a specified zoom + _getBoundsOffset: function (pxBounds, maxBounds, zoom) { + var projectedMaxBounds = toBounds( + this.project(maxBounds.getNorthEast(), zoom), + this.project(maxBounds.getSouthWest(), zoom) + ), + minOffset = projectedMaxBounds.min.subtract(pxBounds.min), + maxOffset = projectedMaxBounds.max.subtract(pxBounds.max), + + dx = this._rebound(minOffset.x, -maxOffset.x), + dy = this._rebound(minOffset.y, -maxOffset.y); + + return new Point(dx, dy); + }, + + _rebound: function (left, right) { + return left + right > 0 ? + Math.round(left - right) / 2 : + Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); + }, + + _limitZoom: function (zoom) { + var min = this.getMinZoom(), + max = this.getMaxZoom(), + snap = any3d ? this.options.zoomSnap : 1; + if (snap) { + zoom = Math.round(zoom / snap) * snap; + } + return Math.max(min, Math.min(max, zoom)); + }, + + _onPanTransitionStep: function () { + this.fire('move'); + }, + + _onPanTransitionEnd: function () { + removeClass(this._mapPane, 'leaflet-pan-anim'); + this.fire('moveend'); + }, + + _tryAnimatedPan: function (center, options) { + // difference between the new and current centers in pixels + var offset = this._getCenterOffset(center)._trunc(); + + // don't animate too far unless animate: true specified in options + if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; } + + this.panBy(offset, options); + + return true; + }, + + _createAnimProxy: function () { + + var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated'); + this._panes.mapPane.appendChild(proxy); + + this.on('zoomanim', function (e) { + var prop = TRANSFORM, + transform = this._proxy.style[prop]; + + setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); + + // workaround for case when transform is the same and so transitionend event is not fired + if (transform === this._proxy.style[prop] && this._animatingZoom) { + this._onZoomTransitionEnd(); + } + }, this); + + this.on('load moveend', function () { + var c = this.getCenter(), + z = this.getZoom(); + setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1)); + }, this); + + this._on('unload', this._destroyAnimProxy, this); + }, + + _destroyAnimProxy: function () { + remove(this._proxy); + delete this._proxy; + }, + + _catchTransitionEnd: function (e) { + if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { + this._onZoomTransitionEnd(); + } + }, + + _nothingToAnimate: function () { + return !this._container.getElementsByClassName('leaflet-zoom-animated').length; + }, + + _tryAnimatedZoom: function (center, zoom, options) { + + if (this._animatingZoom) { return true; } + + options = options || {}; + + // don't animate if disabled, not supported or zoom difference is too large + if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || + Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; } + + // offset is the pixel coords of the zoom origin relative to the current center + var scale = this.getZoomScale(zoom), + offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale); + + // don't animate if the zoom origin isn't within one screen from the current center, unless forced + if (options.animate !== true && !this.getSize().contains(offset)) { return false; } + + requestAnimFrame(function () { + this + ._moveStart(true, false) + ._animateZoom(center, zoom, true); + }, this); + + return true; + }, + + _animateZoom: function (center, zoom, startAnim, noUpdate) { + if (!this._mapPane) { return; } + + if (startAnim) { + this._animatingZoom = true; + + // remember what center/zoom to set after animation + this._animateToCenter = center; + this._animateToZoom = zoom; + + addClass(this._mapPane, 'leaflet-zoom-anim'); + } + + // @event zoomanim: ZoomAnimEvent + // Fired at least once per zoom animation. For continous zoom, like pinch zooming, fired once per frame during zoom. + this.fire('zoomanim', { + center: center, + zoom: zoom, + noUpdate: noUpdate + }); + + // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693 + setTimeout(bind(this._onZoomTransitionEnd, this), 250); + }, + + _onZoomTransitionEnd: function () { + if (!this._animatingZoom) { return; } + + if (this._mapPane) { + removeClass(this._mapPane, 'leaflet-zoom-anim'); + } + + this._animatingZoom = false; + + this._move(this._animateToCenter, this._animateToZoom); + + // This anim frame should prevent an obscure iOS webkit tile loading race condition. + requestAnimFrame(function () { + this._moveEnd(true); + }, this); + } +}); + +// @section + +// @factory L.map(id: String, options?: Map options) +// Instantiates a map object given the DOM ID of a `
` element +// and optionally an object literal with `Map options`. +// +// @alternative +// @factory L.map(el: HTMLElement, options?: Map options) +// Instantiates a map object given an instance of a `
` HTML element +// and optionally an object literal with `Map options`. +function createMap(id, options) { + return new Map(id, options); +} + +/* + * @class Control + * @aka L.Control + * @inherits Class + * + * L.Control is a base class for implementing map controls. Handles positioning. + * All other controls extend from this class. + */ + +var Control = Class.extend({ + // @section + // @aka Control options + options: { + // @option position: String = 'topright' + // The position of the control (one of the map corners). Possible values are `'topleft'`, + // `'topright'`, `'bottomleft'` or `'bottomright'` + position: 'topright' + }, + + initialize: function (options) { + setOptions(this, options); + }, + + /* @section + * Classes extending L.Control will inherit the following methods: + * + * @method getPosition: string + * Returns the position of the control. + */ + getPosition: function () { + return this.options.position; + }, + + // @method setPosition(position: string): this + // Sets the position of the control. + setPosition: function (position) { + var map = this._map; + + if (map) { + map.removeControl(this); + } + + this.options.position = position; + + if (map) { + map.addControl(this); + } + + return this; + }, + + // @method getContainer: HTMLElement + // Returns the HTMLElement that contains the control. + getContainer: function () { + return this._container; + }, + + // @method addTo(map: Map): this + // Adds the control to the given map. + addTo: function (map) { + this.remove(); + this._map = map; + + var container = this._container = this.onAdd(map), + pos = this.getPosition(), + corner = map._controlCorners[pos]; + + addClass(container, 'leaflet-control'); + + if (pos.indexOf('bottom') !== -1) { + corner.insertBefore(container, corner.firstChild); + } else { + corner.appendChild(container); + } + + return this; + }, + + // @method remove: this + // Removes the control from the map it is currently active on. + remove: function () { + if (!this._map) { + return this; + } + + remove(this._container); + + if (this.onRemove) { + this.onRemove(this._map); + } + + this._map = null; + + return this; + }, + + _refocusOnMap: function (e) { + // if map exists and event is not a keyboard event + if (this._map && e && e.screenX > 0 && e.screenY > 0) { + this._map.getContainer().focus(); + } + } +}); + +var control = function (options) { + return new Control(options); +}; + +/* @section Extension methods + * @uninheritable + * + * Every control should extend from `L.Control` and (re-)implement the following methods. + * + * @method onAdd(map: Map): HTMLElement + * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo). + * + * @method onRemove(map: Map) + * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove). + */ + +/* @namespace Map + * @section Methods for Layers and Controls + */ +Map.include({ + // @method addControl(control: Control): this + // Adds the given control to the map + addControl: function (control) { + control.addTo(this); + return this; + }, + + // @method removeControl(control: Control): this + // Removes the given control from the map + removeControl: function (control) { + control.remove(); + return this; + }, + + _initControlPos: function () { + var corners = this._controlCorners = {}, + l = 'leaflet-', + container = this._controlContainer = + create$1('div', l + 'control-container', this._container); + + function createCorner(vSide, hSide) { + var className = l + vSide + ' ' + l + hSide; + + corners[vSide + hSide] = create$1('div', className, container); + } + + createCorner('top', 'left'); + createCorner('top', 'right'); + createCorner('bottom', 'left'); + createCorner('bottom', 'right'); + }, + + _clearControlPos: function () { + for (var i in this._controlCorners) { + remove(this._controlCorners[i]); + } + remove(this._controlContainer); + delete this._controlCorners; + delete this._controlContainer; + } +}); + +/* + * @class Control.Layers + * @aka L.Control.Layers + * @inherits Control + * + * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control/)). Extends `Control`. + * + * @example + * + * ```js + * var baseLayers = { + * "Mapbox": mapbox, + * "OpenStreetMap": osm + * }; + * + * var overlays = { + * "Marker": marker, + * "Roads": roadsLayer + * }; + * + * L.control.layers(baseLayers, overlays).addTo(map); + * ``` + * + * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values: + * + * ```js + * { + * "": layer1, + * "": layer2 + * } + * ``` + * + * The layer names can contain HTML, which allows you to add additional styling to the items: + * + * ```js + * {" My Layer": myLayer} + * ``` + */ + +var Layers = Control.extend({ + // @section + // @aka Control.Layers options + options: { + // @option collapsed: Boolean = true + // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch. + collapsed: true, + position: 'topright', + + // @option autoZIndex: Boolean = true + // If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off. + autoZIndex: true, + + // @option hideSingleBase: Boolean = false + // If `true`, the base layers in the control will be hidden when there is only one. + hideSingleBase: false, + + // @option sortLayers: Boolean = false + // Whether to sort the layers. When `false`, layers will keep the order + // in which they were added to the control. + sortLayers: false, + + // @option sortFunction: Function = * + // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) + // that will be used for sorting the layers, when `sortLayers` is `true`. + // The function receives both the `L.Layer` instances and their names, as in + // `sortFunction(layerA, layerB, nameA, nameB)`. + // By default, it sorts layers alphabetically by their name. + sortFunction: function (layerA, layerB, nameA, nameB) { + return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0); + } + }, + + initialize: function (baseLayers, overlays, options) { + setOptions(this, options); + + this._layerControlInputs = []; + this._layers = []; + this._lastZIndex = 0; + this._handlingClick = false; + + for (var i in baseLayers) { + this._addLayer(baseLayers[i], i); + } + + for (i in overlays) { + this._addLayer(overlays[i], i, true); + } + }, + + onAdd: function (map) { + this._initLayout(); + this._update(); + + this._map = map; + map.on('zoomend', this._checkDisabledLayers, this); + + for (var i = 0; i < this._layers.length; i++) { + this._layers[i].layer.on('add remove', this._onLayerChange, this); + } + + return this._container; + }, + + addTo: function (map) { + Control.prototype.addTo.call(this, map); + // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height. + return this._expandIfNotCollapsed(); + }, + + onRemove: function () { + this._map.off('zoomend', this._checkDisabledLayers, this); + + for (var i = 0; i < this._layers.length; i++) { + this._layers[i].layer.off('add remove', this._onLayerChange, this); + } + }, + + // @method addBaseLayer(layer: Layer, name: String): this + // Adds a base layer (radio button entry) with the given name to the control. + addBaseLayer: function (layer, name) { + this._addLayer(layer, name); + return (this._map) ? this._update() : this; + }, + + // @method addOverlay(layer: Layer, name: String): this + // Adds an overlay (checkbox entry) with the given name to the control. + addOverlay: function (layer, name) { + this._addLayer(layer, name, true); + return (this._map) ? this._update() : this; + }, + + // @method removeLayer(layer: Layer): this + // Remove the given layer from the control. + removeLayer: function (layer) { + layer.off('add remove', this._onLayerChange, this); + + var obj = this._getLayer(stamp(layer)); + if (obj) { + this._layers.splice(this._layers.indexOf(obj), 1); + } + return (this._map) ? this._update() : this; + }, + + // @method expand(): this + // Expand the control container if collapsed. + expand: function () { + addClass(this._container, 'leaflet-control-layers-expanded'); + this._section.style.height = null; + var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); + if (acceptableHeight < this._section.clientHeight) { + addClass(this._section, 'leaflet-control-layers-scrollbar'); + this._section.style.height = acceptableHeight + 'px'; + } else { + removeClass(this._section, 'leaflet-control-layers-scrollbar'); + } + this._checkDisabledLayers(); + return this; + }, + + // @method collapse(): this + // Collapse the control container if expanded. + collapse: function () { + removeClass(this._container, 'leaflet-control-layers-expanded'); + return this; + }, + + _initLayout: function () { + var className = 'leaflet-control-layers', + container = this._container = create$1('div', className), + collapsed = this.options.collapsed; + + // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released + container.setAttribute('aria-haspopup', true); + + disableClickPropagation(container); + disableScrollPropagation(container); + + var section = this._section = create$1('section', className + '-list'); + + if (collapsed) { + this._map.on('click', this.collapse, this); + + if (!android) { + on(container, { + mouseenter: this.expand, + mouseleave: this.collapse + }, this); + } + } + + var link = this._layersLink = create$1('a', className + '-toggle', container); + link.href = '#'; + link.title = 'Layers'; + + if (touch) { + on(link, 'click', stop); + on(link, 'click', this.expand, this); + } else { + on(link, 'focus', this.expand, this); + } + + if (!collapsed) { + this.expand(); + } + + this._baseLayersList = create$1('div', className + '-base', section); + this._separator = create$1('div', className + '-separator', section); + this._overlaysList = create$1('div', className + '-overlays', section); + + container.appendChild(section); + }, + + _getLayer: function (id) { + for (var i = 0; i < this._layers.length; i++) { + + if (this._layers[i] && stamp(this._layers[i].layer) === id) { + return this._layers[i]; + } + } + }, + + _addLayer: function (layer, name, overlay) { + if (this._map) { + layer.on('add remove', this._onLayerChange, this); + } + + this._layers.push({ + layer: layer, + name: name, + overlay: overlay + }); + + if (this.options.sortLayers) { + this._layers.sort(bind(function (a, b) { + return this.options.sortFunction(a.layer, b.layer, a.name, b.name); + }, this)); + } + + if (this.options.autoZIndex && layer.setZIndex) { + this._lastZIndex++; + layer.setZIndex(this._lastZIndex); + } + + this._expandIfNotCollapsed(); + }, + + _update: function () { + if (!this._container) { return this; } + + empty(this._baseLayersList); + empty(this._overlaysList); + + this._layerControlInputs = []; + var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; + + for (i = 0; i < this._layers.length; i++) { + obj = this._layers[i]; + this._addItem(obj); + overlaysPresent = overlaysPresent || obj.overlay; + baseLayersPresent = baseLayersPresent || !obj.overlay; + baseLayersCount += !obj.overlay ? 1 : 0; + } + + // Hide base layers section if there's only one layer. + if (this.options.hideSingleBase) { + baseLayersPresent = baseLayersPresent && baseLayersCount > 1; + this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; + } + + this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; + + return this; + }, + + _onLayerChange: function (e) { + if (!this._handlingClick) { + this._update(); + } + + var obj = this._getLayer(stamp(e.target)); + + // @namespace Map + // @section Layer events + // @event baselayerchange: LayersControlEvent + // Fired when the base layer is changed through the [layer control](#control-layers). + // @event overlayadd: LayersControlEvent + // Fired when an overlay is selected through the [layer control](#control-layers). + // @event overlayremove: LayersControlEvent + // Fired when an overlay is deselected through the [layer control](#control-layers). + // @namespace Control.Layers + var type = obj.overlay ? + (e.type === 'add' ? 'overlayadd' : 'overlayremove') : + (e.type === 'add' ? 'baselayerchange' : null); + + if (type) { + this._map.fire(type, obj); + } + }, + + // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) + _createRadioElement: function (name, checked) { + + var radioHtml = ''; + + var radioFragment = document.createElement('div'); + radioFragment.innerHTML = radioHtml; + + return radioFragment.firstChild; + }, + + _addItem: function (obj) { + var label = document.createElement('label'), + checked = this._map.hasLayer(obj.layer), + input; + + if (obj.overlay) { + input = document.createElement('input'); + input.type = 'checkbox'; + input.className = 'leaflet-control-layers-selector'; + input.defaultChecked = checked; + } else { + input = this._createRadioElement('leaflet-base-layers', checked); + } + + this._layerControlInputs.push(input); + input.layerId = stamp(obj.layer); + + on(input, 'click', this._onInputClick, this); + + var name = document.createElement('span'); + name.innerHTML = ' ' + obj.name; + + // Helps from preventing layer control flicker when checkboxes are disabled + // https://github.com/Leaflet/Leaflet/issues/2771 + var holder = document.createElement('div'); + + label.appendChild(holder); + holder.appendChild(input); + holder.appendChild(name); + + var container = obj.overlay ? this._overlaysList : this._baseLayersList; + container.appendChild(label); + + this._checkDisabledLayers(); + return label; + }, + + _onInputClick: function () { + var inputs = this._layerControlInputs, + input, layer; + var addedLayers = [], + removedLayers = []; + + this._handlingClick = true; + + for (var i = inputs.length - 1; i >= 0; i--) { + input = inputs[i]; + layer = this._getLayer(input.layerId).layer; + + if (input.checked) { + addedLayers.push(layer); + } else if (!input.checked) { + removedLayers.push(layer); + } + } + + // Bugfix issue 2318: Should remove all old layers before readding new ones + for (i = 0; i < removedLayers.length; i++) { + if (this._map.hasLayer(removedLayers[i])) { + this._map.removeLayer(removedLayers[i]); + } + } + for (i = 0; i < addedLayers.length; i++) { + if (!this._map.hasLayer(addedLayers[i])) { + this._map.addLayer(addedLayers[i]); + } + } + + this._handlingClick = false; + + this._refocusOnMap(); + }, + + _checkDisabledLayers: function () { + var inputs = this._layerControlInputs, + input, + layer, + zoom = this._map.getZoom(); + + for (var i = inputs.length - 1; i >= 0; i--) { + input = inputs[i]; + layer = this._getLayer(input.layerId).layer; + input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) || + (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom); + + } + }, + + _expandIfNotCollapsed: function () { + if (this._map && !this.options.collapsed) { + this.expand(); + } + return this; + }, + + _expand: function () { + // Backward compatibility, remove me in 1.1. + return this.expand(); + }, + + _collapse: function () { + // Backward compatibility, remove me in 1.1. + return this.collapse(); + } + +}); + + +// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options) +// Creates an attribution control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation. +var layers = function (baseLayers, overlays, options) { + return new Layers(baseLayers, overlays, options); +}; + +/* + * @class Control.Zoom + * @aka L.Control.Zoom + * @inherits Control + * + * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`. + */ + +var Zoom = Control.extend({ + // @section + // @aka Control.Zoom options + options: { + position: 'topleft', + + // @option zoomInText: String = '+' + // The text set on the 'zoom in' button. + zoomInText: '+', + + // @option zoomInTitle: String = 'Zoom in' + // The title set on the 'zoom in' button. + zoomInTitle: 'Zoom in', + + // @option zoomOutText: String = '−' + // The text set on the 'zoom out' button. + zoomOutText: '−', + + // @option zoomOutTitle: String = 'Zoom out' + // The title set on the 'zoom out' button. + zoomOutTitle: 'Zoom out' + }, + + onAdd: function (map) { + var zoomName = 'leaflet-control-zoom', + container = create$1('div', zoomName + ' leaflet-bar'), + options = this.options; + + this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, + zoomName + '-in', container, this._zoomIn); + this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, + zoomName + '-out', container, this._zoomOut); + + this._updateDisabled(); + map.on('zoomend zoomlevelschange', this._updateDisabled, this); + + return container; + }, + + onRemove: function (map) { + map.off('zoomend zoomlevelschange', this._updateDisabled, this); + }, + + disable: function () { + this._disabled = true; + this._updateDisabled(); + return this; + }, + + enable: function () { + this._disabled = false; + this._updateDisabled(); + return this; + }, + + _zoomIn: function (e) { + if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) { + this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); + } + }, + + _zoomOut: function (e) { + if (!this._disabled && this._map._zoom > this._map.getMinZoom()) { + this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); + } + }, + + _createButton: function (html, title, className, container, fn) { + var link = create$1('a', className, container); + link.innerHTML = html; + link.href = '#'; + link.title = title; + + /* + * Will force screen readers like VoiceOver to read this as "Zoom in - button" + */ + link.setAttribute('role', 'button'); + link.setAttribute('aria-label', title); + + disableClickPropagation(link); + on(link, 'click', stop); + on(link, 'click', fn, this); + on(link, 'click', this._refocusOnMap, this); + + return link; + }, + + _updateDisabled: function () { + var map = this._map, + className = 'leaflet-disabled'; + + removeClass(this._zoomInButton, className); + removeClass(this._zoomOutButton, className); + + if (this._disabled || map._zoom === map.getMinZoom()) { + addClass(this._zoomOutButton, className); + } + if (this._disabled || map._zoom === map.getMaxZoom()) { + addClass(this._zoomInButton, className); + } + } +}); + +// @namespace Map +// @section Control options +// @option zoomControl: Boolean = true +// Whether a [zoom control](#control-zoom) is added to the map by default. +Map.mergeOptions({ + zoomControl: true +}); + +Map.addInitHook(function () { + if (this.options.zoomControl) { + // @section Controls + // @property zoomControl: Control.Zoom + // The default zoom control (only available if the + // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map). + this.zoomControl = new Zoom(); + this.addControl(this.zoomControl); + } +}); + +// @namespace Control.Zoom +// @factory L.control.zoom(options: Control.Zoom options) +// Creates a zoom control +var zoom = function (options) { + return new Zoom(options); +}; + +/* + * @class Control.Scale + * @aka L.Control.Scale + * @inherits Control + * + * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`. + * + * @example + * + * ```js + * L.control.scale().addTo(map); + * ``` + */ + +var Scale = Control.extend({ + // @section + // @aka Control.Scale options + options: { + position: 'bottomleft', + + // @option maxWidth: Number = 100 + // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500). + maxWidth: 100, + + // @option metric: Boolean = True + // Whether to show the metric scale line (m/km). + metric: true, + + // @option imperial: Boolean = True + // Whether to show the imperial scale line (mi/ft). + imperial: true + + // @option updateWhenIdle: Boolean = false + // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)). + }, + + onAdd: function (map) { + var className = 'leaflet-control-scale', + container = create$1('div', className), + options = this.options; + + this._addScales(options, className + '-line', container); + + map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); + map.whenReady(this._update, this); + + return container; + }, + + onRemove: function (map) { + map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); + }, + + _addScales: function (options, className, container) { + if (options.metric) { + this._mScale = create$1('div', className, container); + } + if (options.imperial) { + this._iScale = create$1('div', className, container); + } + }, + + _update: function () { + var map = this._map, + y = map.getSize().y / 2; + + var maxMeters = map.distance( + map.containerPointToLatLng([0, y]), + map.containerPointToLatLng([this.options.maxWidth, y])); + + this._updateScales(maxMeters); + }, + + _updateScales: function (maxMeters) { + if (this.options.metric && maxMeters) { + this._updateMetric(maxMeters); + } + if (this.options.imperial && maxMeters) { + this._updateImperial(maxMeters); + } + }, + + _updateMetric: function (maxMeters) { + var meters = this._getRoundNum(maxMeters), + label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; + + this._updateScale(this._mScale, label, meters / maxMeters); + }, + + _updateImperial: function (maxMeters) { + var maxFeet = maxMeters * 3.2808399, + maxMiles, miles, feet; + + if (maxFeet > 5280) { + maxMiles = maxFeet / 5280; + miles = this._getRoundNum(maxMiles); + this._updateScale(this._iScale, miles + ' mi', miles / maxMiles); + + } else { + feet = this._getRoundNum(maxFeet); + this._updateScale(this._iScale, feet + ' ft', feet / maxFeet); + } + }, + + _updateScale: function (scale, text, ratio) { + scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'; + scale.innerHTML = text; + }, + + _getRoundNum: function (num) { + var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), + d = num / pow10; + + d = d >= 10 ? 10 : + d >= 5 ? 5 : + d >= 3 ? 3 : + d >= 2 ? 2 : 1; + + return pow10 * d; + } +}); + + +// @factory L.control.scale(options?: Control.Scale options) +// Creates an scale control with the given options. +var scale = function (options) { + return new Scale(options); +}; + +/* + * @class Control.Attribution + * @aka L.Control.Attribution + * @inherits Control + * + * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control. + */ + +var Attribution = Control.extend({ + // @section + // @aka Control.Attribution options + options: { + position: 'bottomright', + + // @option prefix: String = 'Leaflet' + // The HTML text shown before the attributions. Pass `false` to disable. + prefix: 'Leaflet' + }, + + initialize: function (options) { + setOptions(this, options); + + this._attributions = {}; + }, + + onAdd: function (map) { + map.attributionControl = this; + this._container = create$1('div', 'leaflet-control-attribution'); + disableClickPropagation(this._container); + + // TODO ugly, refactor + for (var i in map._layers) { + if (map._layers[i].getAttribution) { + this.addAttribution(map._layers[i].getAttribution()); + } + } + + this._update(); + + return this._container; + }, + + // @method setPrefix(prefix: String): this + // Sets the text before the attributions. + setPrefix: function (prefix) { + this.options.prefix = prefix; + this._update(); + return this; + }, + + // @method addAttribution(text: String): this + // Adds an attribution text (e.g. `'Vector data © Mapbox'`). + addAttribution: function (text) { + if (!text) { return this; } + + if (!this._attributions[text]) { + this._attributions[text] = 0; + } + this._attributions[text]++; + + this._update(); + + return this; + }, + + // @method removeAttribution(text: String): this + // Removes an attribution text. + removeAttribution: function (text) { + if (!text) { return this; } + + if (this._attributions[text]) { + this._attributions[text]--; + this._update(); + } + + return this; + }, + + _update: function () { + if (!this._map) { return; } + + var attribs = []; + + for (var i in this._attributions) { + if (this._attributions[i]) { + attribs.push(i); + } + } + + var prefixAndAttribs = []; + + if (this.options.prefix) { + prefixAndAttribs.push(this.options.prefix); + } + if (attribs.length) { + prefixAndAttribs.push(attribs.join(', ')); + } + + this._container.innerHTML = prefixAndAttribs.join(' | '); + } +}); + +// @namespace Map +// @section Control options +// @option attributionControl: Boolean = true +// Whether a [attribution control](#control-attribution) is added to the map by default. +Map.mergeOptions({ + attributionControl: true +}); + +Map.addInitHook(function () { + if (this.options.attributionControl) { + new Attribution().addTo(this); + } +}); + +// @namespace Control.Attribution +// @factory L.control.attribution(options: Control.Attribution options) +// Creates an attribution control. +var attribution = function (options) { + return new Attribution(options); +}; + +Control.Layers = Layers; +Control.Zoom = Zoom; +Control.Scale = Scale; +Control.Attribution = Attribution; + +control.layers = layers; +control.zoom = zoom; +control.scale = scale; +control.attribution = attribution; + +/* + L.Handler is a base class for handler classes that are used internally to inject + interaction features like dragging to classes like Map and Marker. +*/ + +// @class Handler +// @aka L.Handler +// Abstract class for map interaction handlers + +var Handler = Class.extend({ + initialize: function (map) { + this._map = map; + }, + + // @method enable(): this + // Enables the handler + enable: function () { + if (this._enabled) { return this; } + + this._enabled = true; + this.addHooks(); + return this; + }, + + // @method disable(): this + // Disables the handler + disable: function () { + if (!this._enabled) { return this; } + + this._enabled = false; + this.removeHooks(); + return this; + }, + + // @method enabled(): Boolean + // Returns `true` if the handler is enabled + enabled: function () { + return !!this._enabled; + } + + // @section Extension methods + // Classes inheriting from `Handler` must implement the two following methods: + // @method addHooks() + // Called when the handler is enabled, should add event hooks. + // @method removeHooks() + // Called when the handler is disabled, should remove the event hooks added previously. +}); + +// @section There is static function which can be called without instantiating L.Handler: +// @function addTo(map: Map, name: String): this +// Adds a new Handler to the given map with the given name. +Handler.addTo = function (map, name) { + map.addHandler(name, this); + return this; +}; + +var Mixin = {Events: Events}; + +/* + * @class Draggable + * @aka L.Draggable + * @inherits Evented + * + * A class for making DOM elements draggable (including touch support). + * Used internally for map and marker dragging. Only works for elements + * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition). + * + * @example + * ```js + * var draggable = new L.Draggable(elementToDrag); + * draggable.enable(); + * ``` + */ + +var START = touch ? 'touchstart mousedown' : 'mousedown'; +var END = { + mousedown: 'mouseup', + touchstart: 'touchend', + pointerdown: 'touchend', + MSPointerDown: 'touchend' +}; +var MOVE = { + mousedown: 'mousemove', + touchstart: 'touchmove', + pointerdown: 'touchmove', + MSPointerDown: 'touchmove' +}; + + +var Draggable = Evented.extend({ + + options: { + // @section + // @aka Draggable options + // @option clickTolerance: Number = 3 + // The max number of pixels a user can shift the mouse pointer during a click + // for it to be considered a valid click (as opposed to a mouse drag). + clickTolerance: 3 + }, + + // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options) + // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). + initialize: function (element, dragStartTarget, preventOutline$$1, options) { + setOptions(this, options); + + this._element = element; + this._dragStartTarget = dragStartTarget || element; + this._preventOutline = preventOutline$$1; + }, + + // @method enable() + // Enables the dragging ability + enable: function () { + if (this._enabled) { return; } + + on(this._dragStartTarget, START, this._onDown, this); + + this._enabled = true; + }, + + // @method disable() + // Disables the dragging ability + disable: function () { + if (!this._enabled) { return; } + + // If we're currently dragging this draggable, + // disabling it counts as first ending the drag. + if (Draggable._dragging === this) { + this.finishDrag(); + } + + off(this._dragStartTarget, START, this._onDown, this); + + this._enabled = false; + this._moved = false; + }, + + _onDown: function (e) { + // Ignore simulated events, since we handle both touch and + // mouse explicitly; otherwise we risk getting duplicates of + // touch events, see #4315. + // Also ignore the event if disabled; this happens in IE11 + // under some circumstances, see #3666. + if (e._simulated || !this._enabled) { return; } + + this._moved = false; + + if (hasClass(this._element, 'leaflet-zoom-anim')) { return; } + + if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } + Draggable._dragging = this; // Prevent dragging multiple objects at once. + + if (this._preventOutline) { + preventOutline(this._element); + } + + disableImageDrag(); + disableTextSelection(); + + if (this._moving) { return; } + + // @event down: Event + // Fired when a drag is about to start. + this.fire('down'); + + var first = e.touches ? e.touches[0] : e, + sizedParent = getSizedParentNode(this._element); + + this._startPoint = new Point(first.clientX, first.clientY); + + // Cache the scale, so that we can continuously compensate for it during drag (_onMove). + this._parentScale = getScale(sizedParent); + + on(document, MOVE[e.type], this._onMove, this); + on(document, END[e.type], this._onUp, this); + }, + + _onMove: function (e) { + // Ignore simulated events, since we handle both touch and + // mouse explicitly; otherwise we risk getting duplicates of + // touch events, see #4315. + // Also ignore the event if disabled; this happens in IE11 + // under some circumstances, see #3666. + if (e._simulated || !this._enabled) { return; } + + if (e.touches && e.touches.length > 1) { + this._moved = true; + return; + } + + var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), + offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint); + + if (!offset.x && !offset.y) { return; } + if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } + + // We assume that the parent container's position, border and scale do not change for the duration of the drag. + // Therefore there is no need to account for the position and border (they are eliminated by the subtraction) + // and we can use the cached value for the scale. + offset.x /= this._parentScale.x; + offset.y /= this._parentScale.y; + + preventDefault(e); + + if (!this._moved) { + // @event dragstart: Event + // Fired when a drag starts + this.fire('dragstart'); + + this._moved = true; + this._startPos = getPosition(this._element).subtract(offset); + + addClass(document.body, 'leaflet-dragging'); + + this._lastTarget = e.target || e.srcElement; + // IE and Edge do not give the element, so fetch it + // if necessary + if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) { + this._lastTarget = this._lastTarget.correspondingUseElement; + } + addClass(this._lastTarget, 'leaflet-drag-target'); + } + + this._newPos = this._startPos.add(offset); + this._moving = true; + + cancelAnimFrame(this._animRequest); + this._lastEvent = e; + this._animRequest = requestAnimFrame(this._updatePosition, this, true); + }, + + _updatePosition: function () { + var e = {originalEvent: this._lastEvent}; + + // @event predrag: Event + // Fired continuously during dragging *before* each corresponding + // update of the element's position. + this.fire('predrag', e); + setPosition(this._element, this._newPos); + + // @event drag: Event + // Fired continuously during dragging. + this.fire('drag', e); + }, + + _onUp: function (e) { + // Ignore simulated events, since we handle both touch and + // mouse explicitly; otherwise we risk getting duplicates of + // touch events, see #4315. + // Also ignore the event if disabled; this happens in IE11 + // under some circumstances, see #3666. + if (e._simulated || !this._enabled) { return; } + this.finishDrag(); + }, + + finishDrag: function () { + removeClass(document.body, 'leaflet-dragging'); + + if (this._lastTarget) { + removeClass(this._lastTarget, 'leaflet-drag-target'); + this._lastTarget = null; + } + + for (var i in MOVE) { + off(document, MOVE[i], this._onMove, this); + off(document, END[i], this._onUp, this); + } + + enableImageDrag(); + enableTextSelection(); + + if (this._moved && this._moving) { + // ensure drag is not fired after dragend + cancelAnimFrame(this._animRequest); + + // @event dragend: DragEndEvent + // Fired when the drag ends. + this.fire('dragend', { + distance: this._newPos.distanceTo(this._startPos) + }); + } + + this._moving = false; + Draggable._dragging = false; + } + +}); + +/* + * @namespace LineUtil + * + * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast. + */ + +// Simplify polyline with vertex reduction and Douglas-Peucker simplification. +// Improves rendering performance dramatically by lessening the number of points to draw. + +// @function simplify(points: Point[], tolerance: Number): Point[] +// Dramatically reduces the number of points in a polyline while retaining +// its shape and returns a new array of simplified points, using the +// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm). +// Used for a huge performance boost when processing/displaying Leaflet polylines for +// each zoom level and also reducing visual noise. tolerance affects the amount of +// simplification (lesser value means higher quality but slower and with more points). +// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/). +function simplify(points, tolerance) { + if (!tolerance || !points.length) { + return points.slice(); + } + + var sqTolerance = tolerance * tolerance; + + // stage 1: vertex reduction + points = _reducePoints(points, sqTolerance); + + // stage 2: Douglas-Peucker simplification + points = _simplifyDP(points, sqTolerance); + + return points; +} + +// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number +// Returns the distance between point `p` and segment `p1` to `p2`. +function pointToSegmentDistance(p, p1, p2) { + return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true)); +} + +// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number +// Returns the closest point from a point `p` on a segment `p1` to `p2`. +function closestPointOnSegment(p, p1, p2) { + return _sqClosestPointOnSegment(p, p1, p2); +} + +// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm +function _simplifyDP(points, sqTolerance) { + + var len = points.length, + ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, + markers = new ArrayConstructor(len); + + markers[0] = markers[len - 1] = 1; + + _simplifyDPStep(points, markers, sqTolerance, 0, len - 1); + + var i, + newPoints = []; + + for (i = 0; i < len; i++) { + if (markers[i]) { + newPoints.push(points[i]); + } + } + + return newPoints; +} + +function _simplifyDPStep(points, markers, sqTolerance, first, last) { + + var maxSqDist = 0, + index, i, sqDist; + + for (i = first + 1; i <= last - 1; i++) { + sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true); + + if (sqDist > maxSqDist) { + index = i; + maxSqDist = sqDist; + } + } + + if (maxSqDist > sqTolerance) { + markers[index] = 1; + + _simplifyDPStep(points, markers, sqTolerance, first, index); + _simplifyDPStep(points, markers, sqTolerance, index, last); + } +} + +// reduce points that are too close to each other to a single point +function _reducePoints(points, sqTolerance) { + var reducedPoints = [points[0]]; + + for (var i = 1, prev = 0, len = points.length; i < len; i++) { + if (_sqDist(points[i], points[prev]) > sqTolerance) { + reducedPoints.push(points[i]); + prev = i; + } + } + if (prev < len - 1) { + reducedPoints.push(points[len - 1]); + } + return reducedPoints; +} + +var _lastCode; + +// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean +// Clips the segment a to b by rectangular bounds with the +// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) +// (modifying the segment points directly!). Used by Leaflet to only show polyline +// points that are on the screen or near, increasing performance. +function clipSegment(a, b, bounds, useLastCode, round) { + var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds), + codeB = _getBitCode(b, bounds), + + codeOut, p, newCode; + + // save 2nd code to avoid calculating it on the next segment + _lastCode = codeB; + + while (true) { + // if a,b is inside the clip window (trivial accept) + if (!(codeA | codeB)) { + return [a, b]; + } + + // if a,b is outside the clip window (trivial reject) + if (codeA & codeB) { + return false; + } + + // other cases + codeOut = codeA || codeB; + p = _getEdgeIntersection(a, b, codeOut, bounds, round); + newCode = _getBitCode(p, bounds); + + if (codeOut === codeA) { + a = p; + codeA = newCode; + } else { + b = p; + codeB = newCode; + } + } +} + +function _getEdgeIntersection(a, b, code, bounds, round) { + var dx = b.x - a.x, + dy = b.y - a.y, + min = bounds.min, + max = bounds.max, + x, y; + + if (code & 8) { // top + x = a.x + dx * (max.y - a.y) / dy; + y = max.y; + + } else if (code & 4) { // bottom + x = a.x + dx * (min.y - a.y) / dy; + y = min.y; + + } else if (code & 2) { // right + x = max.x; + y = a.y + dy * (max.x - a.x) / dx; + + } else if (code & 1) { // left + x = min.x; + y = a.y + dy * (min.x - a.x) / dx; + } + + return new Point(x, y, round); +} + +function _getBitCode(p, bounds) { + var code = 0; + + if (p.x < bounds.min.x) { // left + code |= 1; + } else if (p.x > bounds.max.x) { // right + code |= 2; + } + + if (p.y < bounds.min.y) { // bottom + code |= 4; + } else if (p.y > bounds.max.y) { // top + code |= 8; + } + + return code; +} + +// square distance (to avoid unnecessary Math.sqrt calls) +function _sqDist(p1, p2) { + var dx = p2.x - p1.x, + dy = p2.y - p1.y; + return dx * dx + dy * dy; +} + +// return closest point on segment or distance to that point +function _sqClosestPointOnSegment(p, p1, p2, sqDist) { + var x = p1.x, + y = p1.y, + dx = p2.x - x, + dy = p2.y - y, + dot = dx * dx + dy * dy, + t; + + if (dot > 0) { + t = ((p.x - x) * dx + (p.y - y) * dy) / dot; + + if (t > 1) { + x = p2.x; + y = p2.y; + } else if (t > 0) { + x += dx * t; + y += dy * t; + } + } + + dx = p.x - x; + dy = p.y - y; + + return sqDist ? dx * dx + dy * dy : new Point(x, y); +} + + +// @function isFlat(latlngs: LatLng[]): Boolean +// Returns true if `latlngs` is a flat array, false is nested. +function isFlat(latlngs) { + return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); +} + +function _flat(latlngs) { + console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.'); + return isFlat(latlngs); +} + + +var LineUtil = (Object.freeze || Object)({ + simplify: simplify, + pointToSegmentDistance: pointToSegmentDistance, + closestPointOnSegment: closestPointOnSegment, + clipSegment: clipSegment, + _getEdgeIntersection: _getEdgeIntersection, + _getBitCode: _getBitCode, + _sqClosestPointOnSegment: _sqClosestPointOnSegment, + isFlat: isFlat, + _flat: _flat +}); + +/* + * @namespace PolyUtil + * Various utility functions for polygon geometries. + */ + +/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] + * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)). + * Used by Leaflet to only show polygon points that are on the screen or near, increasing + * performance. Note that polygon points needs different algorithm for clipping + * than polyline, so there's a separate method for it. + */ +function clipPolygon(points, bounds, round) { + var clippedPoints, + edges = [1, 4, 2, 8], + i, j, k, + a, b, + len, edge, p; + + for (i = 0, len = points.length; i < len; i++) { + points[i]._code = _getBitCode(points[i], bounds); + } + + // for each edge (left, bottom, right, top) + for (k = 0; k < 4; k++) { + edge = edges[k]; + clippedPoints = []; + + for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { + a = points[i]; + b = points[j]; + + // if a is inside the clip window + if (!(a._code & edge)) { + // if b is outside the clip window (a->b goes out of screen) + if (b._code & edge) { + p = _getEdgeIntersection(b, a, edge, bounds, round); + p._code = _getBitCode(p, bounds); + clippedPoints.push(p); + } + clippedPoints.push(a); + + // else if b is inside the clip window (a->b enters the screen) + } else if (!(b._code & edge)) { + p = _getEdgeIntersection(b, a, edge, bounds, round); + p._code = _getBitCode(p, bounds); + clippedPoints.push(p); + } + } + points = clippedPoints; + } + + return points; +} + + +var PolyUtil = (Object.freeze || Object)({ + clipPolygon: clipPolygon +}); + +/* + * @namespace Projection + * @section + * Leaflet comes with a set of already defined Projections out of the box: + * + * @projection L.Projection.LonLat + * + * Equirectangular, or Plate Carree projection — the most simple projection, + * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as + * latitude. Also suitable for flat worlds, e.g. game maps. Used by the + * `EPSG:4326` and `Simple` CRS. + */ + +var LonLat = { + project: function (latlng) { + return new Point(latlng.lng, latlng.lat); + }, + + unproject: function (point) { + return new LatLng(point.y, point.x); + }, + + bounds: new Bounds([-180, -90], [180, 90]) +}; + +/* + * @namespace Projection + * @projection L.Projection.Mercator + * + * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS. + */ + +var Mercator = { + R: 6378137, + R_MINOR: 6356752.314245179, + + bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), + + project: function (latlng) { + var d = Math.PI / 180, + r = this.R, + y = latlng.lat * d, + tmp = this.R_MINOR / r, + e = Math.sqrt(1 - tmp * tmp), + con = e * Math.sin(y); + + var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); + y = -r * Math.log(Math.max(ts, 1E-10)); + + return new Point(latlng.lng * d * r, y); + }, + + unproject: function (point) { + var d = 180 / Math.PI, + r = this.R, + tmp = this.R_MINOR / r, + e = Math.sqrt(1 - tmp * tmp), + ts = Math.exp(-point.y / r), + phi = Math.PI / 2 - 2 * Math.atan(ts); + + for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) { + con = e * Math.sin(phi); + con = Math.pow((1 - con) / (1 + con), e / 2); + dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi; + phi += dphi; + } + + return new LatLng(phi * d, point.x * d / r); + } +}; + +/* + * @class Projection + + * An object with methods for projecting geographical coordinates of the world onto + * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection). + + * @property bounds: Bounds + * The bounds (specified in CRS units) where the projection is valid + + * @method project(latlng: LatLng): Point + * Projects geographical coordinates into a 2D point. + * Only accepts actual `L.LatLng` instances, not arrays. + + * @method unproject(point: Point): LatLng + * The inverse of `project`. Projects a 2D point into a geographical location. + * Only accepts actual `L.Point` instances, not arrays. + + * Note that the projection instances do not inherit from Leafet's `Class` object, + * and can't be instantiated. Also, new classes can't inherit from them, + * and methods can't be added to them with the `include` function. + + */ + + + + +var index = (Object.freeze || Object)({ + LonLat: LonLat, + Mercator: Mercator, + SphericalMercator: SphericalMercator +}); + +/* + * @namespace CRS + * @crs L.CRS.EPSG3395 + * + * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection. + */ +var EPSG3395 = extend({}, Earth, { + code: 'EPSG:3395', + projection: Mercator, + + transformation: (function () { + var scale = 0.5 / (Math.PI * Mercator.R); + return toTransformation(scale, 0.5, -scale, 0.5); + }()) +}); + +/* + * @namespace CRS + * @crs L.CRS.EPSG4326 + * + * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection. + * + * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic), + * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer` + * with this CRS, ensure that there are two 256x256 pixel tiles covering the + * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90), + * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set. + */ + +var EPSG4326 = extend({}, Earth, { + code: 'EPSG:4326', + projection: LonLat, + transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5) +}); + +/* + * @namespace CRS + * @crs L.CRS.Simple + * + * A simple CRS that maps longitude and latitude into `x` and `y` directly. + * May be used for maps of flat surfaces (e.g. game maps). Note that the `y` + * axis should still be inverted (going from bottom to top). `distance()` returns + * simple euclidean distance. + */ + +var Simple = extend({}, CRS, { + projection: LonLat, + transformation: toTransformation(1, 0, -1, 0), + + scale: function (zoom) { + return Math.pow(2, zoom); + }, + + zoom: function (scale) { + return Math.log(scale) / Math.LN2; + }, + + distance: function (latlng1, latlng2) { + var dx = latlng2.lng - latlng1.lng, + dy = latlng2.lat - latlng1.lat; + + return Math.sqrt(dx * dx + dy * dy); + }, + + infinite: true +}); + +CRS.Earth = Earth; +CRS.EPSG3395 = EPSG3395; +CRS.EPSG3857 = EPSG3857; +CRS.EPSG900913 = EPSG900913; +CRS.EPSG4326 = EPSG4326; +CRS.Simple = Simple; + +/* + * @class Layer + * @inherits Evented + * @aka L.Layer + * @aka ILayer + * + * A set of methods from the Layer base class that all Leaflet layers use. + * Inherits all methods, options and events from `L.Evented`. + * + * @example + * + * ```js + * var layer = L.Marker(latlng).addTo(map); + * layer.addTo(map); + * layer.remove(); + * ``` + * + * @event add: Event + * Fired after the layer is added to a map + * + * @event remove: Event + * Fired after the layer is removed from a map + */ + + +var Layer = Evented.extend({ + + // Classes extending `L.Layer` will inherit the following options: + options: { + // @option pane: String = 'overlayPane' + // By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default. + pane: 'overlayPane', + + // @option attribution: String = null + // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers. + attribution: null, + + bubblingMouseEvents: true + }, + + /* @section + * Classes extending `L.Layer` will inherit the following methods: + * + * @method addTo(map: Map|LayerGroup): this + * Adds the layer to the given map or layer group. + */ + addTo: function (map) { + map.addLayer(this); + return this; + }, + + // @method remove: this + // Removes the layer from the map it is currently active on. + remove: function () { + return this.removeFrom(this._map || this._mapToAdd); + }, + + // @method removeFrom(map: Map): this + // Removes the layer from the given map + removeFrom: function (obj) { + if (obj) { + obj.removeLayer(this); + } + return this; + }, + + // @method getPane(name? : String): HTMLElement + // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer. + getPane: function (name) { + return this._map.getPane(name ? (this.options[name] || name) : this.options.pane); + }, + + addInteractiveTarget: function (targetEl) { + this._map._targets[stamp(targetEl)] = this; + return this; + }, + + removeInteractiveTarget: function (targetEl) { + delete this._map._targets[stamp(targetEl)]; + return this; + }, + + // @method getAttribution: String + // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution). + getAttribution: function () { + return this.options.attribution; + }, + + _layerAdd: function (e) { + var map = e.target; + + // check in case layer gets added and then removed before the map is ready + if (!map.hasLayer(this)) { return; } + + this._map = map; + this._zoomAnimated = map._zoomAnimated; + + if (this.getEvents) { + var events = this.getEvents(); + map.on(events, this); + this.once('remove', function () { + map.off(events, this); + }, this); + } + + this.onAdd(map); + + if (this.getAttribution && map.attributionControl) { + map.attributionControl.addAttribution(this.getAttribution()); + } + + this.fire('add'); + map.fire('layeradd', {layer: this}); + } +}); + +/* @section Extension methods + * @uninheritable + * + * Every layer should extend from `L.Layer` and (re-)implement the following methods. + * + * @method onAdd(map: Map): this + * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer). + * + * @method onRemove(map: Map): this + * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer). + * + * @method getEvents(): Object + * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer. + * + * @method getAttribution(): String + * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible. + * + * @method beforeAdd(map: Map): this + * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only. + */ + + +/* @namespace Map + * @section Layer events + * + * @event layeradd: LayerEvent + * Fired when a new layer is added to the map. + * + * @event layerremove: LayerEvent + * Fired when some layer is removed from the map + * + * @section Methods for Layers and Controls + */ +Map.include({ + // @method addLayer(layer: Layer): this + // Adds the given layer to the map + addLayer: function (layer) { + if (!layer._layerAdd) { + throw new Error('The provided object is not a Layer.'); + } + + var id = stamp(layer); + if (this._layers[id]) { return this; } + this._layers[id] = layer; + + layer._mapToAdd = this; + + if (layer.beforeAdd) { + layer.beforeAdd(this); + } + + this.whenReady(layer._layerAdd, layer); + + return this; + }, + + // @method removeLayer(layer: Layer): this + // Removes the given layer from the map. + removeLayer: function (layer) { + var id = stamp(layer); + + if (!this._layers[id]) { return this; } + + if (this._loaded) { + layer.onRemove(this); + } + + if (layer.getAttribution && this.attributionControl) { + this.attributionControl.removeAttribution(layer.getAttribution()); + } + + delete this._layers[id]; + + if (this._loaded) { + this.fire('layerremove', {layer: layer}); + layer.fire('remove'); + } + + layer._map = layer._mapToAdd = null; + + return this; + }, + + // @method hasLayer(layer: Layer): Boolean + // Returns `true` if the given layer is currently added to the map + hasLayer: function (layer) { + return !!layer && (stamp(layer) in this._layers); + }, + + /* @method eachLayer(fn: Function, context?: Object): this + * Iterates over the layers of the map, optionally specifying context of the iterator function. + * ``` + * map.eachLayer(function(layer){ + * layer.bindPopup('Hello'); + * }); + * ``` + */ + eachLayer: function (method, context) { + for (var i in this._layers) { + method.call(context, this._layers[i]); + } + return this; + }, + + _addLayers: function (layers) { + layers = layers ? (isArray(layers) ? layers : [layers]) : []; + + for (var i = 0, len = layers.length; i < len; i++) { + this.addLayer(layers[i]); + } + }, + + _addZoomLimit: function (layer) { + if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { + this._zoomBoundLayers[stamp(layer)] = layer; + this._updateZoomLevels(); + } + }, + + _removeZoomLimit: function (layer) { + var id = stamp(layer); + + if (this._zoomBoundLayers[id]) { + delete this._zoomBoundLayers[id]; + this._updateZoomLevels(); + } + }, + + _updateZoomLevels: function () { + var minZoom = Infinity, + maxZoom = -Infinity, + oldZoomSpan = this._getZoomSpan(); + + for (var i in this._zoomBoundLayers) { + var options = this._zoomBoundLayers[i].options; + + minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom); + maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom); + } + + this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom; + this._layersMinZoom = minZoom === Infinity ? undefined : minZoom; + + // @section Map state change events + // @event zoomlevelschange: Event + // Fired when the number of zoomlevels on the map is changed due + // to adding or removing a layer. + if (oldZoomSpan !== this._getZoomSpan()) { + this.fire('zoomlevelschange'); + } + + if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) { + this.setZoom(this._layersMaxZoom); + } + if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) { + this.setZoom(this._layersMinZoom); + } + } +}); + +/* + * @class LayerGroup + * @aka L.LayerGroup + * @inherits Layer + * + * Used to group several layers and handle them as one. If you add it to the map, + * any layers added or removed from the group will be added/removed on the map as + * well. Extends `Layer`. + * + * @example + * + * ```js + * L.layerGroup([marker1, marker2]) + * .addLayer(polyline) + * .addTo(map); + * ``` + */ + +var LayerGroup = Layer.extend({ + + initialize: function (layers, options) { + setOptions(this, options); + + this._layers = {}; + + var i, len; + + if (layers) { + for (i = 0, len = layers.length; i < len; i++) { + this.addLayer(layers[i]); + } + } + }, + + // @method addLayer(layer: Layer): this + // Adds the given layer to the group. + addLayer: function (layer) { + var id = this.getLayerId(layer); + + this._layers[id] = layer; + + if (this._map) { + this._map.addLayer(layer); + } + + return this; + }, + + // @method removeLayer(layer: Layer): this + // Removes the given layer from the group. + // @alternative + // @method removeLayer(id: Number): this + // Removes the layer with the given internal ID from the group. + removeLayer: function (layer) { + var id = layer in this._layers ? layer : this.getLayerId(layer); + + if (this._map && this._layers[id]) { + this._map.removeLayer(this._layers[id]); + } + + delete this._layers[id]; + + return this; + }, + + // @method hasLayer(layer: Layer): Boolean + // Returns `true` if the given layer is currently added to the group. + // @alternative + // @method hasLayer(id: Number): Boolean + // Returns `true` if the given internal ID is currently added to the group. + hasLayer: function (layer) { + return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers); + }, + + // @method clearLayers(): this + // Removes all the layers from the group. + clearLayers: function () { + return this.eachLayer(this.removeLayer, this); + }, + + // @method invoke(methodName: String, 
): this + // Calls `methodName` on every layer contained in this group, passing any + // additional parameters. Has no effect if the layers contained do not + // implement `methodName`. + invoke: function (methodName) { + var args = Array.prototype.slice.call(arguments, 1), + i, layer; + + for (i in this._layers) { + layer = this._layers[i]; + + if (layer[methodName]) { + layer[methodName].apply(layer, args); + } + } + + return this; + }, + + onAdd: function (map) { + this.eachLayer(map.addLayer, map); + }, + + onRemove: function (map) { + this.eachLayer(map.removeLayer, map); + }, + + // @method eachLayer(fn: Function, context?: Object): this + // Iterates over the layers of the group, optionally specifying context of the iterator function. + // ```js + // group.eachLayer(function (layer) { + // layer.bindPopup('Hello'); + // }); + // ``` + eachLayer: function (method, context) { + for (var i in this._layers) { + method.call(context, this._layers[i]); + } + return this; + }, + + // @method getLayer(id: Number): Layer + // Returns the layer with the given internal ID. + getLayer: function (id) { + return this._layers[id]; + }, + + // @method getLayers(): Layer[] + // Returns an array of all the layers added to the group. + getLayers: function () { + var layers = []; + this.eachLayer(layers.push, layers); + return layers; + }, + + // @method setZIndex(zIndex: Number): this + // Calls `setZIndex` on every layer contained in this group, passing the z-index. + setZIndex: function (zIndex) { + return this.invoke('setZIndex', zIndex); + }, + + // @method getLayerId(layer: Layer): Number + // Returns the internal ID for a layer + getLayerId: function (layer) { + return stamp(layer); + } +}); + + +// @factory L.layerGroup(layers?: Layer[], options?: Object) +// Create a layer group, optionally given an initial set of layers and an `options` object. +var layerGroup = function (layers, options) { + return new LayerGroup(layers, options); +}; + +/* + * @class FeatureGroup + * @aka L.FeatureGroup + * @inherits LayerGroup + * + * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers: + * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip)) + * * Events are propagated to the `FeatureGroup`, so if the group has an event + * handler, it will handle events from any of the layers. This includes mouse events + * and custom events. + * * Has `layeradd` and `layerremove` events + * + * @example + * + * ```js + * L.featureGroup([marker1, marker2, polyline]) + * .bindPopup('Hello world!') + * .on('click', function() { alert('Clicked on a member of the group!'); }) + * .addTo(map); + * ``` + */ + +var FeatureGroup = LayerGroup.extend({ + + addLayer: function (layer) { + if (this.hasLayer(layer)) { + return this; + } + + layer.addEventParent(this); + + LayerGroup.prototype.addLayer.call(this, layer); + + // @event layeradd: LayerEvent + // Fired when a layer is added to this `FeatureGroup` + return this.fire('layeradd', {layer: layer}); + }, + + removeLayer: function (layer) { + if (!this.hasLayer(layer)) { + return this; + } + if (layer in this._layers) { + layer = this._layers[layer]; + } + + layer.removeEventParent(this); + + LayerGroup.prototype.removeLayer.call(this, layer); + + // @event layerremove: LayerEvent + // Fired when a layer is removed from this `FeatureGroup` + return this.fire('layerremove', {layer: layer}); + }, + + // @method setStyle(style: Path options): this + // Sets the given path options to each layer of the group that has a `setStyle` method. + setStyle: function (style) { + return this.invoke('setStyle', style); + }, + + // @method bringToFront(): this + // Brings the layer group to the top of all other layers + bringToFront: function () { + return this.invoke('bringToFront'); + }, + + // @method bringToBack(): this + // Brings the layer group to the back of all other layers + bringToBack: function () { + return this.invoke('bringToBack'); + }, + + // @method getBounds(): LatLngBounds + // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). + getBounds: function () { + var bounds = new LatLngBounds(); + + for (var id in this._layers) { + var layer = this._layers[id]; + bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); + } + return bounds; + } +}); + +// @factory L.featureGroup(layers: Layer[]) +// Create a feature group, optionally given an initial set of layers. +var featureGroup = function (layers) { + return new FeatureGroup(layers); +}; + +/* + * @class Icon + * @aka L.Icon + * + * Represents an icon to provide when creating a marker. + * + * @example + * + * ```js + * var myIcon = L.icon({ + * iconUrl: 'my-icon.png', + * iconRetinaUrl: 'my-icon@2x.png', + * iconSize: [38, 95], + * iconAnchor: [22, 94], + * popupAnchor: [-3, -76], + * shadowUrl: 'my-icon-shadow.png', + * shadowRetinaUrl: 'my-icon-shadow@2x.png', + * shadowSize: [68, 95], + * shadowAnchor: [22, 94] + * }); + * + * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); + * ``` + * + * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default. + * + */ + +var Icon = Class.extend({ + + /* @section + * @aka Icon options + * + * @option iconUrl: String = null + * **(required)** The URL to the icon image (absolute or relative to your script path). + * + * @option iconRetinaUrl: String = null + * The URL to a retina sized version of the icon image (absolute or relative to your + * script path). Used for Retina screen devices. + * + * @option iconSize: Point = null + * Size of the icon image in pixels. + * + * @option iconAnchor: Point = null + * The coordinates of the "tip" of the icon (relative to its top left corner). The icon + * will be aligned so that this point is at the marker's geographical location. Centered + * by default if size is specified, also can be set in CSS with negative margins. + * + * @option popupAnchor: Point = [0, 0] + * The coordinates of the point from which popups will "open", relative to the icon anchor. + * + * @option tooltipAnchor: Point = [0, 0] + * The coordinates of the point from which tooltips will "open", relative to the icon anchor. + * + * @option shadowUrl: String = null + * The URL to the icon shadow image. If not specified, no shadow image will be created. + * + * @option shadowRetinaUrl: String = null + * + * @option shadowSize: Point = null + * Size of the shadow image in pixels. + * + * @option shadowAnchor: Point = null + * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same + * as iconAnchor if not specified). + * + * @option className: String = '' + * A custom class name to assign to both icon and shadow images. Empty by default. + */ + + options: { + popupAnchor: [0, 0], + tooltipAnchor: [0, 0] + }, + + initialize: function (options) { + setOptions(this, options); + }, + + // @method createIcon(oldIcon?: HTMLElement): HTMLElement + // Called internally when the icon has to be shown, returns a `` HTML element + // styled according to the options. + createIcon: function (oldIcon) { + return this._createIcon('icon', oldIcon); + }, + + // @method createShadow(oldIcon?: HTMLElement): HTMLElement + // As `createIcon`, but for the shadow beneath it. + createShadow: function (oldIcon) { + return this._createIcon('shadow', oldIcon); + }, + + _createIcon: function (name, oldIcon) { + var src = this._getIconUrl(name); + + if (!src) { + if (name === 'icon') { + throw new Error('iconUrl not set in Icon options (see the docs).'); + } + return null; + } + + var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null); + this._setIconStyles(img, name); + + return img; + }, + + _setIconStyles: function (img, name) { + var options = this.options; + var sizeOption = options[name + 'Size']; + + if (typeof sizeOption === 'number') { + sizeOption = [sizeOption, sizeOption]; + } + + var size = toPoint(sizeOption), + anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor || + size && size.divideBy(2, true)); + + img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); + + if (anchor) { + img.style.marginLeft = (-anchor.x) + 'px'; + img.style.marginTop = (-anchor.y) + 'px'; + } + + if (size) { + img.style.width = size.x + 'px'; + img.style.height = size.y + 'px'; + } + }, + + _createImg: function (src, el) { + el = el || document.createElement('img'); + el.src = src; + return el; + }, + + _getIconUrl: function (name) { + return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; + } +}); + + +// @factory L.icon(options: Icon options) +// Creates an icon instance with the given options. +function icon(options) { + return new Icon(options); +} + +/* + * @miniclass Icon.Default (Icon) + * @aka L.Icon.Default + * @section + * + * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when + * no icon is specified. Points to the blue marker image distributed with Leaflet + * releases. + * + * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options` + * (which is a set of `Icon options`). + * + * If you want to _completely_ replace the default icon, override the + * `L.Marker.prototype.options.icon` with your own icon instead. + */ + +var IconDefault = Icon.extend({ + + options: { + iconUrl: 'marker-icon.png', + iconRetinaUrl: 'marker-icon-2x.png', + shadowUrl: 'marker-shadow.png', + iconSize: [25, 41], + iconAnchor: [12, 41], + popupAnchor: [1, -34], + tooltipAnchor: [16, -28], + shadowSize: [41, 41] + }, + + _getIconUrl: function (name) { + if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only + IconDefault.imagePath = this._detectIconPath(); + } + + // @option imagePath: String + // `Icon.Default` will try to auto-detect the location of the + // blue icon images. If you are placing these images in a non-standard + // way, set this option to point to the right path. + return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name); + }, + + _detectIconPath: function () { + var el = create$1('div', 'leaflet-default-icon-path', document.body); + var path = getStyle(el, 'background-image') || + getStyle(el, 'backgroundImage'); // IE8 + + document.body.removeChild(el); + + if (path === null || path.indexOf('url') !== 0) { + path = ''; + } else { + path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, ''); + } + + return path; + } +}); + +/* + * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. + */ + + +/* @namespace Marker + * @section Interaction handlers + * + * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example: + * + * ```js + * marker.dragging.disable(); + * ``` + * + * @property dragging: Handler + * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)). + */ + +var MarkerDrag = Handler.extend({ + initialize: function (marker) { + this._marker = marker; + }, + + addHooks: function () { + var icon = this._marker._icon; + + if (!this._draggable) { + this._draggable = new Draggable(icon, icon, true); + } + + this._draggable.on({ + dragstart: this._onDragStart, + predrag: this._onPreDrag, + drag: this._onDrag, + dragend: this._onDragEnd + }, this).enable(); + + addClass(icon, 'leaflet-marker-draggable'); + }, + + removeHooks: function () { + this._draggable.off({ + dragstart: this._onDragStart, + predrag: this._onPreDrag, + drag: this._onDrag, + dragend: this._onDragEnd + }, this).disable(); + + if (this._marker._icon) { + removeClass(this._marker._icon, 'leaflet-marker-draggable'); + } + }, + + moved: function () { + return this._draggable && this._draggable._moved; + }, + + _adjustPan: function (e) { + var marker = this._marker, + map = marker._map, + speed = this._marker.options.autoPanSpeed, + padding = this._marker.options.autoPanPadding, + iconPos = getPosition(marker._icon), + bounds = map.getPixelBounds(), + origin = map.getPixelOrigin(); + + var panBounds = toBounds( + bounds.min._subtract(origin).add(padding), + bounds.max._subtract(origin).subtract(padding) + ); + + if (!panBounds.contains(iconPos)) { + // Compute incremental movement + var movement = toPoint( + (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) - + (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x), + + (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) - + (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y) + ).multiplyBy(speed); + + map.panBy(movement, {animate: false}); + + this._draggable._newPos._add(movement); + this._draggable._startPos._add(movement); + + setPosition(marker._icon, this._draggable._newPos); + this._onDrag(e); + + this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); + } + }, + + _onDragStart: function () { + // @section Dragging events + // @event dragstart: Event + // Fired when the user starts dragging the marker. + + // @event movestart: Event + // Fired when the marker starts moving (because of dragging). + + this._oldLatLng = this._marker.getLatLng(); + this._marker + .closePopup() + .fire('movestart') + .fire('dragstart'); + }, + + _onPreDrag: function (e) { + if (this._marker.options.autoPan) { + cancelAnimFrame(this._panRequest); + this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); + } + }, + + _onDrag: function (e) { + var marker = this._marker, + shadow = marker._shadow, + iconPos = getPosition(marker._icon), + latlng = marker._map.layerPointToLatLng(iconPos); + + // update shadow position + if (shadow) { + setPosition(shadow, iconPos); + } + + marker._latlng = latlng; + e.latlng = latlng; + e.oldLatLng = this._oldLatLng; + + // @event drag: Event + // Fired repeatedly while the user drags the marker. + marker + .fire('move', e) + .fire('drag', e); + }, + + _onDragEnd: function (e) { + // @event dragend: DragEndEvent + // Fired when the user stops dragging the marker. + + cancelAnimFrame(this._panRequest); + + // @event moveend: Event + // Fired when the marker stops moving (because of dragging). + delete this._oldLatLng; + this._marker + .fire('moveend') + .fire('dragend', e); + } +}); + +/* + * @class Marker + * @inherits Interactive layer + * @aka L.Marker + * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`. + * + * @example + * + * ```js + * L.marker([50.5, 30.5]).addTo(map); + * ``` + */ + +var Marker = Layer.extend({ + + // @section + // @aka Marker options + options: { + // @option icon: Icon = * + // Icon instance to use for rendering the marker. + // See [Icon documentation](#L.Icon) for details on how to customize the marker icon. + // If not specified, a common instance of `L.Icon.Default` is used. + icon: new IconDefault(), + + // Option inherited from "Interactive layer" abstract class + interactive: true, + + // @option keyboard: Boolean = true + // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. + keyboard: true, + + // @option title: String = '' + // Text for the browser tooltip that appear on marker hover (no tooltip by default). + title: '', + + // @option alt: String = '' + // Text for the `alt` attribute of the icon image (useful for accessibility). + alt: '', + + // @option zIndexOffset: Number = 0 + // By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively). + zIndexOffset: 0, + + // @option opacity: Number = 1.0 + // The opacity of the marker. + opacity: 1, + + // @option riseOnHover: Boolean = false + // If `true`, the marker will get on top of others when you hover the mouse over it. + riseOnHover: false, + + // @option riseOffset: Number = 250 + // The z-index offset used for the `riseOnHover` feature. + riseOffset: 250, + + // @option pane: String = 'markerPane' + // `Map pane` where the markers icon will be added. + pane: 'markerPane', + + // @option bubblingMouseEvents: Boolean = false + // When `true`, a mouse event on this marker will trigger the same event on the map + // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). + bubblingMouseEvents: false, + + // @section Draggable marker options + // @option draggable: Boolean = false + // Whether the marker is draggable with mouse/touch or not. + draggable: false, + + // @option autoPan: Boolean = false + // Whether to pan the map when dragging this marker near its edge or not. + autoPan: false, + + // @option autoPanPadding: Point = Point(50, 50) + // Distance (in pixels to the left/right and to the top/bottom) of the + // map edge to start panning the map. + autoPanPadding: [50, 50], + + // @option autoPanSpeed: Number = 10 + // Number of pixels the map should pan by. + autoPanSpeed: 10 + }, + + /* @section + * + * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods: + */ + + initialize: function (latlng, options) { + setOptions(this, options); + this._latlng = toLatLng(latlng); + }, + + onAdd: function (map) { + this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation; + + if (this._zoomAnimated) { + map.on('zoomanim', this._animateZoom, this); + } + + this._initIcon(); + this.update(); + }, + + onRemove: function (map) { + if (this.dragging && this.dragging.enabled()) { + this.options.draggable = true; + this.dragging.removeHooks(); + } + delete this.dragging; + + if (this._zoomAnimated) { + map.off('zoomanim', this._animateZoom, this); + } + + this._removeIcon(); + this._removeShadow(); + }, + + getEvents: function () { + return { + zoom: this.update, + viewreset: this.update + }; + }, + + // @method getLatLng: LatLng + // Returns the current geographical position of the marker. + getLatLng: function () { + return this._latlng; + }, + + // @method setLatLng(latlng: LatLng): this + // Changes the marker position to the given point. + setLatLng: function (latlng) { + var oldLatLng = this._latlng; + this._latlng = toLatLng(latlng); + this.update(); + + // @event move: Event + // Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`. + return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); + }, + + // @method setZIndexOffset(offset: Number): this + // Changes the [zIndex offset](#marker-zindexoffset) of the marker. + setZIndexOffset: function (offset) { + this.options.zIndexOffset = offset; + return this.update(); + }, + + // @method setIcon(icon: Icon): this + // Changes the marker icon. + setIcon: function (icon) { + + this.options.icon = icon; + + if (this._map) { + this._initIcon(); + this.update(); + } + + if (this._popup) { + this.bindPopup(this._popup, this._popup.options); + } + + return this; + }, + + getElement: function () { + return this._icon; + }, + + update: function () { + + if (this._icon && this._map) { + var pos = this._map.latLngToLayerPoint(this._latlng).round(); + this._setPos(pos); + } + + return this; + }, + + _initIcon: function () { + var options = this.options, + classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); + + var icon = options.icon.createIcon(this._icon), + addIcon = false; + + // if we're not reusing the icon, remove the old one and init new one + if (icon !== this._icon) { + if (this._icon) { + this._removeIcon(); + } + addIcon = true; + + if (options.title) { + icon.title = options.title; + } + + if (icon.tagName === 'IMG') { + icon.alt = options.alt || ''; + } + } + + addClass(icon, classToAdd); + + if (options.keyboard) { + icon.tabIndex = '0'; + } + + this._icon = icon; + + if (options.riseOnHover) { + this.on({ + mouseover: this._bringToFront, + mouseout: this._resetZIndex + }); + } + + var newShadow = options.icon.createShadow(this._shadow), + addShadow = false; + + if (newShadow !== this._shadow) { + this._removeShadow(); + addShadow = true; + } + + if (newShadow) { + addClass(newShadow, classToAdd); + newShadow.alt = ''; + } + this._shadow = newShadow; + + + if (options.opacity < 1) { + this._updateOpacity(); + } + + + if (addIcon) { + this.getPane().appendChild(this._icon); + } + this._initInteraction(); + if (newShadow && addShadow) { + this.getPane('shadowPane').appendChild(this._shadow); + } + }, + + _removeIcon: function () { + if (this.options.riseOnHover) { + this.off({ + mouseover: this._bringToFront, + mouseout: this._resetZIndex + }); + } + + remove(this._icon); + this.removeInteractiveTarget(this._icon); + + this._icon = null; + }, + + _removeShadow: function () { + if (this._shadow) { + remove(this._shadow); + } + this._shadow = null; + }, + + _setPos: function (pos) { + setPosition(this._icon, pos); + + if (this._shadow) { + setPosition(this._shadow, pos); + } + + this._zIndex = pos.y + this.options.zIndexOffset; + + this._resetZIndex(); + }, + + _updateZIndex: function (offset) { + this._icon.style.zIndex = this._zIndex + offset; + }, + + _animateZoom: function (opt) { + var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); + + this._setPos(pos); + }, + + _initInteraction: function () { + + if (!this.options.interactive) { return; } + + addClass(this._icon, 'leaflet-interactive'); + + this.addInteractiveTarget(this._icon); + + if (MarkerDrag) { + var draggable = this.options.draggable; + if (this.dragging) { + draggable = this.dragging.enabled(); + this.dragging.disable(); + } + + this.dragging = new MarkerDrag(this); + + if (draggable) { + this.dragging.enable(); + } + } + }, + + // @method setOpacity(opacity: Number): this + // Changes the opacity of the marker. + setOpacity: function (opacity) { + this.options.opacity = opacity; + if (this._map) { + this._updateOpacity(); + } + + return this; + }, + + _updateOpacity: function () { + var opacity = this.options.opacity; + + setOpacity(this._icon, opacity); + + if (this._shadow) { + setOpacity(this._shadow, opacity); + } + }, + + _bringToFront: function () { + this._updateZIndex(this.options.riseOffset); + }, + + _resetZIndex: function () { + this._updateZIndex(0); + }, + + _getPopupAnchor: function () { + return this.options.icon.options.popupAnchor; + }, + + _getTooltipAnchor: function () { + return this.options.icon.options.tooltipAnchor; + } +}); + + +// factory L.marker(latlng: LatLng, options? : Marker options) + +// @factory L.marker(latlng: LatLng, options? : Marker options) +// Instantiates a Marker object given a geographical point and optionally an options object. +function marker(latlng, options) { + return new Marker(latlng, options); +} + +/* + * @class Path + * @aka L.Path + * @inherits Interactive layer + * + * An abstract class that contains options and constants shared between vector + * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`. + */ + +var Path = Layer.extend({ + + // @section + // @aka Path options + options: { + // @option stroke: Boolean = true + // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles. + stroke: true, + + // @option color: String = '#3388ff' + // Stroke color + color: '#3388ff', + + // @option weight: Number = 3 + // Stroke width in pixels + weight: 3, + + // @option opacity: Number = 1.0 + // Stroke opacity + opacity: 1, + + // @option lineCap: String= 'round' + // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke. + lineCap: 'round', + + // @option lineJoin: String = 'round' + // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke. + lineJoin: 'round', + + // @option dashArray: String = null + // A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility). + dashArray: null, + + // @option dashOffset: String = null + // A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility). + dashOffset: null, + + // @option fill: Boolean = depends + // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles. + fill: false, + + // @option fillColor: String = * + // Fill color. Defaults to the value of the [`color`](#path-color) option + fillColor: null, + + // @option fillOpacity: Number = 0.2 + // Fill opacity. + fillOpacity: 0.2, + + // @option fillRule: String = 'evenodd' + // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined. + fillRule: 'evenodd', + + // className: '', + + // Option inherited from "Interactive layer" abstract class + interactive: true, + + // @option bubblingMouseEvents: Boolean = true + // When `true`, a mouse event on this path will trigger the same event on the map + // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). + bubblingMouseEvents: true + }, + + beforeAdd: function (map) { + // Renderer is set here because we need to call renderer.getEvents + // before this.getEvents. + this._renderer = map.getRenderer(this); + }, + + onAdd: function () { + this._renderer._initPath(this); + this._reset(); + this._renderer._addPath(this); + }, + + onRemove: function () { + this._renderer._removePath(this); + }, + + // @method redraw(): this + // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses. + redraw: function () { + if (this._map) { + this._renderer._updatePath(this); + } + return this; + }, + + // @method setStyle(style: Path options): this + // Changes the appearance of a Path based on the options in the `Path options` object. + setStyle: function (style) { + setOptions(this, style); + if (this._renderer) { + this._renderer._updateStyle(this); + } + return this; + }, + + // @method bringToFront(): this + // Brings the layer to the top of all path layers. + bringToFront: function () { + if (this._renderer) { + this._renderer._bringToFront(this); + } + return this; + }, + + // @method bringToBack(): this + // Brings the layer to the bottom of all path layers. + bringToBack: function () { + if (this._renderer) { + this._renderer._bringToBack(this); + } + return this; + }, + + getElement: function () { + return this._path; + }, + + _reset: function () { + // defined in child classes + this._project(); + this._update(); + }, + + _clickTolerance: function () { + // used when doing hit detection for Canvas layers + return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance; + } +}); + +/* + * @class CircleMarker + * @aka L.CircleMarker + * @inherits Path + * + * A circle of a fixed size with radius specified in pixels. Extends `Path`. + */ + +var CircleMarker = Path.extend({ + + // @section + // @aka CircleMarker options + options: { + fill: true, + + // @option radius: Number = 10 + // Radius of the circle marker, in pixels + radius: 10 + }, + + initialize: function (latlng, options) { + setOptions(this, options); + this._latlng = toLatLng(latlng); + this._radius = this.options.radius; + }, + + // @method setLatLng(latLng: LatLng): this + // Sets the position of a circle marker to a new location. + setLatLng: function (latlng) { + this._latlng = toLatLng(latlng); + this.redraw(); + return this.fire('move', {latlng: this._latlng}); + }, + + // @method getLatLng(): LatLng + // Returns the current geographical position of the circle marker + getLatLng: function () { + return this._latlng; + }, + + // @method setRadius(radius: Number): this + // Sets the radius of a circle marker. Units are in pixels. + setRadius: function (radius) { + this.options.radius = this._radius = radius; + return this.redraw(); + }, + + // @method getRadius(): Number + // Returns the current radius of the circle + getRadius: function () { + return this._radius; + }, + + setStyle : function (options) { + var radius = options && options.radius || this._radius; + Path.prototype.setStyle.call(this, options); + this.setRadius(radius); + return this; + }, + + _project: function () { + this._point = this._map.latLngToLayerPoint(this._latlng); + this._updateBounds(); + }, + + _updateBounds: function () { + var r = this._radius, + r2 = this._radiusY || r, + w = this._clickTolerance(), + p = [r + w, r2 + w]; + this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p)); + }, + + _update: function () { + if (this._map) { + this._updatePath(); + } + }, + + _updatePath: function () { + this._renderer._updateCircle(this); + }, + + _empty: function () { + return this._radius && !this._renderer._bounds.intersects(this._pxBounds); + }, + + // Needed by the `Canvas` renderer for interactivity + _containsPoint: function (p) { + return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); + } +}); + + +// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options) +// Instantiates a circle marker object given a geographical point, and an optional options object. +function circleMarker(latlng, options) { + return new CircleMarker(latlng, options); +} + +/* + * @class Circle + * @aka L.Circle + * @inherits CircleMarker + * + * A class for drawing circle overlays on a map. Extends `CircleMarker`. + * + * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion). + * + * @example + * + * ```js + * L.circle([50.5, 30.5], {radius: 200}).addTo(map); + * ``` + */ + +var Circle = CircleMarker.extend({ + + initialize: function (latlng, options, legacyOptions) { + if (typeof options === 'number') { + // Backwards compatibility with 0.7.x factory (latlng, radius, options?) + options = extend({}, legacyOptions, {radius: options}); + } + setOptions(this, options); + this._latlng = toLatLng(latlng); + + if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } + + // @section + // @aka Circle options + // @option radius: Number; Radius of the circle, in meters. + this._mRadius = this.options.radius; + }, + + // @method setRadius(radius: Number): this + // Sets the radius of a circle. Units are in meters. + setRadius: function (radius) { + this._mRadius = radius; + return this.redraw(); + }, + + // @method getRadius(): Number + // Returns the current radius of a circle. Units are in meters. + getRadius: function () { + return this._mRadius; + }, + + // @method getBounds(): LatLngBounds + // Returns the `LatLngBounds` of the path. + getBounds: function () { + var half = [this._radius, this._radiusY || this._radius]; + + return new LatLngBounds( + this._map.layerPointToLatLng(this._point.subtract(half)), + this._map.layerPointToLatLng(this._point.add(half))); + }, + + setStyle: Path.prototype.setStyle, + + _project: function () { + + var lng = this._latlng.lng, + lat = this._latlng.lat, + map = this._map, + crs = map.options.crs; + + if (crs.distance === Earth.distance) { + var d = Math.PI / 180, + latR = (this._mRadius / Earth.R) / d, + top = map.project([lat + latR, lng]), + bottom = map.project([lat - latR, lng]), + p = top.add(bottom).divideBy(2), + lat2 = map.unproject(p).lat, + lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / + (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; + + if (isNaN(lngR) || lngR === 0) { + lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425 + } + + this._point = p.subtract(map.getPixelOrigin()); + this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x; + this._radiusY = p.y - top.y; + + } else { + var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); + + this._point = map.latLngToLayerPoint(this._latlng); + this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x; + } + + this._updateBounds(); + } +}); + +// @factory L.circle(latlng: LatLng, options?: Circle options) +// Instantiates a circle object given a geographical point, and an options object +// which contains the circle radius. +// @alternative +// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options) +// Obsolete way of instantiating a circle, for compatibility with 0.7.x code. +// Do not use in new applications or plugins. +function circle(latlng, options, legacyOptions) { + return new Circle(latlng, options, legacyOptions); +} + +/* + * @class Polyline + * @aka L.Polyline + * @inherits Path + * + * A class for drawing polyline overlays on a map. Extends `Path`. + * + * @example + * + * ```js + * // create a red polyline from an array of LatLng points + * var latlngs = [ + * [45.51, -122.68], + * [37.77, -122.43], + * [34.04, -118.2] + * ]; + * + * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map); + * + * // zoom the map to the polyline + * map.fitBounds(polyline.getBounds()); + * ``` + * + * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape: + * + * ```js + * // create a red polyline from an array of arrays of LatLng points + * var latlngs = [ + * [[45.51, -122.68], + * [37.77, -122.43], + * [34.04, -118.2]], + * [[40.78, -73.91], + * [41.83, -87.62], + * [32.76, -96.72]] + * ]; + * ``` + */ + + +var Polyline = Path.extend({ + + // @section + // @aka Polyline options + options: { + // @option smoothFactor: Number = 1.0 + // How much to simplify the polyline on each zoom level. More means + // better performance and smoother look, and less means more accurate representation. + smoothFactor: 1.0, + + // @option noClip: Boolean = false + // Disable polyline clipping. + noClip: false + }, + + initialize: function (latlngs, options) { + setOptions(this, options); + this._setLatLngs(latlngs); + }, + + // @method getLatLngs(): LatLng[] + // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline. + getLatLngs: function () { + return this._latlngs; + }, + + // @method setLatLngs(latlngs: LatLng[]): this + // Replaces all the points in the polyline with the given array of geographical points. + setLatLngs: function (latlngs) { + this._setLatLngs(latlngs); + return this.redraw(); + }, + + // @method isEmpty(): Boolean + // Returns `true` if the Polyline has no LatLngs. + isEmpty: function () { + return !this._latlngs.length; + }, + + // @method closestLayerPoint(p: Point): Point + // Returns the point closest to `p` on the Polyline. + closestLayerPoint: function (p) { + var minDistance = Infinity, + minPoint = null, + closest = _sqClosestPointOnSegment, + p1, p2; + + for (var j = 0, jLen = this._parts.length; j < jLen; j++) { + var points = this._parts[j]; + + for (var i = 1, len = points.length; i < len; i++) { + p1 = points[i - 1]; + p2 = points[i]; + + var sqDist = closest(p, p1, p2, true); + + if (sqDist < minDistance) { + minDistance = sqDist; + minPoint = closest(p, p1, p2); + } + } + } + if (minPoint) { + minPoint.distance = Math.sqrt(minDistance); + } + return minPoint; + }, + + // @method getCenter(): LatLng + // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline. + getCenter: function () { + // throws error when not yet added to map as this center calculation requires projected coordinates + if (!this._map) { + throw new Error('Must add layer to map before using getCenter()'); + } + + var i, halfDist, segDist, dist, p1, p2, ratio, + points = this._rings[0], + len = points.length; + + if (!len) { return null; } + + // polyline centroid algorithm; only uses the first ring if there are multiple + + for (i = 0, halfDist = 0; i < len - 1; i++) { + halfDist += points[i].distanceTo(points[i + 1]) / 2; + } + + // The line is so small in the current view that all points are on the same pixel. + if (halfDist === 0) { + return this._map.layerPointToLatLng(points[0]); + } + + for (i = 0, dist = 0; i < len - 1; i++) { + p1 = points[i]; + p2 = points[i + 1]; + segDist = p1.distanceTo(p2); + dist += segDist; + + if (dist > halfDist) { + ratio = (dist - halfDist) / segDist; + return this._map.layerPointToLatLng([ + p2.x - ratio * (p2.x - p1.x), + p2.y - ratio * (p2.y - p1.y) + ]); + } + } + }, + + // @method getBounds(): LatLngBounds + // Returns the `LatLngBounds` of the path. + getBounds: function () { + return this._bounds; + }, + + // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this + // Adds a given point to the polyline. By default, adds to the first ring of + // the polyline in case of a multi-polyline, but can be overridden by passing + // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)). + addLatLng: function (latlng, latlngs) { + latlngs = latlngs || this._defaultShape(); + latlng = toLatLng(latlng); + latlngs.push(latlng); + this._bounds.extend(latlng); + return this.redraw(); + }, + + _setLatLngs: function (latlngs) { + this._bounds = new LatLngBounds(); + this._latlngs = this._convertLatLngs(latlngs); + }, + + _defaultShape: function () { + return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0]; + }, + + // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way + _convertLatLngs: function (latlngs) { + var result = [], + flat = isFlat(latlngs); + + for (var i = 0, len = latlngs.length; i < len; i++) { + if (flat) { + result[i] = toLatLng(latlngs[i]); + this._bounds.extend(result[i]); + } else { + result[i] = this._convertLatLngs(latlngs[i]); + } + } + + return result; + }, + + _project: function () { + var pxBounds = new Bounds(); + this._rings = []; + this._projectLatlngs(this._latlngs, this._rings, pxBounds); + + var w = this._clickTolerance(), + p = new Point(w, w); + + if (this._bounds.isValid() && pxBounds.isValid()) { + pxBounds.min._subtract(p); + pxBounds.max._add(p); + this._pxBounds = pxBounds; + } + }, + + // recursively turns latlngs into a set of rings with projected coordinates + _projectLatlngs: function (latlngs, result, projectedBounds) { + var flat = latlngs[0] instanceof LatLng, + len = latlngs.length, + i, ring; + + if (flat) { + ring = []; + for (i = 0; i < len; i++) { + ring[i] = this._map.latLngToLayerPoint(latlngs[i]); + projectedBounds.extend(ring[i]); + } + result.push(ring); + } else { + for (i = 0; i < len; i++) { + this._projectLatlngs(latlngs[i], result, projectedBounds); + } + } + }, + + // clip polyline by renderer bounds so that we have less to render for performance + _clipPoints: function () { + var bounds = this._renderer._bounds; + + this._parts = []; + if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { + return; + } + + if (this.options.noClip) { + this._parts = this._rings; + return; + } + + var parts = this._parts, + i, j, k, len, len2, segment, points; + + for (i = 0, k = 0, len = this._rings.length; i < len; i++) { + points = this._rings[i]; + + for (j = 0, len2 = points.length; j < len2 - 1; j++) { + segment = clipSegment(points[j], points[j + 1], bounds, j, true); + + if (!segment) { continue; } + + parts[k] = parts[k] || []; + parts[k].push(segment[0]); + + // if segment goes out of screen, or it's the last one, it's the end of the line part + if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) { + parts[k].push(segment[1]); + k++; + } + } + } + }, + + // simplify each clipped part of the polyline for performance + _simplifyPoints: function () { + var parts = this._parts, + tolerance = this.options.smoothFactor; + + for (var i = 0, len = parts.length; i < len; i++) { + parts[i] = simplify(parts[i], tolerance); + } + }, + + _update: function () { + if (!this._map) { return; } + + this._clipPoints(); + this._simplifyPoints(); + this._updatePath(); + }, + + _updatePath: function () { + this._renderer._updatePoly(this); + }, + + // Needed by the `Canvas` renderer for interactivity + _containsPoint: function (p, closed) { + var i, j, k, len, len2, part, + w = this._clickTolerance(); + + if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } + + // hit detection for polylines + for (i = 0, len = this._parts.length; i < len; i++) { + part = this._parts[i]; + + for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { + if (!closed && (j === 0)) { continue; } + + if (pointToSegmentDistance(p, part[k], part[j]) <= w) { + return true; + } + } + } + return false; + } +}); + +// @factory L.polyline(latlngs: LatLng[], options?: Polyline options) +// Instantiates a polyline object given an array of geographical points and +// optionally an options object. You can create a `Polyline` object with +// multiple separate lines (`MultiPolyline`) by passing an array of arrays +// of geographic points. +function polyline(latlngs, options) { + return new Polyline(latlngs, options); +} + +// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1. +Polyline._flat = _flat; + +/* + * @class Polygon + * @aka L.Polygon + * @inherits Polyline + * + * A class for drawing polygon overlays on a map. Extends `Polyline`. + * + * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points. + * + * + * @example + * + * ```js + * // create a red polygon from an array of LatLng points + * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]]; + * + * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map); + * + * // zoom the map to the polygon + * map.fitBounds(polygon.getBounds()); + * ``` + * + * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape: + * + * ```js + * var latlngs = [ + * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring + * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole + * ]; + * ``` + * + * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape. + * + * ```js + * var latlngs = [ + * [ // first polygon + * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring + * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole + * ], + * [ // second polygon + * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]] + * ] + * ]; + * ``` + */ + +var Polygon = Polyline.extend({ + + options: { + fill: true + }, + + isEmpty: function () { + return !this._latlngs.length || !this._latlngs[0].length; + }, + + getCenter: function () { + // throws error when not yet added to map as this center calculation requires projected coordinates + if (!this._map) { + throw new Error('Must add layer to map before using getCenter()'); + } + + var i, j, p1, p2, f, area, x, y, center, + points = this._rings[0], + len = points.length; + + if (!len) { return null; } + + // polygon centroid algorithm; only uses the first ring if there are multiple + + area = x = y = 0; + + for (i = 0, j = len - 1; i < len; j = i++) { + p1 = points[i]; + p2 = points[j]; + + f = p1.y * p2.x - p2.y * p1.x; + x += (p1.x + p2.x) * f; + y += (p1.y + p2.y) * f; + area += f * 3; + } + + if (area === 0) { + // Polygon is so small that all points are on same pixel. + center = points[0]; + } else { + center = [x / area, y / area]; + } + return this._map.layerPointToLatLng(center); + }, + + _convertLatLngs: function (latlngs) { + var result = Polyline.prototype._convertLatLngs.call(this, latlngs), + len = result.length; + + // remove last point if it equals first one + if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) { + result.pop(); + } + return result; + }, + + _setLatLngs: function (latlngs) { + Polyline.prototype._setLatLngs.call(this, latlngs); + if (isFlat(this._latlngs)) { + this._latlngs = [this._latlngs]; + } + }, + + _defaultShape: function () { + return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; + }, + + _clipPoints: function () { + // polygons need a different clipping algorithm so we redefine that + + var bounds = this._renderer._bounds, + w = this.options.weight, + p = new Point(w, w); + + // increase clip padding by stroke width to avoid stroke on clip edges + bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p)); + + this._parts = []; + if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { + return; + } + + if (this.options.noClip) { + this._parts = this._rings; + return; + } + + for (var i = 0, len = this._rings.length, clipped; i < len; i++) { + clipped = clipPolygon(this._rings[i], bounds, true); + if (clipped.length) { + this._parts.push(clipped); + } + } + }, + + _updatePath: function () { + this._renderer._updatePoly(this, true); + }, + + // Needed by the `Canvas` renderer for interactivity + _containsPoint: function (p) { + var inside = false, + part, p1, p2, i, j, k, len, len2; + + if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } + + // ray casting algorithm for detecting if point is in polygon + for (i = 0, len = this._parts.length; i < len; i++) { + part = this._parts[i]; + + for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { + p1 = part[j]; + p2 = part[k]; + + if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) { + inside = !inside; + } + } + } + + // also check if it's on polygon stroke + return inside || Polyline.prototype._containsPoint.call(this, p, true); + } + +}); + + +// @factory L.polygon(latlngs: LatLng[], options?: Polyline options) +function polygon(latlngs, options) { + return new Polygon(latlngs, options); +} + +/* + * @class GeoJSON + * @aka L.GeoJSON + * @inherits FeatureGroup + * + * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse + * GeoJSON data and display it on the map. Extends `FeatureGroup`. + * + * @example + * + * ```js + * L.geoJSON(data, { + * style: function (feature) { + * return {color: feature.properties.color}; + * } + * }).bindPopup(function (layer) { + * return layer.feature.properties.description; + * }).addTo(map); + * ``` + */ + +var GeoJSON = FeatureGroup.extend({ + + /* @section + * @aka GeoJSON options + * + * @option pointToLayer: Function = * + * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally + * called when data is added, passing the GeoJSON point feature and its `LatLng`. + * The default is to spawn a default `Marker`: + * ```js + * function(geoJsonPoint, latlng) { + * return L.marker(latlng); + * } + * ``` + * + * @option style: Function = * + * A `Function` defining the `Path options` for styling GeoJSON lines and polygons, + * called internally when data is added. + * The default value is to not override any defaults: + * ```js + * function (geoJsonFeature) { + * return {} + * } + * ``` + * + * @option onEachFeature: Function = * + * A `Function` that will be called once for each created `Feature`, after it has + * been created and styled. Useful for attaching events and popups to features. + * The default is to do nothing with the newly created layers: + * ```js + * function (feature, layer) {} + * ``` + * + * @option filter: Function = * + * A `Function` that will be used to decide whether to include a feature or not. + * The default is to include all features: + * ```js + * function (geoJsonFeature) { + * return true; + * } + * ``` + * Note: dynamically changing the `filter` option will have effect only on newly + * added data. It will _not_ re-evaluate already included features. + * + * @option coordsToLatLng: Function = * + * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s. + * The default is the `coordsToLatLng` static method. + */ + + initialize: function (geojson, options) { + setOptions(this, options); + + this._layers = {}; + + if (geojson) { + this.addData(geojson); + } + }, + + // @method addData( data ): this + // Adds a GeoJSON object to the layer. + addData: function (geojson) { + var features = isArray(geojson) ? geojson : geojson.features, + i, len, feature; + + if (features) { + for (i = 0, len = features.length; i < len; i++) { + // only add this if geometry or geometries are set and not null + feature = features[i]; + if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { + this.addData(feature); + } + } + return this; + } + + var options = this.options; + + if (options.filter && !options.filter(geojson)) { return this; } + + var layer = geometryToLayer(geojson, options); + if (!layer) { + return this; + } + layer.feature = asFeature(geojson); + + layer.defaultOptions = layer.options; + this.resetStyle(layer); + + if (options.onEachFeature) { + options.onEachFeature(geojson, layer); + } + + return this.addLayer(layer); + }, + + // @method resetStyle( layer ): this + // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events. + resetStyle: function (layer) { + // reset any custom styles + layer.options = extend({}, layer.defaultOptions); + this._setLayerStyle(layer, this.options.style); + return this; + }, + + // @method setStyle( style ): this + // Changes styles of GeoJSON vector layers with the given style function. + setStyle: function (style) { + return this.eachLayer(function (layer) { + this._setLayerStyle(layer, style); + }, this); + }, + + _setLayerStyle: function (layer, style) { + if (typeof style === 'function') { + style = style(layer.feature); + } + if (layer.setStyle) { + layer.setStyle(style); + } + } +}); + +// @section +// There are several static functions which can be called without instantiating L.GeoJSON: + +// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer +// Creates a `Layer` from a given GeoJSON feature. Can use a custom +// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng) +// functions if provided as options. +function geometryToLayer(geojson, options) { + + var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, + coords = geometry ? geometry.coordinates : null, + layers = [], + pointToLayer = options && options.pointToLayer, + _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng, + latlng, latlngs, i, len; + + if (!coords && !geometry) { + return null; + } + + switch (geometry.type) { + case 'Point': + latlng = _coordsToLatLng(coords); + return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng); + + case 'MultiPoint': + for (i = 0, len = coords.length; i < len; i++) { + latlng = _coordsToLatLng(coords[i]); + layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng)); + } + return new FeatureGroup(layers); + + case 'LineString': + case 'MultiLineString': + latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng); + return new Polyline(latlngs, options); + + case 'Polygon': + case 'MultiPolygon': + latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng); + return new Polygon(latlngs, options); + + case 'GeometryCollection': + for (i = 0, len = geometry.geometries.length; i < len; i++) { + var layer = geometryToLayer({ + geometry: geometry.geometries[i], + type: 'Feature', + properties: geojson.properties + }, options); + + if (layer) { + layers.push(layer); + } + } + return new FeatureGroup(layers); + + default: + throw new Error('Invalid GeoJSON object.'); + } +} + +// @function coordsToLatLng(coords: Array): LatLng +// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude) +// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points. +function coordsToLatLng(coords) { + return new LatLng(coords[1], coords[0], coords[2]); +} + +// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array +// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array. +// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default). +// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function. +function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) { + var latlngs = []; + + for (var i = 0, len = coords.length, latlng; i < len; i++) { + latlng = levelsDeep ? + coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) : + (_coordsToLatLng || coordsToLatLng)(coords[i]); + + latlngs.push(latlng); + } + + return latlngs; +} + +// @function latLngToCoords(latlng: LatLng, precision?: Number): Array +// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng) +function latLngToCoords(latlng, precision) { + precision = typeof precision === 'number' ? precision : 6; + return latlng.alt !== undefined ? + [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] : + [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)]; +} + +// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array +// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs) +// `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default. +function latLngsToCoords(latlngs, levelsDeep, closed, precision) { + var coords = []; + + for (var i = 0, len = latlngs.length; i < len; i++) { + coords.push(levelsDeep ? + latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) : + latLngToCoords(latlngs[i], precision)); + } + + if (!levelsDeep && closed) { + coords.push(coords[0]); + } + + return coords; +} + +function getFeature(layer, newGeometry) { + return layer.feature ? + extend({}, layer.feature, {geometry: newGeometry}) : + asFeature(newGeometry); +} + +// @function asFeature(geojson: Object): Object +// Normalize GeoJSON geometries/features into GeoJSON features. +function asFeature(geojson) { + if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') { + return geojson; + } + + return { + type: 'Feature', + properties: {}, + geometry: geojson + }; +} + +var PointToGeoJSON = { + toGeoJSON: function (precision) { + return getFeature(this, { + type: 'Point', + coordinates: latLngToCoords(this.getLatLng(), precision) + }); + } +}; + +// @namespace Marker +// @method toGeoJSON(): Object +// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature). +Marker.include(PointToGeoJSON); + +// @namespace CircleMarker +// @method toGeoJSON(): Object +// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature). +Circle.include(PointToGeoJSON); +CircleMarker.include(PointToGeoJSON); + + +// @namespace Polyline +// @method toGeoJSON(): Object +// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature). +Polyline.include({ + toGeoJSON: function (precision) { + var multi = !isFlat(this._latlngs); + + var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision); + + return getFeature(this, { + type: (multi ? 'Multi' : '') + 'LineString', + coordinates: coords + }); + } +}); + +// @namespace Polygon +// @method toGeoJSON(): Object +// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature). +Polygon.include({ + toGeoJSON: function (precision) { + var holes = !isFlat(this._latlngs), + multi = holes && !isFlat(this._latlngs[0]); + + var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision); + + if (!holes) { + coords = [coords]; + } + + return getFeature(this, { + type: (multi ? 'Multi' : '') + 'Polygon', + coordinates: coords + }); + } +}); + + +// @namespace LayerGroup +LayerGroup.include({ + toMultiPoint: function (precision) { + var coords = []; + + this.eachLayer(function (layer) { + coords.push(layer.toGeoJSON(precision).geometry.coordinates); + }); + + return getFeature(this, { + type: 'MultiPoint', + coordinates: coords + }); + }, + + // @method toGeoJSON(): Object + // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`). + toGeoJSON: function (precision) { + + var type = this.feature && this.feature.geometry && this.feature.geometry.type; + + if (type === 'MultiPoint') { + return this.toMultiPoint(precision); + } + + var isGeometryCollection = type === 'GeometryCollection', + jsons = []; + + this.eachLayer(function (layer) { + if (layer.toGeoJSON) { + var json = layer.toGeoJSON(precision); + if (isGeometryCollection) { + jsons.push(json.geometry); + } else { + var feature = asFeature(json); + // Squash nested feature collections + if (feature.type === 'FeatureCollection') { + jsons.push.apply(jsons, feature.features); + } else { + jsons.push(feature); + } + } + } + }); + + if (isGeometryCollection) { + return getFeature(this, { + geometries: jsons, + type: 'GeometryCollection' + }); + } + + return { + type: 'FeatureCollection', + features: jsons + }; + } +}); + +// @namespace GeoJSON +// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options) +// Creates a GeoJSON layer. Optionally accepts an object in +// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map +// (you can alternatively add it later with `addData` method) and an `options` object. +function geoJSON(geojson, options) { + return new GeoJSON(geojson, options); +} + +// Backward compatibility. +var geoJson = geoJSON; + +/* + * @class ImageOverlay + * @aka L.ImageOverlay + * @inherits Interactive layer + * + * Used to load and display a single image over specific bounds of the map. Extends `Layer`. + * + * @example + * + * ```js + * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg', + * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]]; + * L.imageOverlay(imageUrl, imageBounds).addTo(map); + * ``` + */ + +var ImageOverlay = Layer.extend({ + + // @section + // @aka ImageOverlay options + options: { + // @option opacity: Number = 1.0 + // The opacity of the image overlay. + opacity: 1, + + // @option alt: String = '' + // Text for the `alt` attribute of the image (useful for accessibility). + alt: '', + + // @option interactive: Boolean = false + // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered. + interactive: false, + + // @option crossOrigin: Boolean|String = false + // Whether the crossOrigin attribute will be added to the image. + // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data. + // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. + crossOrigin: false, + + // @option errorOverlayUrl: String = '' + // URL to the overlay image to show in place of the overlay that failed to load. + errorOverlayUrl: '', + + // @option zIndex: Number = 1 + // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer. + zIndex: 1, + + // @option className: String = '' + // A custom class name to assign to the image. Empty by default. + className: '' + }, + + initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) + this._url = url; + this._bounds = toLatLngBounds(bounds); + + setOptions(this, options); + }, + + onAdd: function () { + if (!this._image) { + this._initImage(); + + if (this.options.opacity < 1) { + this._updateOpacity(); + } + } + + if (this.options.interactive) { + addClass(this._image, 'leaflet-interactive'); + this.addInteractiveTarget(this._image); + } + + this.getPane().appendChild(this._image); + this._reset(); + }, + + onRemove: function () { + remove(this._image); + if (this.options.interactive) { + this.removeInteractiveTarget(this._image); + } + }, + + // @method setOpacity(opacity: Number): this + // Sets the opacity of the overlay. + setOpacity: function (opacity) { + this.options.opacity = opacity; + + if (this._image) { + this._updateOpacity(); + } + return this; + }, + + setStyle: function (styleOpts) { + if (styleOpts.opacity) { + this.setOpacity(styleOpts.opacity); + } + return this; + }, + + // @method bringToFront(): this + // Brings the layer to the top of all overlays. + bringToFront: function () { + if (this._map) { + toFront(this._image); + } + return this; + }, + + // @method bringToBack(): this + // Brings the layer to the bottom of all overlays. + bringToBack: function () { + if (this._map) { + toBack(this._image); + } + return this; + }, + + // @method setUrl(url: String): this + // Changes the URL of the image. + setUrl: function (url) { + this._url = url; + + if (this._image) { + this._image.src = url; + } + return this; + }, + + // @method setBounds(bounds: LatLngBounds): this + // Update the bounds that this ImageOverlay covers + setBounds: function (bounds) { + this._bounds = toLatLngBounds(bounds); + + if (this._map) { + this._reset(); + } + return this; + }, + + getEvents: function () { + var events = { + zoom: this._reset, + viewreset: this._reset + }; + + if (this._zoomAnimated) { + events.zoomanim = this._animateZoom; + } + + return events; + }, + + // @method setZIndex(value: Number): this + // Changes the [zIndex](#imageoverlay-zindex) of the image overlay. + setZIndex: function (value) { + this.options.zIndex = value; + this._updateZIndex(); + return this; + }, + + // @method getBounds(): LatLngBounds + // Get the bounds that this ImageOverlay covers + getBounds: function () { + return this._bounds; + }, + + // @method getElement(): HTMLElement + // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement) + // used by this overlay. + getElement: function () { + return this._image; + }, + + _initImage: function () { + var wasElementSupplied = this._url.tagName === 'IMG'; + var img = this._image = wasElementSupplied ? this._url : create$1('img'); + + addClass(img, 'leaflet-image-layer'); + if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); } + if (this.options.className) { addClass(img, this.options.className); } + + img.onselectstart = falseFn; + img.onmousemove = falseFn; + + // @event load: Event + // Fired when the ImageOverlay layer has loaded its image + img.onload = bind(this.fire, this, 'load'); + img.onerror = bind(this._overlayOnError, this, 'error'); + + if (this.options.crossOrigin || this.options.crossOrigin === '') { + img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; + } + + if (this.options.zIndex) { + this._updateZIndex(); + } + + if (wasElementSupplied) { + this._url = img.src; + return; + } + + img.src = this._url; + img.alt = this.options.alt; + }, + + _animateZoom: function (e) { + var scale = this._map.getZoomScale(e.zoom), + offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min; + + setTransform(this._image, offset, scale); + }, + + _reset: function () { + var image = this._image, + bounds = new Bounds( + this._map.latLngToLayerPoint(this._bounds.getNorthWest()), + this._map.latLngToLayerPoint(this._bounds.getSouthEast())), + size = bounds.getSize(); + + setPosition(image, bounds.min); + + image.style.width = size.x + 'px'; + image.style.height = size.y + 'px'; + }, + + _updateOpacity: function () { + setOpacity(this._image, this.options.opacity); + }, + + _updateZIndex: function () { + if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) { + this._image.style.zIndex = this.options.zIndex; + } + }, + + _overlayOnError: function () { + // @event error: Event + // Fired when the ImageOverlay layer fails to load its image + this.fire('error'); + + var errorUrl = this.options.errorOverlayUrl; + if (errorUrl && this._url !== errorUrl) { + this._url = errorUrl; + this._image.src = errorUrl; + } + } +}); + +// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options) +// Instantiates an image overlay object given the URL of the image and the +// geographical bounds it is tied to. +var imageOverlay = function (url, bounds, options) { + return new ImageOverlay(url, bounds, options); +}; + +/* + * @class VideoOverlay + * @aka L.VideoOverlay + * @inherits ImageOverlay + * + * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`. + * + * A video overlay uses the [`