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/static/js/jquery.ui.effect-fold.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){t.effects.effect.fold=function(e,i){var s,n,a=t(this),o=["position","top","bottom","left","right","height","width"],r=t.effects.setMode(a,e.mode||"hide"),h="show"===r,l="hide"===r,c=e.size||15,u=/([0-9]+)%/.exec(c),d=!!e.horizFirst,p=h!==d,f=p?["width","height"]:["height","width"],m=e.duration/2,g={},v={};t.effects.save(a,o),a.show(),s=t.effects.createWrapper(a).css({overflow:"hidden"}),n=p?[s.width(),s.height()]:[s.height(),s.width()],u&&(c=parseInt(u[1],10)/100*n[l?0:1]),h&&s.css(d?{height:0,width:c}:{height:c,width:0}),g[f[0]]=h?n[0]:c,v[f[1]]=h?n[1]:0,s.animate(g,m,e.easing).animate(v,m,e.easing,function(){l&&a.hide(),t.effects.restore(a,o),t.effects.removeWrapper(a),i()})}})(jQuery);
--------------------------------------------------------------------------------
/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/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 |
11 | {% endmacro %}
12 |
13 | {% block layout %}
14 |
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 |
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 |
47 | Click on a colored block to remove it from the current neighborhood, click on a clear block to add it.
48 |
49 | Removed blocks will remain unlabeled until you edit a different neighborhood and attach them there.
50 |
51 | To make a bigger addition, alt or command click to begin drawing a polygon, and alt or command click to end it.
52 | By default, the selection will be added to the existing neighborhood, this can be changed with
53 | r for redraw mode, and d for delete mode, and a to get back to add.
54 |
55 | Another option is to turn on continuous mode with
56 | c. Once this is enabled, all blocks moused over will be added (or deleted depending on the mode), hit c again to go back to polygon mode.
57 |
58 | (delete neighborhood coming soon)
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
Neighborhood Select
67 |
68 |
69 |
70 |
71 |
Other:
72 |
73 |
74 |
78 |
79 |
80 |
81 |
82 |
83 |
Delete ?
84 |
85 |
86 |
87 |
Reassign blocks to:
88 |
89 |
90 |
95 |
96 |
97 |
98 |
99 |
100 |
Add a new neighborhood?
101 |
102 |
103 |
104 |
Neighborhood:
105 |
106 |
107 |
In city:
108 |
109 |
110 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
Please Login to Edit
122 |
123 |
124 |
125 | If you'd like to contribute to the zetashapes project, please login or register.
126 |
127 |
128 |
129 |
130 |
140 | {% endblock %}
141 |
--------------------------------------------------------------------------------
/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/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:"