├── .github └── workflows │ ├── README.md │ ├── build.yml │ └── release.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Dockerfile ├── README.md ├── deploy ├── nginx-proxy-compose.yml └── task-definition-template.json ├── local-compose.yml ├── scripts ├── build.sh ├── deploy.sh └── task-definition.json └── src ├── NuGet.Config ├── RedisGeo.ServiceInterface ├── Properties │ └── AssemblyInfo.cs ├── RedisGeo.ServiceInterface.csproj └── RedisGeoServices.cs ├── RedisGeo.ServiceModel ├── FindGeoResults.cs ├── Properties │ └── AssemblyInfo.cs └── RedisGeo.ServiceModel.csproj ├── RedisGeo.sln └── RedisGeo ├── AppHost.cs ├── App_Data ├── AU.txt ├── AU_mapping.txt ├── US.txt └── US_mapping.txt ├── Program.cs ├── Properties └── launchSettings.json ├── RedisGeo.csproj ├── Startup.cs ├── appsettings.Development.json ├── appsettings.json └── wwwroot ├── default.html └── js └── jquery-2.2.3.min.js /.github/workflows/README.md: -------------------------------------------------------------------------------- 1 | # ServiceStack mix GitHub Actions 2 | `release.yml` generated from `x mix release-ecr-aws`, this template in designed to help with automating CI deployments to AWS ECS and dedicated AWS ECS cluster. 3 | This is a cheap way to start without an AWS Application Load Balancer (ALB) and also be in a situation that will easier to add one once the web service needs additional scale or high availability. 4 | 5 | ## Overview 6 | `release.yml` is designed to work with a ServiceStack app templates deploying directly to a single server in a dedicated ECS cluster via templated GitHub Actions. 7 | 8 | ## Setup 9 | ### Create unique ECS cluster 10 | For this setup, it is best to create a separate cluster as cluster will only have the single instance in it running. 11 | This pattern is to start from a good base with AWS ECS and automated CI deployment while avoiding the higher costs of needing to run an application load balancer (ALB). 12 | 13 | If/when you can justify the cost of an ALB for easier scaling and zero downtime deployment, the GitHub Action `release.yml` can be slightly modified to be used with a re-created or different ECS Service that is configured to be used with an Application Load Balancer and Target Group. 14 | 15 | ### Elastic IP (optional) 16 | The reason you might want to register this first is because we are only running one EC2 instance and hosting our own `nginx-proxy` on the same instance as the applications. 17 | Since an `A` record will be pointing there, one advantage of not using an auto-assigned IP is that we can reassign the elastic IP if for what ever reason the instance goes down or is lost. 18 | 19 | ## Launch to EC2 Instance 20 | When launching the EC2 instance, you'll need to select an 'ECS optimized' AMI as the image used for your instance. 21 | ### Choose AMI 22 | The easiest way to find the latest Amazon Linux 2 image for this is to go to the [AWS documentation for ECS-optimized AMIs and look up your region here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html#ecs-optimized-ami-linux). 23 | 24 | Using the AMI ID (starts with `ami-`) at the bottom, search in the 'Community AMIs' tab on the first step of the `Launch EC2 Instance` wizard. 25 | 26 | ### Choose Instance Type 27 | A t2.micro or larger will work fine, this pattern can be used to host multiple applications on the 1 server so if the number of applications gets larger, you might need a larger instance type. 28 | > Note this pattern is suitable for testing prototypes or low traffic applications as it is cost effective and makes it easy to bundle multiple apps onto 1 EC2 instance. 29 | 30 | ### Configure Instance 31 | Under `IAM role`, use the `ecsInstanceRole`, if this is not available, see [AWS documentation for the process of checking if it exists and creating it if needed](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/instance_IAM_role.html). 32 | 33 | You will also want to add the following Userdata script (in the `Configure` step of the launch wizard) with your own `ECS_CLUSTER` value. This tells the ecs-agent running on the instance which ECS cluster the instance should join. 34 | 35 | ```bash 36 | #!/bin/bash 37 | cat </etc/ecs/ecs.config 38 | ECS_CLUSTER=redis-geos 39 | ECS_AVAILABLE_LOGGING_DRIVERS=["awslogs", "syslog"] 40 | ECS_ENABLE_CONTAINER_METADATA=true 41 | EOS 42 | ``` 43 | 44 | Note down your cluster name as it will need to be used to create the cluster in ECS before it is visible. 45 | See [`ECS Container Agent Configuration`](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-agent-config.html) for more information. 46 | 47 | ### Add Storage 48 | The default of 30gb is fine but take into account how large/how many applications you'll have running. 49 | 50 | ### Configure Security Groups 51 | You'll want to expose at least ports 80 and 443. 52 | 53 | ### Setup Docker-compose and nginx-proxy 54 | To let your server handle multiple ServiceStack applications and automate the generation and management of TLS certificates, an additional docker-compose file is provided via the `x mix` template, `nginx-proxy-compose.yml`. This docker-compose file is ready to run and can be copied to the deployment server. 55 | > This is done via docker-compose rather than via ECS for simplicity. 56 | 57 | For example, once copied to remote `~/nginx-proxy-compose.yml`, the following command can be run on the remote server. 58 | 59 | ``` 60 | docker-compose -f ~/nginx-proxy-compose.yml up -d 61 | ``` 62 | 63 | This will run an nginx reverse proxy along with a companion container that will watch for additional containers in the same docker network and attempt to initialize them with valid TLS certificates. 64 | 65 | ## GitHub Repository setup 66 | The `release.yml` assumes 6 secrets have been setup. 67 | 68 | - AWS_ACCESS_KEY_ID - AWS access key for programmatic access to AWS APIs. 69 | - AWS_SECRET_ACCESS_KEY - AWS access secrets for programmatic access to AWS APIs. 70 | - AWS_REGION - default region for AWS API calls. 71 | - AWS_ECS_CLUSTER - Cluster name in ECS, this should match the value in your Userdata. 72 | - HOST_DOMAIN - Domain/submain of your application, eg `redis-geo.example.com` . 73 | - LETSENCRYPT_EMAIL - Email address, required for Let's Encrypt automated TLS certificates. 74 | 75 | These secrets are used to populate variables within GitHub Actions and other configuration files. 76 | 77 | For the AWS access, a separate user specifically for deploying via GitHub Actions should be used. 78 | 79 | The policies required for the complete initial setup will be: 80 | - `AmazonEC2ContainerRegistryFullAccess` 81 | - `AmazonECS_FullAccess` 82 | 83 | Once the application is successfully deployed the first time, reduced access for both ECR and ECS can be used instead. For application updates, the GitHub Action can use the following policy. 84 | 85 | ```json 86 | { 87 | "Version": "2012-10-17", 88 | "Statement": [ 89 | { 90 | "Sid": "VisualEditor0", 91 | "Effect": "Allow", 92 | "Action": [ 93 | "ecr:GetRegistryPolicy", 94 | "ecr:PutImageTagMutability", 95 | "ecr:GetDownloadUrlForLayer", 96 | "ecr:DescribeRegistry", 97 | "ecr:GetAuthorizationToken", 98 | "ecr:ListTagsForResource", 99 | "ecr:UploadLayerPart", 100 | "ecr:ListImages", 101 | "ecr:PutImage", 102 | "ecr:UntagResource", 103 | "ecr:BatchGetImage", 104 | "ecr:CompleteLayerUpload", 105 | "ecr:DescribeImages", 106 | "ecr:TagResource", 107 | "ecr:DescribeRepositories", 108 | "ecr:InitiateLayerUpload", 109 | "ecr:BatchCheckLayerAvailability", 110 | "ecr:ReplicateImage", 111 | "ecr:GetRepositoryPolicy", 112 | "ecs:SubmitTaskStateChange", 113 | "ecs:UpdateContainerInstancesState", 114 | "ecs:RegisterContainerInstance", 115 | "ecs:DescribeTaskDefinition", 116 | "ecs:DescribeClusters", 117 | "ecs:ListServices", 118 | "ecs:UpdateService", 119 | "ecs:ListTasks", 120 | "ecs:ListTaskDefinitionFamilies", 121 | "ecs:RegisterTaskDefinition", 122 | "ecs:SubmitContainerStateChange", 123 | "ecs:StopTask", 124 | "ecs:DescribeServices", 125 | "ecs:ListContainerInstances", 126 | "ecs:DescribeContainerInstances", 127 | "ecs:DeregisterContainerInstance", 128 | "ecs:TagResource", 129 | "ecs:DescribeTasks", 130 | "ecs:UntagResource", 131 | "ecs:ListTaskDefinitions", 132 | "ecs:ListClusters" 133 | ], 134 | "Resource": "*" 135 | } 136 | ] 137 | } 138 | ``` 139 | > Further permission reduction can be done by reducing what resources can be accessed. 140 | > Application permissions can be controlled via `taskRoleArn`, see [AWS docs for details](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html). 141 | 142 | ## What's the process of the `release.yml`? 143 | 144 | ![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/mix/release-ecr-aws-diagram.png) 145 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: 7 | - '**' # matches every branch 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-20.04 12 | steps: 13 | - name: checkout 14 | uses: actions/checkout@v2.0.0 15 | 16 | - name: setup .net core 17 | uses: actions/setup-dotnet@v1.7.2 18 | with: 19 | dotnet-version: 5.0.100 20 | 21 | - name: build 22 | run: dotnet build 23 | working-directory: ./src/RedisGeo/ 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | push_to_ecr: 7 | runs-on: ubuntu-20.04 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | 12 | - name: repository name fix 13 | run: echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV 14 | 15 | - name: Configure AWS credentials 16 | uses: aws-actions/configure-aws-credentials@v1 17 | with: 18 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 19 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 20 | aws-region: ${{ secrets.AWS_REGION }} 21 | 22 | - name: Login to Amazon ECR 23 | id: login_ecr 24 | uses: aws-actions/amazon-ecr-login@v1 25 | 26 | - name: Create ECR repo if not exists. 27 | env: 28 | ECR_REPOSITORY: ${{ env.image_repository_name }} 29 | run: aws ecr describe-repositories --repository-names ${ECR_REPOSITORY} || aws ecr create-repository --repository-name ${ECR_REPOSITORY} 30 | 31 | - name: Build and push to ECR 32 | id: push_image_to_ecr 33 | uses: docker/build-push-action@v2.2.2 34 | with: 35 | file: Dockerfile 36 | context: . 37 | push: true 38 | tags: ${{ steps.login_ecr.outputs.registry }}/${{ env.image_repository_name }}:${{ github.event.release.tag_name }} 39 | 40 | deploy_ecs: 41 | needs: push_to_ecr 42 | runs-on: ubuntu-20.04 43 | steps: 44 | - name: checkout 45 | uses: actions/checkout@v2 46 | 47 | - name: Configure AWS credentials 48 | uses: aws-actions/configure-aws-credentials@v1 49 | with: 50 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 51 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 52 | aws-region: ${{ secrets.AWS_REGION }} 53 | 54 | - name: Login to Amazon ECR 55 | id: login_ecr 56 | uses: aws-actions/amazon-ecr-login@v1 57 | 58 | - name: Repository name fix and env values setup 59 | run: | 60 | echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV 61 | echo "domain=${{ secrets.HOST_DOMAIN }}" >> $GITHUB_ENV 62 | echo "letsencrypt_email=${{ secrets.LETSENCRYPT_EMAIL }}" >> $GITHUB_ENV 63 | echo "app_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]' | cut -d'/' -f2)" >> $GITHUB_ENV 64 | echo "cluster_name=${{ secrets.AWS_ECS_CLUSTER }}" >> $GITHUB_ENV 65 | echo "image_url=${{ steps.login_ecr.outputs.registry }}/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]'):${{ github.event.release.tag_name }}" >> $GITHUB_ENV 66 | echo "aws_region=${{ secrets.AWS_REGION }}" >> $GITHUB_ENV 67 | 68 | - name: Populate task definition template 69 | uses: danielr1996/envsubst-action@1.0.0 70 | env: 71 | RELEASE_VERSION: ${{ github.event.release.tag_name }} 72 | APP_NAME: ${{ env.app_name }} 73 | IMAGE_URL: ${{ env.image_url }} 74 | HOST_DOMAIN: ${{ env.domain }} 75 | LETSENCRYPT_EMAIL: ${{ env.letsencrypt_email }} 76 | AWS_REGION: ${{ env.aws_region }} 77 | CLUSTER_NAME: ${{ env.cluster_name }} 78 | with: 79 | input: deploy/task-definition-template.json 80 | output: deploy/task-definition.json 81 | 82 | - name: Create task definition if doesn't exist 83 | run: aws ecs describe-task-definition --task-definition ${{ env.app_name }} || aws ecs register-task-definition --cli-input-json file://deploy/task-definition.json 84 | 85 | - name: Create ECS Service if not exists. 86 | run: aws ecs describe-services --cluster ${{ env.cluster_name }} --services ${{ env.app_name }} | jq '.services[0]' -e || aws ecs create-service --cluster ${{ env.cluster_name }} --service-name ${{ env.app_name }} --task-definition ${{ env.app_name }} --desired-count 1 87 | 88 | - name: Deploy new revision of the task definition 89 | uses: aws-actions/amazon-ecs-deploy-task-definition@v1 90 | with: 91 | task-definition: deploy/task-definition.json 92 | service: ${{ env.app_name }} 93 | cluster: ${{ env.cluster_name }} 94 | force-new-deployment: true 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/src/RedisGeo/bin/Debug/netcoreapp1.1/RedisGeo.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/src/RedisGeo", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart", 18 | "launchBrowser": { 19 | "enabled": true, 20 | "args": "${auto-detect-url}", 21 | "windows": { 22 | "command": "cmd.exe", 23 | "args": "/C start ${auto-detect-url}" 24 | }, 25 | "osx": { 26 | "command": "open" 27 | }, 28 | "linux": { 29 | "command": "xdg-open" 30 | } 31 | }, 32 | "env": { 33 | "ASPNETCORE_ENVIRONMENT": "Development" 34 | }, 35 | "sourceFileMap": { 36 | "/Views": "${workspaceFolder}/Views" 37 | } 38 | }, 39 | { 40 | "name": ".NET Core Attach", 41 | "type": "coreclr", 42 | "request": "attach", 43 | "processId": "${command:pickProcess}" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "taskName": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/src/RedisGeo/RedisGeo.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build 2 | WORKDIR /source 3 | 4 | COPY . . 5 | RUN dotnet restore ./src/RedisGeo.sln 6 | 7 | WORKDIR /source/src/RedisGeo 8 | RUN dotnet publish -c release -o /app --no-restore 9 | 10 | FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS runtime 11 | WORKDIR /app 12 | COPY --from=build /app ./ 13 | ENTRYPOINT ["dotnet", "RedisGeo.dll"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redis GEO Example App 2 | 3 | Redis GEO is a simple example showing how to make use of [Redis 3.2.0 new GEO capabilities](http://antirez.com/news/104): 4 | 5 | [![](https://raw.githubusercontent.com/ServiceStack/Assets/master/img/livedemos/redis-geo/redisgeo-screenshot.png)](https://redis.netcore.io) 6 | 7 | > Live Demo: https://redis.netcore.io 8 | 9 | If Redis hasn't already cemented itself as the venerable Swiss-Army-Knife component present in 10 | [many high-performance server solutions](https://techstacks.io/tech/redis), the latest 3.2.0 release has made 11 | it even more versatile and enhanced it with new [GEO powers](https://redis.io/commands/geoadd). 12 | 13 | Aiming for the simplest possible useful demonstration of this new functionality, Redis GEO App lets you 14 | click on anywhere in the U.S. to find the list of nearest cities within a given radius. 15 | 16 | ## Install Redis 3.2.0 17 | 18 | In order to use the new GEO operations you'll need the latest stable 3.2.0 release of redis which you can 19 | install in your preferred *NIX system with: 20 | 21 | $ wget http://download.redis.io/releases/redis-3.2.0.tar.gz 22 | $ tar xzf redis-3.2.0.tar.gz 23 | $ cd redis-3.2.0 24 | $ make 25 | 26 | This will build the `redis-server` binaries that can be run locally. To also install it as a service that's 27 | globally available and automatically started on each boot, run: 28 | 29 | $ sudo make install 30 | $ cd utils 31 | $ sudo ./install_server.sh 32 | 33 | ## Create Empty ServiceStack Project 34 | 35 | Redis GEO was created from the 36 | [ServiceStack ASP.NET Empty](https://github.com/ServiceStack/ServiceStack/wiki/Creating-your-first-project) 37 | project template. 38 | 39 | ## Import Geonames dataset 40 | 41 | To populate Redis with useful GEO data we'll import the 42 | [geonames.org postal data](http://download.geonames.org/export/zip/) which provides the zip codes of all 43 | US cities as well as their useful longitude and latitude coordinates. 44 | 45 | The dataset is maintained in a tab-delimited `US.txt` text file which we do a fresh import of using the 46 | [ServiceStack.Redis](https://github.com/ServiceStack/ServiceStack.Redis) C# Client when the 47 | [AppHost](https://github.com/NetCoreApps/redis-geo/blob/master/src/RedisGeo/AppHost.cs) 48 | first starts up: 49 | 50 | ```csharp 51 | public class AppHost : AppHostBase 52 | { 53 | public AppHost() 54 | : base("RedisGeo", typeof(RedisGeoServices).Assembly) {} 55 | 56 | public override void Configure(Container container) 57 | { 58 | JsConfig.EmitCamelCaseNames = true; 59 | 60 | container.Register(c => 61 | new RedisManagerPool(AppSettings.Get("RedisHost", defaultValue:"localhost"))); 62 | 63 | ImportCountry(container.Resolve(), "US"); 64 | } 65 | 66 | public static void ImportCountry(IRedisClientsManager redisManager, string countryCode) 67 | { 68 | using (var redis = redisManager.GetClient()) 69 | using (var reader = new StreamReader( 70 | File.OpenRead("~/App_Data/{0}.txt".Fmt(countryCode).MapHostAbsolutePath()))) 71 | { 72 | string line, lastState = null, lastCity = null; 73 | var results = new List(); 74 | while ((line = reader.ReadLine()) != null) 75 | { 76 | var parts = line.Split('\t'); 77 | var city = parts[2]; 78 | var state = parts[4]; 79 | var latitude = double.Parse(parts[9]); 80 | var longitude = double.Parse(parts[10]); 81 | 82 | if (city == lastCity) //Skip duplicate entries 83 | continue; 84 | else 85 | lastCity = city; 86 | 87 | if (lastState == null) 88 | lastState = state; 89 | 90 | if (state != lastState) 91 | { 92 | redis.AddGeoMembers(lastState, results.ToArray()); 93 | lastState = state; 94 | results.Clear(); 95 | } 96 | 97 | results.Add(new ServiceStack.Redis.RedisGeo(longitude, latitude, city)); 98 | } 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | This just parses the `US.txt` file in our Web Applications 105 | [/App_Data](https://github.com/NetCoreApps/redis-geo/tree/master/src/RedisGeo/App_Data) 106 | folder and extracts the **state** which we'll use as the key for our Redis GEO sorted set and populate 107 | it with the **longitude** and **latitude** of each **city**, skipping any duplicates. The script also 108 | imports the dataset for each state in separate batches using 109 | [GEOADD](https://redis.io/commands/geoadd) multi argument API. 110 | 111 | ### Implement the FindGeoResults Service 112 | 113 | Our App only needs a single Service which we define the contract with using the 114 | [FindGeoResults](https://github.com/NetCoreApps/redis-geo/blob/master/src/RedisGeo.ServiceModel/FindGeoResults.cs) 115 | Request DTO: 116 | 117 | ```csharp 118 | [Route("/georesults/{State}")] 119 | public class FindGeoResults : IReturn> 120 | { 121 | public string State { get; set; } 122 | public long? WithinKm { get; set; } 123 | public double Lng { get; set; } 124 | public double Lat { get; set; } 125 | } 126 | ``` 127 | 128 | That's the only DTO our App needs which returns a `List`. Implementing the 129 | [RedisGeoServices](https://github.com/NetCoreApps/redis-geo/blob/master/src/RedisGeo.ServiceInterface/RedisGeoServices.cs) 130 | is then just a matter fulfilling the above contract by delegating our populated Request DTO properties to the 131 | `IRedisClient.FindGeoResultsInRadius()` Redis Client API which itself just calls 132 | [GEORADIUS](https://redis.io/commands/georadius) and returns its results: 133 | 134 | ```csharp 135 | public class RedisGeoServices : Service 136 | { 137 | public object Any(FindGeoResults request) 138 | { 139 | var results = Redis.FindGeoResultsInRadius(request.State, 140 | longitude: request.Lng, latitude: request.Lat, 141 | radius: request.WithinKm.GetValueOrDefault(20), unit: RedisGeoUnit.Kilometers, 142 | sortByNearest: true); 143 | 144 | return results; 145 | } 146 | } 147 | ``` 148 | 149 | ## Implement the Client 150 | 151 | The entire client App is implemented in the static 152 | [default.html](https://github.com/NetCoreApps/redis-geo/blob/master/src/RedisGeo/wwwroot/default.html) 153 | which is just a jQuery App that just consists of the following markup: 154 | 155 | ```html 156 | 172 |
173 | ``` 174 | 175 | To show our results from our **GEORADIUS** query and a `
` placeholder used by 176 | [Google Maps JavaScript API](https://developers.google.com/maps/documentation/javascript/) to render a 177 | our interactive map of the US in. 178 | 179 | The JavaScript below just listens to every click on the map then uses the `Geocoder` API to find out which 180 | state the user clicked on at which point it adds a custom `Marker` and a `Circle` with the radius that's 181 | specified in the **km** textbox. 182 | 183 | It then calls our `/georesults/{State}` Service with the Lat/Lng of where the user clicked as well as the 184 | distance that it should search within, then displays all the cities within that radius in the Sidebar: 185 | 186 | ```js 187 | var map; 188 | function initMap() { 189 | map = new google.maps.Map(document.getElementById('map'), { 190 | center: { lat: 37.09024, lng: -95.7128917 }, 191 | zoom: 5 192 | }); 193 | var geocoder = new google.maps.Geocoder(); 194 | var lastMarker, lastRadius; 195 | 196 | google.maps.event.addListener(map, "click", function(e) { 197 | geocoder.geocode({ 'location': e.latLng }, function(results, status) { 198 | if (status === google.maps.GeocoderStatus.OK) { 199 | map.setCenter(e.latLng); 200 | 201 | if (lastMarker != null) 202 | lastMarker.setMap(null); 203 | 204 | var marker = lastMarker = new google.maps.Marker({ 205 | map: map, 206 | position: e.latLng 207 | }); 208 | 209 | if (lastRadius != null) 210 | lastRadius.setMap(null); 211 | 212 | var km = parseInt($("#km").val()); 213 | var radius = lastRadius = new google.maps.Circle({ 214 | strokeColor: "#c3fc49", 215 | strokeOpacity: 0.8, 216 | strokeWeight: 2, 217 | fillColor: "#c3fc49", 218 | fillOpacity: 0.35, 219 | map: map, 220 | center: e.latLng, 221 | radius: km * 1000 222 | }); 223 | radius.bindTo('center', marker, 'position'); 224 | 225 | var state = getStateAbbr(results); 226 | $("#state").html(state); 227 | $("#instructions").hide(); 228 | $("#info").show(); 229 | 230 | $.getJSON("/georesults/" + state, 231 | { lat: e.latLng.lat(), lng: e.latLng.lng(), withinKm: km }, 232 | function (r) { 233 | var html = $.map(r, function(x) { 234 | return "
  • " + x.member + " (" + x.distance.toFixed(2) + "km)
  • "; 235 | }).join(''); 236 | $("#results").html(html); 237 | }); 238 | }}); 239 | }); 240 | 241 | function getStateAbbr(results) { 242 | for (var i = 0; i < results.length; i++) { 243 | for (var j = 0; j < results[i].address_components.length; j++) { 244 | var addr = results[i].address_components[j]; 245 | if (addr.types.indexOf("administrative_area_level_1") >= 0) 246 | return addr.short_name; 247 | } 248 | } 249 | return null; 250 | } 251 | } 252 | ``` 253 | 254 | The result is a quick demonstration where the user can click on anywhere in the U.S. to return the nearest 255 | points of interest. 256 | 257 | We hope this simple example piques your interest in Redis new GEO features and highlights some potential 258 | use-cases possible with these new capabilities. 259 | 260 | ## Importing different country datasets 261 | 262 | Whilst this example just imports US cities, you can change it to import your preferred country instead by 263 | extracting the [Geonames](http://download.geonames.org/export/zip/) dataset and copying it into the 264 | [/App_Data](https://github.com/NetCoreApps/redis-geo/tree/master/src/RedisGeo/App_Data) 265 | folder then calling `ImportCountry()` with its country code. 266 | 267 | E.g. we can import Ausrtalian Suburbs instead with: 268 | 269 | ```csharp 270 | //ImportCountry(container.Resolve(), "US"); 271 | ImportCountry(container.Resolve(), "AU"); 272 | ``` 273 | 274 | ## [ServiceStack.Redis](https://github.com/ServiceStack/ServiceStack.Redis) GEO APIs 275 | 276 | Human friendly and convenient versions of each Redis GEO API is available in 277 | [IRedisClient](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Interfaces/Redis/IRedisClient.cs) 278 | below: 279 | 280 | ```csharp 281 | public interface IRedisClient 282 | { 283 | //... 284 | long AddGeoMember(string key, double longitude, double latitude, string member); 285 | long AddGeoMembers(string key, params RedisGeo[] geoPoints); 286 | double CalculateDistanceBetweenGeoMembers(string key, string fromMember, string toMember, string unit=null); 287 | string[] GetGeohashes(string key, params string[] members); 288 | List GetGeoCoordinates(string key, params string[] members); 289 | string[] FindGeoMembersInRadius(string key, double longitude, double latitude, double radius, string unit); 290 | List FindGeoResultsInRadius(string key, double longitude, double latitude, double radius, 291 | string unit, int? count = null, bool? sortByNearest = null); 292 | string[] FindGeoMembersInRadius(string key, string member, double radius, string unit); 293 | List FindGeoResultsInRadius(string key, string member, double radius, string unit, 294 | int? count = null, bool? sortByNearest = null); 295 | } 296 | ``` 297 | 298 | Whilst lower-level API's which map 1:1 with Redis server operations are available in 299 | [IRedisNativeClient](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Interfaces/Redis/IRedisNativeClient.cs): 300 | 301 | ```csharp 302 | public interface IRedisNativeClient 303 | { 304 | //... 305 | long GeoAdd(string key, double longitude, double latitude, string member); 306 | long GeoAdd(string key, params RedisGeo[] geoPoints); 307 | double GeoDist(string key, string fromMember, string toMember, string unit = null); 308 | string[] GeoHash(string key, params string[] members); 309 | List GeoPos(string key, params string[] members); 310 | List GeoRadius(string key, double longitude, double latitude, double radius, string unit, 311 | bool withCoords=false, bool withDist=false, bool withHash=false, int? count=null, bool? asc=null); 312 | List GeoRadiusByMember(string key, string member, double radius, string unit, 313 | bool withCoords=false, bool withDist=false, bool withHash=false, int? count=null, bool? asc=null); 314 | } 315 | ``` 316 | -------------------------------------------------------------------------------- /deploy/nginx-proxy-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | nginx-proxy: 5 | image: jwilder/nginx-proxy 6 | container_name: nginx-proxy 7 | restart: always 8 | ports: 9 | - "80:80" 10 | - "443:443" 11 | volumes: 12 | - conf:/etc/nginx/conf.d 13 | - vhost:/etc/nginx/vhost.d 14 | - html:/usr/share/nginx/html 15 | - dhparam:/etc/nginx/dhparam 16 | - certs:/etc/nginx/certs:ro 17 | - /var/run/docker.sock:/tmp/docker.sock:ro 18 | network_mode: bridge 19 | 20 | letsencrypt: 21 | image: jrcs/letsencrypt-nginx-proxy-companion 22 | container_name: nginx-proxy-le 23 | restart: always 24 | environment: 25 | - DEFAULT_EMAIL=you@example.com 26 | volumes_from: 27 | - nginx-proxy 28 | volumes: 29 | - certs:/etc/nginx/certs:rw 30 | - acme:/etc/acme.sh 31 | - /var/run/docker.sock:/var/run/docker.sock:ro 32 | network_mode: bridge 33 | 34 | volumes: 35 | conf: 36 | vhost: 37 | html: 38 | dhparam: 39 | certs: 40 | acme: 41 | 42 | networks: 43 | default: 44 | external: 45 | name: webproxy -------------------------------------------------------------------------------- /deploy/task-definition-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "family": "${APP_NAME}", 3 | "requiresCompatibilities": [ 4 | "EC2" 5 | ], 6 | "networkMode": "bridge", 7 | "containerDefinitions": [ 8 | { 9 | "portMappings": [ 10 | { 11 | "protocol": "tcp", 12 | "containerPort": 80 13 | } 14 | ], 15 | "environment": [ 16 | { 17 | "name": "VIRTUAL_HOST", 18 | "value": "${HOST_DOMAIN}" 19 | }, 20 | { 21 | "name": "LETSENCRYPT_HOST", 22 | "value": "${HOST_DOMAIN}" 23 | }, 24 | { 25 | "name": "LETSENCRYPT_EMAIL", 26 | "value": "${LETSENCRYPT_EMAIL}" 27 | }, 28 | { 29 | "name": "APP_VERSION", 30 | "value": "${RELEASE_VERSION}" 31 | }, 32 | { 33 | "name": "REDIS_HOST", 34 | "value": "redis" 35 | } 36 | ], 37 | "mountPoints": [], 38 | "memoryReservation": 128, 39 | "volumesFrom": [], 40 | "image": "${IMAGE_URL}", 41 | "essential": true, 42 | "name": "${APP_NAME}", 43 | "logConfiguration": { 44 | "logDriver": "awslogs", 45 | "options": { 46 | "awslogs-group": "${CLUSTER_NAME}-${APP_NAME}", 47 | "awslogs-region": "${AWS_REGION}", 48 | "awslogs-create-group": "true" 49 | } 50 | }, 51 | "links": ["redis"] 52 | }, 53 | { 54 | "portMappings": [ 55 | { 56 | "protocol": "tcp", 57 | "containerPort": 6379 58 | } 59 | ], 60 | "cpu": 0, 61 | "memoryReservation": 32, 62 | "image": "redis", 63 | "essential": true, 64 | "name": "redis" 65 | } 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /local-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | redis: 5 | image: redis 6 | 7 | web: 8 | build: . 9 | environment: 10 | - REDIS_HOST=redis 11 | ports: 12 | - "8080:80" 13 | depends_on: 14 | - redis 15 | 16 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source ../deploy-envs.sh 3 | 4 | #AWS_ACCOUNT_NUMBER={} set in private variable 5 | export AWS_ECS_REPO_DOMAIN=$AWS_ACCOUNT_NUMBER.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com 6 | 7 | # Build process 8 | docker build -t $IMAGE_NAME ../ 9 | docker tag $IMAGE_NAME $AWS_ECS_REPO_DOMAIN/$IMAGE_NAME:$IMAGE_VERSION 10 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source ../deploy-envs.sh 3 | 4 | export AWS_ECS_REPO_DOMAIN=$AWS_ACCOUNT_NUMBER.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com 5 | export ECS_SERVICE=$IMAGE_NAME-service 6 | export ECS_TASK=$IMAGE_NAME-task 7 | 8 | # install dependencies 9 | sudo apt-get install jq -y #install jq for json parsing 10 | sudo apt-get install gettext -y 11 | pip install --user awscli # install aws cli w/o sudo 12 | export PATH=$PATH:$HOME/.local/bin # put aws in the path 13 | 14 | # replace environment variables in task-definition 15 | envsubst < task-definition.json > new-task-definition.json 16 | 17 | eval $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email | sed 's|https://||') #needs AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY envvars 18 | 19 | if [ $(aws ecr describe-repositories | jq --arg x $IMAGE_NAME '[.repositories[] | .repositoryName == $x] | any') == "true" ]; then 20 | echo "Found ECS Repository $IMAGE_NAME" 21 | else 22 | echo "ECS Repository doesn't exist, Creating $IMAGE_NAME ..." 23 | aws ecr create-repository --repository-name $IMAGE_NAME 24 | fi 25 | 26 | docker push $AWS_ECS_REPO_DOMAIN/$IMAGE_NAME:$IMAGE_VERSION 27 | 28 | aws ecs register-task-definition --cli-input-json file://new-task-definition.json --region $AWS_DEFAULT_REGION > /dev/null # Create a new task revision 29 | TASK_REVISION=$(aws ecs describe-task-definition --task-definition $ECS_TASK --region $AWS_DEFAULT_REGION | jq '.taskDefinition.revision') #get latest revision 30 | SERVICE_ARN="arn:aws:ecs:$AWS_DEFAULT_REGION:$AWS_ACCOUNT_NUMBER:service/$ECS_SERVICE" 31 | ECS_SERVICE_EXISTS=$(aws ecs list-services --region $AWS_DEFAULT_REGION --cluster $AWS_ECS_CLUSTER_NAME | jq '.serviceArns' | jq 'contains(["'"$SERVICE_ARN"'"])') 32 | if [ "$ECS_SERVICE_EXISTS" == "true" ]; then 33 | echo "ECS Service already exists, Updating $ECS_SERVICE ..." 34 | aws ecs update-service --cluster $AWS_ECS_CLUSTER_NAME --service $ECS_SERVICE --task-definition "$ECS_TASK:$TASK_REVISION" --desired-count 1 --region $AWS_DEFAULT_REGION > /dev/null #update service with latest task revision 35 | else 36 | echo "Creating ECS Service $ECS_SERVICE ..." 37 | aws ecs create-service --cluster $AWS_ECS_CLUSTER_NAME --service-name $ECS_SERVICE --task-definition "$ECS_TASK:$TASK_REVISION" --desired-count 1 --region $AWS_DEFAULT_REGION > /dev/null #create service 38 | fi 39 | if [ "$(aws ecs list-tasks --service-name $ECS_SERVICE --region $AWS_DEFAULT_REGION | jq '.taskArns' | jq 'length')" -gt "0" ]; then 40 | TEMP_ARN=$(aws ecs list-tasks --service-name $ECS_SERVICE --region $AWS_DEFAULT_REGION | jq '.taskArns[0]') # Get current running task ARN 41 | TASK_ARN="${TEMP_ARN%\"}" # strip double quotes 42 | TASK_ARN="${TASK_ARN#\"}" # strip double quotes 43 | aws ecs stop-task --task $TASK_ARN --region $AWS_DEFAULT_REGION > /dev/null # Stop current task to force start of new task revision with new image 44 | fi 45 | -------------------------------------------------------------------------------- /scripts/task-definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "family": "${ECS_TASK}", 3 | "networkMode": "bridge", 4 | "containerDefinitions": [ 5 | { 6 | "image": "${AWS_ECS_REPO_DOMAIN}/${IMAGE_NAME}:${IMAGE_VERSION}", 7 | "name": "${IMAGE_NAME}", 8 | "cpu": 128, 9 | "memory": 256, 10 | "essential": true, 11 | "links": ["${IMAGE_NAME}-redis"], 12 | "portMappings": [ 13 | { 14 | "containerPort": 5000, 15 | "hostPort": 0, 16 | "protocol": "tcp" 17 | } 18 | ], 19 | "environment": [ 20 | { 21 | "name": "VIRTUAL_HOST", 22 | "value": "${AWS_VIRTUAL_HOST}" 23 | }, 24 | { 25 | "name": "SERVICESTACK_LICENSE", 26 | "value": "${SERVICESTACK_LICENSE}" 27 | }, 28 | { 29 | "name": "REDIS_HOST", 30 | "value": "${IMAGE_NAME}-redis" 31 | } 32 | ] 33 | }, 34 | { 35 | "name": "${IMAGE_NAME}-redis", 36 | "image": "redis:3.2", 37 | "cpu": 128, 38 | "memory": 128, 39 | "essential": true 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /src/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/RedisGeo.ServiceInterface/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("RedisGeo.ServiceInterface")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("e532e09d-d750-4a9f-b682-e7ba1508ea2c")] 20 | -------------------------------------------------------------------------------- /src/RedisGeo.ServiceInterface/RedisGeo.ServiceInterface.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | RedisGeo.ServiceInterface 6 | RedisGeo.ServiceInterface 7 | false 8 | false 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/RedisGeo.ServiceInterface/RedisGeoServices.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ServiceStack; 3 | using RedisGeo.ServiceModel; 4 | using ServiceStack.Redis; 5 | 6 | namespace RedisGeo.ServiceInterface 7 | { 8 | public class RedisGeoServices : Service 9 | { 10 | public object Any(FindGeoResults request) 11 | { 12 | var stateCode = Redis.Get("mapping:" + request.State); 13 | if (stateCode == null) 14 | return new List(); 15 | var results = Redis.FindGeoResultsInRadius(stateCode, 16 | longitude: request.Lng, latitude: request.Lat, 17 | radius: request.WithinKm.GetValueOrDefault(20), unit: RedisGeoUnit.Kilometers, 18 | sortByNearest: true); 19 | 20 | return results; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/RedisGeo.ServiceModel/FindGeoResults.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ServiceStack; 3 | using ServiceStack.Redis; 4 | 5 | namespace RedisGeo.ServiceModel 6 | { 7 | [Route("/georesults/{State}")] 8 | public class FindGeoResults : IReturn> 9 | { 10 | public string State { get; set; } 11 | public long? WithinKm { get; set; } 12 | public double Lng { get; set; } 13 | public double Lat { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /src/RedisGeo.ServiceModel/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("RedisGeo.ServiceModel")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("d6e47201-1cf1-48da-8b40-7418070ec783")] 20 | -------------------------------------------------------------------------------- /src/RedisGeo.ServiceModel/RedisGeo.ServiceModel.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | RedisGeo.ServiceModel 6 | RedisGeo.ServiceModel 7 | false 8 | false 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/RedisGeo.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26114.2 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisGeo", "RedisGeo\RedisGeo.csproj", "{EAF8171A-B8E6-4BC3-9A10-6F6C29E7A394}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisGeo.ServiceModel", "RedisGeo.ServiceModel\RedisGeo.ServiceModel.csproj", "{D6E47201-1CF1-48DA-8B40-7418070EC783}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisGeo.ServiceInterface", "RedisGeo.ServiceInterface\RedisGeo.ServiceInterface.csproj", "{E532E09D-D750-4A9F-B682-E7BA1508EA2C}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {EAF8171A-B8E6-4BC3-9A10-6F6C29E7A394}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {EAF8171A-B8E6-4BC3-9A10-6F6C29E7A394}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {EAF8171A-B8E6-4BC3-9A10-6F6C29E7A394}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {EAF8171A-B8E6-4BC3-9A10-6F6C29E7A394}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {D6E47201-1CF1-48DA-8B40-7418070EC783}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {D6E47201-1CF1-48DA-8B40-7418070EC783}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {D6E47201-1CF1-48DA-8B40-7418070EC783}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {D6E47201-1CF1-48DA-8B40-7418070EC783}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {E532E09D-D750-4A9F-B682-E7BA1508EA2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {E532E09D-D750-4A9F-B682-E7BA1508EA2C}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {E532E09D-D750-4A9F-B682-E7BA1508EA2C}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {E532E09D-D750-4A9F-B682-E7BA1508EA2C}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /src/RedisGeo/AppHost.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using Funq; 4 | using ServiceStack; 5 | using RedisGeo.ServiceInterface; 6 | using ServiceStack.Configuration; 7 | using ServiceStack.Redis; 8 | 9 | namespace RedisGeo 10 | { 11 | public class AppHost : AppHostBase 12 | { 13 | /// 14 | /// Configure your ServiceStack AppHost singleton instance: 15 | /// Call base constructor with App Name and assembly where Service classes are located 16 | /// 17 | public AppHost() 18 | : base("RedisGeo", typeof(RedisGeoServices).Assembly) {} 19 | 20 | public override void Configure(Container container) 21 | { 22 | container.Register(c => 23 | new RedisManagerPool(AppSettings.Get("REDIS_HOST", defaultValue:"localhost"))); 24 | 25 | ImportCountry(container.Resolve(), "US"); 26 | } 27 | 28 | public void ImportCountry(IRedisClientsManager redisManager, string countryCode) 29 | { 30 | using (var redis = redisManager.GetClient()) 31 | using (var reader = new StreamReader(File.OpenRead(MapProjectPath($"~/App_Data/{countryCode}.txt")))) 32 | { 33 | string line, lastState = null, lastCity = null; 34 | var results = new List(); 35 | while ((line = reader.ReadLine()) != null) 36 | { 37 | var parts = line.Split('\t'); 38 | var city = parts[2]; 39 | var state = parts[4]; 40 | var latitude = double.Parse(parts[9]); 41 | var longitude = double.Parse(parts[10]); 42 | 43 | if (city == lastCity) //Skip duplicate entries 44 | continue; 45 | else 46 | lastCity = city; 47 | 48 | if (lastState == null) 49 | lastState = state; 50 | 51 | if (state != lastState) 52 | { 53 | redis.AddGeoMembers(lastState, results.ToArray()); 54 | lastState = state; 55 | results.Clear(); 56 | } 57 | 58 | results.Add(new ServiceStack.Redis.RedisGeo(longitude, latitude, city)); 59 | } 60 | } 61 | 62 | using (var redis = redisManager.GetClient()) 63 | using (var reader = 64 | new StreamReader(File.OpenRead(MapProjectPath($"~/App_Data/{countryCode}_mapping.txt")))) 65 | { 66 | string line = null; 67 | while ((line = reader.ReadLine()) != null) 68 | { 69 | if (line.Trim().Length == 0) 70 | continue; 71 | var stateInfo = line.Split("\t"); 72 | if(stateInfo.Length != 2) 73 | continue; 74 | redis.Set("mapping:" + stateInfo[1].Trim(), stateInfo[0].Trim()); 75 | } 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /src/RedisGeo/App_Data/AU_mapping.txt: -------------------------------------------------------------------------------- 1 | ACT Australian Capital Territory 2 | QLD Queensland 3 | WA Western Australia 4 | NT Northern Terriory 5 | SA South Australia 6 | NSW New South Wales 7 | TAS Tasmania 8 | VIC Victoria 9 | -------------------------------------------------------------------------------- /src/RedisGeo/App_Data/US_mapping.txt: -------------------------------------------------------------------------------- 1 | AA Armed Forces America 2 | AE Armed Forces 3 | AK Alaska 4 | AL Alabama 5 | AP Armed Forces Pacific 6 | AR Arkansas 7 | AZ Arizona 8 | CA California 9 | CO Colorado 10 | CT Connecticut 11 | DC Washington DC 12 | DE Delaware 13 | FL Florida 14 | GA Georgia 15 | GU Guam 16 | HI Hawaii 17 | IA Iowa 18 | ID Idaho 19 | IL Illinois 20 | IN Indiana 21 | KS Kansas 22 | KY Kentucky 23 | LA Louisiana 24 | MA Massachusetts 25 | MD Maryland 26 | ME Maine 27 | MI Michigan 28 | MN Minnesota 29 | MO Missouri 30 | MS Mississippi 31 | MT Montana 32 | NC North Carolina 33 | ND North Dakota 34 | NE Nebraska 35 | NH New Hampshire 36 | NJ New Jersey 37 | NM New Mexico 38 | NV Nevada 39 | NY New York 40 | OH Ohio 41 | OK Oklahoma 42 | OR Oregon 43 | PA Pennsylvania 44 | PR Puerto Rico 45 | RI Rhode Island 46 | SC South Carolina 47 | SD South Dakota 48 | TN Tennessee 49 | TX Texas 50 | UT Utah 51 | VA Virginia 52 | VI Virgin Islands 53 | VT Vermont 54 | WA Washington 55 | WI Wisconsin 56 | WV West Virginia 57 | WY Wyoming 58 | -------------------------------------------------------------------------------- /src/RedisGeo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Builder; 8 | using Microsoft.AspNetCore.Hosting; 9 | 10 | namespace RedisGeo 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | BuildWebHost(args).Run(); 17 | } 18 | 19 | public static IWebHost BuildWebHost(string[] args) => 20 | WebHost.CreateDefaultBuilder(args) 21 | .UseStartup() 22 | .Build(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/RedisGeo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57573/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "RedisGeo": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/RedisGeo/RedisGeo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5 5 | true 6 | RedisGeo 7 | Exe 8 | RedisGeo 9 | 10 | 11 | 12 | 13 | PreserveNewest 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | Always 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/RedisGeo/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using ServiceStack; 9 | using ServiceStack.Configuration; 10 | 11 | namespace RedisGeo 12 | { 13 | public class Startup 14 | { 15 | IConfiguration Configuration { get; set; } 16 | public Startup(IConfiguration configuration) => Configuration = configuration; 17 | 18 | // This method gets called by the runtime. Use this method to add services to the container. 19 | // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | } 23 | 24 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 25 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 26 | { 27 | if (env.IsDevelopment()) 28 | { 29 | app.UseDeveloperExceptionPage(); 30 | } 31 | 32 | app.UseServiceStack(new AppHost 33 | { 34 | AppSettings = new NetCoreAppSettings(Configuration) 35 | }); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/RedisGeo/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DebugMode": true, 3 | "Logging": { 4 | "IncludeScopes": false, 5 | "Debug": { 6 | "LogLevel": { 7 | "Default": "Debug", 8 | "System": "Information", 9 | "Microsoft": "Information" 10 | } 11 | }, 12 | "Console": { 13 | "LogLevel": { 14 | "Default": "Debug", 15 | "System": "Information", 16 | "Microsoft": "Information" 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/RedisGeo/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "GMapsApiKey": "AIzaSyDz8ab2kD1_JyffvmcBC_asZl0EG0KTOaU", 3 | "Logging": { 4 | "IncludeScopes": false, 5 | "Debug": { 6 | "LogLevel": { 7 | "Default": "Warning" 8 | } 9 | }, 10 | "Console": { 11 | "LogLevel": { 12 | "Default": "Warning" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/RedisGeo/wwwroot/default.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Simple Map 5 | 6 | 7 | 8 | 11 | 14 | 53 | 54 | 55 | 56 | Fork me on GitHub 57 | 58 | 74 |
    75 | 76 | live demos 77 | 78 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/RedisGeo/wwwroot/js/jquery-2.2.3.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v2.2.3 | (c) jQuery Foundation | jquery.org/license */ 2 | !function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.3",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c; 3 | }catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length",""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,la=/\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n("