Add sample parksmap app and grid template.

This commit is contained in:
2024-03-14 10:50:09 +13:00
parent 3154613dad
commit 162f9477db
55 changed files with 32549 additions and 1 deletions

View File

@ -76,6 +76,36 @@ EOF
#+end_src #+end_src
#+RESULTS: #+RESULTS:
: subscription.operators.coreos.com/openshift-pipelines-operator configured : subscription.operators.coreos.com/openshift-pipelines-operator created
* Step 2 - Create sample selenium pipeline
Once OpenShift Pipelines is installed we can create an example pipeline to demonstrate the flow in the diagram above.
#+begin_src bash :results output
# Create namespace for application test environment
oc new-project parksmap-tst
# Create pipeline
cat << EOF | oc apply --filename -
EOF
#+end_src
#+RESULTS:
#+begin_example
Now using project "parksmap-tst" on server "https://api.cluster-272j9.dynamic.redhatworkshops.io:6443".
You can add applications to this project with the 'new-app' command. For example, try:
oc new-app rails-postgresql-example
to build a new example application in Ruby. Or use kubectl to deploy a simple Kubernetes application:
kubectl create deployment hello-node --image=k8s.gcr.io/e2e-test-images/agnhost:2.33 -- /agnhost serve-hostname
#+end_example

View File

@ -0,0 +1,101 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.19.RELEASE</version>
</parent>
<groupId>com.openshift.evg.roadshow</groupId>
<artifactId>parksmap-web</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<netflix.feign.version>9.3.1</netflix.feign.version>
<springboot.version>1.5.19.RELEASE</springboot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>openshift-client</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>${netflix.feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>${netflix.feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jaxrs</artifactId>
<version>${netflix.feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>${netflix.feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<version>${netflix.feign.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,18 @@
package com.openshift.evg.roadshow;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
/**
* Created by jmorales on 24/08/16.
*/
@SpringBootApplication
@ComponentScan(basePackages = "com.openshift.evg.roadshow.rest")
public class ParksMapApplication {
public static void main(String[] args) {
SpringApplication.run(ParksMapApplication.class, args);
}
}

View File

@ -0,0 +1,146 @@
package com.openshift.evg.roadshow.rest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.openshift.evg.roadshow.rest.gateway.helpers.EndpointRegistrar;
import com.openshift.evg.roadshow.rest.gateway.helpers.EndpointWatcher;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.kubernetes.client.Watcher;
import io.fabric8.openshift.client.DefaultOpenShiftClient;
import io.fabric8.openshift.client.OpenShiftClient;
public abstract class AbstractResourceWatcher<T extends HasMetadata> implements Watcher<T> {
private static final Logger logger = LoggerFactory.getLogger(AbstractResourceWatcher.class);
private String currentNamespace = null;
private Watch watch;
private OpenShiftClient client = new DefaultOpenShiftClient();;
private EndpointRegistrar endpointRegistrar;
private Map<String, EndpointWatcher> endpointsWatchers = new HashMap<String, EndpointWatcher>();
@Override
public void eventReceived(Action action, T t) {
logger.info("Action: {}, Resource: {}", action, t);
String resourceName = t.getMetadata().getName();
// }
if (action == Action.ADDED) {
logger.info("Resource {} added", resourceName);
EndpointWatcher epW = endpointsWatchers.get(resourceName);
if (epW == null) {
epW = new EndpointWatcher(endpointRegistrar, client, currentNamespace, resourceName);
endpointsWatchers.put(resourceName, epW);
}
} else if (action == Action.DELETED) {
logger.info("Resource {} deleted", resourceName);
EndpointWatcher epW = endpointsWatchers.get(resourceName);
if (epW != null) {
epW.close();
endpointRegistrar.unregister(resourceName);
endpointsWatchers.remove(resourceName);
}
} else if (action == Action.MODIFIED) {
// TODO: Modification of a resource is cumbersome. Look into how to
// best implement this
EndpointWatcher epW = endpointsWatchers.get(resourceName);
endpointsWatchers.remove(resourceName);
endpointRegistrar.unregister(resourceName);
epW = new EndpointWatcher(endpointRegistrar, client, currentNamespace, resourceName);
endpointsWatchers.put(resourceName, epW);
} else if (action == Action.ERROR) {
logger.error("Resource ERRORED");
EndpointWatcher epW = endpointsWatchers.get(resourceName);
endpointsWatchers.remove(resourceName);
epW = new EndpointWatcher(endpointRegistrar, client, currentNamespace, resourceName);
endpointsWatchers.put(resourceName, epW);
}
}
/**
* This will get notified when Kubernetes client is closed
*
* @param e
*/
@Override
public void onClose(KubernetesClientException e) {
if (e != null) {
// This is when the client is closed
logger.error("[ERROR] There was an error in the client {}", e.getMessage());
init(endpointRegistrar);
} else {
logger.info("Closing this Watcher");
}
}
private void cleanUp() {
if (watch != null)
watch.close();
// If this watch has been closed, we create complete set of new watches
for (EndpointWatcher epWatcher : endpointsWatchers.values()) {
epWatcher.close();
}
endpointsWatchers.clear();
}
public void init(EndpointRegistrar endpointRegistrar) {
cleanUp();
// BackendsController
if (this.endpointRegistrar == null) {
this.endpointRegistrar = endpointRegistrar;
}
if (currentNamespace == null) {
currentNamespace = client.getNamespace();
}
logger.info("[INFO] {} is watching for resources started in namespace {} ", this.getClass().getName(),
currentNamespace);
try {
List<T> resources = listWatchedResources();
for (T resource : resources) {
String resourceName = resource.getMetadata().getName();
EndpointWatcher endpointWatcher = endpointsWatchers.get(resourceName);
if (endpointWatcher == null) {
endpointWatcher = new EndpointWatcher(endpointRegistrar, client, currentNamespace, resourceName);
endpointsWatchers.put(resourceName, endpointWatcher);
}
}
watch = doInit();
} catch (KubernetesClientException e) {
// If there is no proper permission, don't fail misserably
logger.error(
"Error initialiting application. Probably you need the appropriate permissions to view this namespace {}. {}",
currentNamespace, e.getMessage());
}
}
protected OpenShiftClient getOpenShiftClient() {
return client;
}
protected String getNamespace() {
return currentNamespace;
}
protected abstract List<T> listWatchedResources();
protected abstract Watch doInit();
protected abstract String getUrl(String resourceName);
}

View File

@ -0,0 +1,124 @@
package com.openshift.evg.roadshow.rest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.openshift.evg.roadshow.rest.gateway.ApiGatewayController;
import com.openshift.evg.roadshow.rest.gateway.DataGatewayController;
import com.openshift.evg.roadshow.rest.gateway.helpers.EndpointRegistrar;
import com.openshift.evg.roadshow.rest.gateway.model.Backend;
/**
* Backend controller. Every time a backend appears/dissapears in OpenShift
* it will send a notification to the web to show/hide the appropriate layer/map
* <p>
* Created by jmorales on 24/08/16.
*/
@RestController
@RequestMapping("/ws/backends")
public class BackendsController implements EndpointRegistrar {
private static final Logger logger = LoggerFactory.getLogger(BackendsController.class);
@Value("${test}")
private Boolean test;
@Autowired
private SimpMessagingTemplate messagingTemplate;
@Autowired
private ApiGatewayController apiGateway;
@Autowired
private DataGatewayController dataGateway;
@Autowired
private ServiceWatcher serviceWatcher;
@Autowired
private RouteWatcher routeWatcher;
private Map<String, Backend> registeredBackends = new HashMap<String, Backend>();
/**
* This method is used to start monitoring for services
*/
@RequestMapping(method = RequestMethod.GET, value = "/init")
@PostConstruct
public void init() {
routeWatcher.init(this);
serviceWatcher.init(this);
}
/**
* @param
* @return
*/
@RequestMapping(method = RequestMethod.GET, value = "/register", produces = "application/json")
public List<Backend> register(@RequestParam(value = "endpoint") String endpoint) {
logger.info("Backends.register endpoint at ({})", endpoint);
Backend newBackend = null;
String endpointUrl = routeWatcher.getUrl(endpoint); // try to find a route for endpoint
if (endpointUrl == null || endpointUrl.trim().equals("")) {
endpointUrl = serviceWatcher.getUrl(endpoint); // otherwise, find a service for endpoint
}
// Query for backend data.
if (endpoint != null) {
if ((newBackend = apiGateway.getFromRemote(endpointUrl)) != null) {
// TODO: BackendId should not be fetched from remote. For now I replace the remote one with the local one.
newBackend.setId(endpoint);
// Register the new backend
apiGateway.add(endpoint, endpointUrl);
dataGateway.add(endpoint, endpointUrl);
registeredBackends.put(endpoint, newBackend);
logger.info("Backend from server: ({}) ", newBackend);
// Notify web
messagingTemplate.convertAndSend("/topic/add", newBackend);
} else {
logger.info("Backend with provided id ({}) already registered", endpoint);
}
}
return new ArrayList<Backend>(registeredBackends.values());
}
@RequestMapping(method = RequestMethod.GET, value = "/unregister", produces = "application/json")
public List<Backend> unregister(@RequestParam(value = "endpointName") String endpointName) {
logger.info("Backends.unregister service at ({})", endpointName);
Backend backend = null;
if ((backend = registeredBackends.get(endpointName)) != null) {
messagingTemplate.convertAndSend("/topic/remove", backend); // Notify web
registeredBackends.remove(endpointName);
apiGateway.remove(endpointName);
dataGateway.remove(endpointName);
} else {
logger.info("No backend at ({})", endpointName);
}
return new ArrayList<Backend>(registeredBackends.values());
}
@RequestMapping(method = RequestMethod.GET, value = "/list", produces = "application/json")
public List<Backend> getAll() {
logger.info("Backends: getAll");
return new ArrayList<Backend>(registeredBackends.values());
}
}

View File

@ -0,0 +1,20 @@
package com.openshift.evg.roadshow.rest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* Healthz endpoint for liveness and readiness of the application
*
* Created by jmorales on 11/08/16.
*/
@RestController
@RequestMapping("/ws/healthz")
public class Healthz {
@RequestMapping(method = RequestMethod.GET, value = "/")
public String healthz() {
return "OK";
}
}

View File

@ -0,0 +1,53 @@
package com.openshift.evg.roadshow.rest;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.openshift.api.model.Route;
@Component
public class RouteWatcher extends AbstractResourceWatcher<Route> {
private static final Logger logger = LoggerFactory.getLogger(ServiceWatcher.class);
private static final String PARKSMAP_BACKEND_LABEL = "type=parksmap-backend";
@Override
protected List<Route> listWatchedResources() {
return getOpenShiftClient().routes().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL).list()
.getItems();
}
@Override
protected Watch doInit() {
return getOpenShiftClient().routes().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL).watch(this);
}
@Override
protected String getUrl(String routeName) {
List<Route> routes = getOpenShiftClient().routes().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL)
.withField("metadata.name", routeName).list().getItems();
if (routes.isEmpty()) {
return null;
}
Route route = routes.get(0);
String routeUrl = "";
try {
String protocol = "http://";
if((route.getSpec().getTls()!=null)&&(route.getSpec().getTls().getTermination()!=null)){
protocol = "https://";
}
routeUrl = protocol + route.getSpec().getHost();
} catch (Exception e) {
logger.error("Route {} does not have a port assigned", routeName);
}
logger.info("[INFO] Computed route URL: {}", routeUrl);
return routeUrl;
}
}

View File

@ -0,0 +1,51 @@
package com.openshift.evg.roadshow.rest;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.client.Watch;
@Component
public class ServiceWatcher extends AbstractResourceWatcher<Service> {
private static final Logger logger = LoggerFactory.getLogger(ServiceWatcher.class);
private static final String PARKSMAP_BACKEND_LABEL = "type=parksmap-backend";
@Override
protected List<Service> listWatchedResources() {
return getOpenShiftClient().services().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL).list()
.getItems();
}
@Override
protected Watch doInit() {
return getOpenShiftClient().services().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL).watch(this);
}
@Override
protected String getUrl(String serviceName) {
List<Service> services = getOpenShiftClient().services().inNamespace(getNamespace())
.withLabel(PARKSMAP_BACKEND_LABEL).withField("metadata.name", serviceName).list().getItems();
if (services.isEmpty()) {
return null;
}
Service service = services.get(0);
String serviceURL = "";
int port = 8080;
try {
port = service.getSpec().getPorts().get(0).getPort();
} catch (Exception e) {
logger.error("Service {} does not have a port assigned", serviceName);
}
serviceURL = "http://" + serviceName + ":" + port;
logger.info("[INFO] Computed service URL: {}", serviceURL);
return serviceURL;
}
}

View File

@ -0,0 +1,31 @@
package com.openshift.evg.roadshow.rest;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
/**
* This class provides support for websockets communication with the web Web
* will use t2 topics: /topic/add Notification that there is a new backend
* /topic/remove Notification of removal of backend
* <p>
* Created by jmorales on 26/08/16.
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/socks-backends").withSockJS();
}
}

View File

@ -0,0 +1,101 @@
package com.openshift.evg.roadshow.rest.gateway;
import com.openshift.evg.roadshow.rest.gateway.api.BackendServiceRemote;
import com.openshift.evg.roadshow.rest.gateway.helpers.CustomErrorDecoder;
import com.openshift.evg.roadshow.rest.gateway.model.Backend;
import feign.Feign;
import feign.Retryer;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.jaxrs.JAXRSContract;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import java.util.HashMap;
import java.util.Map;
import static java.util.concurrent.TimeUnit.SECONDS;
/**
* API Gateway. It will dispatch connections to the appropriate backend
* <p>
* Created by jmorales on 24/08/16.
*/
@Controller
public class ApiGatewayController {
private static final Logger logger = LoggerFactory.getLogger(ApiGatewayController.class);
private Map<String, BackendServiceRemote> remoteServices = new HashMap<String, BackendServiceRemote>();
/**
*
*
* @param backendId
* @param url
*/
public final void add(String backendId, String url) {
if (remoteServices.get(backendId) == null) {
remoteServices.put(backendId, Feign.builder().client(CustomFeignClient.getClient()).contract(new JAXRSContract()).encoder(new JacksonEncoder())
.decoder(new JacksonDecoder()).target(BackendServiceRemote.class, url));
logger.info("Backend ({}) added to the API Gateway", backendId);
} else {
logger.error("This backend ({}) did already exist in the API Gateway", backendId);
}
}
/**
*
*
* @param backendId
*/
public final void remove(String backendId) {
if (remoteServices.get(backendId) != null) {
remoteServices.remove(backendId);
logger.info("Backend ({}) removed from the API Gateway", backendId);
} else {
logger.error("This backend ({}) did NOT exist in the API Gateway", backendId);
}
}
/**
*
*
* @param backendId
* @return
*/
public Backend getFromLocal(String backendId) {
BackendServiceRemote backend = null;
if ((backend = remoteServices.get(backendId)) != null) {
logger.info("Calling remote service {}", backendId);
try {
return backend.get();
} catch (Exception e) {
logger.error("Error connecting to backend server {}", e.getMessage());
}
}
return null;
}
/**
*
*
* @param remoteURL
* @return
*/
public Backend getFromRemote(String remoteURL) {
logger.info("Calling remote service at {}", remoteURL);
try {
return Feign.builder().client(CustomFeignClient.getClient()).contract(new JAXRSContract()).encoder(new JacksonEncoder()).decoder(new JacksonDecoder())
.retryer(new Retryer.Default(200, SECONDS.toMillis(1), 5)).errorDecoder(new CustomErrorDecoder())
.target(BackendServiceRemote.class, remoteURL).get();
} catch (Exception e) {
logger.error("Error connecting to backend server {}", e.getMessage());
logger.error("Error message",e);
}
return null;
}
}

View File

@ -0,0 +1,64 @@
package com.openshift.evg.roadshow.rest.gateway;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import feign.Client;
class CustomFeignClient {
private static final Logger logger = LoggerFactory.getLogger(CustomFeignClient.class);
/**
* This method should not be used in production!! It is only in a poof of
* concept!
*
* @return
*/
private static SSLSocketFactory getSSLSocketFactory() {
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new X509TrustManager[] { new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
logger.info("checkClientTrusted");
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
logger.info("checkClientTrusted");
}
public X509Certificate[] getAcceptedIssuers() {
logger.info("checkClientTrusted");
return new X509Certificate[0];
}
} }, new SecureRandom());
logger.warn("Ignoring certification errors! Don't use in production!");
return context.getSocketFactory();
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}
public static Client getClient() {
return new Client.Default(getSSLSocketFactory(), getHostNameVerifier());
}
private static HostnameVerifier getHostNameVerifier() {
HostnameVerifier hostnameVerifier= new HostnameVerifier(){
public boolean verify(String hostname,
javax.net.ssl.SSLSession sslSession) {
logger.warn("Ignoring hostname verification errors! Don't use in production!");
return true;
}
};
return hostnameVerifier;
}
}

View File

@ -0,0 +1,86 @@
package com.openshift.evg.roadshow.rest.gateway;
import com.openshift.evg.roadshow.rest.gateway.api.DataServiceRemote;
import com.openshift.evg.roadshow.rest.gateway.model.DataPoint;
import feign.Feign;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.jaxrs.JAXRSContract;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* API Gateway. It will dispatch connections to the appropriate backend
*
* Created by jmorales on 24/08/16.
*/
@RestController
@RequestMapping("/ws/data")
public class DataGatewayController {
private static final Logger logger = LoggerFactory.getLogger(DataGatewayController.class);
private Map<String, DataServiceRemote> remoteServices = new HashMap<String, DataServiceRemote>();
public DataGatewayController() {
}
/**
*
* @param backendId
* @param url
*/
public final void add(String backendId, String url) {
if (remoteServices.get(backendId) == null) {
remoteServices.put(backendId, Feign.builder().client(CustomFeignClient.getClient()).contract(new JAXRSContract()).encoder(new JacksonEncoder())
.decoder(new JacksonDecoder()).target(DataServiceRemote.class, url));
logger.info("Backend ({}) added to the Data Gateway", backendId);
} else {
logger.error("This backend ({}) did already exist in the Data Gateway", backendId);
}
}
/**
*
* @param backendId
*/
public final void remove(String backendId) {
if (remoteServices.get(backendId) != null) {
remoteServices.remove(backendId);
logger.info("Backend ({}) removed from the Data Gateway", backendId);
} else {
logger.error("This backend ({}) did NOT exist in the Data Gateway", backendId);
}
}
@RequestMapping(method = RequestMethod.GET, value = "/all", produces = "application/json")
public List<DataPoint> getAll(@RequestParam(value = "service") String serviceURL) {
DataServiceRemote remote = remoteServices.get(serviceURL);
if (remote != null) {
logger.info("[WEB-CALL] Calling remote service for {}", serviceURL);
return remote.getAll();
} else {
logger.error("[WEB-CALL] No remote service for {}", serviceURL);
return null;
}
}
@RequestMapping(method = RequestMethod.GET, value = "/within", produces = "application/json")
public List<DataPoint> getWithin(@RequestParam(value = "service") String serviceURL, @RequestParam("lat1") float lat1,
@RequestParam("lon1") float lon1, @RequestParam("lat2") float lat2, @RequestParam("lon2") float lon2) {
DataServiceRemote remote = remoteServices.get(serviceURL);
if (remote != null) {
logger.info("[WEB-CALL] Calling remote service for {}", serviceURL);
return remote.findWithin(lat1, lon1, lat2, lon2);
} else {
logger.error("[WEB-CALL] No remote service for {}", serviceURL);
return null;
}
}
}

View File

@ -0,0 +1,21 @@
package com.openshift.evg.roadshow.rest.gateway.api;
import com.openshift.evg.roadshow.rest.gateway.model.Backend;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
/**
* Contract to use in the backends to provide backend information
*
* Created by jmorales on 26/09/16.
*/
@Path("/ws/info")
public interface BackendServiceRemote {
@GET
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
public Backend get();
}

View File

@ -0,0 +1,35 @@
package com.openshift.evg.roadshow.rest.gateway.api;
import com.openshift.evg.roadshow.rest.gateway.model.DataPoint;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import java.util.List;
/**
* Created by jmorales on 28/09/16.
*/
@RequestMapping("/ws/data")
@Path("/ws/data")
@RestController
public interface DataServiceRemote {
@RequestMapping(method = RequestMethod.GET, value = "/", produces = "application/json")
@GET()
@Path("/all")
@Produces("application/json")
public List<DataPoint> getAll();
@RequestMapping(method = RequestMethod.GET, value = "/within", produces = "application/json")
@GET()
@Path("/within")
@Produces("application/json")
public List<DataPoint> findWithin(@RequestParam("lat1") @QueryParam("lat1") float lat1,
@RequestParam("lon1") @QueryParam("lon1") float lon1, @RequestParam("lat2") @QueryParam("lat2") float lat2,
@RequestParam("lon2") @QueryParam("lon2") float lon2);
}

View File

@ -0,0 +1,20 @@
package com.openshift.evg.roadshow.rest.gateway.helpers;
import feign.Response;
import feign.RetryableException;
import feign.codec.ErrorDecoder;
import static feign.FeignException.errorStatus;
/**
* Created by jmorales on 04/10/16.
*/
public class CustomErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String s, Response response) {
if (response.status() == 503)
return new RetryableException("Error 503 from server. Let's retry", null);
else
return errorStatus(s, response);
}
}

View File

@ -0,0 +1,13 @@
package com.openshift.evg.roadshow.rest.gateway.helpers;
import java.util.List;
import com.openshift.evg.roadshow.rest.gateway.model.Backend;
public interface EndpointRegistrar {
List<Backend> register(String endpointName);
List<Backend> unregister(String endpointName);
void init();
}

View File

@ -0,0 +1,104 @@
package com.openshift.evg.roadshow.rest.gateway.helpers;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.fabric8.kubernetes.api.model.Endpoints;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.kubernetes.client.Watcher;
/**
*
* Created by jmorales on 04/10/16.
*/
public class EndpointWatcher implements Watcher<Endpoints> {
private static final Logger logger = LoggerFactory.getLogger(EndpointWatcher.class);
private Watch watch;
private String namespace;
private String endpointName;
private KubernetesClient client;
private AtomicInteger endpointsAvailable = new AtomicInteger(0);
private EndpointRegistrar callback;
public EndpointWatcher(EndpointRegistrar callback, KubernetesClient client, String namespace, String endpointName) {
this.client = client;
this.namespace = namespace;
this.endpointName = endpointName;
this.callback = callback;
logger.info("EndpointWatcher created for: endpoints/{} -n {}", endpointName, namespace);
if (hasEndpoints()) {
callback.register(endpointName);
} else {
callback.unregister(endpointName);
}
// Create the watch
watch = client.endpoints().inNamespace(namespace).withName(endpointName).watch(this);
}
private boolean hasEndpoints() {
return hasEndpoints(client.endpoints().inNamespace(namespace).withName(endpointName).get());
}
private boolean hasEndpoints(Endpoints endpoints) {
int size = getEndpointsAddressSize(endpoints);
if (size > 0) {
endpointsAvailable.set(size);
return size > 0;
} else
return false;
}
private int getEndpointsAddressSize(Endpoints endpoints) {
if (endpoints.getSubsets().size() > 0)
return endpoints.getSubsets().get(0).getAddresses().size();
else
return 0;
}
@Override
public void eventReceived(Action action, Endpoints endpoints) {
int current = getEndpointsAddressSize(endpoints);
int previous = endpointsAvailable.getAndSet(current);
if (previous != current) {
logger.info("Endpoints changed, from {} to {}", previous, current);
if (previous == 0) {
if (current > 0) {
logger.info("There are endpoints for {} available. Registering", endpointName);
callback.register(endpointName);
}
}
if (current == 0) {
if (previous > 0) {
logger.info("There's no endpoints for {}. Unregistering", endpointName);
callback.unregister(endpointName);
}
}
} else {
logger.info("Endpoints changes ignored");
}
}
@Override
public void onClose(KubernetesClientException e) {
callback.init();
}
public void close() {
if (watch != null)
watch.close();
}
}

View File

@ -0,0 +1,121 @@
package com.openshift.evg.roadshow.rest.gateway.model;
/**
* This represents a backend. Once a backend is registered, a call to the
* backend to get this information about it will be issued.
*
* Created by jmorales on 24/08/16.
*/
public class Backend {
public static final String BACKEND_TYPE_MARKER = "marker";
public static final String BACKEND_TYPE_CLUSTER = "cluster";
public static final String BACKEND_TYPE_TEMP = "temp";
public static final String BACKEND_TYPE_HEATMAP = "heatmap";
public static final String BACKEND_SCOPE_ALL = "all";
public static final String BACKEND_SCOPE_WITHIN = "within";
private String id;
private String displayName;
private Coordinates center = new Coordinates("0", "0");
private int zoom = 1;
private int maxZoom = 1;
private String type = BACKEND_TYPE_CLUSTER;
private boolean visible = true;
private String scope = BACKEND_SCOPE_ALL;
public Backend() {
}
public Backend(String id, String displayName, String service) {
this.id = id;
this.displayName = displayName;
}
public Backend(String id, String displayName, Coordinates center, int zoom) {
this.id = id;
this.displayName = displayName;
this.center = center;
this.zoom = zoom;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public Coordinates getCenter() {
return center;
}
public void setCenter(Coordinates center) {
this.center = center;
}
public int getZoom() {
return zoom;
}
public void setZoom(int zoom) {
this.zoom = zoom;
}
public int getMaxZoom() {
return maxZoom;
}
public void setMaxZoom(int maxzoom) {
this.maxZoom = maxzoom;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public boolean isVisible() {
return visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
@Override
public String toString() {
return "Backend{" +
"id='" + id + '\'' +
", displayName='" + displayName + '\'' +
", center='" + center + '\'' +
", zoom='" + zoom + '\'' +
", type='" + type + '\'' +
", scope='" + scope + '\'' +
", visible='" + visible + '\'' +
", maxZoom='" + maxZoom + '\'' +
'}';
}
}

View File

@ -0,0 +1,52 @@
package com.openshift.evg.roadshow.rest.gateway.model;
import java.util.List;
/**
* TODO: Remove???
* <p>
* Created by jmorales on 18/08/16.
*/
public class Coordinates {
private String latitude;
private String longitude;
public Coordinates() {
}
public Coordinates(String lat, String lng) {
this.latitude = lat;
this.longitude = lng;
}
public Coordinates(List<?> position) {
if (position.size() > 0)
this.latitude = position.get(0).toString();
if (position.size() > 1)
this.longitude = position.get(1).toString();
}
public String getLatitude() {
return latitude;
}
public void setLatitude(String lat) {
this.latitude = lat;
}
public String getLongitude() {
return longitude;
}
public void setLongitude(String lng) {
this.longitude = lng;
}
@Override
public String toString() {
return "Coordinates{" +
"lat='" + latitude + '\'' +
", lng='" + longitude + '\'' +
'}';
}
}

View File

@ -0,0 +1,72 @@
package com.openshift.evg.roadshow.rest.gateway.model;
public class DataPoint {
private String id;
private String name;
private Coordinates position;
private String longitude;
private String latitude;
private String info;
public DataPoint() {
}
public DataPoint(String id, String name) {
this.id = id;
this.name = name;
}
public Object getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Coordinates getPosition() {
return position;
}
public void setPosition(Coordinates position) {
this.position = position;
}
public Object getLongitude() {
return longitude;
}
public void setLongitude(String longitude) {
this.longitude = longitude;
}
public Object getLatitude() {
return latitude;
}
public void setLatitude(String latitude) {
this.latitude = latitude;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public String toString() {
return "NationalPark{" +
"id=" + id +
", name=" + name +
", coordinates=" + position +
", info=" + info +
'}';
}
}

View File

@ -0,0 +1,2 @@
spring.application.name=parksmap
test=true

View File

@ -0,0 +1,2 @@
spring.application.name=parksmap
test=false

View File

@ -0,0 +1,464 @@
<!doctype html>
<html lang="en">
<head>
<title>Map Visualizer on OpenShift 4</title>
<link rel="stylesheet" href="leaflet/leaflet.css"/>
<link rel="stylesheet" href="parksmap.css"/>
<link href="//fonts.googleapis.com/css?family=Oswald" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="leaflet/markercluster/MarkerCluster.css"/>
<link rel="stylesheet" href="leaflet/markercluster/MarkerCluster.Default.css"/>
<link rel="stylesheet" href="leaflet/font-awesome.min.css">
<link rel="stylesheet" href="leaflet/bootstrap.min.css">
<link rel="stylesheet" href="leaflet/beautify-marker/leaflet-beautify-marker-icon.css">
<link rel="stylesheet" href="leaflet/messagebox/leaflet-messagebox.css">
<script src="leaflet/leaflet.js"></script>
<script src="leaflet/markercluster/leaflet.markercluster.js"></script>
<script src="leaflet/heatmap/webgl-heatmap.js"></script>
<script src="leaflet/heatmap/webgl-heatmap-leaflet.js"></script>
<script src="leaflet/beautify-marker/leaflet-beautify-marker-icon.js"></script>
<script src="leaflet/beautify-marker/leaflet-beautify-marker.js"></script>
<script src="leaflet/messagebox/leaflet-messagebox.js"></script>
<script src="leaflet/spin.js"></script>
<script src="jquery-3.1.0.min.js"></script>
<script src="jquery.spin.js"></script>
<!-- For websockets -->
<script src="sockjs-1.1.0.js"></script>
<script src="stomp.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<style type="text/css">
body {
padding: 0;
margin: 0;
}
html, body, #map {
height: 100%;
font-family: 'Oswald';
}
.leaflet-container .leaflet-control-zoom {
margin-left: 13px;
margin-top: 70px;
}
#map {
z-index: 1;
}
#title {
z-index: 2;
position: absolute;
left: 10px;
}
</style>
</head>
<body>
<h1 id="title" class="title">Map Visualizer on OpenShift 4</h1>
<div id="map"></div>
<script>
/*
backend{
id: "name";
displayName: "displayName";
center: ["",""],
zoom: 1,
visible: true,
scope: [all,within],
type: [marker,cluster,temp]
maxzoom: 5,
layer: L.markerClusterGroup()
};
*/
/************************
* BEGIN OF WEBSOCKETS COMM
************************/
var stompClient = null;
const isGeolocationAvailable = () => {
if (location.hostname === "localhost" && "geolocation" in navigator) return true;
if (location.protocol.substr(0, 5) === "https" && "geolocation" in navigator) return true;
return false;
}
const getGeolocation = () => {
let p = new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(pos => {
console.log(`User location is ${pos.coords.latitude},${pos.coords.longitude}`);
resolve(pos.coords);
});
});
return p;
};
function connect() {
var socket = new SockJS('/socks-backends');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
// console.log('Connected: ' + frame);
stompClient.subscribe('/topic/add', function(message){
var backend = getBackend(JSON.parse(message.body));
addBackend(backend);
});
stompClient.subscribe('/topic/remove', function(message){
var backend = getBackend(JSON.parse(message.body));
removeBackend(backend.id);
});
});
}
// TODO: Reconnect automatically. When redeploying application, need to reconnect, otherwise page needs to be reloaded
// Look for solution like: https://github.com/jmesnil/stomp-websocket/issues/81#issuecomment-246854734
// Backend types: {popup_marker,marker,heatmap,temp}
var getBackend = function(backendFromServer){
var backend;
if (backendFromServer.type=='temp'){
backend = {
id: backendFromServer.id,
displayName: backendFromServer.displayName,
center: backendFromServer.center,
zoom: backendFromServer.zoom,
type: backendFromServer.type,
visible: backendFromServer.visible,
scope: backendFromServer.scope,
maxzoom: backendFromServer.maxZoom,
layer: new L.layerGroup()
};
}
else if (backendFromServer.type=='marker'){
backend = {
id: backendFromServer.id,
displayName: backendFromServer.displayName,
center: backendFromServer.center,
zoom: backendFromServer.zoom,
type: backendFromServer.type,
visible: backendFromServer.visible,
scope: backendFromServer.scope,
maxzoom: backendFromServer.maxZoom,
layer: new L.layerGroup()
};
}
else if (backendFromServer.type=='cluster'){
// TODO: Get scope, type and visible from the backend
backend = {
id: backendFromServer.id,
displayName: backendFromServer.displayName,
center: backendFromServer.center,
zoom: backendFromServer.zoom,
type: backendFromServer.type,
visible: backendFromServer.visible,
scope: backendFromServer.scope,
maxzoom: backendFromServer.maxZoom,
layer: new L.markerClusterGroup()
};
}
else if (backendFromServer.type=='heatmap'){
// TODO: Get scope, type and visible from the backend
backend = {
id: backendFromServer.id,
displayName: backendFromServer.displayName,
center: backendFromServer.center,
zoom: backendFromServer.zoom,
type: backendFromServer.type,
visible: backendFromServer.visible,
scope: backendFromServer.scope,
maxzoom: backendFromServer.maxZoom,
layer: L.webGLHeatmap({size: 30000, autoresize: true, alphaRange: 1, opacity: 1}) // opacity close to 0, less clear
};
}
return backend;
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
/************************
* END OF WEBSOCKETS COMM
************************/
var backends = new Map();
var mbAttr = 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
'<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
'Imagery &copy; <a href="http://mapbox.com">Mapbox</a>';
var mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}';
var mapToken = 'pk.eyJ1IjoiZ3JhaGFtZHVtcGxldG9uIiwiYSI6ImNpemR6cnFhbTF0YWszMnA5eTJ0dXY1ZW8ifQ.-91_VlsyyskWAWF54GYFMg';
var streets = L.tileLayer(mbUrl, {
id: 'mapbox/streets-v11',
attribution: mbAttr,
tileSize: 512,
maxZoom: 18,
zoomOffset: -1,
accessToken: mapToken
});
var grayscale = L.tileLayer(mbUrl, {
id: 'mapbox/light-v10',
attribution: mbAttr,
tileSize: 512,
maxZoom: 18,
zoomOffset: -1,
accessToken: mapToken
});
let coords = [27.60567, -18.19336];
var map = L.map('map', {
center: coords,
zoom: 3,
layers: [grayscale]
});
if (isGeolocationAvailable()) {
getGeolocation().then(position => {
map.setView([position.latitude, position.longitude], 3);
});
}
var baseLayers = {
"Grayscale": grayscale,
"Streets": streets
};
var overlays = {};
var controls = L.control.layers(baseLayers, overlays, {collapsed:false} );
controls.addTo(map);
// Add messageBox
var options = { timeout: 5000, position: 'bottomleft' }
var msgBox = L.control.messagebox(options).addTo(map);
async function addBackend(backend){
console.log("Adding layer for backend: " + backend.id);
// Check that the backend (by name did not exist, in which case, we'll remove the old one)
if (backends.has(backend.id)){
console.log("Removing old backend: " + backend.id);
removeBackend(backend.id);
}
backends.set(backend.id, backend);
if (backend.scope=='all') {
queryAll(backend);
}else if (backend.scope=='within'){
queryWithin(backend);
}
// Add layer to the map
map.addLayer(backend.layer);
controls.addOverlay(backend.layer, backend.displayName);
// Focus map on the backend center and zoom
// if geolocation is available, center on current position.
let latitude = backend.center.latitude;
let longitude = backend.center.longitude;
if (isGeolocationAvailable()) {
let position = await getGeolocation();
latitude = position.latitude;
longitude = position.longitude;
}
map.setView([latitude, longitude], backend.zoom);
}
function queryAll(backend){
// Expected dataPoint { name, latitude, longitude, details }
$('#map').spin();
$.get("/ws/data/all?service="+backend.id, function(data){
console.log("Displaying " + data.length + " points from backend " + backend.id);
var dataPoints = [];
for (var i = 0; i < data.length; i++){
dataPoint = data[i];
if (backend.type=='cluster') {
showCluster(backend,dataPoint);
}else if (backend.type=='heatmap') {
showHeatmap(backend,dataPoint);
}else if (backend.type=='marker') {
showMarker(backend,dataPoint);
}else if (backend.type=='temp') {
showTemp(backend,dataPoint);
}else{
console.log("Backend type " + backend.type + " not supported")
}
}
console.log("Done");
$('#map').spin(false);
}, "json");
}
function queryWithin(backend) {
if (backend.visible == true && backend.scope == 'within'){
if (backend.maxzoom<=map.getZoom()) {
console.log("Querying data for backend: " + backend.id);
var bounds = map.getBounds();
var url = "/ws/data/within?service=" + backend.id + "&lat1=" + bounds.getNorthWest().lat + "&lon1=" + bounds.getNorthWest().lng + "&lat2=" + bounds.getSouthEast().lat + "&lon2=" + bounds.getSouthEast().lng;
// Expected dataPoint { name, latitude, longitude, details }
$('#map').spin();
$.get(url, function (data) {
// Clear previous points
backend.layer.clearLayers();
console.log("Displaying " + data.length + " points from backend " + backend.id);
var dataPoints = [];
for (var i = 0; i < data.length; i++) {
dataPoint = data[i];
if (backend.type == 'cluster') {
showCluster(backend, dataPoint);
} else if (backend.type == 'heatmap') {
showHeatmap(backend, dataPoint);
} else if (backend.type == 'marker') {
showMarker(backend, dataPoint);
} else if (backend.type == 'temp') {
showTemp(backend, dataPoint);
} else {
console.log("Backend type " + backend.type + " not supported")
}
}
console.log("Done");
$('#map').spin(false);
}, "json");
}else{
msgBox.show('No data querying for ' + backend.displayName + ' at this level of zoom');
}
}
}
function queryWithinAll(){
backends.forEach(queryWithin);
}
function removeBackend(backendId){
if (backends.has(backendId)) {
var backend = backends.get(backendId);
controls.removeLayer(backend.layer);
map.removeLayer(backend.layer);
backends.delete(backend.id);
} else{
console.log("Trying to remove a non existant backend: " + backendId);
}
}
// TODO: Change initial load to only queryWithins
function initialLoad(){
backends.clear();
$.get("/ws/backends/list", function(data){
for (var i = 0; i < data.length; i++){
var backendFromServer = data[i];
var backend = getBackend(backendFromServer);
addBackend(backend);
}
}, "json");
}
function showCluster(backend, dataPoint){
var popupInformation = "<b>" + dataPoint.name + "</b></br>";
// TODO: Work additionalInfo
var marker = L.marker([dataPoint.latitude, dataPoint.longitude]).bindPopup(popupInformation);
marker.addTo(backend.layer);
}
function showMarker(backend, dataPoint){
var popupInformation = "<b>" + dataPoint.name + "</b></br>";
// TODO: Work additionalInfo
var marker = L.marker([dataPoint.latitude, dataPoint.longitude]).bindPopup(popupInformation);
marker.addTo(backend.layer);
}
function showHeatmap(backend, dataPoint){
if (isNaN(dataPoint.latitude) || isNaN(dataPoint.longitude)){
console.log("Invalid Data Point: " + dataPoint);
}else{
try {
var temp = dataPoint.info.match(/.*Avg:(.*)$/);
if (isNaN(temp[1])){
console.log("No temp in this datapoint: " + dataPoint);
}else {
if (dataPoint.latitude!=null && dataPoint.longitude!=null)
backend.layer.addDataPoint(dataPoint.latitude, dataPoint.longitude, Math.round(temp[1])+50);
}
}catch(err){
console.log("Error with this dataPoint [" + dataPoint + "]. Error="+ err.message);
}
backend.layer.draw();
}
}
function showTemp(backend, dataPoint) {
try{
var temp = dataPoint.info.match(/.*Avg:(.*)$/);
if (isNaN(temp[1])){
console.log("No temp in this datapoint: " + dataPoint);
}else {
if (dataPoint.latitude!=null && dataPoint.longitude!=null) {
var iconColor;
if (temp[1] < 0) iconColor = 'royalblue';
if (temp[1] >= 0) iconColor = 'skyblue';
if (temp[1] >= 10) iconColor = 'yellow';
if (temp[1] >= 20) iconColor = 'orange';
if (temp[1] >= 30) iconColor = 'orangered';
if (temp[1] >= 40) iconColor = 'red';
var iconSize = (map.getZoom() - 5 ) * 3;
var options = { iconShape: 'circle-dot', borderWidth: iconSize, borderColor: iconColor };
var marker = L.marker([dataPoint.latitude, dataPoint.longitude], { icon: L.BeautifyIcon.icon(options) })
.bindPopup("<b>" + temp[0] + "</b>");
marker.addTo(backend.layer);
}
}
}catch(err){
console.log("Error with this dataPoint [" + dataPoint + "]. Error="+ err.message);
}
}
// Set a timeout to load/unload backends
setTimeout(function(){
// Get notified of registrations/unregistrations
}, 5000);
map.whenReady(initialLoad).whenReady(connect);
map.on('moveend', queryWithinAll);
map.on('moveend', function() {
console.log("map was panned!");
console.log("zoom: " + map.getZoom()); // prints out zoom level
console.log("center: " + map.getCenter()); // prints out map center
});
map.on('overlayadd', onOverlayAdd).on('overlayremove', onOverlayRemove);
function onOverlayAdd(e){
backends.forEach(function (backend,backendId,thisMap) {
if (backend.displayName==e.name){
// Execute the actions
backend.visible = true;
}
});
}
function onOverlayRemove(e){
backends.forEach(function (backend,backendId,thisMap) {
if (backend.displayName==e.name){
// Execute the actions
backend.visible = false;
}
});
}
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,79 @@
/**
* Copyright (c) 2011-2014 Felix Gnass
* Licensed under the MIT license
* http://spin.js.org/
*/
/*
Basic Usage:
============
$('#el').spin() // Creates a default Spinner using the text color of #el.
$('#el').spin({ ... }) // Creates a Spinner using the provided options.
$('#el').spin(false) // Stops and removes the spinner.
Using Presets:
==============
$('#el').spin('small') // Creates a 'small' Spinner using the text color of #el.
$('#el').spin('large', '#fff') // Creates a 'large' white Spinner.
Adding a custom preset:
=======================
$.fn.spin.presets.flower = {
lines: 9
, length: 10
, width: 20
, radius: 0
}
$('#el').spin('flower', 'red')
*/
;(function(factory) {
if (typeof exports == 'object') {
// CommonJS
factory(require('jquery'), require('spin.js'))
} else if (typeof define == 'function' && define.amd) {
// AMD, register as anonymous module
define(['jquery', 'spin'], factory)
} else {
// Browser globals
if (!window.Spinner) throw new Error('Spin.js not present')
factory(window.jQuery, window.Spinner)
}
}(function($, Spinner) {
$.fn.spin = function(opts, color) {
return this.each(function() {
var $this = $(this)
, data = $this.data()
if (data.spinner) {
data.spinner.stop()
delete data.spinner
}
if (opts !== false) {
opts = $.extend(
{ color: color || $this.css('color') }
, $.fn.spin.presets[opts] || opts
)
data.spinner = new Spinner(opts).spin(this)
}
})
}
$.fn.spin.presets = {
tiny: { lines: 8, length: 2, width: 2, radius: 3 }
, small: { lines: 8, length: 4, width: 3, radius: 5 }
, large: { lines: 10, length: 8, width: 4, radius: 8 }
}
}));

View File

@ -0,0 +1,27 @@
.leaflet-marker-photo {
border: 2px solid #fff;
box-shadow: 3px 3px 10px #888;
}
.leaflet-marker-photo div {
width: 100%;
height: 100%;
background-size: cover;
background-position: center center;
background-repeat: no-repeat;
}
.leaflet-marker-photo b {
position: absolute;
top: -7px;
right: -11px;
color: #555;
background-color: #fff;
border-radius: 8px;
height: 12px;
min-width: 12px;
line-height: 12px;
text-align: center;
padding: 3px;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}

View File

@ -0,0 +1,83 @@
L.Photo = L.FeatureGroup.extend({
options: {
icon: {
iconSize: [40, 40]
}
},
initialize: function (photos, options) {
L.setOptions(this, options);
L.FeatureGroup.prototype.initialize.call(this, photos);
},
addLayers: function (photos) {
if (photos) {
for (var i = 0, len = photos.length; i < len; i++) {
this.addLayer(photos[i]);
}
}
return this;
},
addLayer: function (photo) {
L.FeatureGroup.prototype.addLayer.call(this, this.createMarker(photo));
},
createMarker: function (photo) {
var marker = L.marker(photo, {
icon: L.divIcon(L.extend({
html: '<div style="background-image: url(' + photo.thumbnail + ');"></div>',
className: 'leaflet-marker-photo'
}, photo, this.options.icon)),
title: photo.caption || ''
});
marker.photo = photo;
return marker;
}
});
L.photo = function (photos, options) {
return new L.Photo(photos, options);
};
if (L.MarkerClusterGroup) {
L.Photo.Cluster = L.MarkerClusterGroup.extend({
options: {
featureGroup: L.photo,
maxClusterRadius: 100,
showCoverageOnHover: false,
iconCreateFunction: function(cluster) {
return new L.DivIcon(L.extend({
className: 'leaflet-marker-photo',
html: '<div style="background-image: url(' + cluster.getAllChildMarkers()[0].photo.thumbnail + ');"></div><b>' + cluster.getChildCount() + '</b>'
}, this.icon));
},
icon: {
iconSize: [40, 40]
}
},
initialize: function (options) {
options = L.Util.setOptions(this, options);
L.MarkerClusterGroup.prototype.initialize.call(this);
this._photos = options.featureGroup(null, options);
},
add: function (photos) {
this.addLayer(this._photos.addLayers(photos));
return this;
},
clear: function () {
this._photos.clearLayers();
this.clearLayers();
}
});
L.photo.cluster = function (options) {
return new L.Photo.Cluster(options);
};
}

View File

@ -0,0 +1,27 @@
/*
Leaflet.BeautifyIcon, a plugin that adds colorful iconic markers for Leaflet by giving full control of style to end user, It has also ability to adjust font awesome
and glyphicon icons
(c) 2016-2017, Muhammad Arslan Sajid
http://leafletjs.com
*/
.beautify-marker {
text-align: center;
font-weight: 700;
font-family: monospace;
position:absolute;
}
.beautify-marker.circle {
border-radius: 100%;
}
.beautify-marker.circle-dot, .beautify-marker.doughnut {
border-radius: 100%;
}
.beautify-marker.marker {
border-top-left-radius: 50%;
border-top-right-radius: 50% 100%;
border-bottom-left-radius: 100% 50%;
border-bottom-right-radius: 0%;
}

View File

@ -0,0 +1,198 @@
/*
Leaflet.BeautifyIcon, a plugin that adds colorful iconic markers for Leaflet by giving full control of style to end user, It has also ability to adjust font awesome
and glyphicon icons
(c) 2016-2017, Muhammad Arslan Sajid
http://leafletjs.com
*/
/*global L of leaflet*/
(function (window, document, undefined) {
'use strict';
/*
* Leaflet.BeautifyIcon assumes that you have already included the Leaflet library.
*/
/*
* Default settings for various style markers
*/
var defaults = {
iconColor: '#1EB300',
iconAnchor: {
'marker': [14, 36]
, 'circle': [11, 10]
, 'circle-dot': [5, 5]
, 'rectangle-dot': [5, 6]
, 'doughnut': [8, 8]
},
popupAnchor: {
'marker': [0, -25]
, 'circle': [-3, -76]
, 'circle-dot': [0, -2]
, 'rectangle-dot': [0, -2]
, 'doughnut': [0, -2]
},
innerIconAnchor: {
'marker': [-2, 5]
, 'circle': [0, 2]
},
iconSize: {
'marker': [28, 28]
, 'circle': [22, 22]
, 'circle-dot': [2, 2]
, 'rectangle-dot': [2, 2]
, 'doughnut': [15, 15]
}
};
L.BeautifyIcon = {
Icon: L.Icon.extend({
options: {
icon: 'leaf'
, iconSize: defaults.iconSize.circle
, iconAnchor: defaults.iconAnchor.circle
, iconShape: 'circle'
, iconStyle: ''
, innerIconAnchor: [0, 3] // circle with fa or glyphicon or marker with text
, innerIconStyle: ''
, isAlphaNumericIcon: false
, text: 1
, borderColor: defaults.iconColor
, borderWidth: 2
, borderStyle: 'solid'
, backgroundColor: 'white'
, textColor: defaults.iconColor
, customClasses: ''
, spin: false
, prefix: 'fa'
, html: ''
},
initialize: function (options) {
this.applyDefaults(options);
this.options = L.Util.setOptions(this, options);
},
applyDefaults: function (options) {
if (options) {
if (!options.iconSize && options.iconShape) {
options.iconSize = defaults.iconSize[options.iconShape];
}
if (!options.iconAnchor && options.iconShape) {
options.iconAnchor = defaults.iconAnchor[options.iconShape];
}
if (!options.popupAnchor && options.iconShape) {
options.popupAnchor = defaults.popupAnchor[options.iconShape];
}
if (!options.innerIconAnchor) {
// if icon is type of circle or marker
if (options.iconShape === 'circle' || options.iconShape === 'marker') {
if (options.iconShape === 'circle' && options.isAlphaNumericIcon) { // if circle with text
options.innerIconAnchor = [0, -1];
}
else if (options.iconShape === 'marker' && !options.isAlphaNumericIcon) {// marker with icon
options.innerIconAnchor = defaults.innerIconAnchor[options.iconShape];
}
}
}
}
},
createIcon: function () {
var iconDiv = document.createElement('div')
, options = this.options;
iconDiv.innerHTML = !options.html ? this.createIconInnerHtml() : options.html;
this._setIconStyles(iconDiv);
return iconDiv;
},
createIconInnerHtml: function () {
var options = this.options;
if (options.iconShape === 'circle-dot' || options.iconShape === 'rectangle-dot' || options.iconShape === 'doughnut') {
return '';
}
var innerIconStyle = this.getInnerIconStyle(options);
if (options.isAlphaNumericIcon) {
return '<div style="' + innerIconStyle + '">' + options.text + '</div>';
}
var spinClass = '';
if (options.spin) {
spinClass = ' fa-spin';
}
return '<i class="' + options.prefix + ' ' + options.prefix + '-' + options.icon + spinClass + '" style="' + innerIconStyle + '"></i>';
},
getInnerIconStyle: function (options) {
var innerAnchor = L.point(options.innerIconAnchor)
return 'color:' + options.textColor + ';margin-top:' + innerAnchor.y + 'px; margin-left:' + innerAnchor.x + 'px;' + options.innerIconStyle;
},
_setIconStyles: function (iconDiv) {
var options = this.options
, size = L.point(options.iconSize)
, anchor = L.point(options.iconAnchor);
iconDiv.className = 'beautify-marker ';
if (options.iconShape) {
iconDiv.className += options.iconShape;
}
if (options.customClasses) {
iconDiv.className += ' ' + options.customClasses;
}
iconDiv.style.backgroundColor = options.backgroundColor;
iconDiv.style.color = options.textColor;
iconDiv.style.borderColor = options.borderColor;
iconDiv.style.borderWidth = options.borderWidth + 'px';
iconDiv.style.borderStyle = options.borderStyle;
if (size) {
iconDiv.style.width = size.x + 'px';
iconDiv.style.height = size.y + 'px';
}
if (anchor) {
iconDiv.style.marginLeft = (-anchor.x) + 'px';
iconDiv.style.marginTop = (-anchor.y) + 'px';
}
if (options.iconStyle) {
var cStyle = iconDiv.getAttribute('style');
cStyle += options.iconStyle;
iconDiv.setAttribute('style', cStyle);
}
}
})
};
L.BeautifyIcon.icon = function (options) {
return new L.BeautifyIcon.Icon(options);
}
}(this, document));

View File

@ -0,0 +1,58 @@
/*
Leaflet.BeautifyMarker, a plugin that adds colorful iconic markers for Leaflet by giving full control of style to end user, It has also ability to adjust font awesome
and glyphicon icons
(c) 2016-2017, Muhammad Arslan Sajid
http://leafletjs.com
*/
/*
* Leaflet.BeautifyIcon assumes that you have already included the Leaflet library.
*/
L.BeautifyMarker = L.Marker.extend({
registerMapZoomendEvent: false,
options: {
icon: L.BeautifyIcon.icon()
},
onAdd: function (map) {
L.Marker.prototype.onAdd.call(this, map);
if (this.options && this.options.icon && this.options.icon.options && this.options.icon.options.iconShape === 'marker') {
var icon = this._icon;
this._updateMarkerShapeIcon(icon);
if (!this.registerMapZoomendEvent) {
L.DomEvent.addListener(map, 'zoomend', this._onMapZoomChange, this);
L.DomEvent.addListener(this, 'drag', this._onMarkerDrag, this);
this.registerMapZoomendEvent = true;
}
}
},
_onMarkerDrag:function(){
this._updateMarkerShapeIcon(this._icon);
},
_onMapZoomChange: function () {
var needToRotateIcons = document.getElementsByClassName('marker');
for (var i = 0, length = needToRotateIcons.length; i < length; i++) {
var icon = needToRotateIcons[i];
this._updateMarkerShapeIcon(icon);
}
},
_updateMarkerShapeIcon: function (icon) {
var transform = icon.style.transform;
icon.style.transform = transform + ' rotate(45deg)';
icon.firstChild.style.transform = 'rotate(315deg)';
}
});
L.BeautifyMarker.marker = function (latlng, options) {
return new L.BeautifyMarker(latlng, options);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,175 @@
/*
* MIT Copyright 2016 Ursudio <info@ursudio.com>
* http://www.ursudio.com/
* Please attribute Ursudio in any production associated with this JavaScript plugin.
*/
L.WebGLHeatMap = L.Renderer.extend({
// tested on Leaflet 1.0.1
version : '0.2.0',
options: {
// @option size: Number
// corresponds with units below
size: 30000,
// @option units: String
// 'm' for meters or 'px' for pixels
units : 'm',
opacity: 1,
gradientTexture: false,
alphaRange: 1,
// @option padding:
// don't add padding (0 helps with zoomanim)
padding: 0
},
_initContainer : function () {
var container = this._container = document.createElement('canvas'),
options = this.options;
container.id = 'webgl-leaflet-' + L.Util.stamp(this);
container.style.opacity = options.opacity;
container.style.position = 'absolute';
this.gl = window.createWebGLHeatmap({
canvas: container,
gradientTexture: options.gradientTexture,
alphaRange: [0, options.alphaRange]
});
this._container = container;
},
onAdd: function () {
L.Renderer.prototype.onAdd.call(this);
this.resize();
},
// events
getEvents : function () {
var events = L.Renderer.prototype.getEvents.call(this);
L.Util.extend(events, {
resize : this.resize,
move : L.Util.throttle(this._update, 49, this)
});
return events;
},
resize: function () {
var canvas = this._container,
size = this._map.getSize();
canvas.width = size.x;
canvas.height = size.y;
this.gl.adjustSize();
this.draw();
},
reposition: function () {
// canvas moves opposite to map pane's position
var pos = this._map
._getMapPanePos()
.multiplyBy(-1);
L.DomUtil.setPosition(this._container, pos);
},
_update : function () {
L.Renderer.prototype._update.call(this);
this.draw();
},
// draw function
draw : function () {
var map = this._map,
heatmap = this.gl,
dataLen = this.data.length,
floor = Math.floor,
scaleFn = this['_scale' + this.options.units];
if (!map) return;
heatmap.clear();
this.reposition();
if (dataLen) {
for (var i = 0; i < dataLen; i++) {
var dataVal = this.data[i],
latlng = L.latLng( dataVal ),
point = map.latLngToContainerPoint( latlng );
heatmap.addPoint(
floor(point.x),
floor(point.y),
scaleFn(this, latlng),
dataVal[2]
);
}
heatmap.update();
if (this._multiply) {
heatmap.multiply( this._multiply );
heatmap.update();
}
}
heatmap.display();
},
// scale methods
_scalem : function (ref, latlng) {
// necessary to maintain accurately sized circles
// to change scale to miles (for example), you will need to convert 40075017 (equatorial circumference of the Earth in metres) to miles
var map = ref._map;
var lngRadius = (ref.options.size / 40075017) *
360 / Math.cos((Math.PI / 180) * latlng.lat),
latlng2 = new L.LatLng(latlng.lat, latlng.lng - lngRadius),
point = map.latLngToLayerPoint(latlng),
point2 = map.latLngToLayerPoint(latlng2);
return Math.max(Math.round(point.x - point2.x), 1);
},
_scalepx: function (ref, latlng) {
return ref.options.size;
},
// data handling methods
data : [],
addDataPoint: function (lat, lon, value) {
this.data.push( [ lat, lon, value / 100 ] );
},
setData: function (dataset) {
// format: [[lat, lon, intensity],...]
this.data = dataset;
this._multiply = null;
this.draw();
},
clear: function () {
this.setData([]);
},
// affects original points
multiply: function (n) {
this._multiply = n;
this.draw();
}
});
L.webGLHeatmap = function ( options ) {
return new L.WebGLHeatMap( options );
};

View File

@ -0,0 +1 @@
L.WebGLHeatMap=L.Renderer.extend({version:"0.2.0",options:{size:3e4,units:"m",opacity:1,gradientTexture:!1,alphaRange:1,padding:0},_initContainer:function(){var t=this._container=document.createElement("canvas"),i=this.options;t.id="webgl-leaflet-"+L.Util.stamp(this),t.style.opacity=i.opacity,t.style.position="absolute",this.gl=window.createWebGLHeatmap({canvas:t,gradientTexture:i.gradientTexture,alphaRange:[0,i.alphaRange]}),this._container=t},onAdd:function(){L.Renderer.prototype.onAdd.call(this),this.resize()},getEvents:function(){var t=L.Renderer.prototype.getEvents.call(this);return L.Util.extend(t,{resize:this.resize,move:L.Util.throttle(this._update,49,this)}),t},resize:function(){var t=this._container,i=this._map.getSize();t.width=i.x,t.height=i.y,this.gl.adjustSize(),this.draw()},reposition:function(){var t=this._map._getMapPanePos().multiplyBy(-1);L.DomUtil.setPosition(this._container,t)},_update:function(){L.Renderer.prototype._update.call(this),this.draw()},draw:function(){var t=this._map,i=this.gl,e=this.data.length,a=Math.floor,n=this["_scale"+this.options.units];if(t){if(i.clear(),this.reposition(),e){for(var s=0;s<e;s++){var o=this.data[s],r=L.latLng(o),l=t.latLngToContainerPoint(r);i.addPoint(a(l.x),a(l.y),n(r),o[2])}i.update(),this._multiply&&(i.multiply(this._multiply),i.update())}i.display()}},_scalem:function(t){var i=this._map,e=this.options.size/40075017*360/Math.cos(L.LatLng.DEG_TO_RAD*t.lat),a=new L.LatLng(t.lat,t.lng-e),n=i.latLngToLayerPoint(t),s=i.latLngToLayerPoint(a);return Math.max(Math.round(n.x-s.x),1)},_scalepx:function(t){return options.size},data:[],addDataPoint:function(t,i,e){this.data.push([t,i,e/100])},setData:function(t){this.data=t,this._multiply=null,this.draw()},clear:function(){this.setData([])},multiply:function(t){this._multiply=t,this.draw()}}),L.webGLHeatmap=function(t){return new L.WebGLHeatMap(t)};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

View File

@ -0,0 +1,623 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-map-pane svg,
.leaflet-map-pane canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg,
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer {
max-width: none !important;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-drag {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-tile {
will-change: opacity;
}
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
-o-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
-o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
-o-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline: 0;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-container a.leaflet-active {
outline: 2px solid orange;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a,
.leaflet-bar a:hover {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-control-zoom-out {
font-size: 20px;
}
.leaflet-touch .leaflet-control-zoom-in {
font-size: 22px;
}
.leaflet-touch .leaflet-control-zoom-out {
font-size: 24px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path {
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.7);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover {
text-decoration: underline;
}
.leaflet-container .leaflet-control-attribution,
.leaflet-container .leaflet-control-scale {
font-size: 11px;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
font-size: 11px;
white-space: nowrap;
overflow: hidden;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: #fff;
background: rgba(255, 255, 255, 0.5);
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 19px;
line-height: 1.4;
}
.leaflet-popup-content p {
margin: 18px 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
padding: 4px 4px 0 0;
border: none;
text-align: center;
width: 18px;
height: 14px;
font: 16px/14px Tahoma, Verdana, sans-serif;
color: #c3c3c3;
text-decoration: none;
font-weight: bold;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover {
color: #999;
}
.leaflet-popup-scrolled {
overflow: auto;
border-bottom: 1px solid #ddd;
border-top: 1px solid #ddd;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-popup-tip-container {
margin-top: -1px;
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-clickable {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,60 @@
.marker-cluster-small {
background-color: rgba(181, 226, 140, 0.6);
}
.marker-cluster-small div {
background-color: rgba(110, 204, 57, 0.6);
}
.marker-cluster-medium {
background-color: rgba(241, 211, 87, 0.6);
}
.marker-cluster-medium div {
background-color: rgba(240, 194, 12, 0.6);
}
.marker-cluster-large {
background-color: rgba(253, 156, 115, 0.6);
}
.marker-cluster-large div {
background-color: rgba(241, 128, 23, 0.6);
}
/* IE 6-8 fallback colors */
.leaflet-oldie .marker-cluster-small {
background-color: rgb(181, 226, 140);
}
.leaflet-oldie .marker-cluster-small div {
background-color: rgb(110, 204, 57);
}
.leaflet-oldie .marker-cluster-medium {
background-color: rgb(241, 211, 87);
}
.leaflet-oldie .marker-cluster-medium div {
background-color: rgb(240, 194, 12);
}
.leaflet-oldie .marker-cluster-large {
background-color: rgb(253, 156, 115);
}
.leaflet-oldie .marker-cluster-large div {
background-color: rgb(241, 128, 23);
}
.marker-cluster {
background-clip: padding-box;
border-radius: 20px;
}
.marker-cluster div {
width: 30px;
height: 30px;
margin-left: 5px;
margin-top: 5px;
text-align: center;
border-radius: 15px;
font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
}
.marker-cluster span {
line-height: 30px;
}

View File

@ -0,0 +1,14 @@
.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
-webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
-moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
-o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in;
transition: transform 0.3s ease-out, opacity 0.3s ease-in;
}
.leaflet-cluster-spider-leg {
/* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */
-webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in;
-moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in;
-o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in;
transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in;
}

View File

@ -0,0 +1,6 @@
.leaflet-control-messagebox {
display: none; /* Initially hidden */
border: 2px solid red;
background-color: white;
padding: 3px 10px;
}

View File

@ -0,0 +1,42 @@
L.Control.Messagebox = L.Control.extend({
options: {
position: 'topright',
timeout: 3000
},
onAdd: function (map) {
this._container = L.DomUtil.create('div', 'leaflet-control-messagebox');
//L.DomEvent.disableClickPropagation(this._container);
return this._container;
},
show: function (message, timeout) {
var elem = this._container;
elem.innerHTML = message;
elem.style.display = 'block';
timeout = timeout || this.options.timeout;
if (typeof this.timeoutID == 'number') {
clearTimeout(this.timeoutID);
}
this.timeoutID = setTimeout(function () {
elem.style.display = 'none';
}, timeout);
}
});
L.Map.mergeOptions({
messagebox: false
});
L.Map.addInitHook(function () {
if (this.options.messagebox) {
this.messagebox = new L.Control.Messagebox();
this.addControl(this.messagebox);
}
});
L.control.messagebox = function (options) {
return new L.Control.Messagebox(options);
};

View File

@ -0,0 +1,373 @@
/**
* Copyright (c) 2011-2014 Felix Gnass
* Licensed under the MIT license
* http://spin.js.org/
*
* Example:
var opts = {
lines: 12, // The number of lines to draw
length: 7, // The length of each line
width: 5, // The line thickness
radius: 10, // The radius of the inner circle
scale: 1.0, // Scales overall size of the spinner
corners: 1, // Roundness (0..1)
color: '#000', // #rgb or #rrggbb
opacity: 1/4, // Opacity of the lines
rotate: 0, // Rotation offset
direction: 1, // 1: clockwise, -1: counterclockwise
speed: 1, // Rounds per second
trail: 100, // Afterglow percentage
fps: 20, // Frames per second when using setTimeout()
zIndex: 2e9, // Use a high z-index by default
className: 'spinner', // CSS class to assign to the element
top: '50%', // center vertically
left: '50%', // center horizontally
shadow: false, // Whether to render a shadow
hwaccel: false, // Whether to use hardware acceleration (might be buggy)
position: 'absolute' // Element positioning
};
var target = document.getElementById('foo');
var spinner = new Spinner(opts).spin(target);
*/
;(function(root, factory) {
if (typeof module == 'object' && module.exports) module.exports = factory(); // CommonJS
else if (typeof define == 'function' && define.amd) define(factory); // AMD module
else root.Spinner = factory(); // Browser global
}
(this, function() {
'use strict';
var prefixes = ['webkit', 'Moz', 'ms', 'O']; // Vendor prefixes
var animations = {}; // Animation rules keyed by their name
var useCssAnimations; // Whether to use CSS animations or setTimeout
var sheet; // A stylesheet to hold the @keyframe or VML rules
/**
* Utility function to create elements. If no tag name is given,
* a DIV is created. Optionally properties can be passed.
*/
function createEl(tag, prop) {
var el = document.createElement(tag || 'div');
var n;
for (n in prop) el[n] = prop[n];
return el;
}
/**
* Appends children and returns the parent.
*/
function ins(parent /* child1, child2, ...*/) {
for (var i = 1, n = arguments.length; i < n; i++) {
parent.appendChild(arguments[i]);
}
return parent;
}
/**
* Creates an opacity keyframe animation rule and returns its name.
* Since most mobile Webkits have timing issues with animation-delay,
* we create separate rules for each line/segment.
*/
function addAnimation(alpha, trail, i, lines) {
var name = ['opacity', trail, ~~(alpha * 100), i, lines].join('-');
var start = 0.01 + i/lines * 100;
var z = Math.max(1 - (1-alpha) / trail * (100-start), alpha);
var prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase();
var pre = prefix && '-' + prefix + '-' || '';
if (!animations[name]) {
sheet.insertRule(
'@' + pre + 'keyframes ' + name + '{' +
'0%{opacity:' + z + '}' +
start + '%{opacity:' + alpha + '}' +
(start+0.01) + '%{opacity:1}' +
(start+trail) % 100 + '%{opacity:' + alpha + '}' +
'100%{opacity:' + z + '}' +
'}', sheet.cssRules.length);
animations[name] = 1;
}
return name;
}
/**
* Tries various vendor prefixes and returns the first supported property.
*/
function vendor(el, prop) {
var s = el.style;
var pp;
var i;
prop = prop.charAt(0).toUpperCase() + prop.slice(1);
if (s[prop] !== undefined) return prop;
for (i = 0; i < prefixes.length; i++) {
pp = prefixes[i]+prop;
if (s[pp] !== undefined) return pp;
}
}
/**
* Sets multiple style properties at once.
*/
function css(el, prop) {
for (var n in prop) {
el.style[vendor(el, n) || n] = prop[n];
}
return el;
}
/**
* Fills in default values.
*/
function merge(obj) {
for (var i = 1; i < arguments.length; i++) {
var def = arguments[i];
for (var n in def) {
if (obj[n] === undefined) obj[n] = def[n];
}
}
return obj;
}
/**
* Returns the line color from the given string or array.
*/
function getColor(color, idx) {
return typeof color == 'string' ? color : color[idx % color.length];
}
// Built-in defaults
var defaults = {
lines: 12, // The number of lines to draw
length: 7, // The length of each line
width: 5, // The line thickness
radius: 10, // The radius of the inner circle
scale: 1.0, // Scales overall size of the spinner
corners: 1, // Roundness (0..1)
color: '#000', // #rgb or #rrggbb
opacity: 1/4, // Opacity of the lines
rotate: 0, // Rotation offset
direction: 1, // 1: clockwise, -1: counterclockwise
speed: 1, // Rounds per second
trail: 100, // Afterglow percentage
fps: 20, // Frames per second when using setTimeout()
zIndex: 2e9, // Use a high z-index by default
className: 'spinner', // CSS class to assign to the element
top: '50%', // center vertically
left: '50%', // center horizontally
shadow: false, // Whether to render a shadow
hwaccel: false, // Whether to use hardware acceleration
position: 'absolute' // Element positioning
};
/** The constructor */
function Spinner(o) {
this.opts = merge(o || {}, Spinner.defaults, defaults);
}
// Global defaults that override the built-ins:
Spinner.defaults = {};
merge(Spinner.prototype, {
/**
* Adds the spinner to the given target element. If this instance is already
* spinning, it is automatically removed from its previous target b calling
* stop() internally.
*/
spin: function(target) {
this.stop();
var self = this;
var o = self.opts;
var el = self.el = createEl(null, {className: o.className});
css(el, {
position: o.position,
width: 0,
zIndex: o.zIndex,
left: o.left,
top: o.top
});
if (target) {
target.insertBefore(el, target.firstChild || null);
}
el.setAttribute('role', 'progressbar');
self.lines(el, self.opts);
if (!useCssAnimations) {
// No CSS animation support, use setTimeout() instead
var i = 0;
var start = (o.lines - 1) * (1 - o.direction) / 2;
var alpha;
var fps = o.fps;
var f = fps / o.speed;
var ostep = (1 - o.opacity) / (f * o.trail / 100);
var astep = f / o.lines;
(function anim() {
i++;
for (var j = 0; j < o.lines; j++) {
alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity);
self.opacity(el, j * o.direction + start, alpha, o);
}
self.timeout = self.el && setTimeout(anim, ~~(1000 / fps));
})();
}
return self;
},
/**
* Stops and removes the Spinner.
*/
stop: function() {
var el = this.el;
if (el) {
clearTimeout(this.timeout);
if (el.parentNode) el.parentNode.removeChild(el);
this.el = undefined;
}
return this;
},
/**
* Internal method that draws the individual lines. Will be overwritten
* in VML fallback mode below.
*/
lines: function(el, o) {
var i = 0;
var start = (o.lines - 1) * (1 - o.direction) / 2;
var seg;
function fill(color, shadow) {
return css(createEl(), {
position: 'absolute',
width: o.scale * (o.length + o.width) + 'px',
height: o.scale * o.width + 'px',
background: color,
boxShadow: shadow,
transformOrigin: 'left',
transform: 'rotate(' + ~~(360/o.lines*i + o.rotate) + 'deg) translate(' + o.scale*o.radius + 'px' + ',0)',
borderRadius: (o.corners * o.scale * o.width >> 1) + 'px'
});
}
for (; i < o.lines; i++) {
seg = css(createEl(), {
position: 'absolute',
top: 1 + ~(o.scale * o.width / 2) + 'px',
transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
opacity: o.opacity,
animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1 / o.speed + 's linear infinite'
});
if (o.shadow) ins(seg, css(fill('#000', '0 0 4px #000'), {top: '2px'}));
ins(el, ins(seg, fill(getColor(o.color, i), '0 0 1px rgba(0,0,0,.1)')));
}
return el;
},
/**
* Internal method that adjusts the opacity of a single line.
* Will be overwritten in VML fallback mode below.
*/
opacity: function(el, i, val) {
if (i < el.childNodes.length) el.childNodes[i].style.opacity = val;
}
});
function initVML() {
/* Utility function to create a VML tag */
function vml(tag, attr) {
return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr);
}
// No CSS transforms but VML support, add a CSS rule for VML elements:
sheet.addRule('.spin-vml', 'behavior:url(#default#VML)');
Spinner.prototype.lines = function(el, o) {
var r = o.scale * (o.length + o.width);
var s = o.scale * 2 * r;
function grp() {
return css(
vml('group', {
coordsize: s + ' ' + s,
coordorigin: -r + ' ' + -r
}),
{ width: s, height: s }
);
}
var margin = -(o.width + o.length) * o.scale * 2 + 'px';
var g = css(grp(), {position: 'absolute', top: margin, left: margin});
var i;
function seg(i, dx, filter) {
ins(
g,
ins(
css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
ins(
css(
vml('roundrect', {arcsize: o.corners}),
{
width: r,
height: o.scale * o.width,
left: o.scale * o.radius,
top: -o.scale * o.width >> 1,
filter: filter
}
),
vml('fill', {color: getColor(o.color, i), opacity: o.opacity}),
vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
)
)
);
}
if (o.shadow)
for (i = 1; i <= o.lines; i++) {
seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)');
}
for (i = 1; i <= o.lines; i++) seg(i);
return ins(el, g);
};
Spinner.prototype.opacity = function(el, i, val, o) {
var c = el.firstChild;
o = o.shadow && o.lines || 0;
if (c && i + o < c.childNodes.length) {
c = c.childNodes[i + o]; c = c && c.firstChild; c = c && c.firstChild;
if (c) c.opacity = val;
}
};
}
if (typeof document !== 'undefined') {
sheet = (function() {
var el = createEl('style', {type : 'text/css'});
ins(document.getElementsByTagName('head')[0], el);
return el.sheet || el.styleSheet;
}());
var probe = css(createEl('group'), {behavior: 'url(#default#VML)'});
if (!vendor(probe, 'transform') && probe.adj) initVML();
else useCssAnimations = vendor(probe, 'animation');
}
return Spinner;
}));

View File

@ -0,0 +1,24 @@
.div-icon-less0 {
background: royalblue;
border: 1px solid royalblue;
}
.div-icon-0-10 {
background: skyblue;
border: 1px solid skyblue;
}
.div-icon-10-20 {
background: yellow;
border: 1px solid yellow;
}
.div-icon-20-30 {
background: orange;
border: 1px solid orange;
}
.div-icon-30-40 {
background: orangered;
border: 1px solid orangered;
}
.div-icon-more40 {
background: red;
border: 1px solid red;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,501 @@
// Generated by CoffeeScript 1.7.1
/*
Stomp Over WebSocket http://www.jmesnil.net/stomp-websocket/doc/ | Apache License V2.0
Copyright (C) 2010-2013 [Jeff Mesnil](http://jmesnil.net/)
Copyright (C) 2012 [FuseSource, Inc.](http://fusesource.com)
*/
(function() {
var Byte, Client, Frame, Stomp,
__hasProp = {}.hasOwnProperty,
__slice = [].slice;
Byte = {
LF: '\x0A',
NULL: '\x00'
};
Frame = (function() {
var unmarshallSingle;
function Frame(command, headers, body) {
this.command = command;
this.headers = headers != null ? headers : {};
this.body = body != null ? body : '';
}
Frame.prototype.toString = function() {
var lines, name, skipContentLength, value, _ref;
lines = [this.command];
skipContentLength = this.headers['content-length'] === false ? true : false;
if (skipContentLength) {
delete this.headers['content-length'];
}
_ref = this.headers;
for (name in _ref) {
if (!__hasProp.call(_ref, name)) continue;
value = _ref[name];
lines.push("" + name + ":" + value);
}
if (this.body && !skipContentLength) {
lines.push("content-length:" + (Frame.sizeOfUTF8(this.body)));
}
lines.push(Byte.LF + this.body);
return lines.join(Byte.LF);
};
Frame.sizeOfUTF8 = function(s) {
if (s) {
return encodeURI(s).match(/%..|./g).length;
} else {
return 0;
}
};
unmarshallSingle = function(data) {
var body, chr, command, divider, headerLines, headers, i, idx, len, line, start, trim, _i, _j, _len, _ref, _ref1;
divider = data.search(RegExp("" + Byte.LF + Byte.LF));
headerLines = data.substring(0, divider).split(Byte.LF);
command = headerLines.shift();
headers = {};
trim = function(str) {
return str.replace(/^\s+|\s+$/g, '');
};
_ref = headerLines.reverse();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
line = _ref[_i];
idx = line.indexOf(':');
headers[trim(line.substring(0, idx))] = trim(line.substring(idx + 1));
}
body = '';
start = divider + 2;
if (headers['content-length']) {
len = parseInt(headers['content-length']);
body = ('' + data).substring(start, start + len);
} else {
chr = null;
for (i = _j = start, _ref1 = data.length; start <= _ref1 ? _j < _ref1 : _j > _ref1; i = start <= _ref1 ? ++_j : --_j) {
chr = data.charAt(i);
if (chr === Byte.NULL) {
break;
}
body += chr;
}
}
return new Frame(command, headers, body);
};
Frame.unmarshall = function(datas) {
var frame, frames, last_frame, r;
frames = datas.split(RegExp("" + Byte.NULL + Byte.LF + "*"));
r = {
frames: [],
partial: ''
};
r.frames = (function() {
var _i, _len, _ref, _results;
_ref = frames.slice(0, -1);
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
frame = _ref[_i];
_results.push(unmarshallSingle(frame));
}
return _results;
})();
last_frame = frames.slice(-1)[0];
if (last_frame === Byte.LF || (last_frame.search(RegExp("" + Byte.NULL + Byte.LF + "*$"))) !== -1) {
r.frames.push(unmarshallSingle(last_frame));
} else {
r.partial = last_frame;
}
return r;
};
Frame.marshall = function(command, headers, body) {
var frame;
frame = new Frame(command, headers, body);
return frame.toString() + Byte.NULL;
};
return Frame;
})();
Client = (function() {
var now;
function Client(ws) {
this.ws = ws;
this.ws.binaryType = "arraybuffer";
this.counter = 0;
this.connected = false;
this.heartbeat = {
outgoing: 10000,
incoming: 10000
};
this.maxWebSocketFrameSize = 16 * 1024;
this.subscriptions = {};
this.partialData = '';
}
Client.prototype.debug = function(message) {
var _ref;
return typeof window !== "undefined" && window !== null ? (_ref = window.console) != null ? _ref.log(message) : void 0 : void 0;
};
now = function() {
if (Date.now) {
return Date.now();
} else {
return new Date().valueOf;
}
};
Client.prototype._transmit = function(command, headers, body) {
var out;
out = Frame.marshall(command, headers, body);
if (typeof this.debug === "function") {
this.debug(">>> " + out);
}
while (true) {
if (out.length > this.maxWebSocketFrameSize) {
this.ws.send(out.substring(0, this.maxWebSocketFrameSize));
out = out.substring(this.maxWebSocketFrameSize);
if (typeof this.debug === "function") {
this.debug("remaining = " + out.length);
}
} else {
return this.ws.send(out);
}
}
};
Client.prototype._setupHeartbeat = function(headers) {
var serverIncoming, serverOutgoing, ttl, v, _ref, _ref1;
if ((_ref = headers.version) !== Stomp.VERSIONS.V1_1 && _ref !== Stomp.VERSIONS.V1_2) {
return;
}
_ref1 = (function() {
var _i, _len, _ref1, _results;
_ref1 = headers['heart-beat'].split(",");
_results = [];
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
v = _ref1[_i];
_results.push(parseInt(v));
}
return _results;
})(), serverOutgoing = _ref1[0], serverIncoming = _ref1[1];
if (!(this.heartbeat.outgoing === 0 || serverIncoming === 0)) {
ttl = Math.max(this.heartbeat.outgoing, serverIncoming);
if (typeof this.debug === "function") {
this.debug("send PING every " + ttl + "ms");
}
this.pinger = Stomp.setInterval(ttl, (function(_this) {
return function() {
_this.ws.send(Byte.LF);
return typeof _this.debug === "function" ? _this.debug(">>> PING") : void 0;
};
})(this));
}
if (!(this.heartbeat.incoming === 0 || serverOutgoing === 0)) {
ttl = Math.max(this.heartbeat.incoming, serverOutgoing);
if (typeof this.debug === "function") {
this.debug("check PONG every " + ttl + "ms");
}
return this.ponger = Stomp.setInterval(ttl, (function(_this) {
return function() {
var delta;
delta = now() - _this.serverActivity;
if (delta > ttl * 2) {
if (typeof _this.debug === "function") {
_this.debug("did not receive server activity for the last " + delta + "ms");
}
return _this.ws.close();
}
};
})(this));
}
};
Client.prototype._parseConnect = function() {
var args, connectCallback, errorCallback, headers;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
headers = {};
switch (args.length) {
case 2:
headers = args[0], connectCallback = args[1];
break;
case 3:
if (args[1] instanceof Function) {
headers = args[0], connectCallback = args[1], errorCallback = args[2];
} else {
headers.login = args[0], headers.passcode = args[1], connectCallback = args[2];
}
break;
case 4:
headers.login = args[0], headers.passcode = args[1], connectCallback = args[2], errorCallback = args[3];
break;
default:
headers.login = args[0], headers.passcode = args[1], connectCallback = args[2], errorCallback = args[3], headers.host = args[4];
}
return [headers, connectCallback, errorCallback];
};
Client.prototype.connect = function() {
var args, errorCallback, headers, out;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
out = this._parseConnect.apply(this, args);
headers = out[0], this.connectCallback = out[1], errorCallback = out[2];
if (typeof this.debug === "function") {
this.debug("Opening Web Socket...");
}
this.ws.onmessage = (function(_this) {
return function(evt) {
var arr, c, client, data, frame, messageID, onreceive, subscription, unmarshalledData, _i, _len, _ref, _results;
data = typeof ArrayBuffer !== 'undefined' && evt.data instanceof ArrayBuffer ? (arr = new Uint8Array(evt.data), typeof _this.debug === "function" ? _this.debug("--- got data length: " + arr.length) : void 0, ((function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = arr.length; _i < _len; _i++) {
c = arr[_i];
_results.push(String.fromCharCode(c));
}
return _results;
})()).join('')) : evt.data;
_this.serverActivity = now();
if (data === Byte.LF) {
if (typeof _this.debug === "function") {
_this.debug("<<< PONG");
}
return;
}
if (typeof _this.debug === "function") {
_this.debug("<<< " + data);
}
unmarshalledData = Frame.unmarshall(_this.partialData + data);
_this.partialData = unmarshalledData.partial;
_ref = unmarshalledData.frames;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
frame = _ref[_i];
switch (frame.command) {
case "CONNECTED":
if (typeof _this.debug === "function") {
_this.debug("connected to server " + frame.headers.server);
}
_this.connected = true;
_this._setupHeartbeat(frame.headers);
_results.push(typeof _this.connectCallback === "function" ? _this.connectCallback(frame) : void 0);
break;
case "MESSAGE":
subscription = frame.headers.subscription;
onreceive = _this.subscriptions[subscription] || _this.onreceive;
if (onreceive) {
client = _this;
messageID = frame.headers["message-id"];
frame.ack = function(headers) {
if (headers == null) {
headers = {};
}
return client.ack(messageID, subscription, headers);
};
frame.nack = function(headers) {
if (headers == null) {
headers = {};
}
return client.nack(messageID, subscription, headers);
};
_results.push(onreceive(frame));
} else {
_results.push(typeof _this.debug === "function" ? _this.debug("Unhandled received MESSAGE: " + frame) : void 0);
}
break;
case "RECEIPT":
_results.push(typeof _this.onreceipt === "function" ? _this.onreceipt(frame) : void 0);
break;
case "ERROR":
_results.push(typeof errorCallback === "function" ? errorCallback(frame) : void 0);
break;
default:
_results.push(typeof _this.debug === "function" ? _this.debug("Unhandled frame: " + frame) : void 0);
}
}
return _results;
};
})(this);
this.ws.onclose = (function(_this) {
return function() {
var msg;
msg = "Whoops! Lost connection to " + _this.ws.url;
if (typeof _this.debug === "function") {
_this.debug(msg);
}
_this._cleanUp();
return typeof errorCallback === "function" ? errorCallback(msg) : void 0;
};
})(this);
return this.ws.onopen = (function(_this) {
return function() {
if (typeof _this.debug === "function") {
_this.debug('Web Socket Opened...');
}
headers["accept-version"] = Stomp.VERSIONS.supportedVersions();
headers["heart-beat"] = [_this.heartbeat.outgoing, _this.heartbeat.incoming].join(',');
return _this._transmit("CONNECT", headers);
};
})(this);
};
Client.prototype.disconnect = function(disconnectCallback, headers) {
if (headers == null) {
headers = {};
}
this._transmit("DISCONNECT", headers);
this.ws.onclose = null;
this.ws.close();
this._cleanUp();
return typeof disconnectCallback === "function" ? disconnectCallback() : void 0;
};
Client.prototype._cleanUp = function() {
this.connected = false;
if (this.pinger) {
Stomp.clearInterval(this.pinger);
}
if (this.ponger) {
return Stomp.clearInterval(this.ponger);
}
};
Client.prototype.send = function(destination, headers, body) {
if (headers == null) {
headers = {};
}
if (body == null) {
body = '';
}
headers.destination = destination;
return this._transmit("SEND", headers, body);
};
Client.prototype.subscribe = function(destination, callback, headers) {
var client;
if (headers == null) {
headers = {};
}
if (!headers.id) {
headers.id = "sub-" + this.counter++;
}
headers.destination = destination;
this.subscriptions[headers.id] = callback;
this._transmit("SUBSCRIBE", headers);
client = this;
return {
id: headers.id,
unsubscribe: function() {
return client.unsubscribe(headers.id);
}
};
};
Client.prototype.unsubscribe = function(id) {
delete this.subscriptions[id];
return this._transmit("UNSUBSCRIBE", {
id: id
});
};
Client.prototype.begin = function(transaction) {
var client, txid;
txid = transaction || "tx-" + this.counter++;
this._transmit("BEGIN", {
transaction: txid
});
client = this;
return {
id: txid,
commit: function() {
return client.commit(txid);
},
abort: function() {
return client.abort(txid);
}
};
};
Client.prototype.commit = function(transaction) {
return this._transmit("COMMIT", {
transaction: transaction
});
};
Client.prototype.abort = function(transaction) {
return this._transmit("ABORT", {
transaction: transaction
});
};
Client.prototype.ack = function(messageID, subscription, headers) {
if (headers == null) {
headers = {};
}
headers["message-id"] = messageID;
headers.subscription = subscription;
return this._transmit("ACK", headers);
};
Client.prototype.nack = function(messageID, subscription, headers) {
if (headers == null) {
headers = {};
}
headers["message-id"] = messageID;
headers.subscription = subscription;
return this._transmit("NACK", headers);
};
return Client;
})();
Stomp = {
VERSIONS: {
V1_0: '1.0',
V1_1: '1.1',
V1_2: '1.2',
supportedVersions: function() {
return '1.1,1.0';
}
},
client: function(url, protocols) {
var klass, ws;
if (protocols == null) {
protocols = ['v10.stomp', 'v11.stomp'];
}
klass = Stomp.WebSocketClass || WebSocket;
ws = new klass(url, protocols);
return new Client(ws);
},
over: function(ws) {
return new Client(ws);
},
Frame: Frame
};
if (typeof exports !== "undefined" && exports !== null) {
exports.Stomp = Stomp;
}
if (typeof window !== "undefined" && window !== null) {
Stomp.setInterval = function(interval, f) {
return window.setInterval(f, interval);
};
Stomp.clearInterval = function(id) {
return window.clearInterval(id);
};
window.Stomp = Stomp;
} else if (!exports) {
self.Stomp = Stomp;
}
}).call(this);

View File

@ -0,0 +1,92 @@
---
apiVersion: v1
kind: Template
metadata:
name: selenium-hub-parksmap
annotations:
description: "A Selenium Grid"
iconClass: "icon-selenium"
tags: "selenium,hub"
objects:
- apiVersion: v1
kind: Service
metadata:
name: selenium-hub-parksmap
spec:
selector:
type: selenium-hub
type: ClusterIP
ports:
- name: web
port: 4444
targetPort: 4444
protocol: TCP
- name: node
port: 5555
targetPort: 5555
protocol: TCP
- name: publish
port: 4442
targetPort: 4442
protocol: TCP
- name: subscribe
port: 4443
targetPort: 4443
protocol: TCP
- apiVersion: v1
kind: DeploymentConfig
metadata:
name: selenium-hub-parksmap
spec:
replicas: 1
selector:
type: selenium-hub
template:
metadata:
labels:
type: selenium-hub
name: selenium-hub-parksmap
spec:
containers:
- env:
- name: JAVA_OPTS
value: "-Xmx512m"
- name: POOL_MAX
value: "30000"
- name: GRID_NEW_SESSION_WAIT_TIMEOUT
value: "30"
- name: GRID_JETTY_MAX_THREADS
value: "100"
- name: GRID_NODE_POLLING
value: "5"
- name: GRID_CLEAN_UP_CYCLE
value: "300"
- name: GRID_TIMEOUT
value: "300"
- name: GRID_BROWSER_TIMEOUT
value: "300"
- name: GRID_MAX_SESSION
value: "3"
- name: GRID_UNREGISTER_IF_STILL_DOWN_AFTER
value: "5"
image: docker.io/selenium/hub:4.18.1
name: selenium-hub-parksmap
ports:
- containerPort: 4444
protocol: TCP
- apiVersion: v1
kind: Route
metadata:
name: selenium-hub-parksmap
spec:
host: selenium-hub-parksmap.apps.cluster-272j9.dynamic.redhatworkshops.io
port:
targetPort: 4444
tls:
termination: edge
to:
kind: Service
name: selenium-hub-parksmap
weight: 100
labels:
selenium-hub: master