Beispiel #1
0
 def post(self, challenge_slug):
     challenge = get_challenge_or_404(challenge_slug, instance_type=False,
                                      abort_if_inactive=False)
     parser = reqparse.RequestParser()
     parser.add_argument('run', required=True,
                         help="Bulk inserts require a Run ID")
     parser.add_argument('tasks', type=JsonTasks, required=True,
                         help="Bulk inserts require tasks")
     run = args['run']
     tasks = args['tasks'].data
     results = []
     for t in tasks:
         task = Task(challenge.slug, t['id'])
         task.instruction = t['text']
         task.location = t['location']
         task.manifest = t['manifest']
         task.run = run
         results.append(marshal(task), task_fields)
         db.session.add(task)
         db.session.flush()
         action = Action(task.id, "created")
         db.session.add(action)
         # This is going to be a bit challenge specific
         action = Action(task.id, "available")
         db.session.add(action)
     db.session.commit()
     return jsonify(tasks=results)
def load_fixtures():
    """Load some fixture data into the database."""
    from maproulette.models import Task, Challenge, User
    from random import randrange, random

    # Create a user
    u = User()
    u.osm_id = 8909
    u.osm_username = '******'
    session.add(u)

    for x in range(1, 11):
        c = Challenge()
        c.name = "Challenge {}".format(x)
        c.instruction = "Challenge {} Instruction".format(x)
        session.add(c)

    for x in range(1, 10000):
        t = Task()
        t.challenge_id = randrange(1, 11)
        t.geometry = 'POINT({lon} {lat})'.format(lon=random() * 360 - 180,
                                                 lat=random() * 180 - 90)
        session.add(t)

    session.commit()
Beispiel #3
0
    def put(self, slug, identifier):
        """Create or update one task. By default, the
        geometry must be supplied as WKB, but this can
        be overridden by adding ?geoformat=geojson to
        the URL"""

        task_geometries = []

        # Get the posted data
        taskdata = json.loads(request.data)

        exists = task_exists(slug, identifier)

        app.logger.debug("taskdata: %s" % (taskdata,))

        # abort if the taskdata does not contain geometries and it's a new task
        if not 'geometries' in taskdata:
            if not exists:
                abort(400)
        else:
            # extract the geometries
            geometries = taskdata.pop('geometries')
            app.logger.debug("geometries: %s" % (geometries,))
            app.logger.debug("features: %s" % (geometries['features'],))

            # parse the geometries
            for feature in geometries['features']:
                app.logger.debug(feature)
                osmid = feature['properties'].get('osmid')
                shape = asShape(feature['geometry'])
                t = TaskGeometry(osmid, shape)
                task_geometries.append(t)

        # there's two possible scenarios:
        # 1.    An existing task gets an update, in that case
        #       we only need the identifier
        # 2.    A new task is inserted, in this case we need at
        #       least an identifier and encoded geometries.

        # now we check if the task exists
        if exists:
            # if it does, update it
            app.logger.debug('existing task')
            task = get_task_or_404(slug, identifier)
            if not task.update(taskdata, task_geometries):
                abort(400)
        else:
            # if it does not, create it
            app.logger.debug('new task')
            new_task = Task(slug, identifier)
            new_task.update(taskdata, task_geometries)
        return {}
Beispiel #4
0
def geojson_to_task(slug, feature):
    """converts one geojson feature to a task.
    This will only work in a limited number of cases, where:
    * The geometry is one single Point or Linestring feature,
    * The OSM ID of the feature is in the id field of the JSON,
    and you are OK with the following:
    * The description cannot be set at the task level
    * The identifier will be slug-osmid"""
    # check for id and geometries
    if 'id' not in feature:
        app.logger.debug('no id in this feature, skipping')
        return None
    if 'geometry' not in feature:
        app.logger.debug('no geometries in feature, skipping')
        return None
    # generate an identifier
    osmid = feature['id']
    identifier = '{slug}-{osmid}'.format(
        slug=slug,
        osmid=osmid)
    # find, or create a task
    task = Task.query.filter(
        Task.challenge_slug == slug).filter(
        Task.identifier == identifier).first()
    if task is None:
        task = Task(slug, identifier)
        app.logger.debug('creating task {identifier}'.format(
            identifier=identifier))
    else:
        app.logger.debug('updating task {identifier}'.format(
            identifier=identifier))
        # clear existing geometries
        task.geometries = []
    # get the geometry
    geom = feature['geometry']
    shape = asShape(geom)
    try:
        g = TaskGeometry(osmid, shape)
        task.geometries.append(g)
        return task
    except Exception as e:
        app.logger.debug("task could not be created, {}".format(e))
        return None
Beispiel #5
0
def json_to_task(slug, data, task=None, identifier=None):
    """Parse task json coming in through the admin api"""

    app.logger.debug(data)

    if identifier is None and task is None:
        if 'identifier' not in data:
            raise Exception('no identifier given')
        identifier = data['identifier']

    if task is None:
        # create the task if none was passed in
        try:
            task = Task(slug, identifier)
        except Exception as e:
            app.logger.warn(e)
            raise e
    else:
        # delete existing task geometries
        task.geometries = []

    # check for instruction
    if 'instruction' in data:
        task.instruction = data['instruction']
    # check for status
    if 'status' in data:
        task.status = data['status']

    # extract the task geometries
    if 'geometries' in data:
        geometries = data.pop('geometries')
        # parse the geometries
        app.logger.debug(geometries)
        for feature in geometries['features']:
            osmid = None
            if 'properties' in feature and feature['properties'].get('osmid'):
                osmid = feature['properties'].get('osmid')
            app.logger.debug(feature)
            shape = asShape(feature['geometry'])
            g = TaskGeometry(shape, osmid)
            task.geometries.append(g)
    return task
Beispiel #6
0
def json_to_task(slug, data, task=None, identifier=None):
    """Parse task json coming in through the admin api"""

    app.logger.debug(data)

    if identifier is None and task is None:
        if 'identifier' not in data:
            raise Exception('no identifier given')
        identifier = data['identifier']

    if task is None:
        # create the task if none was passed in
        try:
            task = Task(slug, identifier)
        except Exception as e:
            app.logger.warn(e)
            raise e
    else:
        # delete existing task geometries
        task.geometries = []

    # check for instruction
    if 'instruction' in data:
        task.instruction = data['instruction']
    # check for status
    if 'status' in data:
        task.status = data['status']

    # extract the task geometries
    if 'geometries' in data:
        geometries = data.pop('geometries')
        # parse the geometries
        app.logger.debug(geometries)
        for feature in geometries['features']:
            osmid = None
            if 'properties' in feature and feature['properties'].get('osmid'):
                osmid = feature['properties'].get('osmid')
            app.logger.debug(feature)
            shape = asShape(feature['geometry'])
            g = TaskGeometry(shape, osmid)
            task.geometries.append(g)
    return task
Beispiel #7
0
 def post(self, challenge_slug):
     challenge = get_challenge_or_404(challenge_slug, instance_type=False,
                                      abort_if_inactive=False)
     parser = reqparse.RequestParser()
     parser.add_argument('run', required=True,
                         help="Bulk inserts require a Run ID")
     parser.add_argument('tasks', type=JsonTasks, required=True,
                         help="Bulk inserts require tasks")
     run = args['run']
     tasks = args['tasks'].data
     results = []
     for t in tasks:
         task = Task(challenge.slug, t['id'])
         task.instruction = t['text']
         task.location = t['location']
         task.manifest = t['manifest']
         task.run = run
         results.append(marshal(task), task_fields)
         db.session.add(task)
         db.session.flush()
         action = Action(task.id, "created")
         db.session.add(action)
         # This is going to be a bit challenge specific
         action = Action(task.id, "available")
         db.session.add(action)
     db.session.commit()
     return jsonify(tasks=results)
Beispiel #8
0
def geojson_to_task(slug, feature):
    """converts one geojson feature to a task.
    This will only work in a limited number of cases, where:
    * The geometry is one single Point or Linestring feature,
    * The OSM ID of the feature is in the id field of the JSON,
    and you are OK with the following:
    * The description cannot be set at the task level
    * The identifier will be slug-osmid"""
    # check for id and geometries
    if 'id' not in feature:
        app.logger.debug('no id in this feature, skipping')
        return None
    if 'geometry' not in feature:
        app.logger.debug('no geometries in feature, skipping')
        return None
    # generate an identifier
    osmid = feature['id']
    identifier = '{slug}-{osmid}'.format(slug=slug, osmid=osmid)
    # find, or create a task
    task = Task.query.filter(Task.challenge_slug == slug).filter(
        Task.identifier == identifier).first()
    if task is None:
        task = Task(slug, identifier)
        app.logger.debug(
            'creating task {identifier}'.format(identifier=identifier))
    else:
        app.logger.debug(
            'updating task {identifier}'.format(identifier=identifier))
        # clear existing geometries
        task.geometries = []
    # get the geometry
    geom = feature['geometry']
    shape = asShape(geom)
    try:
        g = TaskGeometry(osmid, shape)
        task.geometries.append(g)
        return task
    except Exception, e:
        app.logger.debug("task could not be created, {}".format(e))
        return None
Beispiel #9
0
def load_sampledata(path):

    identifier = 0
    tasks = []
    actions = []
    c = Challenge('test')
    c.title = 'Just a test challenge'

    with open(path, 'rb') as filehandle:
        q = json.load(filehandle)

        for feature in q['features']:
            identifier += 1
            coordinates = feature['geometry']['coordinates']
            shape = Point(coordinates[0], coordinates[1])
            properties = feature['properties']
            t = Task('test',identifier)
            t.location = dumps(shape)
            t.run = 1
            a = Action(t.id, "created")
            tasks.append(t)
            
    print tasks
    
    feedengine = create_engine('postgresql://*****:*****@localhost/maproulette')

    Session = sessionmaker(bind=feedengine)

    session = Session()

    session.add(c)
    session.commit()
    for t in tasks:
        session.add(t)
    for a in actions:
        session.add(a)

    c.active = True
    session.commit()
Beispiel #10
0
def parse_task_json(data, slug, identifier, commit=True):
    """Parse task json coming in through the admin api"""

    task_geometries = []

    exists = task_exists(slug, identifier)

    # abort if the taskdata does not contain geometries and it's a new task
    if not 'geometries' in data:
        if not exists:
            abort(400)
    else:
        # extract the geometries
        geometries = data.pop('geometries')
        # parse the geometries
        for feature in geometries['features']:
            osmid = feature['properties'].get('osmid')
            shape = asShape(feature['geometry'])
            t = TaskGeometry(osmid, shape)
            task_geometries.append(t)

    # there's two possible scenarios:
    # 1.    An existing task gets an update, in that case
    #       we only need the identifier
    # 2.    A new task is inserted, in this case we need at
    #       least an identifier and encoded geometries.

    # now we check if the task exists
    if exists:
        # if it does, update it
        task = get_task_or_404(slug, identifier)
        if not task.update(data, task_geometries, commit=commit):
            abort(400)
    else:
        # if it does not, create it
        new_task = Task(slug, identifier)
        new_task.update(data, task_geometries, commit=commit)
    return True
Beispiel #11
0
 def put(self, challenge_slug, task_id):
     challenge = get_challenge_or_404(challenge_slug, instance_type=False,
                                      abort_if_inactive=False)
     task = Task.query(Task.identifier == task_id). \
         filter(Task.challenge_slug == challenge.slug).first()
     if task:
         action = Action(task.id, "modified")
         db.session.add(action)
     else:
         task = Task(challenge.slug, task_id)
         db.session.add(task)
         db.session.flush()
         action = Action(task.id, "created")
         db.session.add(action)
         action = Action(task.id, "available")
         db.session.add(action)
     parser = reqparse.RequestParser()
     parser.add_argument('run', required=True,
                         help="New tasks must include a Run ID")
     parser.add_argument('text', dest='instruction')
     parser.add_argument('location', type=GeoPoint,
                         help="Location must be in the form lon|lat")
     parser.add_argument('manifest', type=JsonData,
                         help="Manifest must be valid JSON")
     args = parser.parse_args()
     if request.form.get('run'):
         task['run'] = request.form['run']
     if request.form.get('text'):
         task['instruction'] = request.form['text']
     if request.form.get('location'):
         # WILL THIS WORK???
         task['location'] = request.form['location']
     if request.form.get('manifest'):
         task['manifest'] = request.form['manifest']
     # LET'S HOPE ADDING IT TWICE DOESN'T BREAK ANYTHING
     db.session.add(task)
     db.commit()
     return task
Beispiel #12
0
def json_to_task(slug, data, task=None, identifier=None):
    """Parse task json coming in through the admin api"""

    if identifier is None and task is None:
        if not 'identifier' in data:
            raise Exception('no identifier given')
        identifier = data['identifier']

    if task is None:
        # create the task if none was passed in
        try:
            task = Task(slug, identifier)
        except Exception, e:
            app.logger.warn(e.message)
            raise e
Beispiel #13
0
 def put(self, challenge_slug, task_id):
     challenge = get_challenge_or_404(challenge_slug, instance_type=False,
                                      abort_if_inactive=False)
     task = Task.query(Task.identifier == task_id). \
         filter(Task.challenge_slug == challenge.slug).first()
     if task:
         action = Action(task.id, "modified")
         db.session.add(action)
     else:
         task = Task(challenge.slug, task_id)
         db.session.add(task)
         db.session.flush()
         action = Action(task.id, "created")
         db.session.add(action)
         action = Action(task.id, "available")
         db.session.add(action)
     parser = reqparse.RequestParser()
     parser.add_argument('run', required=True,
                         help="New tasks must include a Run ID")
     parser.add_argument('text', dest='instruction')
     parser.add_argument('location', type=GeoPoint,
                         help="Location must be in the form lon|lat")
     parser.add_argument('manifest', type=JsonData,
                         help="Manifest must be valid JSON")
     args = parser.parse_args()
     if request.form.get('run'):
         task['run'] = request.form['run']
     if request.form.get('text'):
         task['instruction'] = request.form['text']
     if request.form.get('location'):
         # WILL THIS WORK???
         task['location'] = request.form['location']
     if request.form.get('manifest'):
         task['manifest'] = request.form['manifest']
     # LET'S HOPE ADDING IT TWICE DOESN'T BREAK ANYTHING
     db.session.add(task)
     db.commit()
     return task
Beispiel #14
0
def create_testdata(challenges=10, tasks=100, users=10):
    """Creates test data in the database"""
    import uuid
    import random
    from maproulette import db
    from maproulette.models import User, Challenge, Task, TaskGeometry, Action
    from shapely.geometry import Point, LineString, box

    # statuses to use
    statuses = ['available',
                'skipped',
                'fixed',
                'deleted',
                'alreadyfixed',
                'falsepositive']

    # challenge default strings
    challenge_help_test = "Sample challenge *help* text"
    challenge_instruction_test = "Challenge instruction text"
    task_instruction_text = "Task instruction text"

    # delete old tasks and challenges
    db.session.query(TaskGeometry).delete()
    db.session.query(Action).delete()
    db.session.query(Task).delete()
    db.session.query(Challenge).delete()
    db.session.query(User).delete()
    db.session.commit()

    # create users
    for uid in range(int(users)):
        user = User()
        user.id = uid
        user.display_name = 'Test User {uid}'.format(uid=uid)
        db.session.add(user)
    db.session.commit()

    # create ten challenges
    for i in range(1, int(challenges) + 1):
        print "Generating Test Challenge #%d" % i
        minx = -120
        maxx = -40
        miny = 20
        maxy = 50
        challengepoly = None
        slug = "test%d" % i
        title = "Test Challenge %d" % i
        challenge = Challenge(slug, title)
        challenge.difficulty = random.choice([1, 2, 3])
        challenge.active = True
        challenge.blurb = "This is test challenge number %d" % i
        challenge.description = "This describes challenge %d in detail" % i
        challenge.help = challenge_help_test
        challenge.instruction = challenge_instruction_test
        # have bounding boxes for all but the first two challenges.
        if i > 2:
            minx = random.randrange(-120, -40)
            miny = random.randrange(20, 50)
            maxx = minx + 1
            maxy = miny + 1
            challengepoly = box(minx, miny, maxx, maxy)
            print "\tChallenge has a bounding box of ", challengepoly
            challenge.polygon = challengepoly
        db.session.add(challenge)

        # add some tasks to the challenge
        print "\tGenerating %i tasks for challenge %i" % (int(tasks), i)
        # generate NUM_TASKS random tasks
        for j in range(int(tasks)):
            # generate a unique identifier
            identifier = str(uuid.uuid4())
            # create two random points not too far apart
            task_geometries = []
            p1 = Point(
                random.randrange(minx, maxx) + random.random(),
                random.randrange(miny, maxy) + random.random())
            p2 = Point(
                p1.x + (random.random() * random.choice((1, -1)) * 0.01),
                p1.y + (random.random() * random.choice((1, -1)) * 0.01))
            # create a linestring connecting the two points
            # no constructor for linestring from points?
            l1 = LineString([(p1.x, p1.y), (p2.x, p2.y)])
            # add the first point and the linestring to the task's geometries
            task_geometries.append(TaskGeometry(p1))
            # set a linestring for every other challenge
            if not j % 2:
                task_geometries.append(TaskGeometry(l1))
            # instantiate the task and register it with challenge 'test'
            # Initialize a task with its challenge slug and persistent ID
            task = Task(challenge.slug, identifier, task_geometries)
            # because we are not using the API, we need to call set_location
            # explicitly to set the task's location
            task.set_location()
            # generate random string for the instruction
            task.instruction = task_instruction_text
            # set a status
            action = Action(random.choice(statuses),
                            user_id=random.choice(range(int(users))))
            task.append_action(action)
            # add the task to the session
            db.session.add(task)

    # commit the generated tasks and the challenge to the database.
    db.session.commit()
Beispiel #15
0
        maxy = miny + 1
        challengepoly = box(minx, miny, maxx, maxy)
        print "\tChallenge has a bounding box of ", challengepoly
        challenge.polygon = challengepoly

    db.session.add(challenge)

    # add some tasks to the challenge
    print "\tGenerating %i tasks for challenge %i" % (NUM_TASKS, i)
    # generate NUM_TASKS random tasks
    for j in range(NUM_TASKS):
        # generate a unique identifier
        identifier = str(uuid.uuid4())
        # instantiate the task and register it with challenge 'test'
        # Initialize a task with its challenge slug and persistent ID
        task = Task(challenge.slug, identifier)
        # create two random points not too far apart
        p1 = Point(
            random.randrange(minx, maxx) + random.random(),
            random.randrange(miny, maxy) + random.random())
        p2 = Point(
            p1.x + (random.random() * random.choice((1, -1)) * 0.01),
            p1.y + (random.random() * random.choice((1, -1)) * 0.01))
        # create a linestring connecting the two points
        # no constructor for linestring from points?
        l1 = LineString([(p1.x, p1.y), (p2.x, p2.y)])
        # generate some random 'osm ids'
        osmids = [random.randrange(1000000, 1000000000) for _ in range(2)]
        # add the first point and the linestring to the task's geometries
        task.geometries.append(TaskGeometry(osmids[0], p1))
        task.geometries.append(TaskGeometry(osmids[1], l1))
Beispiel #16
0
def create_testdata(challenges=10, tasks=100, users=10):
    """Creates test data in the database"""
    import uuid
    import random
    from maproulette import db
    from maproulette.models import User, Challenge, Task, TaskGeometry, Action
    from shapely.geometry import Point, LineString, box

    # statuses to use
    statuses = ['available',
                'skipped',
                'fixed',
                'deleted',
                'alreadyfixed',
                'falsepositive']

    # challenge default strings
    challenge_help_test = "Sample challenge *help* text"
    challenge_instruction_test = "Challenge instruction text"
    task_instruction_text = "Task instruction text"

    # delete old tasks and challenges
    db.session.query(TaskGeometry).delete()
    db.session.query(Action).delete()
    db.session.query(Task).delete()
    db.session.query(Challenge).delete()
    db.session.query(User).delete()
    db.session.commit()

    # create users
    for uid in range(int(users)):
        user = User()
        user.id = uid
        user.display_name = 'Test User {uid}'.format(uid=uid)
        db.session.add(user)
    db.session.commit()

    # create ten challenges
    for i in range(1, int(challenges) + 1):
        print "Generating Test Challenge #%d" % i
        minx = -120
        maxx = -40
        miny = 20
        maxy = 50
        challengepoly = None
        slug = "test%d" % i
        title = "Test Challenge %d" % i
        challenge = Challenge(slug, title)
        challenge.difficulty = random.choice([1, 2, 3])
        challenge.active = True
        challenge.blurb = "This is test challenge number %d" % i
        challenge.description = "This describes challenge %d in detail" % i
        challenge.help = challenge_help_test
        challenge.instruction = challenge_instruction_test
        # have bounding boxes for all but the first two challenges.
        if i > 2:
            minx = random.randrange(-120, -40)
            miny = random.randrange(20, 50)
            maxx = minx + 1
            maxy = miny + 1
            challengepoly = box(minx, miny, maxx, maxy)
            print "\tChallenge has a bounding box of ", challengepoly
            challenge.polygon = challengepoly
        db.session.add(challenge)

        # add some tasks to the challenge
        print "\tGenerating %i tasks for challenge %i" % (int(tasks), i)
        # generate NUM_TASKS random tasks
        for j in range(int(tasks)):
            # generate a unique identifier
            identifier = str(uuid.uuid4())
            # create two random points not too far apart
            task_geometries = []
            p1 = Point(
                random.randrange(minx, maxx) + random.random(),
                random.randrange(miny, maxy) + random.random())
            p2 = Point(
                p1.x + (random.random() * random.choice((1, -1)) * 0.01),
                p1.y + (random.random() * random.choice((1, -1)) * 0.01))
            # create a linestring connecting the two points
            # no constructor for linestring from points?
            l1 = LineString([(p1.x, p1.y), (p2.x, p2.y)])
            # generate some random 'osm ids'
            osmids = [random.randrange(1000000, 1000000000) for _ in range(2)]
            # add the first point and the linestring to the task's geometries
            task_geometries.append(TaskGeometry(osmids[0], p1))
            # set a linestring for every other challenge
            if not j % 2:
                task_geometries.append(TaskGeometry(osmids[1], l1))
            # instantiate the task and register it with challenge 'test'
            # Initialize a task with its challenge slug and persistent ID
            task = Task(challenge.slug, identifier, task_geometries)
            # because we are not using the API, we need to call set_location
            # explicitly to set the task's location
            task.set_location()
            # generate random string for the instruction
            task.instruction = task_instruction_text
            # set a status
            action = Action(random.choice(statuses),
                            user_id=random.choice(range(int(users))))
            task.append_action(action)
            # add the task to the session
            db.session.add(task)

    # commit the generated tasks and the challenge to the database.
    db.session.commit()
Beispiel #17
0
def create_testdata():
    """Creates test data in the database"""
    import uuid
    import random
    from maproulette.models import db, Challenge, Task, TaskGeometry, Action
    from shapely.geometry import Point, LineString, box

    num_challenges = 10
    num_tasks = 100
    # the gettysburg address
    challenge_help_test = "Sample challenge *help* text"
    challenge_instruction_test = "Challenge instruction text"
    task_instruction_text = "Task instruction text"
    # delete old tasks and challenges
    db.session.query(TaskGeometry).delete()
    db.session.query(Action).delete()
    db.session.query(Task).delete()
    db.session.query(Challenge).delete()
    db.session.commit()
    for i in range(1, num_challenges + 1):
        print "Generating Test Challenge #%d" % i
        minx = -120
        maxx = -40
        miny = 20
        maxy = 50
        challengepoly = None
        slug = "test%d" % i
        title = "Test Challenge %d" % i
        challenge = Challenge(slug, title)
        challenge.difficulty = random.choice([1, 2, 3])
        challenge.active = True
        challenge.blurb = "This is test challenge number %d" % i
        challenge.description = "This describes test challenge %d in detail" % i
        challenge.help = challenge_help_test
        challenge.instruction = challenge_instruction_test
        # have bounding boxes for all but the first two challenges.
        if i > 2:
            minx = random.randrange(-120, -40)
            miny = random.randrange(20, 50)
            maxx = minx + 1
            maxy = miny + 1
            challengepoly = box(minx, miny, maxx, maxy)
            print "\tChallenge has a bounding box of ", challengepoly
            challenge.polygon = challengepoly
        db.session.add(challenge)

        # add some tasks to the challenge
        print "\tGenerating %i tasks for challenge %i" % (num_tasks, i)
        # generate NUM_TASKS random tasks
        for j in range(num_tasks):
            # generate a unique identifier
            identifier = str(uuid.uuid4())
            # instantiate the task and register it with challenge 'test'
            # Initialize a task with its challenge slug and persistent ID
            task = Task(challenge.slug, identifier)
            # create two random points not too far apart
            p1 = Point(random.randrange(minx, maxx) + random.random(), random.randrange(miny, maxy) + random.random())
            p2 = Point(
                p1.x + (random.random() * random.choice((1, -1)) * 0.01),
                p1.y + (random.random() * random.choice((1, -1)) * 0.01),
            )
            # create a linestring connecting the two points
            # no constructor for linestring from points?
            l1 = LineString([(p1.x, p1.y), (p2.x, p2.y)])
            # generate some random 'osm ids'
            osmids = [random.randrange(1000000, 1000000000) for _ in range(2)]
            # add the first point and the linestring to the task's geometries
            task.geometries.append(TaskGeometry(osmids[0], p1))
            task.geometries.append(TaskGeometry(osmids[1], l1))
            # and add the first point as the task's location
            task.location = p1
            # generate random string for the instruction
            task.instruction = task_instruction_text
            # add the task to the session
            db.session.add(task)

    # commit the generated tasks and the challenge to the database.
    db.session.commit()