├── .gitignore
├── .gitmodules
├── CHANGES.txt
├── MANIFEST.in
├── OSMTM
├── __init__.py
├── history_meta.py
├── models.py
├── resources.py
├── static
│ ├── crossdomain.xml
│ ├── css
│ │ ├── main.less
│ │ ├── main.less.min.css
│ │ ├── map.less
│ │ └── reset.css
│ ├── img
│ │ ├── ajax-loader.gif
│ │ ├── blank.gif
│ │ ├── bm.jpg
│ │ ├── closed_back.gif
│ │ ├── feedback.png
│ │ ├── glyphicons-halflings-white.png
│ │ ├── glyphicons-halflings.png
│ │ ├── lock.gif
│ │ ├── map_pin.png
│ │ ├── marker.png
│ │ ├── marker.svg
│ │ ├── marker_grey.png
│ │ ├── marker_shadow.png
│ │ ├── ol_labeled.svg
│ │ ├── split.png
│ │ ├── status-offline.png
│ │ ├── status.png
│ │ ├── tour
│ │ │ ├── tour1.png
│ │ │ ├── tour3.png
│ │ │ ├── tour_auth_osm.png
│ │ │ ├── tour_home.png
│ │ │ ├── tour_job_task_do.png
│ │ │ ├── tour_job_task_take.png
│ │ │ ├── tour_job_workflow.png
│ │ │ └── tour_josm.png
│ │ ├── world_map.png
│ │ └── zoom-panel.png
│ ├── js
│ │ ├── home.js
│ │ ├── job.edit.js
│ │ ├── job.js
│ │ ├── job.new.js
│ │ ├── less-1.2.2.min.js
│ │ ├── lib
│ │ │ ├── OpenLayers.js
│ │ │ ├── highcharts.js
│ │ │ ├── jquery-1.7.1.min.js
│ │ │ ├── knockout-2.1.0.js
│ │ │ ├── sammy-latest.min.js
│ │ │ └── showdown.js
│ │ ├── main.js
│ │ ├── ol_osmtm.cfg
│ │ └── task.js
│ ├── middlebg.png
│ ├── red-header.png
│ ├── theme
│ │ └── default
│ │ │ └── style.css
│ ├── thumb-up.png
│ └── thumb.png
├── templates
│ ├── about.mako
│ ├── admin.mako
│ ├── base.mako
│ ├── forbidden.mako
│ ├── home.mako
│ ├── imagery.mako
│ ├── job.edit.mako
│ ├── job.mako
│ ├── job.new.mako
│ ├── job.tags.mako
│ ├── job.task_extra.mako
│ ├── job.users.mako
│ ├── license.edit.mako
│ ├── license.mako
│ ├── licenses.mako
│ ├── task.comments.mako
│ ├── task.empty.mako
│ ├── task.gpx.mako
│ ├── task.mako
│ ├── task.osm.mako
│ ├── tour.mako
│ ├── user.mako
│ ├── user_edit.mako
│ └── users.mako
├── tests.py
├── utils.py
└── views
│ ├── __init__.py
│ ├── admin.py
│ ├── crossdomain.py
│ ├── jobs.py
│ ├── license.py
│ ├── osmproxy.py
│ ├── security.py
│ ├── tasks.py
│ └── views.py
├── README.rst
├── README.txt
├── alembic.ini
├── alembic
├── README
├── env.py
├── script.py.mako
└── versions
│ ├── 173a7003596f_added_author_column.py
│ ├── 183cfaae4493_adding_done_and_upda.py
│ ├── 2edcc95ff6f7_.py
│ ├── 2faee55a9ed8_upgrade_after_workfl.py
│ ├── 41cd7451a8d7_adding_offset_fields.py
│ ├── 4fbba110ab8b_adding_index_for_til.py
│ ├── 5229d2fd908d_adding_task_extra_co.py
│ └── 55009f9d3296_added_licenses_table.py
├── development.ini
├── migration
├── README
├── __init__.py
├── manage.py
├── migrate.cfg
└── versions
│ ├── 001_Adding_next_view_related_table_and_columns.py
│ ├── 002_Remove_role_column,_add_admin_column.py
│ ├── 003_Add_short_description_column.py
│ ├── 004_Change_checkout_column_to_update.py
│ ├── 005_Add_status_column_in_jobs_table.py
│ ├── 006_Add_tags.py
│ ├── 007_Add_preset_column.py
│ ├── 008_Adding_new_change_column.py
│ ├── 009_Adding_featured_attribute.py
│ ├── 010_Move_zoom_from_job_to_task.py
│ ├── __init__.py
│ ├── data_upgrade_008.py
│ └── data_upgrade_010.py
├── production.ini
├── scripts
├── delete.py
└── update.py
├── setup.cfg
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | OSMTM.egg-info/
2 | data/
3 | *.pyc
4 | env/
5 | build/
6 | dist/
7 | *.db
8 | *.sw*
9 | .coverage
10 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "OSMTM/static/js/openlayers"]
2 | path = OSMTM/static/js/openlayers
3 | url = git://github.com/openlayers/openlayers.git
4 | [submodule "OSMTM/static/bootstrap"]
5 | path = OSMTM/static/bootstrap
6 | url = git://github.com/twbs/bootstrap.git
7 | [submodule "OSMTM/static/js/lib/openlayers"]
8 | path = OSMTM/static/js/lib/openlayers
9 | url = git://github.com/openlayers/openlayers.git
10 |
--------------------------------------------------------------------------------
/CHANGES.txt:
--------------------------------------------------------------------------------
1 | 0.2.1 (2014-02-09)
2 | ------------------
3 |
4 | - Fixing minor bugs (link to user page, per user map)
5 |
6 | 0.2 (2014-02-09)
7 | ----------------
8 |
9 | - Major changes in the UI
10 |
11 | - Task modifications history
12 |
13 | - Deep linking
14 |
15 | - Better iD integration (gpx and imagery auto loads)
16 |
17 | - Fixed python packages dependency (simplejson)
18 |
19 | 0.1 (2014-02-09)
20 | ----------------
21 |
22 | - Before workflow branch merge
23 |
24 | 0.0
25 | ---
26 |
27 | - Initial version
28 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include *.txt *.ini *.cfg *.rst
2 | recursive-include OSMTM *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
3 |
--------------------------------------------------------------------------------
/OSMTM/__init__.py:
--------------------------------------------------------------------------------
1 | from pyramid_beaker import session_factory_from_settings
2 | from pyramid.config import Configurator
3 | from sqlalchemy import engine_from_config
4 | from pyramid.authentication import AuthTktAuthenticationPolicy
5 | from pyramid.authorization import ACLAuthorizationPolicy
6 | from papyrus.renderers import GeoJSON
7 |
8 | from OSMTM.models import initialize_sql, group_membership
9 |
10 | def main(global_config, **settings):
11 | """ This function returns a Pyramid WSGI application.
12 | """
13 |
14 | settings['mako.directories'] = 'OSMTM:templates'
15 | engine = engine_from_config(settings, 'sqlalchemy.')
16 | admin_user = settings['admin_user']
17 | initialize_sql(engine, admin_user)
18 | authn_policy = AuthTktAuthenticationPolicy(
19 | secret='super_secret', callback=group_membership)
20 | authz_policy = ACLAuthorizationPolicy()
21 | config = Configurator(settings=settings,
22 | root_factory='OSMTM.models.RootFactory',
23 | authentication_policy=authn_policy,
24 | authorization_policy=authz_policy)
25 |
26 | session_factory = session_factory_from_settings(settings)
27 | config.set_session_factory(session_factory)
28 |
29 | config.add_static_view('static', 'OSMTM:static', cache_max_age=3600)
30 |
31 | config.add_view('OSMTM.views.crossdomain.crossdomain_view', name='crossdomain.xml')
32 | config.add_route('crossdomain', '/crossdomain.xml',
33 | view='OSMTM.views.crossdomain.crossdomain_view')
34 | config.add_route('home', '/')
35 | config.add_route('about', '/about')
36 | config.add_route('tour', '/tour')
37 | config.add_route('login', '/login')
38 | config.add_route('logout', '/logout')
39 | config.add_route('admin', '/admin')
40 | config.add_route('job_new', '/job/new')
41 | config.add_route('job_geom', '/job/{job}.json')
42 | config.add_route('job_tiles', '/job/{job}/tiles')
43 | config.add_route('job_tiles_status', '/job/{job}/tiles_status')
44 | config.add_route('job', '/job/{job}', factory='OSMTM.resources.JobFactory')
45 | config.add_route('job_edit', '/job/{job}/edit', factory='OSMTM.resources.JobFactory')
46 | config.add_route('job_feature', '/job/{job}/feature', factory='OSMTM.resources.JobFactory')
47 | config.add_route('job_archive', '/job/{job}/archive', factory='OSMTM.resources.JobFactory')
48 | config.add_route('job_publish', '/job/{job}/publish', factory='OSMTM.resources.JobFactory')
49 | config.add_route('job_users', '/job/{job}/users', factory='OSMTM.resources.JobFactory')
50 | config.add_route('job_user', '/job/{job}/user/{user}', factory='OSMTM.resources.JobFactory')
51 | config.add_route('job_tags', '/job/{job}/tags', factory='OSMTM.resources.JobFactory')
52 | config.add_route('job_export', '/job/{job}/export', factory='OSMTM.resources.JobFactory')
53 | config.add_route('job_preset', '/job/{job}/preset', factory='OSMTM.resources.JobFactory')
54 | config.add_route('job_stats', '/job/{job}/stats', factory='OSMTM.resources.JobFactory')
55 | config.add_route('job_contributors', '/job/{job}/contributors', factory='OSMTM.resources.JobFactory')
56 | config.add_route('task_take_random', '/job/{job}/task/take/{checkin}', factory='OSMTM.resources.JobFactory')
57 | config.add_route('task_xhr', '/job/{job}/task/{x}/{y}/{zoom}', factory='OSMTM.resources.JobFactory', xhr=True)
58 | config.add_route('task_empty', '/job/{job}/task', factory='OSMTM.resources.JobFactory')
59 | config.add_route('task', '/job/{job}/task/{x}/{y}/{zoom}', factory='OSMTM.resources.JobFactory')
60 | config.add_route('task_unlock', '/job/{job}/task/{x}/{y}/{zoom}/unlock', factory='OSMTM.resources.JobFactory')
61 | config.add_route('task_done', '/job/{job}/task/{x}/{y}/{zoom}/done', factory='OSMTM.resources.JobFactory')
62 | config.add_route('task_lock', '/job/{job}/task/{x}/{y}/{zoom}/lock', factory='OSMTM.resources.JobFactory')
63 | config.add_route('task_export_osm', '/job/{job}/task/{x}/{y}/{zoom}/export.osm', factory='OSMTM.resources.JobFactory')
64 | config.add_route('task_export_gpx', '/job/{job}/task/{x}/{y}/{zoom}/export.gpx', factory='OSMTM.resources.JobFactory')
65 | config.add_route('task_split', '/job/{job}/task/{x}/{y}/{zoom}/split', factory='OSMTM.resources.JobFactory')
66 | config.add_route('license_new', '/license/new')
67 | config.add_route('license', '/license/{license}')
68 | config.add_route('licenses', '/licenses')
69 | config.add_route('license_edit', '/license/{license}/edit')
70 | config.add_route('license_delete', '/license/{license}/delete')
71 | config.add_route('user_add', '/user/add')
72 | config.add_route('user', '/user/{id}')
73 | config.add_route('user_edit', '/user/{id}/edit')
74 | config.add_route('user_update', '/user/{id}/update')
75 | config.add_route('users', '/users')
76 | config.add_route('osmproxy', '/osmproxy')
77 | config.add_route('oauth_callback', '/oauth_callback')
78 | config.add_view('OSMTM.views.security.login',
79 | renderer='forbidden.mako',
80 | context='pyramid.exceptions.Forbidden')
81 |
82 | config.add_renderer('geojson', GeoJSON())
83 |
84 | config.scan()
85 | return config.make_wsgi_app()
86 |
87 |
--------------------------------------------------------------------------------
/OSMTM/history_meta.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.ext.declarative import DeclarativeMeta
2 | from sqlalchemy.orm import mapper, class_mapper, attributes, object_mapper
3 | from sqlalchemy.orm.exc import UnmappedClassError, UnmappedColumnError
4 | from sqlalchemy import Table, Column, ForeignKeyConstraint, Integer
5 | from sqlalchemy.orm.interfaces import SessionExtension
6 | from sqlalchemy.orm.properties import RelationshipProperty
7 |
8 | def col_references_table(col, table):
9 | for fk in col.foreign_keys:
10 | if fk.references(table):
11 | return True
12 | return False
13 |
14 | def _history_mapper(local_mapper):
15 | cls = local_mapper.class_
16 |
17 | # set the "active_history" flag
18 | # on on column-mapped attributes so that the old version
19 | # of the info is always loaded (currently sets it on all attributes)
20 | for prop in local_mapper.iterate_properties:
21 | getattr(local_mapper.class_, prop.key).impl.active_history = True
22 |
23 | super_mapper = local_mapper.inherits
24 | super_history_mapper = getattr(cls, '__history_mapper__', None)
25 |
26 | polymorphic_on = None
27 | super_fks = []
28 | if not super_mapper or local_mapper.local_table is not super_mapper.local_table:
29 | cols = []
30 | for column in local_mapper.local_table.c:
31 | if column.name == 'version':
32 | continue
33 |
34 | col = column.copy()
35 | col.unique = False
36 |
37 | if super_mapper and col_references_table(column, super_mapper.local_table):
38 | super_fks.append((col.key, list(super_history_mapper.base_mapper.local_table.primary_key)[0]))
39 |
40 | cols.append(col)
41 |
42 | if column is local_mapper.polymorphic_on:
43 | polymorphic_on = col
44 |
45 | if super_mapper:
46 | super_fks.append(('version', super_history_mapper.base_mapper.local_table.c.version))
47 | cols.append(Column('version', Integer, primary_key=True))
48 | else:
49 | cols.append(Column('version', Integer, primary_key=True))
50 |
51 | if super_fks:
52 | cols.append(ForeignKeyConstraint(*zip(*super_fks)))
53 |
54 | table = Table(local_mapper.local_table.name + '_history', local_mapper.local_table.metadata,
55 | *cols
56 | )
57 | else:
58 | # single table inheritance. take any additional columns that may have
59 | # been added and add them to the history table.
60 | for column in local_mapper.local_table.c:
61 | if column.key not in super_history_mapper.local_table.c:
62 | col = column.copy()
63 | super_history_mapper.local_table.append_column(col)
64 | table = None
65 |
66 | if super_history_mapper:
67 | bases = (super_history_mapper.class_,)
68 | else:
69 | bases = local_mapper.base_mapper.class_.__bases__
70 | versioned_cls = type.__new__(type, "%sHistory" % cls.__name__, bases, {})
71 |
72 | m = mapper(
73 | versioned_cls,
74 | table,
75 | inherits=super_history_mapper,
76 | polymorphic_on=polymorphic_on,
77 | polymorphic_identity=local_mapper.polymorphic_identity
78 | )
79 | cls.__history_mapper__ = m
80 |
81 | if not super_history_mapper:
82 | cls.version = Column('version', Integer, default=1, nullable=False)
83 |
84 |
85 | class VersionedMeta(DeclarativeMeta):
86 | def __init__(cls, classname, bases, dict_):
87 | DeclarativeMeta.__init__(cls, classname, bases, dict_)
88 |
89 | try:
90 | mapper = class_mapper(cls)
91 | _history_mapper(mapper)
92 | except UnmappedClassError:
93 | pass
94 |
95 |
96 | def versioned_objects(iter):
97 | for obj in iter:
98 | if hasattr(obj, '__history_mapper__'):
99 | yield obj
100 |
101 | def create_version(obj, session, deleted = False):
102 | obj_mapper = object_mapper(obj)
103 | history_mapper = obj.__history_mapper__
104 | history_cls = history_mapper.class_
105 |
106 | obj_state = attributes.instance_state(obj)
107 |
108 | attr = {}
109 |
110 | obj_changed = False
111 |
112 | for om, hm in zip(obj_mapper.iterate_to_root(), history_mapper.iterate_to_root()):
113 | if hm.single:
114 | continue
115 |
116 | for hist_col in hm.local_table.c:
117 | if hist_col.key == 'version':
118 | continue
119 |
120 | obj_col = om.local_table.c[hist_col.key]
121 |
122 | # get the value of the
123 | # attribute based on the MapperProperty related to the
124 | # mapped column. this will allow usage of MapperProperties
125 | # that have a different keyname than that of the mapped column.
126 | try:
127 | prop = obj_mapper.get_property_by_column(obj_col)
128 | except UnmappedColumnError:
129 | # in the case of single table inheritance, there may be
130 | # columns on the mapped table intended for the subclass only.
131 | # the "unmapped" status of the subclass column on the
132 | # base class is a feature of the declarative module as of sqla 0.5.2.
133 | continue
134 |
135 | # expired object attributes and also deferred cols might not be in the
136 | # dict. force it to load no matter what by using getattr().
137 | if prop.key not in obj_state.dict:
138 | getattr(obj, prop.key)
139 |
140 | a, u, d = attributes.get_history(obj, prop.key)
141 |
142 | if d:
143 | attr[hist_col.key] = d[0]
144 | obj_changed = True
145 | elif u:
146 | attr[hist_col.key] = u[0]
147 | else:
148 | # if the attribute had no value.
149 | attr[hist_col.key] = a[0]
150 | obj_changed = True
151 |
152 | if not obj_changed:
153 | # not changed, but we have relationships. OK
154 | # check those too
155 | for prop in obj_mapper.iterate_properties:
156 | if isinstance(prop, RelationshipProperty) and \
157 | attributes.get_history(obj, prop.key).has_changes():
158 | obj_changed = True
159 | break
160 |
161 | if not obj_changed and not deleted:
162 | return
163 |
164 | attr['version'] = obj.version
165 | hist = history_cls()
166 | for key, value in attr.iteritems():
167 | setattr(hist, key, value)
168 | session.add(hist)
169 | obj.version += 1
170 |
171 | class VersionedListener(SessionExtension):
172 | def before_flush(self, session, flush_context, instances):
173 | for obj in versioned_objects(session.dirty):
174 | create_version(obj, session)
175 | for obj in versioned_objects(session.deleted):
176 | create_version(obj, session, deleted = True)
177 |
--------------------------------------------------------------------------------
/OSMTM/resources.py:
--------------------------------------------------------------------------------
1 | from pyramid.httpexceptions import HTTPFound
2 | from pyramid.url import route_url
3 | from pyramid.security import Allow, Deny, Everyone
4 | from models import Job, User, RootFactory, DBSession
5 |
6 |
7 | class JobFactory(RootFactory):
8 | def __init__(self, request):
9 | session = DBSession()
10 | job_id = request.matchdict['job']
11 | job = session.query(Job).get(job_id)
12 | if job is not None and job.is_private:
13 | acl = [
14 | (Allow, 'job:'+job_id, 'job'),
15 | (Allow, 'group:admin', 'job'),
16 | (Deny, Everyone, 'job'),
17 | ]
18 | self.__acl__ = acl + list(self.__acl__)
19 |
--------------------------------------------------------------------------------
/OSMTM/static/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/OSMTM/static/css/map.less:
--------------------------------------------------------------------------------
1 | .olTile {
2 | /*margin: 4px;*/
3 | border: 1px solid grey;
4 | }
5 | .olTile div {
6 | height: 100%;
7 | width: 100%;
8 | position: absolute;
9 | }
10 | .olTile div.back {
11 | opacity: 0.4;
12 | }
13 | .hasTags div.back {
14 | background-color: grey;
15 | }
16 | .hover div.back {
17 | background-color: grey;
18 | opacity: 0.6;
19 | }
20 | .olTileImage {
21 | /* override bootstrap */
22 | max-width: none;
23 | }u
24 | lTile {
25 | /*margin: 4px;*/
26 | border: 1px solid grey;
27 | }
28 | .olTile div {
29 | height: 100%;
30 | width: 100%;
31 | position: absolute;
32 | }
33 | .olTile div.back {
34 | opacity: 0.4;
35 | }
36 | .hasTags div.back {
37 | background-color: grey;
38 | }
39 | .hover div.back {
40 | background-color: grey;
41 | opacity: 0.6;
42 | }
43 | .olTileImage {
44 | /* override bootstrap */
45 | max-width: none;
46 | }
47 |
--------------------------------------------------------------------------------
/OSMTM/static/css/reset.css:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/ */
2 | /* v1.0 | 20080212 */
3 |
4 | html, body, div, span, applet, object, iframe,
5 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
6 | a, abbr, acronym, address, big, cite, code,
7 | del, dfn, em, font, img, ins, kbd, q, s, samp,
8 | small, strike, strong, sub, sup, tt, var,
9 | b, u, i, center,
10 | dl, dt, dd, ol, ul, li,
11 | fieldset, form, label, legend,
12 | table, caption, tbody, tfoot, thead, tr, th, td {
13 | margin: 0;
14 | padding: 0;
15 | border: 0;
16 | outline: 0;
17 | font-size: 100%;
18 | vertical-align: baseline;
19 | background: transparent;
20 | }
21 | body {
22 | line-height: 1;
23 | }
24 | ol, ul {
25 | list-style: none;
26 | }
27 | blockquote, q {
28 | quotes: none;
29 | }
30 | blockquote:before, blockquote:after,
31 | q:before, q:after {
32 | content: '';
33 | content: none;
34 | }
35 |
36 | /* remember to define focus styles! */
37 | :focus {
38 | outline: 0;
39 | }
40 |
41 | /* remember to highlight inserts somehow! */
42 | ins {
43 | text-decoration: none;
44 | }
45 | del {
46 | text-decoration: line-through;
47 | }
48 |
49 | /* tables still need 'cellspacing="0"' in the markup */
50 | table {
51 | border-collapse: collapse;
52 | border-spacing: 0;
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/OSMTM/static/img/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/ajax-loader.gif
--------------------------------------------------------------------------------
/OSMTM/static/img/blank.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/blank.gif
--------------------------------------------------------------------------------
/OSMTM/static/img/bm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/bm.jpg
--------------------------------------------------------------------------------
/OSMTM/static/img/closed_back.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/closed_back.gif
--------------------------------------------------------------------------------
/OSMTM/static/img/feedback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/feedback.png
--------------------------------------------------------------------------------
/OSMTM/static/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/OSMTM/static/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/OSMTM/static/img/lock.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/lock.gif
--------------------------------------------------------------------------------
/OSMTM/static/img/map_pin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/map_pin.png
--------------------------------------------------------------------------------
/OSMTM/static/img/marker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/marker.png
--------------------------------------------------------------------------------
/OSMTM/static/img/marker.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
20 |
27 |
34 |
40 |
44 |
48 |
54 |
58 |
59 |
66 |
70 |
71 |
78 |
85 |
93 |
97 |
98 |
99 |
118 |
121 |
122 |
124 |
125 |
127 | image/svg+xml
128 |
130 |
131 |
132 |
133 |
134 |
138 |
144 |
150 |
157 |
164 |
165 |
166 |
--------------------------------------------------------------------------------
/OSMTM/static/img/marker_grey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/marker_grey.png
--------------------------------------------------------------------------------
/OSMTM/static/img/marker_shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/marker_shadow.png
--------------------------------------------------------------------------------
/OSMTM/static/img/split.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/split.png
--------------------------------------------------------------------------------
/OSMTM/static/img/status-offline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/status-offline.png
--------------------------------------------------------------------------------
/OSMTM/static/img/status.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/status.png
--------------------------------------------------------------------------------
/OSMTM/static/img/tour/tour1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/tour/tour1.png
--------------------------------------------------------------------------------
/OSMTM/static/img/tour/tour3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/tour/tour3.png
--------------------------------------------------------------------------------
/OSMTM/static/img/tour/tour_auth_osm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/tour/tour_auth_osm.png
--------------------------------------------------------------------------------
/OSMTM/static/img/tour/tour_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/tour/tour_home.png
--------------------------------------------------------------------------------
/OSMTM/static/img/tour/tour_job_task_do.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/tour/tour_job_task_do.png
--------------------------------------------------------------------------------
/OSMTM/static/img/tour/tour_job_task_take.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/tour/tour_job_task_take.png
--------------------------------------------------------------------------------
/OSMTM/static/img/tour/tour_job_workflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/tour/tour_job_workflow.png
--------------------------------------------------------------------------------
/OSMTM/static/img/tour/tour_josm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/tour/tour_josm.png
--------------------------------------------------------------------------------
/OSMTM/static/img/world_map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/world_map.png
--------------------------------------------------------------------------------
/OSMTM/static/img/zoom-panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/img/zoom-panel.png
--------------------------------------------------------------------------------
/OSMTM/static/js/home.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | $('.delete')
3 | .click(function() {
4 | if (!confirm("Are you sure you want to delete this job?")) {
5 | return false;
6 | }
7 | });
8 |
9 | ko.applyBindings(new JobViewModel(jobs));
10 | });
11 | /**
12 | * Knockout js model
13 | */
14 | function JobViewModel(initialJobs) {
15 | // Data
16 | var self = this;
17 | self.jobs = ko.observableArray();
18 | self.filter = ko.observable('featured');
19 | self.searchValue = ko.observable();
20 |
21 | function changeHash() {
22 | var search = this.searchValue() ?
23 | '/' + this.searchValue() : '';
24 | location.hash = this.filter() + search;
25 | }
26 | self.searchValue.subscribe(changeHash, this);
27 | self.filter.subscribe(changeHash, this);
28 | self.jobs(initialJobs);
29 |
30 | this.filterByFeatured = function() {
31 | var jobs = this.jobs();
32 | return ko.utils.arrayFirst(jobs, function(job) {
33 | return job.featured === true;
34 | });
35 | }.bind(this);
36 |
37 | this.showFeatured = function() {
38 | this.filter('featured');
39 | }.bind(this);
40 | this.showMine = function() {
41 | this.filter('mine');
42 | }.bind(this);
43 |
44 | this.search = function() {
45 | var jobs = [].concat(initialJobs);
46 | this.jobs(jobs);
47 | var self = this;
48 | var searchVal = this.searchValue() && this.searchValue().toLowerCase();
49 | var filter = this.filter();
50 | this.jobs.remove(function(job) {
51 | var text = [job.title, job.description, job.short_description, job.tags.join(',')].join('');
52 | return (searchVal && text.toLowerCase().indexOf(searchVal) == -1) ||
53 | (filter == 'featured' && job.featured !== true) ||
54 | (filter == 'mine' && job.is_mine !== true);
55 | });
56 | }.bind(this);
57 |
58 | this.clearFilter = function() {
59 | this.filter('all');
60 | }.bind(this);
61 |
62 | // Client-side routes
63 | Sammy(function() {
64 | this.get('/', function() {
65 | self.filter('featured');
66 | self.search();
67 | });
68 | this.get('#:filter', function() {
69 | self.filter(this.params.filter);
70 | self.search();
71 | });
72 | this.get('#:filter/:search', function() {
73 | self.filter(this.params.filter);
74 | self.searchValue(this.params.search);
75 | self.search();
76 | });
77 | }).run();
78 | }
79 |
--------------------------------------------------------------------------------
/OSMTM/static/js/job.edit.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | var converter = new Showdown.converter();
3 |
4 | var short_description = $('#id_short_description'),
5 | short_description_preview = $('#short_description_preview');
6 | short_description.keyup(function() {
7 | var html = converter.makeHtml(short_description.val());
8 | short_description_preview.html(html);
9 | }).trigger('keyup');
10 |
11 | var description = $('#id_description'),
12 | description_preview = $('#description_preview');
13 | description.keyup(function() {
14 | var html = converter.makeHtml(description.val());
15 | description_preview.html(html);
16 | }).trigger('keyup');
17 |
18 | var workflow = $('#id_workflow'),
19 | workflow_preview = $('#workflow_preview');
20 | workflow.keyup(function() {
21 | var html = converter.makeHtml(workflow.val());
22 | workflow_preview.html(html);
23 | }).trigger('keyup');
24 | });
25 |
--------------------------------------------------------------------------------
/OSMTM/static/js/job.new.js:
--------------------------------------------------------------------------------
1 | var map = null,
2 | vectorLayer = null,
3 | tiles = null;
4 |
5 | function updateSubmitBtnStatus() {
6 | var disabled = $('#id_title').val() === '' ||
7 | $('#geometry').val() === '';
8 | $('#id_submit')[0].disabled = disabled;
9 | }
10 |
11 | function resetMap () {
12 | $('#geometry').val('');
13 | updateSubmitBtnStatus();
14 | $('#map').show();
15 | map && map.destroy();
16 | map = new OpenLayers.Map('map', {
17 | controls: [],
18 | theme: null
19 | });
20 | var osm = new OpenLayers.Layer.OSM();
21 | map.addLayer(osm);
22 | }
23 |
24 | function activateDrawControl() {
25 | $('#relation_loading_msg').hide();
26 | vectorLayer = new OpenLayers.Layer.Vector("Job Area");
27 | map.addLayer(vectorLayer);
28 | var control = new OpenLayers.Control.DrawFeature(
29 | vectorLayer,
30 | OpenLayers.Handler.Polygon, {
31 | "callbacks": {
32 | point: function(feature) {
33 | vectorLayer.destroyFeatures();
34 | }
35 | }
36 | });
37 | vectorLayer.events.on({
38 | featureadded: function(obj) {
39 | var feature = obj.feature;
40 | var format = new OpenLayers.Format.WKT();
41 | $('#geometry').val(format.write(feature));
42 | adaptZoomLevel(vectorLayer.getDataExtent());
43 | updateSubmitBtnStatus();
44 | }
45 | });
46 | map.addControls([
47 | new OpenLayers.Control.ZoomPanel(),
48 | new OpenLayers.Control.Navigation(),
49 | control
50 | ]);
51 | control.activate();
52 | if (!map.getCenter()) {
53 | map.zoomToMaxExtent();
54 | }
55 | }
56 |
57 | // set the zoom level to the more appropriate value
58 | function adaptZoomLevel(bounds) {
59 | var zoom = $('#id_zoom').val(),
60 | res = map.getResolutionForZoom(zoom),
61 | // the size of a tile in meters for the given zoom
62 | tileSize = res * 256,
63 | // the number of tile in a row
64 | nbByRow = Math.abs(bounds.right - bounds.left) / tileSize,
65 | nbByCol = Math.abs(bounds.top - bounds.bottom) / tileSize;
66 | $('#zoom_level_info').show();
67 | $('#nb_tiles').html(Math.round(nbByRow * nbByCol));
68 | }
69 |
70 | $('input[name=relation_type]')
71 | .change(function() {
72 | resetMap();
73 | if ($(this).val() == "relation") {
74 | $('#id_relation').attr('disabled', false);
75 | $('#id_relation').val('');
76 | } else {
77 | $('#id_relation').attr('disabled', true);
78 | activateDrawControl();
79 | }
80 | });
81 |
82 | $('#id_title').focus();
83 |
84 | $('#id_relation')
85 | .change(function() {
86 | $('#geometry').val('');
87 | if ($("input[name=relation_type]").val() == "relation") {
88 | $('#relation_loading_msg').show();
89 | var url = "http://www.openstreetmap.org/api/0.6/relation/" + this.value + '/full';
90 | vectorLayer = new OpenLayers.Layer.Vector("Objects", {
91 | protocol: new OpenLayers.Protocol.HTTP({
92 | url: url,
93 | format: new OpenLayers.Format.GeoJSON()
94 | }),
95 | strategies: [new OpenLayers.Strategy.Fixed()],
96 | style: {
97 | strokeColor: "blue",
98 | strokeWidth: 3,
99 | strokeOpacity: 0.5,
100 | fillOpacity: 0.2,
101 | fillColor: "lightblue",
102 | pointRadius: 6
103 | },
104 | projection: new OpenLayers.Projection("EPSG:4326"),
105 | displayInLayerSwitcher: false
106 | });
107 |
108 | vectorLayer.events.register("loadend", vectorLayer, function() {
109 | $('#relation_loading_msg').hide();
110 | map.zoomToExtent(vectorLayer.getDataExtent());
111 | var format = new OpenLayers.Format.WKT();
112 | $('#geometry').val(format.write(vectorLayer.features[0]));
113 | adaptZoomLevel(vectorLayer.getDataExtent());
114 | updateSubmitBtnStatus();
115 | });
116 |
117 | map.addLayer(vectorLayer);
118 | }
119 | });
120 |
121 | $('#id_zoom')
122 | .change(function() {
123 | adaptZoomLevel(vectorLayer.getDataExtent());
124 | });
125 |
126 | $('#id_title')
127 | .change(function() {
128 | updateSubmitBtnStatus();
129 | });
130 |
131 | $(document).ready(function() {
132 |
133 | var converter = new Showdown.converter(),
134 | to_convert = ['#id_short_description', '#id_description', '#id_workflow'];
135 |
136 | $(to_convert).each(function(i, sel){
137 | var textarea = $(sel),
138 | preview = $('
').appendTo(sel+'_preview');
139 |
140 | textarea.keyup(function() {
141 | var html = converter.makeHtml(textarea.val());
142 | preview.html(html);
143 | }).trigger('keyup');
144 | });
145 |
146 | resetMap();
147 | });
148 |
--------------------------------------------------------------------------------
/OSMTM/static/js/main.js:
--------------------------------------------------------------------------------
1 | $().ready(function() {
2 | $('#flash').fadeIn().delay(2000).fadeOut(400);
3 | $("a[rel=popover]")
4 | .popover({
5 | offset: 10,
6 | html: true
7 | })
8 | .click(function(e) {
9 | e.preventDefault();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/OSMTM/static/js/ol_osmtm.cfg:
--------------------------------------------------------------------------------
1 | # This file includes a small subset of OpenLayers code, designed to be
2 | # integrated into another application. It includes only the Layer types
3 | # neccesary to create tiled or untiled WMS, and does not include any Controls.
4 | # This is the result of what was at the time called "Webmap.js" at the FOSS4G
5 | # Web Mapping BOF.
6 |
7 | [first]
8 |
9 | [last]
10 |
11 | [include]
12 | OpenLayers/Map.js
13 | OpenLayers/Layer/OSM.js
14 | OpenLayers/Layer/Image.js
15 | OpenLayers/Layer/SphericalMercator.js
16 | OpenLayers/Renderer/SVG.js
17 | OpenLayers/Renderer/Canvas.js
18 | OpenLayers/Layer/Vector.js
19 | OpenLayers/Format/GeoJSON.js
20 | OpenLayers/Format/WKT.js
21 | OpenLayers/Geometry/Polygon.js
22 | OpenLayers/Control/SelectFeature.js
23 | OpenLayers/Control/Navigation.js
24 | OpenLayers/Control/ZoomPanel.js
25 | OpenLayers/Control/Attribution.js
26 | OpenLayers/Protocol/HTTP.js
27 | OpenLayers/Strategy/Fixed.js
28 | OpenLayers/Control/DrawFeature.js
29 | OpenLayers/Handler/Polygon.js
30 |
31 | [exclude]
32 |
33 |
34 |
--------------------------------------------------------------------------------
/OSMTM/static/js/task.js:
--------------------------------------------------------------------------------
1 | var roundd = function(input, decimals) {
2 | var p = Math.pow(10, decimals);
3 | return Math.round(input*p)/p;
4 | };
5 | var getLink = function(options) {
6 | if (options.protocol === 'lbrt') {
7 | var bounds = options.bounds;
8 | return options.base + OpenLayers.Util.getParameterString({
9 | left: roundd(bounds.left,5),
10 | bottom: roundd(bounds.bottom,5),
11 | right: roundd(bounds.right,5),
12 | top: roundd(bounds.top,5)
13 | });
14 | } else if (options.protocol === 'llz') {
15 | var c = options.bounds.getCenterLonLat();
16 | return options.base + OpenLayers.Util.getParameterString({
17 | lon: roundd(c.lon,5),
18 | lat: roundd(c.lat,5),
19 | zoom: options.zoom || 15
20 | });
21 | } else if (options.protocol === 'id') {
22 | var c = options.bounds.getCenterLonLat();
23 | return options.base + '#map=' + [options.zoom, c.lat, c.lon].join('/');
24 | }
25 | };
26 | var exportOpen = function(evt) {
27 |
28 | // if the clicked link has 'disabled' class stop event processing
29 | if ($(evt.target).hasClass('disabled')) {
30 | return false;
31 | };
32 |
33 | var url,
34 | format = new OpenLayers.Format.GeoJSON(),
35 | f = format.read(current_tile)[0],
36 | bounds = f.geometry.getBounds();
37 |
38 | bounds.transform(
39 | new OpenLayers.Projection("EPSG:900913"),
40 | new OpenLayers.Projection("EPSG:4326")
41 | );
42 |
43 | switch (this.id) {
44 | case "josm":
45 | url = getLink({
46 | base: 'http://127.0.0.1:8111/load_and_zoom?',
47 | bounds: bounds,
48 | protocol: 'lbrt'
49 | });
50 | $.ajax({
51 | url: url,
52 | complete: function(t) {
53 | if (t.status != 200) {
54 | alert("JOSM remote control did not respond. Do you have JOSM running and configured to be controlled remotely?");
55 | }
56 | }
57 | });
58 | break;
59 | case "potlatch2":
60 | url = getLink({
61 | base: 'http://www.openstreetmap.org/edit?editor=potlatch2&',
62 | bounds: bounds,
63 | zoom: zoom,
64 | protocol: 'llz'
65 | });
66 | window.open(url);
67 | break;
68 | case "wp":
69 | url = getLink({
70 | base: 'http://walking-papers.org/?',
71 | bounds: bounds,
72 | protocol: 'llz'
73 | });
74 | window.open(url);
75 | break;
76 | case "id":
77 | url = getLink({
78 | base: 'http://www.openstreetmap.org/edit?editor=id',
79 | bounds: bounds,
80 | zoom: zoom,
81 | protocol: 'id'
82 | });
83 | url += "&gpx=" + gpx_url;
84 | if (typeof imagery_url != "undefined") {
85 | // url is supposed to look like tms[22]:http://hiu...
86 | u = imagery_url.substring(imagery_url.indexOf('http'));
87 | u = u.replace('zoom', 'z');
88 | url += "&background=custom:" + u;
89 | }
90 | window.open(url);
91 | break;
92 | default:
93 | break;
94 | }
95 | };
96 | $('#export a').live('click', exportOpen);
97 |
--------------------------------------------------------------------------------
/OSMTM/static/middlebg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/middlebg.png
--------------------------------------------------------------------------------
/OSMTM/static/red-header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/red-header.png
--------------------------------------------------------------------------------
/OSMTM/static/theme/default/style.css:
--------------------------------------------------------------------------------
1 | div.olMap {
2 | z-index: 0;
3 | padding: 0 !important;
4 | margin: 0 !important;
5 | cursor: default;
6 | }
7 |
8 | div.olMapViewport {
9 | text-align: left;
10 | }
11 |
12 | div.olLayerDiv {
13 | -moz-user-select: none;
14 | -khtml-user-select: none;
15 | }
16 |
17 | .olLayerGoogleCopyright {
18 | left: 2px;
19 | bottom: 2px;
20 | }
21 | .olLayerGoogleV3.olLayerGoogleCopyright {
22 | right: auto !important;
23 | }
24 | .olLayerGooglePoweredBy {
25 | left: 2px;
26 | bottom: 15px;
27 | }
28 | .olLayerGoogleV3.olLayerGooglePoweredBy {
29 | bottom: 15px !important;
30 | }
31 | .olControlAttribution {
32 | font-size: smaller;
33 | right: 3px;
34 | bottom: 4.5em;
35 | position: absolute;
36 | display: block;
37 | }
38 | .olControlScale {
39 | right: 3px;
40 | bottom: 3em;
41 | display: block;
42 | position: absolute;
43 | font-size: smaller;
44 | }
45 | .olControlScaleLine {
46 | display: block;
47 | position: absolute;
48 | left: 10px;
49 | bottom: 15px;
50 | font-size: xx-small;
51 | }
52 | .olControlScaleLineBottom {
53 | border: solid 2px black;
54 | border-bottom: none;
55 | margin-top:-2px;
56 | text-align: center;
57 | }
58 | .olControlScaleLineTop {
59 | border: solid 2px black;
60 | border-top: none;
61 | text-align: center;
62 | }
63 |
64 | .olControlPermalink {
65 | right: 3px;
66 | bottom: 1.5em;
67 | display: block;
68 | position: absolute;
69 | font-size: smaller;
70 | }
71 |
72 | div.olControlMousePosition {
73 | bottom: 0em;
74 | right: 3px;
75 | display: block;
76 | position: absolute;
77 | font-family: Arial;
78 | font-size: smaller;
79 | }
80 |
81 | .olControlOverviewMapContainer {
82 | position: absolute;
83 | bottom: 0;
84 | right: 0;
85 | }
86 |
87 | .olControlOverviewMapElement {
88 | padding: 10px 18px 10px 10px;
89 | background-color: #00008B;
90 | -moz-border-radius: 1em 0 0 0;
91 | }
92 |
93 | .olControlOverviewMapMinimizeButton {
94 | right: 0;
95 | bottom: 80px;
96 | cursor: pointer;
97 | }
98 |
99 | .olControlOverviewMapMaximizeButton {
100 | right: 0;
101 | bottom: 80px;
102 | cursor: pointer;
103 | }
104 |
105 | .olControlOverviewMapExtentRectangle {
106 | overflow: hidden;
107 | background-image: url("img/blank.gif");
108 | cursor: move;
109 | border: 2px dotted red;
110 | }
111 | .olControlOverviewMapRectReplacement {
112 | overflow: hidden;
113 | cursor: move;
114 | background-image: url("img/overview_replacement.gif");
115 | background-repeat: no-repeat;
116 | background-position: center;
117 | }
118 |
119 | .olLayerGeoRSSDescription {
120 | float:left;
121 | width:100%;
122 | overflow:auto;
123 | font-size:1.0em;
124 | }
125 | .olLayerGeoRSSClose {
126 | float:right;
127 | color:gray;
128 | font-size:1.2em;
129 | margin-right:6px;
130 | font-family:sans-serif;
131 | }
132 | .olLayerGeoRSSTitle {
133 | float:left;font-size:1.2em;
134 | }
135 |
136 | .olPopupContent {
137 | padding:5px;
138 | overflow: auto;
139 | }
140 |
141 | .olControlNavigationHistory {
142 | background-image: url("img/navigation_history.png");
143 | background-repeat: no-repeat;
144 | width: 24px;
145 | height: 24px;
146 |
147 | }
148 | .olControlNavigationHistoryPreviousItemActive {
149 | background-position: 0 0;
150 | }
151 | .olControlNavigationHistoryPreviousItemInactive {
152 | background-position: 0 -24px;
153 | }
154 | .olControlNavigationHistoryNextItemActive {
155 | background-position: -24px 0;
156 | }
157 | .olControlNavigationHistoryNextItemInactive {
158 | background-position: -24px -24px;
159 | }
160 |
161 | div.olControlSaveFeaturesItemActive {
162 | background-image: url(img/save_features_on.png);
163 | background-repeat: no-repeat;
164 | background-position: 0 1px;
165 | }
166 | div.olControlSaveFeaturesItemInactive {
167 | background-image: url(img/save_features_off.png);
168 | background-repeat: no-repeat;
169 | background-position: 0 1px;
170 | }
171 |
172 | .olHandlerBoxZoomBox {
173 | border: 2px solid red;
174 | position: absolute;
175 | background-color: white;
176 | opacity: 0.50;
177 | font-size: 1px;
178 | filter: alpha(opacity=50);
179 | }
180 | .olHandlerBoxSelectFeature {
181 | border: 2px solid blue;
182 | position: absolute;
183 | background-color: white;
184 | opacity: 0.50;
185 | font-size: 1px;
186 | filter: alpha(opacity=50);
187 | }
188 |
189 | .olControlPanPanel {
190 | top: 10px;
191 | left: 5px;
192 | }
193 |
194 | .olControlPanPanel div {
195 | background-image: url(img/pan-panel.png);
196 | height: 18px;
197 | width: 18px;
198 | cursor: pointer;
199 | position: absolute;
200 | }
201 |
202 | .olControlPanPanel .olControlPanNorthItemInactive {
203 | top: 0;
204 | left: 9px;
205 | background-position: 0 0;
206 | }
207 | .olControlPanPanel .olControlPanSouthItemInactive {
208 | top: 36px;
209 | left: 9px;
210 | background-position: 18px 0;
211 | }
212 | .olControlPanPanel .olControlPanWestItemInactive {
213 | position: absolute;
214 | top: 18px;
215 | left: 0;
216 | background-position: 0 18px;
217 | }
218 | .olControlPanPanel .olControlPanEastItemInactive {
219 | top: 18px;
220 | left: 18px;
221 | background-position: 18px 18px;
222 | }
223 |
224 | .olControlZoomPanel {
225 | top: 71px;
226 | left: 14px;
227 | }
228 |
229 | .olControlZoomPanel div {
230 | background-image: url(img/zoom-panel.png);
231 | position: absolute;
232 | height: 18px;
233 | width: 18px;
234 | cursor: pointer;
235 | }
236 |
237 | .olControlZoomPanel .olControlZoomInItemInactive {
238 | top: 0;
239 | left: 0;
240 | background-position: 0 0;
241 | }
242 |
243 | .olControlZoomPanel .olControlZoomToMaxExtentItemInactive {
244 | top: 18px;
245 | left: 0;
246 | background-position: 0 -18px;
247 | }
248 |
249 | .olControlZoomPanel .olControlZoomOutItemInactive {
250 | top: 36px;
251 | left: 0;
252 | background-position: 0 18px;
253 | }
254 |
255 | /*
256 | * When a potential text is bigger than the image it move the image
257 | * with some headers (closes #3154)
258 | */
259 | .olControlPanZoomBar div {
260 | font-size: 1px;
261 | }
262 |
263 | .olPopupCloseBox {
264 | background: url("img/close.gif") no-repeat;
265 | cursor: pointer;
266 | }
267 |
268 | .olFramedCloudPopupContent {
269 | padding: 5px;
270 | overflow: auto;
271 | }
272 |
273 | .olControlNoSelect {
274 | -moz-user-select: none;
275 | -khtml-user-select: none;
276 | }
277 |
278 | .olImageLoadError {
279 | background-color: white;
280 | opacity: 0.5;
281 | filter: alpha(opacity=50); /* IE */
282 | }
283 |
284 | /**
285 | * Cursor styles
286 | */
287 |
288 | .olCursorWait {
289 | cursor: wait;
290 | }
291 | .olDragDown {
292 | cursor: move;
293 | }
294 | .olDrawBox {
295 | cursor: crosshair;
296 | }
297 | .olControlDragFeatureOver {
298 | cursor: move;
299 | }
300 | .olControlDragFeatureActive.olControlDragFeatureOver.olDragDown {
301 | cursor: -moz-grabbing;
302 | }
303 |
304 | /**
305 | * Layer switcher
306 | */
307 | .olControlLayerSwitcher {
308 | position: absolute;
309 | top: 25px;
310 | right: 0;
311 | width: 20em;
312 | font-family: sans-serif;
313 | font-weight: bold;
314 | margin-top: 3px;
315 | margin-left: 3px;
316 | margin-bottom: 3px;
317 | font-size: smaller;
318 | color: white;
319 | background-color: transparent;
320 | }
321 |
322 | .olControlLayerSwitcher .layersDiv {
323 | padding-top: 5px;
324 | padding-left: 10px;
325 | padding-bottom: 5px;
326 | padding-right: 75px;
327 | background-color: darkblue;
328 | width: 100%;
329 | height: 100%;
330 | }
331 |
332 | .olControlLayerSwitcher .layersDiv .baseLbl,
333 | .olControlLayerSwitcher .layersDiv .dataLbl {
334 | margin-top: 3px;
335 | margin-left: 3px;
336 | margin-bottom: 3px;
337 | }
338 |
339 | .olControlLayerSwitcher .layersDiv .baseLayersDiv,
340 | .olControlLayerSwitcher .layersDiv .dataLayersDiv {
341 | padding-left: 10px;
342 | }
343 |
344 | .olControlLayerSwitcher .maximizeDiv,
345 | .olControlLayerSwitcher .minimizeDiv {
346 | top: 5px;
347 | right: 0;
348 | cursor: pointer;
349 | }
350 |
351 | .olBingAttribution {
352 | color: #DDD;
353 | }
354 | .olBingAttribution.road {
355 | color: #333;
356 | }
357 |
358 | .olGoogleAttribution.hybrid, .olGoogleAttribution.satellite {
359 | color: #EEE;
360 | }
361 | .olGoogleAttribution {
362 | color: #333;
363 | }
364 | span.olGoogleAttribution a {
365 | color: #77C;
366 | }
367 | span.olGoogleAttribution.hybrid a, span.olGoogleAttribution.satellite a {
368 | color: #EEE;
369 | }
370 |
371 | /**
372 | * Editing and navigation icons.
373 | * (using the editing_tool_bar.png sprint image)
374 | */
375 | .olControlNavToolbar ,
376 | .olControlEditingToolbar {
377 | margin: 5px 5px 0 0;
378 | }
379 | .olControlNavToolbar div,
380 | .olControlEditingToolbar div {
381 | background-image: url("img/editing_tool_bar.png");
382 | background-repeat: no-repeat;
383 | margin: 0 0 5px 5px;
384 | width: 24px;
385 | height: 22px;
386 | cursor: pointer
387 | }
388 | /* positions */
389 | .olControlEditingToolbar {
390 | right: 0;
391 | top: 0;
392 | }
393 | .olControlNavToolbar {
394 | top: 295px;
395 | left: 9px;
396 | }
397 | /* layouts */
398 | .olControlEditingToolbar div {
399 | float: right;
400 | }
401 | /* individual controls */
402 | .olControlNavToolbar .olControlNavigationItemInactive,
403 | .olControlEditingToolbar .olControlNavigationItemInactive {
404 | background-position: -103px -1px;
405 | }
406 | .olControlNavToolbar .olControlNavigationItemActive ,
407 | .olControlEditingToolbar .olControlNavigationItemActive {
408 | background-position: -103px -24px;
409 | }
410 | .olControlNavToolbar .olControlZoomBoxItemInactive {
411 | background-position: -128px -1px;
412 | }
413 | .olControlNavToolbar .olControlZoomBoxItemActive {
414 | background-position: -128px -24px;
415 | }
416 | .olControlEditingToolbar .olControlDrawFeaturePointItemInactive {
417 | background-position: -77px -1px;
418 | }
419 | .olControlEditingToolbar .olControlDrawFeaturePointItemActive {
420 | background-position: -77px -24px;
421 | }
422 | .olControlEditingToolbar .olControlDrawFeaturePathItemInactive {
423 | background-position: -51px -1px;
424 | }
425 | .olControlEditingToolbar .olControlDrawFeaturePathItemActive {
426 | background-position: -51px -24px;
427 | }
428 | .olControlEditingToolbar .olControlDrawFeaturePolygonItemInactive{
429 | background-position: -26px -1px;
430 | }
431 | .olControlEditingToolbar .olControlDrawFeaturePolygonItemActive {
432 | background-position: -26px -24px;
433 | }
434 |
--------------------------------------------------------------------------------
/OSMTM/static/thumb-up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/thumb-up.png
--------------------------------------------------------------------------------
/OSMTM/static/thumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/static/thumb.png
--------------------------------------------------------------------------------
/OSMTM/templates/about.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">home%def>
3 | <%def name="title()">About%def>
4 |
5 |
6 |
9 |
10 |
11 |
Goal Coordinate Efforts
12 |
OpenStreetMap has been shown to be an effective collection mechanism for infrastructure data. One thing that is lacking is the ability to coordinate workers surveying in the field or working remotely. The goal of the OpenStreetMap Tasking Tool is to make it easy for administrators to define collection areas of interest and collection workflows as well as allowing workers to easily determine what areas they should be working on.
13 |
14 |
15 |
Context
16 |
17 | The current application is the result of an initial work funded by AIFDR . It was imagined by the Humanitarian OpenStreetMap Team which contracted Camptocamp to initiate the work.
18 |
19 |
20 | Then, the project continued to evolve on its own with the help from several developers.
21 |
22 |
23 |
24 |
27 |
28 |
29 | The
code is available on Github.
30 |
31 |
32 |
33 |
34 |
Contributors
35 |
36 |
44 |
45 |
46 |
47 |
Softwares This application runs thanks to the following softwares
48 |
49 |
50 |
Client-side
51 |
52 | OpenLayers
53 | JQuery
54 | Twitter Bootstrap
55 | Less CSS
56 |
57 |
Images
58 |
61 |
62 |
63 |
Server-side
64 |
65 | Pyramid
66 | SQLite
67 | SQLAlchemy
68 | Shapely
69 | ImpOSM-parser
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/OSMTM/templates/admin.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">admin%def>
3 | <%def name="title()">Admin Page%def>
4 |
5 |
Users
6 |
Manage users
7 |
Licenses
8 |
Manage licenses
9 |
10 |
--------------------------------------------------------------------------------
/OSMTM/templates/base.mako:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | OSM Tasking Manager - ${self.title()}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
29 |
30 | <%
31 | from pyramid.security import authenticated_userid
32 | from OSMTM.models import DBSession, User
33 | username = authenticated_userid(request)
34 | if username is not None:
35 | user = DBSession().query(User).get(username)
36 | else:
37 | user = None
38 | %>
39 |
40 |
41 |
43 |
62 |
63 |
68 |
69 |
70 | % if request.session.peek_flash():
71 |
72 | <% flash = request.session.pop_flash() %>
73 | % for message in flash:
74 | ${message}
75 | % endfor
76 |
77 | % endif
78 |
79 | ${self.body()}
80 |
81 |
82 |
83 |
84 |
85 |
86 |
98 |
99 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/OSMTM/templates/forbidden.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">home%def>
3 | <%def name="title()">
4 | % if user is not None:
5 | Forbidden
6 | % else:
7 | Login
8 | % endif
9 | %def>
10 |
11 |
12 |
13 |
14 |
About The Tasking Manager
15 | Coordinate Efforts
16 | OpenStreetMap has been shown to be an effective collection mechanism for infrastructure data. One thing that is lacking is the ability to coordinate workers surveying in the field or working remotely. The goal of the OpenStreetMap Tasking Tool is to make it easy for administrators to define collection areas of interest and collection workflows as well as allowing workers to easily determine what areas they should be working on.
17 |
18 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/OSMTM/templates/home.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">home%def>
3 | <%def name="title()">HOT Task Server - Home Page%def>
4 |
5 |
6 |
19 | % if admin:
20 |
28 | % endif
29 |
30 |
31 |
32 |
33 | No job matches your search criteria
34 |
35 |
36 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | Created by
79 |
80 |
81 |
84 |
85 |
86 | % if user.is_admin():
87 |
88 |
89 | mark as unfeatured
91 |
92 |
93 | mark as featured
95 |
96 | |
97 |
98 | archive
100 |
101 |
102 | publish
104 |
105 | |
106 | edit
108 |
109 | % endif
110 |
111 |
112 |
113 |
114 |
115 | Updated ago
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
128 |
--------------------------------------------------------------------------------
/OSMTM/templates/imagery.mako:
--------------------------------------------------------------------------------
1 | <%
2 | license_accepted = job.license in user.accepted_licenses
3 | %>
4 | % if job.imagery is not None and job.imagery != 'None':
5 | Imagery
6 | % if license_accepted or not job.license:
7 | <%
8 | type = job.imagery.lower()[:3]
9 | %>
10 |
${job.imagery}
11 |
12 | % if job.imagery_offset_x or job.imagery_offset_y:
13 | Offset : Please beware that the image aligment needs to be modified by the given offset: ${job.imagery_offset_x}:${job.imagery_offset_y}
14 | % endif
15 | % endif
16 | % if job.license:
17 |
30 | % endif
31 | % endif
32 |
--------------------------------------------------------------------------------
/OSMTM/templates/job.edit.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">job_new%def>
3 | <%def name="title()">Edit Job%def>
4 |
5 |
150 |
151 |
--------------------------------------------------------------------------------
/OSMTM/templates/job.mako:
--------------------------------------------------------------------------------
1 | <%!
2 | import markdown
3 | %>
4 | <%inherit file="/base.mako"/>
5 | <%def name="id()">job%def>
6 | <%def name="title()">Job - ${job.title}%def>
7 |
8 |
23 | % if job.status == 0:
24 |
25 | Warning!
26 | This job has been archived. You're not supposed to work on it anymore.
27 |
28 | % endif
29 |
30 |
31 |
38 |
39 |
40 |
${markdown.markdown(job.description)|n}
41 |
42 |
43 |
${markdown.markdown(job.workflow)|n}
44 | % if job.imagery:
45 | <%include file="imagery.mako" />
46 | % endif
47 | % if job.josm_preset:
48 |
49 | Using JOSM? Don't hesitate to use the dedicated
preset .
50 |
51 | % endif
52 |
53 |
54 | % if tile is not None:
55 |
60 | % else:
61 | <%include file="/task.empty.mako" />
62 | % endif
63 |
64 |
73 |
76 |
77 |
78 |
85 |
86 |
87 |
88 |
104 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/OSMTM/templates/job.new.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">job_new%def>
3 | <%def name="title()">New Job%def>
4 |
5 |
6 |
New Job
7 |
8 |
9 |
Title
10 |
11 |
12 |
13 |
14 |
15 | Area of interest
16 |
17 |
18 |
19 |
Area
20 |
36 |
45 |
46 |
47 |
48 |
Zoom level
49 |
50 |
51 | 10
52 | 11
53 | 12
54 | 13
55 | 14
56 | 15
57 | 16
58 | 17
59 | 18
60 |
61 |
62 |
63 | Up to tiles will be created.
64 |
65 |
66 |
67 |
68 |
69 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
84 |
85 |
--------------------------------------------------------------------------------
/OSMTM/templates/job.tags.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">job_tags%def>
3 | <%def name="title()">Job tags - ${job.title}%def>
4 |
5 | <%
6 | import json
7 | tags = json.dumps([tag.tag for tag in all_tags])
8 | %>
9 |
10 |
27 |
--------------------------------------------------------------------------------
/OSMTM/templates/job.task_extra.mako:
--------------------------------------------------------------------------------
1 | <%
2 | content = job.task_extra
3 | content = content.replace('{x}', str(tile.x)) \
4 | .replace('{y}', str(tile.y)) \
5 | .replace('{z}', str(tile.zoom))
6 | %>
7 |
8 | Extra instructions
9 | ${content|n}
10 |
--------------------------------------------------------------------------------
/OSMTM/templates/job.users.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">job_users%def>
3 | <%def name="title()">Job whitelist - ${job.title}%def>
4 |
5 |
33 |
--------------------------------------------------------------------------------
/OSMTM/templates/license.edit.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">license_new%def>
3 | <%def name="title()">Edit Imagery License%def>
4 |
5 |
Edit License
6 |
7 |
8 |
Name
9 |
10 |
12 |
13 |
14 |
15 |
16 |
17 |
Description
18 |
19 | ${license.description}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
Plain Text
29 |
30 | ${license.plain_text}
33 |
34 |
35 |
36 |
37 |
41 |
42 |
43 |
51 |
--------------------------------------------------------------------------------
/OSMTM/templates/license.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">license%def>
3 | <%def name="title()">${license.name} License Acknowledgement%def>
4 |
23 |
--------------------------------------------------------------------------------
/OSMTM/templates/licenses.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">licenses%def>
3 | <%def name="title()">Admin - Licenses%def>
4 |
5 |
Licenses
6 |
7 |
8 |
9 | % for license in licenses:
10 | ${license.name}
11 | edit
12 |
13 | % endfor
14 |
15 |
16 |
17 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/OSMTM/templates/task.comments.mako:
--------------------------------------------------------------------------------
1 | <%
2 | steps = []
3 | prev_change = False
4 | for i, step in enumerate(history):
5 | # exclude last one
6 | if i == 0:
7 | continue
8 | # don't show unlocks which follow a change
9 | if prev_change:
10 | prev_change = False
11 | continue
12 | steps.append(step)
13 | prev_change = True if step.change else False
14 | if tile.version != 1 and not prev_change:
15 | steps.append(tile)
16 | steps.reverse()
17 | %>
18 |
19 | History
20 | % if len(steps) > 0:
21 | <%
22 | from OSMTM.utils import timesince
23 | %>
24 | % for i, step in enumerate(steps):
25 | <%
26 | first = "first" if i == 0 else ""
27 | last = "last" if i == len(steps) - 1 else ""
28 | %>
29 |
30 |
31 |
32 | % if step.checkout:
33 | Locked
34 | % elif step.change:
35 | % if step.checkin == 0:
36 | Invalidated
37 | % elif step.checkin == 2:
38 | Validated
39 | % elif step.checkin == 1:
40 | Marked as done
41 | % endif
42 | % else:
43 | Unlocked
44 | % endif
45 |
46 | % if step.change or step.checkout:
47 | by
48 | ${step.username}
49 |
50 | % endif
51 | % if step.comment is not None:
52 |
53 | ${step.comment}
54 |
55 | % endif
56 |
57 | <%
58 | time_ago = timesince(step.update)
59 | %>
60 | ${time_ago} ago
61 |
62 |
63 | % endfor
64 | <%
65 | minx, miny, maxx, maxy = tile.to_polygon(4326).bounds
66 | %>
67 | OSM changesets
68 | % else:
69 | Nothing has happen yet.
70 | % endif
71 |
--------------------------------------------------------------------------------
/OSMTM/templates/task.empty.mako:
--------------------------------------------------------------------------------
1 | % if error_msg is not UNDEFINED and error_msg is not None:
2 |
3 | ${error_msg}
4 |
5 | % endif
6 |
7 | Pick a task randomly
9 |
10 |
11 | Or choose one by clicking on the map.
12 |
13 |
14 |
15 | If you're an experienced mapper, you can also be given a task to validate .
16 |
20 | % if split_id is not UNDEFINED:
21 | <%
22 | from geojson import dumps
23 | tiles = dumps(new_tiles)
24 | %>
25 |
28 | % endif
29 |
--------------------------------------------------------------------------------
/OSMTM/templates/task.gpx.mako:
--------------------------------------------------------------------------------
1 |
2 | <%!
3 | import datetime
4 | %><%
5 | timestamp = datetime.datetime.now()
6 | timestamp = timestamp.isoformat()
7 | %>
8 |
9 |
10 |
11 | HOT Tasking Manager
12 |
13 | ${timestamp}
14 |
15 |
16 | Task tile for job ${job_id}
17 |
18 | % for point in polygon.exterior.coords:
19 |
20 | % endfor
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/OSMTM/templates/task.mako:
--------------------------------------------------------------------------------
1 |
2 | Loading
3 |
4 | <%!
5 | import markdown
6 | %>
7 | <%
8 | # allow editing only if a task is taken by a user or wating for validation
9 | if (tile.checkout and tile.user == user) or tile.checkin == 1:
10 | disabled = ''
11 | else:
12 | disabled = 'disabled'
13 | %>
14 | % if not tile:
15 | <%include file="/task.empty.mako" />
16 | % else:
17 |
33 |
34 | % if tile.username == user.username or tile.checkin >= 1:
35 |
36 | <%
37 | if tile.username == user.username:
38 | comment_label = 'Please add a comment'
39 | else:
40 | comment_label = 'Please write why you marked this tile as invalid so that the user may eventually correct his mistakes if any.'
41 | %>
42 |
43 | % if tile.username == user.username:
44 |
45 | min. left
48 | You locked this task.
49 | Unlock it
50 |
51 |
52 | Mark task as done
53 |
54 | % elif tile.checkin >= 1:
55 |
56 |
57 | Invalidate
58 |
59 | % if tile.checkin == 1:
60 |
61 |
62 | Validate
63 |
64 | % endif
65 | Clear selection
66 | % endif
67 |
81 |
82 | % else:
83 | % if current_task is not None:
84 |
85 |
86 | You already have a task locked.
87 |
88 | % endif
89 | % if tile.checkout != True and tile.checkin != 2 and current_task is None:
90 | <%
91 | disabled = ""
92 | tooltip = ""
93 | if current_task is not None:
94 | disabled = "disabled"
95 | tooltip = "You cannot lock more than one task"
96 | else:
97 | tooltip = "Lock this task to notify others that you are currently working on it."
98 | %>
99 |
100 | Yes, I want to work on this task
103 |
104 | % elif tile.username is not None:
105 |
106 | Already locked by ${tile.username} .
107 |
108 | % endif
109 | % if tile.checkin == 0 and (tile.zoom - job.zoom) < 1 and tile.username is None:
110 |
111 | Split!
112 | % endif
113 | Clear selection
114 |
115 | % endif
116 | % if job.task_extra is not None:
117 | <%include file="job.task_extra.mako" />
118 | % endif
119 | <%include file="task.comments.mako" />
120 |
121 |
139 | % endif
140 |
143 |
--------------------------------------------------------------------------------
/OSMTM/templates/task.osm.mako:
--------------------------------------------------------------------------------
1 |
2 | <%
3 | id = -2
4 | %>
5 | % for point in polygon.exterior.coords:
6 |
7 | <% id = id -1 %>
8 | % endfor
9 |
10 | % for i in [-2, -3, -4, -5, -6]:
11 |
12 | % endfor
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/OSMTM/templates/tour.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">tour%def>
3 | <%def name="title()">Tour%def>
4 |
6 |
8 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Login Page
20 |
Log in.
21 |
22 |
23 |
24 |
25 |
26 |
OpenStreetMap Authentication
27 |
Login to OpenStreetMap and authorize access to your account.
28 |
29 |
30 |
31 |
32 |
33 |
Jobs List
34 |
Choose the job you want to work on.
35 |
36 |
37 |
38 |
39 |
40 |
Job's Workflow
41 |
Pay attention to what the job is about.
42 |
43 |
44 |
45 |
46 |
47 |
Take a Task (1)
48 |
Take a task either by letting the service choose for you or clicking on the map.
49 |
50 |
51 |
52 |
53 |
54 |
Take a Task (2)
55 |
Choose your favorite editor.
56 |
57 |
58 |
59 |
60 |
61 |
Do The Mapping
62 |
Digitize the features as described in the workflow using JOSM or potlatch.
63 |
64 |
65 |
66 |
67 |
68 |
Mark task as done
69 |
Go back to the tasking manager page and mark the task as done and take a new one.
70 |
71 |
72 |
73 |
‹
74 |
›
75 |
76 |
77 |
78 |
79 |
85 |
--------------------------------------------------------------------------------
/OSMTM/templates/user.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">user%def>
3 | <%def name="title()">User Profile%def>
4 |
5 |
Profile for ${user.username}
6 |
7 |
8 |
9 | % if user.is_admin():
10 | This user is an administrator.
11 | % else:
12 | This user has no admin privileges.
13 | % endif
14 |
15 | % if admin:
16 | Edit privileges
17 | % endif
18 |
19 |
20 |
26 |
27 |
Jobs
28 | % if jobs:
29 |
30 | % for job_info in jobs:
31 | ${job_info["job"].title} (${job_info["count"]} tiles)
32 |
33 | % endfor
34 |
35 | % else:
36 | User hasn't contribute yet.
37 | % endif
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/OSMTM/templates/user_edit.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">user%def>
3 | <%def name="title()">User Profile%def>
4 |
5 | % if admin:
6 |
Profile for ${user.username}
7 |
8 | % else:
9 | Profile
10 |
11 | % endif
12 |
13 |
User role
14 |
15 | % if admin or user.is_admin():
16 |
17 |
22 | Admin
23 |
24 | % endif
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/OSMTM/templates/users.mako:
--------------------------------------------------------------------------------
1 | <%inherit file="/base.mako"/>
2 | <%def name="id()">users%def>
3 | <%def name="title()">Registered Users%def>
4 |
5 |
Registered Users
6 |
7 |
8 |
9 | % for user in users:
10 | ${user.username}
11 | % if admin:
12 | edit
13 | % endif
14 |
15 | % endfor
16 |
17 |
18 | % if admin:
19 |
28 | % endif
29 |
30 |
31 |
--------------------------------------------------------------------------------
/OSMTM/tests.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from pyramid.config import Configurator
3 | from pyramid import testing
4 |
5 | def _initTestingDB():
6 | from sqlalchemy import create_engine
7 | from OSMTM.models import initialize_sql, populate
8 | session = initialize_sql(create_engine('sqlite:///:memory:'))
9 | populate()
10 | return session
11 |
12 | def _registerRoutes(config):
13 | config.add_route('job', 'job/{job}')
14 |
15 | class TileModelTests(unittest.TestCase):
16 |
17 | def setUp(self):
18 | self.config = testing.setUp()
19 | _initTestingDB()
20 |
21 | def tearDown(self):
22 | testing.tearDown()
23 |
24 | def _getTargetClass(self):
25 | from OSMTM.models import Tile
26 | return Tile
27 |
28 | def _makeOne(self, x=1, y=2):
29 | return self._getTargetClass()(x, y)
30 |
31 | def test_constructor(self):
32 | instance = self._makeOne()
33 | self.assertEqual(instance.x, 1)
34 | self.assertEqual(instance.y, 2)
35 | self.assertEqual(instance.checkin, 0)
36 |
37 | class JobModelTests(unittest.TestCase):
38 |
39 | def setUp(self):
40 | self.config = testing.setUp()
41 | _initTestingDB()
42 |
43 | def tearDown(self):
44 | testing.tearDown()
45 |
46 | def _getTargetClass(self):
47 | from OSMTM.models import Job
48 | return Job
49 |
50 | def _makeOne(self, title='SomeTitle',
51 | short_description='a short description', description='some description',
52 | workflow='some workflow', geometry='POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', zoom=1):
53 | return self._getTargetClass()(title, short_description, description, workflow, geometry, zoom)
54 |
55 | def test_constructor(self):
56 | instance = self._makeOne()
57 | self.assertEqual(instance.title, 'SomeTitle')
58 | self.assertEqual(instance.status, 1)
59 | self.assertEqual(instance.description, 'some description')
60 | self.assertEqual(instance.geometry, 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))')
61 | self.assertEqual(instance.workflow, 'some workflow')
62 | self.assertEqual(instance.zoom, 1)
63 |
64 | class UserModelTests(unittest.TestCase):
65 |
66 | def setUp(self):
67 | self.config = testing.setUp()
68 | _initTestingDB()
69 |
70 | def tearDown(self):
71 | testing.tearDown()
72 |
73 | def _getTargetClass(self):
74 | from OSMTM.models import User
75 | return User
76 |
77 | def _makeOne(self, username=u'bar'):
78 | user = self._getTargetClass()(username)
79 | return user
80 |
81 | def test_constructor(self):
82 | instance = self._makeOne()
83 | self.assertEqual(instance.username, u'bar')
84 |
85 | class TestHome(unittest.TestCase):
86 | def setUp(self):
87 | self.config = testing.setUp()
88 | _initTestingDB()
89 |
90 | def tearDown(self):
91 | testing.tearDown()
92 |
93 | def test_it(self):
94 | from OSMTM.views.views import home
95 | request = testing.DummyRequest()
96 | self.config.testing_securitypolicy(userid=u'foo')
97 | info = home(request)
98 | self.assertEqual(len(info['jobs']), 1)
99 | self.assertEqual(info['admin'], False)
100 |
101 | class TestJobNew(unittest.TestCase):
102 |
103 | def setUp(self):
104 | self.config = testing.setUp()
105 | self.session = _initTestingDB()
106 |
107 | def tearDown(self):
108 | testing.tearDown()
109 |
110 | def test_it(self):
111 | _registerRoutes(self.config)
112 | from OSMTM.views.jobs import job_new
113 | request = testing.DummyRequest()
114 | request.params = {
115 | 'form.submitted': True,
116 | 'title':u'NewJob',
117 | 'short_description':u'SomeShortDescription',
118 | 'description':u'SomeDescription',
119 | 'geometry':u'POLYGON((0 0, 100 0, 100 100, 0 100, 0 0))',
120 | 'workflow':u'SomeWorflow',
121 | 'imagery':u'',
122 | 'zoom':20
123 | }
124 | response = job_new(request)
125 | self.assertEqual(response.location, 'http://example.com/job/2')
126 | from OSMTM.models import Job
127 | self.assertEqual(len(self.session.query(Job).get(2).tiles),
128 | 9)
129 |
130 | class TestJob(unittest.TestCase):
131 |
132 | def setUp(self):
133 | self.config = testing.setUp()
134 | self.session = _initTestingDB()
135 |
136 | def tearDown(self):
137 | testing.tearDown()
138 |
139 | def test_it(self):
140 | _registerRoutes(self.config)
141 | from OSMTM.views.jobs import job
142 | request = testing.DummyRequest()
143 | self.config.testing_securitypolicy(userid=u'foo')
144 | request.matchdict = {'job': 1}
145 | info = job(request)
146 | from OSMTM.models import Job
147 | self.assertEqual(info['job'], self.session.query(Job).get(1))
148 |
149 | class FunctionalTests(unittest.TestCase):
150 |
151 | def setUp(self):
152 | from OSMTM import main
153 | settings = {
154 | 'sqlalchemy.url': 'sqlite:///:memory:'
155 | }
156 | self.app = main({}, **settings)
157 | from webtest import TestApp
158 | self.testapp = TestApp(self.app)
159 |
160 | from OSMTM.models import populate
161 | populate()
162 |
163 | def __remember(self, username):
164 | from pyramid.security import remember
165 | request = testing.DummyRequest(environ={'SERVER_NAME': 'servername'})
166 | request.registry = self.app.registry
167 | headers = remember(request, username, max_age=2*7*24*60*60)
168 | return {'Cookie': headers[0][1].split(';')[0]}
169 |
170 | def __forget(self):
171 | from pyramid.security import forget
172 | request = testing.DummyRequest(environ={'SERVER_NAME': 'servername'})
173 | request.registry = self.app.registry
174 | forget(request)
175 |
176 | def test_root(self):
177 | res = self.testapp.get('/', status=200)
178 | self.failUnless('About Task Server' in res.body)
179 | self.failUnless('Login' in res.body)
180 |
181 | def test_authenticated(self):
182 | headers = self.__remember('foo')
183 | try:
184 | res = self.testapp.get('/', headers=headers, status=200)
185 | finally:
186 | self.__forget()
187 | self.failUnless('You are foo' in res.body)
188 |
189 | def test_user_authenticated(self):
190 | headers = self.__remember('foo')
191 | try:
192 | res = self.testapp.get('/', headers=headers, status=200)
193 | finally:
194 | self.__forget()
195 | self.assertFalse('Users ' in res.body)
196 |
197 | def test_user_users(self):
198 | headers = self.__remember('foo')
199 | try:
200 | res = self.testapp.get('/users', headers=headers, status=200)
201 | finally:
202 | self.__forget()
203 |
204 | def test_user_profile(self):
205 | headers = self.__remember('foo')
206 | try:
207 | res = self.testapp.get('/user/foo', headers=headers, status=200)
208 | finally:
209 | self.__forget()
210 | self.assertTrue('Forbidden' in res.body)
211 |
212 | def test_admin_authenticated(self):
213 | headers = self.__remember('admin_user')
214 | try:
215 | res = self.testapp.get('/', headers=headers, status=200)
216 | finally:
217 | self.__forget()
218 | self.assertTrue('Users ' in res.body)
219 |
220 | def test_about(self):
221 | headers = self.__remember('foo')
222 | try:
223 | res = self.testapp.get('/about', headers=headers, status=200)
224 | finally:
225 | self.__forget()
226 | self.assertEquals(res.html.head.title.string, 'OSM Tasking Manager - About')
227 |
228 | def test_admin_user(self):
229 | headers = self.__remember('admin_user')
230 | try:
231 | res = self.testapp.get('/user/foo', headers=headers, status=200)
232 | self.assertTrue('Profile for foo' in res.body)
233 | self.assertFalse(res.html.find(id='admin').checked)
234 | finally:
235 | self.__forget()
236 |
237 | try:
238 | res = self.testapp.get('/user/admin_user', headers=headers, status=200)
239 | self.assertTrue('Profile for admin_user' in res.body)
240 | self.assertTrue(res.html.find(id='admin')['checked'] == 'checked')
241 | finally:
242 | self.__forget()
243 |
244 | def test_admin_user_update(self):
245 | headers = self.__remember('admin_user')
246 | try:
247 | res = self.testapp.get('/user/foo',
248 | headers=headers, status=200)
249 | res.form['admin'].checked = True
250 | res2 = res.form.submit('form.submitted', headers=headers,
251 | status=302)
252 | res3 = res2.follow(headers=headers, status=200)
253 | self.assertTrue('Profile for foo' in res3.body)
254 | self.assertTrue(res3.form['admin'].checked)
255 | finally:
256 | self.__forget()
257 |
258 | #########
259 | # tasks #
260 | #########
261 |
262 | def test_task_not_found(self):
263 | headers = self.__remember('foo')
264 | try:
265 | res = self.testapp.get('/job/1/task/1/1', headers=headers,
266 | status=404)
267 | finally:
268 | self.__forget()
269 |
270 | def test_task(self):
271 | headers = self.__remember('foo')
272 | try:
273 | res = self.testapp.get('/job/1/task/32774/42026', headers=headers,
274 | status=302)
275 | self.assertEquals(res.location, 'http://localhost/job/1')
276 | finally:
277 | self.__forget()
278 |
279 | def test_task_take(self):
280 | headers = self.__remember('foo')
281 | try:
282 | res = self.testapp.get('/job/1/task/32774/42026/take', headers=headers,
283 | status=302)
284 | self.assertEquals(res.location,
285 | 'http://localhost/job/1/task/32774/42026')
286 | res2 = self.testapp.get(res.location, headers=headers,
287 | status=200)
288 |
289 | form = res2.form
290 | res3 = form.submit(headers=headers, status=302)
291 | res4 = res3.follow(headers=headers, status=200)
292 | from OSMTM.models import DBSession, Tile
293 | session = DBSession()
294 | tile = session.query(Tile).get((32774, 42026, 1))
295 | self.assertEquals(tile.checkin, 1)
296 |
297 | res5 = form.submit(headers=headers)
298 | tile = session.query(Tile).get((32774, 42026, 1))
299 | self.assertEquals(tile.checkin, 1)
300 | finally:
301 | self.__forget()
302 |
--------------------------------------------------------------------------------
/OSMTM/utils.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from math import floor, ceil, pi, atan, exp
3 | from shapely.geometry import Polygon
4 |
5 | import logging
6 | log = logging.getLogger(__name__)
7 |
8 | # Maximum resolution
9 | MAXRESOLUTION = 156543.0339
10 |
11 | # X/Y axis limit
12 | max_limit = MAXRESOLUTION * 256 / 2
13 |
14 |
15 | class TileBuilder(object):
16 | def __init__(self, parameter):
17 | self.a = parameter
18 |
19 | def create_square(self, i, j, srs=900913):
20 | """
21 | creates a Shapely Polygon geometry representing tile indexed by (i,j)
22 | in OSMQA v2 with dimension a
23 | """
24 | xmin = i * self.a - max_limit
25 | ymin = j * self.a - max_limit
26 | xmax = (i + 1) * self.a - max_limit
27 | ymax = (j + 1) * self.a - max_limit
28 | if srs == 4326:
29 | xmin, ymin = transform_900913_to_4326(xmin, ymin)
30 | xmax, ymax = transform_900913_to_4326(xmax, ymax)
31 | return Polygon(
32 | [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
33 | )
34 |
35 |
36 | def get_tiles_in_geom(geom, z):
37 | """
38 | This method finds the tiles that intersect the given geometry for the
39 | given zoom
40 | """
41 | xmin = geom.bounds[0]
42 | ymin = geom.bounds[1]
43 | xmax = geom.bounds[2]
44 | ymax = geom.bounds[3]
45 |
46 | # tile size (in meters) at the required zoom level
47 | step = max_limit / (2 ** (z - 1))
48 |
49 | xminstep = int(floor((xmin + max_limit) / step))
50 | xmaxstep = int(ceil((xmax + max_limit) / step))
51 | yminstep = int(floor((ymin + max_limit) / step))
52 | ymaxstep = int(ceil((ymax + max_limit) / step))
53 |
54 | tb = TileBuilder(step)
55 | polygons = []
56 | tiles = []
57 | for i in range(xminstep, xmaxstep + 1):
58 | for j in range(yminstep, ymaxstep + 1):
59 | polygon = tb.create_square(i, j)
60 | if geom.intersects(polygon):
61 | polygons.append(polygon)
62 | tiles.append((i, j))
63 | return tiles
64 |
65 |
66 | def transform_900913_to_4326(x, y):
67 | """
68 | Transforms pair of mercator coordinates to lonlat
69 | """
70 | lon = (x / 20037508.34) * 180
71 | lat = (y / 20037508.34) * 180
72 |
73 | lat = 180 / pi * (2 * atan(exp(lat * pi / 180)) - pi / 2)
74 |
75 | return lon, lat
76 |
77 |
78 | def ungettext(a, b, count):
79 | if count:
80 | return b
81 | return a
82 |
83 |
84 | def ugettext(a):
85 | return a
86 |
87 |
88 | def timesince(d, now=None):
89 | if d is None:
90 | return
91 | """
92 | Takes two datetime objects and returns the time between d and now
93 | as a nicely formatted string, e.g. "10 minutes". If d occurs after now,
94 | then "0 minutes" is returned.
95 |
96 | Units used are years, months, weeks, days, hours, and minutes.
97 | Seconds and microseconds are ignored. Up to two adjacent units will be
98 | displayed. For example, "2 weeks, 3 days" and "1 year, 3 months" are
99 | possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not.
100 |
101 | Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
102 | """
103 | chunks = (
104 | (60 * 60 * 24 * 365, lambda n: ungettext('year', 'years', n)),
105 | (60 * 60 * 24 * 30, lambda n: ungettext('month', 'months', n)),
106 | (60 * 60 * 24 * 7, lambda n: ungettext('week', 'weeks', n)),
107 | (60 * 60 * 24, lambda n: ungettext('day', 'days', n)),
108 | (60 * 60, lambda n: ungettext('hour', 'hours', n)),
109 | (60, lambda n: ungettext('minute', 'minutes', n))
110 | )
111 | # Convert datetime.date to datetime.datetime for comparison.
112 | if not isinstance(d, datetime.datetime):
113 | d = datetime.datetime(d.year, d.month, d.day)
114 | if now and not isinstance(now, datetime.datetime):
115 | now = datetime.datetime(now.year, now.month, now.day)
116 |
117 | if not now:
118 | if d.tzinfo:
119 | now = datetime.datetime.now(LocalTimezone(d))
120 | else:
121 | now = datetime.datetime.now()
122 |
123 | # ignore microsecond part of 'd' since we removed it from 'now'
124 | delta = now - (d - datetime.timedelta(0, 0, d.microsecond))
125 | since = delta.days * 24 * 60 * 60 + delta.seconds
126 | if since <= 0:
127 | # d is in the future compared to now, stop processing.
128 | return u'0 ' + ugettext('minutes')
129 | for i, (seconds, name) in enumerate(chunks):
130 | count = since // seconds
131 | if count != 0:
132 | break
133 | s = ugettext(
134 | '%(number)d %(type)s') % {'number': count, 'type': name(count)}
135 |
136 | if i + 1 < len(chunks):
137 | # Now get the second item
138 | seconds2, name2 = chunks[i + 1]
139 | count2 = (since - (seconds * count)) // seconds2
140 | if count2 != 0:
141 | s += ugettext(', %(number)d %(type)s') % {
142 | 'number': count2, 'type': name2(count2)
143 | }
144 | return s
145 |
146 |
147 | def parse_float(input_value, default_output=0):
148 | """
149 | Float parsing utility function
150 |
151 | :arg input_value: the value to parse
152 | :type input_value: string
153 |
154 | :arg default_output: returned value if input is not parsable
155 | :type default_output: float
156 |
157 | :returns: a float number
158 | :rtype: float
159 | """
160 | try:
161 | output = float(input_value)
162 | except Exception, e:
163 | # catch all exceptions
164 | log.debug('Could not parse float value, argument %s', input_value)
165 | log.debug('Exception: %s', e.message)
166 |
167 | output = default_output
168 |
169 | return output
170 |
--------------------------------------------------------------------------------
/OSMTM/views/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/OSMTM/views/__init__.py
--------------------------------------------------------------------------------
/OSMTM/views/admin.py:
--------------------------------------------------------------------------------
1 | from pyramid.view import view_config
2 | from pyramid.url import route_url
3 |
4 | from pyramid.security import remember, forget, authenticated_userid
5 |
6 | from OSMTM.models import License
7 |
8 | @view_config(route_name='admin', renderer='admin.mako', permission='edit')
9 | def admin(request):
10 | return dict()
11 |
--------------------------------------------------------------------------------
/OSMTM/views/crossdomain.py:
--------------------------------------------------------------------------------
1 | import os
2 | from webob import Response
3 |
4 | def crossdomain_view(request):
5 | here = os.path.dirname(__file__)
6 | file = open(os.path.join(here, '../static', 'crossdomain.xml'))
7 | return Response(content_type='text/xml', app_iter=file)
8 |
--------------------------------------------------------------------------------
/OSMTM/views/license.py:
--------------------------------------------------------------------------------
1 | from pyramid.httpexceptions import HTTPFound
2 | from pyramid.view import view_config
3 | from pyramid.url import route_url
4 |
5 | from pyramid.security import remember, forget, authenticated_userid
6 |
7 | from OSMTM.models import DBSession
8 | from OSMTM.models import User
9 | from OSMTM.models import License
10 |
11 | @view_config(route_name='licenses', renderer='licenses.mako', permission='edit')
12 | def licenses(request):
13 | session = DBSession()
14 | licenses = session.query(License).all()
15 | return dict(licenses=licenses)
16 |
17 | @view_config(route_name='license', renderer='license.mako', permission='edit')
18 | def license(request):
19 | session = DBSession()
20 | id = request.matchdict['license']
21 | license = session.query(License).get(id)
22 |
23 | username = authenticated_userid(request)
24 | user = session.query(User).get(username)
25 | redirect = request.params.get("redirect", request.route_url("home"))
26 | if "accepted_terms" in request.params:
27 | if request.params["accepted_terms"] == "I AGREE":
28 | user.accepted_licenses.append(license)
29 | elif license in user.accepted_licenses:
30 | user.accepted_licenses.remove(license)
31 | return HTTPFound(location=redirect)
32 | else:
33 | return dict(user=user, license=license, redirect=redirect)
34 |
35 | @view_config(route_name='license_new', permission='admin')
36 | def license_new(request):
37 | session = DBSession()
38 | license = License()
39 | license.name = ''
40 | license.description = ''
41 | license.plain_text = ''
42 |
43 | session.add(license)
44 | session.flush()
45 | return HTTPFound(location = route_url('license_edit', request, license=license.id))
46 |
47 | @view_config(route_name='license_delete', permission='admin')
48 | def license_delete(request):
49 | session = DBSession()
50 | id = request.matchdict['license']
51 | license = session.query(License).get(id)
52 |
53 | session.delete(license)
54 | session.flush()
55 | request.session.flash('License removed!')
56 | return HTTPFound(location = route_url('licenses', request))
57 |
58 | @view_config(route_name='license_edit', renderer='license.edit.mako',
59 | permission='admin')
60 | def license_edit(request):
61 | id = request.matchdict['license']
62 | session = DBSession()
63 | license = session.query(License).get(id)
64 |
65 | if 'form.submitted' in request.params:
66 | license.name = request.params['name']
67 | license.description = request.params['description']
68 | license.plain_text = request.params['plain_text']
69 |
70 | session.add(license)
71 | request.session.flash('License updated!')
72 | return dict(license=license)
73 |
--------------------------------------------------------------------------------
/OSMTM/views/osmproxy.py:
--------------------------------------------------------------------------------
1 | import tempfile
2 | import urllib
3 |
4 | from pyramid.view import view_config
5 | from pyramid.httpexceptions import (HTTPBadRequest)
6 | from pyramid.response import Response
7 |
8 | from imposm.parser import OSMParser
9 | from shapely.geometry import Polygon
10 | from shapely.geometry import MultiPolygon
11 |
12 | from geojson import Feature, FeatureCollection
13 |
14 | import logging
15 | log = logging.getLogger(__file__)
16 |
17 | @view_config(route_name='osmproxy', renderer='geojson')
18 | def osmproxy(request):
19 | url = request.params.get("url")
20 | if url is None:
21 | return HTTPBadRequest()
22 |
23 | # instantiate parser and parser and start parsing
24 | parser = RelationParser()
25 | p = OSMParser(concurrency=1,
26 | coords_callback=parser.get_coords,
27 | relations_callback=parser.get_relations,
28 | ways_callback=parser.get_ways)
29 |
30 | temp = tempfile.NamedTemporaryFile(suffix='.osm')
31 | urllib.urlretrieve(url, temp.name)
32 | p.parse(temp.name)
33 | temp.close()
34 |
35 | polygons = []
36 | r = parser.relation
37 |
38 | # first check for self closing ways
39 | for i in range(len(r) - 1, 0, -1):
40 | w = parser.ways[r[i]]
41 | if w[len(w) - 1] == w[0]:
42 | r.pop(i)
43 | nodes = []
44 | polygon = Polygon([parser.nodes[node] for node in w])
45 | polygons.append(polygon)
46 |
47 | if len(r) > 0:
48 | prev = parser.ways[r[0]]
49 | ordered_ways = []
50 | ordered_ways.append(prev)
51 | r.pop(0)
52 | while len(r):
53 | match = False
54 | for i in range(0, len(r)):
55 | w = parser.ways[r[i]]
56 | # first node of the next way matches the last of the previous one
57 | if w[0] == prev[len(prev) - 1]:
58 | match = w
59 | # or maybe the way has to be reversed
60 | elif w[len(w) - 1] == prev[len(prev) - 1]:
61 | match = w[::-1]
62 | if match:
63 | prev = match
64 | ordered_ways.append(match)
65 | r.pop(i)
66 | break
67 |
68 | if len(ordered_ways) > 0:
69 | # now that ways are correctly ordered, we can create a unique geometry
70 | nodes = []
71 | for way in ordered_ways:
72 | for node in way:
73 | nodes.append(parser.nodes[node])
74 | # make sure that first and last node are similar
75 | if nodes[0] != nodes[len(nodes) - 1]:
76 | raise
77 | # create a shapely polygon with the nodes
78 | polygons.append(Polygon(nodes))
79 |
80 | multipolygon = MultiPolygon(polygons)
81 | return FeatureCollection([Feature(geometry=multipolygon)])
82 |
83 | # simple class that handles the parsed OSM data.
84 | class RelationParser(object):
85 | def __init__(self):
86 | self.nodes = {}
87 | self.ways = {}
88 | self.relation = []
89 |
90 | def get_coords(self, coords):
91 | # callback method for nodes
92 | for osm_id, lon, lat in coords:
93 | self.nodes[osm_id] = (lon, lat)
94 |
95 | def get_ways(self, ways):
96 | # callback method for ways
97 | for way in ways:
98 | self.ways[way[0]] = way[2]
99 |
100 | def get_relations(self, relations):
101 | # callback method for relations
102 | # there should be only one in our case
103 | if len(relations) == 0:
104 | return
105 | for member in relations[0][2]:
106 | if member[1] == 'way':
107 | self.relation.append(member[0])
108 |
--------------------------------------------------------------------------------
/OSMTM/views/security.py:
--------------------------------------------------------------------------------
1 | from pyramid.security import authenticated_userid
2 |
3 | def login(request):
4 | return dict(user=authenticated_userid(request))
5 |
--------------------------------------------------------------------------------
/OSMTM/views/tasks.py:
--------------------------------------------------------------------------------
1 | from pyramid.httpexceptions import HTTPFound
2 | from pyramid.httpexceptions import HTTPNotFound
3 | from pyramid.view import view_config
4 | from pyramid.url import route_url
5 |
6 | from OSMTM.models import DBSession
7 | from OSMTM.models import Tile
8 | from OSMTM.models import TileHistory
9 | from OSMTM.models import User
10 | from OSMTM.models import Job
11 |
12 | from OSMTM.views.views import EXPIRATION_DURATION, checkTask
13 |
14 | from geojson import Feature, FeatureCollection
15 | from sqlalchemy.sql.expression import and_
16 | from sqlalchemy.orm.exc import NoResultFound
17 |
18 | from datetime import datetime
19 | import random
20 |
21 | from pyramid.security import authenticated_userid
22 |
23 | import logging
24 | log = logging.getLogger(__name__)
25 |
26 | @view_config(route_name="task_empty", renderer="task.empty.mako", permission="job")
27 | def task_empty(request):
28 | id = request.matchdict['job']
29 | session = DBSession()
30 | job = session.query(Job).get(id)
31 | return dict(job=job)
32 |
33 | @view_config(route_name='task_xhr', renderer='task.mako', permission='job',
34 | http_cache=0)
35 | def task_xhr(request):
36 | job_id = request.matchdict['job']
37 | x = request.matchdict['x']
38 | y = request.matchdict['y']
39 | zoom = request.matchdict['zoom']
40 | session = DBSession()
41 | tile = session.query(Tile).get((x, y, zoom, job_id))
42 | if tile is None:
43 | return HTTPNotFound()
44 | username = authenticated_userid(request)
45 | user = session.query(User).get(username)
46 | time_left = 'null'
47 | #if tile.user != user:
48 | #request.session.flash('You cannot see this task.')
49 | #return HTTPFound(location=request.route_url('job', job=job_id))
50 | if tile.update:
51 | time_left = (tile.update - (datetime.now() - EXPIRATION_DURATION)) \
52 | .seconds
53 | filter = and_(TileHistory.x==x, TileHistory.y==y, TileHistory.job_id==job_id)
54 | history = session.query(TileHistory).filter(filter)\
55 | .order_by(TileHistory.update)\
56 | .all()
57 |
58 | current_task = get_locked_task(job_id, username)
59 | log.debug(
60 | 'Tile username: %s, checkout: %s, checkin: %s',
61 | tile.username, tile.checkout, tile.checkin
62 | )
63 | return dict(tile=tile,
64 | current_task=current_task,
65 | history=history,
66 | time_left=time_left,
67 | user=user,
68 | job=tile.job)
69 |
70 | @view_config(route_name='task_done', permission='job', renderer='json')
71 | def done(request):
72 | job_id = request.matchdict['job']
73 | x = request.matchdict['x']
74 | y = request.matchdict['y']
75 | zoom = request.matchdict['zoom']
76 | session = DBSession()
77 | username = authenticated_userid(request)
78 | tile = session.query(Tile).get((x, y, zoom, job_id))
79 | tile.comment = request.params['comment']
80 | if 'invalidate' in request.params:
81 | # task goes back to the queue
82 | tile.checkin = 0
83 | tile.username = username
84 | elif 'validate' in request.params:
85 | # task goes back to the queue
86 | tile.checkin = 2
87 | tile.username = username
88 | else:
89 | #task is done
90 | tile.checkin = 1
91 | tile.change = True
92 | tile.checkout = False
93 | session.add(tile)
94 | session.flush()
95 |
96 | # reset tile values
97 | tile.username = None
98 | tile.change = False
99 | tile.comment = None
100 | session.add(tile)
101 | return dict(success=True, tile=dict(x=tile.x, y=tile.y, z=tile.zoom))
102 |
103 | @view_config(route_name='task_unlock', permission='job', renderer='json')
104 | def unlock(request):
105 | job_id = request.matchdict['job']
106 | x = request.matchdict['x']
107 | y = request.matchdict['y']
108 | zoom = request.matchdict['zoom']
109 | session = DBSession()
110 | tile = session.query(Tile).get((x, y, zoom, job_id))
111 | tile.comment = request.params['comment']
112 | tile.username = None
113 | tile.checkout = False
114 | tile.change = False
115 | session.add(tile)
116 | return dict(success=True, tile=dict(x=tile.x, y=tile.y, z=tile.zoom))
117 |
118 | @view_config(route_name='task_lock', permission='job', renderer="json")
119 | def lock(request):
120 | job_id = request.matchdict['job']
121 | session = DBSession()
122 | username = authenticated_userid(request)
123 | user = session.query(User).get(username)
124 | job = session.query(Job).get(job_id)
125 |
126 | x = request.matchdict['x']
127 | y = request.matchdict['y']
128 | zoom = request.matchdict['zoom']
129 | tile = session.query(Tile).get((x, y, zoom, job_id))
130 |
131 | # task is already checked out by someone else
132 | if tile.checkout is True and tile.username != user:
133 | msg = 'You cannot lock this task. Someone else is already working on it.'
134 | return dict(error_msg=msg)
135 |
136 | # check if user has no task he's currently working on
137 | filter = and_(Tile.username==username, Tile.checkout==True, Tile.job_id==job_id)
138 | tiles_current = session.query(Tile).filter(filter).all()
139 | if len(tiles_current) > 0 and tile.user != user:
140 | msg = 'You already have a task to work on. Finish it before you can accept a new one.'
141 | return dict(error_msg=msg)
142 |
143 | try:
144 | tile.username = username
145 | tile.checkout = True
146 | tile.change = False
147 | tile.comment = None
148 | session.add(tile)
149 | return dict(success=True, tile=dict(x=tile.x, y=tile.y, z=tile.zoom))
150 | except:
151 | if int(checkin) == 1:
152 | msg = 'Sorry. No task available to validate.'
153 | else:
154 | msg = 'Sorry. No task available to take.'
155 | return dict(job=job, error_msg=msg)
156 |
157 | @view_config(route_name='task_take_random', permission='job', renderer="json")
158 | def take_random(request):
159 | job_id = request.matchdict['job']
160 | if "checkin" in request.matchdict:
161 | checkin = request.matchdict['checkin']
162 | else:
163 | checkin = None
164 | session = DBSession()
165 | username = authenticated_userid(request)
166 | user = session.query(User).get(username)
167 | job = session.query(Job).get(job_id)
168 |
169 | filter = and_(Tile.checkin==checkin, Tile.job_id==job_id)
170 | tiles = session.query(Tile).filter(filter).all()
171 | # take random tile
172 | if checkin is not None:
173 | # get the tile the user worked on previously
174 | filter = and_(TileHistory.username==username, TileHistory.job_id==job_id)
175 | p = session.query(TileHistory).filter(filter).order_by(TileHistory.update.desc()).limit(4).all()
176 | tile = None
177 | if p is not None and len(p) > 0:
178 | p = p[len(p) -1]
179 | neighbours = [
180 | (p.x - 1, p.y - 1), (p.x - 1, p.y), (p.x - 1, p.y + 1),
181 | (p.x, p.y - 1), (p.x, p.y + 1),
182 | (p.x + 1, p.y - 1), (p.x + 1, p.y), (p.x + 1, p.y + 1)]
183 | for t in tiles:
184 | if (t.x, t.y) in neighbours:
185 | tile = t
186 | break
187 | if tile is None:
188 | tile = tiles[random.randrange(0, len(tiles))]
189 |
190 | return dict(success=True, tile=dict(x=tile.x, y=tile.y, z=tile.zoom))
191 |
192 |
193 | @view_config(route_name='task_split', permission='job', renderer="geojson")
194 | def split_tile(request):
195 | """
196 | Split the tile and copy history of the parent tile
197 | """
198 | job_id = request.matchdict['job']
199 | x = request.matchdict['x']
200 | y = request.matchdict['y']
201 | zoom = request.matchdict['zoom']
202 | session = DBSession()
203 | job = session.query(Job).get(job_id)
204 | tile = session.query(Tile).get((x, y, zoom, job_id))
205 | session.delete(tile)
206 |
207 | # reference tile history
208 | tileHistory = (
209 | session.query(TileHistory)
210 | .filter_by(x=x, y=y, zoom=zoom, job_id=job_id)
211 | .order_by(TileHistory.update)
212 | .all()
213 | )
214 |
215 | new_tiles = []
216 | t = []
217 | for i in range(0, 2):
218 | for j in range(0, 2):
219 | # add new tile
220 | X = int(x) * 2 + i
221 | Y = int(y) * 2 + j
222 | Zoom = int(zoom) + 1
223 | newTile = Tile(X, Y, Zoom)
224 | newTile.job = job
225 |
226 | for idx, historyRecord in enumerate(tileHistory):
227 | # copy tileHistory... use negative versions to prevent unique
228 | # key conflicts, and enable filtering (exclusion) when
229 | # generating stats
230 | newTileHistory = TileHistory(
231 | x=newTile.x,
232 | y=newTile.y,
233 | zoom=newTile.zoom,
234 | username=historyRecord.username,
235 | update=historyRecord.update,
236 | checkout=historyRecord.checkout,
237 | checkin=historyRecord.checkin,
238 | change=historyRecord.change,
239 | comment=historyRecord.comment,
240 | job_id=job.id,
241 | version=-idx
242 | )
243 | session.add(newTileHistory)
244 |
245 | t.append(newTile)
246 | for tile in t:
247 | new_tiles.append(
248 | Feature(
249 | geometry=tile.to_polygon(),
250 | id=str(tile.x) + '-' + str(tile.y) + '-' + str(tile.zoom)))
251 | return dict(
252 | success=True, split_id="-".join([x, y, zoom]),
253 | new_tiles=FeatureCollection(new_tiles)
254 | )
255 |
256 |
257 | @view_config(route_name="task_export_osm", renderer="task.osm.mako")
258 | def task_export_osm(request):
259 | job_id = request.matchdict['job']
260 | x = request.matchdict['x']
261 | y = request.matchdict['y']
262 | zoom = request.matchdict['zoom']
263 | session = DBSession()
264 | tile = session.query(Tile).get((x, y, zoom, job_id))
265 | return dict(polygon=tile.to_polygon(4326))
266 |
267 | @view_config(route_name="task_export_gpx", renderer="task.gpx.mako")
268 | def task_export_gpx(request):
269 | job_id = request.matchdict['job']
270 | x = request.matchdict['x']
271 | y = request.matchdict['y']
272 | zoom = request.matchdict['zoom']
273 | session = DBSession()
274 | tile = session.query(Tile).get((x, y, zoom, job_id))
275 | return dict(polygon=tile.to_polygon(4326), job_id=job_id)
276 |
277 | def get_locked_task(job_id, username):
278 | session = DBSession()
279 | try:
280 | filter = and_(Tile.username==username, Tile.checkout==True, Tile.job_id==job_id)
281 | return session.query(Tile).filter(filter).one()
282 | except NoResultFound, e:
283 | return None
284 |
--------------------------------------------------------------------------------
/OSMTM/views/views.py:
--------------------------------------------------------------------------------
1 | import urlparse
2 | from xml.etree import ElementTree
3 | from pyramid.httpexceptions import HTTPFound, HTTPBadGateway, HTTPBadRequest
4 | from pyramid.view import view_config
5 | from pyramid.url import route_url
6 | from pyramid.renderers import render_to_response
7 |
8 | from OSMTM.models import DBSession
9 | from OSMTM.models import Job
10 | from OSMTM.models import User
11 | from OSMTM.models import Tile
12 | from OSMTM.models import TileHistory
13 | from OSMTM.models import Tag
14 |
15 | import oauth2 as oauth
16 |
17 | from pyramid.security import remember, forget, authenticated_userid
18 |
19 | from json import dumps
20 | from markdown import markdown
21 | from OSMTM.utils import timesince
22 | from datetime import datetime, timedelta
23 | from sqlalchemy import desc, distinct, func
24 | from sqlalchemy.sql.expression import and_
25 |
26 | from OSMTM.utils import transform_900913_to_4326
27 |
28 | import logging
29 | log = logging.getLogger(__file__)
30 |
31 | #
32 | # Constants
33 | #
34 |
35 | # our oauth key and secret (we're the consumer in the oauth protocol)
36 | # consumer key and secret created by Kate Chapman
37 | CONSUMER_KEY = 'BOFkVgLDXTSMP6VHfiX8MQ'
38 | CONSUMER_SECRET = '4o4uLSqLWMciG2fE2zGncLcdewPNi9wU1To51Iz2E'
39 |
40 | # OSM oauth URLs
41 | BASE_URL = 'http://www.openstreetmap.org/oauth'
42 | REQUEST_TOKEN_URL = '%s/request_token' % BASE_URL
43 | ACCESS_TOKEN_URL = '%s/access_token' % BASE_URL
44 | AUTHORIZE_URL = '%s/authorize' % BASE_URL
45 |
46 | # OSM user details URL
47 | USER_DETAILS_URL = 'http://api.openstreetmap.org/api/0.6/user/details'
48 |
49 | # an oauth consumer instance using our key and secret
50 | consumer = oauth.Consumer(CONSUMER_KEY, CONSUMER_SECRET)
51 |
52 | @view_config(route_name='login')
53 | def login(request):
54 | # get the request token
55 | client = oauth.Client(consumer)
56 | oauth_callback_url = request.route_url('oauth_callback')
57 | url = "%s?oauth_callback=%s" % (REQUEST_TOKEN_URL, oauth_callback_url)
58 | resp, content = client.request(url, "GET")
59 | if resp['status'] != '200':
60 | return HTTPBadGateway('The OSM authentication server didn\'t respond correctly')
61 | request_token = dict(urlparse.parse_qsl(content))
62 | # store the request token in the session, we'll need in the callback
63 | session = request.session
64 | session['request_token'] = request_token
65 | session['came_from'] = request.params.get('came_from')
66 | session.save()
67 | redirect_url = "%s?oauth_token=%s" % \
68 | (AUTHORIZE_URL, request_token['oauth_token'])
69 | return HTTPFound(location=redirect_url)
70 |
71 | @view_config(route_name='oauth_callback')
72 | def oauth_callback(request):
73 | # the request token we have in the user session should be the same
74 | # as the one passed to the callback
75 | session = request.session
76 | request_token = session.get('request_token')
77 | if request.params.get('oauth_token') != request_token['oauth_token']:
78 | return HTTPBadRequest('Tokens don\'t match')
79 | # get the access token
80 | token = oauth.Token(request_token['oauth_token'],
81 | request_token['oauth_token_secret'])
82 | verifier = request.params.get('oauth_verifier')
83 | token.set_verifier(verifier)
84 | client = oauth.Client(consumer, token)
85 | resp, content = client.request(ACCESS_TOKEN_URL, "POST")
86 | access_token = dict(urlparse.parse_qsl(content))
87 | token = access_token['oauth_token']
88 | token_secret = access_token['oauth_token_secret']
89 | # get the user details, finally
90 | token = oauth.Token(token, token_secret)
91 | client = oauth.Client(consumer, token)
92 | resp, content = client.request(USER_DETAILS_URL, "GET")
93 | user_elt = ElementTree.XML(content).find('user')
94 | # save the user's "display name" in the session
95 | if 'display_name' in user_elt.attrib:
96 | username = user_elt.attrib['display_name']
97 | db_session = DBSession()
98 | if db_session.query(User).get(username) is None:
99 | db_session.add(User(username))
100 | db_session.flush()
101 | headers = remember(request, username, max_age=20*7*24*60*60)
102 |
103 | # and redirect to the main page
104 | return HTTPFound(location=session.get('came_from'), headers=headers)
105 |
106 | @view_config(route_name='logout')
107 | def logout(request):
108 | headers = forget(request)
109 | return HTTPFound(location=request.route_url('home'), headers=headers)
110 |
111 | @view_config(route_name='home', renderer='home.mako', permission='edit')
112 | def home(request):
113 | session = DBSession()
114 | username = authenticated_userid(request)
115 | user = session.query(User).get(username)
116 | jobs = session.query(Job).order_by(desc(Job.id))
117 | if user is None:
118 | redirect = request.params.get("redirect", request.route_url("logout"))
119 | return HTTPFound(location=redirect)
120 | if not user.is_admin():
121 | jobs = [job for job in jobs if not job.is_private and job.status == 1] + user.private_jobs
122 | tiles = session.query(Tile) \
123 | .filter(Tile.username!=None) \
124 | .group_by(Tile.username)
125 | # unlock expired tiles
126 | for tile in tiles:
127 | checkTask(tile)
128 | my_jobs = session.query(TileHistory) \
129 | .filter(TileHistory.username==user.username) \
130 | .group_by(TileHistory.job_id)
131 | my_jobs = [tile.job_id for tile in my_jobs]
132 |
133 | dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.datetime) else None
134 |
135 | def to_five(i):
136 | return int(round(i/5)) * 5
137 |
138 | def to_dict(job):
139 | centroid = job.get_centroid()
140 | filter = and_(Tile.job==job,Tile.checkout==True, Tile.username!=None)
141 | current_users = session.query(distinct(Tile.username)) \
142 | .filter(filter).all()
143 | current_users = [u[0] for u in current_users]
144 |
145 | x, y = transform_900913_to_4326(centroid.x, centroid.y)
146 | left = (x + 180) * 120 / 360 - 1
147 | top = (-y + 90) * 60 / 180 - 1
148 |
149 | return dict(
150 | title=job.title,
151 | status=job.status,
152 | short_description=markdown(job.short_description),
153 | author=job.author,
154 | is_private=job.is_private,
155 | featured=job.featured,
156 | last_update=timesince(job.last_update),
157 | done=job.done,
158 | users=current_users,
159 | usersText="Currently working: %s" % ", ".join(current_users),
160 | url=request.route_url('job', job=job.id),
161 | feature_url=request.route_url('job_feature', job=job.id),
162 | archive_url=request.route_url('job_archive', job=job.id),
163 | publish_url=request.route_url('job_publish', job=job.id),
164 | edit_url=request.route_url('job_edit', job=job.id),
165 | tags=[tag.tag for tag in job.tags],
166 | is_mine=job.id in [_job for _job in my_jobs],
167 | lon=centroid.x,
168 | lat=centroid.y,
169 | left=left,
170 | top=top
171 | )
172 |
173 | jobs = dumps([to_dict(job) for job in jobs], default=dthandler)
174 |
175 | return dict(jobs=jobs,
176 | user=user,
177 | admin=user.is_admin(),
178 | my_jobs=my_jobs)
179 |
180 | @view_config(route_name='about', renderer='about.mako')
181 | def about(request):
182 | return dict()
183 |
184 | @view_config(route_name='user', renderer='user.mako')
185 | def user(request):
186 | session = DBSession()
187 | profile_user = session.query(User).get(request.matchdict["id"])
188 | jobs = user_job_info(profile_user.username)
189 |
190 | username = authenticated_userid(request)
191 | user = session.query(User).get(username)
192 | admin=user.is_admin()
193 | return dict(user=profile_user, jobs=jobs, admin=admin)
194 |
195 | @view_config(route_name='user_edit', renderer='user_edit.mako', permission='admin')
196 | def user_edit(request):
197 | session = DBSession()
198 | user = session.query(User).get(request.matchdict["id"])
199 | return dict(user=user, admin=True)
200 |
201 | @view_config(route_name='user_update', permission='admin')
202 | def user_update(request):
203 | session = DBSession()
204 | user = session.query(User).get(request.matchdict["id"])
205 | if 'form.submitted' in request.params:
206 | user.admin = True if 'admin' in request.params else False
207 | session.flush()
208 | #request.session.flash('Profile correctly updated!')
209 | return HTTPFound(location=request.route_url('user',id=user.username))
210 |
211 | @view_config(route_name='user_add', permission='admin')
212 | def user_add(request):
213 | session = DBSession()
214 | username = request.params.get("username")
215 | if session.query(User).get(username) is None:
216 | session.add(User(username))
217 | session.flush()
218 | return HTTPFound(location=request.route_url('user', id=username))
219 |
220 | @view_config(route_name='users', renderer='users.mako', permission="edit")
221 | def users(request):
222 | session = DBSession()
223 | current_username = authenticated_userid(request)
224 | current_user = session.query(User).get(current_username)
225 | return dict(users = session.query(User), admin=current_user.is_admin())
226 |
227 | def user_job_info(username):
228 | session = DBSession()
229 |
230 | """ get the tiles that changed """
231 | filter = and_(TileHistory.change==True, TileHistory.checkin==1,
232 | TileHistory.username == username, TileHistory.job_id==Job.id)
233 | user_jobs = session.query(Job, func.count(TileHistory.username)) \
234 | .filter(filter) \
235 | .group_by(Job.id) \
236 | .all()
237 | job_info = [{"job": job[0], "count": job[1]} for job in user_jobs]
238 | return job_info
239 |
240 | @view_config(route_name='tour', renderer='tour.mako')
241 | def tour(request):
242 | return dict()
243 |
244 | # the time delta after which the task is unlocked (in seconds)
245 | EXPIRATION_DURATION = timedelta(seconds=2 * 60 * 60)
246 |
247 | # unlock the tile if expired
248 | def checkTask(tile):
249 | session = DBSession()
250 | if tile.checkout is not False and tile.checkout is not None:
251 | if datetime.now() > tile.update + EXPIRATION_DURATION:
252 | tile.username = None
253 | tile.checkout = False
254 | tile.update = datetime.now()
255 | session.add(tile)
256 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Deprecated
2 | ==========
3 |
4 | **This repo is now deprecated in favor of** osm-tasking-manager2_
5 |
6 | .. _osm-tasking-manager2: https://github.com/hotosm/osm-tasking-manager2
7 |
8 |
9 |
10 |
11 |
12 | OpenStreetMap Tasking Manager
13 | =============================
14 |
15 | About
16 | -----
17 |
18 | OSMTM enables collaborative work on specific areas in OpenStreetMap by defining
19 | clear workflows to be achieved and by breaking tasks down into pieces.
20 |
21 | The application is written in Python using the Pylons framework.
22 |
23 |
24 | Dependencies
25 | ------------
26 |
27 | OSMTM has a set of dependencies that you need to install first.
28 |
29 | On debian systems you can do::
30 |
31 | sudo apt-get install build-essential protobuf-compiler libprotobuf-dev libgeos-dev python-dev
32 |
33 | On OS X you can do::
34 |
35 | brew install protobuf geos
36 |
37 |
38 | Installation
39 | ------------
40 |
41 | First clone the git repository::
42 |
43 | git clone git://github.com/hotosm/osm-tasking-manager.git
44 |
45 | Update and load the submodules::
46 |
47 | cd osm-tasking-manager
48 | git submodule update --init
49 |
50 | Installing OSMTM in a Virtual Python environment is recommended.
51 |
52 | To create a virtual Python environment::
53 |
54 | wget http://pypi.python.org/packages/source/v/virtualenv/virtualenv-1.8.tar.gz
55 | tar xvzf virtualenv-1.8.tar.gz
56 | python virtualenv-1.8/virtualenv.py --distribute --no-site-packages env
57 | rm -rf distribute-0.6.28.tar.gz
58 | rm -rf virtualenv-1.8*
59 | source env/bin/activate
60 |
61 | To install OSMTM from source (the only option at this point) in the virtual
62 | Python environment execute the ``setup.py`` script::
63 |
64 | python setup.py install
65 |
66 | Edit the development.ini file and change the ``admin_user`` variable to match
67 | your OSM username.
68 |
69 |
70 | Run OSMTM
71 | ---------
72 |
73 | To run OSMTM the easiest is to use ``pserve``::
74 |
75 | pserve --reload development.ini
76 |
77 |
78 | Installation as a mod_wsgi Application
79 | --------------------------------------
80 |
81 | Edit the production.ini file and change the ``admin_user`` variable to match
82 | your OSM username.
83 |
84 | Install and enable mod_wsgi module in Apache::
85 |
86 | sudo apt-get install libapache2-mod-wsgi
87 |
88 | Create a new Apache config file with the following::
89 |
90 | # Use only 1 Python sub-interpreter. Multiple sub-interpreters
91 | # play badly with C extensions.
92 | WSGIPassAuthorization On
93 | WSGIDaemonProcess OSMTM_process user=ubuntu group=ubuntu processes=1 \
94 | threads=4 \
95 | python-path=/home/ubuntu/osm-tasking-manager/env/lib/python2.7/site-packages
96 | WSGIScriptAlias /OSMTM /home/ubuntu/osm-tasking-manager/env/OSMTM.wsgi
97 | WSGIRestrictStdin Off
98 |
99 |
100 | WSGIProcessGroup OSMTM_process
101 | WSGIApplicationGroup %{GLOBAL}
102 |
103 |
104 | You may need to adpat the user, group and paths values.
105 |
106 | Create a new `OSMTM.wsgi` in your virtual env directory with the following::
107 |
108 | import sys
109 | sys.stdout = sys.stderr
110 |
111 | from pyramid.paster import get_app
112 | application = get_app(
113 | '/home/ubuntu/osm-tasking-manager/production.ini', 'main')
114 |
115 | You can then test config and restart Apache.
116 | Your application should be available at http://host.domain/OSMTM
117 |
118 |
119 | You may also need to enable CORS. See http://enable-cors.org.
120 | Add the following to your virtual hosts::
121 |
122 | Header set Access-Control-Allow-Origin "*"
123 |
124 | Styles
125 | ------
126 |
127 | The CSS stylesheet are compiled using less. Launch the following command as
128 | soon as you change the css::
129 |
130 | lessc OSMTM/static/css/main.less > OSMTM/static/css/main.less.min.css
131 |
132 | Run Tests
133 | ---------
134 |
135 | To ensure your build is working properly run the tests (in active virtual env)::
136 |
137 | nosetests
138 |
139 | Upgrade notes
140 | -------------
141 |
142 | Database versions are now managed using Alembic.
143 | The following commands should help upgrading the database.
144 |
145 | *Don't forget to make copies of your db file before running any upgrade.*::
146 |
147 | alembic upgrade head
148 |
149 | Note: Please contact the maintainer if you encounter problems.
150 |
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | OSMTM README
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/alembic.ini:
--------------------------------------------------------------------------------
1 | # A generic, single database configuration.
2 |
3 | [alembic]
4 | # path to migration scripts
5 | script_location = alembic
6 |
7 | # template used to generate migration files
8 | # file_template = %%(rev)s_%%(slug)s
9 |
10 | # set to 'true' to run the environment during
11 | # the 'revision' command, regardless of autogenerate
12 | # revision_environment = false
13 |
14 | sqlalchemy.url = sqlite:///%(here)s/OSMTM.db
15 |
16 | # Logging configuration
17 | [loggers]
18 | keys = root,sqlalchemy,alembic
19 |
20 | [handlers]
21 | keys = console
22 |
23 | [formatters]
24 | keys = generic
25 |
26 | [logger_root]
27 | level = WARN
28 | handlers = console
29 | qualname =
30 |
31 | [logger_sqlalchemy]
32 | level = WARN
33 | handlers =
34 | qualname = sqlalchemy.engine
35 |
36 | [logger_alembic]
37 | level = INFO
38 | handlers =
39 | qualname = alembic
40 |
41 | [handler_console]
42 | class = StreamHandler
43 | args = (sys.stderr,)
44 | level = NOTSET
45 | formatter = generic
46 |
47 | [formatter_generic]
48 | format = %(levelname)-5.5s [%(name)s] %(message)s
49 | datefmt = %H:%M:%S
50 |
--------------------------------------------------------------------------------
/alembic/README:
--------------------------------------------------------------------------------
1 | Generic single-database configuration.
--------------------------------------------------------------------------------
/alembic/env.py:
--------------------------------------------------------------------------------
1 | from __future__ import with_statement
2 | from alembic import context
3 | from sqlalchemy import engine_from_config, pool
4 | from logging.config import fileConfig
5 |
6 | # this is the Alembic Config object, which provides
7 | # access to the values within the .ini file in use.
8 | config = context.config
9 |
10 | # Interpret the config file for Python logging.
11 | # This line sets up loggers basically.
12 | fileConfig(config.config_file_name)
13 |
14 | # add your model's MetaData object here
15 | # for 'autogenerate' support
16 | # from myapp import mymodel
17 | # target_metadata = mymodel.Base.metadata
18 | from OSMTM.models import Base
19 | target_metadata = Base.metadata
20 |
21 | # other values from the config, defined by the needs of env.py,
22 | # can be acquired:
23 | # my_important_option = config.get_main_option("my_important_option")
24 | # ... etc.
25 |
26 | def run_migrations_offline():
27 | """Run migrations in 'offline' mode.
28 |
29 | This configures the context with just a URL
30 | and not an Engine, though an Engine is acceptable
31 | here as well. By skipping the Engine creation
32 | we don't even need a DBAPI to be available.
33 |
34 | Calls to context.execute() here emit the given string to the
35 | script output.
36 |
37 | """
38 | url = config.get_main_option("sqlalchemy.url")
39 | context.configure(url=url)
40 |
41 | with context.begin_transaction():
42 | context.run_migrations()
43 |
44 | def run_migrations_online():
45 | """Run migrations in 'online' mode.
46 |
47 | In this scenario we need to create an Engine
48 | and associate a connection with the context.
49 |
50 | """
51 | engine = engine_from_config(
52 | config.get_section(config.config_ini_section),
53 | prefix='sqlalchemy.',
54 | poolclass=pool.NullPool)
55 |
56 | connection = engine.connect()
57 | context.configure(
58 | connection=connection,
59 | target_metadata=target_metadata
60 | )
61 |
62 | try:
63 | with context.begin_transaction():
64 | context.run_migrations()
65 | finally:
66 | connection.close()
67 |
68 | if context.is_offline_mode():
69 | run_migrations_offline()
70 | else:
71 | run_migrations_online()
72 |
73 |
--------------------------------------------------------------------------------
/alembic/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision}
5 | Create Date: ${create_date}
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = ${repr(up_revision)}
11 | down_revision = ${repr(down_revision)}
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 | ${imports if imports else ""}
16 |
17 | def upgrade():
18 | ${upgrades if upgrades else "pass"}
19 |
20 |
21 | def downgrade():
22 | ${downgrades if downgrades else "pass"}
23 |
--------------------------------------------------------------------------------
/alembic/versions/173a7003596f_added_author_column.py:
--------------------------------------------------------------------------------
1 | """Added author column
2 |
3 | Revision ID: 173a7003596f
4 | Revises: 4fbba110ab8b
5 | Create Date: 2012-11-11 17:03:32.593702
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '173a7003596f'
11 | down_revision = '4fbba110ab8b'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.add_column('jobs', sa.Column('author', sa.Unicode(), nullable=True))
20 | ### end Alembic commands ###
21 |
22 |
23 | def downgrade():
24 | ### commands auto generated by Alembic - please adjust! ###
25 | op.drop_column('jobs', 'author')
26 | ### end Alembic commands ###
27 |
--------------------------------------------------------------------------------
/alembic/versions/183cfaae4493_adding_done_and_upda.py:
--------------------------------------------------------------------------------
1 | """Adding done and last_update columns
2 |
3 | Revision ID: 183cfaae4493
4 | Revises: None
5 | Create Date: 2012-10-25 08:11:03.811958
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '183cfaae4493'
11 | down_revision = None
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.add_column('jobs', sa.Column('done', sa.Integer(), nullable=True))
20 | op.add_column('jobs', sa.Column('last_update', sa.DateTime(), nullable=True))
21 | ### end Alembic commands ###
22 |
23 |
24 | def downgrade():
25 | ### commands auto generated by Alembic - please adjust! ###
26 | op.drop_column('jobs', 'last_update')
27 | op.drop_column('jobs', 'done')
28 | ### end Alembic commands ###
29 |
--------------------------------------------------------------------------------
/alembic/versions/2edcc95ff6f7_.py:
--------------------------------------------------------------------------------
1 | """empty message
2 |
3 | Revision ID: 2edcc95ff6f7
4 | Revises: 55009f9d3296
5 | Create Date: 2013-03-26 16:22:55.243858
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '2edcc95ff6f7'
11 | down_revision = '55009f9d3296'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | pass
19 |
20 |
21 | def downgrade():
22 | pass
23 |
--------------------------------------------------------------------------------
/alembic/versions/2faee55a9ed8_upgrade_after_workfl.py:
--------------------------------------------------------------------------------
1 | """Upgrade after workflow branch merge
2 |
3 | Revision ID: 2faee55a9ed8
4 | Revises: 5229d2fd908d
5 | Create Date: 2014-02-09 16:02:41.025040
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '2faee55a9ed8'
11 | down_revision = '5229d2fd908d'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 | from sqlalchemy import and_
16 |
17 | # we build a quick link for the current connection of alembic
18 | connection = op.get_bind()
19 |
20 | def upgrade():
21 | """ this script is made to upgrade data from 9 -> 10 """
22 | from sqlalchemy.engine import create_engine
23 | engine = create_engine('sqlite:///OSMTM.db')
24 | connection = engine.connect()
25 |
26 | from OSMTM.models import TileHistory, Tile, Job
27 | from OSMTM.history_meta import VersionedListener
28 | from sqlalchemy import orm
29 | from sqlalchemy.sql.expression import and_
30 |
31 | sm = orm.sessionmaker(bind=engine, autoflush=True, autocommit=False,
32 | expire_on_commit=True,
33 | extension=[VersionedListener()])
34 | session = orm.scoped_session(sm)
35 |
36 | jobs = session.query(Job).all()
37 |
38 | for job in jobs:
39 |
40 | print "job: %s" % job.id
41 |
42 | tiles = session.query(Tile).filter(and_(Tile.change==True, Tile.job_id==job.id))
43 | for tile in tiles:
44 | tile.change = False
45 | tile.username = None
46 | tile.comment = None
47 | session.add(tile)
48 |
49 | session.commit()
50 |
51 | pass
52 |
53 |
54 | def downgrade():
55 | pass
56 |
--------------------------------------------------------------------------------
/alembic/versions/41cd7451a8d7_adding_offset_fields.py:
--------------------------------------------------------------------------------
1 | """Adding offset fields
2 |
3 | Revision ID: 41cd7451a8d7
4 | Revises: 2faee55a9ed8
5 | Create Date: 2014-03-26 15:04:22.142334
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '41cd7451a8d7'
11 | down_revision = '2faee55a9ed8'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('jobs', sa.Column('imagery_offset_x', sa.Float(), default=0))
19 | op.add_column('jobs', sa.Column('imagery_offset_y', sa.Float(), default=0))
20 | pass
21 |
22 |
23 | def downgrade():
24 | op.drop_column('jobs', 'imagery_offset_x')
25 | op.drop_column('jobs', 'imagery_offset_y')
26 | pass
27 |
--------------------------------------------------------------------------------
/alembic/versions/4fbba110ab8b_adding_index_for_til.py:
--------------------------------------------------------------------------------
1 | """Adding index for tiles.job_id
2 |
3 | Revision ID: 4fbba110ab8b
4 | Revises: 183cfaae4493
5 | Create Date: 2012-10-25 22:12:38.516115
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4fbba110ab8b'
11 | down_revision = '183cfaae4493'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.create_index('tiles_job_id_ndx', 'tiles', ['job_id'])
20 | ### end Alembic commands ###
21 |
22 |
23 | def downgrade():
24 | ### commands auto generated by Alembic - please adjust! ###
25 | op.drop_index('tiles_job_id_ndx')
26 | ### end Alembic commands ###
27 |
--------------------------------------------------------------------------------
/alembic/versions/5229d2fd908d_adding_task_extra_co.py:
--------------------------------------------------------------------------------
1 | """Adding task_extra column
2 |
3 | Revision ID: 5229d2fd908d
4 | Revises: 2edcc95ff6f7
5 | Create Date: 2013-03-26 16:28:22.364216
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '5229d2fd908d'
11 | down_revision = '2edcc95ff6f7'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.add_column('jobs', sa.Column('task_extra', sa.Unicode(), nullable=True))
20 | ### end Alembic commands ###
21 |
22 |
23 | def downgrade():
24 | ### commands auto generated by Alembic - please adjust! ###
25 | op.drop_column('jobs', 'task_extra')
26 | ### end Alembic commands ###
27 |
--------------------------------------------------------------------------------
/alembic/versions/55009f9d3296_added_licenses_table.py:
--------------------------------------------------------------------------------
1 | """Added licenses table
2 |
3 | Revision ID: 55009f9d3296
4 | Revises: 173a7003596f
5 | Create Date: 2012-12-20 21:58:33.616095
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '55009f9d3296'
11 | down_revision = '173a7003596f'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 | from sqlalchemy import Unicode, Integer
16 | from sqlalchemy.sql import table, column
17 |
18 |
19 | def upgrade():
20 | ### commands auto generated by Alembic - please adjust! ###
21 | op.create_table('licenses',
22 | sa.Column('id', sa.Integer(), nullable=False),
23 | sa.Column('name', sa.Unicode(), nullable=True),
24 | sa.Column('description', sa.Unicode(), nullable=True),
25 | sa.Column('plain_text', sa.Unicode(), nullable=True),
26 | sa.PrimaryKeyConstraint('id')
27 | )
28 | op.add_column('jobs', sa.Column('license_id', sa.Integer(), nullable=True))
29 |
30 | op.create_table('users_licenses',
31 | sa.Column('user', sa.Integer(), nullable=True),
32 | sa.Column('license', sa.Integer(), nullable=True),
33 | sa.ForeignKeyConstraint(['license'], ['licenses.id'], ),
34 | sa.ForeignKeyConstraint(['user'], ['users.username'], ),
35 | sa.PrimaryKeyConstraint()
36 | )
37 | # cannot drop column with SQLite
38 | #op.drop_column('jobs', u'requires_nextview')
39 | ### end Alembic commands ###
40 |
41 | licenses = table('licenses',
42 | column('name'),
43 | column('description')
44 | )
45 | op.execute("INSERT INTO licenses (name, description, plain_text)\
46 | VALUES ('NextView', 'This data is licensed for use by the US Government (USG) under the NextView (NV) license and copyrighted by Digital Globe or GeoEye. The NV license allows the USG to share the imagery and Literal Imagery Derived Products (LIDP) with entities outside the USG when that entity is working directly with the USG, for the USG, or in a manner that is directly beneficial to the USG. The party receiving the data can only use the imagery or LIDP for the original purpose or only as otherwise agreed to by the USG. The party receiving the data cannot share the imagery or LIDP with a third party without express permission from the USG. At no time should this imagery or LIDP be used for other than USG-related purposes and must not be used for commercial gain. The copyright information should be maintained at all times. Your acceptance of these license terms is implied by your use.', 'In other words, you may only use NextView imagery linked from this site for digitizing OpenStreetMap data for humanitarian purposes.')")
47 | op.execute("UPDATE jobs SET license_id = 1 WHERE requires_nextview = 1")
48 | op.execute("INSERT INTO users_licenses (user, license) SELECT username, 1 FROM users WHERE accepted_nextview = 1")
49 |
--------------------------------------------------------------------------------
/development.ini:
--------------------------------------------------------------------------------
1 | [app:OSMTM]
2 | use = egg:OSMTM
3 | reload_templates = true
4 | debug_authorization = false
5 | debug_notfound = false
6 | debug_routematch = false
7 | debug_templates = true
8 | default_locale_name = en
9 | sqlalchemy.url = sqlite:///%(here)s/OSMTM.db
10 | session.type = file
11 | session.data_dir = %(here)s/data/sessions/data
12 | session.lock_dir = %(here)s/data/sessions/lock
13 | session.key = OSMQA
14 | session.secret = ae02254a597c45ca3f3b952183ad5f846f9b5b9a
15 | admin_user = pgiraud
16 |
17 | fanstatic.bottom = true
18 | fanstatic.debug = true
19 |
20 | pyramid.includes = pyramid_debugtoolbar
21 |
22 | [pipeline:main]
23 | pipeline =
24 | egg:WebError#evalerror
25 | tm
26 | OSMTM
27 |
28 | [filter:tm]
29 | use = egg:repoze.tm2#tm
30 | commit_veto = repoze.tm:default_commit_veto
31 |
32 | [server:main]
33 | use = egg:Paste#http
34 | host = 0.0.0.0
35 | port = 6543
36 |
37 | # Begin logging configuration
38 |
39 | [loggers]
40 | keys = root, OSMTM, sqlalchemy
41 |
42 | [handlers]
43 | keys = console
44 |
45 | [formatters]
46 | keys = generic
47 |
48 | [logger_root]
49 | level = INFO
50 | handlers = console
51 |
52 | [logger_OSMTM]
53 | level = DEBUG
54 | handlers =
55 | qualname = OSMTM
56 |
57 | [logger_sqlalchemy]
58 | level = WARN
59 | handlers =
60 | qualname = sqlalchemy.engine
61 | # "level = INFO" logs SQL queries.
62 | # "level = DEBUG" logs SQL queries and results.
63 | # "level = WARN" logs neither. (Recommended for production systems.)
64 |
65 | [handler_console]
66 | class = StreamHandler
67 | args = (sys.stderr,)
68 | level = NOTSET
69 | formatter = generic
70 |
71 | [formatter_generic]
72 | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
73 |
74 | # End logging configuration
75 |
--------------------------------------------------------------------------------
/migration/README:
--------------------------------------------------------------------------------
1 | This is a database migration repository.
2 |
3 | More information at
4 | http://code.google.com/p/sqlalchemy-migrate/
5 |
--------------------------------------------------------------------------------
/migration/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/migration/__init__.py
--------------------------------------------------------------------------------
/migration/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from migrate.versioning.shell import main
3 | main(debug='False')
4 |
--------------------------------------------------------------------------------
/migration/migrate.cfg:
--------------------------------------------------------------------------------
1 | [db_settings]
2 | # Used to identify which repository this database is versioned under.
3 | # You can use the name of your project.
4 | repository_id=OSMTM migration
5 |
6 | # The name of the database table used to track the schema version.
7 | # This name shouldn't already be used by your project.
8 | # If this is changed once a database is under version control, you'll need to
9 | # change the table name in each database too.
10 | version_table=migrate_version
11 |
12 | # When committing a change script, Migrate will attempt to generate the
13 | # sql for all supported databases; normally, if one of them fails - probably
14 | # because you don't have that database installed - it is ignored and the
15 | # commit continues, perhaps ending successfully.
16 | # Databases in this list MUST compile successfully during a commit, or the
17 | # entire commit will fail. List the databases your application will actually
18 | # be using to ensure your updates to that database work properly.
19 | # This must be a list; example: ['postgres','sqlite']
20 | required_dbs=[]
21 |
--------------------------------------------------------------------------------
/migration/versions/001_Adding_next_view_related_table_and_columns.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import *
2 | from migrate import *
3 | from OSMTM.models import Job
4 |
5 |
6 |
7 | def upgrade(migrate_engine):
8 | meta = MetaData()
9 | meta.bind = migrate_engine
10 |
11 | job_whitelists = Table('job_whitelists', meta,
12 | Column('job_id', Integer()),
13 | Column('user_id', String()),
14 | )
15 |
16 | jobs = Table('jobs', meta,
17 | Column('id', Integer(), primary_key=True, nullable=False),
18 | Column('title', String()),
19 | Column('description', String()),
20 | Column('geometry', String()),
21 | Column('workflow', String()),
22 | Column('zoom', Integer()),
23 | )
24 |
25 | users = Table('users', meta,
26 | Column('username', String(), primary_key=True, nullable=False),
27 | Column('role', Integer()),
28 | )
29 |
30 | job_whitelists.create()
31 | col = Column('imagery', String)
32 | col.create(jobs)
33 | col = Column('is_private', Boolean)
34 | col.create(jobs)
35 | col = Column('requires_nextview', Boolean)
36 | col.create(jobs)
37 | col = Column('accepted_nextview', Boolean)
38 | col.create(users)
39 | pass
40 |
41 | def downgrade(migrate_engine):
42 | meta = MetaData()
43 | meta.bind = migrate_engine
44 |
45 | job_whitelists = Table('job_whitelists', meta,
46 | Column('job_id', Integer()),
47 | Column('user_id', String()),
48 | )
49 |
50 | jobs = Table('jobs', meta,
51 | Column('id', Integer(), primary_key=True, nullable=False),
52 | Column('title', String()),
53 | Column('description', String()),
54 | Column('geometry', String()),
55 | Column('workflow', String()),
56 | Column('imagery', String()),
57 | Column('zoom', Integer()),
58 | Column('is_private', Integer()),
59 | Column('requires_nextview', Integer()),
60 | )
61 |
62 | users = Table('users', meta,
63 | Column('username', String(), primary_key=True, nullable=False),
64 | Column('role', Integer()),
65 | Column('accepted_nextview', Integer()),
66 | )
67 |
68 | job_whitelists.drop()
69 | jobs.c.imagery.drop()
70 | jobs.c.is_private.drop()
71 | jobs.c.requires_nextview.drop()
72 | users.c.accepted_nextview.drop()
73 | pass
74 |
--------------------------------------------------------------------------------
/migration/versions/002_Remove_role_column,_add_admin_column.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import *
2 | from migrate import *
3 |
4 | def upgrade(migrate_engine):
5 | meta = MetaData()
6 | meta.bind = migrate_engine
7 | users = Table('users', meta,
8 | Column('username', String(), primary_key=True, nullable=False),
9 | Column('role', Integer()),
10 | Column('accepted_nextview', Boolean()),
11 | )
12 | col = Column('admin', Boolean)
13 | col.create(users)
14 | col = Column('role', Integer)
15 | users.c.role.drop()
16 | pass
17 |
18 | def downgrade(migrate_engine):
19 | meta = MetaData()
20 | meta.bind = migrate_engine
21 | users = Table('users', meta,
22 | Column('username', String(), primary_key=True, nullable=False),
23 | Column('admin', Integer()),
24 | Column('accepted_nextview', Boolean()),
25 | )
26 | users.c.admin.drop()
27 | col = Column('role', Integer)
28 | col.create(users)
29 | pass
30 |
--------------------------------------------------------------------------------
/migration/versions/003_Add_short_description_column.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import *
2 | from migrate import *
3 |
4 | def upgrade(migrate_engine):
5 | meta = MetaData()
6 | meta.bind = migrate_engine
7 | jobs = Table('jobs', meta,
8 | Column('id', Integer(), primary_key=True, nullable=False),
9 | Column('title', String()),
10 | Column('description', String()),
11 | Column('geometry', String()),
12 | Column('workflow', String()),
13 | Column('imagery', String()),
14 | Column('zoom', Integer()),
15 | Column('is_private', Integer()),
16 | Column('requires_nextview', Integer()),
17 | )
18 | col = Column('short_description', String(), default='')
19 | col.create(jobs)
20 | pass
21 |
22 | def downgrade(migrate_engine):
23 | meta = MetaData()
24 | meta.bind = migrate_engine
25 | jobs = Table('jobs', meta,
26 | Column('id', Integer(), primary_key=True, nullable=False),
27 | Column('title', String()),
28 | Column('description', String()),
29 | Column('geometry', String()),
30 | Column('workflow', String()),
31 | Column('imagery', String()),
32 | Column('zoom', Integer()),
33 | Column('is_private', Integer()),
34 | Column('requires_nextview', Integer()),
35 | Column('short_description', String())
36 | )
37 | jobs.c.short_description.drop()
38 | pass
39 |
--------------------------------------------------------------------------------
/migration/versions/004_Change_checkout_column_to_update.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import *
2 | from migrate import *
3 |
4 | def upgrade(migrate_engine):
5 | meta = MetaData()
6 | meta.bind = migrate_engine
7 | tiles = Table('tiles', meta,
8 | Column('x', Integer, primary_key=True),
9 | Column('y', Integer, primary_key=True),
10 | Column('job_id', Integer, primary_key=True),
11 | Column('username', Unicode),
12 | Column('checkout', DateTime),
13 | Column('checkin', Integer),
14 | Column('comment', Unicode),
15 | Column('version', Integer),
16 | )
17 | tiles.c.checkout.alter(name='update')
18 |
19 | tiles_h = Table('tiles_history', meta,
20 | Column('x', Integer, primary_key=True),
21 | Column('y', Integer, primary_key=True),
22 | Column('job_id', Integer, primary_key=True),
23 | Column('username', Unicode),
24 | Column('checkout', DateTime),
25 | Column('checkin', Integer),
26 | Column('comment', Unicode),
27 | Column('version', Integer, primary_key=True),
28 | )
29 | tiles_h.c.checkout.alter(name='update')
30 | pass
31 |
32 | def downgrade(migrate_engine):
33 | meta = MetaData()
34 | meta.bind = migrate_engine
35 | tiles = Table('tiles', meta,
36 | Column('x', Integer, primary_key=True),
37 | Column('y', Integer, primary_key=True),
38 | Column('job_id', Integer, primary_key=True),
39 | Column('username', Unicode),
40 | Column('update', DateTime),
41 | Column('checkin', Integer),
42 | Column('comment', Unicode),
43 | Column('version', Integer),
44 | )
45 | tiles.c['update'].alter(name='checkout')
46 |
47 | tiles_h = Table('tiles_history', meta,
48 | Column('x', Integer, primary_key=True),
49 | Column('y', Integer, primary_key=True),
50 | Column('job_id', Integer, primary_key=True),
51 | Column('username', Unicode),
52 | Column('update', DateTime),
53 | Column('checkin', Integer),
54 | Column('comment', Unicode),
55 | Column('version', Integer, primary_key=True),
56 | )
57 | tiles_h.c['update'].alter(name='checkout')
58 | pass
59 |
--------------------------------------------------------------------------------
/migration/versions/005_Add_status_column_in_jobs_table.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import *
2 | from migrate import *
3 |
4 | def upgrade(migrate_engine):
5 | meta = MetaData()
6 | meta.bind = migrate_engine
7 | jobs = Table('jobs', meta,
8 | Column('id', Integer(), primary_key=True, nullable=False),
9 | Column('title', String()),
10 | Column('description', String()),
11 | Column('geometry', String()),
12 | Column('workflow', String()),
13 | Column('imagery', String()),
14 | Column('zoom', Integer()),
15 | Column('is_private', Integer()),
16 | Column('requires_nextview', Integer()),
17 | Column('short_description', String())
18 | )
19 | col = Column('status', Integer(), default=1)
20 | col.create(jobs)
21 | pass
22 |
23 | def downgrade(migrate_engine):
24 | meta = MetaData()
25 | meta.bind = migrate_engine
26 | jobs = Table('jobs', meta,
27 | Column('id', Integer(), primary_key=True, nullable=False),
28 | Column('title', String()),
29 | Column('description', String()),
30 | Column('geometry', String()),
31 | Column('workflow', String()),
32 | Column('imagery', String()),
33 | Column('zoom', Integer()),
34 | Column('is_private', Integer()),
35 | Column('requires_nextview', Integer()),
36 | Column('short_description', String()),
37 | Column('status', Integer())
38 | )
39 | jobs.c.status.drop()
40 | pass
41 |
--------------------------------------------------------------------------------
/migration/versions/006_Add_tags.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import *
2 | from migrate import *
3 | from OSMTM.models import *
4 |
5 | meta = MetaData()
6 | jobs = Table('jobs', meta,
7 | Column('id', Integer(), primary_key=True))
8 | tags = Table('tags', meta,
9 | Column('tag', String(), primary_key=True, nullable=False),
10 | )
11 |
12 | job_tags_table = Table('job_tags', meta,
13 | Column('job_id', Integer, ForeignKey('jobs.id')),
14 | Column('tag', Integer, ForeignKey('tags.tag'))
15 | )
16 |
17 | def upgrade(migrate_engine):
18 | meta.bind = migrate_engine
19 | tags.create()
20 | job_tags_table.create()
21 | pass
22 |
23 | def downgrade(migrate_engine):
24 | meta.bind = migrate_engine
25 | tags.drop()
26 | job_tags_table.drop()
27 | pass
28 |
--------------------------------------------------------------------------------
/migration/versions/007_Add_preset_column.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import *
2 | from migrate import *
3 | from OSMTM.models import *
4 |
5 | meta = MetaData()
6 |
7 | def upgrade(migrate_engine):
8 | meta.bind = migrate_engine
9 | jobs = Table('jobs', meta,
10 | Column('id', Integer(), primary_key=True, nullable=False),
11 | Column('title', String()),
12 | Column('description', String()),
13 | Column('geometry', String()),
14 | Column('workflow', String()),
15 | Column('imagery', String()),
16 | Column('zoom', Integer()),
17 | Column('is_private', Integer()),
18 | Column('requires_nextview', Integer()),
19 | Column('short_description', String()),
20 | Column('status', Integer()),
21 | )
22 | col = Column('josm_preset', String())
23 | col.create(jobs)
24 | pass
25 |
26 | def downgrade(migrate_engine):
27 | meta.bind = migrate_engine
28 | jobs = Table('jobs', meta,
29 | Column('id', Integer(), primary_key=True, nullable=False),
30 | Column('title', String()),
31 | Column('description', String()),
32 | Column('geometry', String()),
33 | Column('workflow', String()),
34 | Column('imagery', String()),
35 | Column('zoom', Integer()),
36 | Column('is_private', Integer()),
37 | Column('requires_nextview', Integer()),
38 | Column('short_description', String()),
39 | Column('status', Integer()),
40 | Column('josm_preset', String()),
41 | )
42 | jobs.c.josm_preset.drop()
43 | pass
44 |
--------------------------------------------------------------------------------
/migration/versions/008_Adding_new_change_column.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import *
2 | from migrate import *
3 |
4 | print "Also ensure that you correctly update your data using the script as well"
5 |
6 | def upgrade(migrate_engine):
7 | meta = MetaData()
8 | meta.bind = migrate_engine
9 | tiles = Table('tiles', meta,
10 | Column('x', Integer(), primary_key=True),
11 | Column('y', Integer(), primary_key=True),
12 | Column('job_id', Integer(), primary_key=True),
13 | Column('username', Unicode()),
14 | Column('update', DateTime()),
15 | Column('checkin', Integer()),
16 | Column('comment', Unicode()),
17 | Column('version', Integer()),
18 | )
19 |
20 | tiles_h = Table('tiles_history', meta,
21 | Column('x', Integer(), primary_key=True),
22 | Column('y', Integer(), primary_key=True),
23 | Column('job_id', Integer(), primary_key=True),
24 | Column('username', Unicode()),
25 | Column('update', DateTime()),
26 | Column('checkin', Integer()),
27 | Column('comment', Unicode()),
28 | Column('version', Integer(), primary_key=True),
29 | )
30 | col1 = Column('checkout', Boolean())
31 | col1.create(tiles)
32 | col2 = Column('change', Boolean())
33 | col2.create(tiles)
34 | col1 = Column('checkout', Boolean())
35 | col1.create(tiles_h)
36 | col2 = Column('change', Boolean())
37 | col2.create(tiles_h)
38 | pass
39 |
40 | def downgrade(migrate_engine):
41 | meta = MetaData()
42 | meta.bind = migrate_engine
43 | tiles = Table('tiles', meta,
44 | Column('x', Integer(), primary_key=True),
45 | Column('y', Integer(), primary_key=True),
46 | Column('job_id', Integer(), primary_key=True),
47 | Column('username', Unicode()),
48 | Column('update', DateTime()),
49 | Column('checkin', Integer()),
50 | Column('comment', Unicode()),
51 | Column('version', Integer()),
52 | Column('checkout', String()),
53 | Column('change', String()),
54 | )
55 |
56 | tiles_h = Table('tiles_history', meta,
57 | Column('x', Integer(), primary_key=True),
58 | Column('y', Integer(), primary_key=True),
59 | Column('job_id', Integer(), primary_key=True),
60 | Column('username', Unicode()),
61 | Column('update', DateTime()),
62 | Column('checkin', Integer()),
63 | Column('comment', Unicode()),
64 | Column('version', Integer(), primary_key=True),
65 | Column('checkout', String()),
66 | Column('change', String()),
67 | )
68 | tiles.c.checkout.drop()
69 | tiles.c.change.drop()
70 | tiles_h.c.checkout.drop()
71 | tiles_h.c.change.drop()
72 | pass
73 |
--------------------------------------------------------------------------------
/migration/versions/009_Adding_featured_attribute.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import *
2 | from migrate import *
3 | from OSMTM.models import *
4 |
5 |
6 | def upgrade(migrate_engine):
7 | meta = MetaData()
8 | meta.bind = migrate_engine
9 | jobs = Table('jobs', meta,
10 | Column('id', Integer(), primary_key=True, nullable=False),
11 | Column('title', String()),
12 | Column('description', String()),
13 | Column('geometry', String()),
14 | Column('workflow', String()),
15 | Column('imagery', String()),
16 | Column('zoom', Integer()),
17 | Column('is_private', Integer()),
18 | Column('requires_nextview', Integer()),
19 | Column('short_description', String()),
20 | Column('status', Integer()),
21 | Column('josm_preset', String()),
22 | )
23 | col = Column('featured', Boolean())
24 | col.create(jobs)
25 | pass
26 |
27 |
28 | def downgrade(migrate_engine):
29 | meta = MetaData()
30 | meta.bind = migrate_engine
31 | jobs = Table('jobs', meta,
32 | Column('id', Integer(), primary_key=True, nullable=False),
33 | Column('title', String()),
34 | Column('description', String()),
35 | Column('geometry', String()),
36 | Column('workflow', String()),
37 | Column('imagery', String()),
38 | Column('zoom', Integer()),
39 | Column('is_private', Integer()),
40 | Column('requires_nextview', Integer()),
41 | Column('short_description', String()),
42 | Column('status', Integer()),
43 | Column('josm_preset', String()),
44 | Column('featured', String())
45 | )
46 | jobs.c.featured.drop()
47 | pass
48 |
--------------------------------------------------------------------------------
/migration/versions/010_Move_zoom_from_job_to_task.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import *
2 | from migrate import *
3 |
4 | def upgrade(migrate_engine):
5 | meta = MetaData()
6 | meta.bind = migrate_engine
7 |
8 | tiles = Table('tiles', meta,
9 | Column('x', Integer(), primary_key=True),
10 | Column('y', Integer(), primary_key=True),
11 | Column('job_id', Integer(), primary_key=True),
12 | Column('username', Unicode()),
13 | Column('update', DateTime()),
14 | Column('checkin', Integer()),
15 | Column('comment', Unicode()),
16 | Column('version', Integer()),
17 | Column('checkout', String()),
18 | Column('change', String()),
19 | )
20 | col = Column('zoom', Integer(), server_default="1")
21 | col.create(tiles)
22 |
23 | cons = PrimaryKeyConstraint(tiles.c.x, tiles.c.y, tiles.c.job_id, col)
24 | cons.create()
25 |
26 | tiles_h = Table('tiles_history', meta,
27 | Column('x', Integer(), primary_key=True),
28 | Column('y', Integer(), primary_key=True),
29 | Column('job_id', Integer(), primary_key=True),
30 | Column('username', Unicode()),
31 | Column('update', DateTime()),
32 | Column('checkin', Integer()),
33 | Column('comment', Unicode()),
34 | Column('version', Integer(), primary_key=True),
35 | Column('checkout', String()),
36 | Column('change', String()),
37 | )
38 | col = Column('zoom', Integer(), server_default="1")
39 | col.create(tiles_h)
40 |
41 | cons = PrimaryKeyConstraint(tiles_h.c.x, tiles_h.c.y, tiles_h.c.job_id, col, tiles_h.c.version)
42 | cons.create()
43 | pass
44 |
45 |
46 | def downgrade(migrate_engine):
47 | # supporting downgrade is really a pain
48 | pass
49 |
--------------------------------------------------------------------------------
/migration/versions/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hotosm/osm-tasking-manager/03474f4590f6f000d0b05895aff5298227e013c5/migration/versions/__init__.py
--------------------------------------------------------------------------------
/migration/versions/data_upgrade_008.py:
--------------------------------------------------------------------------------
1 | """ this script is made to upgrade data from 7 -> 8 """
2 | from sqlalchemy.engine import create_engine
3 | engine = create_engine('sqlite:///OSMTM.db')
4 | connection = engine.connect()
5 |
6 | from OSMTM.models import TileHistory, Tile
7 | from sqlalchemy import orm
8 | from sqlalchemy.sql.expression import and_
9 |
10 | sm = orm.sessionmaker(bind=engine, autoflush=True, autocommit=False,
11 | expire_on_commit=True)
12 | session = orm.scoped_session(sm)
13 |
14 | tiles = session.query(Tile) \
15 | .all()
16 |
17 | def compare_checkin(old, new):
18 | # task done
19 | if old == 0 and new == 1:
20 | return 1
21 | # task validated
22 | if old == 1 and new == 2:
23 | return 2
24 | # task invalidated
25 | if old == 1 and new == 0:
26 | return 3
27 |
28 | for tile in tiles:
29 | filter = and_(TileHistory.x==tile.x, TileHistory.y==tile.y, TileHistory.job_id==tile.job_id)
30 | tiles_history = session.query(TileHistory) \
31 | .filter(filter) \
32 | .order_by(TileHistory.version) \
33 | .all()
34 |
35 | """ (re)initialize values """
36 | username = None
37 | update = None
38 | last_history = None
39 | for ndx, i in enumerate(tiles_history):
40 |
41 | if i.username:
42 | i.checkout = True
43 | session.add(i)
44 |
45 | for ndx, i in enumerate(tiles_history):
46 | print "%s %s %s %s %s %s" % (i.job_id, i.x, i.y, i.username, i.checkin, i.version)
47 |
48 | if ndx > 0:
49 | if i.checkout:
50 | username = i.username
51 | update = i.update
52 | checkin = i.checkin
53 |
54 | status = compare_checkin(checkin, i.checkin)
55 | if status is not None:
56 | i.change = True
57 | i.username = username
58 | i.update = i.update if i.update != None else update
59 | else:
60 | i.comment = None
61 |
62 | last_history = i
63 |
64 | """ now compare with current tile status """
65 | if last_history is not None:
66 | status = compare_checkin(last_history.checkin, tile.checkin)
67 | if status is not None:
68 | tile.change = True
69 | tile.username = username
70 | tile.update = tile.update if tile.update != None else update
71 |
72 | session.commit()
73 |
74 | """
75 | tiles_history = session.query(TileHistory) \
76 | .filter(TileHistory.job_id==job_id) \
77 | .order_by(TileHistory.x, TileHistory.y) \
78 | .all()
79 | user = None
80 | for ndx, i in enumerate(tiles_history):
81 | print "%s %s %s %s %s" % (str(i.x), str(i.y), i.username, i.checkin, i.version)
82 |
83 | if user is not None:
84 | status = compare_checkin(checkin, i.checkin)
85 | print "status %s " % status
86 |
87 | if i.username:
88 | user = i.username
89 |
90 | checkin = i.checkin
91 | if ndx < len(tiles_history) - 1 and tiles_history[ndx + 1].version == 1 or \
92 | ndx == len(tiles_history) - 1:
93 | tile = session.query(Tile) \
94 | .get((i.x, i.y, 9))
95 | if user is not None and tile is not None:
96 | status = compare_checkin(checkin, tile.checkin)
97 | print "status %s" % status
98 | checkin = 0
99 | user = None
100 | """
101 |
--------------------------------------------------------------------------------
/migration/versions/data_upgrade_010.py:
--------------------------------------------------------------------------------
1 | """ this script is made to upgrade data from 9 -> 10 """
2 | from sqlalchemy.engine import create_engine
3 | engine = create_engine('sqlite:///OSMTM.db')
4 | connection = engine.connect()
5 |
6 | from OSMTM.models import TileHistory, Tile, Job
7 | from sqlalchemy import orm
8 | from sqlalchemy.sql.expression import and_
9 |
10 | sm = orm.sessionmaker(bind=engine, autoflush=True, autocommit=False,
11 | expire_on_commit=True)
12 | session = orm.scoped_session(sm)
13 |
14 | jobs = session.query(Job).all()
15 |
16 | for job in jobs:
17 |
18 | print "job: %s" % job.id
19 | tiles_h = session.query(TileHistory) \
20 | .filter(TileHistory.job_id==job.id)
21 | for tile in tiles_h:
22 | tile.zoom = job.zoom
23 | session.add(tile)
24 |
25 | tiles = session.query(Tile) \
26 | .filter(Tile.job_id==job.id)
27 | for tile in tiles:
28 | tile.zoom = job.zoom
29 | session.add(tile)
30 |
31 | session.commit()
32 |
--------------------------------------------------------------------------------
/production.ini:
--------------------------------------------------------------------------------
1 | [app:OSMTM]
2 | use = egg:OSMTM
3 | reload_templates = false
4 | debug_authorization = false
5 | debug_notfound = false
6 | debug_routematch = false
7 | debug_templates = false
8 | default_locale_name = en
9 | sqlalchemy.url = sqlite:///%(here)s/OSMTM.db
10 | admin_user = pgiraud
11 |
12 | [filter:weberror]
13 | use = egg:WebError#error_catcher
14 | debug = false
15 | ;error_log =
16 | ;show_exceptions_in_wsgi_errors = true
17 | ;smtp_server = localhost
18 | ;error_email = janitor@example.com
19 | ;smtp_username = janitor
20 | ;smtp_password = "janitor's password"
21 | ;from_address = paste@localhost
22 | ;error_subject_prefix = "Pyramid Error"
23 | ;smtp_use_tls =
24 | ;error_message =
25 |
26 | [filter:tm]
27 | use = egg:repoze.tm2#tm
28 | commit_veto = repoze.tm:default_commit_veto
29 |
30 | [pipeline:main]
31 | pipeline =
32 | weberror
33 | tm
34 | OSMTM
35 |
36 | [server:main]
37 | use = egg:Paste#http
38 | host = 0.0.0.0
39 | port = 6543
40 |
41 | # Begin logging configuration
42 |
43 | [loggers]
44 | keys = root, OSMTM, sqlalchemy
45 |
46 | [handlers]
47 | keys = console
48 |
49 | [formatters]
50 | keys = generic
51 |
52 | [logger_root]
53 | level = WARN
54 | handlers = console
55 |
56 | [logger_OSMTM]
57 | level = WARN
58 | handlers =
59 | qualname = OSMTM
60 |
61 | [logger_sqlalchemy]
62 | level = WARN
63 | handlers =
64 | qualname = sqlalchemy.engine
65 | # "level = INFO" logs SQL queries.
66 | # "level = DEBUG" logs SQL queries and results.
67 | # "level = WARN" logs neither. (Recommended for production systems.)
68 |
69 | [handler_console]
70 | class = StreamHandler
71 | args = (sys.stderr,)
72 | level = NOTSET
73 | formatter = generic
74 |
75 | [formatter_generic]
76 | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
77 |
78 | # End logging configuration
79 |
--------------------------------------------------------------------------------
/scripts/delete.py:
--------------------------------------------------------------------------------
1 | #!env/bin/python
2 | __requires__ = 'OSMTM'
3 |
4 | import os
5 | import sys
6 | import transaction
7 |
8 | from sqlalchemy import create_engine
9 |
10 | from pyramid.paster import (
11 | get_appsettings,
12 | setup_logging,
13 | )
14 |
15 | from OSMTM.models import (
16 | DBSession,
17 | Job,
18 | Tile,
19 | TileHistory,
20 | Base,
21 | )
22 |
23 | from OSMTM.utils import *
24 |
25 | id = sys.argv[1]
26 |
27 | engine = create_engine('sqlite:///OSMTM.db')
28 | DBSession.configure(bind=engine)
29 | with transaction.manager:
30 | job = DBSession.query(Job).filter(Job.id==id).one()
31 | DBSession.delete(job)
32 |
--------------------------------------------------------------------------------
/scripts/update.py:
--------------------------------------------------------------------------------
1 | #!env/bin/python
2 | __requires__ = 'OSMTM'
3 |
4 | import os
5 | import sys
6 | import transaction
7 |
8 | from sqlalchemy import create_engine
9 |
10 | from pyramid.paster import (
11 | get_appsettings,
12 | setup_logging,
13 | )
14 |
15 | from OSMTM.models import (
16 | DBSession,
17 | Job,
18 | Tile,
19 | TileHistory,
20 | Base,
21 | )
22 |
23 | from OSMTM.utils import *
24 |
25 | engine = create_engine('sqlite:///OSMTM.db')
26 | DBSession.configure(bind=engine)
27 | with transaction.manager:
28 | tile = DBSession.query(Tile).filter(Tile.job_id==292, Tile.x==19372, Tile.y==17805).one()
29 | tile.checkin = 1
30 | DBSession.add(tile)
31 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [nosetests]
2 | match=^test
3 | nocapture=1
4 | cover-package=OSMTM
5 | with-coverage=1
6 | cover-erase=1
7 |
8 | [compile_catalog]
9 | directory = OSMTM/locale
10 | domain = OSMTM
11 | statistics = true
12 |
13 | [extract_messages]
14 | add_comments = TRANSLATORS:
15 | output_file = OSMTM/locale/OSMTM.pot
16 | width = 80
17 |
18 | [init_catalog]
19 | domain = OSMTM
20 | input_file = OSMTM/locale/OSMTM.pot
21 | output_dir = OSMTM/locale
22 |
23 | [update_catalog]
24 | domain = OSMTM
25 | input_file = OSMTM/locale/OSMTM.pot
26 | output_dir = OSMTM/locale
27 | previous = true
28 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | from setuptools import setup, find_packages
5 |
6 | here = os.path.abspath(os.path.dirname(__file__))
7 | README = open(os.path.join(here, 'README.txt')).read()
8 | CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
9 |
10 | requires = [
11 | 'pyramid==1.4',
12 | 'pyramid_beaker',
13 | 'pyramid_debugtoolbar',
14 | 'SQLAlchemy==0.7.9',
15 | 'Shapely',
16 | 'transaction',
17 | 'repoze.tm2>=1.0b1', # default_commit_veto
18 | 'zope.sqlalchemy',
19 | 'WebError',
20 | 'oauth2==1.2.0',
21 | 'imposm.parser',
22 | 'geojson==1.0.1',
23 | 'WebTest==2.0.9',
24 | 'sqlalchemy-migrate',
25 | 'markdown',
26 | 'nose',
27 | 'coverage',
28 | 'beautifulSoup',
29 | 'pyshp==1.1.4',
30 | 'papyrus==0.7',
31 | 'simplejson',
32 | 'alembic'
33 | ]
34 |
35 | if sys.version_info[:3] < (2,5,0):
36 | requires.append('pysqlite')
37 |
38 | setup(name='OSMTM',
39 | version='0.2.1',
40 | description='OSMTM',
41 | long_description=README + '\n\n' + CHANGES,
42 | classifiers=[
43 | "Programming Language :: Python",
44 | "Framework :: Pylons",
45 | "Topic :: Internet :: WWW/HTTP",
46 | "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
47 | ],
48 | author='',
49 | author_email='',
50 | url='',
51 | keywords='web wsgi bfg pylons pyramid',
52 | packages=find_packages(),
53 | include_package_data=True,
54 | zip_safe=False,
55 | test_suite='OSMTM',
56 | install_requires = requires,
57 | entry_points = """\
58 | [paste.app_factory]
59 | main = OSMTM:main
60 | """,
61 | paster_plugins=['pyramid'],
62 | )
63 |
64 |
--------------------------------------------------------------------------------