Commit 6f00c73c authored by icyrizard's avatar icyrizard

worked on scriptie, altered icon

parent 5650724f
scriptie.tex: title.tex fsb.tex area_api.tex references.bbl
scriptie.tex: title.tex fsb.tex area_api.tex lsm-api.tex drfsm-api.tex references.bbl
%.pdf: %.tex
pdflatex $^
......
......@@ -10,14 +10,16 @@
%\maketitle
\section{Introduction}
The UrbanFlood is a European project that aims to create an early warning system in European cities\cite{UrbanFlood}. In the today's changing climate, more and more cities have to deal with floods more often. \todo{Boek ref}. Due to extreme rainfall, rising water or long lasting drought, the instruments that protect the civilians of a certain area are negatively influenced. The project involves setting up a system that can make estimates on how dikes would behave in the near future. Vigilance against weak spots in the dykes is needed 24/7. To do this they placed sensors in dykes that can be monitored remotely via internet. But this not the only concern. They also developed a system that can create flood simulations of a certain area. This is used for testing dikes in a possible scenario but also for educative proposes. This subject is discussed in the present document.
The UrbanFlood is a European project that aims to create an early warning system in European cities\cite{UrbanFlood}. In the today's changing climate, more and more cities have to deal with floods more often. A book appeared of the Urban Flood project. (Ashley, Garvin, Pasche, Vassilopoulos and Zevenbergen, 2007)\cite{urbanflood1}. It describes the challenges that are faced around this subject. A lot of research went in the possible solutions for specific problems. Due to extreme rainfall, rising water or long lasting drought, the instruments that protect the civilians of a certain area are negatively influenced. The project involves setting up a system that can make estimates on how dikes would behave in the near future. Vigilance against weak spots in the dykes is needed 24/7. To do this they placed sensors in dykes that can be monitored remotely via internet. But this not the only concern. They also developed a system that can create flood simulations of a certain area. This is used for testing dikes in a possible scenario but also for educative proposes. This subject is discussed in the present document.
This project concerns the Flood Simulation Browser. The concept of this browser is to illustrate/visualize a flood in a particular area. With this technology people can see the flow that the water will take. When a dike breaks it is important to know where the water will flow. Information about which locations in the area will be under water first can result in a successful evacuation plan. This system already exists, but is build for a multi-touch table only and is accessible by few people. The project that is the subject of this thesis, aims on making it easier available and accessible. The accessibility of the application (the tablet can be at night stand of the dijkgraaf) makes it more likely that someone with right authority can take better decisions about placing/reinforcing dikes or creating evacuation plans. Moreover, if this system is more accessible, then civilians of a certain neighbourhood that is threatened by water, have the ability to gain knowledge of where the water will go first. They then are able to take the the right crucial decisions based on this knowledge in the hour of need.
This thesis concerns the Flood Simulation Browser. The concept of this browser is to illustrate/visualize a flood in a particular area. With this technology people can see the flow that the water will take. When a dike breaks it is important to know where the water will flow. Information about which locations in the area will be under water first can result in a successful evacuation plan. This system already exists on a multi-touch table\cite{touchtable}. The touch table is used in workshops for an interactive way of informing about the risk of floods. The disadvantage of the multi touch table is that it is accessible in certain occasions. The project that is the subject of this thesis, aims on making it easier available and accessible. The accessibility of the application
% (the tablet can be at night stand of the dijkgraaf)\todo{wat is dijkgraaf}
makes it more likely that someone with right authority can take better decisions about placing/reinforcing dikes or creating evacuation plans. Moreover, if this system is more accessible, civilians of a certain area that is threatened by water, have the ability to gain knowledge of where the water will go first. They then are able to take the the right crucial decisions based on this knowledge in the hour of need.
The implementation will be on a multi-touch device. In particular iPad\cite{ipad} and Android\cite{android} tablets. Users can use this application in an intuitive way to get more intelligence about the complex situation at hand. They have the ability to choose from several simulations that were already made for a specific area and also creating new simulations.
This thesis covers design choices that where made in order to reach that goal. The first step is to get an idea of the existing simulation system of which this application will make use of. This includes a description of where this application is situated in the Urban Flood Project as a whole. The study also includes an extensive examination of the capabilities of the different devices available, as well as a requirement analysis specifically for this application. The resulting app will be intend to extend the simulation with a use-ability and above all mobility factor. Of course, performance is one of the main issues here.
A second objective of this study is testing the scalability of the REST\todo{ref REST} server. By testing how much requests the server can handle at once it helps the urban flood project to estimate how many users can use the application simultaneously. The original multi touch application on the multi touch table was only one client for the server. It's important to know that an early warning system stays online under the heavy load of a multitude of clients.
A second objective of this study is testing the scalability of the REST server\cite{REST}. By testing how much requests the server can handle at once it helps the urban flood project to estimate how many users can use the application simultaneously. The original multi touch application on the multi touch table was only one client for the server. It's important to know that an early warning system stays online under the heavy load of a multitude of clients.
The scientific research of this project will be devoted to the development of an intuitive design. The main research will be, where do buttons, lists, and other important components need to be situated in the application. Another aspect of this project is the research for scalability of the server. This data will provide information about how many users can be handled by a server at once. The important part of this research will be the decision making of which framework or tool to use for the implementation of this application.
......@@ -26,9 +28,9 @@ The first part of this document will describe the global information needed for
\part{Discovering the subject}
\label{part:Discovering}
\section{Flood Simulation System}
Two systems are involved in creating the backbone and resource for the Flood Simulation Browser(F.S.B). One part is the system where the client application directly communicates to and where the API is stalled. The other is used by that system to simulate new simulations when a request at client side is made. The systems are needed for retrieving data about a simulation and ultimately to display the simulations. This generation of new simulations is a service of HR Wallingford \cite{wallingford}, a company specialized in providing services like these for water management projects. The F.S.B. is a GUI\cite{GUI} that can display the simulations on a map that were made by the HR Wallingford's server and retrieved from\url{sangkil.science.uva.nl}.
Two systems are involved in creating the backbone and resource for the Flood Simulation Browser(F.S.B). One part is the system where the client application directly communicates to and where the API is stalled, specifically at \url{sangkil.science.uva.nl}. The other is used by that system to simulate new simulations when a request at client side is made. The systems are needed for retrieving data about a simulation and ultimately to display the simulations. The generation of new simulations is a service of HR Wallingford \cite{wallingford}, a company specialized in providing services like these for water management projects. The F.S.B. is a GUI\cite{GUI} that can display the simulations on a map that were made by the HR Wallingford's server and retrieved from \url{sangkil.science.uva.nl}. The implementation of a F.S.B is used for illustrative and educative purposes.
The simulation system of HR Wallingsford runs in the cloud. That means it does all the calculation for the client and it is not important to know at which physical computer the simulation runs. The system can calculate simulations by providing parameters about a certain area. For instance, the location and how much water you want to simulate. When a simulation is submitted and calculated, the simulation is stored. With http calls(GET or POST) to the API at \url{sangkil}, it returns a set of simulations that are present. The important thing to note is that the simulation consists of images that have to be displayed on a map, e.g. Google Maps. The flood simulation system performs a complex simulation with the height map of the area. The height map holds detailed information about the height of an area. With this data, the flow of the water can be calculated and turned in to images. These images are used by the client application, this an important part of this project.
The simulation system of HR Wallingsford runs in the cloud\cite{cloud}. That means it does all the calculation for the client and it is not important to know at which physical computer the simulation runs. The system can calculate simulations by providing parameters about a certain area. For instance, the location and how much water you want to simulate. When a simulation is submitted and calculated, the simulation is stored. With http calls(GET or POST) to the API at \url{sangkil}, it returns a set of simulations that are present. The important thing to note is that the simulation consists of images that have to be displayed on a map, e.g. Google Maps. The flood simulation system performs a complex simulation with the height map of the area. The height map holds detailed information about the height of an area. With this data, the flow of the water can be calculated and turned in to images. These images are used by the client application, this an important part of this project.
Not only does the system calculates the flow of water but it also calculates the estimated route that people take. It can calculate how much people would survive a simulated flood, keeping in mind that this project is all about saving people, this is interesting information.
\subsection{Flood API}
......@@ -76,15 +78,15 @@ Sencha touch 2 is a framework focused on the Model View Controller design patter
In the items array, see Figure \ref{fig:sencha}, the items in that array can be anything supported by Sencha Touch like lists, a tabview, a map, etcetera.
Sencha Touch provides a command-line tool to build a starting environment. It creates files where the developer can start from. It is recommended to use this as a starting point. The advantage is an application that is structured out of the box with a design pattern that is the current standard(MVC). With Sencha Touch 2 there is no need for PhoneGap, unlike jQuery Mobile. Sencha Touch 2 provides the possibility to run the code on your simulator or native device with the command line tool. Testing for Android and iOS became much more simple, resulting in less developing time. Running PhoneGap for Android, needs Eclipse\cite{eclipse} to build, for iOS the developer needs to build in Xcode\cite{xcode}. When testing on both devices the developer needs to be able to test in both environments, and testing from the command-line is a lot easier than running both environments.
A disadvantage of Sencha Touch could be, as seen in figure ~\ref{fig:sencha}, the syntax of Sencha Touch is not so easy at first. So the learning curve could be very steep.
A disadvantage of Sencha Touch could be, as seen in listing \ref{lst:sencha}, the syntax of Sencha Touch is not so easy at first. So the learning curve could be very steep.
The last argument that has a great influence on deciding which framework to use, could be that Sencha Touch provides an add-on that has the capability to create multi-touch charts. The original implementation of the urban flood browser on the multi-touch table has this feature. It would be a great extension of the project if this was possible. So with an eye on the future it could save a lot trouble, and it could add up to a more intuitive design. The next section will discuss this in more detail.
\begin{figure}[H]
\begin{lstlisting}
\vspace{1cm}
\begin{lstlisting}[caption=Sencha Touch application example. This example creates an application with the name 'foo'. The html can be placed inside the items array, label={lst:sencha}]
Ext.application({
name: 'foo',
launch: function() {
Ext.create("Ext.tab.Panel", {
fullscreen: true,
......@@ -99,9 +101,6 @@ Ext.application({
}
});
\end{lstlisting}
\caption{Sencha Touch application example. This example creates an application with the name 'foo'. The html can be placed inside the items array. }
\label{fig:sencha}
\end{figure}
\end{description}
\subsection{Requirements Analysis}
......@@ -184,7 +183,7 @@ Part \ref{part:Discovering} describes the questions and some answers on what the
\part{Implementation Details}
\label{part:implementation}
\section{Server API}
A clear understanding of which request have to be done in order to gain the right information is needed first before beginning with any implementation. The full api is referenced in the appendix \ref{sec:area_api}. The appendix is referenced and used for explanations in the following sections.
A clear understanding of which requests have to be done in order to gain the right information, is needed before beginning with any implementation. The full api is referenced in the appendix A, B and C. The appendix is referenced and used for explanations in the following sections.
\section{Sencha Touch 2}
The final choice for the development of this application was made in favor of Sencha Touch 2. Sencha Touch provides cross-platform capability and is based on web technology. Although Sencha Touch comes with a unique syntax, based on Ext (reference!!), the content, look and logic is based on web languages.
......@@ -231,9 +230,10 @@ Is in sync with multiple urls, \url{http://sangkil.science.uva.nl:8003/area/<are
\texttt{Fields: ['area\_id', 'test\_id', 'submitted']} \\
This store holds information of the population dynamics, meaning how the evacuation of the individuals will proceed.
\item SimulationSummary.js \\
\todo{fields}
Syncs with \url{http://sangkil.science.uva.nl:8003/drfsm/list.json?summary}. This returns a json string that holds a list of all the simulations. A store can be filtered on a certain value. All the simulations have an area\_id specified. The store is filtered on the area\_id selected in List.js. Note that filters stay present, so before doing anything the store has to be cleared of all the filters.
\texttt{Fields :['area\_id', 'test\_id', 'submitted']},\\
Syncs with \url{http://sangkil.science.uva.nl:8003/drfsm/list.json?summary}. This returns a JSON string that holds a list of all the simulations. A store can be filtered on a certain value. All the simulations have an area\_id specified. The store is filtered on the area\_id selected in List.js. Note that filters stay present, so before doing anything the store has to be cleared of all the filters.
\item ChartStore.js \\
\texttt{Fields: ['time', 'volume']}\\
This store is used for creating the chart. The chart expects a jsonStore with data and fields to plot. The data is retrieved from the server at run time of the application in \texttt{Api.js} and set in the store by the setData function.
\end{itemize}
\subsection{Layouts and xtype}
......@@ -331,7 +331,7 @@ The controls to change the simulation step when a simulation is selected, appear
By tapping on the upper button, the next image is placed on the map. The right button plays through the simulation automatically. The left and bottom button both do the opposite. Also a pause button stops calling the next or prevImage function out the map object. As previously discussed, the controls appear in the right top of the screen. If the user needs the controls on the bottom part of the screen, the user can also tap and drag the controls. Because the space of the tablet is limited the user can make the decision of placing the controls somewhere else, note that the controls are constrained to the right side of the screen. The reason is that the controls are meant to be used by the user's right hand.
\subsection{Chart Display}
The chart data is displayed when the user taps on a simulation image on the map. The google maps API supports the click event, the event has a payload where the latitude and longitude data can be obtained. A marker is put down at that location and the API is called to retrieve the izid of the tapped lat,lng. When the izid is known, a second request has to be done to obtain the corresponding .csv file. See listing \ref{lst:csvrequest} for the requests.
The chart data is displayed when the user taps on a simulation image on the map. The google maps API supports the click event, the event has a payload where the latitude and longitude data can be obtained. A marker is put down at that location and the API (at \texttt{sangkil}) is called to retrieve the izid of the tapped lat,lng. When the izid is known, a second request has to be done to obtain the corresponding .csv file. See listing \ref{lst:csvrequest} for the requests.
\begin{lstlisting}[caption={requests for flood data}, label={lst:csvrequests}]
/* obtain izid */
......@@ -373,18 +373,23 @@ Ext.define('app.view.Chart', {
position: 'bottom',
fields: ['Time Steps']
}
]
]
series: [{
type: 'line',
xField: ['time'],
yField: ['volume'],
}],
}]
}
}]
}):
\end{lstlisting}
Because the chart can be fairly large, this component is also made draggable.
The \emph{series} attribute in the Chart configuration means what kind of plots are going to displayed. So it is possible to create more series and display different types of data in the chart. Also because the chart can be fairly large, this component is also made draggable for the user to create more space.
\section{Scalability}
\todo{server specs}
The server \url{sangkil.science.uva.nl} is tested on scalability. An important part to notice when testing a server is how well it performs depending on the number of peers it is serving. By looking at how the response time will change if the number of clients increases, can tell how the rate of scalability. The number of clients a server can serve simultaneously is of course the number 1 issue in scalability. So how do the values change when connections, and therefore client numbers, start to rise.
The server \url{sangkil.science.uva.nl} is tested on scalability. The server specifications are two Intel(R) Xeon(R) CPU E5620 @ 2.40GHz processors, with both 4 cores and 8 threats and 23GiB of memory. An important part to notice when testing a server is how well it performs depending on the number of peers it is serving. By looking at how the response time will change if the number of clients increases, can tell how the rate of scalability. The number of clients a server can serve simultaneously is of course the number 1 issue in scalability. So how do the values change when connections, and therefore client numbers, start to rise.
To test the server, all bottlenecks at client side have to be brought down to a minimum. \url{mangkus.science.uva.nl} is the address of another server with the same specifications. The mangkus server is situated in the same server rack as sangkil. The TCP/IP packages do not have to travel around the world in order to reach the destination, so the response time when not in a stress test could be in optimum state.
To test the server, all bottlenecks at client side have to be brought down to a minimum. \url{mangkus.science.uva.nl} is the address of another server with the same specifications. The \texttt{mangkus} server is situated in the same server rack as \texttt{sangkil, and has the exact same specifications}. The TCP/IP packages do not have to travel around the world in order to reach the destination, so the response time when not in a stress test could be in optimum state.
\subsection{Testing}
So testing the sangkil server is done from the server mangkus. A tool siege is used for testing the server, previously discussed in section \ref{sec:scalability} in part one.
......@@ -396,7 +401,7 @@ $ siege -i -b -f file.txt -c $1 -r $2
-r number of repetitions
\end{lstlisting}
Testing is done by changing the concurrent processes and keeping the repetitions the same, or the other way around. That way a little grid search can be preformed to find a weakness in the server. The results of this search are discussed in the following sections.
Testing is done by changing the concurrent processes and keeping the repetitions the same, or the other way around. That way a little grid search can be preformed to find a weakness in the server. The results of this search are discussed in the following sections. Note that a maximum of 500 clients could be tested by Siege, anything above that would result in a failure at the server mangkus.
\subsection{Results and Analysis}
In the first series of tests the repetitions number is the same and the amount of concurrent clients is changed. The amount of concurrency is displayed on the x-axis. Note that it could be that a concurrent value that is tested is not been reached by the server. Concurrency means the amount of clients averagely served by the server for testing at a certain concurrent number. So 500 clients with 10 repetitions could mean that the server does not reach a concurrent level of 500 clients. The reason is that the requests happens so fast that their is no point in time the server handles the full amount of clients that are requesting data.
......@@ -408,11 +413,11 @@ In the first series of tests the repetitions number is the same and the amount o
\end{figure}
See top left graph of figure \ref{fig:10r}. These results suggests that with 10 repetitions and a rising number of concurrent clients the response time increases some what linearly. 100 more clients can be roughly the same as an increase of response time of 0.1/0.2 seconds.
The throughput (top right figure \ref{fig:10r}) rises when the number of clients rises. This is a good sign when talking about scalability. When the throughput rises when client numbers are rising it means that it can transfer more data when more clients are requesting data.
The throughput, top right graph, rises when the number of clients rises. This is a good sign when talking about scalability. When the throughput rises when client numbers are rising it means that it can transfer more data when more clients are requesting data.
The transfer rate is the average number of requests that are handled per second. This rate does increase when more clients are requesting url's but it does not go up linearly.
The transfer rate(left bottom graph) is the average number of requests that are handled per second. This rate does increase when more clients are requesting url's but it stops at 350.
The availability (right bottom graph) drops when concurrency increases. But it does not drop a lot. That's a positive and negative finding. When the amount of users starts to rise the availability needs to be 100\%. A drop of 0.4\% means that roughly 20 requests ($5000 * 0.004$) out of the 5000 requests in total, would not be answered.
The availability (right bottom graph) drops when concurrency increases. But it does not drop much. That is a positive and negative finding. When the amount of users starts to rise the availability needs to be 100\%. A drop of 0.4\% means that roughly 20 requests ($5000 * 0.004$) out of the 5000 requests in total, would not be answered. The fact that it has a denial of service is worrying, but 0.4\% is not drastic.
\vspace{0.5cm}
\begin{figure}[H]
......@@ -422,11 +427,17 @@ The availability (right bottom graph) drops when concurrency increases. But it d
\end{figure}
The same conclusion can be drawn concerning the response time. Notice that the linear increase is roughly the same, but the concurrent clients are now more than in the previous test.
The Throughput is higher than in the previous test. This means that the server's bandwidth sends more MB/s when more clients are requesting data. It would be interesting to see if this number would climb further.
The transfer rate rises more than with 10 repetitions. That means that there capable of dealing with more clients.
The availability is lower than with 10 repetitions, a drop of 0.6\% now means ($2500 * 0.08$) 200 requests that result in a failure. That is a higher than before.
\begin{figure}[H]
\center
\includegraphics[scale=0.6]{server/siege_100r.png}
\caption{100 repetitions, concurrency 10, 50, 100, 200, 300, 400, 500}
\end{figure}
The transfer rate rises more than with 10 repetitions. That means that there capable of dealing with more clients.
All the values stay roughly the same with as 50 repetitions. The walk of the availability graph looks the same as the one with 50 repetitions. But now ($50000 * 0.007$) 350 requests are not answered.
\begin{figure}[H]
\center
......@@ -434,8 +445,7 @@ The Throughput is higher than in the previous test. This means that the server's
\caption{200 repetitions, concurrency 10, 50, 100, 200, 300, 400, 500}
\end{figure}
It does not seem to matter how much repetitions are preformed by a certain number of concurrent clients. This suggests that it could mean that only weak spot of the server concerning the response time depends on how much clients at a time are demanding a request. It looks like it does not matter how much repetitions those clients preform, it has no influence on the response time.
The throughput does rise when the amount of repetitions go up.
It does not seem to matter how much repetitions are preformed by a certain number of concurrent clients. This suggests that the only weak spot of the server concerning the response time depends on how much clients at a time are demanding a request. It looks like it does not matter how much repetitions those clients preform, it has no influence on the response time. The throughput does rise when the amount of repetitions go up, but stops rising at around 440 connections.
\section{Deployment}
For installing the application to a device the sencha's command line tool is used. In the root folder \texttt{packager.json} can be found. In this file the field for building the application has to changed. See below, listing \ref{lst:packager}.
......@@ -459,7 +469,15 @@ $ sencha app build native
The application is packaged. All the javascript code's dependencies are resolved, minified and placed in the \texttt{build} folder. The \texttt{build} contains two folders. One \texttt{package} and one \texttt{native} folder. In the \texttt{package} folder the developer can see what result of the minification is, and can also test in the browser if everything still works. The native files are placed in the \texttt{native} folder. When executing for Android the applications extension will be a .apk\todo{ref to apk?}, for iOS it will be a .app file. The file can be transferred to the device and installed. On Android it's easier that iOS. When building for Android the result of the command(listing \ref{lst:buildnative}) runs the application when done with packaging. When building for iOS, sencha creates a .app file that can be transferred to the device by Itunes\footnote{How to do this can be found in the video ``Getting started'' at: \url{http://docs.sencha.com/touch/2-0/\#!/guide/getting\_started}}.
The deployment can be distributed to other devices. On Android devices it's possible to install .apk files directly to the device from the command line. The generated .apk file can also be distributed to other Android devices and can be installed without the use of the command line tool of Sencha Touch.
For iOS this is more difficult. When a developer is in the possession of an Apple's developer account the UUID(Unique Identifier) needs to be added to the provisioning portal. This grants the device to install applications that are also registered by the developers account. \todo{apple provisioning}. If the application only needs to tested on iOS it is possible to jailbreak the iPad without obtaining an Apple's developer account. How to jailbreak the iPad to is desribed here \url{http://greenpois0n.com/}. For enabling to install without a developers account apps without ``Appsync'' has to be installed.\footnote{\url{http://www.ijailbreak.com/cydia/install-cracked-apps-ios-5-0-1-with-appsync-installous/}}
For iOS this is more difficult. When a developer is in the possession of an Apple's developer account the UUID(Unique Identifier) needs to be added to the provisioning portal. This grants the device to install applications that are also registered by the developers account. If the application only needs to tested on iOS it is possible to jailbreak the iPad without obtaining an Apple's developer account. How to jailbreak the iPad to is desribed here \url{http://greenpois0n.com/}. For enabling to install without a developers account apps without ``Appsync'' has to be installed.\footnote{\url{http://www.ijailbreak.com/cydia/install-cracked-apps-ios-5-0-1-with-appsync-installous/}}
\subsection{Get the app}
The .apk and .ipa are located on github in the \texttt{www/build/native} folder. \\
\begin{lstlisting}
$ git clone git@github.com:icyrizard/FloodSimulation-Browser.git}
\end{lstlisting}
Also the .apk and .ipa can be build from the code in the www folder. \\
\section{Does Sencha Touch suffice}
\label{sec:suffice}
......@@ -470,8 +488,10 @@ Because of the fact that both webkit browsers have similarly Javascript engines,
\section{Conclusion}
This thesis has described the process and requirements to reach the goal of a cross-platform application with a Javascript framework Sencha Touch 2. Almost all the problems have been tackled, the problems of the picking the best suitable tools, the app design, the communication with the API, selecting simulations and the ability to scroll through them, creating image overlays of the images delivered by the API, creating a chart of a certain area when the map is tapped and deploying it to both Android and iPad.
Although the final performance of Sencha Touch 2 on Android could be considered the as biggest drawback of this project, the application does work on Android. If it is due to a difference in resolution of the screen or an implementation of the native Android browser, meaning the rendering of CSS animation via the GPU, the difference in performance is their and cannot be denied.
The server scalability did good for a server that exists out of one physical system. When the number of clients rise to 500, the average response time rises above 1 second, which is acceptable. Judging from the fact that the response time rises linearly with the amount of clients, when 5000 clients are active with the application, the response time will be around 10 seconds. Which is not an acceptable response time. But nevertheless on a ``small'' the server performs average.
Although the final performance of Sencha Touch 2 on Android could be considered the as biggest drawback of this project, the application does work on Android. If it is due to a difference in resolution of the screen or an implementation of the native Android browser, meaning the rendering of CSS animation via the GPU, the difference in performance is a fact and cannot be denied.
The server scalability did good for a server that exists out of one physical system. When the number of clients rise to 500, the average response time rises above 1 second, which is acceptable. Judging from the fact that the response time rises linearly with the amount of clients, when 5000 clients are active with the application, the response time will be around 10 seconds. Which is not an acceptable response time. But nevertheless on a ``small'' scale the server performs average.
\section{Discussion}
Future research:
......@@ -483,8 +503,9 @@ lsm weinig data, ook bij andere steden weinig te zien.
weet nietwelke lsm data bij welke simulatie hoort.
zelf simulaties toevoegen
wallclock time berekenen aan de hand van timesteps.
more concurrent processes from different ip adresses
In section \ref{sec:suffice} the issue ``if Sencha Touch suffices the cross-platform demand'' is discussed and the conclusion more or less was that it does the job wel but not animation/performance wise. More research has to be done to solve this animation issue to increase the use-ability of this application. It well could be that the Mobile Browsers are not all the same and do not fully support the today's standards of HTML5 and CSS3. At this moment a future project can be at best natively programmed if performance is an issue. In this case the performance of the application on Android does the job and the performance is just more a convenience than a necessity.
In section \ref{sec:suffice} the issue ``if Sencha Touch suffices the cross-platform demand'' is discussed and the conclusion more or less was that it does the job wel but not animation/performance wise. More research has to be done to solve this animation issue to increase the use-ability of this application. It well could be that the Mobile Browsers are not all the same and do not fully support the today's standards of HTML5 and CSS3. At this moment a future project can be at best natively programmed if performance is an issue. Another possibility could be Kivy \cite{kivy} for android. Kivy makes it possible to use OpenGL \cite{opengl} and multitouch gestures and program at a higher level in Python\cite{python}. But in this case the performance of the application on Android does the job and the performance is just more a convenience than a necessity.
When the list of the individual simulations is loaded, the list items the image of the last simulation step in the image container of the list item. This is an image only of the water. In the future this can be processed at server side and a screenshot of the simulation \textbf{with} the map then would be available as a callback by the API. The application would look a lot better and the user can see where the simulation is done in the area.
......@@ -492,6 +513,9 @@ An improvement of the application would be that the Lsm data(population dynamics
The chart where the information about how much the cubic meters of water per time step flows in that area, only shows one graph at a time. It would be nice to select a multitude of places and see what the difference is between one area and another. This could be done by placing a marker for every tap event and by deleting a certain marker, that this removes that data from the chart. In Sencha Touch this is not so easy to do. Therefore there was no time left to accomplish this. But it would be a big extension of the application.
The scalability test on \texttt{sangkil.science.uva.nl} can be expanded if the concurrent processes are more than 500. The test was limited due to errors in the mangkus server. But it would be better to test the sangkil server with a lot more clients. At 500 processes the server begins to have issues with availability and response time. But what will happen when the number of clients raises to 5000 ? Would the server perform worse and worse, or will it be the same. The results of the tests suggests that the response time climbs linearly, the throughput has a limit of roughly 14MB/s so transferring more and more data would not happen due to the bandwidth limit.
A last point that is already shortly discussed is that the map object can be replaced by another map, for example OpenStreetMaps. Maybe the performance is higher than the Google Maps, it is certainly cheaper.
......
......@@ -31,7 +31,6 @@
note = "\url{http://jquerymobile.com/}",
}
@misc{PhoneGap,
title = "PhoneGap",
note = "\url{http://www.phonegap.com/}",
......@@ -44,6 +43,7 @@
@misc{Siege,
title = "Siege",
author="Joe Dog",
note = "\url{http://www.joedog.org/siege-home/}",
}
......@@ -71,7 +71,7 @@
title="GUI definition",
author="Linux Information Project",
year = "2004",
note= "Retrieved 10 June 2012"
note= "Retrieved June 10, 2012"
}
@book{REST,
......@@ -111,20 +111,59 @@ note = "\url{http://en.wikipedia.org/wiki/Software_development_kit}"
note="\url{http://www.webkit.org/}",
}
@misc{MVC,
title= "The DCI Architecture: A New Vision of Object-Oriented Programming",
author="Trygve Reenskaug and James O. Coplien"
title= "Web-application development using the Model/View/Controller design pattern",
author="Leff, A. ",
pages = "118 - 127",
journal="Enterprise Distributed Object Computing Conference, 2001. EDOC '01. Proceedings. Fifth IEEE International",
year="2001",
type="Conference Publications"
}
%Rayfield, J.T. ", IBM and Thomas J. Watson Res. Center, Hawthorne, NY
@misc{eclipse,
title="Eclipse",
note="\url{http://www.eclipse.org/}",
}
@misc{kivy,
title = "Kivy for Android",
note = "http://kivy.org/docs/guide/android.html. Retrieved Juli 4, 2012"
}
@misc{python,
title="Python Programming Language",
note="http://www.python.org/"
}
@misc{opengl,
title="OpenGL, The Industry's Foundation for High Performance Graphics",
note="http://www.opengl.org/",
}
@article{cloud,
title="A break in the clouds: towards a cloud definition",
author="Luis M. Vaguero and Luis Rodero-Merino and Jaun Caceres and Maik Lindner",
year="2009",
volume="39",
pages="50-55",
}
@book{urbanflood1,
title="Urban Flood Management",
author = "R. Ashley and S.Garvin, E. Pasche, A. Vassilopoulos and C. Zevenbergen",
year = "2007",
}
@misc{touchtable,
title= "Newsletter of Urban Flood Workshop",
note="\url{http://www.urbanflood.eu/Pages/Newsletter2.aspx#interactive_flood_simulation}"
}
@misc{xcode,
title="Xcode",
note="\url{https://developer.apple.com/xcode/}",
}
%http://www.sencha.com/products/touch
%http://usingimho.wordpress.com/2011/06/14/why-you-should-stay-away-from-appcelerators-titanium/
......
......@@ -62,4 +62,8 @@ In this thesis we discuss the design and implementation of a cross platform appl
\newpage
\appendix
\include{area_api}
\newpage
\include{lsm-api}
\newpage
\include{drfsm-api}
\end{document}
\ No newline at end of file
Ext.define('app.Api', {
mixins: ['Ext.mixin.Observable'],
singleton: true,
getIzid: function(lat, lng, area_id) {
var me = this;
var requestName = Ext.Ajax.request({
......
Ext.define("app.model.SimulationModel",{extend:"Ext.data.Model",config:{fields:["name","corners","visbounds","area_id","center","size"],},});Ext.define("app.model.SimulationDetails",{extend:"Ext.data.Model",config:{fields:["name","center","corners","size","extents","visbounds","vissize","projection","dikes"],},});Ext.define("app.view.Main",{extend:"Ext.Container",requires:["Ext.TitleBar"],config:{title:"Simulation Browser",layout:"hbox",fullscreen:true,items:[{xtype:"listpanel",width:"20%",style:"border-right: 1px solid #373737",flex:1,},{xtype:"simulationpanel",flex:2}],}});Ext.define("app.view.Simulation",{extend:"Ext.Panel",xtype:"simulationpanel",config:{layout:"card",items:[{docked:"top",xtype:"toolbar",title:"Flood Simulation Browser",},{xtype:"SimulationMap",}],}});Ext.define("app.view.List",{extend:"Ext.navigation.View",xtype:"listpanel",requires:["Ext.data.Store","Ext.dataview.List"],title:"Simulations",config:{navigationBar:{title:"simulations",items:[{id:"simulationOptions",xtype:"button",iconCls:"list",iconMask:true,left:"100%",top:7,ui:["square"],}]},items:[{title:"Locations",id:"cities",xtype:"list",ui:"round",itemTpl:"<div>{name}</div>",store:"SimulationStore",}],},});Ext.define("app.view.Map",{extend:"Ext.Map",xtype:"SimulationMap",id:"mapa",config:{useLoadMask:true,mapOptions:{zoom:12,center:new google.maps.LatLng(52.354453,4.95536),mapTypeId:google.maps.MapTypeId.ROADMAP,navigationControl:true,scrollwheel:true,scaleControl:true,rotateControl:true,panControl:true,overviewMapControl:true,zoomControl:true,navigationControlOptions:{style:google.maps.NavigationControlStyle.DEFAULT}},listeners:{maprender:function(){this.overlayImages=[];this.Images=[];this.imageIndex=0;this.imageBounds=null;this.play=false;this.testId=0;this.areaId=0;this.listener_ref=null;this.marker_listener=null}}},getReference:function(){return this.globalMap},setGlobalMap:function(a,b){this.globalMap=b;this.globalExtMap=a},setCenterMap:function(a){this.globalExtMap.setMapCenter({latitude:a[0],longitude:a[1]})},alterMapOptions:function(a){this.globalExtMap.setMapOptions(a)},createMarker:function(b,a){map=this.globalExtMap;var b=new google.maps.LatLng(b[0],b[1]);if(a!=null){icon=a.icon||"resources/images/Google_Maps_Marker.png";draggable=a.draggable||false;raiseOnDrag=a.raiseOnDrag||false}else{icon="resources/images/Google_Maps_Marker.png";draggable=false;raiseOnDrag=false}marker=new google.maps.Marker({position:b,icon:icon,map:this.globalMap,title:"you",raiseOnDrag:true,draggable:draggable,});if(this.marker_listener!=null){google.maps.event.removeListener(this.marker_listener)}this.marker_listener=google.maps.event.addListener(marker,"dragend",function(c){map.fireEvent("dragend",c)});return marker},createOverlayImage:function(d,g,b,f){var c=this;var a="";if(f=="Flood"){a="http://sangkil.science.uva.nl:8003/drfsm/"+g+"/visualization/level/map/"}else{if(f=="Lsm"){a="http://sangkil.science.uva.nl:8003/lsm/"+g+"/visualization/paru/map/"}else{return false}}this.imageIndex=0;this.imageBounds=new google.maps.LatLngBounds(new google.maps.LatLng(d[2],d[3]),new google.maps.LatLng(d[0],d[1]));this.bounds=d;if(this.overlayImages.length>1){this.removeImages()}for(i in b){var e=new Image();e.src=a+b[i]+".png";console.log(e.src);this.overlayImages.push(new google.maps.GroundOverlay(e.src,this.imageBounds))}this.overlayImages[0].setMap(this.globalMap);this.imageIndex=this.overlayImages.length;if(this.listeners_ref!=null){google.maps.event.removeListener(this.listeners_ref)}this.listeners_ref=google.maps.event.addListener(this.overlayImages[0],"click",function(h){c.fireEvent("gotClick",h)})},nextImage:function(){var a=this;if(this.overlayImages.length<=1){return}var b=(this.overlayImages.length+this.imageIndex)%this.overlayImages.length;this.imageIndex=(this.overlayImages.length+this.imageIndex+1)%this.overlayImages.length;this.overlayImages[this.imageIndex].setMap(this.globalMap);if(this.listeners_ref!=null){google.maps.event.removeListener(this.listeners_ref)}this.listeners_ref=google.maps.event.addListener(this.overlayImages[this.imageIndex],"click",function(c){a.fireEvent("gotClick",c)});this.overlayImages[b].setMap(null)},prevImage:function(){if(this.overlayImages.length<=1){return}this.overlayImages[this.imageIndex].setMap(null);this.imageIndex-=this.imageIndex>0?1:0;if(this.imageIndex>=0){this.overlayImages[this.imageIndex].setMap(this.globalMap)}},removeImages:function(){for(i in this.overlayImages){this.overlayImages[i].setMap(null)}this.imageIndex=0;this.overlayImages=[];this.Images=[]},createOverlayPolygon:function(a){var d=[];for(var c=0;c<a[0].length-1;c++){d.push(new google.maps.LatLng(a[0][c][0],a[0][c][1]))}var b=new google.maps.Polygon({paths:d,strokeColor:"#FF0000",strokeOpacity:0.8,strokeWeight:2,fillColor:"#FF0000",fillOpacity:0.35});b.setMap(this.globalMap)},getCurrentImage:function(){if(this.overlayImages.length>0){this.overlayImages[this.imageIndex];return this.overlayImages[this.imageIndex]}},getFloodImage:function(b,a){if(a){return"http://sangkil.science.uva.nl:8003/drfsm/"+b+"/visualization/level/map/"+a+".png"}else{return false}}});Ext.define("app.view.StepsOverlay",{extend:"Ext.Container",xtype:"StepsOverlay",requires:["Ext.Anim"],config:{id:"overlay",style:"background: none;",html:'<div id="controls_title"><h2>Controls<h2></div>',showAnimation:{type:"slideIn",direction:"left",},hidden:true,width:220,height:130,scroll:false,draggable:true,items:[{xtype:"button",id:"closebutton",top:-10,right:0,ui:"plain",border:"solid",style:"color: white; background: none",html:"x",handler:this.closebutton,scope:this,},{top:30,left:35,align:"center",items:[{xtype:"button",id:"forward",ui:"blue",cls:"overlay-button",width:45,iconCls:"arrow_up",floating:"right",iconMask:true,align:"center",left:40},{top:50,xtype:"button",id:"backwards",ui:"blue",cls:"overlay-button",width:45,floating:"left",iconCls:"arrow_down",align:"center",iconMask:true,left:40},{top:24,xtype:"button",id:"play",ui:"blue",cls:"overlay-button",width:40,floating:"left",iconCls:"arrow_right",align:"center",iconMask:true,left:85,},{top:24,hidden:true,xtype:"button",id:"pause",ui:"red",cls:"overlay-button",width:40,floating:"left",iconCls:"pause",align:"center",iconMask:true,left:85,},{top:24,xtype:"button",id:"play-backw",ui:"blue",cls:"overlay-button",width:40,floating:"left",iconCls:"arrow_left",align:"center",iconMask:true,left:0,},{top:24,hidden:true,xtype:"button",id:"pause-backw",ui:"red",cls:"overlay-button",width:40,floating:"left",iconCls:"pause",align:"center",iconMask:true,left:0,}],}],closebutton:function(){Ext.dispatch({controller:app.controller.Main,action:"closeoverlay"})}}});Ext.define("app.view.OptionsPanel",{extend:"Ext.Panel",xtype:"SimulationOptions",id:"simulation_options",requires:["Ext.dataview.List"],config:{layout:"hbox",right:40,width:200,height:100,hideOnMaskTap:true,modal:true,items:[{xtype:"list",id:"optionsList",width:"100%",itemTpl:"Simulation {type}",data:[{type:"Flood"},{type:"Lsm"}],}],}});Ext.define("app.view.SimulationList",{extend:"Ext.List",xtype:"SimulationList",id:"summary",config:{scrollable:{momentum:false},title:"Floods",fullscreen:true,store:"SimulationsSummary",itemTpl:'<div><img class="map_thumb" id="{test_id}_map"src=""/> <img class="flood_thumb" id="{test_id}_flood"style:"width: 100px;" src=""/><div style:"clear:both"><div id="{test_id}_control" class="control"></div></div><h3><b>submitted:</b></h3> <bold>{submitted}</bold></div></div>'},listeners:{scroll:function(){console.log("scroll")}}});Ext.define("app.view.LsmSimulationList",{extend:"Ext.List",xtype:"LsmSimulationList",id:"lsmsimulation-list",config:{title:"lsm",store:"LsmStore",itemTpl:"{submitted}",}});Ext.define("app.view.Chart",{extend:"Ext.Panel",id:"flood-chart",xtype:"floodChart",requires:["Ext.data.Store"],config:{width:520,height:420,layout:"fit",draggable:true,items:[{xtype:"chart",theme:"Base",animate:true,id:"flood-chart-id",store:"chartStore",left:20,width:490,height:400,axes:[{type:"Numeric",position:"left",fields:["volume"],title:"Volume",minimum:0,adjustMinimumByMajorUnit:0,},{type:"Category",position:"bottom",fields:["time"],title:"Time Step",minimum:0,adjustMinimumByMajorUnit:0,}],series:[{type:"line",highlight:false,smooth:true,axis:"left",xField:["time"],yField:["volume"],}],},{xtype:"button",ui:"close",id:"closeChart",left:0,top:0,iconCls:"delete",iconMask:true,}],}});Ext.define("app.controller.Main",{extend:"Ext.app.Controller",config:{refs:{map:"#mapa",sidepanel:"listpanel",api:"api",play:"#play",pause:"#pause",playBackw:"#play-backw",pauseBackw:"#pause-backw",mapView:"SimulationMap",simulationList:{selector:"summary",xtype:"SimulationList",autoCreate:true,},overlay:{selector:"#overlay",xtype:"StepsOverlay",autoCreate:true,},simulationOptions:{selector:"#simulation-options",xtype:"SimulationOptions",autoCreate:true,},simOptionsButton:{selector:"#simulationOptions",autoCreate:true,},optionsList:{selector:"#optionsList",autoCreate:true,},lsmSimulation:{selector:"lsmsimulation-list",xtype:"LsmSimulationList",autoCreate:true,},simulation:"simulationpanel"},control:{"listpanel #cities":{itemtap:"showList",show:"initiateCities"},"listpanel #summary":{itemtap:"simulate",},"#mapa":{maprender:"setMap"},"#closebutton":{tap:"closeoverlay"},"#backwards":{tap:"prevImage"},"#forward":{tap:"nextImage"},"#simulationOptions":{tap:"openSimulationsOptions"},"#pause":{tap:"stopPlayImages"},"#play":{tap:"playImages"},"#pause-backw":{tap:"stopBackwImages"},"#play-backw":{tap:"playBackwImages"},"#optionsList":{itemtap:"changeSimulationType",},"listpanel #lsmsimulation-list":{itemtap:"simulate",}}},init:function(){this.selectedIndex=[];this.SimulType="Flood";this.test_id=null},initiateCities:function(){this.getSimOptionsButton().show()},setMap:function(a,b){this.globalMap=b;this.getMapView().setGlobalMap(a,b)},stopBackwImages:function(a){clearInterval(this.interval);a.hide();this.getPlayBackw().show()},playBackwImages:function(b){this.getPlay().show();this.getPause().hide();clearInterval(this.interval);b.hide();this.getPauseBackw().show();var a=this.getMapView();this.interval=setInterval(function(){a.prevImage()},500)},playImages:function(b){this.getPlayBackw().show();this.getPauseBackw().hide();clearInterval(this.interval);b.hide();this.getPause().show();var a=this.getMapView();this.interval=setInterval(function(){a.nextImage()},500)},stopPlayImages:function(a){clearInterval(this.interval);a.hide();this.getPlay().show();clearInterval(this.interval)},prevImage:function(){this.getMapView().prevImage()},nextImage:function(){this.getMapView().nextImage()},closeoverlay:function(){this.getPlay().show();this.getPause().hide();this.getPlayBackw().show();this.getPauseBackw().hide();clearInterval(this.interval);this.getMapView().removeImages();this.getOverlay().hide()},simulate:function(j,h,e,g){var k=this;this.getMapView().removeImages();var c=this.getMapView(),f=g.get("test_id"),a;this.test_id=f;var d=function(m){var n=m.timesteps||m.images;if(n.length>0){c.createOverlayImage(a,f,n,k.SimulType);c.testId=m.test_id;c.areaId=m.area_id}};var l=Ext.getStore("FloodDetailStore");l.each(function(m){a=m.get("visbounds")});var b="";if(this.SimulType=="Flood"){b="http://sangkil.science.uva.nl:8003/drfsm/"+f+"/info.json"}else{b="http://sangkil.science.uva.nl:8003/lsm/"+f+"/visualization/paru/info.json"}this.requestInfo(f,d,b);this.getOverlay().showBy(this.getMapView(),"tr-tr")},showList:function(l,k,g,j){this.getSimOptionsButton().hide();this.getMapView().removeImages();var c=j.get("center");var e=j.get("area_id");var f=false;for(i in this.selectedIndex){if(this.selectedIndex[i]==k){f=true}}var m=Ext.getStore("SimulationsSummary");var d=Ext.getStore("LsmStore");d.clearFilter();d.filter("area_id",e);m.clearFilter();m.filter("area_id",e);if(this.SimulType=="Flood"){this.setThumb(c);this.getSidepanel().push(this.getSimulationList())}else{if(this.SimulType=="Lsm"){this.getSidepanel().push(this.getLsmSimulation())}}var m=Ext.getStore("FloodDetailStore");m.setUrl(e);m.load();if(f==false){var b=null;var a=j.get("visbounds");var h=j.get("corners");this.selectedIndex.push(k);m.each(function(n){b=n.get("dikes")});if(b.length!=0){this.getMapView().createOverlayPolygon(b)}this.getMapView().createMarker(c);this.getSimulationOptions().show();this.getSimulationOptions().hide()}this.getMapView().setCenterMap(c);this.getMapView().alterMapOptions({zoom:13})},setThumb:function(){if(typeof this.test_id==undefined){return}var b=this;var a=Ext.getStore("SimulationsSummary");a.each(function(d){var e=d.get("test_id");var c=function(f){var g=b.getMapView().getFloodImage(e,f.timesteps[f.timesteps.length-1])||"resources/images/noimage.png";document.getElementById(e+"_flood").src=g};b.requestInfo(d.get("test_id"),c)})},requestInfo:function(b,d,a){var a=a||"http://sangkil.science.uva.nl:8003/drfsm/"+b+"/info.json";var c=Ext.Ajax.request({method:"GET",url:a,success:function(f,g){var e=Ext.decode(f.responseText);d(e)},failure:function(){console.log("failed to create images")}})},openSimulationsOptions:function(a){this.getSimulationOptions().showBy(a)},closeSimulationsOptions:function(a){this.getSimulationOptions().hide()},changeSimulationType:function(d,b,c,a){if(a.get("type")=="Lsm"&&this.SimulType!="Lsm"){this.SimulType="Lsm"}else{if(a.get("type")=="Flood"&&this.SimulType!="Flood"){this.SimulType="Flood"}}this.getSimulationOptions().hide()},});Ext.define("app.store.SimulationStore",{extend:"Ext.data.Store",requires:["Ext.data.proxy.Rest"],id:"simulationList",config:{autoLoad:true,model:"app.model.SimulationModel",proxy:{type:"rest",url:"http://sangkil.science.uva.nl:8003/area/list.json",reader:{type:"json",rootProperty:"areas"},}}});Ext.define("app.store.FloodDetailStore",{extend:"Ext.data.Store",requires:["Ext.data.proxy.Rest"],xtype:"FloodDetailStore",config:{autoLoad:true,model:"app.model.SimulationDetails",fields:["name","center","corners","size","extents","visbounds","vissize","projection","dikes"],proxy:{type:"rest",url:"http://sangkil.science.uva.nl:8003/area/1/info.json",reader:{type:"json",},},listeners:{}},setUrl:function(b){var a=Ext.getStore("FloodDetailStore").getProxy();a._url="http://sangkil.science.uva.nl:8003/area/"+b+"/info.json"},});Ext.define("app.store.SimulationsSummary",{extend:"Ext.data.Store",requires:["Ext.data.proxy.Rest"],config:{autoLoad:true,fields:["area_id","test_id","submitted"],proxy:{type:"rest",url:"http://sangkil.science.uva.nl:8003/drfsm/list.json?summary",reader:{type:"json",rootProperty:"simulations"}}},});Ext.define("app.store.LsmStore",{extend:"Ext.data.Store",config:{autoLoad:true,fields:["area_id","test_id","submitted"],proxy:{type:"rest",url:"http://sangkil.science.uva.nl:8003/lsm/list.json",reader:{type:"json",rootProperty:"simulations"}}}});Ext.define("app.store.chartStore",{extend:"Ext.data.JsonStore",config:{fields:["time","volume"]}});Ext.define("app.Api",{mixins:["Ext.mixin.Observable"],singleton:true,getIzid:function(d,a,c){var b=this;var e=Ext.Ajax.request({method:"GET",url:"http://sangkil.science.uva.nl:8003/area/"+c+"/izid.json?latlng="+d+","+a,success:function(g,h){var f=Ext.decode(g.responseText);b.fireEvent("gotIzid",f.izid)},failure:function(){console.log("getIzid: failed to get izid")}})},getCsvFile:function(a,d){var b=this;var c=Ext.Ajax.request({method:"GET",url:"http://sangkil.science.uva.nl:8003/drfsm/"+a+"/results/izid/"+d+".csv",success:function(e,f){b.fireEvent("gotCsv",e.responseText)},failure:function(){console.log("getCsvFile: failed to get csv file")}})},requestInfo:function(b,d,a){var a=a||"http://sangkil.science.uva.nl:8003/drfsm/"+b+"/info.json";var c=Ext.Ajax.request({method:"GET",url:a,success:function(f,g){var e=Ext.decode(f.responseText);d(e)},failure:function(){console.log("failed to create images")}})}});Ext.define("app.controller.ChartController",{extend:"Ext.app.Controller",requires:["app.Api"],config:{refs:{Floodchart:{xtype:"floodChart",selector:"flood-chart",autoCreate:true,},ChartData:{selector:"flood-chart-id",autoCreate:true,},ExpandButton:"#expand-button",mapView:"SimulationMap",},control:{"#mapa":{maprender:"addListener"},"#closeChart":{tap:"closeFloodChart"},"#expand-button":{tap:"expandChart"},},},init:function(){self.marker=null;map=this.getMapView();app.Api.on({gotIzid:function(a){app.Api.getCsvFile(map.testId,a)},gotCsv:function(a){me.plot(a)}})},addListener:function(){me=this;map=this.getMapView();map.on({gotClick:function(a){if(self.marker!=null){self.marker.setMap(null);self.marker=null}lat=a.latLng.lat();lng=a.latLng.lng();options={icon:"resources/images/marker.png",draggable:true,raiseOnDrag:true,};self.marker=map.createMarker([lat,lng],options);app.Api.getIzid(lat,lng,map.areaId)},dragend:function(a){console.log(a);lat=a.latLng.lat();lng=a.latLng.lng();app.Api.getIzid(lat,lng,map.areaId)}})},plot:function(a){array=a.split("\n");store=Ext.getStore("chartStore");columns=array[0];volume=[];time=[];data=[];for(i=1;i<array.length;i++){line_clmns=array[i].split(",");data.push({time:parseInt(line_clmns[0]),volume:parseInt(line_clmns[2]),})}store.setData(data);this.openChart()},openChart:function(){this.getFloodchart().showBy(this.getMapView(),"t-t")},closeFloodChart:function(){this.getFloodchart().hide()},expandChart:function(){}});Ext.application({controllers:["Main","ChartController"],models:["SimulationModel","SimulationDetails"],stores:["SimulationStore","FloodDetailStore","SimulationsSummary","LsmStore","chartStore"],name:"app",requires:["Ext.MessageBox",],views:["Main","Simulation","List","Map","StepsOverlay","OptionsPanel","SimulationList","LsmSimulationList","Chart"],icon:{57:"resources/icons/Icon.png",72:"resources/icons/Icon~ipad.png",114:"resources/icons/Icon@2x.png",144:"resources/icons/Icon~ipad@2x.png"},phoneStartupScreen:"resources/loading/Homescreen.jpg",tabletStartupScreen:"resources/loading/Homescreen~ipad.jpg",launch:function(){Ext.fly("appLoadingIndicator").destroy();Ext.Viewport.add(Ext.create("app.view.Main"))},onUpdated:function(){Ext.Msg.confirm("Application Update","This application has just successfully been updated to the latest version. Reload now?",function(){window.location.reload()})}});
\ No newline at end of file
Ext.define("app.model.SimulationModel",{extend:"Ext.data.Model",config:{fields:["name","corners","visbounds","area_id","center","size"],},});Ext.define("app.model.SimulationDetails",{extend:"Ext.data.Model",config:{fields:["name","center","corners","size","extents","visbounds","vissize","projection","dikes"],},});Ext.define("app.view.Main",{extend:"Ext.Container",requires:["Ext.TitleBar"],config:{title:"Simulation Browser",layout:"hbox",fullscreen:true,items:[{xtype:"listpanel",width:"20%",style:"border-right: 1px solid #373737",flex:1,},{xtype:"simulationpanel",flex:2}],}});Ext.define("app.view.Simulation",{extend:"Ext.Panel",xtype:"simulationpanel",config:{layout:"card",items:[{docked:"top",xtype:"toolbar",title:"Flood Simulation Browser",},{xtype:"SimulationMap",}],}});Ext.define("app.view.List",{extend:"Ext.navigation.View",xtype:"listpanel",requires:["Ext.data.Store","Ext.dataview.List"],title:"Simulations",config:{navigationBar:{title:"simulations",items:[{id:"simulationOptions",xtype:"button",iconCls:"list",iconMask:true,left:"100%",top:7,ui:["square"],}]},items:[{title:"Locations",id:"cities",xtype:"list",ui:"round",itemTpl:"<div>{name}</div>",store:"SimulationStore",}],},});Ext.define("app.view.Map",{extend:"Ext.Map",xtype:"SimulationMap",id:"mapa",config:{useLoadMask:true,mapOptions:{zoom:12,center:new google.maps.LatLng(52.354453,4.95536),mapTypeId:google.maps.MapTypeId.ROADMAP,navigationControl:true,scrollwheel:true,scaleControl:true,rotateControl:true,panControl:true,overviewMapControl:true,zoomControl:true,navigationControlOptions:{style:google.maps.NavigationControlStyle.DEFAULT}},listeners:{maprender:function(){this.overlayImages=[];this.Images=[];this.imageIndex=0;this.imageBounds=null;this.play=false;this.testId=0;this.areaId=0;this.listener_ref=null;this.marker_listener=null}}},getReference:function(){return this.globalMap},setGlobalMap:function(a,b){this.globalMap=b;this.globalExtMap=a},setCenterMap:function(a){this.globalExtMap.setMapCenter({latitude:a[0],longitude:a[1]})},alterMapOptions:function(a){this.globalExtMap.setMapOptions(a)},createMarker:function(b,a){map=this.globalExtMap;var b=new google.maps.LatLng(b[0],b[1]);if(a!=null){icon=a.icon||"resources/images/Google_Maps_Marker.png";draggable=a.draggable||false;raiseOnDrag=a.raiseOnDrag||false}else{icon="resources/images/Google_Maps_Marker.png";draggable=false;raiseOnDrag=false}marker=new google.maps.Marker({position:b,icon:icon,map:this.globalMap,title:"you",raiseOnDrag:true,draggable:draggable,});if(this.marker_listener!=null){google.maps.event.removeListener(this.marker_listener)}this.marker_listener=google.maps.event.addListener(marker,"dragend",function(c){map.fireEvent("dragend",c)});return marker},createOverlayImage:function(d,g,b,f){var c=this;var a="";if(f=="Flood"){a="http://sangkil.science.uva.nl:8003/drfsm/"+g+"/visualization/level/map/"}else{if(f=="Lsm"){a="http://sangkil.science.uva.nl:8003/lsm/"+g+"/visualization/paru/map/"}else{return false}}this.imageIndex=0;this.imageBounds=new google.maps.LatLngBounds(new google.maps.LatLng(d[2],d[3]),new google.maps.LatLng(d[0],d[1]));this.bounds=d;if(this.overlayImages.length>1){this.removeImages()}for(i in b){var e=new Image();e.src=a+b[i]+".png";this.overlayImages.push(new google.maps.GroundOverlay(e.src,this.imageBounds))}this.overlayImages[0].setMap(this.globalMap);this.imageIndex=this.overlayImages.length;if(this.listeners_ref!=null){google.maps.event.removeListener(this.listeners_ref)}this.listeners_ref=google.maps.event.addListener(this.overlayImages[0],"click",function(h){c.fireEvent("gotClick",h)})},nextImage:function(){var a=this;if(this.overlayImages.length<=1){return}var b=(this.overlayImages.length+this.imageIndex)%this.overlayImages.length;this.imageIndex=(this.overlayImages.length+this.imageIndex+1)%this.overlayImages.length;this.overlayImages[this.imageIndex].setMap(this.globalMap);if(this.listeners_ref!=null){google.maps.event.removeListener(this.listeners_ref)}this.listeners_ref=google.maps.event.addListener(this.overlayImages[this.imageIndex],"click",function(c){a.fireEvent("gotClick",c)});this.overlayImages[b].setMap(null)},prevImage:function(){if(this.overlayImages.length<=1){return}this.overlayImages[this.imageIndex].setMap(null);this.imageIndex-=this.imageIndex>0?1:0;if(this.imageIndex>=0){this.overlayImages[this.imageIndex].setMap(this.globalMap)}},removeImages:function(){for(i in this.overlayImages){this.overlayImages[i].setMap(null)}this.imageIndex=0;this.overlayImages=[];this.Images=[]},createOverlayPolygon:function(a){var d=[];for(var c=0;c<a[0].length-1;c++){d.push(new google.maps.LatLng(a[0][c][0],a[0][c][1]))}var b=new google.maps.Polygon({paths:d,strokeColor:"#FF0000",strokeOpacity:0.8,strokeWeight:2,fillColor:"#FF0000",fillOpacity:0.35});b.setMap(this.globalMap)},getCurrentImage:function(){if(this.overlayImages.length>0){this.overlayImages[this.imageIndex];return this.overlayImages[this.imageIndex]}},getFloodImage:function(b,a){if(a){return"http://sangkil.science.uva.nl:8003/drfsm/"+b+"/visualization/level/map/"+a+".png"}else{return false}}});Ext.define("app.view.StepsOverlay",{extend:"Ext.Container",xtype:"StepsOverlay",requires:["Ext.Anim"],config:{id:"overlay",style:"background: none;",html:'<div id="controls_title"><h2>Controls<h2></div>',showAnimation:{type:"slideIn",direction:"left",},hidden:true,width:220,height:130,scroll:false,draggable:true,items:[{xtype:"button",id:"closebutton",top:-10,right:0,ui:"plain",border:"solid",style:"color: white; background: none",html:"x",handler:this.closebutton,scope:this,},{top:30,left:35,align:"center",items:[{xtype:"button",id:"forward",ui:"blue",cls:"overlay-button",width:45,iconCls:"arrow_up",floating:"right",iconMask:true,align:"center",left:40},{top:50,xtype:"button",id:"backwards",ui:"blue",cls:"overlay-button",width:45,floating:"left",iconCls:"arrow_down",align:"center",iconMask:true,left:40},{top:24,xtype:"button",id:"play",ui:"blue",cls:"overlay-button",width:40,floating:"left",iconCls:"arrow_right",align:"center",iconMask:true,left:85,},{top:24,hidden:true,xtype:"button",id:"pause",ui:"red",cls:"overlay-button",width:40,floating:"left",iconCls:"pause",align:"center",iconMask:true,left:85,},{top:24,xtype:"button",id:"play-backw",ui:"blue",cls:"overlay-button",width:40,floating:"left",iconCls:"arrow_left",align:"center",iconMask:true,left:0,},{top:24,hidden:true,xtype:"button",id:"pause-backw",ui:"red",cls:"overlay-button",width:40,floating:"left",iconCls:"pause",align:"center",iconMask:true,left:0,}],}],closebutton:function(){Ext.dispatch({controller:app.controller.Main,action:"closeoverlay"})}}});Ext.define("app.view.OptionsPanel",{extend:"Ext.Panel",xtype:"SimulationOptions",id:"simulation_options",requires:["Ext.dataview.List"],config:{layout:"hbox",right:40,width:200,height:100,hideOnMaskTap:true,modal:true,items:[{xtype:"list",id:"optionsList",width:"100%",itemTpl:"Simulation {type}",data:[{type:"Flood"},{type:"Lsm"}],}],}});Ext.define("app.view.SimulationList",{extend:"Ext.List",xtype:"SimulationList",id:"summary",config:{scrollable:{momentum:false},title:"Floods",fullscreen:true,store:"SimulationsSummary",itemTpl:'<div><img class="map_thumb" id="{test_id}_map"src=""/> <img class="flood_thumb" id="{test_id}_flood"style:"width: 100px;" src=""/><div style:"clear:both"><div id="{test_id}_control" class="control"></div></div><h3><b>submitted:</b></h3> <bold>{submitted}</bold></div></div>'},listeners:{scroll:function(){console.log("scroll")}}});Ext.define("app.view.LsmSimulationList",{extend:"Ext.List",xtype:"LsmSimulationList",id:"lsmsimulation-list",config:{title:"lsm",store:"LsmStore",itemTpl:"{submitted}",}});Ext.define("app.view.Chart",{extend:"Ext.Panel",id:"flood-chart",xtype:"floodChart",requires:["Ext.data.Store"],config:{width:520,height:420,layout:"fit",draggable:true,items:[{xtype:"chart",theme:"Base",animate:true,id:"flood-chart-id",store:",",left:20,width:490,height:400,axes:[{type:"Numeric",position:"left",fields:["volume"],title:"Volume",minimum:0,adjustMinimumByMajorUnit:0,},{type:"Category",position:"bottom",fields:["time"],title:"Time Step",minimum:0,adjustMinimumByMajorUnit:0,}],series:[{type:"line",highlight:false,smooth:true,axis:"left",xField:["time"],yField:["volume"],}],},{xtype:"button",ui:"close",id:"closeChart",left:0,top:0,iconCls:"delete",iconMask:true,}],}});Ext.define("app.controller.Main",{extend:"Ext.app.Controller",config:{refs:{map:"#mapa",sidepanel:"listpanel",api:"api",play:"#play",pause:"#pause",playBackw:"#play-backw",pauseBackw:"#pause-backw",mapView:"SimulationMap",simulationList:{selector:"summary",xtype:"SimulationList",autoCreate:true,},overlay:{selector:"#overlay",xtype:"StepsOverlay",autoCreate:true,},simulationOptions:{selector:"#simulation-options",xtype:"SimulationOptions",autoCreate:true,},simOptionsButton:{selector:"#simulationOptions",autoCreate:true,},optionsList:{selector:"#optionsList",autoCreate:true,},lsmSimulation:{selector:"lsmsimulation-list",xtype:"LsmSimulationList",autoCreate:true,},simulation:"simulationpanel"},control:{"listpanel #cities":{itemtap:"showList",show:"initiateCities"},"listpanel #summary":{itemtap:"simulate",},"#mapa":{maprender:"setMap"},"#closebutton":{tap:"closeoverlay"},"#backwards":{tap:"prevImage"},"#forward":{tap:"nextImage"},"#simulationOptions":{tap:"openSimulationsOptions"},"#pause":{tap:"stopPlayImages"},"#play":{tap:"playImages"},"#pause-backw":{tap:"stopBackwImages"},"#play-backw":{tap:"playBackwImages"},"#optionsList":{itemtap:"changeSimulationType",},"listpanel #lsmsimulation-list":{itemtap:"simulate",}}},init:function(){this.selectedIndex=[];this.SimulType="Flood";this.test_id=null},initiateCities:function(){this.getSimOptionsButton().show()},setMap:function(a,b){this.globalMap=b;this.getMapView().setGlobalMap(a,b)},stopBackwImages:function(a){clearInterval(this.interval);a.hide();this.getPlayBackw().show()},playBackwImages:function(b){this.getPlay().show();this.getPause().hide();clearInterval(this.interval);b.hide();this.getPauseBackw().show();var a=this.getMapView();this.interval=setInterval(function(){a.prevImage()},500)},playImages:function(b){this.getPlayBackw().show();this.getPauseBackw().hide();clearInterval(this.interval);b.hide();this.getPause().show();var a=this.getMapView();this.interval=setInterval(function(){a.nextImage()},500)},stopPlayImages:function(a){clearInterval(this.interval);a.hide();this.getPlay().show();clearInterval(this.interval)},prevImage:function(){this.getMapView().prevImage()},nextImage:function(){this.getMapView().nextImage()},closeoverlay:function(){this.getPlay().show();this.getPause().hide();this.getPlayBackw().show();this.getPauseBackw().hide();clearInterval(this.interval);this.getMapView().removeImages();this.getOverlay().hide()},simulate:function(j,h,e,g){var k=this;this.getMapView().removeImages();var c=this.getMapView(),f=g.get("test_id"),a;this.test_id=f;var d=function(m){var n=m.timesteps||m.images;if(n.length>0){c.createOverlayImage(a,f,n,k.SimulType);c.testId=m.test_id;c.areaId=m.area_id}};var l=Ext.getStore("FloodDetailStore");l.each(function(m){a=m.get("visbounds")});var b="";if(this.SimulType=="Flood"){b="http://sangkil.science.uva.nl:8003/drfsm/"+f+"/info.json"}else{b="http://sangkil.science.uva.nl:8003/lsm/"+f+"/visualization/paru/info.json"}this.requestInfo(f,d,b);this.getOverlay().showBy(this.getMapView(),"tr-tr")},showList:function(l,k,g,j){this.getSimOptionsButton().hide();this.getMapView().removeImages();var c=j.get("center");var e=j.get("area_id");var f=false;for(i in this.selectedIndex){if(this.selectedIndex[i]==k){f=true}}var m=Ext.getStore("SimulationsSummary");var d=Ext.getStore("LsmStore");d.clearFilter();d.filter("area_id",e);m.clearFilter();m.filter("area_id",e);if(this.SimulType=="Flood"){this.setThumb(c);this.getSidepanel().push(this.getSimulationList())}else{if(this.SimulType=="Lsm"){this.getSidepanel().push(this.getLsmSimulation())}}var m=Ext.getStore("FloodDetailStore");m.setUrl(e);m.load();if(f==false){var b=null;var a=j.get("visbounds");var h=j.get("corners");this.selectedIndex.push(k);m.each(function(n){b=n.get("dikes")});if(b.length!=0){this.getMapView().createOverlayPolygon(b)}this.getMapView().createMarker(c);this.getSimulationOptions().show();this.getSimulationOptions().hide()}this.getMapView().setCenterMap(c);this.getMapView().alterMapOptions({zoom:13})},setThumb:function(){if(typeof this.test_id==undefined){return}var b=this;var a=Ext.getStore("SimulationsSummary");a.each(function(d){var e=d.get("test_id");var c=function(f){var g=b.getMapView().getFloodImage(e,f.timesteps[f.timesteps.length-1])||"resources/images/noimage.png";document.getElementById(e+"_flood").src=g};b.requestInfo(d.get("test_id"),c)})},requestInfo:function(b,d,a){var a=a||"http://sangkil.science.uva.nl:8003/drfsm/"+b+"/info.json";var c=Ext.Ajax.request({method:"GET",url:a,success:function(f,g){var e=Ext.decode(f.responseText);d(e)},failure:function(){console.log("failed to create images")}})},openSimulationsOptions:function(a){this.getSimulationOptions().showBy(a)},closeSimulationsOptions:function(a){this.getSimulationOptions().hide()},changeSimulationType:function(d,b,c,a){if(a.get("type")=="Lsm"&&this.SimulType!="Lsm"){this.SimulType="Lsm"}else{if(a.get("type")=="Flood"&&this.SimulType!="Flood"){this.SimulType="Flood"}}this.getSimulationOptions().hide()},});Ext.define("app.store.SimulationStore",{extend:"Ext.data.Store",requires:["Ext.data.proxy.Rest"],id:"simulationList",config:{autoLoad:true,fields:["name","corners","visbounds","area_id","center"],proxy:{type:"rest",url:"http://sangkil.science.uva.nl:8003/area/list.json",reader:{type:"json",rootProperty:"areas"},}}});Ext.define("app.store.FloodDetailStore",{extend:"Ext.data.Store",requires:["Ext.data.proxy.Rest"],xtype:"FloodDetailStore",config:{autoLoad:true,fields:["name","center","corners","size","extents","visbounds","vissize","projection","dikes"],proxy:{type:"rest",url:"http://sangkil.science.uva.nl:8003/area/1/info.json",reader:{type:"json",},},listeners:{}},setUrl:function(b){var a=Ext.getStore("FloodDetailStore").getProxy();a._url="http://sangkil.science.uva.nl:8003/area/"+b+"/info.json"},});Ext.define("app.store.SimulationsSummary",{extend:"Ext.data.Store",requires:["Ext.data.proxy.Rest"],config:{autoLoad:true,fields:["area_id","test_id","submitted"],proxy:{type:"rest",url:"http://sangkil.science.uva.nl:8003/drfsm/list.json?summary",reader:{type:"json",rootProperty:"simulations"}}},});Ext.define("app.store.LsmStore",{extend:"Ext.data.Store",config:{autoLoad:true,fields:["area_id","test_id","submitted"],proxy:{type:"rest",url:"http://sangkil.science.uva.nl:8003/lsm/list.json",reader:{type:"json",rootProperty:"simulations"}}}});Ext.define("app.store.chartStore",{extend:"Ext.data.JsonStore",config:{fields:["time","volume"]}});Ext.define("app.Api",{mixins:["Ext.mixin.Observable"],singleton:true,getIzid:function(d,a,c){var b=this;var e=Ext.Ajax.request({method:"GET",url:"http://sangkil.science.uva.nl:8003/area/"+c+"/izid.json?latlng="+d+","+a,success:function(g,h){var f=Ext.decode(g.responseText);b.fireEvent("gotIzid",f.izid)},failure:function(){console.log("getIzid: failed to get izid")}})},getCsvFile:function(a,d){var b=this;var c=Ext.Ajax.request({method:"GET",url:"http://sangkil.science.uva.nl:8003/drfsm/"+a+"/results/izid/"+d+".csv",success:function(e,f){b.fireEvent("gotCsv",e.responseText)},failure:function(){console.log("getCsvFile: failed to get csv file")}})},requestInfo:function(b,d,a){var a=a||"http://sangkil.science.uva.nl:8003/drfsm/"+b+"/info.json";var c=Ext.Ajax.request({method:"GET",url:a,success:function(f,g){var e=Ext.decode(f.responseText);d(e)},failure:function(){console.log("failed to create images")}})}});Ext.define("app.controller.ChartController",{extend:"Ext.app.Controller",requires:["app.Api"],config:{refs:{Floodchart:{xtype:"floodChart",selector:"flood-chart",autoCreate:true,},ChartData:{selector:"flood-chart-id",autoCreate:true,},ExpandButton:"#expand-button",mapView:"SimulationMap",},control:{"#mapa":{maprender:"addListener"},"#closeChart":{tap:"closeFloodChart"},"#expand-button":{tap:"expandChart"},},},init:function(){self.marker=null;map=this.getMapView();app.Api.on({gotIzid:function(a){app.Api.getCsvFile(map.testId,a)},gotCsv:function(a){me.plot(a)}})},addListener:function(){me=this;map=this.getMapView();map.on({gotClick:function(a){if(self.marker!=null){self.marker.setMap(null);self.marker=null}lat=a.latLng.lat();lng=a.latLng.lng();options={icon:"resources/images/marker.png",draggable:true,raiseOnDrag:true,};self.marker=map.createMarker([lat,lng],options);app.Api.getIzid(lat,lng,map.areaId)},dragend:function(a){console.log(a);lat=a.latLng.lat();lng=a.latLng.lng();app.Api.getIzid(lat,lng,map.areaId)}})},plot:function(a){array=a.split("\n");store=Ext.getStore("chartStore");columns=array[0];volume=[];time=[];data=[];for(i=1;i<array.length;i++){line_clmns=array[i].split(",");data.push({time:parseInt(line_clmns[0]),volume:parseInt(line_clmns[2]),})}store.setData(data);this.openChart()},openChart:function(){this.getFloodchart().showBy(this.getMapView(),"t-t")},closeFloodChart:function(){this.getFloodchart().hide()},expandChart:function(){}});Ext.application({controllers:["Main","ChartController"],models:["SimulationModel","SimulationDetails"],stores:["SimulationStore","FloodDetailStore","SimulationsSummary","LsmStore","chartStore"],name:"app",requires:["Ext.MessageBox",],views:["Main","Simulation","List","Map","StepsOverlay","OptionsPanel","SimulationList","LsmSimulationList","Chart"],icon:{57:"resources/icons/Icon.png",72:"resources/icons/Icon~ipad.png",114:"resources/icons/Icon@2x.png",144:"resources/icons/Icon~ipad@2x.png"},phoneStartupScreen:"resources/loading/Homescreen.jpg",tabletStartupScreen:"resources/loading/Homescreen~ipad.jpg",launch:function(){Ext.fly("appLoadingIndicator").destroy();Ext.Viewport.add(Ext.create("app.view.Main"))},onUpdated:function(){Ext.Msg.confirm("Application Update","This application has just successfully been updated to the latest version. Reload now?",function(){window.location.reload()})}});
\ No newline at end of file
......@@ -63,7 +63,7 @@
* - Android
* - AndroidEmulator
*/
"platform":"iOS",
"platform":"Android",
/**
* @cfg {String} deviceType
......
www/resources/icons/Icon.png

3.53 KB | W: | H:

www/resources/icons/Icon.png

7.4 KB | W: | H:

www/resources/icons/Icon.png
www/resources/icons/Icon.png
www/resources/icons/Icon.png
www/resources/icons/Icon.png
  • 2-up
  • Swipe
  • Onion skin
www/resources/icons/Icon@2x.png

7.15 KB | W: | H:

www/resources/icons/Icon@2x.png

10.9 KB | W: | H:

www/resources/icons/Icon@2x.png
www/resources/icons/Icon@2x.png
www/resources/icons/Icon@2x.png
www/resources/icons/Icon@2x.png
  • 2-up
  • Swipe
  • Onion skin
www/resources/icons/Icon~ipad.png

4.37 KB | W: | H:

www/resources/icons/Icon~ipad.png

7.4 KB | W: | H:

www/resources/icons/Icon~ipad.png
www/resources/icons/Icon~ipad.png
www/resources/icons/Icon~ipad.png
www/resources/icons/Icon~ipad.png
  • 2-up
  • Swipe
  • Onion skin
www/resources/icons/icon-spot~ipad.png

2.96 KB | W: | H:

www/resources/icons/icon-spot~ipad.png

5.54 KB | W: | H:

www/resources/icons/icon-spot~ipad.png
www/resources/icons/icon-spot~ipad.png
www/resources/icons/icon-spot~ipad.png
www/resources/icons/icon-spot~ipad.png
  • 2-up
  • Swipe
  • Onion skin
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment