├── .gitignore ├── README.md ├── block-matchr-postgis.py ├── geocrawlr.py ├── geoplanet.sql ├── gis-db-schema.sql ├── log.txt ├── make-osm-blocks.py ├── match-shapefile-data.py ├── samples └── nyc-pediacities-neighborhoods-polygon-v2.geojson ├── testsite ├── LICENSE ├── Procfile ├── README.md ├── TODO.txt ├── Vagrantfile ├── app.db ├── app │ ├── __init__.py │ ├── api_routes.py │ ├── blockr.py │ ├── config │ │ ├── app.yml │ │ └── credentials.yml.example │ ├── flask_gzip.py │ ├── forms.py │ ├── geo_utils.py │ ├── helpers.py │ ├── middleware.py │ ├── models.py │ ├── outliers.py │ ├── static │ │ ├── .webassets-cache │ │ │ └── .gitignore │ │ ├── css │ │ │ ├── bootstrap-responsive.css │ │ │ ├── bootstrap-responsive.min.css │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.map.css │ │ │ ├── bootstrap.min.css │ │ │ ├── combobox.css │ │ │ ├── images │ │ │ │ ├── animated-overlay.gif │ │ │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ │ │ ├── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ │ │ ├── ui-icons_222222_256x240.png │ │ │ │ ├── ui-icons_2e83ff_256x240.png │ │ │ │ ├── ui-icons_454545_256x240.png │ │ │ │ ├── ui-icons_888888_256x240.png │ │ │ │ └── ui-icons_cd0a0a_256x240.png │ │ │ ├── jquery-ui.css │ │ │ ├── jquery.ui.accordion.css │ │ │ ├── jquery.ui.all.css │ │ │ ├── jquery.ui.autocomplete.css │ │ │ ├── jquery.ui.base.css │ │ │ ├── jquery.ui.button.css │ │ │ ├── jquery.ui.core.css │ │ │ ├── jquery.ui.datepicker.css │ │ │ ├── jquery.ui.dialog.css │ │ │ ├── jquery.ui.menu.css │ │ │ ├── jquery.ui.progressbar.css │ │ │ ├── jquery.ui.resizable.css │ │ │ ├── jquery.ui.selectable.css │ │ │ ├── jquery.ui.slider.css │ │ │ ├── jquery.ui.spinner.css │ │ │ ├── jquery.ui.tabs.css │ │ │ ├── jquery.ui.theme.css │ │ │ ├── jquery.ui.tooltip.css │ │ │ ├── keys.css │ │ │ ├── leaflet.css │ │ │ ├── leaflet.label.css │ │ │ ├── main.css │ │ │ ├── src │ │ │ │ └── styles.less │ │ │ ├── styles.css │ │ │ └── zetashapes.css │ │ ├── html │ │ │ ├── states.html │ │ │ └── us-states.json │ │ ├── images │ │ │ ├── layers-2x.png │ │ │ ├── layers.png │ │ │ ├── marker-icon-2x.png │ │ │ ├── marker-icon.png │ │ │ └── marker-shadow.png │ │ ├── img │ │ │ ├── demo-nyc.png │ │ │ ├── glyphicons-halflings-white.png │ │ │ ├── glyphicons-halflings.png │ │ │ └── spinner.gif │ │ └── js │ │ │ ├── Map.Draw.js │ │ │ ├── backbone.js │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.min.js │ │ │ ├── combobox.js │ │ │ ├── editor.js │ │ │ ├── jquery-1.9.1.js │ │ │ ├── jquery-1.9.1.min.js │ │ │ ├── jquery-migrate-1.1.1.min.js │ │ │ ├── jquery-ui.min.js │ │ │ ├── jquery.ui.accordion.min.js │ │ │ ├── jquery.ui.autocomplete.min.js │ │ │ ├── jquery.ui.button.min.js │ │ │ ├── jquery.ui.core.min.js │ │ │ ├── jquery.ui.datepicker.min.js │ │ │ ├── jquery.ui.dialog.min.js │ │ │ ├── jquery.ui.draggable.min.js │ │ │ ├── jquery.ui.droppable.min.js │ │ │ ├── jquery.ui.effect-blind.min.js │ │ │ ├── jquery.ui.effect-bounce.min.js │ │ │ ├── jquery.ui.effect-clip.min.js │ │ │ ├── jquery.ui.effect-drop.min.js │ │ │ ├── jquery.ui.effect-explode.min.js │ │ │ ├── jquery.ui.effect-fade.min.js │ │ │ ├── jquery.ui.effect-fold.min.js │ │ │ ├── jquery.ui.effect-highlight.min.js │ │ │ ├── jquery.ui.effect-pulsate.min.js │ │ │ ├── jquery.ui.effect-scale.min.js │ │ │ ├── jquery.ui.effect-shake.min.js │ │ │ ├── jquery.ui.effect-slide.min.js │ │ │ ├── jquery.ui.effect-transfer.min.js │ │ │ ├── jquery.ui.effect.min.js │ │ │ ├── jquery.ui.menu.min.js │ │ │ ├── jquery.ui.mouse.min.js │ │ │ ├── jquery.ui.position.min.js │ │ │ ├── jquery.ui.progressbar.min.js │ │ │ ├── jquery.ui.resizable.min.js │ │ │ ├── jquery.ui.selectable.min.js │ │ │ ├── jquery.ui.slider.min.js │ │ │ ├── jquery.ui.sortable.min.js │ │ │ ├── jquery.ui.spinner.min.js │ │ │ ├── jquery.ui.tabs.min.js │ │ │ ├── jquery.ui.tooltip.min.js │ │ │ ├── jquery.ui.widget.min.js │ │ │ ├── keymaster.min.js │ │ │ ├── leaflet-src.js │ │ │ ├── leaflet.draw.js │ │ │ ├── leaflet.js │ │ │ ├── leaflet.label-src.js │ │ │ ├── leaflet.label.js │ │ │ ├── leaflet.polylineDecorator.min.js │ │ │ ├── leaflet.spin.js │ │ │ ├── libs.js │ │ │ ├── libs │ │ │ ├── bootstrap.min.js │ │ │ └── jquery-1.7.1.min.js │ │ │ ├── main.js │ │ │ ├── places.js │ │ │ ├── src │ │ │ └── main.js │ │ │ └── underscore-min.js │ ├── templates │ │ ├── _macros.html │ │ ├── _messages.html │ │ ├── _nav.html │ │ ├── _place_search.html │ │ ├── admin.html │ │ ├── editor.html │ │ ├── index.html │ │ ├── layouts │ │ │ └── boilerplate.html │ │ ├── license.html │ │ ├── login.html │ │ ├── profile.html │ │ ├── register.html │ │ └── thanks.html │ ├── tools.py │ ├── views.py │ └── vote_utils.py ├── cookbooks │ └── flask-social-example │ │ ├── README.md │ │ ├── attributes │ │ └── default.rb │ │ ├── metadata.rb │ │ └── recipes │ │ └── default.rb ├── db_create.py ├── generate-areainfo.py ├── generate-cities.py ├── make-json.py ├── manage.py ├── places.js ├── requirements.txt └── wsgi.py └── votes.sql /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vagrant 3 | .settings 4 | .project 5 | .pydevproject 6 | testsite/app/data/* 7 | testsite/app/static/.webaassets-cache/* 8 | !testsite/app/static/.webassets-cache/.gitignore 9 | *.pyc 10 | testsite/app/config/*.yml 11 | !testsite/app/config/app.yml 12 | app/data/GeoLiteCity.dat 13 | testsite/app/config/credentials.yml 14 | *.cache 15 | *.json 16 | *.log 17 | testsite/app/static/json/* 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | the-neighborhoods-project 2 | ========================= 3 | 4 | it's a collection of things. around neighborhoods. get ready. -------------------------------------------------------------------------------- /block-matchr-postgis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import psycopg2 4 | from collections import defaultdict 5 | 6 | # SELECT * FROM "tabblock2010_36_pophu-900913" WHERE ST_CONTAINS(geom, ST_Transform(ST_GeomFromText('POINT(-74 40.74)', 4326), 900913)); 7 | 8 | import sys 9 | 10 | ID_COL = 'geoid10' 11 | TABLE = 'tabblock10' 12 | FROM_SRID = 4326 13 | TABLE_SRID = 4326 14 | # TABLE_SRID = 900913 15 | 16 | VOTE_TABLE = 'votes' 17 | 18 | conn = psycopg2.connect("dbname='gis' user='blackmad' host='localhost' password='xxx'") 19 | 20 | def process_file(input_file): 21 | counts = defaultdict(lambda: defaultdict(int)) 22 | c = 0 23 | for line in input_file: 24 | line = line.strip() 25 | c += 1 26 | if (c % 1000) == 0: 27 | print 'finished %d input lines' % c 28 | #3502363673 23511889 -73.961902 40.803524 29 | try: 30 | parts = line.split('\t') 31 | (junk, id, lon, lat) = parts[0], parts[1], parts[2], parts[3] 32 | cur = conn.cursor() 33 | cur.execute("""SELECT %s FROM "%s" WHERE ST_CONTAINS(geom, ST_Transform(ST_GeomFromText('POINT(%s %s)', %s), %s))""" % (ID_COL, TABLE, lon, lat, FROM_SRID, TABLE_SRID)) 34 | rows = cur.fetchall() 35 | 36 | #if len(rows) == 0: 37 | # print "no blocks found for %s %s" % (lat, lon) 38 | 39 | for row in rows: 40 | counts[row[0]][id] += 1 41 | except: 42 | print "bad line: " + line 43 | import traceback 44 | print traceback.print_exc() 45 | 46 | c = 0 47 | for blockid in counts: 48 | for woeid in counts[blockid]: 49 | c += 1 50 | cur = conn.cursor() 51 | cur.execute("""SELECT * FROM """ + VOTE_TABLE + """ WHERE id=%s AND label=%s AND source='flickr'""", ( 52 | blockid, woeid)) 53 | if len(cur.fetchall()) == 0: 54 | cur = conn.cursor() 55 | cur.execute("""INSERT INTO """ + VOTE_TABLE + """ (id, label, count, source) VALUES (%s, %s, %s, 'flickr');""", ( 56 | blockid, woeid, counts[blockid][woeid])) 57 | else: 58 | cur = conn.cursor() 59 | cur.execute("""UPDATE """ + VOTE_TABLE + """ SET label=%s, count=%s, source='flickr' WHERE id=%s AND source='flickr' AND label=%s""", ( 60 | woeid, counts[blockid][woeid], blockid, woeid)) 61 | 62 | #print "%s\t%s\t%s" % (blockid, woeid, counts[blockid][woeid]) 63 | if (c % 1000) == 0: 64 | conn.commit() 65 | conn.commit() 66 | 67 | for (index, f) in enumerate(sys.argv[1:]): 68 | print 'processing %s (%d of %d)' % (f, index, len(sys.argv) -1) 69 | process_file(open(f)) 70 | conn.close() 71 | 72 | 73 | -------------------------------------------------------------------------------- /geocrawlr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import Flickr.API 3 | import json, time, sys, os 4 | 5 | FLICKR_KEY = os.environ["FLICKR_KEY"] 6 | FLICKR_SECRET = os.environ["FLICKR_SECRET"] 7 | 8 | START_PAGE = 1 9 | END_PAGE = 100 10 | 11 | api = Flickr.API.API(FLICKR_KEY, FLICKR_SECRET) 12 | 13 | def process_file(file): 14 | for line in open(file): 15 | woe_id = line.split('\t')[0] 16 | output_filename = '%s-flickr.txt' % woe_id 17 | 18 | if os.path.exists(output_filename): 19 | print >>sys.stderr, ">>> skipping %s, already have output file %s" % (woe_id, output_filename) 20 | continue 21 | 22 | output_file = open(output_filename, 'w') 23 | 24 | print >>sys.stderr, ">>> scraping %s to %s" % (woe_id, output_filename) 25 | 26 | print >>sys.stderr, "WOEID:", woe_id 27 | page = total_pages = START_PAGE 28 | 29 | while page <= total_pages: 30 | print >>sys.stderr, ">>> Reading %d of %d... " % (page, total_pages), 31 | request = Flickr.API.Request( 32 | method="flickr.photos.search", 33 | format="json", 34 | nojsoncallback=1, 35 | sort="interestingness-desc", 36 | page=page, 37 | woe_id=woe_id, 38 | extras="geo", 39 | min_date_taken="2007-01-01 00:00:00" 40 | ) 41 | start = time.time() 42 | response = None 43 | while response is None: 44 | try: 45 | response = api.execute_request(request).read() 46 | except Exception, e: 47 | print >>sys.stderr, "Retrying due to:", e 48 | try: 49 | result = json.loads(response) 50 | result = result["photos"] 51 | print >>sys.stderr, "%d results, %.1fs elapsed." % (len(result["photo"]),time.time()-start) 52 | for item in result["photo"]: 53 | try: 54 | output_file.write("\t".join(str(item[k]) for k in ("id","woeid","longitude","latitude")) + "\n") 55 | except Exception, e: 56 | print >>sys.stderr, e 57 | total_pages = min(int(result["pages"]), END_PAGE) 58 | #time.sleep(1.0) 59 | except Exception, e: 60 | print >>sys.stderr, e 61 | page += 1 62 | output_file.close() 63 | 64 | for file in sys.argv[1:]: 65 | process_file(file) 66 | -------------------------------------------------------------------------------- /geoplanet.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS geoplanet_places; 2 | CREATE TABLE geoplanet_places 3 | ( 4 | woe_id integer NOT NULL, 5 | iso character varying(2) NOT NULL, 6 | "name" character varying(150) NOT NULL, 7 | "language" character varying(3) NOT NULL, 8 | placetype character varying(20) NOT NULL, 9 | parent_id integer NOT NULL, 10 | CONSTRAINT places_pkey PRIMARY KEY (woe_id) 11 | ); 12 | -------------------------------------------------------------------------------- /log.txt: -------------------------------------------------------------------------------- 1 | shp2pgsql -p -I -D tl_2010_06_tabblock10.shp tabblock10 | psql -U postgres gis 2 | # for i in *shp; do shp2pgsql -s 4326:900913 -a $i tabblock10 | psql -U postgres gis; done 3 | for i in *shp; do shp2pgsql -s 4326 -a $i tabblock10 | psql -U postgres gis; done 4 | 5 | 6 | psql gis -f votes.sql 7 | 8 | copy geoplanet_places FROM '/home/blackmad/geoplanet/geoplanet_places_7.10.0.tsv' WITH csv header delimiter e'\t'; 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /make-osm-blocks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from shapely.geometry import Point, Polygon, MultiPolygon, asShape, LineString 4 | from shapely.ops import cascaded_union, polygonize, transform 5 | import sys, json, math, pickle, os, geojson 6 | from shapely.validation import explain_validity 7 | from functools import partial 8 | import fiona 9 | import json 10 | import geojson 11 | import traceback 12 | import pyproj 13 | 14 | import rtree 15 | 16 | from optparse import OptionParser 17 | parser = OptionParser() 18 | parser.add_option("--reload", dest="reload", action="store_true", default=False) 19 | parser.add_option("-r", "--resegment", dest="resegment", action="store_true", default=False) 20 | (options, args) = parser.parse_args() 21 | 22 | rtree_basename = 'rtree' 23 | should_do_load = True 24 | if os.path.exists(rtree_basename + '.idx') and not options.reload: 25 | print 'found existing index %s.idx, and --reload not specified, reusing index' % rtree_basename 26 | should_do_load = False 27 | 28 | idx = rtree.index.Index(rtree_basename, overwrite = should_do_load) 29 | lineIndex = 0 30 | lines = {} 31 | minlat = sys.float_info.max 32 | minlng = sys.float_info.max 33 | maxlat = sys.float_info.min 34 | maxlng = sys.float_info.min 35 | def process_line(line): 36 | global lineIndex, minlat, minlng, maxlat, maxlng 37 | lineIndex += 1 38 | lines[lineIndex] = line 39 | for c in line.coords: 40 | minlat = min(minlat, c[1]) 41 | maxlat = max(maxlat, c[1]) 42 | minlng = min(minlng, c[0]) 43 | maxlng = max(maxlng, c[0]) 44 | 45 | def loadGenerator(): 46 | for f in args: 47 | source = fiona.open(f, 'r') 48 | for index, feature in enumerate(source): 49 | if index % 1000 == 0: 50 | print "loaded %s of %s" % (index, len(source)) 51 | if feature['geometry']['type'] == 'LineString': 52 | coords = feature['geometry']['coordinates'] 53 | for (start, end) in zip(coords[:-1], coords[1:]): 54 | line = LineString([start, end]) 55 | process_line(line) 56 | yield (lineIndex, line.bounds, line) 57 | 58 | if feature['geometry']['type'] == 'MultiLineString': 59 | print 'got multilinestring' 60 | for coords in feature['geometry']['coordinates']: 61 | print coords 62 | for (start, end) in zip(coords[:-1], coords[1:]): 63 | line = LineString([start, end]) 64 | process_line(line) 65 | yield (lineIndex, line.bounds, line) 66 | 67 | # load everything into an rtree 68 | def doLoad(): 69 | global idx 70 | idx = rtree.index.Index(loadGenerator()) 71 | 72 | # for every line, fine all the intersections 73 | def doResegment(): 74 | global lines 75 | newLines = [] 76 | for lineIndex, line in enumerate(lines.values()): 77 | if lineIndex % 1000 == 0: 78 | print "processed %s of %s" % (lineIndex, len(lines)) 79 | 80 | lineParts = [line] 81 | # split the line into all the intersecting pieces 82 | intersections = idx.intersection(line.bounds, objects = True) 83 | for intersectingLine in intersections: 84 | newLineParts = [] 85 | for linePart in lineParts: 86 | intersection = linePart.intersection(intersectingLine.object) 87 | if intersection.is_empty or intersection.coords[0] in linePart.coords: 88 | newLineParts.append(linePart) 89 | else: 90 | newLineParts.append(LineString([linePart.coords[0], intersection.coords[0]])) 91 | newLineParts.append(LineString([linePart.coords[1], intersection.coords[0]])) 92 | lineParts = newLineParts 93 | newLines = newLines + lineParts 94 | 95 | lines = newLines 96 | 97 | def writeBlocks(blocks, filename): 98 | outputFile = open(filename, 'w') 99 | outputFile.write('{ "type": "FeatureCollection", "features": [') 100 | 101 | for index, poly in enumerate(blocks): 102 | if index % 1000 == 0: 103 | print "wrote %s blocks so far" % (index) 104 | 105 | try: 106 | if not poly.is_valid: 107 | poly = poly.buffer(0) 108 | 109 | jsonGeom = json.loads(geojson.dumps(poly.__geo_interface__)) 110 | 111 | if index != 0: 112 | outputFile.write(',') 113 | outputFile.write(json.dumps({'geometry': jsonGeom, 'properties': {}})) 114 | except: 115 | print b 116 | print traceback.print_exc() 117 | outputFile.write(']}') 118 | outputFile.close() 119 | 120 | def doPolygonize(): 121 | blocks = polygonize(lines) 122 | writeBlocks(blocks, args[0] + '-blocks.geojson') 123 | 124 | blocks = polygonize(lines) 125 | bounds = Polygon([ 126 | [minlng, minlat], 127 | [minlng, maxlat], 128 | [maxlng, maxlat], 129 | [maxlng, minlat], 130 | [minlng, minlat] 131 | ]) 132 | # Geometry transform function based on pyproj.transform 133 | project = partial( 134 | pyproj.transform, 135 | pyproj.Proj(init='EPSG:3785'), 136 | pyproj.Proj(init='EPSG:4326')) 137 | print bounds 138 | print transform(project, bounds) 139 | 140 | print 'finding holes' 141 | for index, block in enumerate(blocks): 142 | if index % 1000 == 0: 143 | print "diff'd %s" % (index) 144 | if not block.is_valid: 145 | print explain_validity(block) 146 | print transform(project, block) 147 | else: 148 | bounds = bounds.difference(block) 149 | print bounds 150 | 151 | if should_do_load: 152 | doLoad() 153 | if options.resegment: 154 | doResegment() 155 | else: 156 | lines = lines.values() 157 | doPolygonize() 158 | 159 | -------------------------------------------------------------------------------- /match-shapefile-data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import fiona 4 | from shapely.geometry import asShape 5 | from shapely.geometry import mapping 6 | from collections import namedtuple 7 | import psycopg2 8 | import sys 9 | import itertools 10 | 11 | def levenshtein(a,b): 12 | "Calculates the Levenshtein distance between a and b." 13 | n, m = len(a), len(b) 14 | if n > m: 15 | # Make sure n <= m, to use O(min(n,m)) space 16 | a,b = b,a 17 | n,m = m,n 18 | 19 | current = range(n+1) 20 | for i in range(1,m+1): 21 | previous, current = current, [i]+[0]*n 22 | for j in range(1,n+1): 23 | add, delete = previous[j]+1, current[j-1]+1 24 | change = previous[j-1] 25 | if a[j-1] != b[i-1]: 26 | change = change + 1 27 | current[j] = min(add, delete, change) 28 | 29 | return current[n] 30 | 31 | def query_yes_no(question, default="yes"): 32 | """Ask a yes/no question via raw_input() and return their answer. 33 | 34 | "question" is a string that is presented to the user. 35 | "default" is the presumed answer if the user just hits . 36 | It must be "yes" (the default), "no" or None (meaning 37 | an answer is required of the user). 38 | 39 | The "answer" return value is one of "yes" or "no". 40 | """ 41 | valid = {"yes":True, "y":True, "ye":True, 42 | "no":False, "n":False} 43 | if default == None: 44 | prompt = " [y/n] " 45 | elif default == "yes": 46 | prompt = " [Y/n] " 47 | elif default == "no": 48 | prompt = " [y/N] " 49 | else: 50 | raise ValueError("invalid default answer: '%s'" % default) 51 | 52 | while True: 53 | sys.stdout.write(question + prompt) 54 | choice = raw_input().lower() 55 | if default is not None and choice == '': 56 | return valid[default] 57 | elif choice in valid: 58 | return valid[choice] 59 | else: 60 | sys.stdout.write("Please respond with 'yes' or 'no' "\ 61 | "(or 'y' or 'n').\n") 62 | 63 | conn = psycopg2.connect("dbname='gis' user='blackmad' host='localhost' password='xxx'") 64 | cur = conn.cursor() 65 | 66 | def get_all_parents(woe_id): 67 | parents = [] 68 | while 1 not in parents: 69 | cur.execute("select parent_id FROM geoplanet_places WHERE woe_id = %s", (woe_id,)) 70 | rows = cur.fetchall() 71 | parents.append(rows[0][0]) 72 | woe_id = rows[0][0] 73 | return parents 74 | 75 | def find_place(name, placetype, parent=None): 76 | print 'looking for %s of %s in %s' % (name, placetype, parent) 77 | cur.execute("select woe_id, parent_id FROM geoplanet_places WHERE name = %s AND placetype = %s", (name, placetype)) 78 | rows = cur.fetchall() 79 | print rows 80 | if parent: 81 | rows = [r for r in rows if parent in get_all_parents(r[1])] 82 | if len(rows) > 1: 83 | print 'ambiguous!!!' 84 | print rows 85 | sys.exit(1) 86 | if len(rows) == 0: 87 | print "could not find at all" 88 | sys.exit(1) 89 | return rows[0][0] 90 | 91 | def find_children(ids): 92 | if len(ids) == 0: 93 | return [] 94 | cur.execute("select woe_id FROM geoplanet_places WHERE parent_id IN %s", (tuple(ids),)) 95 | rows = list([i[0] for i in cur.fetchall()]) 96 | return rows + find_children(rows) 97 | 98 | filename = sys.argv[1] 99 | placeparts = sys.argv[2].split(',') 100 | colname = sys.argv[3] 101 | sourcename = sys.argv[4] 102 | areaids = sys.argv[5].split(',') 103 | 104 | parentid = None 105 | for i in range(0, len(placeparts), 2): 106 | placename = placeparts[i] 107 | placetype = placeparts[i+1] 108 | parentid = find_place(placename, placetype, parentid) 109 | 110 | def normalize(s): 111 | return s.lower().replace(' ', '').replace('-', '') 112 | 113 | validids = find_children([parentid,]) 114 | cur.execute("select name, woe_id FROM geoplanet_places WHERE woe_id IN %s AND placetype='Suburb'", (tuple(validids),)) 115 | placeDict = {} 116 | placeNameDict = {} 117 | for r in cur.fetchall(): 118 | name = normalize(r[0]) 119 | placeDict[name] = r[1] 120 | placeNameDict[r[1]] = name 121 | 122 | bestLabels = {} 123 | 124 | def add_place(name): 125 | print 'adding place: %s' % name 126 | cur.execute("insert into geoplanet_places values ((select max(woe_id) FROM geoplanet_places) +1, 'US', %s, 'en', 'Suburb', %s) RETURNING woe_id", (name, parentid)) 127 | conn.commit() 128 | id = cur.fetchone()[0] 129 | placeDict[name] = id 130 | return id 131 | 132 | BlockOverlap = namedtuple('BlockOverlap', ['hoodid', 'overlap']) 133 | 134 | for f in fiona.open(filename, 'r'): 135 | hoodid = None 136 | hoodname = normalize(f['properties'][colname]) 137 | if hoodname not in placeDict: 138 | print f['properties'][colname] 139 | print hoodname 140 | for n in placeDict.keys(): 141 | if levenshtein(n, hoodname) < 5: 142 | if query_yes_no('Accept match %s %s?' % (n, hoodname), 'no'): 143 | hoodid = placeDict[n] 144 | else: 145 | hoodid = add_place(f['properties'][colname]) 146 | break 147 | if hoodid is None: 148 | hoodid = add_place(f['properties'][colname]) 149 | else: 150 | hoodid = placeDict[hoodname] 151 | 152 | if not hoodid: 153 | continue 154 | 155 | wkt = asShape(f['geometry']).wkt 156 | print "Examining %s %s: %s" % (hoodid, hoodname, wkt) 157 | cur.execute("""select geoid10, ST_Area(ST_Intersection(geom, ST_GeomFromText(%s, 4326))) / ST_Area(geom) FROM tabblock10 tb WHERE ST_Intersects(geom, ST_Transform(ST_GeomFromText(%s, 4326), 4326)) AND blockce10 NOT LIKE '0%%'""", (wkt,wkt)) 158 | for r in cur.fetchall(): 159 | geoid = r[0] 160 | overlap = r[1] 161 | if overlap*100 < 10: 162 | continue 163 | 164 | print "overlapping block %s %s%%" % (geoid, overlap*100) 165 | if geoid not in bestLabels: 166 | bestLabels[geoid] = BlockOverlap(hoodid, overlap) 167 | elif bestLabels[geoid].overlap < overlap: 168 | print 'superceding %s for %s with %s' % (bestLabels[geoid], geoid, hoodid) 169 | bestLabels[geoid] = BlockOverlap(hoodid, overlap) 170 | 171 | source = 'official-%s' % sourcename 172 | cur.execute("""DELETE FROM votes WHERE source = %s""", (source,)) 173 | 174 | for (geoid, hoodid) in bestLabels.items(): 175 | cur.execute("""INSERT INTO votes (id, label, count, source) values (%s, %s, %s, %s)""", ( 176 | geoid, hoodid[0], 1, source)) 177 | 178 | for areaid in areaids: 179 | statefp10 = areaid[0:2] 180 | countyfp10 = areaid[2:] 181 | cur.execute("""select geoid10 FROM tabblock10 tb WHERE statefp10 = %s AND countyfp10 = %s AND blockce10 NOT LIKE '0%%' AND geoid10 NOT IN %s""", (statefp10, countyfp10, tuple(bestLabels.keys()))) 182 | unlabeledBlockIds = [i[0] for i in cur.fetchall()] 183 | for geoid in unlabeledBlockIds: 184 | cur.execute("""INSERT INTO votes (id, label, count, source) values (%s, %s, %s, %s)""", ( 185 | geoid, '-1', 1, source)) 186 | 187 | conn.commit() 188 | -------------------------------------------------------------------------------- /testsite/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2012 by Matt Wright 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /testsite/Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn wsgi:app -b 0.0.0.0:$PORT -w 3 -------------------------------------------------------------------------------- /testsite/README.md: -------------------------------------------------------------------------------- 1 | # Flask-Social Example Application 2 | 3 | This application illustrates how to use [Flask-Social](http://packages.python.org/Flask-Social). It is developed for deployment at [Heroku](http://www.heroku.com). 4 | 5 | This application is currently deployed at [http://flask-social-example.herokuapp.com](http://flask-social-example.herokuapp.com) 6 | -------------------------------------------------------------------------------- /testsite/TODO.txt: -------------------------------------------------------------------------------- 1 | --add neighborhood 2 | --allow overlaps 3 | --start using sqlalchemy everywhere 4 | 5 | --change how we fetch votes 6 | --figure out what to do about brooklyn piers 7 | 8 | - Style these buttons! http://cl.ly/image/2V0i02413d12 9 | 10 | --do something with unassigned blocks? 11 | --delete neighborhood 12 | --send down metros instead of counties? 13 | --allow people to fetch the raw data 14 | -------------------------------------------------------------------------------- /testsite/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant::Config.run do |config| 5 | config.vm.define :flask_social_example_vm do |config| 6 | config.vm.box = "precise64" 7 | config.vm.box_url = "http://files.vagrantup.com/precise64.box" 8 | config.vm.network :hostonly, "192.168.0.10" 9 | config.vm.provision :chef_solo do |chef| 10 | chef.cookbooks_path = "cookbooks" 11 | chef.add_recipe "flask-social-example" 12 | chef.json.merge!({ 13 | :postgresql => { 14 | :config => { 15 | :listen_addresses => "*" 16 | }, 17 | :password => { 18 | :postgres => "password" 19 | }, 20 | :pg_hba => [ 21 | {:type => 'local', :db => 'all', :user => 'postgres', :addr => nil, :method => 'trust'}, 22 | {:type => 'local', :db => 'all', :user => 'all', :addr => nil, :method => 'trust'}, 23 | {:type => 'host', :db => 'all', :user => 'all', :addr => 'all', :method => 'trust'} 24 | ] 25 | } 26 | }) 27 | end 28 | end 29 | end -------------------------------------------------------------------------------- /testsite/app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app.db -------------------------------------------------------------------------------- /testsite/app/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from flask import redirect, url_for, session 3 | from flask.ext.security import Security, SQLAlchemyUserDatastore 4 | from flask.ext.social import Social, SQLAlchemyConnectionDatastore, \ 5 | login_failed 6 | from flask.ext.social.utils import get_conection_values_from_oauth_response 7 | from flask.ext.sqlalchemy import SQLAlchemy 8 | 9 | from .helpers import Flask 10 | from .middleware import MethodRewriteMiddleware 11 | import os 12 | import flask_gzip 13 | import flask_social.providers.foursquare 14 | import foursquare 15 | 16 | def get_provider_user_id(response, **kwargs): 17 | if response: 18 | api = foursquare.Foursquare(access_token=response['access_token']) 19 | return api.users()['user']['id'] 20 | return None 21 | flask_social.providers.foursquare.get_provider_user_id = get_provider_user_id 22 | 23 | app = Flask(__name__) 24 | app.config.from_yaml(app.root_path) 25 | app.config['DEBUG'] = True 26 | app.wsgi_app = MethodRewriteMiddleware(app.wsgi_app) 27 | app.secret_key = 'why would I tell you my secret key?' 28 | 29 | flask_gzip.Gzip(app) 30 | 31 | basedir = os.path.abspath('.') 32 | # app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'app.db') 33 | app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres@localhost:5432/gis' 34 | db = SQLAlchemy(app) 35 | 36 | # Late import so modules can import their dependencies properly 37 | from . import models, views, api_routes 38 | 39 | 40 | security_ds = SQLAlchemyUserDatastore(db, models.User, models.Role) 41 | social_ds = SQLAlchemyConnectionDatastore(db, models.Connection) 42 | 43 | app.security = Security(app, security_ds) 44 | app.social = Social(app, social_ds) 45 | 46 | 47 | class SocialLoginError(Exception): 48 | def __init__(self, provider): 49 | self.provider = provider 50 | 51 | 52 | @app.before_first_request 53 | def before_first_request(): 54 | try: 55 | models.db.create_all() 56 | except Exception, e: 57 | app.logger.error(str(e)) 58 | 59 | 60 | @app.context_processor 61 | def template_extras(): 62 | return dict( 63 | google_analytics_id=app.config.get('GOOGLE_ANALYTICS_ID', None)) 64 | 65 | 66 | @login_failed.connect_via(app) 67 | def on_login_failed(sender, provider, oauth_response): 68 | app.logger.debug('Social Login Failed via %s; ' 69 | '&oauth_response=%s' % (provider.name, oauth_response)) 70 | 71 | # Save the oauth response in the session so we can make the connection 72 | # later after the user possibly registers 73 | session['failed_login_connection'] = \ 74 | get_conection_values_from_oauth_response(provider, oauth_response) 75 | 76 | raise SocialLoginError(provider) 77 | 78 | 79 | @app.errorhandler(SocialLoginError) 80 | def social_login_error(error): 81 | return redirect( 82 | url_for('register', provider_id=error.provider.id, login_failed=1)) 83 | -------------------------------------------------------------------------------- /testsite/app/config/app.yml: -------------------------------------------------------------------------------- 1 | COMMON: &common 2 | SECRET_KEY: insecure1 3 | SECURITY_POST_LOGIN_VIEW: '/profile' 4 | 5 | ADMIN_CREDENTIALS: 'admin,password' 6 | 7 | SECURITY_TRACKABLE: True 8 | SOCIAL_CONNECT_ALLOW_VIEW: '/profile' 9 | SOCIAL_APP_URL: http://flask-social-example.herokuapp.com/ 10 | URL: http://flask-social-example.herokuapp.com/ 11 | 12 | DEVELOPMENT: &development 13 | <<: *common 14 | SQLALCHEMY_DATABASE_URI: 'postgresql://postgres@localhost:5432/gisdev' 15 | DEBUG: True 16 | 17 | PRODUCTION: &production 18 | <<: *common 19 | SQLALCHEMY_DATABASE_URI: 'postgresql://postgres@localhost:5432/gis' 20 | DEBUG: True 21 | SECURITY_PASSWORD_HASH: bcrypt 22 | SOCIAL_APP_URL: http://flask-social-example.herokuapp.com/ 23 | -------------------------------------------------------------------------------- /testsite/app/config/credentials.yml.example: -------------------------------------------------------------------------------- 1 | 2 | DEVELOPMENT: 3 | SOCIAL_TWITTER: 4 | consumer_key: 'dev consumer key' 5 | consumer_secret: 'dev consumer secret' 6 | 7 | SOCIAL_FACEBOOK: 8 | consumer_key: 'dev app id' 9 | consumer_secret: 'dev app secret' 10 | request_token_params: 11 | scope: 'email,publish_stream' 12 | 13 | SOCIAL_GITHUB: 14 | consumer_key: 'dev app id' 15 | consumer_secret: 'dev app secret' 16 | module: 'app.github' 17 | request_token_params: 18 | scope: 'user' 19 | 20 | PRODUCTION: 21 | SECRET_KEY: somethingsecret 22 | GOOGLE_ANALYTICS_ID: your_GA_id 23 | 24 | SOCIAL_TWITTER: 25 | consumer_key: 'production consumer key' 26 | consumer_secret: 'production consumer secret' 27 | 28 | SOCIAL_FACEBOOK: 29 | consumer_key: 'production app id' 30 | consumer_secret: 'production app secret' 31 | request_token_params: 32 | scope: 'email,publish_stream' 33 | 34 | SOCIAL_GITHUB: 35 | consumer_key: 'production app id' 36 | consumer_secret: 'production app secret' 37 | module: 'app.github' 38 | request_token_params: 39 | scope: 'user' -------------------------------------------------------------------------------- /testsite/app/flask_gzip.py: -------------------------------------------------------------------------------- 1 | import gzip 2 | import StringIO 3 | from flask import request 4 | 5 | 6 | class Gzip(object): 7 | def __init__(self, app, compress_level=6, minimum_size=500): 8 | self.app = app 9 | self.compress_level = compress_level 10 | self.minimum_size = minimum_size 11 | self.app.after_request(self.after_request) 12 | 13 | def after_request(self, response): 14 | accept_encoding = request.headers.get('Accept-Encoding', '') 15 | 16 | if 'gzip' not in accept_encoding.lower(): 17 | return response 18 | 19 | if (200 > response.status_code >= 300) or len(response.data) < self.minimum_size or 'Content-Encoding' in response.headers: 20 | return response 21 | 22 | gzip_buffer = StringIO.StringIO() 23 | gzip_file = gzip.GzipFile(mode='wb', compresslevel=self.compress_level, fileobj=gzip_buffer) 24 | gzip_file.write(response.data) 25 | gzip_file.close() 26 | response.data = gzip_buffer.getvalue() 27 | response.headers['Content-Encoding'] = 'gzip' 28 | response.headers['Content-Length'] = len(response.data) 29 | 30 | return response 31 | -------------------------------------------------------------------------------- /testsite/app/forms.py: -------------------------------------------------------------------------------- 1 | 2 | from flask import current_app 3 | from flask.ext.wtf import (Form, TextField, PasswordField, Required, Email, 4 | Length, Regexp, ValidationError, EqualTo) 5 | 6 | 7 | class UniqueUser(object): 8 | def __init__(self, message="User exists"): 9 | self.message = message 10 | 11 | def __call__(self, form, field): 12 | if current_app.security.datastore.find_user(email=field.data): 13 | raise ValidationError(self.message) 14 | 15 | validators = { 16 | 'email': [ 17 | Required(), 18 | Email(), 19 | UniqueUser(message='Email address is associated with ' 20 | 'an existing account') 21 | ] 22 | } 23 | 24 | 25 | class RegisterForm(Form): 26 | email = TextField('Email', validators['email']) 27 | -------------------------------------------------------------------------------- /testsite/app/geo_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import json 4 | import re 5 | from functools import wraps 6 | from collections import namedtuple 7 | import psycopg2 8 | import psycopg2.extras 9 | from collections import defaultdict 10 | import os 11 | import json 12 | from itertools import groupby 13 | from shapely.ops import cascaded_union 14 | from shapely.geometry import mapping, asShape 15 | from shapely import speedups 16 | import shapely 17 | import shapely.geometry 18 | import vote_utils 19 | from shapely.ops import transform 20 | from functools import partial 21 | import pyproj 22 | 23 | state_codes = { 24 | 'WA': '53', 'DE': '10', 'DC': '11', 'WI': '55', 'WV': '54', 'HI': '15', 25 | 'FL': '12', 'WY': '56', 'PR': '72', 'NJ': '34', 'NM': '35', 'TX': '48', 26 | 'LA': '22', 'NC': '37', 'ND': '38', 'NE': '31', 'TN': '47', 'NY': '36', 27 | 'PA': '42', 'AK': '02', 'NV': '32', 'NH': '33', 'VA': '51', 'CO': '08', 28 | 'CA': '06', 'AL': '01', 'AR': '05', 'VT': '50', 'IL': '17', 'GA': '13', 29 | 'IN': '18', 'IA': '19', 'MA': '25', 'AZ': '04', 'ID': '16', 'CT': '09', 30 | 'ME': '23', 'MD': '24', 'OK': '40', 'OH': '39', 'UT': '49', 'MO': '29', 31 | 'MN': '27', 'MI': '26', 'RI': '44', 'KS': '20', 'MT': '30', 'MS': '28', 32 | 'SC': '45', 'KY': '21', 'OR': '41', 'SD': '46' 33 | } 34 | 35 | fips_codes = {v:k for k, v in state_codes.iteritems()} 36 | 37 | def areaInfo(rows): 38 | responses = [] 39 | for r in rows: 40 | d = { 41 | 'displayName': "%s, %s" % (r['name10'], fips_codes[r['statefp10']]), 42 | 'name': r['name10'], 43 | 'state': fips_codes[r['statefp10']], 44 | 'areaid': r['geoid10'], 45 | 'lat': r['intptlat10'], 46 | 'lng': r['intptlon10'], 47 | } 48 | if 'bbox' in r: 49 | d['bbox'] = json.loads(r['bbox']) 50 | if 'geojson' in r: 51 | d['geom'] = json.loads(r['geojson']) 52 | responses.append(d) 53 | return responses 54 | 55 | def getNearestCounties(conn, lat, lng): 56 | cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) 57 | cur.execute("""select * FROM tl_2010_us_county10 WHERE ST_DWithin(ST_SetSRID(ST_MakePoint(%s, %s), 4326), geom, 0.1)""", (lng, lat)) 58 | rows = cur.fetchall() 59 | return areaInfo(rows) 60 | 61 | def getInfoForAreaIds(conn, areaids): 62 | if areaids: 63 | cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) 64 | cur.execute("""select *,ST_AsGeoJson(ST_Envelope(geom)) as bbox FROM tl_2010_us_county10 WHERE geoid10 IN %s""", (tuple(areaids),)) 65 | rows = cur.fetchall() 66 | return areaInfo(rows) 67 | else: 68 | return [] 69 | 70 | def getInfoForNearbyAreaIds(conn, areaids): 71 | if areaids: 72 | cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) 73 | cur.execute("""select *,ST_AsGeoJson(c1.geom) as geojson, ST_AsGeoJson(ST_Envelope(c1.geom)) as bbox FROM tl_2010_us_county10 c1 JOIN (select geom FROM tl_2010_us_county10 WHERE geoid10 IN %s) c2 ON c1.geom && c2.geom WHERE geoid10 NOT IN %s""", (tuple(areaids), tuple(areaids))) 74 | rows = cur.fetchall() 75 | return areaInfo(rows) 76 | else: 77 | return [] 78 | 79 | NeighborhoodArea = namedtuple('NeighborhoodArea', ['shape', 'blockids', 'pop10', 'housing10']) 80 | def getNeighborhoodsByAreas(conn, areaids, user): 81 | print 'getting votes' 82 | (blocks, allVotes) = vote_utils.getVotes(conn, areaids, user) 83 | print 'got votes' 84 | 85 | blocks_by_hoodid = defaultdict(list) 86 | id_to_label = {} 87 | 88 | for block in blocks: 89 | votes = allVotes[block['geoid10']] 90 | #print block['geoid10'] 91 | maxVotes = vote_utils.pickBestVotes(votes) 92 | for maxVote in maxVotes: 93 | blocks_by_hoodid[maxVote['id']].append(block) 94 | id_to_label[maxVote['id']] = maxVote['label'] 95 | 96 | hoods = {} 97 | print 'doing unions' 98 | for (id, blocks) in blocks_by_hoodid.iteritems(): 99 | geoms = [asShape(eval(block['geojson_geom'])) for block in blocks] 100 | blockids = [block['geoid10'] for block in blocks] 101 | pop10 = sum([block['pop10'] for block in blocks]) 102 | housing10 = sum([block['housing10'] for block in blocks]) 103 | 104 | geom = cascaded_union(geoms) 105 | hoods[id] = NeighborhoodArea(geom, blockids, pop10, housing10) 106 | return (hoods, id_to_label) 107 | 108 | def reproject(latlngs): 109 | """Returns the x & y coordinates in meters using a sinusoidal projection""" 110 | from math import pi, cos, radians 111 | earth_radius = 6371009 # in meters 112 | lat_dist = pi * earth_radius / 180.0 113 | y = [ll[0] * lat_dist for ll in latlngs] 114 | x = [long * lat_dist * cos(radians(lat)) 115 | for lat, long in latlngs] 116 | return x, y 117 | 118 | def area_of_polygon(x, y): 119 | """Calculates the area of an arbitrary polygon given its verticies""" 120 | area = 0.0 121 | for i in xrange(-1, len(x)-1): 122 | area += x[i] * (y[i+1] - y[i-1]) 123 | return abs(area) / 2.0 124 | 125 | def area_of_shape(shape): 126 | (x, y) = reproject([(ll[1], ll[0]) for ll in shape.exterior.coords]) 127 | return area_of_polygon(x, y) 128 | 129 | def getNeighborhoodsGeoJsonByAreas(conn, areaids, user): 130 | (hoods, id_to_label) = getNeighborhoodsByAreas(conn, areaids, user) 131 | neighborhoods = [] 132 | 133 | for (id, nhoodarea) in hoods.iteritems(): 134 | shape = nhoodarea.shape 135 | area_m = 0 136 | if type(shape) == shapely.geometry.Polygon: 137 | area_m = area_of_shape(shape) 138 | elif type(shape) == shapely.geometry.MultiPolygon: 139 | area_m = sum([area_of_shape(s) for s in shape.geoms]) 140 | else: 141 | print 'unkown shape type: ' + type(shape) 142 | 143 | geojson = { 144 | 'type': 'Feature', 145 | 'properties': { 146 | 'id': id, 147 | 'area_m': area_m, 148 | 'label': id_to_label[id], 149 | 'blockids': ','.join(nhoodarea.blockids), 150 | 'pop10': nhoodarea.pop10, 151 | 'housing10': nhoodarea.housing10 152 | }, 153 | 'geometry': mapping(nhoodarea.shape) 154 | } 155 | neighborhoods.append(geojson) 156 | return neighborhoods 157 | 158 | 159 | -------------------------------------------------------------------------------- /testsite/app/helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | 4 | from flask import Flask as BaseFlask, Config as BaseConfig 5 | 6 | 7 | class Config(BaseConfig): 8 | 9 | def from_heroku(self): 10 | # Register database schemes in URLs. 11 | for key in ['DATABASE_URL']: 12 | if key in os.environ: 13 | self['SQLALCHEMY_DATABASE_URI'] = os.environ[key] 14 | break 15 | 16 | for key in ['SECRET_KEY', 'GOOGLE_ANALYTICS_ID', 'ADMIN_CREDENTIALS']: 17 | if key in os.environ: 18 | self[key] = os.environ[key] 19 | 20 | for key_prefix in ['TWITTER', 'FACEBOOK', 'GITHUB', 'GOOGLE', 'FOURSQUARE']: 21 | for key_suffix in ['key', 'secret']: 22 | ev = '%s_CONSUMER_%s' % (key_prefix, key_suffix.upper()) 23 | if ev in os.environ: 24 | social_key = 'SOCIAL_' + key_prefix 25 | oauth_key = 'consumer_' + key_suffix 26 | self[social_key][oauth_key] = os.environ[ev] 27 | 28 | def from_yaml(self, root_path): 29 | env = os.environ.get('FLASK_ENV', 'development').upper() 30 | self['ENVIRONMENT'] = env.lower() 31 | 32 | for fn in ('app', 'credentials'): 33 | config_file = os.path.join(root_path, 'config', '%s.yml' % fn) 34 | print config_file 35 | 36 | try: 37 | with open(config_file) as f: 38 | c = yaml.load(f) 39 | 40 | c = c.get(env, c) 41 | 42 | for key in c.iterkeys(): 43 | if key.isupper(): 44 | self[key] = c[key] 45 | except: 46 | pass 47 | 48 | 49 | class Flask(BaseFlask): 50 | """Extended version of `Flask` that implements custom config class 51 | and adds `register_middleware` method""" 52 | 53 | def make_config(self, instance_relative=False): 54 | root_path = self.root_path 55 | if instance_relative: 56 | root_path = self.instance_path 57 | return Config(root_path, self.default_config) 58 | 59 | def register_middleware(self, middleware_class): 60 | """Register a WSGI middleware on the application 61 | :param middleware_class: A WSGI middleware implementation 62 | """ 63 | self.wsgi_app = middleware_class(self.wsgi_app) 64 | -------------------------------------------------------------------------------- /testsite/app/middleware.py: -------------------------------------------------------------------------------- 1 | from werkzeug import url_decode 2 | 3 | 4 | class MethodRewriteMiddleware(object): 5 | 6 | def __init__(self, app): 7 | self.app = app 8 | 9 | def __call__(self, environ, start_response): 10 | if 'METHOD_OVERRIDE' in environ.get('QUERY_STRING', ''): 11 | args = url_decode(environ['QUERY_STRING']) 12 | method = args.get('__METHOD_OVERRIDE__') 13 | if method: 14 | method = method.encode('ascii', 'replace') 15 | environ['REQUEST_METHOD'] = method 16 | return self.app(environ, start_response) 17 | -------------------------------------------------------------------------------- /testsite/app/models.py: -------------------------------------------------------------------------------- 1 | 2 | from flask.ext.security import UserMixin, RoleMixin 3 | 4 | from . import db 5 | 6 | 7 | roles_users = db.Table('roles_users', 8 | db.Column('user_id', db.Integer(), db.ForeignKey('users.id')), 9 | db.Column('role_id', db.Integer(), db.ForeignKey('roles.id'))) 10 | 11 | 12 | class Role(db.Model, RoleMixin): 13 | 14 | __tablename__ = "roles" 15 | 16 | id = db.Column(db.Integer(), primary_key=True) 17 | name = db.Column(db.String(80), unique=True) 18 | description = db.Column(db.String(255)) 19 | 20 | 21 | class User(db.Model, UserMixin): 22 | 23 | __tablename__ = "users" 24 | 25 | id = db.Column(db.Integer, primary_key=True) 26 | level = db.Column(db.Integer, default=0) 27 | email = db.Column(db.String(255), unique=True) 28 | password = db.Column(db.String(120)) 29 | active = db.Column(db.Boolean()) 30 | last_login_at = db.Column(db.DateTime()) 31 | current_login_at = db.Column(db.DateTime()) 32 | last_login_ip = db.Column(db.String(100)) 33 | current_login_ip = db.Column(db.String(100)) 34 | login_count = db.Column(db.Integer) 35 | api_key = db.Column(db.String(120)) 36 | roles = db.relationship('Role', secondary=roles_users, 37 | backref=db.backref('users', lazy='dynamic')) 38 | connections = db.relationship('Connection', 39 | backref=db.backref('user', lazy='joined'), cascade="all") 40 | 41 | 42 | class Connection(db.Model): 43 | 44 | __tablename__ = "connections" 45 | 46 | id = db.Column(db.Integer, primary_key=True) 47 | user_id = db.Column(db.Integer, db.ForeignKey('users.id')) 48 | provider_id = db.Column(db.String(255)) 49 | provider_user_id = db.Column(db.String(255)) 50 | access_token = db.Column(db.String(255)) 51 | secret = db.Column(db.String(255)) 52 | display_name = db.Column(db.String(255)) 53 | profile_url = db.Column(db.String(512)) 54 | image_url = db.Column(db.String(512)) 55 | rank = db.Column(db.Integer) 56 | -------------------------------------------------------------------------------- /testsite/app/outliers.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | import sys 3 | import math 4 | 5 | MEDIAN_THRESHOLD = 5.0 6 | 7 | median_distance_cache = {} 8 | def median_distances(pts, aggregate=numpy.median): 9 | key = tuple(sorted(pts)) 10 | if key in median_distance_cache: return median_distance_cache[key] 11 | median = (numpy.median([pt[0] for pt in pts]), 12 | numpy.median([pt[1] for pt in pts])) 13 | distances = [] 14 | for pt in pts: 15 | dist = math.sqrt(((median[0]-pt[0])*math.cos(median[1]*math.pi/180.0))**2+(median[1]-pt[1])**2) 16 | distances.append((dist, pt)) 17 | 18 | median_dist = aggregate([dist for dist, pt in distances]) 19 | median_distance_cache[key] = (median_dist, distances) 20 | return (median_dist, distances) 21 | 22 | def mean_distances(pts): 23 | return median_distances(pts, numpy.mean) 24 | 25 | def discard_outliers(places, threshold=MEDIAN_THRESHOLD): 26 | count = 0 27 | discarded = 0 28 | result = {} 29 | for place_id, pts in places.items(): 30 | count += 1 31 | print >>sys.stderr, "\rComputing outliers for %d of %d places..." % (count, len(places)), 32 | median_dist, distances = median_distances(pts) 33 | keep = [pt for dist, pt in distances if dist < median_dist * threshold] 34 | discarded += len(pts) - len(keep) 35 | result[place_id] = keep 36 | print >>sys.stderr, "%d points discarded." % discarded 37 | return result 38 | 39 | def get_bbox_for_points(places): 40 | bbox = [180, 90, -180, -90] 41 | for pid, pts in places.items(): 42 | for pt in pts: 43 | for i in range(4): 44 | bbox[i] = min(bbox[i], pt[i%2]) if i<2 else max(bbox[i], pt[i%2]) 45 | return bbox 46 | 47 | def main(filename): 48 | places = load_points(filename) 49 | places = discard_outliers(places) 50 | bbox = get_bbox_for_points(places) 51 | #print ",".join(map(str, bbox)) 52 | print "%s %s, %s %s" % (bbox[0], bbox[1], bbox[2], bbox[3]) 53 | 54 | if __name__ == "__main__": 55 | main(sys.argv[1]) 56 | 57 | -------------------------------------------------------------------------------- /testsite/app/static/.webassets-cache/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/.webassets-cache/.gitignore -------------------------------------------------------------------------------- /testsite/app/static/css/bootstrap.map.css: -------------------------------------------------------------------------------- 1 | body.map { 2 | height: 100%; 3 | padding:0; 4 | } 5 | 6 | .map .navbar { 7 | position: relative; 8 | z-index:999; 9 | margin:0; 10 | } 11 | 12 | .map .container-fluid { 13 | position: absolute; 14 | top:40px; 15 | left:0; 16 | right:0; 17 | bottom:0; 18 | padding:0; 19 | } 20 | 21 | .map .row-fluid { 22 | height:100%; 23 | } 24 | 25 | .map .row-fluid > [class*="span"] { 26 | position:relative; 27 | height:100%; 28 | } 29 | 30 | .map #map { 31 | position:absolute; 32 | top:0; 33 | right:0; 34 | left:0; 35 | bottom:0; 36 | } -------------------------------------------------------------------------------- /testsite/app/static/css/combobox.css: -------------------------------------------------------------------------------- 1 | .custom-combobox { 2 | position: relative; 3 | display: inline-block; 4 | } 5 | .custom-combobox-toggle { 6 | position: absolute; 7 | top: 0; 8 | bottom: 0; 9 | margin-left: -1px; 10 | padding: 0; 11 | /* support: IE7 */ 12 | *height: 1.7em; 13 | *top: 0.1em; 14 | } 15 | .custom-combobox-input { 16 | margin: 0; 17 | padding: 0.3em; 18 | } 19 | 20 | .ui-front { 21 | z-index: 10000 !important; 22 | } 23 | -------------------------------------------------------------------------------- /testsite/app/static/css/images/animated-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/css/images/animated-overlay.gif -------------------------------------------------------------------------------- /testsite/app/static/css/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/css/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /testsite/app/static/css/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/css/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /testsite/app/static/css/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/css/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /testsite/app/static/css/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/css/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /testsite/app/static/css/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/css/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /testsite/app/static/css/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/css/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /testsite/app/static/css/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/css/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /testsite/app/static/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /testsite/app/static/css/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/css/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /testsite/app/static/css/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/css/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /testsite/app/static/css/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/css/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /testsite/app/static/css/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/css/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /testsite/app/static/css/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/css/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.accordion.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Accordion 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Accordion#theming 10 | */ 11 | .ui-accordion .ui-accordion-header { 12 | display: block; 13 | cursor: pointer; 14 | position: relative; 15 | margin-top: 2px; 16 | padding: .5em .5em .5em .7em; 17 | min-height: 0; /* support: IE7 */ 18 | } 19 | .ui-accordion .ui-accordion-icons { 20 | padding-left: 2.2em; 21 | } 22 | .ui-accordion .ui-accordion-noicons { 23 | padding-left: .7em; 24 | } 25 | .ui-accordion .ui-accordion-icons .ui-accordion-icons { 26 | padding-left: 2.2em; 27 | } 28 | .ui-accordion .ui-accordion-header .ui-accordion-header-icon { 29 | position: absolute; 30 | left: .5em; 31 | top: 50%; 32 | margin-top: -8px; 33 | } 34 | .ui-accordion .ui-accordion-content { 35 | padding: 1em 2.2em; 36 | border-top: 0; 37 | overflow: auto; 38 | } 39 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.all.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI CSS Framework 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Theming 10 | */ 11 | @import "jquery.ui.base.css"; 12 | @import "jquery.ui.theme.css"; 13 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.autocomplete.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Autocomplete 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Autocomplete#theming 10 | */ 11 | .ui-autocomplete { 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | cursor: default; 16 | } 17 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.base.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI CSS Framework 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Theming 10 | */ 11 | @import url("jquery.ui.core.css"); 12 | 13 | @import url("jquery.ui.accordion.css"); 14 | @import url("jquery.ui.autocomplete.css"); 15 | @import url("jquery.ui.button.css"); 16 | @import url("jquery.ui.datepicker.css"); 17 | @import url("jquery.ui.dialog.css"); 18 | @import url("jquery.ui.menu.css"); 19 | @import url("jquery.ui.progressbar.css"); 20 | @import url("jquery.ui.resizable.css"); 21 | @import url("jquery.ui.selectable.css"); 22 | @import url("jquery.ui.slider.css"); 23 | @import url("jquery.ui.spinner.css"); 24 | @import url("jquery.ui.tabs.css"); 25 | @import url("jquery.ui.tooltip.css"); 26 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.button.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Button 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Button#theming 10 | */ 11 | .ui-button { 12 | display: inline-block; 13 | position: relative; 14 | padding: 0; 15 | line-height: normal; 16 | margin-right: .1em; 17 | cursor: pointer; 18 | vertical-align: middle; 19 | text-align: center; 20 | overflow: visible; /* removes extra width in IE */ 21 | } 22 | .ui-button, 23 | .ui-button:link, 24 | .ui-button:visited, 25 | .ui-button:hover, 26 | .ui-button:active { 27 | text-decoration: none; 28 | } 29 | /* to make room for the icon, a width needs to be set here */ 30 | .ui-button-icon-only { 31 | width: 2.2em; 32 | } 33 | /* button elements seem to need a little more width */ 34 | button.ui-button-icon-only { 35 | width: 2.4em; 36 | } 37 | .ui-button-icons-only { 38 | width: 3.4em; 39 | } 40 | button.ui-button-icons-only { 41 | width: 3.7em; 42 | } 43 | 44 | /* button text element */ 45 | .ui-button .ui-button-text { 46 | display: block; 47 | line-height: normal; 48 | } 49 | .ui-button-text-only .ui-button-text { 50 | padding: .4em 1em; 51 | } 52 | .ui-button-icon-only .ui-button-text, 53 | .ui-button-icons-only .ui-button-text { 54 | padding: .4em; 55 | text-indent: -9999999px; 56 | } 57 | .ui-button-text-icon-primary .ui-button-text, 58 | .ui-button-text-icons .ui-button-text { 59 | padding: .4em 1em .4em 2.1em; 60 | } 61 | .ui-button-text-icon-secondary .ui-button-text, 62 | .ui-button-text-icons .ui-button-text { 63 | padding: .4em 2.1em .4em 1em; 64 | } 65 | .ui-button-text-icons .ui-button-text { 66 | padding-left: 2.1em; 67 | padding-right: 2.1em; 68 | } 69 | /* no icon support for input elements, provide padding by default */ 70 | input.ui-button { 71 | padding: .4em 1em; 72 | } 73 | 74 | /* button icon element(s) */ 75 | .ui-button-icon-only .ui-icon, 76 | .ui-button-text-icon-primary .ui-icon, 77 | .ui-button-text-icon-secondary .ui-icon, 78 | .ui-button-text-icons .ui-icon, 79 | .ui-button-icons-only .ui-icon { 80 | position: absolute; 81 | top: 50%; 82 | margin-top: -8px; 83 | } 84 | .ui-button-icon-only .ui-icon { 85 | left: 50%; 86 | margin-left: -8px; 87 | } 88 | .ui-button-text-icon-primary .ui-button-icon-primary, 89 | .ui-button-text-icons .ui-button-icon-primary, 90 | .ui-button-icons-only .ui-button-icon-primary { 91 | left: .5em; 92 | } 93 | .ui-button-text-icon-secondary .ui-button-icon-secondary, 94 | .ui-button-text-icons .ui-button-icon-secondary, 95 | .ui-button-icons-only .ui-button-icon-secondary { 96 | right: .5em; 97 | } 98 | 99 | /* button sets */ 100 | .ui-buttonset { 101 | margin-right: 7px; 102 | } 103 | .ui-buttonset .ui-button { 104 | margin-left: 0; 105 | margin-right: -.3em; 106 | } 107 | 108 | /* workarounds */ 109 | /* reset extra padding in Firefox, see h5bp.com/l */ 110 | input.ui-button::-moz-focus-inner, 111 | button.ui-button::-moz-focus-inner { 112 | border: 0; 113 | padding: 0; 114 | } 115 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.core.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI CSS Framework 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Theming/API 10 | */ 11 | 12 | /* Layout helpers 13 | ----------------------------------*/ 14 | .ui-helper-hidden { 15 | display: none; 16 | } 17 | .ui-helper-hidden-accessible { 18 | border: 0; 19 | clip: rect(0 0 0 0); 20 | height: 1px; 21 | margin: -1px; 22 | overflow: hidden; 23 | padding: 0; 24 | position: absolute; 25 | width: 1px; 26 | } 27 | .ui-helper-reset { 28 | margin: 0; 29 | padding: 0; 30 | border: 0; 31 | outline: 0; 32 | line-height: 1.3; 33 | text-decoration: none; 34 | font-size: 100%; 35 | list-style: none; 36 | } 37 | .ui-helper-clearfix:before, 38 | .ui-helper-clearfix:after { 39 | content: ""; 40 | display: table; 41 | border-collapse: collapse; 42 | } 43 | .ui-helper-clearfix:after { 44 | clear: both; 45 | } 46 | .ui-helper-clearfix { 47 | min-height: 0; /* support: IE7 */ 48 | } 49 | .ui-helper-zfix { 50 | width: 100%; 51 | height: 100%; 52 | top: 0; 53 | left: 0; 54 | position: absolute; 55 | opacity: 0; 56 | filter:Alpha(Opacity=0); 57 | } 58 | 59 | .ui-front { 60 | z-index: 100; 61 | } 62 | 63 | 64 | /* Interaction Cues 65 | ----------------------------------*/ 66 | .ui-state-disabled { 67 | cursor: default !important; 68 | } 69 | 70 | 71 | /* Icons 72 | ----------------------------------*/ 73 | 74 | /* states and images */ 75 | .ui-icon { 76 | display: block; 77 | text-indent: -99999px; 78 | overflow: hidden; 79 | background-repeat: no-repeat; 80 | } 81 | 82 | 83 | /* Misc visuals 84 | ----------------------------------*/ 85 | 86 | /* Overlays */ 87 | .ui-widget-overlay { 88 | position: fixed; 89 | top: 0; 90 | left: 0; 91 | width: 100%; 92 | height: 100%; 93 | } 94 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.datepicker.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Datepicker 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Datepicker#theming 10 | */ 11 | .ui-datepicker { 12 | width: 17em; 13 | padding: .2em .2em 0; 14 | display: none; 15 | } 16 | .ui-datepicker .ui-datepicker-header { 17 | position: relative; 18 | padding: .2em 0; 19 | } 20 | .ui-datepicker .ui-datepicker-prev, 21 | .ui-datepicker .ui-datepicker-next { 22 | position: absolute; 23 | top: 2px; 24 | width: 1.8em; 25 | height: 1.8em; 26 | } 27 | .ui-datepicker .ui-datepicker-prev-hover, 28 | .ui-datepicker .ui-datepicker-next-hover { 29 | top: 1px; 30 | } 31 | .ui-datepicker .ui-datepicker-prev { 32 | left: 2px; 33 | } 34 | .ui-datepicker .ui-datepicker-next { 35 | right: 2px; 36 | } 37 | .ui-datepicker .ui-datepicker-prev-hover { 38 | left: 1px; 39 | } 40 | .ui-datepicker .ui-datepicker-next-hover { 41 | right: 1px; 42 | } 43 | .ui-datepicker .ui-datepicker-prev span, 44 | .ui-datepicker .ui-datepicker-next span { 45 | display: block; 46 | position: absolute; 47 | left: 50%; 48 | margin-left: -8px; 49 | top: 50%; 50 | margin-top: -8px; 51 | } 52 | .ui-datepicker .ui-datepicker-title { 53 | margin: 0 2.3em; 54 | line-height: 1.8em; 55 | text-align: center; 56 | } 57 | .ui-datepicker .ui-datepicker-title select { 58 | font-size: 1em; 59 | margin: 1px 0; 60 | } 61 | .ui-datepicker select.ui-datepicker-month-year { 62 | width: 100%; 63 | } 64 | .ui-datepicker select.ui-datepicker-month, 65 | .ui-datepicker select.ui-datepicker-year { 66 | width: 49%; 67 | } 68 | .ui-datepicker table { 69 | width: 100%; 70 | font-size: .9em; 71 | border-collapse: collapse; 72 | margin: 0 0 .4em; 73 | } 74 | .ui-datepicker th { 75 | padding: .7em .3em; 76 | text-align: center; 77 | font-weight: bold; 78 | border: 0; 79 | } 80 | .ui-datepicker td { 81 | border: 0; 82 | padding: 1px; 83 | } 84 | .ui-datepicker td span, 85 | .ui-datepicker td a { 86 | display: block; 87 | padding: .2em; 88 | text-align: right; 89 | text-decoration: none; 90 | } 91 | .ui-datepicker .ui-datepicker-buttonpane { 92 | background-image: none; 93 | margin: .7em 0 0 0; 94 | padding: 0 .2em; 95 | border-left: 0; 96 | border-right: 0; 97 | border-bottom: 0; 98 | } 99 | .ui-datepicker .ui-datepicker-buttonpane button { 100 | float: right; 101 | margin: .5em .2em .4em; 102 | cursor: pointer; 103 | padding: .2em .6em .3em .6em; 104 | width: auto; 105 | overflow: visible; 106 | } 107 | .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { 108 | float: left; 109 | } 110 | 111 | /* with multiple calendars */ 112 | .ui-datepicker.ui-datepicker-multi { 113 | width: auto; 114 | } 115 | .ui-datepicker-multi .ui-datepicker-group { 116 | float: left; 117 | } 118 | .ui-datepicker-multi .ui-datepicker-group table { 119 | width: 95%; 120 | margin: 0 auto .4em; 121 | } 122 | .ui-datepicker-multi-2 .ui-datepicker-group { 123 | width: 50%; 124 | } 125 | .ui-datepicker-multi-3 .ui-datepicker-group { 126 | width: 33.3%; 127 | } 128 | .ui-datepicker-multi-4 .ui-datepicker-group { 129 | width: 25%; 130 | } 131 | .ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, 132 | .ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { 133 | border-left-width: 0; 134 | } 135 | .ui-datepicker-multi .ui-datepicker-buttonpane { 136 | clear: left; 137 | } 138 | .ui-datepicker-row-break { 139 | clear: both; 140 | width: 100%; 141 | font-size: 0; 142 | } 143 | 144 | /* RTL support */ 145 | .ui-datepicker-rtl { 146 | direction: rtl; 147 | } 148 | .ui-datepicker-rtl .ui-datepicker-prev { 149 | right: 2px; 150 | left: auto; 151 | } 152 | .ui-datepicker-rtl .ui-datepicker-next { 153 | left: 2px; 154 | right: auto; 155 | } 156 | .ui-datepicker-rtl .ui-datepicker-prev:hover { 157 | right: 1px; 158 | left: auto; 159 | } 160 | .ui-datepicker-rtl .ui-datepicker-next:hover { 161 | left: 1px; 162 | right: auto; 163 | } 164 | .ui-datepicker-rtl .ui-datepicker-buttonpane { 165 | clear: right; 166 | } 167 | .ui-datepicker-rtl .ui-datepicker-buttonpane button { 168 | float: left; 169 | } 170 | .ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, 171 | .ui-datepicker-rtl .ui-datepicker-group { 172 | float: right; 173 | } 174 | .ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, 175 | .ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { 176 | border-right-width: 0; 177 | border-left-width: 1px; 178 | } 179 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.dialog.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Dialog 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Dialog#theming 10 | */ 11 | .ui-dialog { 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | padding: .2em; 16 | outline: 0; 17 | } 18 | .ui-dialog .ui-dialog-titlebar { 19 | padding: .4em 1em; 20 | position: relative; 21 | } 22 | .ui-dialog .ui-dialog-title { 23 | float: left; 24 | margin: .1em 0; 25 | white-space: nowrap; 26 | width: 90%; 27 | overflow: hidden; 28 | text-overflow: ellipsis; 29 | } 30 | .ui-dialog .ui-dialog-titlebar-close { 31 | position: absolute; 32 | right: .3em; 33 | top: 50%; 34 | width: 21px; 35 | margin: -10px 0 0 0; 36 | padding: 1px; 37 | height: 20px; 38 | } 39 | .ui-dialog .ui-dialog-content { 40 | position: relative; 41 | border: 0; 42 | padding: .5em 1em; 43 | background: none; 44 | overflow: auto; 45 | } 46 | .ui-dialog .ui-dialog-buttonpane { 47 | text-align: left; 48 | border-width: 1px 0 0 0; 49 | background-image: none; 50 | margin-top: .5em; 51 | padding: .3em 1em .5em .4em; 52 | } 53 | .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { 54 | float: right; 55 | } 56 | .ui-dialog .ui-dialog-buttonpane button { 57 | margin: .5em .4em .5em 0; 58 | cursor: pointer; 59 | } 60 | .ui-dialog .ui-resizable-se { 61 | width: 12px; 62 | height: 12px; 63 | right: -5px; 64 | bottom: -5px; 65 | background-position: 16px 16px; 66 | } 67 | .ui-draggable .ui-dialog-titlebar { 68 | cursor: move; 69 | } 70 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.menu.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Menu 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Menu#theming 10 | */ 11 | .ui-menu { 12 | list-style: none; 13 | padding: 2px; 14 | margin: 0; 15 | display: block; 16 | outline: none; 17 | } 18 | .ui-menu .ui-menu { 19 | margin-top: -3px; 20 | position: absolute; 21 | } 22 | .ui-menu .ui-menu-item { 23 | margin: 0; 24 | padding: 0; 25 | width: 100%; 26 | /* support: IE10, see #8844 */ 27 | list-style-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); 28 | } 29 | .ui-menu .ui-menu-divider { 30 | margin: 5px -2px 5px -2px; 31 | height: 0; 32 | font-size: 0; 33 | line-height: 0; 34 | border-width: 1px 0 0 0; 35 | } 36 | .ui-menu .ui-menu-item a { 37 | text-decoration: none; 38 | display: block; 39 | padding: 2px .4em; 40 | line-height: 1.5; 41 | min-height: 0; /* support: IE7 */ 42 | font-weight: normal; 43 | } 44 | .ui-menu .ui-menu-item a.ui-state-focus, 45 | .ui-menu .ui-menu-item a.ui-state-active { 46 | font-weight: normal; 47 | margin: -1px; 48 | } 49 | 50 | .ui-menu .ui-state-disabled { 51 | font-weight: normal; 52 | margin: .4em 0 .2em; 53 | line-height: 1.5; 54 | } 55 | .ui-menu .ui-state-disabled a { 56 | cursor: default; 57 | } 58 | 59 | /* icon support */ 60 | .ui-menu-icons { 61 | position: relative; 62 | } 63 | .ui-menu-icons .ui-menu-item a { 64 | position: relative; 65 | padding-left: 2em; 66 | } 67 | 68 | /* left-aligned */ 69 | .ui-menu .ui-icon { 70 | position: absolute; 71 | top: .2em; 72 | left: .2em; 73 | } 74 | 75 | /* right-aligned */ 76 | .ui-menu .ui-menu-icon { 77 | position: static; 78 | float: right; 79 | } 80 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.progressbar.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Progressbar 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Progressbar#theming 10 | */ 11 | .ui-progressbar { 12 | height: 2em; 13 | text-align: left; 14 | overflow: hidden; 15 | } 16 | .ui-progressbar .ui-progressbar-value { 17 | margin: -1px; 18 | height: 100%; 19 | } 20 | .ui-progressbar .ui-progressbar-overlay { 21 | background: url("images/animated-overlay.gif"); 22 | height: 100%; 23 | filter: alpha(opacity=25); 24 | opacity: 0.25; 25 | } 26 | .ui-progressbar-indeterminate .ui-progressbar-value { 27 | background-image: none; 28 | } 29 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.resizable.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Resizable 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Resizable#theming 10 | */ 11 | .ui-resizable { 12 | position: relative; 13 | } 14 | .ui-resizable-handle { 15 | position: absolute; 16 | font-size: 0.1px; 17 | display: block; 18 | } 19 | .ui-resizable-disabled .ui-resizable-handle, 20 | .ui-resizable-autohide .ui-resizable-handle { 21 | display: none; 22 | } 23 | .ui-resizable-n { 24 | cursor: n-resize; 25 | height: 7px; 26 | width: 100%; 27 | top: -5px; 28 | left: 0; 29 | } 30 | .ui-resizable-s { 31 | cursor: s-resize; 32 | height: 7px; 33 | width: 100%; 34 | bottom: -5px; 35 | left: 0; 36 | } 37 | .ui-resizable-e { 38 | cursor: e-resize; 39 | width: 7px; 40 | right: -5px; 41 | top: 0; 42 | height: 100%; 43 | } 44 | .ui-resizable-w { 45 | cursor: w-resize; 46 | width: 7px; 47 | left: -5px; 48 | top: 0; 49 | height: 100%; 50 | } 51 | .ui-resizable-se { 52 | cursor: se-resize; 53 | width: 12px; 54 | height: 12px; 55 | right: 1px; 56 | bottom: 1px; 57 | } 58 | .ui-resizable-sw { 59 | cursor: sw-resize; 60 | width: 9px; 61 | height: 9px; 62 | left: -5px; 63 | bottom: -5px; 64 | } 65 | .ui-resizable-nw { 66 | cursor: nw-resize; 67 | width: 9px; 68 | height: 9px; 69 | left: -5px; 70 | top: -5px; 71 | } 72 | .ui-resizable-ne { 73 | cursor: ne-resize; 74 | width: 9px; 75 | height: 9px; 76 | right: -5px; 77 | top: -5px; 78 | } 79 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.selectable.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Selectable 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Selectable#theming 10 | */ 11 | .ui-selectable-helper { 12 | position: absolute; 13 | z-index: 100; 14 | border: 1px dotted black; 15 | } 16 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.slider.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Slider 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Slider#theming 10 | */ 11 | .ui-slider { 12 | position: relative; 13 | text-align: left; 14 | } 15 | .ui-slider .ui-slider-handle { 16 | position: absolute; 17 | z-index: 2; 18 | width: 1.2em; 19 | height: 1.2em; 20 | cursor: default; 21 | } 22 | .ui-slider .ui-slider-range { 23 | position: absolute; 24 | z-index: 1; 25 | font-size: .7em; 26 | display: block; 27 | border: 0; 28 | background-position: 0 0; 29 | } 30 | 31 | /* For IE8 - See #6727 */ 32 | .ui-slider.ui-state-disabled .ui-slider-handle, 33 | .ui-slider.ui-state-disabled .ui-slider-range { 34 | filter: inherit; 35 | } 36 | 37 | .ui-slider-horizontal { 38 | height: .8em; 39 | } 40 | .ui-slider-horizontal .ui-slider-handle { 41 | top: -.3em; 42 | margin-left: -.6em; 43 | } 44 | .ui-slider-horizontal .ui-slider-range { 45 | top: 0; 46 | height: 100%; 47 | } 48 | .ui-slider-horizontal .ui-slider-range-min { 49 | left: 0; 50 | } 51 | .ui-slider-horizontal .ui-slider-range-max { 52 | right: 0; 53 | } 54 | 55 | .ui-slider-vertical { 56 | width: .8em; 57 | height: 100px; 58 | } 59 | .ui-slider-vertical .ui-slider-handle { 60 | left: -.3em; 61 | margin-left: 0; 62 | margin-bottom: -.6em; 63 | } 64 | .ui-slider-vertical .ui-slider-range { 65 | left: 0; 66 | width: 100%; 67 | } 68 | .ui-slider-vertical .ui-slider-range-min { 69 | bottom: 0; 70 | } 71 | .ui-slider-vertical .ui-slider-range-max { 72 | top: 0; 73 | } 74 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.spinner.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Spinner 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Spinner#theming 10 | */ 11 | .ui-spinner { 12 | position: relative; 13 | display: inline-block; 14 | overflow: hidden; 15 | padding: 0; 16 | vertical-align: middle; 17 | } 18 | .ui-spinner-input { 19 | border: none; 20 | background: none; 21 | color: inherit; 22 | padding: 0; 23 | margin: .2em 0; 24 | vertical-align: middle; 25 | margin-left: .4em; 26 | margin-right: 22px; 27 | } 28 | .ui-spinner-button { 29 | width: 16px; 30 | height: 50%; 31 | font-size: .5em; 32 | padding: 0; 33 | margin: 0; 34 | text-align: center; 35 | position: absolute; 36 | cursor: default; 37 | display: block; 38 | overflow: hidden; 39 | right: 0; 40 | } 41 | /* more specificity required here to overide default borders */ 42 | .ui-spinner a.ui-spinner-button { 43 | border-top: none; 44 | border-bottom: none; 45 | border-right: none; 46 | } 47 | /* vertical centre icon */ 48 | .ui-spinner .ui-icon { 49 | position: absolute; 50 | margin-top: -8px; 51 | top: 50%; 52 | left: 0; 53 | } 54 | .ui-spinner-up { 55 | top: 0; 56 | } 57 | .ui-spinner-down { 58 | bottom: 0; 59 | } 60 | 61 | /* TR overrides */ 62 | .ui-spinner .ui-icon-triangle-1-s { 63 | /* need to fix icons sprite */ 64 | background-position: -65px -16px; 65 | } 66 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.tabs.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Tabs 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Tabs#theming 10 | */ 11 | .ui-tabs { 12 | position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ 13 | padding: .2em; 14 | } 15 | .ui-tabs .ui-tabs-nav { 16 | margin: 0; 17 | padding: .2em .2em 0; 18 | } 19 | .ui-tabs .ui-tabs-nav li { 20 | list-style: none; 21 | float: left; 22 | position: relative; 23 | top: 0; 24 | margin: 1px .2em 0 0; 25 | border-bottom-width: 0; 26 | padding: 0; 27 | white-space: nowrap; 28 | } 29 | .ui-tabs .ui-tabs-nav li a { 30 | float: left; 31 | padding: .5em 1em; 32 | text-decoration: none; 33 | } 34 | .ui-tabs .ui-tabs-nav li.ui-tabs-active { 35 | margin-bottom: -1px; 36 | padding-bottom: 1px; 37 | } 38 | .ui-tabs .ui-tabs-nav li.ui-tabs-active a, 39 | .ui-tabs .ui-tabs-nav li.ui-state-disabled a, 40 | .ui-tabs .ui-tabs-nav li.ui-tabs-loading a { 41 | cursor: text; 42 | } 43 | .ui-tabs .ui-tabs-nav li a, /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ 44 | .ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a { 45 | cursor: pointer; 46 | } 47 | .ui-tabs .ui-tabs-panel { 48 | display: block; 49 | border-width: 0; 50 | padding: 1em 1.4em; 51 | background: none; 52 | } 53 | -------------------------------------------------------------------------------- /testsite/app/static/css/jquery.ui.tooltip.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Tooltip 1.10.3 3 | * http://jqueryui.com 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | */ 9 | .ui-tooltip { 10 | padding: 8px; 11 | position: absolute; 12 | z-index: 9999; 13 | max-width: 300px; 14 | -webkit-box-shadow: 0 0 5px #aaa; 15 | box-shadow: 0 0 5px #aaa; 16 | } 17 | body .ui-tooltip { 18 | border-width: 2px; 19 | } 20 | -------------------------------------------------------------------------------- /testsite/app/static/css/keys.css: -------------------------------------------------------------------------------- 1 | /** 2 | * KEYS.css 3 | * 4 | * A simple stylesheet for rendering beautiful keyboard-style elements. 5 | * 6 | * Author: Michael Hüneburg 7 | * Website: http://michaelhue.com/keyscss 8 | * License: MIT License (see LICENSE.txt) 9 | */ 10 | 11 | /* Base style, essential for every key. */ 12 | kbd, .key { 13 | display: inline; 14 | display: inline-block; 15 | min-width: 1em; 16 | padding: .2em .3em; 17 | font: normal .85em/1 "Lucida Grande", Lucida, Arial, sans-serif; 18 | text-align: center; 19 | text-decoration: none; 20 | -moz-border-radius: .3em; 21 | -webkit-border-radius: .3em; 22 | border-radius: .3em; 23 | border: none; 24 | cursor: default; 25 | -moz-user-select: none; 26 | -webkit-user-select: none; 27 | user-select: none; 28 | } 29 | kbd[title], .key[title] { 30 | cursor: help; 31 | } 32 | 33 | /* Dark style for display on light background. This is the default style. */ 34 | kbd, kbd.dark, .dark-keys kbd, .key, .key.dark, .dark-keys .key { 35 | background: rgb(80, 80, 80); 36 | background: -moz-linear-gradient(top, rgb(60, 60, 60), rgb(80, 80, 80)); 37 | background: -webkit-gradient(linear, left top, left bottom, from(rgb(60, 60, 60)), to(rgb(80, 80, 80))); 38 | color: rgb(250, 250, 250); 39 | text-shadow: -1px -1px 0 rgb(70, 70, 70); 40 | -moz-box-shadow: inset 0 0 1px rgb(150, 150, 150), inset 0 -.05em .4em rgb(80, 80, 80), 0 .1em 0 rgb(30, 30, 30), 0 .1em .1em rgba(0, 0, 0, .3); 41 | -webkit-box-shadow: inset 0 0 1px rgb(150, 150, 150), inset 0 -.05em .4em rgb(80, 80, 80), 0 .1em 0 rgb(30, 30, 30), 0 .1em .1em rgba(0, 0, 0, .3); 42 | box-shadow: inset 0 0 1px rgb(150, 150, 150), inset 0 -.05em .4em rgb(80, 80, 80), 0 .1em 0 rgb(30, 30, 30), 0 .1em .1em rgba(0, 0, 0, .3); 43 | } 44 | 45 | /* Light style for display on dark background. */ 46 | kbd.light, .light-keys kbd, .key.light, .light-keys .key { 47 | background: rgb(250, 250, 250); 48 | background: -moz-linear-gradient(top, rgb(210, 210, 210), rgb(255, 255, 255)); 49 | background: -webkit-gradient(linear, left top, left bottom, from(rgb(210, 210, 210)), to(rgb(255, 255, 255))); 50 | color: rgb(50, 50, 50); 51 | text-shadow: 0 0 2px rgb(255, 255, 255); 52 | -moz-box-shadow: inset 0 0 1px rgb(255, 255, 255), inset 0 0 .4em rgb(200, 200, 200), 0 .1em 0 rgb(130, 130, 130), 0 .11em 0 rgba(0, 0, 0, .4), 0 .1em .11em rgba(0, 0, 0, .9); 53 | -webkit-box-shadow: inset 0 0 1px rgb(255, 255, 255), inset 0 0 .4em rgb(200, 200, 200), 0 .1em 0 rgb(130, 130, 130), 0 .11em 0 rgba(0, 0, 0, .4), 0 .1em .11em rgba(0, 0, 0, .9); 54 | box-shadow: inset 0 0 1px rgb(255, 255, 255), inset 0 0 .4em rgb(200, 200, 200), 0 .1em 0 rgb(130, 130, 130), 0 .11em 0 rgba(0, 0, 0, .4), 0 .1em .11em rgba(0, 0, 0, .9); 55 | } -------------------------------------------------------------------------------- /testsite/app/static/css/leaflet.label.css: -------------------------------------------------------------------------------- 1 | .leaflet-label { 2 | background: rgb(235, 235, 235); 3 | background: rgba(235, 235, 235, 0.81); 4 | background-clip: padding-box; 5 | border-color: #777; 6 | border-color: rgba(0,0,0,0.25); 7 | border-radius: 4px; 8 | border-style: solid; 9 | border-width: 4px; 10 | color: #111; 11 | display: block; 12 | font: 12px/20px "Helvetica Neue", Arial, Helvetica, sans-serif; 13 | font-weight: bold; 14 | padding: 1px 6px; 15 | position: absolute; 16 | -webkit-user-select: none; 17 | -moz-user-select: none; 18 | -ms-user-select: none; 19 | user-select: none; 20 | pointer-events: none; 21 | white-space: nowrap; 22 | z-index: 6; 23 | } 24 | 25 | .leaflet-label.leaflet-clickable { 26 | cursor: pointer; 27 | pointer-events: auto; 28 | } 29 | 30 | .leaflet-label:before, 31 | .leaflet-label:after { 32 | border-top: 6px solid transparent; 33 | border-bottom: 6px solid transparent; 34 | content: none; 35 | position: absolute; 36 | top: 5px; 37 | } 38 | 39 | .leaflet-label:before { 40 | border-right: 6px solid black; 41 | border-right-color: inherit; 42 | left: -10px; 43 | } 44 | 45 | .leaflet-label:after { 46 | border-left: 6px solid black; 47 | border-left-color: inherit; 48 | right: -10px; 49 | } 50 | 51 | .leaflet-label-right:before, 52 | .leaflet-label-left:after { 53 | content: ""; 54 | } 55 | -------------------------------------------------------------------------------- /testsite/app/static/css/src/styles.less: -------------------------------------------------------------------------------- 1 | .tidy { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | .navbar { 7 | .github { 8 | li { 9 | margin: 10px 0 0 10px; 10 | } 11 | } 12 | } 13 | 14 | .footer { 15 | margin: 50px 0; 16 | padding: 10px; 17 | border-top: 1px solid #EEE; 18 | text-align: center; 19 | color: #888; 20 | } 21 | 22 | .main-index { 23 | 24 | div.repo-info { 25 | text-align: center; 26 | 27 | h2 { 28 | margin-bottom: 15px; 29 | } 30 | } 31 | 32 | ul.repo-actions { 33 | .tidy; 34 | list-style: none; 35 | 36 | li { 37 | .tidy; 38 | display: inline; 39 | overflow: auto; 40 | } 41 | } 42 | } 43 | 44 | .main-login, .main-register { 45 | ul.errors { 46 | .tidy; 47 | color: #FF0000; 48 | list-style: none; 49 | } 50 | 51 | .content { 52 | margin-top: 30px; 53 | 54 | .col2 { 55 | text-align: center; 56 | padding-top: 80px; 57 | } 58 | 59 | .col3 { 60 | padding-top: 50px; 61 | } 62 | } 63 | } 64 | 65 | .main-profile { 66 | textarea { 67 | display: block; 68 | } 69 | } -------------------------------------------------------------------------------- /testsite/app/static/css/styles.css: -------------------------------------------------------------------------------- 1 | .tidy { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | .navbar .github li { 6 | margin: 10px 0 0 10px; 7 | } 8 | .footer { 9 | margin: 50px 0; 10 | padding: 10px; 11 | border-top: 1px solid #EEE; 12 | text-align: center; 13 | color: #888; 14 | } 15 | .main-index div.repo-info { 16 | text-align: center; 17 | } 18 | .main-index div.repo-info h2 { 19 | margin-bottom: 15px; 20 | } 21 | .main-index ul.repo-actions { 22 | margin: 0; 23 | padding: 0; 24 | list-style: none; 25 | } 26 | .main-index ul.repo-actions li { 27 | margin: 0; 28 | padding: 0; 29 | display: inline; 30 | overflow: auto; 31 | } 32 | .main-login ul.errors, 33 | .main-register ul.errors { 34 | margin: 0; 35 | padding: 0; 36 | color: #FF0000; 37 | list-style: none; 38 | } 39 | .main-login .content, 40 | .main-register .content { 41 | margin-top: 30px; 42 | } 43 | .main-login .content .col2, 44 | .main-register .content .col2 { 45 | text-align: center; 46 | padding-top: 80px; 47 | } 48 | .main-login .content .col3, 49 | .main-register .content .col3 { 50 | padding-top: 50px; 51 | } 52 | .main-profile textarea { 53 | display: block; 54 | } 55 | -------------------------------------------------------------------------------- /testsite/app/static/css/zetashapes.css: -------------------------------------------------------------------------------- 1 | .titleHelp { 2 | float: left 3 | } 4 | 5 | .loginText { 6 | float:right; 7 | } 8 | 9 | .downloadLink { 10 | float:right; 11 | } 12 | 13 | .neighborhoodInfo { 14 | float: left 15 | font-size: 2em; 16 | font-weight: bold; 17 | padding-left: 10px; 18 | } 19 | 20 | #spinner { 21 | position: fixed; 22 | top: 50%; 23 | left: 50%; 24 | z-index: 200000; 25 | display: none; 26 | } 27 | 28 | .loginAlert { 29 | position: fixed; 30 | top: 20%; 31 | left: 50%; 32 | z-index: 200000; 33 | display: none; 34 | } 35 | 36 | .controls { 37 | z-index: 900000; 38 | position: fixed; 39 | top: 47px; 40 | height: 20px; 41 | right: 10%; 42 | width: 80%; 43 | overflow: hidden; 44 | 45 | padding: 10px; 46 | -webkit-border-radius: 15px; 47 | -moz-border-radius: 15px; 48 | border-radius: 15px; 49 | 50 | background: #E9EAEE; 51 | border: 2px solid white; 52 | margin: 0 auto; 53 | box-shadow: 1px 2px 6px rgba(0, 0, 0, 0.5); 54 | -moz-box-shadow: 1px 2px 6px rgba(0,0,0, 0.5); 55 | -webkit-box-shadow: 1px 2px 6 56 | 57 | 58 | z-index: 50; 59 | background: white; 60 | } 61 | 62 | .controls.neighborhoodMode .exitBlockMode { display: none } 63 | .controls.blockMode .neighborhoodControls { display: none } 64 | 65 | .neighborhoodControls.nohover .hover {display:none} 66 | .neighborhoodControls.hover .nohover {display:none} 67 | 68 | .controls.loading {display:none} 69 | 70 | .hoodEntry .tt-dropdown-menu { 71 | max-height: 50px; 72 | overflow-y: auto; 73 | } 74 | -------------------------------------------------------------------------------- /testsite/app/static/html/states.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 26 | 27 | 28 | 29 | 30 | 65 | -------------------------------------------------------------------------------- /testsite/app/static/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/images/layers-2x.png -------------------------------------------------------------------------------- /testsite/app/static/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/images/layers.png -------------------------------------------------------------------------------- /testsite/app/static/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/images/marker-icon-2x.png -------------------------------------------------------------------------------- /testsite/app/static/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/images/marker-icon.png -------------------------------------------------------------------------------- /testsite/app/static/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/images/marker-shadow.png -------------------------------------------------------------------------------- /testsite/app/static/img/demo-nyc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/img/demo-nyc.png -------------------------------------------------------------------------------- /testsite/app/static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /testsite/app/static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /testsite/app/static/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/app/static/img/spinner.gif -------------------------------------------------------------------------------- /testsite/app/static/js/Map.Draw.js: -------------------------------------------------------------------------------- 1 | L.Map.Draw = L.Handler.extend({ 2 | 3 | options: { 4 | icon: new L.DivIcon({ 5 | iconSize: new L.Point(8, 8), 6 | className: 'leaflet-div-icon leaflet-editing-icon' 7 | }) 8 | }, 9 | 10 | initialize: function (map, type, options) { 11 | this._map = map; 12 | this.setType(type); 13 | L.Util.setOptions(this, options); 14 | }, 15 | 16 | addHooks: function () { 17 | if(this._type == 'marker'){ 18 | this._marker = null; 19 | this._map.on('click', this._drawMarker); 20 | }else{ 21 | 22 | if(this._type == 'polygon'){ 23 | this._poly = new L.Polygon([]); 24 | }else if(this._type == 'rectangle'){ 25 | this._poly = new L.Polygon([]); 26 | }else{ 27 | this._poly = new L.Polyline([]); 28 | } 29 | 30 | this._initMarkers(); 31 | this._map.addLayer(this._markerGroup); 32 | this._map.addLayer(this._poly); 33 | this._map.on('click', this._drawPoly); 34 | } 35 | }, 36 | 37 | removeHooks: function () { 38 | 39 | if(this._type == 'marker'){ 40 | this._map.fire("drawend", {marker: this._marker}); 41 | delete this._marker; 42 | this._map.off('click', this._drawMarker); 43 | 44 | }else{ 45 | this._map.fire("drawend", {poly: this._poly}); 46 | this._map.removeLayer(this._markerGroup); 47 | delete this._markerGroup; 48 | delete this._markers; 49 | delete this._poly; 50 | this._map.off('click', this._drawPoly); 51 | } 52 | }, 53 | 54 | setType: function (type) { 55 | this._type = type; 56 | return this._type; 57 | }, 58 | 59 | _initMarkers: function () { 60 | if (!this._markerGroup) { 61 | this._markerGroup = new L.LayerGroup(); 62 | } 63 | 64 | this._markers = []; 65 | 66 | var latlngs = this._poly._latlngs, 67 | i, len, marker; 68 | 69 | 70 | for (i = 0, len = latlngs.length; i < len; i++) { 71 | marker = this._createMarker(latlngs[i], i); 72 | this._markers.push(marker); 73 | } 74 | 75 | }, 76 | 77 | _createMarker: function (latlng) { 78 | 79 | var marker = new L.Marker(latlng, { 80 | icon: this.options.icon 81 | }); 82 | 83 | marker.on('click', this._map.draw.disable, this); 84 | 85 | this._markerGroup.addLayer(marker); 86 | 87 | return marker; 88 | }, 89 | 90 | _drawPoly: function (e) { 91 | this.draw._poly.addLatLng(e.latlng); 92 | this.draw._markerGroup.clearLayers(); 93 | if((this.draw._type == 'rectangle') && ( this.draw._poly.getLatLngs().length ==2 ) ){ 94 | var rectangle = new L.Rectangle(this.draw._poly.getBounds()); 95 | this.draw._poly.setLatLngs(rectangle.getLatLngs()); 96 | rectangle = null; 97 | this.draw.disable(); 98 | }else{ 99 | this.draw._initMarkers(); 100 | } 101 | }, 102 | 103 | _drawMarker: function (e) { 104 | this.draw._marker = new L.Marker(e.latlng); 105 | this.draw._map.addLayer(this.draw._marker); 106 | this.draw.disable(); 107 | } 108 | 109 | 110 | }); 111 | 112 | L.Map.addInitHook('addHandler', 'draw', L.Map.Draw); -------------------------------------------------------------------------------- /testsite/app/static/js/combobox.js: -------------------------------------------------------------------------------- 1 | (function( $ ) { 2 | $.widget( "custom.combobox", { 3 | _create: function() { 4 | this.wrapper = $( "" ) 5 | .addClass( "custom-combobox" ) 6 | .insertAfter( this.element ); 7 | 8 | this.element.hide(); 9 | this._createAutocomplete(); 10 | this._createShowAllButton(); 11 | }, 12 | 13 | _createAutocomplete: function() { 14 | var selected = this.element.children( ":selected" ), 15 | value = selected.val() ? selected.text() : ""; 16 | 17 | this.input = $( "" ) 18 | .appendTo( this.wrapper ) 19 | .val( value ) 20 | .attr( "title", "" ) 21 | .addClass( "custom-combobox-input ui-widget ui-widget-content ui-state-default ui-corner-left" ) 22 | .autocomplete({ 23 | delay: 0, 24 | minLength: 0, 25 | source: $.proxy( this, "_source" ) 26 | }) 27 | .tooltip({ 28 | tooltipClass: "ui-state-highlight" 29 | }); 30 | 31 | this._on( this.input, { 32 | autocompleteselect: function( event, ui ) { 33 | ui.item.option.selected = true; 34 | this._trigger( "select", event, { 35 | item: ui.item.option 36 | }); 37 | }, 38 | 39 | autocompletechange: "_removeIfInvalid" 40 | }); 41 | }, 42 | 43 | _createShowAllButton: function() { 44 | var input = this.input, 45 | wasOpen = false; 46 | 47 | $( "" ) 48 | .attr( "tabIndex", -1 ) 49 | .attr( "title", "Show All Items" ) 50 | .tooltip() 51 | .appendTo( this.wrapper ) 52 | .button({ 53 | icons: { 54 | primary: "ui-icon-triangle-1-s" 55 | }, 56 | text: false 57 | }) 58 | .removeClass( "ui-corner-all" ) 59 | .addClass( "custom-combobox-toggle ui-corner-right" ) 60 | .mousedown(function() { 61 | wasOpen = input.autocomplete( "widget" ).is( ":visible" ); 62 | }) 63 | .click(function() { 64 | input.focus(); 65 | 66 | // Close if already visible 67 | if ( wasOpen ) { 68 | return; 69 | } 70 | 71 | // Pass empty string as value to search for, displaying all results 72 | input.autocomplete( "search", "" ); 73 | }); 74 | }, 75 | 76 | _source: function( request, response ) { 77 | var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" ); 78 | response( this.element.children( "option" ).map(function() { 79 | var text = $( this ).text(); 80 | if ( this.value && ( !request.term || matcher.test(text) ) ) 81 | return { 82 | label: text, 83 | value: text, 84 | option: this 85 | }; 86 | }) ); 87 | }, 88 | 89 | _removeIfInvalid: function( event, ui ) { 90 | 91 | // Selected an item, nothing to do 92 | if ( ui.item ) { 93 | return; 94 | } 95 | 96 | // Search for a match (case-insensitive) 97 | var value = this.input.val(), 98 | valueLowerCase = value.toLowerCase(), 99 | valid = false; 100 | this.element.children( "option" ).each(function() { 101 | if ( $( this ).text().toLowerCase() === valueLowerCase ) { 102 | this.selected = valid = true; 103 | return false; 104 | } 105 | }); 106 | 107 | // Found a match, nothing to do 108 | if ( valid ) { 109 | return; 110 | } 111 | 112 | // Remove invalid value 113 | this.input 114 | .val( "" ) 115 | .attr( "title", value + " didn't match any item" ) 116 | .tooltip( "open" ); 117 | this.element.val( "" ); 118 | this._delay(function() { 119 | this.input.tooltip( "close" ).attr( "title", "" ); 120 | }, 2500 ); 121 | this.input.data( "ui-autocomplete" ).term = ""; 122 | }, 123 | 124 | _destroy: function() { 125 | this.wrapper.remove(); 126 | this.element.show(); 127 | } 128 | }); 129 | })( jQuery ); 130 | -------------------------------------------------------------------------------- /testsite/app/static/js/jquery-migrate-1.1.1.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery Migrate v1.1.1 | (c) 2005, 2013 jQuery Foundation, Inc. and other contributors | jquery.org/license */ 2 | jQuery.migrateMute===void 0&&(jQuery.migrateMute=!0),function(e,t,n){function r(n){o[n]||(o[n]=!0,e.migrateWarnings.push(n),t.console&&console.warn&&!e.migrateMute&&(console.warn("JQMIGRATE: "+n),e.migrateTrace&&console.trace&&console.trace()))}function a(t,a,o,i){if(Object.defineProperty)try{return Object.defineProperty(t,a,{configurable:!0,enumerable:!0,get:function(){return r(i),o},set:function(e){r(i),o=e}}),n}catch(s){}e._definePropertyBroken=!0,t[a]=o}var o={};e.migrateWarnings=[],!e.migrateMute&&t.console&&console.log&&console.log("JQMIGRATE: Logging is active"),e.migrateTrace===n&&(e.migrateTrace=!0),e.migrateReset=function(){o={},e.migrateWarnings.length=0},"BackCompat"===document.compatMode&&r("jQuery is not compatible with Quirks Mode");var i=e("",{size:1}).attr("size")&&e.attrFn,s=e.attr,u=e.attrHooks.value&&e.attrHooks.value.get||function(){return null},c=e.attrHooks.value&&e.attrHooks.value.set||function(){return n},l=/^(?:input|button)$/i,d=/^[238]$/,p=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,f=/^(?:checked|selected)$/i;a(e,"attrFn",i||{},"jQuery.attrFn is deprecated"),e.attr=function(t,a,o,u){var c=a.toLowerCase(),g=t&&t.nodeType;return u&&(4>s.length&&r("jQuery.fn.attr( props, pass ) is deprecated"),t&&!d.test(g)&&(i?a in i:e.isFunction(e.fn[a])))?e(t)[a](o):("type"===a&&o!==n&&l.test(t.nodeName)&&t.parentNode&&r("Can't change the 'type' of an input or button in IE 6/7/8"),!e.attrHooks[c]&&p.test(c)&&(e.attrHooks[c]={get:function(t,r){var a,o=e.prop(t,r);return o===!0||"boolean"!=typeof o&&(a=t.getAttributeNode(r))&&a.nodeValue!==!1?r.toLowerCase():n},set:function(t,n,r){var a;return n===!1?e.removeAttr(t,r):(a=e.propFix[r]||r,a in t&&(t[a]=!0),t.setAttribute(r,r.toLowerCase())),r}},f.test(c)&&r("jQuery.fn.attr('"+c+"') may use property instead of attribute")),s.call(e,t,a,o))},e.attrHooks.value={get:function(e,t){var n=(e.nodeName||"").toLowerCase();return"button"===n?u.apply(this,arguments):("input"!==n&&"option"!==n&&r("jQuery.fn.attr('value') no longer gets properties"),t in e?e.value:null)},set:function(e,t){var a=(e.nodeName||"").toLowerCase();return"button"===a?c.apply(this,arguments):("input"!==a&&"option"!==a&&r("jQuery.fn.attr('value', val) no longer sets properties"),e.value=t,n)}};var g,h,v=e.fn.init,m=e.parseJSON,y=/^(?:[^<]*(<[\w\W]+>)[^>]*|#([\w\-]*))$/;e.fn.init=function(t,n,a){var o;return t&&"string"==typeof t&&!e.isPlainObject(n)&&(o=y.exec(t))&&o[1]&&("<"!==t.charAt(0)&&r("$(html) HTML strings must start with '<' character"),n&&n.context&&(n=n.context),e.parseHTML)?v.call(this,e.parseHTML(e.trim(t),n,!0),n,a):v.apply(this,arguments)},e.fn.init.prototype=e.fn,e.parseJSON=function(e){return e||null===e?m.apply(this,arguments):(r("jQuery.parseJSON requires a valid JSON string"),null)},e.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||0>e.indexOf("compatible")&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e.browser||(g=e.uaMatch(navigator.userAgent),h={},g.browser&&(h[g.browser]=!0,h.version=g.version),h.chrome?h.webkit=!0:h.webkit&&(h.safari=!0),e.browser=h),a(e,"browser",e.browser,"jQuery.browser is deprecated"),e.sub=function(){function t(e,n){return new t.fn.init(e,n)}e.extend(!0,t,this),t.superclass=this,t.fn=t.prototype=this(),t.fn.constructor=t,t.sub=this.sub,t.fn.init=function(r,a){return a&&a instanceof e&&!(a instanceof t)&&(a=t(a)),e.fn.init.call(this,r,a,n)},t.fn.init.prototype=t.fn;var n=t(document);return r("jQuery.sub() is deprecated"),t},e.ajaxSetup({converters:{"text json":e.parseJSON}});var b=e.fn.data;e.fn.data=function(t){var a,o,i=this[0];return!i||"events"!==t||1!==arguments.length||(a=e.data(i,t),o=e._data(i,t),a!==n&&a!==o||o===n)?b.apply(this,arguments):(r("Use of jQuery.fn.data('events') is deprecated"),o)};var j=/\/(java|ecma)script/i,w=e.fn.andSelf||e.fn.addBack;e.fn.andSelf=function(){return r("jQuery.fn.andSelf() replaced by jQuery.fn.addBack()"),w.apply(this,arguments)},e.clean||(e.clean=function(t,a,o,i){a=a||document,a=!a.nodeType&&a[0]||a,a=a.ownerDocument||a,r("jQuery.clean() is deprecated");var s,u,c,l,d=[];if(e.merge(d,e.buildFragment(t,a).childNodes),o)for(c=function(e){return!e.type||j.test(e.type)?i?i.push(e.parentNode?e.parentNode.removeChild(e):e):o.appendChild(e):n},s=0;null!=(u=d[s]);s++)e.nodeName(u,"script")&&c(u)||(o.appendChild(u),u.getElementsByTagName!==n&&(l=e.grep(e.merge([],u.getElementsByTagName("script")),c),d.splice.apply(d,[s+1,0].concat(l)),s+=l.length));return d});var Q=e.event.add,x=e.event.remove,k=e.event.trigger,N=e.fn.toggle,C=e.fn.live,S=e.fn.die,T="ajaxStart|ajaxStop|ajaxSend|ajaxComplete|ajaxError|ajaxSuccess",M=RegExp("\\b(?:"+T+")\\b"),H=/(?:^|\s)hover(\.\S+|)\b/,A=function(t){return"string"!=typeof t||e.event.special.hover?t:(H.test(t)&&r("'hover' pseudo-event is deprecated, use 'mouseenter mouseleave'"),t&&t.replace(H,"mouseenter$1 mouseleave$1"))};e.event.props&&"attrChange"!==e.event.props[0]&&e.event.props.unshift("attrChange","attrName","relatedNode","srcElement"),e.event.dispatch&&a(e.event,"handle",e.event.dispatch,"jQuery.event.handle is undocumented and deprecated"),e.event.add=function(e,t,n,a,o){e!==document&&M.test(t)&&r("AJAX events should be attached to document: "+t),Q.call(this,e,A(t||""),n,a,o)},e.event.remove=function(e,t,n,r,a){x.call(this,e,A(t)||"",n,r,a)},e.fn.error=function(){var e=Array.prototype.slice.call(arguments,0);return r("jQuery.fn.error() is deprecated"),e.splice(0,0,"error"),arguments.length?this.bind.apply(this,e):(this.triggerHandler.apply(this,e),this)},e.fn.toggle=function(t,n){if(!e.isFunction(t)||!e.isFunction(n))return N.apply(this,arguments);r("jQuery.fn.toggle(handler, handler...) is deprecated");var a=arguments,o=t.guid||e.guid++,i=0,s=function(n){var r=(e._data(this,"lastToggle"+t.guid)||0)%i;return e._data(this,"lastToggle"+t.guid,r+1),n.preventDefault(),a[r].apply(this,arguments)||!1};for(s.guid=o;a.length>i;)a[i++].guid=o;return this.click(s)},e.fn.live=function(t,n,a){return r("jQuery.fn.live() is deprecated"),C?C.apply(this,arguments):(e(this.context).on(t,this.selector,n,a),this)},e.fn.die=function(t,n){return r("jQuery.fn.die() is deprecated"),S?S.apply(this,arguments):(e(this.context).off(t,this.selector||"**",n),this)},e.event.trigger=function(e,t,n,a){return n||M.test(e)||r("Global events are undocumented and deprecated"),k.call(this,e,t,n||document,a)},e.each(T.split("|"),function(t,n){e.event.special[n]={setup:function(){var t=this;return t!==document&&(e.event.add(document,n+"."+e.guid,function(){e.event.trigger(n,null,t,!0)}),e._data(this,n,e.guid++)),!1},teardown:function(){return this!==document&&e.event.remove(document,n+"."+e._data(this,n)),!1}}})}(jQuery,window); 3 | //@ sourceMappingURL=dist/jquery-migrate.min.map -------------------------------------------------------------------------------- /testsite/app/static/js/jquery.ui.button.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.10.3 - 2013-05-03 2 | * http://jqueryui.com 3 | * Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ 4 | (function(t){var e,i,s,n,a="ui-button ui-widget ui-state-default ui-corner-all",o="ui-state-hover ui-state-active ",r="ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",h=function(){var e=t(this);setTimeout(function(){e.find(":ui-button").button("refresh")},1)},l=function(e){var i=e.name,s=e.form,n=t([]);return i&&(i=i.replace(/'/g,"\\'"),n=s?t(s).find("[name='"+i+"']"):t("[name='"+i+"']",e.ownerDocument).filter(function(){return!this.form})),n};t.widget("ui.button",{version:"1.10.3",defaultElement:" 32 | 33 | 34 | Editing Instructions 35 | mode 36 | 37 | 38 | 39 | 40 | 41 | 42 | 61 | 62 | 63 | 79 | 80 | 96 | 97 | 115 | 116 | 117 | 118 | 129 | 130 | 140 | {% endblock %} 141 | -------------------------------------------------------------------------------- /testsite/app/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends '/layouts/boilerplate.html' %} 2 | 3 | {% block body_class %}main main-index map{% endblock %} 4 | 5 | {% block layout %} 6 |
7 |

Crowdsourcing Neighborhoods

8 | 19 |
20 |
21 |
22 |
23 |

Nearby Counties

24 | 29 |
30 |
31 |
32 |
33 |

Search for a place

34 | {% include "_place_search.html" %} 35 |
36 |
37 |
38 |
39 |
40 |

Zetashapes is an experiment in crowdsourced US neighborhood polygons. From here, you can download neighborhood boundaries for every city in the US derived from geotags on flickr photos. You can also edit these boundaries and download custom GeoJSON with your edits applied. Your edits will also be contributed back into improving the overall boundaries.

41 | 42 |

still todo 43 |

    44 |
  • allow adding new neighborhoods 45 |
  • lazy loading of nearby counties 46 |
  • build the 4sq game 47 |
  • deal with overlapping/fuzzy/micro/mega neighborhoods 48 |
  • generate a nationwide file for download 49 |
  • possibly add a draw-from-scratch mode 50 |
  • highlight orphaned blocks 51 |
  • figure out how to balance user vs derived labels 52 |
53 |

54 | 55 |

56 | 57 |

58 |
59 |
60 | {% endblock %} 61 | -------------------------------------------------------------------------------- /testsite/app/templates/layouts/boilerplate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | The Neighborhoods Project {{ page_title }} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 41 | 42 | 52 | 53 | 54 | 55 |
56 | {% include "_nav.html" %} 57 | {% include "_messages.html" -%} 58 | {% block layout %}{% endblock -%} 59 | 62 |
63 | 64 | 65 | -------------------------------------------------------------------------------- /testsite/app/templates/license.html: -------------------------------------------------------------------------------- 1 | {% extends '/layouts/boilerplate.html' %} 2 | 3 | {% block body_class %}main main-thanks{% endblock %} 4 | 5 | {% block layout %} 6 |

License!

7 | 8 | The polygons generated by this site do not have any added restrictions beyond the base data from tiger and flickr.
9 |

10 | By submitting votes to this site you are agreeing that your votes will be public and in the public domain.
11 | 12 |

13 | The basic source data is from US TIGER/Line Census Data which is public domain (Q10). This site also makes use of data scraped from the flickr api -- you should probably mention on your site if you reuse this data that there is flickr data associated with it. 14 |

15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /testsite/app/templates/login.html: -------------------------------------------------------------------------------- 1 | {% from '_macros.html' import render_field %} 2 | 3 | {% extends '/layouts/boilerplate.html' %} 4 | 5 | {% block body_class %}main main-login{% endblock %} 6 | 7 | {% macro social_login(provider_id, display_name) %} 8 |

9 | 10 |
11 | {% endmacro %} 12 | 13 | {% block layout %} 14 | 17 |
18 |
19 | {{ social_login('twitter', 'Twitter' )}} 20 | {{ social_login('facebook', 'Facebook' )}} 21 | {{ social_login('foursquare', 'Foursquare' )}} 22 | {{ social_login('google', 'Google' )}} 23 | 24 |
25 |
26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /testsite/app/templates/profile.html: -------------------------------------------------------------------------------- 1 | {% extends '/layouts/boilerplate.html' %} 2 | 3 | {% block body_class %}main main-profile{% endblock %} 4 | 5 | {% macro show_provider_button(provider_id, display_name, conn, show_update_form=True) %} 6 |
7 |

{{ display_name }}

8 |
9 | {% if conn %} 10 |
11 | 12 |
13 | {% else %} 14 |
15 |
16 |
17 | 18 |
19 |
20 |
21 | {% endif %} 22 |
23 | {% endmacro %} 24 | 25 | {% block layout %} 26 | 41 | 42 |
43 |

Places you've edited

44 | {% if areas|length > 0 %} 45 | 50 | {% else %} 51 | You haven't edited any areas yet, why not start by searching for a city you know well?
52 | {% include "_place_search.html" %} 53 | {% endif %} 54 |
55 | 56 | 57 |
58 | {{ show_provider_button('twitter', 'Twitter', twitter_conn) }} 59 | {{ show_provider_button('facebook', 'Facebook', facebook_conn) }} 60 | {{ show_provider_button('foursquare', 'Foursquare', foursquare_conn) }} 61 | {{ show_provider_button('google', 'Google', google_conn) }} 62 |
63 | 68 | {% endblock %} 69 | -------------------------------------------------------------------------------- /testsite/app/templates/register.html: -------------------------------------------------------------------------------- 1 | {% from '_macros.html' import render_field %} 2 | 3 | {% extends '/layouts/boilerplate.html' %} 4 | 5 | {% block body_class %}main main-register{% endblock %} 6 | 7 | {% macro social_register(provider_id, display_name) %} 8 |
9 | 10 |
11 | {% endmacro %} 12 | 13 | {% block layout %} 14 | 17 |
18 | {% if provider %} 19 |

Register with your {{ provider.name }} account: {{ connection_values['display_name'] }}

20 | 21 |
22 |
23 | {{ form.hidden_tag() }} 24 | {{ render_field(form.email) }} 25 | 26 | 27 |
28 |
29 | {% else %} 30 | {% if not login_failed %} 31 |
32 | {{ social_register('twitter', 'Twitter' )}} 33 | {{ social_register('facebook', 'Facebook' )}} 34 | {{ social_register('google', 'Google' )}} 35 | 36 | 37 |
38 | {% endif %} 39 | {% endif %} 40 |
41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /testsite/app/templates/thanks.html: -------------------------------------------------------------------------------- 1 | {% extends '/layouts/boilerplate.html' %} 2 | 3 | {% block body_class %}main main-thanks{% endblock %} 4 | 5 | {% block layout %} 6 |

Thank You!

7 | 8 |

Thanks for registering {{ user.email }}! You can now view your profile page

9 | {% endblock %} -------------------------------------------------------------------------------- /testsite/app/tools.py: -------------------------------------------------------------------------------- 1 | 2 | from functools import wraps 3 | 4 | from flask import Response, current_app, request 5 | 6 | 7 | def check_auth(username, password): 8 | creds = current_app.config['ADMIN_CREDENTIALS'].split(',') 9 | return username == creds[0] and password == creds[1] 10 | 11 | 12 | def authenticate(): 13 | return Response( 14 | 'Could not verify your access level for that URL.\n' 15 | 'You have to login with proper credentials', 401, 16 | {'WWW-Authenticate': 'Basic realm="Login Required"'}) 17 | 18 | 19 | def requires_auth(f): 20 | @wraps(f) 21 | def decorated(*args, **kwargs): 22 | auth = request.authorization 23 | if not auth or not check_auth(auth.username, auth.password): 24 | return authenticate() 25 | return f(*args, **kwargs) 26 | return decorated 27 | -------------------------------------------------------------------------------- /testsite/app/views.py: -------------------------------------------------------------------------------- 1 | from flask import render_template, redirect, request, current_app, session, \ 2 | flash, url_for 3 | from flask.ext.security import LoginForm, current_user, login_required, \ 4 | login_user 5 | from flask.ext.social.utils import get_provider_or_404 6 | from flask.ext.social.views import connect_handler 7 | from .forms import RegisterForm 8 | 9 | from .forms import RegisterForm 10 | import geo_utils 11 | import vote_utils 12 | 13 | from . import app, db 14 | from .models import User 15 | from .tools import requires_auth 16 | 17 | import random 18 | import string 19 | 20 | import pygeoip 21 | gi = pygeoip.GeoIP('app/data/GeoLiteCity.dat', pygeoip.MEMORY_CACHE) 22 | 23 | import psycopg2 24 | import sqlalchemy.pool as pool 25 | psycopg2 = pool.manage(psycopg2) 26 | 27 | def getPostgresConnection(): 28 | import urlparse 29 | result = urlparse.urlparse(app.config['SQLALCHEMY_DATABASE_URI']) 30 | username = result.username 31 | password = result.password 32 | database = result.path[1:] 33 | hostname = result.hostname 34 | return psycopg2.connect( 35 | database = database, 36 | user = username, 37 | password = password, 38 | host = hostname 39 | ) 40 | 41 | 42 | @app.route('/api/test') 43 | def test(): 44 | return render_template('index.html', total_users=0, areas=[]) 45 | 46 | @app.route('/') 47 | def index(): 48 | conn = getPostgresConnection() 49 | print request.remote_addr 50 | areas = [] 51 | if request.access_route: 52 | geoip = gi.record_by_addr(request.remote_addr) 53 | if geoip and 'country_code' in geoip and geoip['country_code'] == 'US': 54 | areas = geo_utils.getNearestCounties(conn, geoip['latitude'], geoip['longitude']) 55 | 56 | return render_template('index.html', total_users=User.query.count(), areas=areas) 57 | 58 | 59 | @app.route('/login') 60 | def login(): 61 | if current_user.is_authenticated(): 62 | return redirect(request.referrer or '/') 63 | 64 | return render_template('login.html', form=LoginForm()) 65 | 66 | @app.route('/games/foursquare') 67 | def editor(areaid=None): 68 | return render_template('games/foursquare.html', areaid=areaid) 69 | 70 | @app.route('/editor/') 71 | def editor(areaid=None): 72 | return render_template('editor.html', areaid=areaid) 73 | 74 | @app.route('/register', methods=['GET', 'POST']) 75 | @app.route('/register/', methods=['GET', 'POST']) 76 | def register(provider_id=None): 77 | print 'REGISTER' 78 | if current_user.is_authenticated(): 79 | return redirect(request.referrer or '/') 80 | 81 | print 'HELLLLLLOOOOO' 82 | form = RegisterForm() 83 | 84 | if provider_id: 85 | print provider_id 86 | provider = get_provider_or_404(provider_id) 87 | connection_values = session.get('failed_login_connection', None) 88 | else: 89 | provider = None 90 | connection_values = None 91 | 92 | print 'true?' 93 | print provider 94 | print connection_values 95 | print form.validate_on_submit() 96 | print form.email.data 97 | 98 | if provider and connection_values and form.validate_on_submit(): 99 | char_set = string.ascii_uppercase + string.digits 100 | ds = current_app.security.datastore 101 | user = ds.create_user( 102 | email=form.email.data, 103 | password=''.join(random.sample(char_set*32,32)) 104 | ) 105 | user.api_key = ''.join(random.sample(char_set*32,32)) 106 | 107 | ds.commit() 108 | 109 | # See if there was an attempted social login prior to registering 110 | # and if so use the provider connect_handler to save a connection 111 | connection_values = session.pop('failed_login_connection', None) 112 | 113 | if connection_values: 114 | connection_values['user_id'] = user.id 115 | connect_handler(connection_values, provider) 116 | 117 | if login_user(user): 118 | ds.commit() 119 | flash('Account created successfully', 'info') 120 | return redirect(url_for('profile')) 121 | 122 | return render_template('thanks.html', user=user) 123 | 124 | login_failed = int(request.args.get('login_failed', 0)) 125 | print "login failed? " 126 | print login_failed 127 | 128 | return render_template('register.html', 129 | form=form, 130 | provider=provider, 131 | login_failed=login_failed, 132 | connection_values=connection_values) 133 | 134 | 135 | @app.route('/profile') 136 | @login_required 137 | def profile(): 138 | conn = getPostgresConnection() 139 | areaids = vote_utils.getAreaIdsForUserId(conn, current_user.id) 140 | areas = geo_utils.getInfoForAreaIds(conn, areaids) 141 | return render_template('profile.html', 142 | areas=areas, 143 | twitter_conn=current_app.social.twitter.get_connection(), 144 | facebook_conn=current_app.social.facebook.get_connection(), 145 | foursquare_conn=current_app.social.foursquare.get_connection(), 146 | google_conn=current_app.social.google.get_connection() 147 | ) 148 | 149 | 150 | @app.route('/admin') 151 | @requires_auth 152 | def admin(): 153 | users = User.query.all() 154 | return render_template('admin.html', users=users) 155 | 156 | @app.route('/license') 157 | def license(): 158 | return render_template('license.html') 159 | 160 | @app.route('/admin/users/', methods=['DELETE']) 161 | @requires_auth 162 | def delete_user(user_id): 163 | user = User.query.get_or_404(user_id) 164 | db.session.delete(user) 165 | db.session.commit() 166 | flash('User deleted successfully', 'info') 167 | return redirect(url_for('admin')) 168 | -------------------------------------------------------------------------------- /testsite/app/vote_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from flask import Flask 4 | import flask_gzip 5 | import json 6 | import re 7 | from functools import wraps 8 | from collections import namedtuple 9 | from flask import redirect, request, current_app, jsonify 10 | import psycopg2 11 | import psycopg2.extras 12 | from collections import defaultdict 13 | import os 14 | from itertools import groupby 15 | from shapely.ops import cascaded_union 16 | from shapely.geometry import mapping, asShape 17 | 18 | VOTES_TABLE = 'votes_dev' 19 | USER_VOTES_TABLE = 'user_votes_dev' 20 | 21 | def pickBestVotesHelper(votes, preferSmear=True, preferOfficial=True): 22 | maxVote = None 23 | 24 | selfVotes = [v for v in votes if v['source'] == 'self'] 25 | positiveSelfVotes = None 26 | negativeSelfVotes = [] 27 | if len(selfVotes) > 0: 28 | negativeSelfVotes = [v for v in selfVotes if v['count'] < 0] 29 | positiveSelfVotes = [v for v in selfVotes if v['count'] > 0] 30 | if negativeSelfVotes and not positiveSelfVotes: 31 | pass 32 | else: 33 | votes = positiveSelfVotes 34 | if not maxVote and len(votes) > 0: 35 | maxVote = max(votes, key=lambda x:x['count']) 36 | 37 | negativeSelfBlocks = set([b['id'] for b in negativeSelfVotes]) 38 | usersVotes = [v for v in votes if v['source'] == 'users' and v['id'] not in negativeSelfBlocks] 39 | if usersVotes and not positiveSelfVotes: 40 | usersVotes.sort(key=lambda x: x['count'] * -1) 41 | return [usersVotes[0],] 42 | 43 | officialVotes = [v for v in votes if v['source'].startswith('official')] 44 | if preferOfficial and officialVotes and not positiveSelfVotes: 45 | return [officialVotes[0],] 46 | 47 | blockrVotes = [v for v in votes if v['source'] == 'blockr'] 48 | if preferSmear and blockrVotes and not positiveSelfVotes: 49 | return [blockrVotes[0],] 50 | 51 | smearVotes = [v for v in votes if v['source'] == 'smear'] 52 | if preferSmear and smearVotes and not positiveSelfVotes: 53 | return [smearVotes[0],] 54 | 55 | if maxVote: 56 | return [maxVote,] 57 | 58 | return [] 59 | 60 | def pickBestVotes(votes, preferSmear=True, preferOfficial=True): 61 | maxVotes = pickBestVotesHelper(votes, preferSmear, preferOfficial) 62 | if maxVotes and maxVotes[0]['id'] == -1: 63 | return [] 64 | else: 65 | return maxVotes 66 | 67 | def getAreaIdsForUserId(conn, userId): 68 | cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) 69 | cur.execute("""select blockid FROM """ + USER_VOTES_TABLE + """ v JOIN geoplanet_places g ON v.woe_id = g.woe_id WHERE v.userid = %s""" % (userId)) 70 | areaids = tuple(set([x['blockid'][0:5] for x in cur.fetchall()])) 71 | return areaids 72 | 73 | def addUserVotes(userVoteRows, votesDict): 74 | dedupedRows = {} 75 | 76 | # take your first positive vote, unless you have a negative vote that comes after it that invalidates that 77 | for r in userVoteRows: 78 | if r['blockid'] in dedupedRows: 79 | if ( 80 | dedupedRows[r['blockid']]['weight'] == 1 and 81 | dedupedRows[r['blockid']]['woe_id'] == r['woe_id'] and 82 | r['weight'] == -1 83 | ): 84 | dedupedRows[r['blockid']] = r 85 | if r['weight'] == 1: 86 | dedupedRows[r['blockid']] = r 87 | else: 88 | dedupedRows[r['blockid']] = r 89 | 90 | for r in dedupedRows.values(): 91 | votesDict[r['blockid']].append({ 92 | 'label': r['name'], 93 | 'id': r['woe_id'], 94 | 'source': 'self', 95 | 'count': r['weight'] 96 | }) 97 | 98 | def buildVoteDict(rows): 99 | votes = defaultdict(list) 100 | for r in rows: 101 | votes[r['id']].append({ 102 | 'label': r['name'], 103 | 'id': r['woe_id'], 104 | 'count': r['count'], 105 | 'source': r['source'] 106 | }) 107 | return votes 108 | 109 | def getUserVotesForBlocks(conn, userId, blockids): 110 | cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) 111 | comm = cur.mogrify("""select * FROM """ + USER_VOTES_TABLE + """ WHERE userid=%s AND blockid IN %s""", ( 112 | userId, 113 | blockids 114 | )) 115 | #print comm 116 | cur.execute(comm) 117 | 118 | return cur.fetchall() 119 | 120 | def getVotesForBlocks(conn, blockids, user): 121 | cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) 122 | cur.execute("""select woe_id, id, label, count, v.source, name FROM """ + VOTES_TABLE + """ v JOIN geoplanet_places ON label::int = woe_id WHERE id IN %s""", (tuple(blockids),)) 123 | votes = buildVoteDict(cur.fetchall()) 124 | if user: 125 | print user 126 | userId = user['id'] 127 | cur.execute("""select g.woe_id, blockid, name, weight FROM """ + USER_VOTES_TABLE + """ v JOIN geoplanet_places g ON v.woe_id = g.woe_id WHERE v.userid = %s AND v.blockid IN %s ORDER BY ts ASC""", (userId, tuple(blockids))) 128 | addUserVotes(cur.fetchall(), votes) 129 | return votes 130 | 131 | def getVotes(conn, areaids, user): 132 | cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) 133 | 134 | areaid_clauses = [] 135 | for areaid in areaids: 136 | statefp10 = areaid[0:2] 137 | countyfp10 = areaid[2:] 138 | areaid_clauses.append(cur.mogrify("statefp10 = %s AND countyfp10 = %s", (statefp10, countyfp10))) 139 | 140 | if not areaids: 141 | raise Exception("uh, missing areaid???") 142 | print areaid 143 | 144 | print 'getting blocks with geoms' 145 | cur.execute("""select geoid10, pop10, housing10, ST_AsGeoJSON(ST_Transform(geom, 4326)) as geojson_geom FROM tabblock2010_pophu tb WHERE (""" + ' OR '.join(areaid_clauses) + """) AND blockce10 NOT LIKE '0%%'""", (statefp10, countyfp10)) 146 | rows = cur.fetchall() 147 | print 'got' 148 | 149 | 150 | print 'getting votes' 151 | id_clauses = [] 152 | for areaid in areaids: 153 | id_clauses.append("id LIKE '%s%%'" % areaid) 154 | cur.execute("""select woe_id, id, label, count, v.source, name FROM """ + VOTES_TABLE + """ v JOIN geoplanet_places ON label::int = woe_id WHERE """ + ' OR '.join(id_clauses)) 155 | globalVotes = cur.fetchall() 156 | print 'got' 157 | 158 | votes = buildVoteDict(globalVotes) 159 | 160 | id_clauses = [] 161 | for areaid in areaids: 162 | id_clauses.append("v.blockid LIKE '%s%%'" % areaid) 163 | 164 | user_votes = {} 165 | print 'user? %s' % user 166 | print user 167 | if user: 168 | userId = user['id'] 169 | print 'getting user votes' 170 | cur.execute("""select g.woe_id, blockid, name, weight FROM """ + USER_VOTES_TABLE + """ v JOIN geoplanet_places g ON v.woe_id = g.woe_id WHERE v.userid = %s """ % userId + """ AND (""" + ' OR '.join(id_clauses) + """) ORDER BY ts ASC""") 171 | print 'got' 172 | addUserVotes(cur.fetchall(), votes) 173 | 174 | return (rows, votes) 175 | 176 | -------------------------------------------------------------------------------- /testsite/cookbooks/flask-social-example/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/cookbooks/flask-social-example/README.md -------------------------------------------------------------------------------- /testsite/cookbooks/flask-social-example/attributes/default.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackmad/zetashapes/8ab8fe1fc277a71b0e7a5f29b4f550ce4d071b21/testsite/cookbooks/flask-social-example/attributes/default.rb -------------------------------------------------------------------------------- /testsite/cookbooks/flask-social-example/metadata.rb: -------------------------------------------------------------------------------- 1 | name "flask-social-example" 2 | maintainer "Matt Wright" 3 | maintainer_email "matt@nobien.net" 4 | license "Apache 2.0" 5 | description "Installs and configures various software to run flask-social-example" 6 | long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) 7 | version "0.1.0" 8 | recipe "flask-social-example", "Installs and configures various software to run flask-social-example" 9 | 10 | supports "ubuntu" 11 | depends "database" -------------------------------------------------------------------------------- /testsite/cookbooks/flask-social-example/recipes/default.rb: -------------------------------------------------------------------------------- 1 | 2 | include_recipe "postgresql::server" 3 | include_recipe "database::postgresql" 4 | 5 | postgresql_connection_info = { 6 | :host => 'localhost' , 7 | :port => 5432, 8 | :username => 'postgres', 9 | :password => node['postgresql']['password']['postgres'] 10 | } 11 | 12 | postgresql_database "flask_social_example_development" do 13 | connection postgresql_connection_info 14 | action :create 15 | end -------------------------------------------------------------------------------- /testsite/db_create.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from app import db 3 | from app.models import User 4 | import os.path 5 | db.create_all() 6 | admin = User() 7 | admin.username = 'admin' 8 | db.session.add(admin) 9 | db.session.commit() 10 | -------------------------------------------------------------------------------- /testsite/generate-areainfo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import json 4 | import re 5 | from functools import wraps 6 | from collections import namedtuple 7 | import psycopg2 8 | import psycopg2.extras 9 | from collections import defaultdict 10 | import os 11 | import sys 12 | from itertools import groupby 13 | from shapely.ops import cascaded_union 14 | from shapely.geometry import mapping, asShape 15 | from shapely import speedups 16 | import app.geo_utils 17 | 18 | conn = psycopg2.connect("dbname='gis' user='blackmad' host='localhost' password='xxx'") 19 | 20 | areaid = sys.argv[1] 21 | 22 | areaInfos = app.geo_utils.getInfoForAreaIds(conn, [areaid,]) 23 | json.dump({'areas': areaInfos}, open('app/static/json/info-%s.json' % areaid, 'w')) 24 | -------------------------------------------------------------------------------- /testsite/generate-cities.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import json 4 | import re 5 | from functools import wraps 6 | from collections import namedtuple 7 | import psycopg2 8 | import psycopg2.extras 9 | from collections import defaultdict 10 | import os 11 | from itertools import groupby 12 | from shapely.ops import cascaded_union 13 | from shapely.geometry import mapping, asShape 14 | from shapely import speedups 15 | import app.geo_utils 16 | 17 | conn = psycopg2.connect("dbname='gis' user='blackmad' host='localhost' password='xxx'") 18 | cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) 19 | cur.execute("select * FROM geoname WHERE population > 0 AND country = 'US'") 20 | places = [] 21 | for r in cur: 22 | if r['admin2'] and r['fclass'] == 'P': 23 | areaid = '%s%s' % (app.geo_utils.state_codes[r['admin1']], r['admin2']) 24 | name = r['name'].replace('City of', '').replace('Town of', '').replace('Village of', '').strip() 25 | places.append(("[\"%s, %s\",%s]," % (name, r['admin1'], areaid), r['population'])) 26 | 27 | for r in sorted(list(set(places)), key=lambda x: x[1]*-1): 28 | print r[0] 29 | 30 | -------------------------------------------------------------------------------- /testsite/make-json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | import psycopg2 5 | import json 6 | 7 | conn = psycopg2.connect("dbname='gis' user='blackmad' host='localhost' password='xxx'") 8 | cur = conn.cursor() 9 | 10 | areaid = sys.argv[1] 11 | statefp10 = areaid[0:2] 12 | countyfp10 = areaid[2:] 13 | 14 | def makeFeature(row): 15 | return { 16 | "type": "Feature", 17 | "geometry": eval(row[1]), 18 | "properties": { 19 | "id": row[0] 20 | } 21 | } 22 | 23 | output = open('static/json/%s.json' % areaid, 'w') 24 | cur.execute("""select geoid10, ST_AsGeoJSON(ST_Transform(geom, 4326)) as geojson_geom FROM tabblock10 tb WHERE statefp10 = %s AND countyfp10 = %s AND blockce10 NOT LIKE '0%%'""", (statefp10, countyfp10)) 25 | blocks = [] 26 | for r in cur: 27 | blocks.append(makeFeature(r)) 28 | 29 | response = { 30 | "type": "FeatureCollection", 31 | "features": blocks 32 | } 33 | json.dump(response, output) 34 | 35 | 36 | -------------------------------------------------------------------------------- /testsite/manage.py: -------------------------------------------------------------------------------- 1 | from flask.ext.assets import ManageAssets 2 | from flask.ext.script import Manager 3 | from flask.ext.security.script import CreateUserCommand 4 | 5 | from app import app 6 | 7 | manager = Manager(app) 8 | manager.add_command("assets", ManageAssets()) 9 | manager.add_command('create_user', CreateUserCommand()) 10 | 11 | if __name__ == "__main__": 12 | manager.run() 13 | -------------------------------------------------------------------------------- /testsite/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.9 2 | Flask-Assets==0.7 3 | Flask-Login==0.1.3 4 | Flask-Mail==0.7.3 5 | Flask-OAuth==0.12 6 | Flask-Principal==0.3.3 7 | Flask-SQLAlchemy==0.15 8 | Flask-Script==0.3.2 9 | Flask-Security==1.5.2 10 | Flask-Social==1.5.3 11 | Flask-WTF==0.8 12 | Jinja2==2.6 13 | PyYAML==3.10 14 | SQLAlchemy==0.7.6 15 | WTForms==1.0.2 16 | Werkzeug==0.8.3 17 | argparse==1.2.1 18 | blinker==1.2 19 | chardet==1.0.1 20 | cssmin==0.1.4 21 | distribute==0.6.28 22 | facebook-sdk==0.3.1 23 | github3.py==0.2 24 | gunicorn==0.14.6 25 | httplib2==0.7.7 26 | itsdangerous==0.17 27 | jsmin==2.0.2 28 | lockfile==0.9.1 29 | mock==0.8.0 30 | nose==1.1.2 31 | oauth2==1.5.211 32 | passlib==1.6.1 33 | psycopg2==2.4.4 34 | python-daemon==1.6 35 | python-twitter==0.8.2 36 | requests==0.14.1 37 | simplejson==2.4.0 38 | webassets==0.7 39 | wsgiref==0.1.2 40 | -------------------------------------------------------------------------------- /testsite/wsgi.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | from app import app 5 | 6 | if __name__ == '__main__': 7 | port = int(os.environ.get('PORT', 8000)) 8 | app.run(host='0.0.0.0', port=port) 9 | -------------------------------------------------------------------------------- /votes.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- PostgreSQL database dump 3 | -- 4 | 5 | SET statement_timeout = 0; 6 | SET client_encoding = 'UTF8'; 7 | SET standard_conforming_strings = on; 8 | SET check_function_bodies = false; 9 | SET client_min_messages = warning; 10 | 11 | SET search_path = public, pg_catalog; 12 | 13 | SET default_tablespace = ''; 14 | 15 | SET default_with_oids = false; 16 | 17 | -- 18 | -- Name: votes; Type: TABLE; Schema: public; Owner: blackmad; Tablespace: 19 | -- 20 | 21 | CREATE TABLE votes ( 22 | id character varying(100) NOT NULL, 23 | label character varying(100) NOT NULL, 24 | count integer, 25 | source character varying(100) NOT NULL, 26 | countyfp10 character varying(10), 27 | statefp10 character varying(10) 28 | ); 29 | 30 | 31 | ALTER TABLE public.votes OWNER TO blackmad; 32 | 33 | -- 34 | -- Name: votes_pkey; Type: CONSTRAINT; Schema: public; Owner: blackmad; Tablespace: 35 | -- 36 | 37 | ALTER TABLE ONLY votes 38 | ADD CONSTRAINT votes_pkey PRIMARY KEY (id, label, source); 39 | 40 | 41 | -- 42 | -- PostgreSQL database dump complete 43 | -- 44 | 45 | --------------------------------------------------------------------------------